Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion audio/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion log/colors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions module/envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions module/envelope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ func TestEnvelope_Step(t *testing.T) {
},
},
}),
want: -1,
want: 0,
wantTriggeredAt: 2,
wantGateValue: 1,
},
Expand Down Expand Up @@ -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",
Expand All @@ -310,7 +310,7 @@ func TestEnvelope_Step(t *testing.T) {
},
},
}),
want: -1,
want: 0,
wantTriggeredAt: 2,
wantReleasedAt: 5,
wantGateValue: -1,
Expand All @@ -336,7 +336,7 @@ func TestEnvelope_Step(t *testing.T) {
},
},
}),
want: 0.5,
want: 0.75,
wantTriggeredAt: 2,
wantReleasedAt: 0,
wantGateValue: 1,
Expand Down
6 changes: 3 additions & 3 deletions module/gate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions module/gate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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),
},
},
}),
Expand Down
10 changes: 4 additions & 6 deletions module/mixer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
}
Expand Down
8 changes: 6 additions & 2 deletions module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ var (
Min: -24,
Max: 24,
}
cvRange = calc.Range{
Min: 0,
Max: 1,
}
)

func NewModuleMap(m map[string]IModule) *ModuleMap {
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion module/oscillator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion module/pan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
6 changes: 3 additions & 3 deletions module/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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,
Expand Down
16 changes: 8 additions & 8 deletions module/sequencer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func TestSequencer_Step(t *testing.T) {
triggerValue: 0,
},
modules: &ModuleMap{},
want: -1,
want: 0,
wantTrigger: 0,
},
{
Expand All @@ -202,7 +202,7 @@ func TestSequencer_Step(t *testing.T) {
},
},
}),
want: calc.Transpose(110, freqRange, outputRange),
want: calc.Transpose(110, freqRange, cvRange),
wantTrigger: 1,
},
{
Expand All @@ -221,7 +221,7 @@ func TestSequencer_Step(t *testing.T) {
},
},
}),
want: calc.Transpose(440, freqRange, outputRange),
want: calc.Transpose(440, freqRange, cvRange),
wantTrigger: 1,
},
}
Expand Down Expand Up @@ -337,15 +337,15 @@ func TestSequencer_Update(t *testing.T) {
}
}

func TestSequencer_initialze(t *testing.T) {
func TestSequencer_initialize(t *testing.T) {
tests := []struct {
name string
s *Sequencer
want *Sequencer
wantErr bool
}{
{
name: "set limmits correctly",
name: "set limits correctly",
s: &Sequencer{
Sequence: []string{"a_4", "a_3"},
Trigger: "trigger",
Expand Down Expand Up @@ -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)
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion module/signal_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
4 changes: 2 additions & 2 deletions module/wavetable.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions module/wavetable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion synth/synth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading