diff --git a/concurrency/map.go b/concurrency/map.go new file mode 100644 index 0000000..4a33cd4 --- /dev/null +++ b/concurrency/map.go @@ -0,0 +1,52 @@ +package concurrency + +import ( + "sync" + + "github.com/samber/lo" +) + +type ( + SyncMap[T comparable, E any] struct { + mu sync.Mutex + m map[T]E + } +) + +func NewSyncMap[T comparable, E any](m map[T]E) *SyncMap[T, E] { + return &SyncMap[T, E]{ + m: m, + } +} + +func (m *SyncMap[T, E]) Get(idx T) (E, bool) { + m.mu.Lock() + defer func() { + m.mu.Unlock() + }() + e, found := m.m[idx] + return e, found +} + +func (m *SyncMap[T, E]) Set(idx T, e E) { + m.mu.Lock() + defer func() { + m.mu.Unlock() + }() + if m.m == nil { + m.m = make(map[T]E) + } + m.m[idx] = e +} + +func (m *SyncMap[T, E]) Delete(idx T) { + m.mu.Lock() + defer func() { + m.mu.Unlock() + }() + delete(m.m, idx) +} + +func (m *SyncMap[T, E]) Keys() []T { + return lo.Keys(m.m) +} diff --git a/module/envelope.go b/module/envelope.go index b01afdc..af9227e 100644 --- a/module/envelope.go +++ b/module/envelope.go @@ -98,8 +98,8 @@ func (e *Envelope) Update(new *Envelope) { e.initializeFaders() } -func (e *Envelope) Step(t float64, modules ModuleMap) { - gateValue := getMono(modules[e.Gate]) +func (e *Envelope) Step(t float64, modules *ModuleMap) { + gateValue := getMono(modules, e.Gate) switch { case e.gateValue <= 0 && gateValue > 0: diff --git a/module/envelope_test.go b/module/envelope_test.go index 01cd02e..497a0c2 100644 --- a/module/envelope_test.go +++ b/module/envelope_test.go @@ -233,7 +233,7 @@ func TestEnvelope_Step(t *testing.T) { name string e *Envelope t float64 - modules ModuleMap + modules *ModuleMap want float64 wantTriggeredAt float64 wantReleasedAt float64 @@ -250,13 +250,13 @@ func TestEnvelope_Step(t *testing.T) { Level: 0.5, }, t: 2, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "gate": &Module{ current: Output{ Mono: 1, }, }, - }, + }), want: -1, wantTriggeredAt: 2, wantGateValue: 1, @@ -277,13 +277,13 @@ func TestEnvelope_Step(t *testing.T) { gateValue: 1, }, t: 5, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "gate": &Module{ current: Output{ Mono: -1, }, }, - }, + }), want: 0.5, wantTriggeredAt: 2, wantReleasedAt: 5, @@ -303,13 +303,13 @@ func TestEnvelope_Step(t *testing.T) { level: 0.25, }, t: 8, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "gate": &Module{ current: Output{ Mono: -1, }, }, - }, + }), want: -1, wantTriggeredAt: 2, wantReleasedAt: 5, @@ -329,13 +329,13 @@ func TestEnvelope_Step(t *testing.T) { Level: 0.75, }, t: 8, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "gate": &Module{ current: Output{ Mono: 1, }, }, - }, + }), want: 0.5, wantTriggeredAt: 2, wantReleasedAt: 0, diff --git a/module/filter.go b/module/filter.go index 84ebce1..261a3d0 100644 --- a/module/filter.go +++ b/module/filter.go @@ -38,13 +38,6 @@ const ( filterTypeLowPass filterType = "LowPass" filterTypeHighPass filterType = "HighPass" filterTypeBandPass filterType = "BandPass" - - gain = -50 - slope = 0.99 -) - -var ( - amp = math.Pow(10, gain/40) ) func (m FilterMap) Initialize(sampleRate float64) error { @@ -110,15 +103,15 @@ func (f *Filter) Update(new *Filter) { f.initializeFaders() } -func (f *Filter) Step(modules ModuleMap) { +func (f *Filter) Step(modules *ModuleMap) { freq := f.Freq if f.CV != "" { - freq = cv(freqRange, getMono(modules[f.CV])) + freq = cv(freqRange, getMono(modules, f.CV)) } - freq = modulate(freq, freqRange, getMono(modules[f.Mod])) + freq = modulate(freq, freqRange, getMono(modules, f.Mod)) f.calculateCoeffs(freq) - x := getMono(modules[f.In]) + x := getMono(modules, f.In) y := calc.Limit(f.tap(x, freq), outputRange) f.current = Output{ @@ -232,8 +225,7 @@ func getOmega(freq float64, sampleRate float64) float64 { } func getAlphaLPHP(omega float64) float64 { - rootArg := (amp+1/amp)*(1/slope-slope) + 2 - root := math.Sqrt(rootArg) + root := math.Sqrt(2) factor := math.Sin(omega) / 2 return factor * root } diff --git a/module/gate.go b/module/gate.go index 98924dd..0984faa 100644 --- a/module/gate.go +++ b/module/gate.go @@ -72,7 +72,7 @@ func (g *Gate) Update(new *Gate) { } } -func (g *Gate) Step(modules ModuleMap) { +func (g *Gate) Step(modules *ModuleMap) { if len(g.Signal) < 1 { return } @@ -86,10 +86,10 @@ func (g *Gate) Step(modules ModuleMap) { bpm := g.BPM if g.CV != "" { - bpm = cv(bpmRange, getMono(modules[g.CV])) + bpm = cv(bpmRange, getMono(modules, g.CV)) } - bpm = modulate(bpm, bpmRange, getMono(modules[g.Mod])) + bpm = modulate(bpm, bpmRange, getMono(modules, g.Mod)) spb := samplesPerBeat(g.sampleRate, bpm) if spb == 0 { return diff --git a/module/gate_test.go b/module/gate_test.go index 789e585..c2dbfa4 100644 --- a/module/gate_test.go +++ b/module/gate_test.go @@ -39,7 +39,7 @@ func TestGate_Step(t *testing.T) { tests := []struct { name string g *Gate - modules ModuleMap + modules *ModuleMap want float64 wantIdx float64 }{ @@ -52,6 +52,7 @@ func TestGate_Step(t *testing.T) { sampleRate: sampleRate, idx: 0, }, + modules: &ModuleMap{}, want: 1, wantIdx: 0, }, @@ -63,6 +64,7 @@ func TestGate_Step(t *testing.T) { sampleRate: sampleRate, idx: 0, }, + modules: &ModuleMap{}, want: -1, wantIdx: 1 / sampleRate, }, @@ -75,13 +77,13 @@ func TestGate_Step(t *testing.T) { idx: 1, CV: "cv", }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "cv": &Module{ current: Output{ Mono: calc.Transpose(120, bpmRange, outputRange), }, }, - }, + }), want: 1, wantIdx: 1 + 2/sampleRate, }, @@ -94,13 +96,13 @@ func TestGate_Step(t *testing.T) { idx: 2, Mod: "mod", }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "mod": &Module{ current: Output{ Mono: -0.03, }, }, - }, + }), want: -1, wantIdx: 2 + 0.5/sampleRate, }, diff --git a/module/mixer.go b/module/mixer.go index 16691d5..de65060 100644 --- a/module/mixer.go +++ b/module/mixer.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/iljarotar/synth/calc" + "github.com/iljarotar/synth/concurrency" ) type ( @@ -15,10 +16,11 @@ type ( In map[string]float64 `yaml:"in"` Fade float64 `yaml:"fade"` + in *concurrency.SyncMap[string, float64] sampleRate float64 gainFader *fader - inputFaders map[string]*fader + inputFaders *concurrency.SyncMap[string, *fader] } MixerMap map[string]*Mixer @@ -46,14 +48,16 @@ func (m *Mixer) initialize(sampleRate float64) error { target: m.Gain, } - m.inputFaders = map[string]*fader{} - for mod, gain := range m.In { - m.In[mod] = calc.Limit(gain, inputGainRange) + m.in = concurrency.NewSyncMap(m.In) + m.inputFaders = concurrency.NewSyncMap(map[string]*fader{}) + for _, name := range m.in.Keys() { + gain, _ := m.in.Get(name) + m.in.Set(name, calc.Limit(gain, inputGainRange)) - m.inputFaders[mod] = &fader{ + m.inputFaders.Set(name, &fader{ current: gain, target: gain, - } + }) } m.initializeFaders() @@ -68,16 +72,18 @@ func (m *Mixer) Update(new *Mixer) { m.CV = new.CV m.Mod = new.Mod m.Fade = new.Fade + m.In = new.In m.updateGains(new) } -func (m *Mixer) Step(modules ModuleMap) { +func (m *Mixer) Step(modules *ModuleMap) { var ( left, right, mono float64 ) - for name, gain := range m.In { - if mod, ok := modules[name]; ok { + for _, name := range m.in.Keys() { + gain, _ := m.in.Get(name) + if mod, _ := modules.Get(name); mod != nil { left += mod.Current().Left * gain right += mod.Current().Right * gain mono += mod.Current().Mono * gain @@ -86,9 +92,9 @@ func (m *Mixer) Step(modules ModuleMap) { gain := m.Gain if m.CV != "" { - gain = cv(gainRange, getMono(modules[m.CV])) + gain = cv(gainRange, getMono(modules, m.CV)) } - gain = modulate(gain, gainRange, getMono(modules[m.Mod])) + gain = modulate(gain, gainRange, getMono(modules, m.Mod)) left = calc.Limit(left*gain, outputRange) right = calc.Limit(right*gain, outputRange) @@ -108,15 +114,16 @@ func (m *Mixer) fade() { m.Gain = m.gainFader.fade() } - for mod, f := range m.inputFaders { + for _, name := range m.inputFaders.Keys() { + f, _ := m.inputFaders.Get(name) if f == nil { continue } - m.In[mod] = f.fade() + m.in.Set(name, f.fade()) if f.current == 0 && f.target == 0 { - delete(m.inputFaders, mod) - delete(m.In, mod) + m.inputFaders.Delete(name) + m.in.Delete(name) } } } @@ -126,9 +133,9 @@ func (m *Mixer) initializeFaders() { m.gainFader.initialize(m.Fade, m.sampleRate) } - for _, fader := range m.inputFaders { - if fader != nil { - fader.initialize(m.Fade, m.sampleRate) + for _, name := range m.inputFaders.Keys() { + if f, _ := m.inputFaders.Get(name); f != nil { + f.initialize(m.Fade, m.sampleRate) } } } @@ -139,33 +146,35 @@ func (m *Mixer) updateGains(new *Mixer) { } if m.inputFaders == nil { - m.inputFaders = map[string]*fader{} + m.inputFaders = concurrency.NewSyncMap(map[string]*fader{}) } - if m.In == nil { - m.In = map[string]float64{} + if m.in == nil { + m.in = concurrency.NewSyncMap(map[string]float64{}) } - for mod, gain := range new.In { - f, ok := m.inputFaders[mod] + for _, name := range new.in.Keys() { + gain, _ := new.in.Get(name) - if ok && f != nil { + f, _ := m.inputFaders.Get(name) + if f != nil { f.target = gain continue } - m.inputFaders[mod] = &fader{ + m.inputFaders.Set(name, &fader{ current: 0, target: gain, - } - m.In[mod] = 0 + }) + m.in.Set(name, 0) } - for mod, f := range m.inputFaders { + for _, name := range m.inputFaders.Keys() { + f, _ := m.inputFaders.Get(name) if f == nil { continue } - if _, ok := new.In[mod]; !ok { + if _, ok := new.in.Get(name); !ok { f.target = 0 } } diff --git a/module/mixer_test.go b/module/mixer_test.go index 4fafbcc..e34d507 100644 --- a/module/mixer_test.go +++ b/module/mixer_test.go @@ -4,25 +4,29 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/iljarotar/synth/concurrency" ) func TestMixer_Step(t *testing.T) { tests := []struct { name string m *Mixer - modules ModuleMap + modules *ModuleMap want Output }{ { name: "no input", m: &Mixer{ - Gain: 0, - In: map[string]float64{}, - sampleRate: 1, + Gain: 0, + In: map[string]float64{}, + sampleRate: 1, + inputFaders: concurrency.NewSyncMap(map[string]*fader{}), + in: concurrency.NewSyncMap(map[string]float64{}), }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Module{}, - }, + }), want: Output{}, }, { @@ -32,13 +36,15 @@ func TestMixer_Step(t *testing.T) { In: map[string]float64{ "sine": 1, }, - sampleRate: 1, + sampleRate: 1, + inputFaders: concurrency.NewSyncMap(map[string]*fader{}), + in: concurrency.NewSyncMap(map[string]float64{}), }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Oscillator{ Module: Module{}, }, - }, + }), want: Output{}, }, { @@ -48,9 +54,11 @@ func TestMixer_Step(t *testing.T) { In: map[string]float64{ "in": 0, }, - sampleRate: 1, + sampleRate: 1, + inputFaders: concurrency.NewSyncMap(map[string]*fader{}), + in: concurrency.NewSyncMap(map[string]float64{}), }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Module{ current: Output{ Mono: 1, @@ -58,7 +66,7 @@ func TestMixer_Step(t *testing.T) { Right: 0.5, }, }, - }, + }), want: Output{}, }, { @@ -68,9 +76,13 @@ func TestMixer_Step(t *testing.T) { In: map[string]float64{ "in": 1, }, - sampleRate: 1, + sampleRate: 1, + inputFaders: concurrency.NewSyncMap(map[string]*fader{}), + in: concurrency.NewSyncMap(map[string]float64{ + "in": 1, + }), }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Module{ current: Output{ Mono: 1, @@ -78,7 +90,7 @@ func TestMixer_Step(t *testing.T) { Right: 0.5, }, }, - }, + }), want: Output{ Mono: 1, Left: 0.5, @@ -93,9 +105,13 @@ func TestMixer_Step(t *testing.T) { In: map[string]float64{ "in": 1, }, - sampleRate: 1, + sampleRate: 1, + inputFaders: concurrency.NewSyncMap(map[string]*fader{}), + in: concurrency.NewSyncMap(map[string]float64{ + "in": 1, + }), }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Module{ current: Output{ Mono: 1, @@ -110,7 +126,7 @@ func TestMixer_Step(t *testing.T) { Right: 0.25, }, }, - }, + }), want: Output{ Mono: 1 * 0.75, Left: 0.5 * 0.75, @@ -125,9 +141,13 @@ func TestMixer_Step(t *testing.T) { In: map[string]float64{ "in": 1, }, - sampleRate: 1, + sampleRate: 1, + inputFaders: concurrency.NewSyncMap(map[string]*fader{}), + in: concurrency.NewSyncMap(map[string]float64{ + "in": 1, + }), }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Module{ current: Output{ Mono: 1, @@ -142,7 +162,7 @@ func TestMixer_Step(t *testing.T) { Right: 0.25, }, }, - }, + }), want: Output{ Mono: 1 * 0.75, Left: 0.5 * 0.75, @@ -191,13 +211,16 @@ func TestMixer_Update(t *testing.T) { target: 1, step: 0.5, }, - inputFaders: map[string]*fader{ + inputFaders: concurrency.NewSyncMap(map[string]*fader{ "in1": { current: 1, target: 1, step: 0.5, }, - }, + }), + in: concurrency.NewSyncMap(map[string]float64{ + "in": 1, + }), }, new: nil, want: &Mixer{ @@ -219,13 +242,16 @@ func TestMixer_Update(t *testing.T) { target: 1, step: 0.5, }, - inputFaders: map[string]*fader{ + inputFaders: concurrency.NewSyncMap(map[string]*fader{ "in1": { current: 1, target: 1, step: 0.5, }, - }, + }), + in: concurrency.NewSyncMap(map[string]float64{ + "in": 1, + }), }, }, { @@ -250,7 +276,7 @@ func TestMixer_Update(t *testing.T) { target: 1, step: 0.5, }, - inputFaders: map[string]*fader{ + inputFaders: concurrency.NewSyncMap(map[string]*fader{ "in1": { current: 1, target: 1, @@ -261,7 +287,11 @@ func TestMixer_Update(t *testing.T) { target: 1, step: 0.5, }, - }, + }), + in: concurrency.NewSyncMap(map[string]float64{ + "in1": 1, + "in2": 1, + }), }, new: &Mixer{ Gain: 0.5, @@ -272,6 +302,10 @@ func TestMixer_Update(t *testing.T) { "in3": 0.5, }, Fade: 2, + in: concurrency.NewSyncMap(map[string]float64{ + "in1": 0.5, + "in3": 0.5, + }), }, want: &Mixer{ Module: Module{ @@ -283,9 +317,8 @@ func TestMixer_Update(t *testing.T) { CV: "new-cv", Mod: "new-mod", In: map[string]float64{ - "in1": 1, - "in2": 1, - "in3": 0, + "in1": 0.5, + "in3": 0.5, }, Fade: 2, sampleRate: sampleRate, @@ -294,7 +327,7 @@ func TestMixer_Update(t *testing.T) { target: 0.5, step: -0.25 / sampleRate, }, - inputFaders: map[string]*fader{ + inputFaders: concurrency.NewSyncMap(map[string]*fader{ "in1": { current: 1, target: 0.5, @@ -310,14 +343,28 @@ func TestMixer_Update(t *testing.T) { target: 0.5, step: 0.25 / sampleRate, }, - }, + }), + in: concurrency.NewSyncMap(map[string]float64{ + "in1": 1, + "in2": 1, + "in3": 0, + }), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.m.Update(tt.new) - if diff := cmp.Diff(tt.want, tt.m, cmp.AllowUnexported(Module{}, Mixer{}, fader{})); diff != "" { + if diff := cmp.Diff( + tt.want, + tt.m, + cmp.AllowUnexported( + Module{}, + Mixer{}, + fader{}, + ), + cmpopts.IgnoreUnexported(concurrency.SyncMap[string, *fader]{}, concurrency.SyncMap[string, float64]{}), + ); diff != "" { t.Errorf("Mixer.Update() diff = %s", diff) } }) @@ -343,7 +390,7 @@ func TestMixer_fade(t *testing.T) { target: 1, step: 0.5, }, - inputFaders: map[string]*fader{ + inputFaders: concurrency.NewSyncMap(map[string]*fader{ "in1": { current: 1, target: 1, @@ -354,7 +401,11 @@ func TestMixer_fade(t *testing.T) { target: 1, step: 0.5, }, - }, + }), + in: concurrency.NewSyncMap(map[string]float64{ + "in1": 1, + "in2": 1, + }), }, want: &Mixer{ Gain: 1, @@ -366,7 +417,7 @@ func TestMixer_fade(t *testing.T) { current: 1, target: 1, }, - inputFaders: map[string]*fader{ + inputFaders: concurrency.NewSyncMap(map[string]*fader{ "in1": { current: 1, target: 1, @@ -375,7 +426,11 @@ func TestMixer_fade(t *testing.T) { current: 1, target: 1, }, - }, + }), + in: concurrency.NewSyncMap(map[string]float64{ + "in1": 1, + "in2": 1, + }), }, }, { @@ -391,7 +446,7 @@ func TestMixer_fade(t *testing.T) { target: 0.5, step: -0.1, }, - inputFaders: map[string]*fader{ + inputFaders: concurrency.NewSyncMap(map[string]*fader{ "in1": { current: 1, target: 0.5, @@ -402,32 +457,49 @@ func TestMixer_fade(t *testing.T) { target: 0, step: -0.2, }, - }, + }), + in: concurrency.NewSyncMap(map[string]float64{ + "in1": 1, + "in2": 0.1, + }), }, want: &Mixer{ Gain: 0.9, In: map[string]float64{ - "in1": 0.8, + "in1": 1, + "in2": 0.1, }, gainFader: &fader{ current: 0.9, target: 0.5, step: -0.1, }, - inputFaders: map[string]*fader{ + inputFaders: concurrency.NewSyncMap(map[string]*fader{ "in1": { current: 0.8, target: 0.5, step: -0.2, }, - }, + }), + in: concurrency.NewSyncMap(map[string]float64{ + "in1": 0.8, + }), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.m.fade() - if diff := cmp.Diff(tt.want, tt.m, cmp.AllowUnexported(Module{}, Mixer{}, fader{})); diff != "" { + if diff := cmp.Diff( + tt.want, + tt.m, + cmp.AllowUnexported( + Module{}, + Mixer{}, + fader{}, + ), + cmpopts.IgnoreUnexported(concurrency.SyncMap[string, *fader]{}, concurrency.SyncMap[string, float64]{}), + ); diff != "" { t.Errorf("Mixer.fade() diff = %s", diff) } }) diff --git a/module/module.go b/module/module.go index 41c0bda..1ad0cad 100644 --- a/module/module.go +++ b/module/module.go @@ -1,13 +1,16 @@ package module -import "github.com/iljarotar/synth/calc" +import ( + "github.com/iljarotar/synth/calc" + "github.com/iljarotar/synth/concurrency" +) type ( IModule interface { Current() Output } - ModuleMap map[string]IModule + ModuleMap = concurrency.SyncMap[string, IModule] Module struct { current Output @@ -61,6 +64,10 @@ var ( } ) +func NewModuleMap(m map[string]IModule) *ModuleMap { + return concurrency.NewSyncMap(m) +} + func (m *Module) Current() Output { return m.current } @@ -81,7 +88,8 @@ func cv(rng calc.Range, val float64) float64 { return calc.Transpose(val, outputRange, rng) } -func getMono(mod IModule) float64 { +func getMono(modules *ModuleMap, name string) float64 { + mod, _ := modules.Get(name) if mod == nil { return 0 } diff --git a/module/oscillator.go b/module/oscillator.go index 0741665..fa82388 100644 --- a/module/oscillator.go +++ b/module/oscillator.go @@ -93,15 +93,15 @@ func (o *Oscillator) Update(new *Oscillator) { o.initializeFaders() } -func (o *Oscillator) Step(modules ModuleMap) { +func (o *Oscillator) Step(modules *ModuleMap) { twoPi := 2 * math.Pi freq := o.Freq if o.CV != "" { - freq = cv(freqRange, getMono(modules[o.CV])) + freq = cv(freqRange, getMono(modules, o.CV)) } c := twoPi * o.Phase - mod := math.Pow(2, getMono(modules[o.Mod])) + mod := math.Pow(2, getMono(modules, o.Mod)) val := o.signal(o.arg + c) o.current = Output{ diff --git a/module/oscillator_test.go b/module/oscillator_test.go index 8c44db5..c12ff09 100644 --- a/module/oscillator_test.go +++ b/module/oscillator_test.go @@ -13,14 +13,14 @@ func TestOscillator_Step(t *testing.T) { tests := []struct { name string - modules ModuleMap + modules *ModuleMap o *Oscillator want float64 wantArg float64 }{ { name: "all zero", - modules: ModuleMap{}, + modules: &ModuleMap{}, o: &Oscillator{ Freq: 200, signal: SineSignalFunc(), @@ -31,7 +31,7 @@ func TestOscillator_Step(t *testing.T) { }, { name: "after one second", - modules: ModuleMap{}, + modules: &ModuleMap{}, o: &Oscillator{ Freq: 200, signal: SineSignalFunc(), @@ -44,7 +44,7 @@ func TestOscillator_Step(t *testing.T) { }, { name: "phase shift", - modules: ModuleMap{}, + modules: &ModuleMap{}, o: &Oscillator{ Freq: 200, signal: SineSignalFunc(), @@ -57,13 +57,13 @@ func TestOscillator_Step(t *testing.T) { }, { name: "modulation", - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "mod": &Module{ current: Output{ Mono: 1, }, }, - }, + }), o: &Oscillator{ Freq: 200, signal: SineSignalFunc(), @@ -77,13 +77,13 @@ func TestOscillator_Step(t *testing.T) { }, { name: "cv", - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "cv": &Module{ current: Output{ Mono: 1, }, }, - }, + }), o: &Oscillator{ Freq: 200, signal: SineSignalFunc(), diff --git a/module/pan.go b/module/pan.go index 4fdcb92..c1364ae 100644 --- a/module/pan.go +++ b/module/pan.go @@ -54,10 +54,10 @@ func (p *Pan) Update(new *Pan) { } } -func (p *Pan) Step(modules ModuleMap) { - pan := modulate(p.Pan, panRange, getMono(modules[p.Mod])) +func (p *Pan) Step(modules *ModuleMap) { + pan := modulate(p.Pan, panRange, getMono(modules, p.Mod)) percent := calc.Percentage(pan, panRange) - in := getMono(modules[p.In]) + in := getMono(modules, p.In) p.current = Output{ Mono: in, diff --git a/module/pan_test.go b/module/pan_test.go index 7e235b2..dc31c14 100644 --- a/module/pan_test.go +++ b/module/pan_test.go @@ -10,7 +10,7 @@ func TestPan_Step(t *testing.T) { tests := []struct { name string p *Pan - modules ModuleMap + modules *ModuleMap want Output }{ { @@ -19,13 +19,13 @@ func TestPan_Step(t *testing.T) { Pan: 0.5, In: "in", }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Module{ current: Output{ Mono: 1, }, }, - }, + }), want: Output{ Mono: 1, Left: 0.25, @@ -39,7 +39,7 @@ func TestPan_Step(t *testing.T) { Mod: "mod", In: "in", }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Module{ current: Output{ Mono: 1, @@ -50,7 +50,7 @@ func TestPan_Step(t *testing.T) { Mono: 0.25, }, }, - }, + }), want: Output{ Mono: 1, Left: 0.25, diff --git a/module/sampler.go b/module/sampler.go index 6d91e5d..2963226 100644 --- a/module/sampler.go +++ b/module/sampler.go @@ -20,11 +20,11 @@ func (s *Sampler) Update(new *Sampler) { s.Trigger = new.Trigger } -func (s *Sampler) Step(modules ModuleMap) { - triggerValue := getMono(modules[s.Trigger]) +func (s *Sampler) Step(modules *ModuleMap) { + triggerValue := getMono(modules, s.Trigger) if triggerValue > 0 && s.triggerValue <= 0 { - val := getMono(modules[s.In]) + val := getMono(modules, s.In) s.current = Output{ Mono: val, diff --git a/module/sampler_test.go b/module/sampler_test.go index 8ae2c6e..fa1d261 100644 --- a/module/sampler_test.go +++ b/module/sampler_test.go @@ -10,7 +10,7 @@ func TestSampler_Step(t *testing.T) { tests := []struct { name string s *Sampler - modules ModuleMap + modules *ModuleMap want float64 wantTrigger float64 }{ @@ -26,7 +26,7 @@ func TestSampler_Step(t *testing.T) { Trigger: "trigger", triggerValue: 1, }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Module{ current: Output{ Mono: 1, @@ -37,7 +37,7 @@ func TestSampler_Step(t *testing.T) { Mono: 0, }, }, - }, + }), want: 0.5, wantTrigger: 0, }, @@ -53,7 +53,7 @@ func TestSampler_Step(t *testing.T) { Trigger: "trigger", triggerValue: -1, }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Module{ current: Output{ Mono: 1, @@ -64,7 +64,7 @@ func TestSampler_Step(t *testing.T) { Mono: 1, }, }, - }, + }), want: 1, wantTrigger: 1, }, @@ -80,7 +80,7 @@ func TestSampler_Step(t *testing.T) { Trigger: "trigger", triggerValue: 1, }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "in": &Module{ current: Output{ Mono: 1, @@ -91,7 +91,7 @@ func TestSampler_Step(t *testing.T) { Mono: 0.5, }, }, - }, + }), want: 0.5, wantTrigger: 0.5, }, diff --git a/module/sequencer.go b/module/sequencer.go index 8a48408..5017ad1 100644 --- a/module/sequencer.go +++ b/module/sequencer.go @@ -72,12 +72,12 @@ func (s *Sequencer) Update(new *Sequencer) { } } -func (s *Sequencer) Step(modules ModuleMap) { +func (s *Sequencer) Step(modules *ModuleMap) { if len(s.sequence) < 1 { return } - triggerValue := getMono(modules[s.Trigger]) + triggerValue := getMono(modules, s.Trigger) if triggerValue > 0 && s.triggerValue <= 0 { if s.Randomize { s.idx = rand.Intn(len(s.sequence)) diff --git a/module/sequencer_test.go b/module/sequencer_test.go index 181fbd5..b8f6a45 100644 --- a/module/sequencer_test.go +++ b/module/sequencer_test.go @@ -170,7 +170,7 @@ func TestSequencer_Step(t *testing.T) { tests := []struct { name string s *Sequencer - modules ModuleMap + modules *ModuleMap want float64 wantTrigger float64 }{ @@ -182,6 +182,7 @@ func TestSequencer_Step(t *testing.T) { idx: -1, triggerValue: 0, }, + modules: &ModuleMap{}, want: -1, wantTrigger: 0, }, @@ -194,13 +195,13 @@ func TestSequencer_Step(t *testing.T) { idx: 1, triggerValue: 0, }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "trigger": &Module{ current: Output{ Mono: 1, }, }, - }, + }), want: calc.Transpose(110, freqRange, outputRange), wantTrigger: 1, }, @@ -213,13 +214,13 @@ func TestSequencer_Step(t *testing.T) { idx: 2, triggerValue: 0, }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "trigger": &Module{ current: Output{ Mono: 1, }, }, - }, + }), want: calc.Transpose(440, freqRange, outputRange), wantTrigger: 1, }, diff --git a/module/wavetable.go b/module/wavetable.go index cef672f..e6039cd 100644 --- a/module/wavetable.go +++ b/module/wavetable.go @@ -67,7 +67,7 @@ func (w *Wavetable) Update(new *Wavetable) { } } -func (w *Wavetable) Step(modules ModuleMap) { +func (w *Wavetable) Step(modules *ModuleMap) { if len(w.Signal) < 1 { return } @@ -80,10 +80,10 @@ func (w *Wavetable) Step(modules ModuleMap) { freq := w.Freq if w.CV != "" { - freq = cv(freqRange, getMono(modules[w.CV])) + freq = cv(freqRange, getMono(modules, w.CV)) } - mod := math.Pow(2, getMono(modules[w.Mod])) + mod := math.Pow(2, getMono(modules, w.Mod)) w.idx += freq * mod * float64(len(w.Signal)) / w.sampleRate w.fade() diff --git a/module/wavetable_test.go b/module/wavetable_test.go index 791c749..0e6a1b2 100644 --- a/module/wavetable_test.go +++ b/module/wavetable_test.go @@ -37,7 +37,7 @@ func TestWavetable_Step(t *testing.T) { tests := []struct { name string w *Wavetable - modules ModuleMap + modules *ModuleMap want float64 wantIdx float64 }{ @@ -49,7 +49,7 @@ func TestWavetable_Step(t *testing.T) { sampleRate: sampleRate, idx: 44100.0 / 8, }, - modules: ModuleMap{}, + modules: &ModuleMap{}, want: 0, wantIdx: 44100.0/8 + 8/44100.0, }, @@ -62,13 +62,13 @@ func TestWavetable_Step(t *testing.T) { sampleRate: sampleRate, idx: 0, }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "cv": &Module{ current: Output{ Mono: 0, }, }, - }, + }), want: 1, wantIdx: 4 * freqRange.Max / (2 * sampleRate), }, @@ -81,13 +81,13 @@ func TestWavetable_Step(t *testing.T) { sampleRate: sampleRate, idx: 2.5, }, - modules: ModuleMap{ + modules: NewModuleMap(map[string]IModule{ "mod": &Module{ current: Output{ Mono: 1, }, }, - }, + }), want: -1, wantIdx: 2.5 + 16/sampleRate, }, diff --git a/synth/synth.go b/synth/synth.go index 64666d7..1074bd3 100644 --- a/synth/synth.go +++ b/synth/synth.go @@ -38,7 +38,7 @@ type Synth struct { volumeStep float64 notifyFadeoutChan chan<- bool logger *log.Logger - modules module.ModuleMap + modules *module.ModuleMap envelopes []*module.Envelope filters []*module.Filter @@ -98,11 +98,15 @@ func (s *Synth) Update(from *Synth) error { } func (s *Synth) GetOutput() Output { + if s.modules == nil { + return Output{} + } + s.step() s.adjustVolume() out := Output{Time: s.Time} - if mod, ok := s.modules[s.Out]; ok { + if mod, _ := s.modules.Get(s.Out); mod != nil { out.Left = mod.Current().Left * s.Volume out.Right = mod.Current().Right * s.Volume out.Mono = mod.Current().Mono * s.Volume @@ -225,67 +229,69 @@ func secondsToStep(seconds, delta, sampleRate float64) float64 { } func (s *Synth) makeModulesMap() { - s.modules = module.ModuleMap{} + if s.modules == nil { + s.modules = module.NewModuleMap(map[string]module.IModule{}) + } for name, e := range s.Envelopes { if e == nil { continue } - s.modules[name] = e + s.modules.Set(name, e) } for name, f := range s.Filters { if f == nil { continue } - s.modules[name] = f + s.modules.Set(name, f) } for name, g := range s.Gates { if g == nil { continue } - s.modules[name] = g + s.modules.Set(name, g) } for name, m := range s.Mixers { if m == nil { continue } - s.modules[name] = m + s.modules.Set(name, m) } for name, n := range s.Noises { if n == nil { continue } - s.modules[name] = n + s.modules.Set(name, n) } for name, osc := range s.Oscillators { if osc == nil { continue } - s.modules[name] = osc + s.modules.Set(name, osc) } for name, p := range s.Pans { if p == nil { continue } - s.modules[name] = p + s.modules.Set(name, p) } for name, smplr := range s.Samplers { if smplr == nil { continue } - s.modules[name] = smplr + s.modules.Set(name, smplr) } for name, seq := range s.Sequencers { if seq == nil { continue } - s.modules[name] = seq + s.modules.Set(name, seq) } for name, w := range s.Wavetables { if w == nil { continue } - s.modules[name] = w + s.modules.Set(name, w) } } @@ -303,10 +309,14 @@ func (s *Synth) flattenModules() { } func (s *Synth) deleteOldModules(new *Synth) { + if s.modules == nil { + s.modules = module.NewModuleMap(map[string]module.IModule{}) + } + for name, env := range s.Envelopes { if _, ok := new.Envelopes[name]; !ok { delete(s.Envelopes, name) - delete(s.modules, name) + s.modules.Delete(name) s.envelopes = slices.DeleteFunc(s.envelopes, func(e *module.Envelope) bool { return env == e }) @@ -315,7 +325,7 @@ func (s *Synth) deleteOldModules(new *Synth) { for name, filter := range s.Filters { if _, ok := new.Filters[name]; !ok { delete(s.Filters, name) - delete(s.modules, name) + s.modules.Delete(name) s.filters = slices.DeleteFunc(s.filters, func(f *module.Filter) bool { return filter == f }) @@ -324,7 +334,7 @@ func (s *Synth) deleteOldModules(new *Synth) { for name, gate := range s.Gates { if _, ok := new.Gates[name]; !ok { delete(s.Gates, name) - delete(s.modules, name) + s.modules.Delete(name) s.gates = slices.DeleteFunc(s.gates, func(g *module.Gate) bool { return gate == g }) @@ -333,7 +343,7 @@ func (s *Synth) deleteOldModules(new *Synth) { for name, mixer := range s.Mixers { if _, ok := new.Mixers[name]; !ok { delete(s.Mixers, name) - delete(s.modules, name) + s.modules.Delete(name) s.mixers = slices.DeleteFunc(s.mixers, func(m *module.Mixer) bool { return mixer == m }) @@ -342,7 +352,7 @@ func (s *Synth) deleteOldModules(new *Synth) { for name, noise := range s.Noises { if _, ok := new.Noises[name]; !ok { delete(s.Noises, name) - delete(s.modules, name) + s.modules.Delete(name) s.noises = slices.DeleteFunc(s.noises, func(n *module.Noise) bool { return noise == n }) @@ -351,7 +361,7 @@ func (s *Synth) deleteOldModules(new *Synth) { for name, osc := range s.Oscillators { if _, ok := new.Oscillators[name]; !ok { delete(s.Oscillators, name) - delete(s.modules, name) + s.modules.Delete(name) s.oscillators = slices.DeleteFunc(s.oscillators, func(o *module.Oscillator) bool { return osc == o }) @@ -360,7 +370,7 @@ func (s *Synth) deleteOldModules(new *Synth) { for name, pan := range s.Pans { if _, ok := new.Pans[name]; !ok { delete(s.Pans, name) - delete(s.modules, name) + s.modules.Delete(name) s.pans = slices.DeleteFunc(s.pans, func(p *module.Pan) bool { return pan == p }) @@ -369,7 +379,7 @@ func (s *Synth) deleteOldModules(new *Synth) { for name, sampler := range s.Samplers { if _, ok := new.Samplers[name]; !ok { delete(s.Samplers, name) - delete(s.modules, name) + s.modules.Delete(name) s.samplers = slices.DeleteFunc(s.samplers, func(smplr *module.Sampler) bool { return sampler == smplr }) @@ -378,7 +388,7 @@ func (s *Synth) deleteOldModules(new *Synth) { for name, seq := range s.Sequencers { if _, ok := new.Sequencers[name]; !ok { delete(s.Sequencers, name) - delete(s.modules, name) + s.modules.Delete(name) s.sequencers = slices.DeleteFunc(s.sequencers, func(sq *module.Sequencer) bool { return seq == sq }) @@ -387,7 +397,7 @@ func (s *Synth) deleteOldModules(new *Synth) { for name, wt := range s.Wavetables { if _, ok := new.Wavetables[name]; !ok { delete(s.Wavetables, name) - delete(s.modules, name) + s.modules.Delete(name) s.wavetables = slices.DeleteFunc(s.wavetables, func(w *module.Wavetable) bool { return wt == w }) @@ -396,74 +406,78 @@ func (s *Synth) deleteOldModules(new *Synth) { } func (s *Synth) addNewModules(new *Synth) { - for name, env := range new.Envelopes { + if s.modules == nil { + s.modules = module.NewModuleMap(map[string]module.IModule{}) + } + + for name, e := range new.Envelopes { if _, ok := s.Envelopes[name]; !ok { - s.Envelopes[name] = env - s.envelopes = append(s.envelopes, env) - s.modules[name] = env + s.Envelopes[name] = e + s.envelopes = append(s.envelopes, e) + s.modules.Set(name, e) } } - for name, filter := range new.Filters { + for name, f := range new.Filters { if _, ok := s.Filters[name]; !ok { - s.Filters[name] = filter - s.filters = append(s.filters, filter) - s.modules[name] = filter + s.Filters[name] = f + s.filters = append(s.filters, f) + s.modules.Set(name, f) } } - for name, gate := range new.Gates { + for name, g := range new.Gates { if _, ok := s.Gates[name]; !ok { - s.Gates[name] = gate - s.gates = append(s.gates, gate) - s.modules[name] = gate + s.Gates[name] = g + s.gates = append(s.gates, g) + s.modules.Set(name, g) } } - for name, mixer := range new.Mixers { + for name, m := range new.Mixers { if _, ok := s.Mixers[name]; !ok { - s.Mixers[name] = mixer - s.mixers = append(s.mixers, mixer) - s.modules[name] = mixer + s.Mixers[name] = m + s.mixers = append(s.mixers, m) + s.modules.Set(name, m) } } - for name, noise := range new.Noises { + for name, n := range new.Noises { if _, ok := s.Noises[name]; !ok { - s.Noises[name] = noise - s.noises = append(s.noises, noise) - s.modules[name] = noise + s.Noises[name] = n + s.noises = append(s.noises, n) + s.modules.Set(name, n) } } for name, osc := range new.Oscillators { if _, ok := s.Oscillators[name]; !ok { s.Oscillators[name] = osc s.oscillators = append(s.oscillators, osc) - s.modules[name] = osc + s.modules.Set(name, osc) } } - for name, pan := range new.Pans { + for name, p := range new.Pans { if _, ok := s.Pans[name]; !ok { - s.Pans[name] = pan - s.pans = append(s.pans, pan) - s.modules[name] = pan + s.Pans[name] = p + s.pans = append(s.pans, p) + s.modules.Set(name, p) } } - for name, sampler := range new.Samplers { + for name, smplr := range new.Samplers { if _, ok := s.Samplers[name]; !ok { - s.Samplers[name] = sampler - s.samplers = append(s.samplers, sampler) - s.modules[name] = sampler + s.Samplers[name] = smplr + s.samplers = append(s.samplers, smplr) + s.modules.Set(name, smplr) } } for name, seq := range new.Sequencers { if _, ok := s.Sequencers[name]; !ok { s.Sequencers[name] = seq s.sequencers = append(s.sequencers, seq) - s.modules[name] = seq + s.modules.Set(name, seq) } } - for name, wt := range new.Wavetables { + for name, w := range new.Wavetables { if _, ok := s.Wavetables[name]; !ok { - s.Wavetables[name] = wt - s.wavetables = append(s.wavetables, wt) - s.modules[name] = wt + s.Wavetables[name] = w + s.wavetables = append(s.wavetables, w) + s.modules.Set(name, w) } } } diff --git a/synth/synth_test.go b/synth/synth_test.go index 55bcb78..cac8da0 100644 --- a/synth/synth_test.go +++ b/synth/synth_test.go @@ -1,6 +1,7 @@ package synth import ( + "sync" "testing" "github.com/google/go-cmp/cmp" @@ -71,10 +72,12 @@ func TestSynth_Update(t *testing.T) { w2 = &module.Wavetable{} ) tests := []struct { - name string - s *Synth - new *Synth - want *Synth + name string + s *Synth + new *Synth + want *Synth + currentModules map[string]module.IModule + wantModules map[string]module.IModule }{ { name: "update all modules", @@ -125,7 +128,7 @@ func TestSynth_Update(t *testing.T) { VolumeMemory: 1, sampleRate: 44100, volumeStep: 0.1, - modules: module.ModuleMap{ + modules: module.NewModuleMap(map[string]module.IModule{ "env1": env1, "env2": env2, "f1": f1, @@ -146,7 +149,7 @@ func TestSynth_Update(t *testing.T) { "seq2": seq2, "w1": w1, "w2": w2, - }, + }), envelopes: []*module.Envelope{env1, env2}, filters: []*module.Filter{f1, f2}, gates: []*module.Gate{g1, g2}, @@ -267,7 +270,7 @@ func TestSynth_Update(t *testing.T) { CV: "new-mod", Mod: "new-mod", In: map[string]float64{ - "new-in": 0, + "new-in": 1, }, }, }, @@ -307,14 +310,14 @@ func TestSynth_Update(t *testing.T) { "w2": { CV: "new-cv", Mod: "new-mod", - Signal: []float64{2}, + Signal: []float64{1}, }, }, Time: 5, VolumeMemory: 1, sampleRate: 44100, volumeStep: 0.1, - modules: module.ModuleMap{ + modules: module.NewModuleMap(map[string]module.IModule{ "env2": env2, "f2": f2, "g2": g2, @@ -325,7 +328,7 @@ func TestSynth_Update(t *testing.T) { "s2": s2, "seq2": seq2, "w2": w2, - }, + }), envelopes: []*module.Envelope{env2}, filters: []*module.Filter{f2}, gates: []*module.Gate{g2}, @@ -341,6 +344,11 @@ func TestSynth_Update(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + err := tt.new.Initialize(tt.s.sampleRate) + if err != nil { + t.Errorf("Synth.Update() new.Initialize() error %v", err) + } + tt.s.Update(tt.new) if diff := cmp.Diff(tt.want, tt.s, cmpopts.IgnoreUnexported( @@ -356,7 +364,8 @@ func TestSynth_Update(t *testing.T) { module.Sequencer{}, module.Wavetable{}, ), - cmp.AllowUnexported(Synth{}), + cmp.AllowUnexported(Synth{}, module.ModuleMap{}), + cmpopts.IgnoreUnexported(sync.Mutex{}), ); diff != "" { t.Errorf("Synth.Update() diff = %s", diff) }