feat(02-01): implement synth config, oscillator, and layer
- synth/config.go: FreqConfig, HarmonicDef types; ClassFreqConfigs for all 11 traffic classes with frequencies in 60-800 Hz using musical intervals; pan positions per D-12 - synth/oscillator.go: phase-accumulator oscillator with additive harmonic synthesis and phase subtraction wrap (not math.Mod) per D-05 - synth/layer.go: EMA amplitude smoothing with tau-based alpha; whisper floor (0.03) prevents silence once class has been seen; UpdateTarget/AdvanceSample interface - All 12 tests pass (TestAllClassesHaveConfig, TestFrequenciesUnique, TestEMAAmplitudeRise, etc.)
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package synth
|
||||
|
||||
import "math"
|
||||
|
||||
// Oscillator is a phase-accumulator oscillator that generates additive sine waveforms.
|
||||
type Oscillator struct {
|
||||
phase float64
|
||||
freq float64
|
||||
sr float64
|
||||
}
|
||||
|
||||
// NewOscillator creates an oscillator at the given frequency and sample rate.
|
||||
func NewOscillator(freq float64, sampleRate int) *Oscillator {
|
||||
return &Oscillator{freq: freq, sr: float64(sampleRate)}
|
||||
}
|
||||
|
||||
// Advance returns one sample: fundamental + harmonics summed and normalized to [-1, 1].
|
||||
func (o *Oscillator) Advance(harmonics []HarmonicDef) float64 {
|
||||
sum := 0.0
|
||||
totalWeight := 0.0
|
||||
for _, h := range harmonics {
|
||||
sum += h.Amplitude * math.Sin(2*math.Pi*o.phase*float64(h.Ratio))
|
||||
totalWeight += h.Amplitude
|
||||
}
|
||||
o.phase += o.freq / o.sr
|
||||
if o.phase >= 1.0 {
|
||||
o.phase -= 1.0
|
||||
}
|
||||
if totalWeight > 0 {
|
||||
return sum / totalWeight
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Phase returns the current phase (for testing).
|
||||
func (o *Oscillator) Phase() float64 {
|
||||
return o.phase
|
||||
}
|
||||
Reference in New Issue
Block a user