2026-03-26 11:58:44 +01:00
|
|
|
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].
|
2026-03-27 14:15:27 +01:00
|
|
|
// Normalization uses sum of absolute amplitudes so that alternating-sign harmonic series
|
|
|
|
|
// (e.g. triangle wave) are correctly bounded. Without math.Abs, signed cancellation
|
|
|
|
|
// produces an inflated normalization denominator that causes output to exceed [-1, 1].
|
2026-03-26 11:58:44 +01:00
|
|
|
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))
|
2026-03-27 14:15:27 +01:00
|
|
|
totalWeight += math.Abs(h.Amplitude)
|
2026-03-26 11:58:44 +01:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
}
|