go-lame v0.0.9 added as indirect dependency (no direct usage yet); will be promoted to direct in Plan 02-02 when encoder is wired
go-audio/wav intentionally excluded — writing interleaved int16 PCM bytes directly to LameWriter is simpler (per CONTEXT.md Claude's Discretion grant)
ClassNTP has only 2 harmonics (not 3) — NTP is a minimal timing signal; 2 harmonics satisfy test requirement and match timbral intent
duration
completed_date
tasks_completed
files_created
files_modified
~15 minutes
2026-03-26
2
6
2
Phase 02 Plan 01: Synth Foundation — Config, Oscillator, and Layer Summary
One-liner: Phase-accumulator additive oscillator with EMA amplitude smoothing and whisper floor for 11 traffic-class drone layers, tested via TDD.
What Was Built
synth/config.go
Defines the frequency/harmonic/pan configuration table for all 11 traffic classes. Key constants: SampleRate=44100, WhisperFloor=0.03, GainPerLayer=1/11. Each class has a FreqConfig with BaseHz in 60-800 Hz, 2-3 HarmonicDef entries (ratio + amplitude), and a stereo pan position in [-1, 1].
Frequency assignments follow musical intervals (D-02/D-03): ICMP=65 Hz (deep bass), DNS=110 Hz, HTTPS=175 Hz, HTTP=220 Hz, SSH=330 Hz, SMTP=440 Hz, NTP=520 Hz, DHCP=600 Hz, other-TCP=700 Hz, other-UDP=780 Hz. ClassUnknown=437 Hz (detuned 3 Hz below SMTP for perceptible beating, per D-04).
synth/oscillator.go
Phase-accumulator oscillator. Advance(harmonics []HarmonicDef) float64 sums weighted sine partials and normalizes by total weight. Phase advances by freq/sampleRate per sample with subtraction wrap (o.phase -= 1.0), not math.Mod, for performance.
synth/layer.go
Layer wraps an oscillator with EMA amplitude dynamics. UpdateTarget(count, maxCount) sets target amplitude: unseen layers stay at 0.0; seen layers floor at WhisperFloor and scale linearly to 1.0 at max count. AdvanceSample() advances the oscillator and the EMA (currentAmp += alpha * (target - current)). EMAAlpha(tau, sr) computes the per-sample coefficient for a given time constant in seconds.