main: basic organ with sine waves and one octave Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
A => README.md +7 -0
@@ 1,7 @@ ## Chipalee - Silly Chiptune Organ A silly little keyboard-driven software organ that I wrote to entertain my newborn daughter. It's named for her (Rosalee). See the source code for license details. There's more than one because I borrowed some code.
A => go.mod +29 -0
@@ 1,29 @@ module git.sr.ht/~whereswaldon/chipalee go 1.18 require ( gioui.org v0.0.0-20220517073035-79f037f983a7 github.com/hajimehoshi/oto/v2 v2.1.0 golang.org/x/exp v0.0.0-20220516143420-24438e51023a ) require ( gioui.org/cmd v0.0.0-20220508120214-35e56c5af956 // indirect gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect gioui.org/shader v1.0.6 // indirect github.com/akavel/rsrc v0.10.1 // indirect github.com/benoitkugler/textlayout v0.1.1 // indirect github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 // indirect github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b // indirect github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 // indirect github.com/shurcooL/go-goon v0.0.0-20210110234559-7585751d9a17 // indirect golang.org/x/exp/shiny v0.0.0-20220516143420-24438e51023a // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect )
A => go.sum +115 -0
@@ 1,115 @@ eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q= gioui.org v0.0.0-20220517073035-79f037f983a7 h1:ABouFbIw9IK+MBKiTWPr2R/XVfzu9hPVho1Sdv01Uac= gioui.org v0.0.0-20220517073035-79f037f983a7/go.mod h1:WHoHbUjH91BJS2xkfps2AhKxji+9o3xwfsphGsCBfnM= gioui.org/cmd v0.0.0-20220508120214-35e56c5af956 h1:01a14htOjgDQNe3DEdphpRlkd1O+c52kZT6wkbIrmgs= gioui.org/cmd v0.0.0-20220508120214-35e56c5af956/go.mod h1:AkJ8aiLLWhV2ZdteqnHuvMEunYJ3DPG2+48fDFXpt34= gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y= gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= github.com/akavel/rsrc v0.10.1 h1:hCCPImjmFKVNGpeLZyTDRHEFC283DzyTXTo0cO0Rq9o= github.com/akavel/rsrc v0.10.1/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= github.com/benoitkugler/textlayout v0.0.5/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8= github.com/benoitkugler/textlayout v0.1.1 h1:hizE/085xAeY8q7gwV00uHR2Q27KYB2g1HW+UacXl68= github.com/benoitkugler/textlayout v0.1.1/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 h1:1bjaB/5IIicfKpP4k0s30T2WEw//Kh00zULa8DQ0cxA= github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12/go.mod h1:kDhBRTA/i3H46PVdhqcw26TdGSIj42TOKNWKY+Kipnw= github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b h1:WINlj3ANt+CVrO2B4NGDHRlPvEWZPxjhb7z+JKypwXI= github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b/go.mod h1:ZNYu5saGoMOqtkVH5T8onTwhzenDUVszI+5WFHJRaxQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hajimehoshi/oto/v2 v2.1.0 h1:/h+UkbKzhD7xBHOQlWgKUplBPZ+J4DK3P2Y7g2UF1X4= github.com/hajimehoshi/oto/v2 v2.1.0/go.mod h1:9i0oYbpJ8BhVGkXDKdXKfFthX1JUNfXjeTp944W8TGM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20210110234559-7585751d9a17 h1:lRAUE0dIvigSSFAmaM2dfg7OH8T+a8zJ5smEh09a/GI= github.com/shurcooL/go-goon v0.0.0-20210110234559-7585751d9a17/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20220516143420-24438e51023a h1:tiLLxEjKNE6Hrah/Dp/cyHvsyjDLcMFSocOHO5XDmOM= golang.org/x/exp v0.0.0-20220516143420-24438e51023a/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/exp/shiny v0.0.0-20220516143420-24438e51023a h1:L9p3lDQCHRwXNyNo4kUbtxWVE0ksiBvWhLSQqCb2vAk= golang.org/x/exp/shiny v0.0.0-20220516143420-24438e51023a/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4= golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023 h1:0c3L82FDQ5rt1bjTBlchS8t6RQ6299/+5bWMnRLh+uI= golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
A => main.go +361 -0
@@ 1,361 @@ // Copyright 2019 The Oto Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "flag" "image/color" "io" "log" "math" "strings" "gioui.org/app" "gioui.org/font/gofont" "gioui.org/io/key" "gioui.org/io/system" "gioui.org/layout" "gioui.org/op" "gioui.org/widget" "gioui.org/widget/material" "github.com/hajimehoshi/oto/v2" "golang.org/x/exp/maps" ) /* Code starting here from the oto project under the terms of the Apache 2.0 license */ var ( sampleRate = flag.Int("samplerate", 44100, "sample rate") channelNum = flag.Int("channelnum", 2, "number of channel") bitDepthInBytes = flag.Int("bitdepthinbytes", 2, "bit depth in bytes") ) type SineWave struct { freq float64 length int64 pos int64 remaining []byte } func NewSineWave(freq float64) *SineWave { return &SineWave{ freq: freq, length: math.MaxInt, } } func (s *SineWave) Read(buf []byte) (int, error) { if len(s.remaining) > 0 { n := copy(buf, s.remaining) copy(s.remaining, s.remaining[n:]) s.remaining = s.remaining[:len(s.remaining)-n] return n, nil } if s.pos == s.length { return 0, io.EOF } eof := false if s.pos+int64(len(buf)) > s.length { buf = buf[:s.length-s.pos] eof = true } var origBuf []byte if len(buf)%4 > 0 { origBuf = buf buf = make([]byte, len(origBuf)+4-len(origBuf)%4) } length := float64(*sampleRate) / float64(s.freq) num := (*bitDepthInBytes) * (*channelNum) p := s.pos / int64(num) switch *bitDepthInBytes { case 1: for i := 0; i < len(buf)/num; i++ { const max = 127 b := int(math.Sin(2*math.Pi*float64(p)/length) * 0.3 * max) for ch := 0; ch < *channelNum; ch++ { buf[num*i+ch] = byte(b + 128) } p++ } case 2: for i := 0; i < len(buf)/num; i++ { const max = 32767 b := int16(math.Sin(2*math.Pi*float64(p)/length) * 0.3 * max) for ch := 0; ch < *channelNum; ch++ { buf[num*i+2*ch] = byte(b) buf[num*i+1+2*ch] = byte(b >> 8) } p++ } } s.pos += int64(len(buf)) n := len(buf) if origBuf != nil { n = copy(origBuf, buf) s.remaining = buf[n:] } if eof { return n, io.EOF } return n, nil } /* End of Apache 2.0 licensed code. */ const ( A4 = 440 AS4 = 466.2 BF4 = AS4 B4 = 493.9 C5 = 523.3 CS5 = 554.4 DF5 = CS5 D5 = 587.3 DS5 = 622.3 EF5 = DS5 E5 = 659.3 F5 = 698.5 FS5 = 740 GF5 = FS5 G5 = 784.0 GS5 = 830.6 AF5 = GS5 A5 = 880 AS5 = 932.3 BF5 = AS5 B5 = 987.8 IdxA4 = iota IdxAS4 IdxBF4 IdxB4 IdxC5 IdxCS5 IdxDF5 IdxD5 IdxDS5 IdxEF5 IdxE5 IdxF5 IdxFS5 IdxGF5 IdxG5 IdxGS5 IdxAF5 IdxA5 IdxAS5 IdxBF5 IdxB5 ) var freqs = []float64{ IdxA4: A4, IdxAS4: AS4, IdxBF4: BF4, IdxB4: B4, IdxC5: C5, IdxCS5: CS5, IdxDF5: DF5, IdxD5: D5, IdxDS5: DS5, IdxEF5: EF5, IdxE5: E5, IdxF5: F5, IdxFS5: FS5, IdxGF5: GF5, IdxG5: G5, IdxGS5: GS5, IdxAF5: AF5, IdxA5: A5, IdxAS5: AS5, IdxBF5: BF5, IdxB5: B5, } var notes = [][]int{ IdxA4: {IdxA4}, IdxB4: {IdxB4}, IdxC5: {IdxC5}, IdxD5: {IdxD5}, IdxE5: {IdxE5}, IdxF5: {IdxF5}, IdxG5: {IdxG5}, IdxA5: {IdxA5}, IdxB5: {IdxB5}, } var halfStepUp = [][]int{ IdxA4: {IdxA4 + 1}, IdxB4: {IdxB4 + 1}, IdxC5: {IdxC5 + 1}, IdxD5: {IdxD5 + 1}, IdxE5: {IdxE5 + 1}, IdxF5: {IdxF5 + 1}, IdxG5: {IdxG5 + 1}, IdxA5: {IdxA5 + 1}, IdxB5: {IdxB5 + 1}, } var majChords = [][]int{ IdxA4: {IdxA4, IdxC5, IdxE5}, IdxB4: {IdxB4, IdxD5, IdxF5}, IdxC5: {IdxC5, IdxE5, IdxG5}, IdxD5: {IdxD5, IdxF5, IdxA4}, IdxE5: {IdxE5, IdxG5, IdxB4}, IdxF5: {IdxF5, IdxA4, IdxC5}, IdxG5: {IdxG5, IdxB4, IdxD5}, } var majExtChords = [][]int{ IdxA4: {IdxA4, IdxC5, IdxE5, IdxG5}, IdxB4: {IdxB4, IdxD5, IdxF5, IdxA4}, IdxC5: {IdxC5, IdxE5, IdxG5, IdxB4}, IdxD5: {IdxD5, IdxF5, IdxA4, IdxC5}, IdxE5: {IdxE5, IdxG5, IdxB4, IdxD5}, IdxF5: {IdxF5, IdxA4, IdxC5, IdxE5}, IdxG5: {IdxG5, IdxB4, IdxD5, IdxF5}, } var keyToIdx = map[string]int{ "A": IdxA4, "S": IdxB4, "D": IdxC5, "F": IdxD5, "G": IdxE5, "H": IdxF5, "J": IdxG5, "K": IdxA5, "L": IdxB5, } func keySet() key.Set { baseKeys := maps.Keys(keyToIdx) chords := []string{} chords = append(chords, baseKeys...) for _, key := range baseKeys { chords = append(chords, "Shift-"+key) chords = append(chords, "Ctrl-"+key) chords = append(chords, "Alt-"+key) } return key.Set(strings.Join(chords, "|")) } func run(w *app.Window) error { log.Println("run start") c, ready, err := oto.NewContext(*sampleRate, *channelNum, *bitDepthInBytes) if err != nil { return err } players := make([]oto.Player, len(freqs)) toggleIndex := func(idx int, event key.Event) { if idx >= len(players) { return } if event.State == key.Press { players[idx].Play() } else { players[idx].Pause() } } keySet := keySet() th := material.NewTheme(gofont.Collection()) audioInit := false var ops op.Ops var ed widget.Editor for { select { case event := <-w.Events(): switch event := event.(type) { case system.DestroyEvent: return event.Err case system.FrameEvent: gtx := layout.NewContext(&ops, event) if !audioInit { material.Loader(th).Layout(gtx) layout.Center.Layout(gtx, material.Body1(th, "Loading...\nYou may need to click here for loading to complete.").Layout) } else { for _, event := range gtx.Events(w) { switch event := event.(type) { default: log.Printf("%#+v", event) case key.Event: idx, ok := keyToIdx[event.Name] if !ok { continue } var playingFrom [][]int switch { case event.Modifiers.Contain(key.ModShift): playingFrom = majChords case event.Modifiers.Contain(key.ModCtrl): playingFrom = majExtChords case event.Modifiers.Contain(key.ModAlt): playingFrom = halfStepUp default: playingFrom = notes } if idx >= len(playingFrom) { continue } for _, idx := range playingFrom[idx] { toggleIndex(idx, event) } } } key.InputOp{ Tag: w, Keys: keySet, }.Add(gtx.Ops) } // This editor is needed for JS to properly deliver key events // to us. med := material.Editor(th, &ed, "") med.Color = color.NRGBA{} ed.SetText("") med.Layout(gtx) event.Frame(gtx.Ops) } case <-ready: ready = nil audioInit = true for idx, f := range freqs { players[idx] = c.NewPlayer(NewSineWave(f)) } log.Println("organ initialized") } } return nil } func main() { flag.Parse() if err := run(app.NewWindow(app.Title("Chipalee - Silly Chiptune Organ"))); err != nil { panic(err) } app.Main() }