diff --git a/README.md b/README.md index 63681fc..6593d96 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Run `synth -h` to see all configuration options. | phase | Float | phase in range [-1,1] | | pan | Input | stereo balance in range [-1,1] | | filters | String[0..*] | names of the filters to apply | +| envelope | String | name of the envelope to apply | | OscillatorType | | --------------- | @@ -88,6 +89,7 @@ Run `synth -h` to see all configuration options. | amp | Input | amplitude in range [0,2] | | pan | Input | stereo balance in range [-1,1] | | filters | String[0..*] | names of the filters to apply | +| envelope | String | name of the envelope to apply | | Wavetable | | | | --------- | ------------ | ----------------------------------------- | @@ -98,6 +100,7 @@ Run `synth -h` to see all configuration options. | freq | Input | periods per second [0,20000] | | table | Float [0..*] | output values | | filters | String[0..*] | names of the filters to apply | +| envelope | String | name of the envelope to apply | | Sampler | | | | --------- | ------------ | -------------------------------------------------- | @@ -108,6 +111,7 @@ Run `synth -h` to see all configuration options. | freq | Input | frequency in range [0,SAMPLE_RATE (default 44100)] | | filters | String[0..*] | names of the filters to apply | | inputs | String[0..*] | names of the modules that will be sampled | +| envelope | String | name of the envelope to apply | A sampler periodically samples the output values of the given inputs and outputs their sum. @@ -135,12 +139,12 @@ transitioning at the `low-cutoff` frequency. If both cutoff frequencies are defi | bpm | Input | triggers per minute [0,600000] | | time-shift | Float | initial time shift | -| Input | | | -| --------- | ------------- | ------------------------------------------ | -| **Field** | **Type** | **Description** | -| val | Float | initial value of the respective parameter | -| mod | String [0..*] | names of modulating modules | -| mod-amp | Float | amplitude of the modulation in range [0,1] | +| Input | | | +| --------- | ------------- | ----------------------------------------------------------------------- | +| **Field** | **Type** | **Description** | +| val | Float | initial value of the respective parameter | +| mod | String [0..*] | names of modulating modules (oscillators, samplers, wavetables, noises) | +| mod-amp | Float | amplitude of the modulation in range [0,1] | ## Example patch file diff --git a/examples/envelope.yaml b/examples/envelope.yaml index 679756f..a9798a9 100644 --- a/examples/envelope.yaml +++ b/examples/envelope.yaml @@ -1,10 +1,30 @@ vol: 1 -out: [o1] +out: [s1] +wavetables: + - name: w1 + amp: {val: 1} + freq: {val: 200} + table: [1,0] + envelope: e1 + +samplers: + - name: s1 + amp: {val: 1} + freq: {val: 44100} + inputs: [n1] + envelope: e1 + +noises: + - name: n1 + amp: {val: 0.1} + envelope: e1 + oscillators: - name: o1 type: Triangle freq: {val: 200} - amp: {val: 0, mod: [e1], mod-amp: 1} + amp: {val: 1} + envelope: e1 - name: lfo1 type: Square diff --git a/module/envelope.go b/module/envelope.go index 738e352..6356021 100644 --- a/module/envelope.go +++ b/module/envelope.go @@ -6,8 +6,9 @@ import ( "github.com/iljarotar/synth/utils" ) +type EnvelopesMap map[string]*Envelope + type Envelope struct { - Module Name string `yaml:"name"` Attack Input `yaml:"attack"` Decay Input `yaml:"decay"` @@ -17,6 +18,7 @@ type Envelope struct { SustainLevel Input `yaml:"sustain-level"` TimeShift float64 `yaml:"time-shift"` BPM Input `yaml:"bpm"` + current float64 lastTriggeredAt *float64 currentConfig envelopeConfig } @@ -32,14 +34,13 @@ type envelopeConfig struct { func (e *Envelope) Initialize() { e.limitParams() - e.current = output{Mono: 0, Left: 0, Right: 0} } func (e *Envelope) Next(t float64, modMap ModulesMap) { bpm := modulate(e.BPM, bpmLimits, modMap) e.trigger(t, bpm, modMap) y := e.getCurrentValue(t) - e.current = output{Mono: y, Left: 0, Right: 0} + e.current = y } func (e *Envelope) getCurrentConfig(t float64, modMap ModulesMap) { diff --git a/module/module.go b/module/module.go index 6dffd70..45fa380 100644 --- a/module/module.go +++ b/module/module.go @@ -66,6 +66,13 @@ func modulate(param Input, lim limits, modMap ModulesMap) float64 { return utils.Limit(y, lim.min, lim.max) } +func applyEnvelope(x float64, envelopeName string, envelopesMap EnvelopesMap) float64 { + if e, ok := envelopesMap[envelopeName]; ok { + return x * e.current + } + return x +} + func stereo(x, pan float64) output { out := output{} p := utils.Percentage(pan, -1, 1) diff --git a/module/noise.go b/module/noise.go index dead8bf..2f8ba3b 100644 --- a/module/noise.go +++ b/module/noise.go @@ -12,6 +12,7 @@ type Noise struct { Amp Input `yaml:"amp"` Pan Input `yaml:"pan"` Filters []string `yaml:"filters"` + Envelope string `yaml:"envelope"` inputs []filterInputs sampleRate float64 } @@ -23,7 +24,7 @@ func (n *Noise) Initialize(sampleRate float64) { n.current = stereo(noise()*n.Amp.Val, n.Pan.Val) } -func (n *Noise) Next(modMap ModulesMap, filtersMap FiltersMap) { +func (n *Noise) Next(modMap ModulesMap, filtersMap FiltersMap, envelopesMap EnvelopesMap) { pan := modulate(n.Pan, panLimits, modMap) amp := modulate(n.Amp, ampLimits, modMap) @@ -34,6 +35,7 @@ func (n *Noise) Next(modMap ModulesMap, filtersMap FiltersMap) { } y, newInputs := cfg.applyFilters(noise()) + y = applyEnvelope(y, n.Envelope, envelopesMap) n.integral += y / n.sampleRate n.inputs = newInputs n.current = stereo(y*amp, pan) diff --git a/module/oscillator.go b/module/oscillator.go index ebf8fd4..52d6133 100644 --- a/module/oscillator.go +++ b/module/oscillator.go @@ -29,6 +29,7 @@ type Oscillator struct { Phase float64 `yaml:"phase"` Pan Input `yaml:"pan"` Filters []string `yaml:"filters"` + Envelope string `yaml:"envelope"` inputs []filterInputs signal SignalFunc sampleRate float64 @@ -44,7 +45,7 @@ func (o *Oscillator) Initialize(sampleRate float64) { o.current = stereo(y, o.Pan.Val) } -func (o *Oscillator) Next(t float64, modMap ModulesMap, filtersMap FiltersMap) { +func (o *Oscillator) Next(t float64, modMap ModulesMap, filtersMap FiltersMap, envelopesMap EnvelopesMap) { pan := modulate(o.Pan, panLimits, modMap) amp := modulate(o.Amp, ampLimits, modMap) offset := o.getOffset(modMap) @@ -57,6 +58,7 @@ func (o *Oscillator) Next(t float64, modMap ModulesMap, filtersMap FiltersMap) { x := o.signalValue(t, amp, offset) y, newInputs := cfg.applyFilters(x) + y = applyEnvelope(y, o.Envelope, envelopesMap) avg := (y + o.Current().Mono) / 2 o.integral += avg / o.sampleRate o.inputs = newInputs diff --git a/module/sampler.go b/module/sampler.go index 934ee21..0bd3ee3 100644 --- a/module/sampler.go +++ b/module/sampler.go @@ -12,6 +12,7 @@ type Sampler struct { Freq Input `yaml:"freq"` Filters []string `yaml:"filters"` Inputs []string `yaml:"inputs"` + Envelope string `yaml:"envelope"` inputs []filterInputs lastTriggeredAt float64 limits @@ -26,7 +27,7 @@ func (s *Sampler) Initialize(sampleRate float64) { s.current = stereo(0, s.Pan.Val) } -func (s *Sampler) Next(t float64, modMap ModulesMap, filtersMap FiltersMap) { +func (s *Sampler) Next(t float64, modMap ModulesMap, filtersMap FiltersMap, envelopesMap EnvelopesMap) { amp := modulate(s.Amp, ampLimits, modMap) pan := modulate(s.Pan, panLimits, modMap) freq := modulate(s.Freq, s.limits, modMap) @@ -39,6 +40,7 @@ func (s *Sampler) Next(t float64, modMap ModulesMap, filtersMap FiltersMap) { x := s.sample(t, freq, amp, modMap) y, newInputs := cfg.applyFilters(x) + y = applyEnvelope(y, s.Envelope, envelopesMap) s.integral += y / s.sampleRate s.inputs = newInputs s.current = stereo(y, pan) diff --git a/module/wavetable.go b/module/wavetable.go index 3e2a05a..ef5fc29 100644 --- a/module/wavetable.go +++ b/module/wavetable.go @@ -14,6 +14,7 @@ type Wavetable struct { Amp Input `yaml:"amp"` Pan Input `yaml:"pan"` Filters []string `yaml:"filters"` + Envelope string `yaml:"envelope"` inputs []filterInputs sampleRate float64 } @@ -28,7 +29,7 @@ func (w *Wavetable) Initialize(sampleRate float64) { w.current = stereo(y, w.Pan.Val) } -func (w *Wavetable) Next(t float64, modMap ModulesMap, filtersMap FiltersMap) { +func (w *Wavetable) Next(t float64, modMap ModulesMap, filtersMap FiltersMap, envelopesMap EnvelopesMap) { pan := modulate(w.Pan, panLimits, modMap) amp := modulate(w.Amp, ampLimits, modMap) freq := modulate(w.Freq, freqLimits, modMap) @@ -41,6 +42,7 @@ func (w *Wavetable) Next(t float64, modMap ModulesMap, filtersMap FiltersMap) { x := w.signalValue(t, amp, freq) y, newInputs := cfg.applyFilters(x) + y = applyEnvelope(y, w.Envelope, envelopesMap) w.integral += y / w.sampleRate w.inputs = newInputs w.current = stereo(y, pan) diff --git a/synth/synth.go b/synth/synth.go index ff4003b..5d2494a 100644 --- a/synth/synth.go +++ b/synth/synth.go @@ -29,6 +29,7 @@ type Synth struct { sampleRate float64 modMap module.ModulesMap filtersMap module.FiltersMap + envelopesMap module.EnvelopesMap step, volumeMemory float64 notifyFadeOutDone chan bool fadeDirection FadeDirection @@ -164,19 +165,19 @@ func (s *Synth) getCurrentValue() (left, right, mono float64) { func (s *Synth) updateCurrentValues() { for _, o := range s.Oscillators { osc := o - osc.Next(s.Time, s.modMap, s.filtersMap) + osc.Next(s.Time, s.modMap, s.filtersMap, s.envelopesMap) } for _, n := range s.Noises { - n.Next(s.modMap, s.filtersMap) + n.Next(s.modMap, s.filtersMap, s.envelopesMap) } for _, c := range s.Wavetables { - c.Next(s.Time, s.modMap, s.filtersMap) + c.Next(s.Time, s.modMap, s.filtersMap, s.envelopesMap) } for _, smplr := range s.Samplers { - smplr.Next(s.Time, s.modMap, s.filtersMap) + smplr.Next(s.Time, s.modMap, s.filtersMap, s.envelopesMap) } for _, e := range s.Envelopes { @@ -193,6 +194,7 @@ func (s *Synth) updateCurrentValues() { func (s *Synth) makeMaps() { modMap := make(module.ModulesMap) filtersMap := make(module.FiltersMap) + envelopesMap := make(module.EnvelopesMap) for _, osc := range s.Oscillators { modMap[osc.Name] = osc @@ -210,16 +212,17 @@ func (s *Synth) makeMaps() { modMap[smplr.Name] = smplr } - for _, e := range s.Envelopes { - modMap[e.Name] = e - } - for _, f := range s.Filters { filtersMap[f.Name] = f } + for _, e := range s.Envelopes { + envelopesMap[e.Name] = e + } + s.modMap = modMap s.filtersMap = filtersMap + s.envelopesMap = envelopesMap } func secondsToStep(seconds, delta, sampleRate float64) float64 {