├── .gitignore ├── README.md ├── examples ├── fft │ └── main.go ├── filter_sweep │ └── main.go ├── gated_sine │ └── main.go ├── modules │ ├── adsr │ │ └── main.go │ ├── midi │ │ └── main.go │ ├── mixer │ │ └── main.go │ └── osc │ │ ├── main.go │ │ └── noise │ │ └── main.go └── printmidi │ └── main.go ├── go.mod ├── go.sum ├── midi └── tuning.go ├── modio ├── fft.go └── repeat.go ├── modular.go └── modules ├── adsr └── adsr.go ├── filter └── lowpass.go ├── mathmod └── math.go ├── midi └── midi.go ├── osc ├── noise.go └── osc.go ├── output └── otoplayer │ └── oto.go └── tape └── tape.go /.gitignore: -------------------------------------------------------------------------------- 1 | /analysis/testdata 2 | *.sample 3 | *.osc.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-modular (v2) 2 | 3 | A software modular synthesizer with Midi support. 4 | 5 | **!! Warning: High amplitude sounds can cause ear damage. Lower your headphone volume before using !!** 6 | 7 | ## Install 8 | 9 | See required setup in [oto](https://github.com/hajimehoshi/oto/blob/master/README.md#prerequisite) for all platforms. This library is used to play PCM sound. 10 | 11 | ``` 12 | go get github.com/ajzaff/go-modular 13 | ``` 14 | 15 | ### Midi 16 | 17 | Midi drivers required ahead. 18 | 19 | See [rtmididrv](https://gitlab.com/gomidi/rtmididrv#installation) for installation instructions for all platforms. 20 | 21 | ## Examples 22 | 23 | See the `examples` directory for demo content. -------------------------------------------------------------------------------- /examples/fft/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ajzaff/go-modular" 5 | "github.com/ajzaff/go-modular/modio" 6 | "github.com/ajzaff/go-modular/modules/osc" 7 | "github.com/ajzaff/go-modular/modules/output/otoplayer" 8 | ) 9 | 10 | const blockSize = 1024 11 | 12 | func main() { 13 | cfg := modular.New() 14 | 15 | b := make([]float32, 5*44100) 16 | noise := osc.Noise(.1) 17 | noise.SetConfig(cfg) 18 | noise.Process(b) 19 | 20 | // w := osc.Sine(.1, osc.Range16, osc.Fine(midi.StdTuning)) 21 | // w.SetConfig(cfg) 22 | // w.Process(b) 23 | 24 | fft := modio.FFT{} 25 | 26 | for i := 0; i+blockSize <= len(b); i += blockSize { 27 | block := b[i : i+blockSize] 28 | 29 | h := make([]float32, blockSize) 30 | for i := blockSize / 2; i < blockSize; i++ { 31 | h[i] = 1 32 | } 33 | hc := fft.Compute(h) 34 | 35 | fft.Reset() 36 | fft.StoreFFT(block) 37 | fft.UpdateAll(func(i int, v complex128) complex128 { 38 | return v * hc[i] 39 | }) 40 | fft.Process(block) 41 | } 42 | 43 | oto := otoplayer.New() 44 | oto.SetConfig(cfg) 45 | oto.PlayStereo(b) 46 | } 47 | -------------------------------------------------------------------------------- /examples/filter_sweep/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ajzaff/go-modular" 5 | "github.com/ajzaff/go-modular/modules/filter" 6 | "github.com/ajzaff/go-modular/modules/osc" 7 | "github.com/ajzaff/go-modular/modules/output/otoplayer" 8 | ) 9 | 10 | const ( 11 | blockSize = 1024 12 | sampleRate = 44100 13 | ) 14 | 15 | func main() { 16 | cfg := modular.New() 17 | cfg.SampleRate = sampleRate 18 | cfg.BufferSize = blockSize 19 | 20 | b := make([]float32, 7*sampleRate) 21 | 22 | noise := osc.Noise(.1) 23 | noise.SetConfig(cfg) 24 | noise.Process(b) 25 | 26 | f := filter.LowPass{} 27 | f.SetConfig(cfg) 28 | 29 | var i float32 30 | f.SetCutoff(func() float32 { 31 | defer func() { i++ }() 32 | x := sampleRate / 2 * i / (5 * sampleRate) 33 | if x > sampleRate/2 { 34 | return 0 35 | } 36 | return sampleRate/2 - x 37 | }) 38 | 39 | for i := 0; i+blockSize <= len(b); i += blockSize { 40 | f.Process(b[i : i+blockSize]) 41 | } 42 | 43 | oto := otoplayer.New() 44 | oto.SetConfig(cfg) 45 | oto.PlayStereo(b) 46 | } 47 | -------------------------------------------------------------------------------- /examples/gated_sine/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ajzaff/go-modular" 5 | "github.com/ajzaff/go-modular/midi" 6 | "github.com/ajzaff/go-modular/modules/osc" 7 | "github.com/ajzaff/go-modular/modules/output/otoplayer" 8 | ) 9 | 10 | func main() { 11 | cfg := modular.New() 12 | 13 | b := make([]float32, 10*44100) 14 | w := osc.Sine(.1, osc.Range16, osc.Fine(midi.StdTuning)) 15 | w.Voltage = func() float32 { 16 | return 69. / 12 17 | } 18 | w.SetConfig(cfg) 19 | w.Process(b) 20 | 21 | lfo := osc.Pulse(.5, .5, osc.Range64, 0, .5) 22 | lfo.SetConfig(cfg) 23 | 24 | for i, v := range b { 25 | b[i] = v * lfo.Next() 26 | } 27 | 28 | oto := otoplayer.New() 29 | oto.SetConfig(cfg) 30 | oto.PlayStereo(b) 31 | } 32 | -------------------------------------------------------------------------------- /examples/modules/adsr/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/ajzaff/go-modular" 7 | "github.com/ajzaff/go-modular/midi" 8 | "github.com/ajzaff/go-modular/modules/adsr" 9 | "github.com/ajzaff/go-modular/modules/osc" 10 | "github.com/ajzaff/go-modular/modules/output/otoplayer" 11 | ) 12 | 13 | func main() { 14 | cfg := modular.New() 15 | 16 | b := make([]float32, 5*44100) 17 | w := osc.Sine(.1, osc.Range16, osc.Fine(midi.StdTuning)) 18 | w.SetConfig(cfg) 19 | w.Process(b) 20 | 21 | g := adsr.New(time.Second, time.Second, .5, time.Second) 22 | g.SetConfig(cfg) 23 | g.SetSustain(time.Second) 24 | g.Process(b) 25 | 26 | oto := otoplayer.New() 27 | oto.SetConfig(cfg) 28 | oto.PlayStereo(b) 29 | } 30 | -------------------------------------------------------------------------------- /examples/modules/midi/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ajzaff/go-modular" 5 | "github.com/ajzaff/go-modular/midi" 6 | midimodule "github.com/ajzaff/go-modular/modules/midi" 7 | "github.com/ajzaff/go-modular/modules/osc" 8 | ) 9 | 10 | func main() { 11 | cfg := modular.New() 12 | 13 | mid, err := midimodule.New(1, 0) 14 | if err != nil { 15 | panic(err) 16 | } 17 | mid.SetConfig(cfg) 18 | 19 | wave := osc.Saw(.5, osc.Range8, osc.Fine(midi.StdTuning)) 20 | wave.SetConfig(cfg) 21 | 22 | // FIXME: 23 | 24 | } 25 | -------------------------------------------------------------------------------- /examples/modules/mixer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ajzaff/go-modular" 5 | "github.com/ajzaff/go-modular/midi" 6 | "github.com/ajzaff/go-modular/modules/osc" 7 | "github.com/ajzaff/go-modular/modules/output/otoplayer" 8 | ) 9 | 10 | func main() { 11 | cfg := modular.New() 12 | 13 | w := osc.Sine(.5, osc.Range16, osc.Fine(midi.StdTuning)) 14 | w.SetConfig(cfg) 15 | 16 | b := make([]float32, 5*44100) 17 | for i := range b { 18 | b[i] = w.Func(45./12)/3 + w.Func(64./12)/3 + w.Func(73./12)/3 19 | w.Advance(1) 20 | } 21 | 22 | oto := otoplayer.New() 23 | defer oto.Close() 24 | 25 | oto.SetConfig(cfg) 26 | oto.PlayStereo(b) 27 | } 28 | -------------------------------------------------------------------------------- /examples/modules/osc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ajzaff/go-modular" 5 | "github.com/ajzaff/go-modular/midi" 6 | "github.com/ajzaff/go-modular/modules/osc" 7 | "github.com/ajzaff/go-modular/modules/output/otoplayer" 8 | ) 9 | 10 | func main() { 11 | cfg := modular.New() 12 | 13 | b := make([]float32, 5*44100) 14 | w := osc.Sine(.1, osc.Range8, osc.Fine(midi.StdTuning)) 15 | w.Voltage = func() float32 { 16 | return 69. / 12 17 | } 18 | w.SetConfig(cfg) 19 | w.Process(b) 20 | 21 | oto := otoplayer.New() 22 | oto.SetConfig(cfg) 23 | oto.PlayStereo(b) 24 | } 25 | -------------------------------------------------------------------------------- /examples/modules/osc/noise/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ajzaff/go-modular" 5 | "github.com/ajzaff/go-modular/modules/osc" 6 | "github.com/ajzaff/go-modular/modules/output/otoplayer" 7 | ) 8 | 9 | func main() { 10 | cfg := modular.New() 11 | 12 | b := make([]float32, 5*44100) 13 | 14 | noise := osc.Noise(.1) 15 | noise.SetConfig(cfg) 16 | noise.Process(b) 17 | 18 | oto := otoplayer.New() 19 | oto.SetConfig(cfg) 20 | oto.PlayStereo(b) 21 | } 22 | -------------------------------------------------------------------------------- /examples/printmidi/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "gitlab.com/gomidi/midi" 7 | "gitlab.com/gomidi/midi/reader" 8 | "gitlab.com/gomidi/rtmididrv" 9 | ) 10 | 11 | func must(err error) { 12 | if err != nil { 13 | panic(err.Error()) 14 | } 15 | } 16 | 17 | func main() { 18 | drv, err := rtmididrv.New() 19 | must(err) 20 | 21 | defer drv.Close() 22 | 23 | ins, err := drv.Ins() 24 | must(err) 25 | 26 | printInPorts(ins) 27 | 28 | in := ins[1] 29 | 30 | must(in.Open()) 31 | 32 | rd := reader.New() 33 | rd.ListenTo(in) 34 | } 35 | 36 | func printPort(port midi.Port) { 37 | fmt.Printf("[%v] %s\n", port.Number(), port.String()) 38 | } 39 | 40 | func printInPorts(ports []midi.In) { 41 | fmt.Printf("MIDI IN Ports\n") 42 | for _, port := range ports { 43 | printPort(port) 44 | } 45 | fmt.Printf("\n\n") 46 | } 47 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajzaff/go-modular 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/hajimehoshi/oto v0.6.1 7 | github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 8 | gitlab.com/gomidi/midi v1.23.5 9 | gitlab.com/gomidi/rtmididrv v0.11.0 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/hajimehoshi/oto v0.6.1 h1:7cJz/zRQV4aJvMSSRqzN2TImoVVMpE0BCY4nrNJaDOM= 2 | github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= 3 | github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 h1:dd7vnTDfjtwCETZDrRe+GPYNLA1jBtbZeyfyE8eZCyk= 4 | github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12/go.mod h1:i/KKcxEWEO8Yyl11DYafRPKOPVYTrhxiTRigjtEEXZU= 5 | gitlab.com/gomidi/midi v1.21.0/go.mod h1:3ohtNOhqoSakkuLG/Li1OI6I3J1c2LErnJF5o/VBq1c= 6 | gitlab.com/gomidi/midi v1.23.5 h1:5/MOi3fWEzSLNgpY5oNZqc6vsHAKEiEPFYeUCzgB1hQ= 7 | gitlab.com/gomidi/midi v1.23.5/go.mod h1:3ohtNOhqoSakkuLG/Li1OI6I3J1c2LErnJF5o/VBq1c= 8 | gitlab.com/gomidi/rtmididrv v0.11.0 h1:Bc/zr8agMU2KDcpj932Vy51K4YO5Cp6pPIY6Buf6Q6Y= 9 | gitlab.com/gomidi/rtmididrv v0.11.0/go.mod h1:wjwyt4rCwmvS++uk5mW5S+MnSbUnQUMNVvaEUCunjJE= 10 | gitlab.com/gomidi/rtmididrv/imported/rtmidi v0.0.0-20191025100939-514fe0ed97a6 h1:0XqAH/BAxH5TTBzIWkdlZqpp6VUx6DFcQnMWW6G6hIc= 11 | gitlab.com/gomidi/rtmididrv/imported/rtmidi v0.0.0-20191025100939-514fe0ed97a6/go.mod h1:FYVFN2H23IsX56VntiDF9DgCIekHh359wW+iMl1W8rQ= 12 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg= 13 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 14 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0= 15 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 16 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc= 17 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 18 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 19 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8= 20 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 22 | -------------------------------------------------------------------------------- /midi/tuning.go: -------------------------------------------------------------------------------- 1 | package midi 2 | 3 | import "math" 4 | 5 | // Tuning is an interface for a chromatic scale. 6 | type Tuning interface { 7 | // A4Hz returns the frequency of the note A4. 8 | A4Hz() float32 9 | } 10 | 11 | // StdTuning is an A440 tuning. 12 | var StdTuning = stdTuning{} 13 | 14 | type stdTuning struct{} 15 | 16 | func (stdTuning) A4Hz() float32 { 17 | return 440 18 | } 19 | 20 | func clampKey(k int) uint8 { 21 | if k < 0 { 22 | k = 0 23 | } 24 | if k > 127 { 25 | k = 127 26 | } 27 | return uint8(k) 28 | } 29 | 30 | // Key returns the MIDI key value from tuning t and frequency f. 31 | // A4 is MIDI key 69 for instance. 32 | func Key(t Tuning, f float32) int { 33 | return int(69 + 12*math.Log2(float64(f)/float64(t.A4Hz()))) 34 | } 35 | 36 | // Pitch returns the pitch of the midi key in tuning t. 37 | func Pitch(t Tuning, key int) float32 { 38 | return Tone(t, float32(key)) 39 | } 40 | 41 | // Tone returns the tone of the fractional midi key in tuning t. 42 | func Tone(t Tuning, key float32) float32 { 43 | return t.A4Hz() * float32(math.Pow(2, (float64(key)-69)/12)) 44 | } 45 | 46 | // Note constants starting at octave 0. 47 | const ( 48 | C = 12 + iota // C0 49 | Db // Db0 50 | D // D0 51 | Eb // Eb0 52 | E // E0 53 | F // F0 54 | Gb // Gb0 55 | G // G0 56 | Ab // Ab0 57 | A // A0 58 | Bb // Bb0 59 | B // B0 60 | ) 61 | 62 | // Note returns the midi note in octave oct. 63 | // 64 | // Example: 65 | // A4 = Note(A, 4) 66 | // 67 | // Piano notes range from A0 (21) to C8 (108). 68 | // Midi notes range from C-1 (0) to G9 (127). 69 | func Note(note, oct int) int { 70 | return note + 12*oct 71 | } 72 | -------------------------------------------------------------------------------- /modio/fft.go: -------------------------------------------------------------------------------- 1 | package modio 2 | 3 | import ( 4 | "github.com/ajzaff/go-modular" 5 | "github.com/mjibson/go-dsp/fft" 6 | ) 7 | 8 | type FFT struct { 9 | buf []complex128 10 | 11 | blockSize int 12 | sampleRate int 13 | } 14 | 15 | func (x *FFT) Reset() { 16 | x.buf = nil 17 | } 18 | 19 | func (x *FFT) SetConfig(cfg *modular.Config) { 20 | x.blockSize = cfg.BufferSize 21 | x.sampleRate = cfg.SampleRate 22 | } 23 | 24 | // Compute FFT(b). 25 | func (x *FFT) Compute(b []float32) []complex128 { 26 | p := make([]complex128, len(b)) 27 | for i, v := range b { 28 | p[i] = complex(float64(v), 0) 29 | } 30 | return fft.FFT(p) 31 | } 32 | 33 | // Store FFT(b). 34 | func (x *FFT) StoreFFT(b []float32) { 35 | if x.buf != nil { 36 | return 37 | } 38 | x.buf = x.Compute(b) 39 | } 40 | 41 | // Receive IFFT(x.buf) into b. 42 | func (x *FFT) Process(b []float32) { 43 | for i, v := range fft.IFFT(x.buf) { 44 | b[i] = float32(real(v)) 45 | } 46 | } 47 | 48 | // Returns FFT(b)_i. 49 | func (x *FFT) Get(i int) complex128 { 50 | return x.buf[i] 51 | } 52 | 53 | // Update FFT(b)_i = v. 54 | func (x *FFT) Update(i int, v complex128) { 55 | x.buf[i] = v 56 | } 57 | 58 | // Update all FFT(b)_i by applying f. 59 | func (x *FFT) UpdateAll(f func(i int, v complex128) complex128) { 60 | for i, v := range x.buf { 61 | x.buf[i] = f(i, v) 62 | } 63 | } 64 | 65 | func CopyToReal32(dst []float32, src []complex128) { 66 | if len(dst) != len(src) { 67 | panic("modio.ToReal32: slices must have equal length") 68 | } 69 | for i := range dst { 70 | dst[i] = float32(real(src[i])) 71 | } 72 | } 73 | 74 | func CopyToComplex128(dst []complex128, src []float32) { 75 | if len(dst) != len(src) { 76 | panic("modio.ToComplex128: slices must have equal length") 77 | } 78 | for i := range dst { 79 | dst[i] = complex(float64(src[i]), 0) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /modio/repeat.go: -------------------------------------------------------------------------------- 1 | package modio 2 | 3 | // Repeat returns a new sample with b repeated count times. 4 | // 5 | // It panics if count is negative or if 6 | // the result of (len(b) * count) overflows. 7 | func Repeat(b []float32, count int) []float32 { 8 | if count == 0 { 9 | return []float32{} 10 | } 11 | if count < 0 { 12 | panic("modio.Repeat: negative Repeat count") 13 | } else if len(b)*count/count != len(b) { 14 | panic("modio.Repeat: count causes overflow") 15 | } 16 | nb := make([]float32, len(b)*count) 17 | bp := copy(nb, b) 18 | for bp < len(nb) { 19 | copy(nb[bp:], nb[:bp]) 20 | bp *= 2 21 | } 22 | return nb 23 | } 24 | -------------------------------------------------------------------------------- /modular.go: -------------------------------------------------------------------------------- 1 | package modular 2 | 3 | // Config provides synth configuration and options. 4 | type Config struct { 5 | // SampleRate configures how many samples are played per second. 6 | // 7 | // Defaults to 44.1k. 8 | SampleRate int 9 | 10 | // BufferSize configures the size of module buffers. 11 | // 12 | // Defaults to 44.1k. 13 | BufferSize int 14 | 15 | // DriverBufferSize configures the size of driver buffers. 16 | // 17 | // Defaults to 44.1k. 18 | // 19 | // Note that drivers require a larger buffer to be performant, 20 | // but sound might not begin playing until the buffer is full. 21 | DriverBufferSize int 22 | 23 | // SampleSize configures the sample size in number of samples. 24 | // The default is 512 (about 12ms at the standard sample rate). 25 | SampleSize int 26 | 27 | // Phase configures the phase shift value for supported modules. 28 | Phase float32 29 | } 30 | 31 | // New returns a new modular config with default values. 32 | func New() *Config { 33 | return &Config{ 34 | SampleRate: 44100, 35 | BufferSize: 44100, 36 | DriverBufferSize: 44100, 37 | SampleSize: 512, // ~12ms 38 | } 39 | } 40 | 41 | // Processor is an interface for block processors. 42 | type Processor interface { 43 | // Process processes the audio block in place. 44 | // 45 | // The processor must not retain b. 46 | Process(b []float32) 47 | } 48 | 49 | // Module is an interface for configurable block processors. 50 | type Module interface { 51 | Processor 52 | 53 | // SetConfig updates the module configuration to cfg. 54 | // 55 | // Modules should expect UpdateConfig once for initialization and allow 56 | // arbitrarily many times after. 57 | SetConfig(cfg *Config) error 58 | } 59 | -------------------------------------------------------------------------------- /modules/adsr/adsr.go: -------------------------------------------------------------------------------- 1 | package adsr 2 | 3 | import ( 4 | "math" 5 | "time" 6 | 7 | "github.com/ajzaff/go-modular" 8 | ) 9 | 10 | // ADSR is a basic ADSR envelope generator. 11 | type ADSR struct { 12 | a, d time.Duration 13 | s float32 14 | r time.Duration 15 | 16 | phase phase 17 | begin int 18 | p int 19 | end int 20 | sustain struct { 21 | set bool 22 | pos int 23 | } 24 | gate func() bool 25 | 26 | sampleRate int 27 | } 28 | 29 | func New(a time.Duration, d time.Duration, s float32, r time.Duration) *ADSR { 30 | adsr := &ADSR{ 31 | a: a, 32 | d: d, 33 | s: s, 34 | r: r, 35 | sampleRate: 44100, 36 | } 37 | adsr.Reset() 38 | return adsr 39 | } 40 | 41 | func (a *ADSR) SetConfig(cfg *modular.Config) error { 42 | a.sampleRate = cfg.SampleRate 43 | a.Reset() 44 | return nil 45 | } 46 | 47 | type phase int 48 | 49 | const ( 50 | attack phase = iota 51 | decay 52 | sustain 53 | release 54 | ) 55 | 56 | func (a *ADSR) samples(d time.Duration) int { 57 | return int(math.Round(float64(a.sampleRate) * float64(d.Seconds()))) 58 | } 59 | 60 | // Reset manually resets the ADSR to the attack phase. 61 | // 62 | // Use SetGate to automate the reset. 63 | func (a *ADSR) Reset() { 64 | a.phase = attack 65 | a.begin = 0 66 | a.p = 0 67 | a.end = a.samples(a.a) 68 | } 69 | 70 | // ResetSustain clears the fixed sustain duration. 71 | func (a *ADSR) ResetSustain() { 72 | a.sustain = struct { 73 | set bool 74 | pos int 75 | }{} 76 | } 77 | 78 | // SetSustain optionally sets a fixed duration for the sustain phase. 79 | // 80 | // The duration d should not include attack and decay. 81 | func (a *ADSR) SetSustain(d time.Duration) { 82 | a.sustain = struct { 83 | set bool 84 | pos int 85 | }{true, a.samples(a.a + a.d + d)} 86 | } 87 | 88 | // ResetGate unsets the automatic gate trigger. 89 | func (a *ADSR) ResetGate() { 90 | a.gate = nil 91 | } 92 | 93 | // SetGate sets the automatic gate trigger. 94 | // 95 | // gate will be called once per sample. 96 | // `gate() == true` resets the ADSR. 97 | func (a *ADSR) SetGate(gate func() bool) { 98 | a.gate = gate 99 | } 100 | 101 | // Release releases the note now. 102 | func (a *ADSR) Release() { 103 | if a.phase == sustain { 104 | a.releaseNow() 105 | } 106 | } 107 | 108 | func (a *ADSR) releaseNow() { 109 | a.phase = release 110 | a.begin = a.p 111 | a.end = a.p + a.samples(a.r) 112 | } 113 | 114 | // Envelope returns the next envelope amplitude. 115 | // 116 | // Envelope calls mutate the ADSR. 117 | func (a *ADSR) Envelope() float32 { 118 | switch a.phase { 119 | case attack: 120 | if a.p >= a.end { 121 | a.phase = decay 122 | a.begin = a.p 123 | a.end = a.p + a.samples(a.d) 124 | return 1 125 | } 126 | defer func() { a.p++ }() 127 | // TODO: Configurable attackRamp func. 128 | return float32(a.p-a.begin) / float32(a.end-a.begin) 129 | case decay: 130 | if a.p >= a.end { 131 | a.phase = sustain 132 | a.begin = -1 133 | a.end = -1 134 | return a.s 135 | } 136 | defer func() { a.p++ }() 137 | return 1 - a.s*float32(a.p-a.begin)/float32(a.end-a.begin) 138 | case sustain: 139 | if a.sustain.set && a.p >= a.sustain.pos { 140 | a.releaseNow() 141 | return a.s 142 | } 143 | a.p++ 144 | return a.s 145 | case release: 146 | if a.p >= a.end { 147 | return 0 148 | } 149 | defer func() { a.p++ }() 150 | // TODO: Configurable release ramp. 151 | return a.s - a.s*float32(a.p-a.begin)/float32(a.end-a.begin) 152 | default: 153 | panic("ADSR.Envelope: impossible state reached") 154 | } 155 | } 156 | 157 | // Process convolves the block with the ADSR envelope. 158 | func (a *ADSR) Process(b []float32) { 159 | for i, v := range b { 160 | if a.gate != nil && a.gate() { 161 | a.Reset() 162 | } 163 | b[i] = v * a.Envelope() 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /modules/filter/lowpass.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/ajzaff/go-modular" 7 | "github.com/ajzaff/go-modular/modio" 8 | "github.com/mjibson/go-dsp/fft" 9 | "github.com/mjibson/go-dsp/window" 10 | ) 11 | 12 | type LowPass struct { 13 | blockSize int 14 | rate int 15 | 16 | cutoff func() float32 17 | 18 | filter []complex128 19 | fft modio.FFT 20 | } 21 | 22 | func (f *LowPass) SetConfig(cfg *modular.Config) { 23 | f.blockSize = cfg.BufferSize 24 | f.rate = cfg.SampleRate 25 | } 26 | 27 | func sincfn(x float32) float32 { 28 | return float32(math.Sin(math.Pi*float64(x)) / (math.Pi * float64(x))) 29 | } 30 | 31 | // SetCutoff sets the cutoff closure used in 32 | func (f *LowPass) SetCutoff(cutoff func() float32) { 33 | f.cutoff = cutoff 34 | } 35 | 36 | // Adapted from https://ccrma.stanford.edu/~jos/sasp/Example_1_Low_Pass_Filtering.html. 37 | func (f *LowPass) computeFilter() { 38 | filter := f.filter 39 | if f.filter == nil { 40 | filter = make([]complex128, f.blockSize) 41 | } 42 | for i := range filter { 43 | c := f.cutoff() 44 | t := -(float32(f.blockSize)-1)/2 + float32(i) 45 | v := (2 * c / float32(f.rate)) * sincfn(2*c*t/float32(f.rate)) 46 | filter[i] = complex(float64(v), 0) 47 | } 48 | for i, v := range window.Blackman(f.blockSize) { 49 | filter[i] *= complex(v, 0) 50 | } 51 | f.filter = fft.FFT(filter) 52 | } 53 | 54 | func (f *LowPass) Process(b []float32) { 55 | if len(b) != f.blockSize { 56 | panic("filter.LowPass: Process called with wrong size block") 57 | } 58 | f.computeFilter() 59 | f.fft.Reset() 60 | f.fft.StoreFFT(b) 61 | f.fft.UpdateAll(func(i int, v complex128) complex128 { 62 | return v * f.filter[i] 63 | }) 64 | f.fft.Process(b) 65 | } 66 | -------------------------------------------------------------------------------- /modules/mathmod/math.go: -------------------------------------------------------------------------------- 1 | package mathmod 2 | 3 | type Func func(float32) float32 4 | 5 | func (fn Func) Process(b []float32) { 6 | for i, v := range b { 7 | b[i] = fn(v) 8 | } 9 | } 10 | 11 | type Func2 func(i int, v float32) float32 12 | 13 | func (fn Func2) Process(b []float32) { 14 | for i, v := range b { 15 | b[i] = fn(i, v) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/midi/midi.go: -------------------------------------------------------------------------------- 1 | package midi 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ajzaff/go-modular" 7 | "gitlab.com/gomidi/midi" 8 | "gitlab.com/gomidi/midi/reader" 9 | "gitlab.com/gomidi/rtmididrv" 10 | ) 11 | 12 | // Interface returns midi CVs for the stream of midi messages 13 | // on the single midi input channel (i, ch). 14 | // 15 | // Interface is unbuffered to minimize trigger latency. 16 | type Interface struct { 17 | gate float32 18 | in midi.In 19 | ch uint8 20 | drv *rtmididrv.Driver 21 | } 22 | 23 | // New creates a new midi interface on input i MIDI channel ch. 24 | func New(i, ch uint8) (iface *Interface, err error) { 25 | defer func() { 26 | if err != nil { 27 | err = fmt.Errorf("midi.New: %v", err) 28 | } 29 | }() 30 | 31 | drv, err := rtmididrv.New() 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | ins, err := drv.Ins() 37 | if len(ins) <= int(i) { 38 | return nil, err 39 | } 40 | 41 | in := ins[i] 42 | if err := in.Open(); err != nil { 43 | return nil, err 44 | } 45 | 46 | iface = &Interface{ch: ch, in: in} 47 | return iface, nil 48 | } 49 | 50 | func (i *Interface) SetConfig(cfg *modular.Config) error { 51 | return nil 52 | } 53 | 54 | func (i *Interface) GateKey() (gate, key modular.Processor) { 55 | ch := i.ch 56 | g, k := &midiGateProcessor{}, &midiKeyProcessor{} 57 | rd := reader.New(reader.NoLogger()) 58 | rd.Channel.NoteOn = func(p *reader.Position, channel uint8, key uint8, velocity uint8) { 59 | if channel != ch { 60 | return 61 | } 62 | k.key = float32(key) 63 | g.gate = 1 64 | } 65 | rd.Channel.NoteOff = func(p *reader.Position, channel uint8, key uint8, velocity uint8) { 66 | if channel != ch { 67 | return 68 | } 69 | g.gate = 0 70 | } 71 | if err := rd.ListenTo(i.in); err != nil { 72 | panic(fmt.Errorf("midi.Interface.Key: %v", err)) 73 | } 74 | return g, k 75 | } 76 | 77 | type midiKeyProcessor struct { 78 | in midi.In 79 | key float32 80 | } 81 | 82 | func (r *midiKeyProcessor) Process(b []float32) { 83 | for i := range b { 84 | b[i] = r.key 85 | } 86 | } 87 | 88 | func (r *midiKeyProcessor) Close() error { 89 | if err := r.in.StopListening(); err != nil { 90 | return err 91 | } 92 | return r.in.Close() 93 | } 94 | 95 | type midiGateProcessor struct { 96 | in midi.In 97 | gate float32 98 | } 99 | 100 | func (r *midiGateProcessor) Process(b []float32) { 101 | for i := range b { 102 | b[i] = r.gate 103 | } 104 | } 105 | 106 | func (r *midiGateProcessor) Close() error { 107 | if err := r.in.StopListening(); err != nil { 108 | return err 109 | } 110 | return r.in.Close() 111 | } 112 | 113 | func (i *Interface) Vel() modular.Processor { 114 | panic("midi.Interface.Vel: not implemented") 115 | } 116 | -------------------------------------------------------------------------------- /modules/osc/noise.go: -------------------------------------------------------------------------------- 1 | package osc 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/ajzaff/go-modular" 7 | ) 8 | 9 | const noiseSeed = 1260667865 10 | 11 | // Xorshift32 from p. 4 of Marsaglia, "Xorshift RNGs" 12 | type NoiseOsc struct { 13 | State uint32 14 | A Polarity 15 | C float32 16 | } 17 | 18 | func Noise(a Polarity) *NoiseOsc { 19 | return &NoiseOsc{A: a} 20 | } 21 | 22 | func (*NoiseOsc) SetConfig(*modular.Config) {} 23 | 24 | func (o *NoiseOsc) Next() float32 { 25 | v, x := nextRand(o.State) 26 | o.State = x 27 | return v*float32(o.A) + o.C 28 | } 29 | 30 | func nextRand(x uint32) (v float32, state uint32) { 31 | x ^= x << 13 32 | x ^= x >> 17 33 | x ^= x << 5 34 | return 2*(float32(x)/math.MaxUint32) - 1, x 35 | } 36 | 37 | func (o *NoiseOsc) Process(b []float32) { 38 | x := o.State 39 | if x == 0 { 40 | x = noiseSeed 41 | } 42 | for i := range b { 43 | var v float32 44 | v, x = nextRand(x) 45 | b[i] = v*float32(o.A) + o.C 46 | } 47 | o.State = x 48 | } 49 | -------------------------------------------------------------------------------- /modules/osc/osc.go: -------------------------------------------------------------------------------- 1 | // Package osc provides standard VCO and LFO waveforms. 2 | package osc 3 | 4 | import ( 5 | "math" 6 | 7 | "github.com/ajzaff/go-modular" 8 | "github.com/ajzaff/go-modular/midi" 9 | "github.com/ajzaff/go-modular/modules/mathmod" 10 | ) 11 | 12 | // Polarity controls the polarity of waveform functions. 13 | // 14 | // Negative "inverts" the wave, while positive maintains 15 | // the true shape. Conveniently, non-pole values can be 16 | // used to set the amplitude. 17 | type Polarity float32 18 | 19 | const ( 20 | Negative Polarity = 2*iota - 1 // inverted 21 | Positive // regular 22 | ) 23 | 24 | // Range presents a pipe organ length setting. 25 | // The zero value is LFO and higher values are 26 | // octaves at 32hz doubling at each setting. 27 | type Range int 28 | 29 | const ( 30 | Range64 Range = iota // 1hz 31 | Range32 // 2hz 32 | Range16 // 4hz 33 | Range8 // 8hz 34 | Range4 // 16hz 35 | Range2 // 32hz 36 | ) 37 | 38 | // Tone returns the tone frequency for the range and fine tuning. 39 | func Tone(r Range, fine float32) float32 { 40 | return float32(math.Pow(2, float64(r)+float64(fine))) 41 | } 42 | 43 | // Fine returns the fine tuning constant to tune the oscillators to t at Range8. 44 | func Fine(t midi.Tuning) float32 { 45 | return 12*float32(math.Log2(float64(t.A4Hz()))) - 105 46 | } 47 | 48 | // Osc is a simple wave oscillator. 49 | type Osc struct { 50 | // Func is the underlying oscillator waveform. 51 | // 52 | // Responsible for mapping Voltage to amplitudes. 53 | Func mathmod.Func 54 | // Voltage closure. 55 | // 56 | // Using the one-volt-per-octave standard (e.g.: 0 = MIDI 0, 5.75 = A4). 57 | Voltage func() float32 58 | 59 | p float32 60 | sampleRate float32 61 | } 62 | 63 | // Reset the phase. 64 | func (a *Osc) Reset() { 65 | a.p = 0 66 | } 67 | 68 | const ( 69 | twoPi = 2 * math.Pi 70 | twoOverPi = 2 / math.Pi 71 | ) 72 | 73 | // SetPhase sets the phase to p samples. 74 | func (a *Osc) SetPhase(p float32) { 75 | a.p = p 76 | } 77 | 78 | // Advance the phase by n samples. 79 | func (a *Osc) Advance(n float32) { 80 | a.p += n 81 | } 82 | 83 | // Phase returns the oscillator phase. 84 | func (a *Osc) Phase() float32 { 85 | return a.p 86 | } 87 | 88 | // Next calls the oscillator and advances the phase once. 89 | func (a *Osc) Next() float32 { 90 | defer a.Advance(1) 91 | return a.Func(a.Voltage()) 92 | } 93 | 94 | // Process the block b. 95 | func (a *Osc) Process(b []float32) { 96 | for i := range b { 97 | b[i] = a.Next() 98 | } 99 | } 100 | 101 | func (o *Osc) SetConfig(cfg *modular.Config) error { 102 | o.sampleRate = float32(cfg.SampleRate) 103 | return nil 104 | } 105 | 106 | // Sine outputs an sine audio wave from the linear signal and parameters. 107 | func Sine(a Polarity, r Range, fine float32) *Osc { 108 | osc := &Osc{Voltage: func() float32 { return 0 }} 109 | osc.Func = func(x float32) float32 { 110 | return float32(a) * float32(math.Sin(twoPi*float64(osc.p)*float64(Tone(r, fine+x))/float64(osc.sampleRate))) 111 | } 112 | return osc 113 | } 114 | 115 | // Triangle outputs an triangle wave from the linear signal and parameters. 116 | func Triangle(a Polarity, r Range, fine float32) *Osc { 117 | osc := &Osc{Voltage: func() float32 { return 0 }} 118 | osc.Func = func(x float32) float32 { 119 | return float32(a) * float32(twoOverPi*math.Asin(math.Sin(twoPi*float64(osc.p))*float64(Tone(r, fine+x))/float64(osc.sampleRate))) 120 | } 121 | return osc 122 | } 123 | 124 | // Saw outputs an sawtooth wave from the linear signal and parameters. 125 | func Saw(a Polarity, r Range, fine float32) *Osc { 126 | osc := &Osc{Voltage: func() float32 { return 0 }} 127 | osc.Func = func(x float32) float32 { 128 | return float32(a) * float32(twoOverPi*math.Atan(math.Tan(math.Pi*float64(osc.p))*float64(Tone(r, fine+x))/float64(osc.sampleRate))) 129 | } 130 | return osc 131 | } 132 | 133 | // Square outputs an square wave from the linear signal and parameters. 134 | func Square(a Polarity, r Range, fine float32) *Osc { 135 | return Pulse(a, 0, r, fine, .5) 136 | } 137 | 138 | // Pulse outputs an pulse wave from the linear signal and parameters. 139 | // 140 | // Pulse width w is in the range 0 to 1. 141 | func Pulse(a Polarity, c float32, r Range, fine float32, w float32) *Osc { 142 | osc := &Osc{Voltage: func() float32 { return 0 }} 143 | osc.Func = func(x float32) float32 { 144 | if math.Mod(float64(osc.p)*float64(Tone(r, fine+x))/float64(osc.sampleRate), 2) < 2*float64(w) { 145 | return float32(a) + c 146 | } 147 | return float32(-a) + c 148 | } 149 | return osc 150 | } 151 | -------------------------------------------------------------------------------- /modules/output/otoplayer/oto.go: -------------------------------------------------------------------------------- 1 | package otoplayer 2 | 3 | import ( 4 | "encoding/binary" 5 | "sync" 6 | 7 | "github.com/ajzaff/go-modular" 8 | "github.com/hajimehoshi/oto" 9 | ) 10 | 11 | type Context struct { 12 | ctx *oto.Context 13 | mu sync.Mutex 14 | } 15 | 16 | // New creates a new Oto output context. 17 | func New() *Context { 18 | return &Context{} 19 | } 20 | 21 | func (d *Context) SetConfig(cfg *modular.Config) error { 22 | if err := d.Close(); err != nil { 23 | return err 24 | } 25 | oto, err := oto.NewContext(cfg.SampleRate, 2, 2, cfg.DriverBufferSize) 26 | if err != nil { 27 | panic(err) 28 | } 29 | d.ctx = oto 30 | return nil 31 | } 32 | 33 | func (d *Context) NewStereoPlayer() *StereoPlayer { 34 | d.mu.Lock() 35 | defer d.mu.Unlock() 36 | if d.ctx == nil { 37 | panic("otoplayer.SendStereo called before SetConfig") 38 | } 39 | return &StereoPlayer{d.ctx.NewPlayer()} 40 | } 41 | 42 | func (d *Context) PlayStereo(b []float32) { 43 | p := d.NewStereoPlayer() 44 | defer p.Close() 45 | p.Process(b) 46 | } 47 | 48 | func (d *Context) NewPlayer(ch int) *Player { 49 | d.mu.Lock() 50 | defer d.mu.Unlock() 51 | if d.ctx == nil { 52 | panic("otoplayer.SendStereo called before SetConfig") 53 | } 54 | return &Player{d.ctx.NewPlayer(), ch} 55 | } 56 | 57 | func (d *Context) Play(ch int, b []float32) { 58 | d.mu.Lock() 59 | defer d.mu.Unlock() 60 | if d.ctx == nil { 61 | panic("otoplayer.Send called before SetConfig") 62 | } 63 | p := &Player{d.ctx.NewPlayer(), ch} 64 | defer p.Close() 65 | p.Process(b) 66 | } 67 | 68 | func (d *Context) Close() error { 69 | if d.ctx == nil { 70 | return nil 71 | } 72 | return d.ctx.Close() 73 | } 74 | 75 | type Player struct { 76 | player *oto.Player 77 | ch int 78 | } 79 | 80 | // Send outputs to the speaker using the Oto driver. 81 | func (d *Player) Process(b []float32) { 82 | if d.ch == 1 { 83 | binary.Write(d.player, binary.LittleEndian, uint16(0)) 84 | } 85 | for _, v := range b[:len(b)-1] { 86 | binary.Write(d.player, binary.LittleEndian, convert(v)) 87 | binary.Write(d.player, binary.LittleEndian, uint16(0)) 88 | } 89 | binary.Write(d.player, binary.LittleEndian, convert(b[len(b)-1])) 90 | if d.ch == 0 { 91 | binary.Write(d.player, binary.LittleEndian, uint16(0)) 92 | } 93 | } 94 | 95 | func (d *Player) Close() error { 96 | return d.player.Close() 97 | } 98 | 99 | type StereoPlayer struct { 100 | player *oto.Player 101 | } 102 | 103 | // Send outputs to the speaker using the Oto driver. 104 | func (d *StereoPlayer) Process(b []float32) { 105 | for _, v := range b { 106 | binary.Write(d.player, binary.LittleEndian, convert(v)) 107 | binary.Write(d.player, binary.LittleEndian, convert(v)) 108 | } 109 | } 110 | 111 | func (d *StereoPlayer) Close() error { 112 | return d.player.Close() 113 | } 114 | 115 | func convert(x float32) int16 { 116 | if x < -1 { 117 | x = -1 118 | } 119 | if x > 1 { 120 | x = 1 121 | } 122 | return int16((-1 << 15) + (x+1)/2*(1<<16-1)) 123 | } 124 | -------------------------------------------------------------------------------- /modules/tape/tape.go: -------------------------------------------------------------------------------- 1 | package modular 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | type Buffer struct { 9 | buf []float32 10 | p int64 11 | } 12 | 13 | func (t *Buffer) Pos() int64 { 14 | return t.p 15 | } 16 | 17 | func (t *Buffer) Seek(offset int64, whence int) (int64, error) { 18 | switch whence { 19 | case io.SeekCurrent: 20 | t.p += offset 21 | case io.SeekEnd: 22 | t.p = int64(len(t.buf)) - t.p 23 | case io.SeekStart: 24 | t.p = offset 25 | default: 26 | return 0, fmt.Errorf("tape.Buffer.Seek: invalid whence") 27 | } 28 | if t.p < 0 { 29 | t.p = 0 30 | } else if t.p > int64(len(t.buf)) { 31 | t.p = int64(len(t.buf)) 32 | } 33 | return t.p, nil 34 | } 35 | --------------------------------------------------------------------------------