Replace static EMA-smoothed drones with an evolving ambient soundscape:
- ADSR envelope system with sustained (2s attack, 4s release) and bursty
(30ms attack, no sustain) modes per protocol group
- LFO pitch wobble and amplitude tremolo with incommensurable rates per
group (Eno technique) so modulation patterns never repeat
- C major pentatonic frequency tuning (just intonation) — any combination
of active protocols sounds consonant
- tanh soft limiter on master output prevents clipping
- Sync all documentation: README, PROJECT.md, ARCHITECTURE.md, v1.2
requirements traceability
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add TestGroupFieldPopulated test to verify all ClassFreqConfigs have non-empty Group (GRP-01)
- Change TestHarmonicsNonEmpty threshold from < 2 to < 1 (accepts single-harmonic sine entries)
- Fix oscillator Advance() to use math.Abs(h.Amplitude) for normalization weight accumulation:
triangle wave uses alternating-sign amplitudes; signed sum underestimates totalWeight causing
output to exceed [-1,1] bounds when using WaveformPresetHarmonics (Rule 1 bug fix)
- Update TestStereoPan to use ClassSSH (pan=-0.7) instead of ClassDHCP (pan changed to 0.1)
- Update TestNewBankCustomConfigNoClip: passes after oscillator normalization fix
- Fix TestLoadPartialOverrideFrequency: derive expected WaveformType from defaults (not hardcoded 0)
- Fix TestAutoFreqSkipsBuiltins: derive expected HTTPS BaseHz from defaults (150.0 in Phase 9)
- WaveformType int with five constants: WaveformCustom (0), WaveformSine,
WaveformSquare, WaveformSawtooth, WaveformTriangle
- WaveformPresetHarmonics generates bandlimited harmonic series for each type
- WaveformCustom returns nil to preserve existing hand-tuned harmonics
- All generated harmonics are below Nyquist (sampleRate/2)
- FreqConfig gains WaveformType field (zero value = WaveformCustom)
- ClassFreqConfigs converted to named fields (required for new struct field)
- All existing synth tests continue to pass (38 tests total)
- Replace ClassUnknown with ClassUnknown1-4 (unknown-1 through unknown-4)
- AllClasses() now returns 14 elements
- Add hashBucket() function routing unrecognized traffic deterministically
- Update synth/config.go: NumLayers=14, 4 dissonant unknown tone configs at 862-1047 Hz
- Add TestAllClassesCount and TestHashBucketDistribution tests
- Add TestClassFreqConfigsComplete and TestNumLayersMatchesAllClasses to config_test.go
- Update TestNewBankHas14Layers, TestMixerNoClip for 14 classes
- All classify and synth tests pass
- NewBank creates 11 layers from ClassFreqConfigs, one per TrafficClass
- RenderWindow transforms WindowSnapshot into SamplesPerWindow stereo frames
- GainPerLayer (1/11) applied per layer guarantees no clipping with all layers at max
- PanGains applied per layer for constant-power stereo positioning
- EMA amplitude smoothing provides temporal convergence across windows
- 7 bank tests: layer count, output length, silence, non-zero, no-clip, stereo pan, EMA convergence
- 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.)