--- phase: 02-audio-synthesis-engine plan: "01" subsystem: synth tags: [synthesis, oscillator, EMA, harmonics, audio, go-lame, CGo] dependency_graph: requires: [classify/types.go] provides: [synth/config.go, synth/oscillator.go, synth/layer.go] affects: [synth/mixer.go (Plan 02-02)] tech_stack: added: [github.com/sjzar/go-lame@v0.0.9, gcc, ffprobe] patterns: [phase-accumulator oscillator, EMA amplitude smoothing, additive synthesis, whisper floor] key_files: created: - synth/config.go - synth/oscillator.go - synth/layer.go - synth/config_test.go - synth/oscillator_test.go - synth/layer_test.go modified: - go.mod - go.sum decisions: - "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" metrics: duration: "~15 minutes" completed_date: "2026-03-26" tasks_completed: 2 files_created: 6 files_modified: 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. ### Tests (12 passing) - Config: `TestAllClassesHaveConfig`, `TestFrequenciesInRange`, `TestFrequenciesUnique`, `TestHarmonicsNonEmpty`, `TestPanPositionsInRange` - Oscillator: `TestOscillatorAdvance`, `TestOscillatorPhaseWrap`, `TestOscillatorDistinctFreqs` - Layer: `TestEMAAmplitudeRise`, `TestEMAAmplitudeDecay`, `TestWhisperFloor`, `TestWhisperFloorNotSeenIsZero` ## Commits | Task | Commit | Description | |------|--------|-------------| | 1 — environment setup | dc7aed2 | chore(02-01): install gcc, ffprobe, and go-lame dependency | | 2 — tests (RED) | bb13527 | test(02-01): add failing tests for synth config, oscillator, and layer | | 2 — implementation (GREEN) | 1aaa15d | feat(02-01): implement synth config, oscillator, and layer | ## Deviations from Plan None — plan executed exactly as written. ## Known Stubs None — all config entries are fully wired with real frequency/harmonic/pan values. No placeholder data. ## Self-Check: PASSED - synth/config.go: FOUND - synth/oscillator.go: FOUND - synth/layer.go: FOUND - synth/config_test.go: FOUND - synth/oscillator_test.go: FOUND - synth/layer_test.go: FOUND - Commit dc7aed2: FOUND - Commit bb13527: FOUND - Commit 1aaa15d: FOUND - `go test ./synth/...`: 12 tests PASS - `CGO_ENABLED=1 go build ./...`: PASS - go.mod contains go-lame v0.0.9: VERIFIED