├── .travis.yml ├── LICENSE ├── README.md ├── chords └── main.go ├── euclidean ├── euclidean.go └── euclidean_test.go ├── examples ├── offline │ ├── main.go │ └── stereo │ │ └── main.go └── realtime │ └── main.go ├── generator.go ├── generator_test.go ├── go.mod ├── go.sum ├── osc.go └── osc_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.10.x" 4 | - tip 5 | sudo: false 6 | addons: 7 | apt: 8 | packages: 9 | - portaudio19-dev 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 Matt Aimonetti 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator 2 | A PCM generator using the go-audio interface 3 | 4 | [![GoDoc](http://godoc.org/github.com/go-audio/generator?status.svg)](http://godoc.org/github.com/go-audio/generator) 5 | [![Build Status](https://travis-ci.org/go-audio/generator.svg)](https://travis-ci.org/go-audio/generator) 6 | 7 | Checkout the [examples](https://github.com/go-audio/generator/tree/master/examples) to get an idea of how to use the generator. 8 | -------------------------------------------------------------------------------- /chords/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // this generator example outputs a MIDI file using generated chords. 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "log" 9 | "math/rand" 10 | "os" 11 | "strings" 12 | "time" 13 | 14 | "github.com/go-audio/generator/euclidean" 15 | "github.com/go-audio/midi" 16 | "github.com/go-audio/music/theory" 17 | ) 18 | 19 | var ( 20 | ppq uint32 = 480 21 | flagFromFreq = flag.Float64("freq", 0, "Use this frequency as the root of the generated data") 22 | flagFromNote = flag.String("root", "", "Root note to use") 23 | flagOctave = flag.Int("octave", 3, "Default octave to start from") 24 | flagIsMinor = flag.Bool("minor", false, "should we use a minor scale") 25 | ) 26 | 27 | func main() { 28 | flag.Parse() 29 | f, err := os.Create("output.mid") 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | rand.Seed(int64(time.Now().Nanosecond())) 35 | 36 | enc := midi.NewEncoder(f, midi.SingleTrack, uint16(ppq)) 37 | tr := enc.NewTrack() 38 | tr.AddAfterDelta(0, midi.CopyrightEvent("Generated by Go-Audio")) 39 | 40 | defer func() { 41 | tr.AddAfterDelta(0, midi.EndOfTrack()) 42 | if err := enc.Write(); err != nil { 43 | log.Fatal(err) 44 | } 45 | f.Close() 46 | fmt.Println("output.mid generated for you") 47 | }() 48 | 49 | // generate a chord progression 50 | 51 | // scale 52 | var scale theory.ScaleName 53 | if *flagIsMinor { 54 | scale = theory.NaturalMinorScale 55 | } else { 56 | scale = theory.MajorScale 57 | } 58 | 59 | var rootInt int 60 | if *flagFromNote != "" { 61 | rootInt = midi.NotesToInt[strings.ToUpper(*flagFromNote)] 62 | } else if *flagFromFreq != 0 { 63 | rootInt = midi.FreqToNote(*flagFromFreq) 64 | } else { 65 | fmt.Println("You didn't pass a key root so we are picking up random one on your behalf.") 66 | // random root 67 | rootInt = rand.Intn(12) 68 | } 69 | 70 | root := midi.Notes[rootInt] 71 | scaleName := fmt.Sprintf("%s %s scale", root, scale) 72 | fmt.Println(scaleName) 73 | keys, notes := theory.ScaleNotes(root, scale) 74 | fmt.Println("Notes in scale:", notes) 75 | tr.SetName(fmt.Sprintf("%s %v", scaleName, notes)) 76 | octaveBump := 12 * (*flagOctave + 1) 77 | // move to the target octave 78 | for i := 0; i < len(keys); i++ { 79 | keys[i] += octaveBump 80 | } 81 | 82 | var progression []int 83 | if scale == theory.MajorScale { 84 | n := rand.Intn(len(theory.MajorProgressions)) 85 | progression = theory.MajorProgressions[n] 86 | } else { 87 | n := rand.Intn(len(theory.MinorProgressions)) 88 | progression = theory.MinorProgressions[n] 89 | } 90 | 91 | // TODO: also write to own file 92 | playScale(tr, root, scale) 93 | fmt.Println() 94 | playProgression(tr, root, scale, progression) 95 | fmt.Println() 96 | playScaleChords(tr, root, scale) 97 | // euclideanImprov(tr, root, keys) 98 | } 99 | 100 | func euclideanImprov(tr *midi.Track, root string, keys []int) { 101 | var ( 102 | isAPressed bool 103 | isBPressed bool 104 | wasAPressed bool 105 | wasBPressed bool 106 | ) 107 | 108 | var key int 109 | key = midi.KeyInt(root, 3) 110 | key2 := keys[rand.Intn(len(keys))] 111 | // let's play 4 bars 112 | for i := 0; i < 4; i++ { 113 | beatsA := euclidean.Rhythm(rand.Intn(16), 16) 114 | beatsB := euclidean.Rhythm(rand.Intn(16), 16) 115 | var delta uint32 116 | for i, on := range beatsA { 117 | wasAPressed = isAPressed 118 | wasBPressed = isBPressed 119 | isAPressed = false 120 | isBPressed = false 121 | if on { 122 | isAPressed = true 123 | tr.AddAfterDelta(delta, midi.NoteOn(1, key, 99)) 124 | delta = 0 125 | } 126 | if beatsB[i] { 127 | isBPressed = true 128 | tr.AddAfterDelta(delta, midi.NoteOn(1, key2, 99)) 129 | delta = 0 130 | } 131 | 132 | if wasAPressed && !isAPressed { 133 | tr.AddAfterDelta(delta, midi.NoteOff(1, key)) 134 | delta = 0 135 | } 136 | if wasBPressed && !isBPressed { 137 | tr.AddAfterDelta(delta, midi.NoteOff(1, key2)) 138 | delta = 0 139 | } 140 | if isAPressed || isBPressed || wasAPressed || wasBPressed { 141 | delta = ppq / 4 142 | continue 143 | } 144 | 145 | delta += ppq / 4 146 | } 147 | } 148 | } 149 | 150 | func playScaleChords(tr *midi.Track, root string, scale theory.ScaleName) { 151 | fmt.Println("Chords in scale") 152 | var timeBuffer uint32 153 | keys, _ := theory.ScaleNotes(root, scale) 154 | octaveBump := 12 * 2 155 | 156 | start := keys[0] + octaveBump 157 | // move to the 3rd octave 158 | for i := 0; i < len(keys); i++ { 159 | keys[i] += octaveBump 160 | // keep going up 161 | if keys[i] < start { 162 | keys[i] += 12 163 | } 164 | } 165 | 166 | var firstChord *theory.Chord 167 | 168 | for chordTypeIDX := 0; chordTypeIDX < len(theory.ScaleChords[scale][0]); chordTypeIDX++ { 169 | fmt.Println("Chord Type", chordTypeIDX) 170 | scaleChords := theory.ScaleChords[scale] 171 | // play all chords in the scale 172 | for i, roman := range theory.RomanNumerals[scale] { 173 | if i > len(scaleChords) || chordTypeIDX > len(scaleChords[i]) { 174 | fmt.Println(roman, "not found") 175 | continue 176 | } 177 | chordName := fmt.Sprintf("%s%s\n", 178 | midi.Notes[keys[i]%12], 179 | scaleChords[i][chordTypeIDX]) 180 | 181 | fmt.Printf("%s\t%s", roman, chordName) 182 | c := theory.NewChordFromAbbrev(chordName) 183 | if i == 0 { 184 | firstChord = c 185 | } 186 | if c == nil { 187 | fmt.Println("failed to find chord named", chordName) 188 | continue 189 | } 190 | 191 | for i, k := range c.Keys { 192 | tr.AddAfterDelta(timeBuffer, midi.NoteOn(1, k+octaveBump, 99)) 193 | if i == 0 { 194 | timeBuffer = 0 195 | } 196 | } 197 | for i, k := range c.Keys { 198 | if i == 0 { 199 | timeBuffer = ppq * 2 200 | } 201 | tr.AddAfterDelta(timeBuffer, midi.NoteOff(1, k+octaveBump)) 202 | if i == 0 { 203 | timeBuffer = 0 204 | } 205 | } 206 | } 207 | 208 | // back to the first chord 209 | for i, k := range firstChord.Keys { 210 | if i == 0 { 211 | timeBuffer = ppq * 2 212 | } 213 | tr.AddAfterDelta(0, midi.NoteOn(1, k+24, 99)) 214 | } 215 | for i, k := range firstChord.Keys { 216 | if i == 0 { 217 | timeBuffer = ppq * 2 218 | } 219 | tr.AddAfterDelta(timeBuffer, midi.NoteOff(1, k+24)) 220 | if i == 0 { 221 | timeBuffer = 0 222 | } 223 | } 224 | } 225 | } 226 | 227 | func playScale(tr *midi.Track, root string, scale theory.ScaleName) { 228 | keys, _ := theory.ScaleNotes(root, scale) 229 | octaveBump := 12 * 4 230 | 231 | start := keys[0] + octaveBump 232 | // move to the 3rd octave 233 | for i := 0; i < len(keys); i++ { 234 | keys[i] += octaveBump 235 | // keep going up 236 | if keys[i] < start { 237 | keys[i] += 12 238 | } 239 | } 240 | 241 | for _, k := range keys { 242 | tr.AddAfterDelta(0, midi.NoteOn(1, k, 99)) 243 | tr.AddAfterDelta(ppq, midi.NoteOff(1, k)) 244 | } 245 | 246 | // back to the first note 247 | tr.AddAfterDelta(0, midi.NoteOn(1, keys[0]+12, 99)) 248 | tr.AddAfterDelta(ppq, midi.NoteOff(1, keys[0]+12)) 249 | } 250 | 251 | func playProgression(tr *midi.Track, root string, scale theory.ScaleName, progression []int) { 252 | var timeBuffer uint32 253 | octaveBump := 12 * 2 254 | keys, _ := theory.ScaleNotes(root, scale) 255 | var c *theory.Chord 256 | 257 | // play the same chord twice 258 | repeatedChords := func(rate uint32) func() { 259 | return func() { 260 | repeats := int((ppq / rate) * 2) 261 | timeBuffer = 0 262 | for n := 0; n < repeats; n++ { 263 | // note on 264 | for i, k := range c.Keys { 265 | tr.AddAfterDelta(timeBuffer, midi.NoteOn(1, k+octaveBump, 99)) 266 | if i == 0 { 267 | timeBuffer = 0 268 | } 269 | } 270 | // note off 271 | for i, k := range c.Keys { 272 | if i == 0 { 273 | timeBuffer = rate 274 | } 275 | tr.AddAfterDelta(timeBuffer, midi.NoteOff(1, k+octaveBump)) 276 | if i == 0 { 277 | timeBuffer = 0 278 | } 279 | } 280 | } 281 | } 282 | } 283 | 284 | // play the same chord twice once at a lower octave then up 285 | downUp := func(rate uint32) func() { 286 | return func() { 287 | repeats := int((ppq / rate) * 4) 288 | timeBuffer = 0 289 | for n := 0; n < repeats; n++ { 290 | bump := octaveBump + ((n % 2) * 12) 291 | // note on 292 | for i, k := range c.Keys { 293 | if i == 0 { 294 | k -= 12 295 | } 296 | tr.AddAfterDelta(timeBuffer, midi.NoteOn(1, k+bump, 99)) 297 | if i == 0 { 298 | timeBuffer = 0 299 | if n%2 == 0 { 300 | break 301 | } 302 | } 303 | } 304 | // note off 305 | for i, k := range c.Keys { 306 | if i == 0 { 307 | timeBuffer = rate 308 | k -= 12 309 | } 310 | tr.AddAfterDelta(timeBuffer, midi.NoteOff(1, k+bump)) 311 | if i == 0 { 312 | timeBuffer = 0 313 | if n%2 == 0 { 314 | break 315 | } 316 | } 317 | } 318 | } 319 | } 320 | } 321 | 322 | arps := []func(){ 323 | repeatedChords(ppq * 2), 324 | repeatedChords(ppq), 325 | downUp(ppq), 326 | downUp(ppq / 2)} 327 | 328 | fmt.Println("Chord progression:") 329 | for i, fn := range arps { 330 | for _, k := range progression { 331 | // testing using triad vs 7th 332 | var chordType int 333 | if k%2 == 0 { 334 | chordType = 1 335 | } 336 | chordName := fmt.Sprintf("%s%s\n", 337 | midi.Notes[keys[k]%12], 338 | theory.ScaleChords[scale][k][chordType]) 339 | 340 | if i == 0 { 341 | fmt.Printf("%s\t%s", theory.RomanNumerals[scale][k], chordName) 342 | } 343 | c = theory.NewChordFromAbbrev(chordName) 344 | if c == nil { 345 | fmt.Println("Couldn't find chord", chordName) 346 | continue 347 | } 348 | 349 | fn() 350 | } 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /euclidean/euclidean.go: -------------------------------------------------------------------------------- 1 | // Euclidean package implements the Euclidean Algorithm to generate traditional 2 | // musical rhythms. This popular rhythm approach was defined by Godfried 3 | // Toussaint in 2005 4 | // https://en.wikipedia.org/wiki/Euclidean_rhythm 5 | package euclidean 6 | 7 | // Rhythm returns a rhythmical pattern of equally distributed accents throughout 8 | // the total steps. The Euclidean rhythms are explained in this white paper: 9 | // http://cgm.cs.mcgill.ca/~godfried/publications/banff.pdf 10 | // The total steps are the steps in a grid (or a circle) and the accents is the 11 | // number of those steps you want to be triggered. The algorithm will position 12 | // the accents (aka pulses) equally distributed across the available steps. 13 | func Rhythm(accents, totalSteps int) []bool { 14 | var pattern []bool 15 | if totalSteps <= 0 { 16 | return []bool{} 17 | } 18 | if accents <= 0 { 19 | return make([]bool, totalSteps) 20 | } 21 | if accents > totalSteps { 22 | pattern = make([]bool, totalSteps) 23 | // we can't have more accent steps than total steps 24 | // so we just set all steps as accented 25 | for i := range pattern { 26 | pattern[i] = true 27 | } 28 | return pattern 29 | } 30 | 31 | a := make([][]bool, accents) 32 | aLen := len(a) 33 | for i := 0; i < aLen; i++ { 34 | a[i] = []bool{true} 35 | } 36 | b := make([][]bool, totalSteps-accents) 37 | for i := 0; i < len(b); i++ { 38 | b[i] = []bool{false} 39 | } 40 | 41 | minLen := min(aLen, len(b)) 42 | thresh := 0 43 | // Loop until len(a or b) > 2 44 | for minLen > thresh { 45 | // set the threshold to 1 after the first pass 46 | if thresh == 0 { 47 | thresh = 1 48 | } 49 | 50 | for i := 0; i < minLen; i++ { 51 | a[i] = append(a[i], b[i]...) 52 | } 53 | 54 | // if the b was the bigger array, only keep what we need 55 | if minLen == aLen { 56 | b = b[minLen:] 57 | } else { 58 | // update the smallest array with the remainders of a 59 | // and update a to include only the extended sub-arrays 60 | b = a[minLen:] 61 | a = a[:minLen] 62 | } 63 | aLen = len(a) 64 | minLen = min(aLen, len(b)) 65 | } 66 | 67 | pattern = make([]bool, 0, totalSteps) 68 | for i := 0; i < len(a); i++ { 69 | pattern = append(pattern, a[i]...) 70 | } 71 | for i := 0; i < len(b); i++ { 72 | pattern = append(pattern, b[i]...) 73 | } 74 | 75 | return pattern 76 | } 77 | 78 | func min(a, b int) int { 79 | if a < b { 80 | return a 81 | } 82 | return b 83 | } 84 | -------------------------------------------------------------------------------- /euclidean/euclidean_test.go: -------------------------------------------------------------------------------- 1 | package euclidean 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestRhythm(t *testing.T) { 10 | type args struct { 11 | accents int 12 | totalSteps int 13 | } 14 | tests := []struct { 15 | args args 16 | want []bool 17 | }{ 18 | // test values extracted from the white paper on euclidean rhythms 19 | // http://cgm.cs.mcgill.ca/~godfried/publications/banff.pdf 20 | // more accents than steps 21 | {args{13, 5}, []bool{true, true, true, true, true}}, 22 | // invalid accent number 23 | {args{-1, 5}, []bool{false, false, false, false, false}}, 24 | // invalid step number 25 | {args{5, -1}, []bool{}}, 26 | // tricky one 27 | {args{5, 13}, []bool{true, false, false, true, false, true, false, false, true, false, true, false, false}}, 28 | // basic 29 | {args{1, 1}, []bool{true}}, 30 | {args{1, 2}, []bool{true, false}}, 31 | {args{1, 3}, []bool{true, false, false}}, 32 | {args{1, 4}, []bool{true, false, false, false}}, 33 | // West African, Latin American 34 | {args{2, 3}, []bool{true, false, true}}, 35 | // Classical jazz and Persian 36 | {args{2, 5}, []bool{true, false, true, false, false}}, 37 | // Trinidad and Persian 38 | {args{3, 4}, []bool{true, false, true, true}}, 39 | // Rumanian and Persian necklaces 40 | {args{3, 5}, []bool{true, false, true, false, true}}, 41 | // Bulgarian Folk 42 | {args{3, 7}, []bool{true, false, true, false, true, false, false}}, 43 | // West Africa 44 | {args{3, 8}, []bool{true, false, false, true, false, false, true, false}}, 45 | // Bulgaria 46 | {args{4, 7}, []bool{true, false, true, false, true, false, true}}, 47 | // Turkish 48 | {args{4, 9}, []bool{true, false, true, false, true, false, true, false, false}}, 49 | // Frank Zappa 50 | {args{4, 11}, []bool{true, false, false, true, false, false, true, false, false, true, false}}, 51 | // Arab 52 | {args{5, 6}, []bool{true, false, true, true, true, true}}, 53 | // Arab 54 | {args{5, 7}, []bool{true, false, true, true, false, true, true}}, 55 | // West African 56 | {args{5, 8}, []bool{true, false, true, true, false, true, true, false}}, 57 | // Arab rhythm, South African and Rumanian necklaces 58 | {args{5, 9}, []bool{true, false, true, false, true, false, true, false, true}}, 59 | // Classical 60 | {args{5, 11}, []bool{true, false, true, false, true, false, true, false, true, false, false}}, 61 | // South African 62 | {args{5, 12}, []bool{true, false, false, true, false, true, false, false, true, false, true, false}}, 63 | // Brazilian necklace 64 | {args{5, 16}, []bool{true, false, false, true, false, false, true, false, false, true, false, false, true, false, false, false}}, 65 | // Tuareg rhythm of Libya 66 | {args{7, 8}, []bool{true, false, true, true, true, true, true, true}}, 67 | // West African 68 | {args{7, 12}, []bool{true, false, true, true, false, true, false, true, true, false, true, false}}, 69 | // Brazilian necklace 70 | {args{7, 16}, []bool{true, false, false, true, false, true, false, true, false, false, true, false, true, false, true, false}}, 71 | // West and Central African, and Brazilian necklaces 72 | {args{9, 16}, []bool{true, false, true, true, false, true, false, true, false, true, true, false, true, false, true, false}}, 73 | // Central African 74 | {args{11, 24}, []bool{true, false, false, true, false, true, false, true, false, true, false, true, false, false, true, false, true, false, true, false, true, false, true, false}}, 75 | // Central African necklace 76 | {args{13, 24}, []bool{true, false, true, true, false, true, false, true, false, true, false, true, false, true, true, false, true, false, true, false, true, false, true, false}}, 77 | } 78 | for _, tt := range tests { 79 | t.Run(fmt.Sprintf("%d-%d", tt.args.accents, tt.args.totalSteps), func(t *testing.T) { 80 | if got := Rhythm(tt.args.accents, tt.args.totalSteps); !reflect.DeepEqual(got, tt.want) { 81 | t.Errorf("Failed:\n%v, want\n%v", got, tt.want) 82 | } 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /examples/offline/main.go: -------------------------------------------------------------------------------- 1 | // generator example 2 | package main 3 | 4 | import ( 5 | "errors" 6 | "flag" 7 | "os" 8 | 9 | "fmt" 10 | "io" 11 | "strings" 12 | 13 | "github.com/go-audio/aiff" 14 | "github.com/go-audio/audio" 15 | "github.com/go-audio/generator" 16 | "github.com/go-audio/wav" 17 | ) 18 | 19 | var ( 20 | freqFlag = flag.Float64("freq", 440, "frequency to generate") 21 | bitDepthFlag = flag.Int("bitDepth", 16, "bit size to use when generating the auid file") 22 | durationFlag = flag.Int("duration", 4, "duration of the generated file") 23 | formatFlag = flag.String("format", "wav", "the audio format of the output file") 24 | ) 25 | 26 | func main() { 27 | flag.Parse() 28 | var err error 29 | 30 | freq := *freqFlag 31 | fs := 44100 32 | biteDepth := *bitDepthFlag 33 | 34 | osc := generator.NewOsc(generator.WaveSine, float64(freq), fs) 35 | // our osc generates values from -1 to 1, we need to go back to PCM scale 36 | factor := float64(audio.IntMaxSignedValue(biteDepth)) 37 | osc.Amplitude = factor 38 | data := make([]float64, fs**durationFlag) 39 | buf := &audio.FloatBuffer{Data: data, Format: audio.FormatMono44100} 40 | osc.Fill(buf) 41 | 42 | // generate the sound file 43 | var outName string 44 | var format string 45 | switch strings.ToLower(*formatFlag) { 46 | case "aif", "aiff": 47 | format = "aif" 48 | outName = "generated.aiff" 49 | default: 50 | format = "wav" 51 | outName = "generated.wav" 52 | } 53 | 54 | o, err := os.Create(outName) 55 | if err != nil { 56 | panic(err) 57 | } 58 | defer o.Close() 59 | if err := encode(format, buf, o); err != nil { 60 | panic(err) 61 | } 62 | fmt.Println(outName, "generated") 63 | } 64 | 65 | func encode(format string, buf audio.Buffer, w io.WriteSeeker) error { 66 | switch format { 67 | case "wav": 68 | e := wav.NewEncoder(w, 69 | buf.PCMFormat().SampleRate, 70 | 16, 71 | buf.PCMFormat().NumChannels, 1) 72 | if err := e.Write(buf.AsIntBuffer()); err != nil { 73 | return err 74 | } 75 | return e.Close() 76 | case "aiff": 77 | e := aiff.NewEncoder(w, 78 | buf.PCMFormat().SampleRate, 79 | 16, 80 | buf.PCMFormat().NumChannels) 81 | if err := e.Write(buf.AsIntBuffer()); err != nil { 82 | return err 83 | } 84 | return e.Close() 85 | default: 86 | return errors.New("unknown format") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/offline/stereo/main.go: -------------------------------------------------------------------------------- 1 | // sterep generator example 2 | package main 3 | 4 | import ( 5 | "errors" 6 | "flag" 7 | "os" 8 | 9 | "fmt" 10 | "io" 11 | "strings" 12 | 13 | "github.com/go-audio/aiff" 14 | "github.com/go-audio/audio" 15 | "github.com/go-audio/generator" 16 | "github.com/go-audio/wav" 17 | ) 18 | 19 | var ( 20 | freqFlag = flag.Float64("freq", 440, "frequency to generate") 21 | bitDepthFlag = flag.Int("bitDepth", 16, "bit size to use when generating the auid file") 22 | durationFlag = flag.Int("duration", 4, "duration of the generated file") 23 | formatFlag = flag.String("format", "wav", "the audio format of the output file") 24 | ) 25 | 26 | func main() { 27 | flag.Parse() 28 | var err error 29 | 30 | freq := *freqFlag 31 | fs := 44100 32 | biteDepth := *bitDepthFlag 33 | 34 | osc := generator.NewOsc(generator.WaveSine, float64(freq), fs) 35 | // our osc generates values from -1 to 1, we need to go back to PCM scale 36 | factor := float64(audio.IntMaxSignedValue(biteDepth)) 37 | osc.Amplitude = factor 38 | data := make([]float64, fs**durationFlag*2) 39 | buf := &audio.FloatBuffer{Data: data, Format: audio.FormatStereo44100} 40 | osc.Fill(buf) 41 | 42 | // generate the sound file 43 | var outName string 44 | var format string 45 | switch strings.ToLower(*formatFlag) { 46 | case "aif", "aiff": 47 | format = "aif" 48 | outName = "stereoGenerated.aiff" 49 | default: 50 | format = "wav" 51 | outName = "stereoGenerated.wav" 52 | } 53 | 54 | o, err := os.Create(outName) 55 | if err != nil { 56 | panic(err) 57 | } 58 | defer o.Close() 59 | if err := encode(format, buf, o); err != nil { 60 | panic(err) 61 | } 62 | fmt.Println(outName, "generated") 63 | } 64 | 65 | func encode(format string, buf audio.Buffer, w io.WriteSeeker) error { 66 | switch format { 67 | case "wav": 68 | e := wav.NewEncoder(w, 69 | buf.PCMFormat().SampleRate, 70 | 16, 71 | buf.PCMFormat().NumChannels, 1) 72 | if err := e.Write(buf.AsIntBuffer()); err != nil { 73 | return err 74 | } 75 | return e.Close() 76 | case "aiff": 77 | e := aiff.NewEncoder(w, 78 | buf.PCMFormat().SampleRate, 79 | 16, 80 | buf.PCMFormat().NumChannels) 81 | if err := e.Write(buf.AsIntBuffer()); err != nil { 82 | return err 83 | } 84 | return e.Close() 85 | default: 86 | return errors.New("unknown format") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/realtime/main.go: -------------------------------------------------------------------------------- 1 | // demo package simulating a realtime generation and processing. 2 | // Start the example from your terminal and type a letter + enter. 3 | package main 4 | 5 | import ( 6 | "bufio" 7 | "fmt" 8 | "log" 9 | "math" 10 | "os" 11 | "os/signal" 12 | 13 | "github.com/go-audio/audio" 14 | "github.com/go-audio/generator" 15 | "github.com/go-audio/transforms" 16 | "github.com/gordonklaus/portaudio" 17 | ) 18 | 19 | func main() { 20 | bufferSize := 512 21 | buf := &audio.FloatBuffer{ 22 | Data: make([]float64, bufferSize), 23 | Format: audio.FormatMono44100, 24 | } 25 | currentNote := 440.0 26 | osc := generator.NewOsc(generator.WaveSine, currentNote, buf.Format.SampleRate) 27 | osc.Amplitude = 1 28 | 29 | sig := make(chan os.Signal, 1) 30 | signal.Notify(sig, os.Interrupt, os.Kill) 31 | 32 | gainControl := 0.0 33 | currentVol := osc.Amplitude 34 | 35 | fmt.Println(`This is a demo, press a key followed by enter, the played note should change. 36 | Use the - and + keys follow by enter to decrease or increase the volume\nPress q or ctrl-c to exit. 37 | Note that the sound will come out of your default sound card.`) 38 | 39 | scanner := bufio.NewScanner(os.Stdin) 40 | go func() { 41 | for scanner.Scan() { 42 | if len(scanner.Text()) > 0 { 43 | k := scanner.Text()[0] 44 | switch k { 45 | case 'q': 46 | sig <- os.Interrupt 47 | case '+': 48 | gainControl += 0.10 49 | case '-': 50 | gainControl -= 0.10 51 | default: 52 | v := float64(math.Abs(float64(int(k - 100)))) 53 | currentNote = 440.0 * math.Pow(2, (v)/12.0) 54 | fmt.Printf("switching oscillator to %.2f Hz\n", currentNote) 55 | if currentNote > 22000 { 56 | currentNote = 440.0 57 | } 58 | osc.SetFreq(currentNote) 59 | } 60 | } 61 | } 62 | }() 63 | 64 | // Audio output 65 | portaudio.Initialize() 66 | defer portaudio.Terminate() 67 | out := make([]float32, bufferSize) 68 | stream, err := portaudio.OpenDefaultStream(0, 1, 44100, len(out), &out) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | defer stream.Close() 73 | 74 | if err := stream.Start(); err != nil { 75 | log.Fatal(err) 76 | } 77 | defer stream.Stop() 78 | for { 79 | 80 | // populate the out buffer 81 | if err := osc.Fill(buf); err != nil { 82 | log.Printf("error filling up the buffer") 83 | } 84 | // apply vol control if needed (applied as a transform instead of a control 85 | // on the osc) 86 | if gainControl != 0 { 87 | currentVol += gainControl 88 | if currentVol < 0.1 { 89 | currentVol = 0 90 | } 91 | if currentVol > 6 { 92 | currentVol = 6 93 | } 94 | fmt.Printf("new vol %f.2", currentVol) 95 | gainControl = 0 96 | } 97 | transforms.Gain(buf, currentVol) 98 | 99 | f64ToF32Copy(out, buf.Data) 100 | 101 | // write to the stream 102 | if err := stream.Write(); err != nil { 103 | log.Printf("error writing to stream : %v\n", err) 104 | } 105 | select { 106 | case <-sig: 107 | fmt.Println("\tCiao!") 108 | return 109 | default: 110 | } 111 | } 112 | } 113 | 114 | // portaudio doesn't support float64 so we need to copy our data over to the 115 | // destination buffer. 116 | func f64ToF32Copy(dst []float32, src []float64) { 117 | for i := range src { 118 | dst[i] = float32(src[i]) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import "math" 4 | 5 | // WaveType is an alias type for the type of waveforms that can be generated 6 | type WaveType uint16 7 | 8 | const ( 9 | WaveSine WaveType = iota // 0 10 | WaveTriangle // 1 11 | WaveSaw // 2 12 | WaveSqr //3 13 | ) 14 | 15 | const ( 16 | TwoPi = float64(2 * math.Pi) 17 | ) 18 | 19 | const ( 20 | SineB = 4.0 / math.Pi 21 | SineC = -4.0 / (math.Pi * math.Pi) 22 | Q = 0.775 23 | SineP = 0.225 24 | ) 25 | 26 | // Sine takes an input value from -Pi to Pi 27 | // and returns a value between -1 and 1 28 | func Sine(x32 float64) float64 { 29 | x := float64(x32) 30 | y := SineB*x + SineC*x*(math.Abs(x)) 31 | y = SineP*(y*(math.Abs(y))-y) + y 32 | return float64(y) 33 | } 34 | 35 | const TringleA = 2.0 / math.Pi 36 | 37 | // Triangle takes an input value from -Pi to Pi 38 | // and returns a value between -1 and 1 39 | func Triangle(x float64) float64 { 40 | return float64(TringleA*x) - 1.0 41 | } 42 | 43 | // Square takes an input value from -Pi to Pi 44 | // and returns -1 or 1 45 | func Square(x float64) float64 { 46 | if x >= 0.0 { 47 | return 1 48 | } 49 | return -1.0 50 | } 51 | 52 | const SawtoothA = 1.0 / math.Pi 53 | 54 | // Triangle takes an input value from -Pi to Pi 55 | // and returns a value between -1 and 1 56 | func Sawtooth(x float64) float64 { 57 | return SawtoothA * x 58 | } 59 | -------------------------------------------------------------------------------- /generator_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestSine(t *testing.T) { 9 | testCases := []struct { 10 | in float64 11 | out float64 12 | }{ 13 | 0: {float64(-math.Pi), 0}, 14 | 1: {0.007, 0.006909727339533104}, 15 | 2: {-0.5, -0.47932893655759223}, 16 | 3: {0.1, 0.09895415534087945}, 17 | 4: {1.5862234, 0.9998818440160414}, 18 | 5: {2.0, 0.909795856141705}, 19 | 6: {3.0, 0.14008939955174454}, 20 | 7: {math.Pi, 0}, 21 | } 22 | 23 | for i, tc := range testCases { 24 | if out := Sine(tc.in); !nearlyEqual(out, tc.out, 0.0001) { 25 | t.Logf("[%d] sine(%f) => %.7f != %.7f", i, tc.in, out, tc.out) 26 | t.Fail() 27 | } 28 | } 29 | } 30 | 31 | func nearlyEqual(a, b, epsilon float64) bool { 32 | if a == b { 33 | return true 34 | } 35 | absA := math.Abs(float64(a)) 36 | absB := math.Abs(float64(b)) 37 | diff := math.Abs(float64(a) - float64(b)) 38 | 39 | if a == 0 || b == 0 || diff < 0.0000001 { 40 | return diff < (float64(epsilon)) 41 | } 42 | return diff/(absA+absB) < float64(epsilon) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-audio/generator 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/go-audio/aiff v1.0.0 7 | github.com/go-audio/audio v1.0.0 8 | github.com/go-audio/midi v1.0.0 9 | github.com/go-audio/music v0.0.0-20190404192933-efa583cde964 10 | github.com/go-audio/wav v1.0.0 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-audio/aiff v0.0.0-20180403003018-6c3a8a6aff12/go.mod h1:AMSAp6W1zd0koOdX6QDgGIuBDTUvLa2SLQtm7d9eM3c= 2 | github.com/go-audio/aiff v1.0.0 h1:Mn4zZvfGjTUc9VWXiTlj6BYUADTsmh3qZtpCyHFRMiE= 3 | github.com/go-audio/aiff v1.0.0/go.mod h1:Kazp+9JR/Y1ITCXaDlO6OIIOrz6eGGAn+dGT04V4HPM= 4 | github.com/go-audio/audio v0.0.0-20180206231410-b697a35b5608/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= 5 | github.com/go-audio/audio v1.0.0 h1:zS9vebldgbQqktK4H0lUqWrG8P0NxCJVqcj7ZpNnwd4= 6 | github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= 7 | github.com/go-audio/midi v1.0.0 h1:xeDg6E0ygfpsTPRTi1GdrGcDvJ8zt9pemqOJ5cHoLZ8= 8 | github.com/go-audio/midi v1.0.0/go.mod h1:PoFcd6KPFUn++NHd1libpb/WifNIb/qKVvZ71McMVmo= 9 | github.com/go-audio/music v0.0.0-20190404192933-efa583cde964 h1:J2ZIL+zcPu44sGZWm13Gmlik4ztbkqAoaj579+uBFe0= 10 | github.com/go-audio/music v0.0.0-20190404192933-efa583cde964/go.mod h1:YB8q3qa/GIHSguIMcxbGSIYhkjo6S2zh8pRpEkWH3JI= 11 | github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA= 12 | github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498= 13 | github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks= 14 | github.com/go-audio/wav v1.0.0 h1:WdSGLhtyud6bof6XHL28xKeCQRzCV06pOFo3LZsFdyE= 15 | github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE= 16 | github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21/go.mod h1:LlQmBGkOuV/SKzEDXBPKauvN2UqCgzXO2XjecTGj40s= 17 | github.com/mattetti/filebuffer v1.0.0/go.mod h1:X6nyAIge2JGVmuJt2MFCqmHrb/5IHiphfHtot0s5cnI= 18 | -------------------------------------------------------------------------------- /osc.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/go-audio/audio" 8 | ) 9 | 10 | // Osc is an oscillator 11 | type Osc struct { 12 | Shape WaveType 13 | Amplitude float64 14 | DcOffset float64 15 | Freq float64 16 | // SampleRate 17 | Fs int 18 | PhaseOffset float64 19 | CurrentPhaseAngle float64 20 | phaseAngleIncr float64 21 | // currentSample allows us to track where we are at in the signal life 22 | // and setup an envelope accordingly 23 | currentSample int 24 | // ADSR 25 | attackInSamples int 26 | } 27 | 28 | // NewOsc returns a new oscillator, note that if you change the phase offset of the returned osc, 29 | // you also need to set the CurrentPhaseAngle 30 | func NewOsc(shape WaveType, hz float64, fs int) *Osc { 31 | return &Osc{Shape: shape, Amplitude: 1, Freq: hz, Fs: fs, phaseAngleIncr: ((hz * TwoPi) / float64(fs))} 32 | } 33 | 34 | // Reset sets the oscillator back to its starting state 35 | func (o *Osc) Reset() { 36 | o.phaseAngleIncr = ((o.Freq * TwoPi) / float64(o.Fs)) 37 | o.currentSample = 0 38 | } 39 | 40 | // SetFreq updates the oscillator frequency 41 | func (o *Osc) SetFreq(hz float64) { 42 | if o.Freq != hz { 43 | o.Freq = hz 44 | o.phaseAngleIncr = ((hz * TwoPi) / float64(o.Fs)) 45 | } 46 | } 47 | 48 | // SetAttackInMs sets the duration for the oscillator to be at full amplitude 49 | // after it starts. 50 | func (o *Osc) SetAttackInMs(ms int) { 51 | if o == nil { 52 | return 53 | } 54 | if ms <= 0 { 55 | o.attackInSamples = 0 56 | return 57 | } 58 | o.attackInSamples = int(float32(o.Fs) / (1000.0 / float32(ms))) 59 | } 60 | 61 | // Signal uses the osc to generate a discreet signal 62 | func (o *Osc) Signal(length int) []float64 { 63 | output := make([]float64, length) 64 | for i := 0; i < length; i++ { 65 | output[i] = o.Sample() 66 | } 67 | return output 68 | } 69 | 70 | // Fill fills up the pass audio Buffer with the output of the oscillator. 71 | func (o *Osc) Fill(buf *audio.FloatBuffer) error { 72 | if o == nil { 73 | return nil 74 | } 75 | numChans := 1 76 | if f := buf.Format; f != nil { 77 | numChans = f.NumChannels 78 | } 79 | frameCount := buf.NumFrames() 80 | var sample float64 81 | for i := 0; i < frameCount; i++ { 82 | sample = o.Sample() 83 | for j := 0; j < numChans; j++ { 84 | buf.Data[i*numChans+j] = sample 85 | } 86 | } 87 | return nil 88 | } 89 | 90 | // Sample returns the next sample generated by the oscillator 91 | func (o *Osc) Sample() (output float64) { 92 | if o == nil { 93 | return 94 | } 95 | o.currentSample++ 96 | if o.CurrentPhaseAngle < -math.Pi { 97 | o.CurrentPhaseAngle += TwoPi 98 | } else if o.CurrentPhaseAngle > math.Pi { 99 | o.CurrentPhaseAngle -= TwoPi 100 | } 101 | 102 | var amp float64 103 | if o.attackInSamples > o.currentSample { 104 | // linear fade in 105 | amp = float64(o.currentSample) * (o.Amplitude / float64(o.attackInSamples)) 106 | } else { 107 | amp = o.Amplitude 108 | } 109 | 110 | switch o.Shape { 111 | case WaveSine: 112 | output = amp*Sine(o.CurrentPhaseAngle) + o.DcOffset 113 | case WaveTriangle: 114 | output = amp*Triangle(o.CurrentPhaseAngle) + o.DcOffset 115 | case WaveSaw: 116 | output = amp*Sawtooth(o.CurrentPhaseAngle) + o.DcOffset 117 | case WaveSqr: 118 | fmt.Println(o.CurrentPhaseAngle) 119 | output = amp*Square(o.CurrentPhaseAngle) + o.DcOffset 120 | } 121 | 122 | o.CurrentPhaseAngle += o.phaseAngleIncr 123 | return output 124 | } 125 | -------------------------------------------------------------------------------- /osc_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import "testing" 4 | 5 | func TestOsc_Signal(t *testing.T) { 6 | osc := NewOsc(WaveSine, 440, 44100) 7 | if osc.CurrentPhaseAngle != 0 { 8 | t.Fatalf("expected the current phase to be zero") 9 | } 10 | if osc.phaseAngleIncr != 0.06268937721449021 { 11 | t.Fatalf("Wrong phase angle increment") 12 | } 13 | sample := osc.Sample() 14 | if phase := osc.CurrentPhaseAngle; phase != 0.06268937721449021 { 15 | t.Fatalf("wrong phase angle: %f, expected 0.06268937721449021", phase) 16 | } 17 | if sample != 0.0 { 18 | t.Fatalf("wrong first sample: %f expected 0.0", sample) 19 | } 20 | signal := osc.Signal(19) 21 | expected := []float64{0.062001866171879985, 0.12406665714043713, 0.1858716671561314, 0.24710788950751222, 0.3074800165212184, 22 | 0.3667064395619785, 0.42451924903261046, 0.4806642343740216, 0.5349008840652089, 0.5870023856232587, 0.6367556256033469, 23 | 0.6839611895987389, 0.7284333622407899, 0.7700001271989437, 0.8085031671807348, 0.8437978639317864, 0.8757532982358108, 24 | 0.9042522499146113, 0.9291911978280791} 25 | 26 | for i, s := range signal { 27 | if !nearlyEqual(s, expected[i], 0.000001) { 28 | t.Logf("sample %d didn't match, expected: %f got %f\n", i, expected[i], s) 29 | t.Fail() 30 | } 31 | } 32 | 33 | osc = NewOsc(WaveSine, 400, 1000) 34 | signal = osc.Signal(100) 35 | 36 | expected = []float64{0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001, 0, 0.5881600000000001, -0.9513600000000001, 0.9513600000000001, -0.5881600000000001} 37 | for i, s := range signal { 38 | if !nearlyEqual(s, expected[i], 0.00000001) { 39 | t.Logf("sample %d didn't match, expected: %f got %f\n", i, expected[i], s) 40 | t.Fail() 41 | } 42 | } 43 | } 44 | --------------------------------------------------------------------------------