Skip to content

Commit

Permalink
Sampler (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
iljarotar authored Jul 25, 2024
1 parent e9737e3 commit 5c07436
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 8 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Run `synth -h` to see all configuration options.
| oscillators | Oscillator [0..*] | all oscillators |
| noises | Noise [0..*] | all noise generators |
| wavetables | Wavetables [0..*] | all wavetables |
| samplers | Sampler[0..*] | all samplers |
| envelopes | Envelope [0..*] | all envelopes |

| Oscillator | | |
Expand Down Expand Up @@ -98,6 +99,18 @@ Run `synth -h` to see all configuration options.
| table | Float [0..*] | output values |
| filters | String[0..*] | names of the filters to apply |

| Sampler | | |
| --------- | ------------ | -------------------------------------------------- |
| **Field** | **Type** | **Description** |
| name | String | should be unique in the scope of the file |
| amp | Input | amplitude in range [0,1] |
| pan | Input | stereo balance in range [-1,1] |
| 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 |

A sampler periodically samples the output values of the given inputs and outputs their sum.

| Filter | | |
| ----------- | -------- | ---------------------------------------------------------- |
| **Field** | **Type** | **Description** |
Expand Down
23 changes: 23 additions & 0 deletions examples/sampler.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
vol: 1
out: [o1]
noises:
- name: n1
amp: {val: 1}

oscillators:
- name: o1
type: Sine
amp: {val: 1}
freq: {val: 400, mod: [s1], mod-amp: 100}

- name: o2
type: Sine
amp: {val: 1}
freq: {val: 0.5}


samplers:
- name: s1
amp: {val: 1}
freq: {val: 2, mod: [o2], mod-amp: 1}
inputs: [n1]
4 changes: 3 additions & 1 deletion module/module.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package module

import "github.com/iljarotar/synth/utils"
import (
"github.com/iljarotar/synth/utils"
)

type Module struct {
integral float64
Expand Down
2 changes: 2 additions & 0 deletions module/noise.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package module
import (
"math/rand"

"github.com/iljarotar/synth/config"
"github.com/iljarotar/synth/utils"
)

Expand Down Expand Up @@ -32,6 +33,7 @@ func (n *Noise) Next(modMap ModulesMap, filtersMap FiltersMap) {
}

y, newInputs := cfg.applyFilters(noise())
n.integral += y / config.Config.SampleRate
n.inputs = newInputs
n.current = stereo(y*amp, pan)
}
Expand Down
9 changes: 3 additions & 6 deletions module/oscillator.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ func (o *Oscillator) Next(t float64, modMap ModulesMap, filtersMap FiltersMap) {

x := o.signalValue(t, amp, offset)
y, newInputs := cfg.applyFilters(x)
avg := (y + o.Current().Mono) / 2
o.integral += avg / config.Config.SampleRate
o.inputs = newInputs
o.current = stereo(y, pan)
}
Expand All @@ -76,12 +78,7 @@ func (o *Oscillator) getOffset(modMap ModulesMap) float64 {
func (o *Oscillator) signalValue(t, amp, offset float64) float64 {
shift := o.Phase / o.Freq.Val // shift is a fraction of one period
phi := 2 * math.Pi * (o.Freq.Val*(t+shift) + offset)
y := o.signal(phi) * amp

avg := (y + o.Current().Mono) / 2
o.integral += avg / config.Config.SampleRate

return y
return o.signal(phi) * amp
}

func (o *Oscillator) limitParams() {
Expand Down
76 changes: 76 additions & 0 deletions module/sampler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package module

import (
"github.com/iljarotar/synth/config"
"github.com/iljarotar/synth/utils"
)

type Sampler struct {
Module
Name string `yaml:"name"`
Amp Input `yaml:"amp"`
Pan Input `yaml:"pan"`
Freq Input `yaml:"freq"`
Filters []string `yaml:"filters"`
Inputs []string `yaml:"inputs"`
inputs []filterInputs
lastTriggeredAt float64
limits
}

func (s *Sampler) Initialize() {
s.limits = limits{min: 0, max: config.Config.SampleRate}
s.limitParams()
s.inputs = make([]filterInputs, len(s.Filters))
s.current = stereo(0, s.Pan.Val)
}

func (s *Sampler) Next(t float64, modMap ModulesMap, filtersMap FiltersMap) {
amp := modulate(s.Amp, ampLimits, modMap)
pan := modulate(s.Pan, panLimits, modMap)
freq := modulate(s.Freq, s.limits, modMap)

cfg := filterConfig{
filterNames: s.Filters,
inputs: s.inputs,
FiltersMap: filtersMap,
}

x := s.sample(t, freq, modMap) * amp
y, newInputs := cfg.applyFilters(x)
s.integral += y / config.Config.SampleRate
s.inputs = newInputs
s.current = stereo(y, pan)
}

func (s *Sampler) sample(t, freq float64, modMap ModulesMap) float64 {
secondsBetweenTwoBeats := 1 / freq
if t-s.lastTriggeredAt >= secondsBetweenTwoBeats {
s.lastTriggeredAt = t
return s.getCurrentOutputValue(modMap)
}
return s.current.Mono
}

func (s *Sampler) getCurrentOutputValue(modMap ModulesMap) float64 {
var y float64
for _, m := range s.Inputs {
mod, ok := modMap[m]
if !ok {
continue
}
y += mod.Current().Mono
}
return y
}

func (s *Sampler) limitParams() {
s.Amp.Val = utils.Limit(s.Amp.Val, ampLimits.min, ampLimits.max)
s.Amp.ModAmp = utils.Limit(s.Amp.ModAmp, ampLimits.min, ampLimits.max)

s.Pan.Val = utils.Limit(s.Pan.Val, panLimits.min, panLimits.max)
s.Pan.ModAmp = utils.Limit(s.Pan.ModAmp, panLimits.min, panLimits.max)

s.Freq.Val = utils.Limit(s.Freq.Val, s.limits.min, s.limits.max)
s.Freq.ModAmp = utils.Limit(s.Freq.ModAmp, s.limits.min, s.limits.max)
}
2 changes: 1 addition & 1 deletion module/wavetable.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (w *Wavetable) Next(t float64, modMap ModulesMap, filtersMap FiltersMap) {

x := w.signalValue(t, amp, freq)
y, newInputs := cfg.applyFilters(x)
w.integral += y / config.Config.SampleRate
w.inputs = newInputs
w.current = stereo(y, pan)
}
Expand All @@ -64,7 +65,6 @@ func (w *Wavetable) signalValue(t, amp, freq float64) float64 {
}

y := amp * val
w.integral += y / config.Config.SampleRate

return y
}
13 changes: 13 additions & 0 deletions synth/synth.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Synth struct {
Oscillators []*module.Oscillator `yaml:"oscillators"`
Noises []*module.Noise `yaml:"noises"`
Wavetables []*module.Wavetable `yaml:"wavetables"`
Samplers []*module.Sampler `yaml:"samplers"`
Envelopes []*module.Envelope `yaml:"envelopes"`
Filters []*module.Filter `yaml:"filters"`
Time float64 `yaml:"time"`
Expand Down Expand Up @@ -58,6 +59,10 @@ func (s *Synth) Initialize() {
c.Initialize()
}

for _, smplr := range s.Samplers {
smplr.Initialize()
}

for _, e := range s.Envelopes {
e.Initialize()
}
Expand Down Expand Up @@ -177,6 +182,10 @@ func (s *Synth) updateCurrentValues() {
c.Next(s.Time, s.modMap, s.filtersMap)
}

for _, smplr := range s.Samplers {
smplr.Next(s.Time, s.modMap, s.filtersMap)
}

for _, e := range s.Envelopes {
e.Next(s.Time, s.modMap)
}
Expand Down Expand Up @@ -204,6 +213,10 @@ func (s *Synth) makeMaps() {
modMap[c.Name] = c
}

for _, smplr := range s.Samplers {
modMap[smplr.Name] = smplr
}

for _, e := range s.Envelopes {
modMap[e.Name] = e
}
Expand Down

0 comments on commit 5c07436

Please sign in to comment.