diff --git a/README.md b/README.md index a91dd2b..3dd1c16 100644 --- a/README.md +++ b/README.md @@ -35,22 +35,32 @@ Then save the file and the transition will start. ### Patch Files This section explains all available modules and provides example configurations. -To see some more examples see the [examples](examples/) directory. +For more examples see the [examples](examples/) directory. Each module must have a unique name across all modules. This name is used as a reference in other modules, e.g. when a module is used as a CV or modulator. +Each module outputs values in the interval `[-1, 1]`. +Additionally, all parameters of a module are limited not to extend the reasonable ranges for each specific parameter, e.g. an oscillator's frequency will never exceed 20,000Hz. +Such limitations make the outcome of a configuration more predictable. +Those modules whose main purpose is to provide CV values for other modules only output values between `0` and `1`. + +#### CV + If a module is provided with a CV its static value is ignored. For example, if you pass a CV to an oscillator this CV will provide the oscillator's frequency and the statically assigned frequency will be ignored. -If a module is provided with a modulator it will modulate a parameter around its static or CV-provided value. -For example, a mixer with a gain of `0.5` and a sine wave as a modulator will output a tremolo around the gain value of `0.5`. +When mapping a CV-provider's output to a parameter, only positive values are considered. +So a value in the interval `[0, 1]` is mapped to the respective parameter's range. +For example, a sequencer will output values in the range `[0, 1]` and those values will be mapped to an oscillator's frequency range `[0, 20000]` if the sequencer is used as a CV for that oscillator. -Each module outputs values in the range `[-1, 1]`. -When using a module as a CV or modulator for some parameter of another module the range `[-1, 1]` is mapped to the range of the respective parameter. +#### Modulation -Example: -An oscillator's frequency is in the range `[0, 20000]`. +If a module is provided with a modulator it will modulate a parameter around its static or CV-provided value. +For example, a mixer with a gain of `0.5` and a sine wave as a modulator will output a tremolo around the gain value of `0.5`. +For example, an oscillator's frequency is in the range `[0, 20000]`. A modulator that outputs values in the entire possible range of `[-1, 1]` will modulate the oscillator's frequency in the entire range `[0, 20000]`. -To control the amount of modulation you must pass the modulator through a mixer and attenuate its gain. +To control the amount of modulation you must send the modulator through a mixer and attenuate its gain. + +#### Module Reference The following yaml file provides examples and explanations for all configuration parameters. @@ -65,6 +75,7 @@ vol: 1 out: name-of-main-module # adsr envelopes +# output values in range [0, 1] envelopes: # the unique module name to be used as a reference in other modules envelope: @@ -236,22 +247,23 @@ samplers: trigger: name-of-trigger-module # sequencers can be combined with oscillators or wavetables to create melodic sequences +# output values in range [0, 1] sequencers: # the unique module name to be used as a reference in other modules sequencer: - # a seqeunce of notes in scientific pitch notation + # a sequence of notes in scientific pitch notation # flats are denoted by `b`, sharps by `#` # a note is separated from its octave by an underscore # minimum octave is 0, maximum is 10 sequence: ["a_4", "eb_3", "c#_5"] - # when the trigger's value changes from negative or zero to postive the next note in the sequence is triggered + # when the trigger's value changes from negative or zero to positive the next note in the sequence is triggered trigger: name-of-trigger-module # base pitch from which to calculate all other frequencies pitch: 440 - # tranpose the whole sequence by any number of semitones + # transpose the whole sequence by any number of semitones # range `[-24, 24]` transpose: -4 diff --git a/audio/reader.go b/audio/reader.go index b32af3d..7c89dea 100644 --- a/audio/reader.go +++ b/audio/reader.go @@ -11,7 +11,7 @@ type reader struct { func (r *reader) Read(buf []byte) (int, error) { if len(buf)%int(bytesPerSample) != 0 { - return 0, fmt.Errorf("buffer lenght must be divisible by %d", bytesPerSample) + return 0, fmt.Errorf("buffer length must be divisible by %d", bytesPerSample) } numSamples := len(buf) / int(bytesPerSample) diff --git a/log/colors.go b/log/colors.go index c98f5a5..7907fab 100644 --- a/log/colors.go +++ b/log/colors.go @@ -4,7 +4,7 @@ type Color string const ( ColorWhite Color = "\033[0m" - ColorOrangeStorng Color = "\033[1;33m" + ColorOrangeStrong Color = "\033[1;33m" ColorBlueStrong Color = "\033[1;34m" ColorGreenStrong Color = "\033[1;32m" ColorRedStrong Color = "\033[1;31m" diff --git a/log/log.go b/log/log.go index f3d18a4..375d828 100644 --- a/log/log.go +++ b/log/log.go @@ -44,7 +44,7 @@ func (l *Logger) Info(log string) { } func (l *Logger) Warning(log string) { - l.sendLog(log, labelWarning, ColorOrangeStorng) + l.sendLog(log, labelWarning, ColorOrangeStrong) } func (l *Logger) Error(log string) { diff --git a/module/envelope.go b/module/envelope.go index af9227e..84d90fe 100644 --- a/module/envelope.go +++ b/module/envelope.go @@ -106,12 +106,12 @@ func (e *Envelope) Step(t float64, modules *ModuleMap) { e.triggeredAt = t case e.gateValue > 0 && gateValue <= 0: e.releasedAt = t - e.level = calc.Transpose(e.current.Mono, outputRange, gainRange) + e.level = calc.Transpose(e.current.Mono, cvRange, gainRange) default: // noop } - val := calc.Transpose(e.getValue(t), gainRange, outputRange) + val := calc.Transpose(e.getValue(t), gainRange, cvRange) e.current = Output{ Mono: val, Left: val / 2, diff --git a/module/envelope_test.go b/module/envelope_test.go index 497a0c2..188b818 100644 --- a/module/envelope_test.go +++ b/module/envelope_test.go @@ -257,7 +257,7 @@ func TestEnvelope_Step(t *testing.T) { }, }, }), - want: -1, + want: 0, wantTriggeredAt: 2, wantGateValue: 1, }, @@ -288,7 +288,7 @@ func TestEnvelope_Step(t *testing.T) { wantTriggeredAt: 2, wantReleasedAt: 5, wantGateValue: -1, - wantLevel: 0.75, + wantLevel: 0.5, }, { name: "noop after release", @@ -310,7 +310,7 @@ func TestEnvelope_Step(t *testing.T) { }, }, }), - want: -1, + want: 0, wantTriggeredAt: 2, wantReleasedAt: 5, wantGateValue: -1, @@ -336,7 +336,7 @@ func TestEnvelope_Step(t *testing.T) { }, }, }), - want: 0.5, + want: 0.75, wantTriggeredAt: 2, wantReleasedAt: 0, wantGateValue: 1, diff --git a/module/gate.go b/module/gate.go index 0984faa..7067e1d 100644 --- a/module/gate.go +++ b/module/gate.go @@ -25,16 +25,16 @@ type ( GateMap map[string]*Gate ) -func (m GateMap) Initialze(sampleRate float64) { +func (m GateMap) Initialize(sampleRate float64) { for _, g := range m { if g == nil { continue } - g.initialze(sampleRate) + g.initialize(sampleRate) } } -func (g *Gate) initialze(sampleRate float64) { +func (g *Gate) initialize(sampleRate float64) { g.sampleRate = sampleRate g.BPM = calc.Limit(g.BPM, bpmRange) g.Fade = calc.Limit(g.Fade, fadeRange) diff --git a/module/gate_test.go b/module/gate_test.go index c2dbfa4..595d2b1 100644 --- a/module/gate_test.go +++ b/module/gate_test.go @@ -8,7 +8,7 @@ import ( "github.com/iljarotar/synth/calc" ) -func TestGate_initialze(t *testing.T) { +func TestGate_initialize(t *testing.T) { tests := []struct { name string g *Gate @@ -24,7 +24,7 @@ func TestGate_initialze(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.g.initialze(44100) + tt.g.initialize(44100) if diff := cmp.Diff(tt.wantSignal, tt.g.Signal); diff != "" { t.Errorf("Gate.initialize() signal diff = %s", diff) @@ -80,7 +80,7 @@ func TestGate_Step(t *testing.T) { modules: NewModuleMap(map[string]IModule{ "cv": &Module{ current: Output{ - Mono: calc.Transpose(120, bpmRange, outputRange), + Mono: calc.Transpose(120, bpmRange, cvRange), }, }, }), diff --git a/module/mixer_test.go b/module/mixer_test.go index e34d507..b5fa3ed 100644 --- a/module/mixer_test.go +++ b/module/mixer_test.go @@ -157,16 +157,14 @@ func TestMixer_Step(t *testing.T) { }, "cv": &Module{ current: Output{ - Mono: 0.5, - Left: 0.25, - Right: 0.25, + Mono: 0.5, }, }, }), want: Output{ - Mono: 1 * 0.75, - Left: 0.5 * 0.75, - Right: 0.5 * 0.75, + Mono: 1 * 0.5, + Left: 0.5 * 0.5, + Right: 0.5 * 0.5, }, }, } diff --git a/module/module.go b/module/module.go index 1ad0cad..150bd8e 100644 --- a/module/module.go +++ b/module/module.go @@ -62,6 +62,10 @@ var ( Min: -24, Max: 24, } + cvRange = calc.Range{ + Min: 0, + Max: 1, + } ) func NewModuleMap(m map[string]IModule) *ModuleMap { @@ -84,8 +88,8 @@ func modulate(x float64, rng calc.Range, mod float64) float64 { } func cv(rng calc.Range, val float64) float64 { - val = calc.Limit(val, outputRange) - return calc.Transpose(val, outputRange, rng) + val = calc.Limit(val, cvRange) + return calc.Transpose(val, cvRange, rng) } func getMono(modules *ModuleMap, name string) float64 { diff --git a/module/oscillator.go b/module/oscillator.go index fa82388..e266be8 100644 --- a/module/oscillator.go +++ b/module/oscillator.go @@ -43,7 +43,7 @@ func (m OscillatorMap) Initialize(sampleRate float64) error { continue } if err := o.initialize(sampleRate); err != nil { - return fmt.Errorf("failed to initialze oscillator %s: %w", name, err) + return fmt.Errorf("failed to initialize oscillator %s: %w", name, err) } } return nil diff --git a/module/pan_test.go b/module/pan_test.go index dc31c14..ddeec76 100644 --- a/module/pan_test.go +++ b/module/pan_test.go @@ -79,7 +79,7 @@ func TestPan_Update(t *testing.T) { want *Pan }{ { - name: "no udpate necessary", + name: "no update necessary", p: &Pan{ Module: Module{ current: Output{ diff --git a/module/sequencer.go b/module/sequencer.go index 5017ad1..7aac0d4 100644 --- a/module/sequencer.go +++ b/module/sequencer.go @@ -33,14 +33,14 @@ func (m SequencerMap) Initialize() error { if s == nil { continue } - if err := s.initialze(); err != nil { + if err := s.initialize(); err != nil { return fmt.Errorf("failed to initialize sequencer %s: %w", name, err) } } return nil } -func (s *Sequencer) initialze() error { +func (s *Sequencer) initialize() error { s.Pitch = calc.Limit(s.Pitch, pitchRange) s.Transpose = calc.Limit(s.Transpose, transposeRange) @@ -94,7 +94,7 @@ func (s *Sequencer) Step(modules *ModuleMap) { freq = s.sequence[s.idx] } - val := calc.Transpose(freq, freqRange, outputRange) + val := calc.Transpose(freq, freqRange, cvRange) s.current = Output{ Mono: val, Left: val / 2, diff --git a/module/sequencer_test.go b/module/sequencer_test.go index b8f6a45..bfb8e3c 100644 --- a/module/sequencer_test.go +++ b/module/sequencer_test.go @@ -183,7 +183,7 @@ func TestSequencer_Step(t *testing.T) { triggerValue: 0, }, modules: &ModuleMap{}, - want: -1, + want: 0, wantTrigger: 0, }, { @@ -202,7 +202,7 @@ func TestSequencer_Step(t *testing.T) { }, }, }), - want: calc.Transpose(110, freqRange, outputRange), + want: calc.Transpose(110, freqRange, cvRange), wantTrigger: 1, }, { @@ -221,7 +221,7 @@ func TestSequencer_Step(t *testing.T) { }, }, }), - want: calc.Transpose(440, freqRange, outputRange), + want: calc.Transpose(440, freqRange, cvRange), wantTrigger: 1, }, } @@ -337,7 +337,7 @@ func TestSequencer_Update(t *testing.T) { } } -func TestSequencer_initialze(t *testing.T) { +func TestSequencer_initialize(t *testing.T) { tests := []struct { name string s *Sequencer @@ -345,7 +345,7 @@ func TestSequencer_initialze(t *testing.T) { wantErr bool }{ { - name: "set limmits correctly", + name: "set limits correctly", s: &Sequencer{ Sequence: []string{"a_4", "a_3"}, Trigger: "trigger", @@ -373,11 +373,11 @@ func TestSequencer_initialze(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := tt.s.initialze(); (err != nil) != tt.wantErr { - t.Errorf("Sequencer.initialze() error = %v, wantErr %v", err, tt.wantErr) + if err := tt.s.initialize(); (err != nil) != tt.wantErr { + t.Errorf("Sequencer.initialize() error = %v, wantErr %v", err, tt.wantErr) } if diff := cmp.Diff(tt.want, tt.s, cmp.AllowUnexported(Module{}, Sequencer{})); diff != "" { - t.Errorf("Sequencer.initialze() diff = %s", diff) + t.Errorf("Sequencer.initialize() diff = %s", diff) } }) } diff --git a/module/signal_functions.go b/module/signal_functions.go index 2a4783b..f0e03c4 100644 --- a/module/signal_functions.go +++ b/module/signal_functions.go @@ -20,7 +20,7 @@ func newSignalFunc(oscType oscillatorType) (SignalFunc, error) { case oscillatorTypeReverseSawtooth: return ReverseSawtoothSignalFunc(), nil default: - return NoSignalFunc(), fmt.Errorf("unknow oscillator type %s", oscType) + return NoSignalFunc(), fmt.Errorf("unknown oscillator type %s", oscType) } } diff --git a/module/wavetable.go b/module/wavetable.go index e6039cd..e460fa6 100644 --- a/module/wavetable.go +++ b/module/wavetable.go @@ -29,11 +29,11 @@ func (m WavetableMap) Initialize(sampleRate float64) { if w == nil { continue } - w.initialze(sampleRate) + w.initialize(sampleRate) } } -func (w *Wavetable) initialze(sampleRate float64) { +func (w *Wavetable) initialize(sampleRate float64) { w.sampleRate = sampleRate w.Freq = calc.Limit(w.Freq, freqRange) w.Fade = calc.Limit(w.Fade, fadeRange) diff --git a/module/wavetable_test.go b/module/wavetable_test.go index 0e6a1b2..3dee5a8 100644 --- a/module/wavetable_test.go +++ b/module/wavetable_test.go @@ -6,7 +6,7 @@ import ( "github.com/google/go-cmp/cmp" ) -func TestWavetable_initialze(t *testing.T) { +func TestWavetable_initialize(t *testing.T) { tests := []struct { name string w *Wavetable @@ -22,7 +22,7 @@ func TestWavetable_initialze(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.w.initialze(0) + tt.w.initialize(0) if diff := cmp.Diff(tt.want, tt.w.Signal); diff != "" { t.Errorf("Wavetable.initialize() diff = %s", diff) diff --git a/synth/synth.go b/synth/synth.go index 1074bd3..298f427 100644 --- a/synth/synth.go +++ b/synth/synth.go @@ -78,7 +78,7 @@ func (s *Synth) Initialize(sampleRate float64) error { } s.Envelopes.Initialize(sampleRate) - s.Gates.Initialze(sampleRate) + s.Gates.Initialize(sampleRate) s.Pans.Initialize(sampleRate) s.Wavetables.Initialize(sampleRate) diff --git a/ui/ui.go b/ui/ui.go index f32f1c1..773ae96 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -141,7 +141,7 @@ func (ui *UI) resetScreen() { LineBreaks(1) } if ui.showVolumeWarning { - fmt.Printf("%s", log.Colored("[WARNING] Volume exceeded 100%%", log.ColorOrangeStorng)) + fmt.Printf("%s", log.Colored("[WARNING] Volume exceeded 100%%", log.ColorOrangeStrong)) LineBreaks(2) } fmt.Printf("%s", ui.time)