- 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.)
39 lines
948 B
Go
39 lines
948 B
Go
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
|
|
}
|