Files
yoloyolo/.planning/phases/09-frequency-design-and-group-architecture/09-01-SUMMARY.md
T
gurix fe7ee0e797 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
2026-03-27 14:18:42 +01:00

121 lines
6.6 KiB
Markdown

---
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)