docs(05-02): complete bank-decoupling plan — SUMMARY, STATE, ROADMAP updated

This commit is contained in:
2026-03-26 17:39:48 +01:00
parent a94ffdd8ef
commit 8e357cf432
3 changed files with 95 additions and 12 deletions
@@ -0,0 +1,80 @@
---
phase: 05-waveform-types-and-bank-decoupling
plan: "02"
subsystem: synth
tags: [bank, decoupling, dynamic-gain, injection-seam, refactor]
dependency_graph:
requires: [05-01]
provides: [NewBank injected config map, gainPerLayer dynamic computation]
affects: [synth/bank.go, encode/mp3.go, synth/bank_test.go, synth/config_test.go]
tech_stack:
added: []
patterns: [dependency injection, dynamic gain scaling, config map injection]
key_files:
created: []
modified:
- synth/bank.go
- encode/mp3.go
- synth/bank_test.go
- synth/config_test.go
decisions:
- "NewBank now accepts (tau float64, cfgs map[classify.TrafficClass]FreqConfig) — no global state dependency"
- "gainPerLayer computed as 1.0/float64(len(cfgs)) so any N-class config auto-scales to avoid clipping"
- "RenderWindow iterates b.layers directly in both loops — no classify.AllClasses() dependency"
- "encode/mp3.go passes synth.ClassFreqConfigs as default — v1.0 behavior preserved exactly"
metrics:
duration: "~4 min"
completed_date: "2026-03-26"
tasks: 2
files: 4
requirements:
- WAVE-01
- WAVE-02
---
# Phase 5 Plan 02: Bank Decoupling and Dynamic GainPerLayer Summary
OscillatorBank decoupled from global ClassFreqConfigs via injected config map, with gainPerLayer computed dynamically as 1/N so any class count produces correct no-clip mixing.
## What Was Built
- **`OscillatorBank.gainPerLayer float64`** field added to struct — computed at construction time as `1.0 / float64(len(cfgs))`
- **`NewBank(tau float64, cfgs map[classify.TrafficClass]FreqConfig)`** — new two-argument signature replaces global ClassFreqConfigs dependency; iterates cfgs map directly to create layers
- **`RenderWindow` UpdateTarget loop** — refactored from `classify.AllClasses()` iteration to `for class, layer := range b.layers`, making it work for any config map
- **`RenderWindow` render loop** — refactored to use `b.gainPerLayer` (instance field) instead of `GainPerLayer` constant, enabling correct scaling for non-14 class counts
- **`encode/mp3.go` call site** — updated to `synth.NewBank(1.0, synth.ClassFreqConfigs)`, preserving v1.0 behavior exactly
- **Updated test suite** — all 7 existing `NewBank` calls updated to two-argument form; two new tests added: `TestNewBankDynamicGain` (verifies 1/3 gain for 3-class config) and `TestNewBankCustomConfigNoClip` (verifies no-clip with 2-class config)
- **`TestNumLayersMatchesAllClasses`** updated to assert `len(synth.ClassFreqConfigs) == len(classify.AllClasses())` without depending on `synth.NumLayers`
## Tasks Completed
| Task | Description | Commit | Files |
|------|-------------|--------|-------|
| 1 | Decouple NewBank and fix GainPerLayer | 43307c3 | synth/bank.go, encode/mp3.go |
| 2 | Update tests for new NewBank signature and dynamic gain | b2b5ab6 | synth/bank_test.go, synth/config_test.go |
## Verification
- `go test ./synth/... ./encode/... -v`: 44 tests, all pass (41 existing + 2 new bank tests)
- `go test ./...`: all 6 packages pass (aggregate, capture, classify, cmd, encode, synth)
- `go vet ./...`: clean
- `go build ./...`: clean
- `classify.AllClasses()` not referenced in bank.go (confirmed via grep)
- No single-argument `NewBank(` calls remain in production or test code
## Deviations from Plan
None — plan executed exactly as written.
## Known Stubs
None — all decoupling logic is fully implemented and wired.
## Self-Check: PASSED
- synth/bank.go: FOUND (verified by go build)
- encode/mp3.go NewBank call updated: FOUND (synth.NewBank(1.0, synth.ClassFreqConfigs))
- synth/bank_test.go TestNewBankDynamicGain: FOUND (verified by go test)
- synth/bank_test.go TestNewBankCustomConfigNoClip: FOUND (verified by go test)
- synth/config_test.go TestNumLayersMatchesAllClasses updated: FOUND
- Commits 43307c3, b2b5ab6: both present in git log