From fe7ee0e797685ba39d4aec8a8cc8c9afdbbc876a Mon Sep 17 00:00:00 2001 From: Markus Graf Date: Fri, 27 Mar 2026 14:17:18 +0100 Subject: [PATCH] docs(09-01): complete frequency-design and group-architecture plan - 09-01-SUMMARY.md: plan execution complete, 2 tasks, oscillator bug documented - STATE.md: decisions recorded (Group as string, oscillator fix), session updated - REQUIREMENTS.md: FREQ-01, FREQ-02, FREQ-03, GRP-01, GRP-04 marked complete --- .../09-01-SUMMARY.md | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 .planning/phases/09-frequency-design-and-group-architecture/09-01-SUMMARY.md diff --git a/.planning/phases/09-frequency-design-and-group-architecture/09-01-SUMMARY.md b/.planning/phases/09-frequency-design-and-group-architecture/09-01-SUMMARY.md new file mode 100644 index 0000000..d6d0334 --- /dev/null +++ b/.planning/phases/09-frequency-design-and-group-architecture/09-01-SUMMARY.md @@ -0,0 +1,120 @@ +--- +phase: 09-frequency-design-and-group-architecture +plan: "01" +subsystem: synth +tags: [frequency-design, group-architecture, config, oscillator-fix] +dependency_graph: + requires: [08-01] + provides: [FreqConfig.Group, rebalanced-ClassFreqConfigs, oscillator-normalization-fix] + affects: [synth, config, bank_test, config_test] +tech_stack: + added: [] + patterns: + - "Major-second ladder Hz allocation for family-band frequency design" + - "math.Abs normalization for alternating-sign harmonic series in additive oscillator" +key_files: + created: [] + modified: + - synth/config.go + - synth/config_test.go + - synth/oscillator.go + - synth/bank_test.go + - config/config_test.go +decisions: + - "Group field added to FreqConfig as a string (not enum) — extensible for Phase 10 new family names without code changes" + - "Within-family spacing satisfies major second (1.122 ratio): 65->73 = 1.123, 73->82 = 1.123, 82->133 = 1.622 (Infrastructure skips slots reserved for Phase 10), 150->169 = 1.127 (Web), 771->866 = 1.123 (Unknown)" + - "Oscillator normalization fixed to use math.Abs — alternating-sign harmonics (triangle wave) were causing output to exceed [-1,1] bounds when using WaveformPresetHarmonics" +metrics: + duration: "6m 30s" + completed: "2026-03-27" + tasks_completed: 2 + files_modified: 5 +--- + +# Phase 9 Plan 1: Frequency Design and Group Architecture Summary + +FreqConfig gains a Group string field; all 14 ClassFreqConfigs entries rebalanced to major-second ladder in family frequency bands (65-1375 Hz), with family-specific waveforms. Oscillator normalization bug fixed for alternating-sign harmonic series. + +## What Was Built + +### Task 1: Add Group field to FreqConfig and rebalance ClassFreqConfigs + +`synth/config.go` was updated with: + +1. `Group string` field added to `FreqConfig` struct after `WaveformType` +2. Frequency allocation table comment block documenting all 32 designed slots (Phase 9 + Phase 10 placeholders) +3. Stale `NumLayers` and `GainPerLayer` constants removed (Phase 8 cleanup carried forward) +4. All 14 `ClassFreqConfigs` entries rebalanced to the major-second ladder: + - Infrastructure (Triangle, 65-133 Hz): ICMP=65, NTP=73, DHCP=82, DNS=133 + - Web (Sawtooth, 150-169 Hz): HTTPS=150, HTTP=169 + - Mail (Triangle, 214 Hz): SMTP=214 + - Remote Access (Square, 343 Hz): SSH=343 + - Unknown (Custom harmonics, 771-1375 Hz): Unknown1=771, Unknown2=866, Unknown3=972, Unknown4=1091, OtherTCP=1225, OtherUDP=1375 + +Non-Unknown entries use `WaveformPresetHarmonics`; Unknown entries retain hand-tuned `{1,1.0},{2,0.8},{3,0.4}` with `WaveformType` left as zero value (`WaveformCustom`) so `bank.go` uses stored harmonics directly. + +### Task 2: Update synth tests and fix oscillator normalization bug + +`synth/config_test.go`: +- `TestGroupFieldPopulated` added: iterates all ClassFreqConfigs entries, fails if any have empty Group +- `TestHarmonicsNonEmpty` threshold changed from `< 2` to `< 1` (accepts single-harmonic sine entries for Phase 10 SIP) + +**Auto-fix (Rule 1 - Bug): Oscillator normalization for alternating-sign harmonics** + +Found during Task 2 verification: `TestNewBankCustomConfigNoClip` was failing with `L=-1.023`. Root cause: `oscillator.go` `Advance()` accumulated `totalWeight += h.Amplitude` (signed sum), but `WaveformPresetHarmonics(WaveformTriangle, ...)` produces alternating-sign amplitudes. The signed sum (~0.916 for 65 Hz triangle with 170 harmonics) is much smaller than the absolute sum (~1.232), causing the normalization denominator to be deflated, which inflated output amplitude beyond [-1, 1]. + +Fix: changed to `totalWeight += math.Abs(h.Amplitude)` in `oscillator.go`. The old config used positive-only amplitudes `{k, 1/k}` so the bug was latent. The new `WaveformPresetHarmonics(WaveformTriangle, ...)` exposed it. + +**Test updates (Rule 1 - Bug):** +- `synth/bank_test.go`: `TestStereoPan` updated from ClassDHCP (new pan=0.1, right-biased) to ClassSSH (pan=-0.7, wide-left); `TestNewBankCustomConfigNoClip` passes after oscillator fix +- `config/config_test.go`: `TestLoadPartialOverrideFrequency` changed from hardcoded `WaveformCustom` to `synth.ClassFreqConfigs[classify.ClassICMP].WaveformType` (ICMP is now WaveformTriangle); `TestAutoFreqSkipsBuiltins` changed from hardcoded 175.0 to `synth.ClassFreqConfigs[classify.ClassHTTPS].BaseHz` (now 150.0) + +## Verification Results + +``` +go build ./synth/... → exit 0 +go test ./synth/... -v -count=1 → 42 tests PASS +go test ./... → all 7 packages PASS +grep -c 'Group:' synth/config.go → 14 +grep 'Group string' synth/config.go → FOUND +grep -c 'BaseHz: 65.0' synth/config.go → ICMP confirmed +grep '// Frequency Allocation Table' synth/config.go → FOUND +``` + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Fixed oscillator normalization for alternating-sign harmonic amplitudes** +- **Found during:** Task 2 verification (`go test ./synth/...`) +- **Issue:** `oscillator.Advance()` used `totalWeight += h.Amplitude` (signed sum). Triangle wave harmonics from `WaveformPresetHarmonics` alternate signs (1, -1/9, 1/25, -1/49...), making signed total ~0.916 vs absolute total ~1.232. This caused 34% undercount, so output exceeded [-1, 1] bounds. +- **Fix:** Changed to `totalWeight += math.Abs(h.Amplitude)` in `synth/oscillator.go` +- **Files modified:** `synth/oscillator.go`, `synth/bank_test.go`, `config/config_test.go` +- **Commit:** c97682d + +**2. [Rule 1 - Bug] Updated TestStereoPan for new ClassDHCP pan value** +- **Found during:** Task 2 verification +- **Issue:** TestStereoPan expected ClassDHCP to be left-panned (old pan=-0.75) but new config has pan=0.1 (right-biased) +- **Fix:** Changed test to use ClassSSH (pan=-0.7, clearly left-biased in new config) +- **Files modified:** `synth/bank_test.go` +- **Commit:** c97682d + +**3. [Rule 1 - Bug] Updated config_test.go for new default values** +- **Found during:** Task 2 running `go test ./...` +- **Issue:** Two config tests had hardcoded values from old defaults (ICMP WaveformCustom, HTTPS BaseHz=175.0) +- **Fix:** Changed assertions to derive expected values from `synth.ClassFreqConfigs` defaults +- **Files modified:** `config/config_test.go` +- **Commit:** c97682d + +## Known Stubs + +None. All 14 ClassFreqConfigs entries have populated Group, Hz, waveform, and harmonics. Phase 10 slot placeholders are documented in comments only. + +## Self-Check: PASSED + +- synth/config.go: FOUND +- synth/config_test.go: FOUND +- synth/oscillator.go: FOUND +- 09-01-SUMMARY.md: FOUND +- Commit 812f0de: FOUND (feat: Group field + rebalanced ClassFreqConfigs) +- Commit c97682d: FOUND (fix: TestGroupFieldPopulated + oscillator normalization + test updates)