diff --git a/CONTEXT.md b/CONTEXT.md index abaa99f..810316d 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -2301,7 +2301,10 @@ shell maps that pure result to a cursor. No cue logic in the shell; the shell on > findings report — a functional-correctness/algorithm-quality complement to the SOLID/naming audit > below. **Q-W1 is gated on Q-W0's triage being complete and Daniel signing off on each finding's > disposition** (fix-now vs. document-and-defer). Spec: §"The pre-restructure audit wave (Q-W0)" -> below. +> below. **STATUS (2026-07-28): Q-W0 COMPLETE and SIGNED OFF — all 59 dispositions approved; the +> Q-W1 sub-gate is satisfied once the six approved fix-now remediations land (in flight on +> `pq-w0-fixes`). The audit's plan reshape is folded into this spec: Q-W2 6→8 seams, NEW wave +> Q-W2v (parallel with Q-W2), Q-W3 3→4 hoists + riders, Q-W5 + the ext-state-loop dedupe.** ## What it is @@ -2330,6 +2333,21 @@ reinvented wheel or a numerically-fragile DSP path should be eliminated or consc tree. Bringing the code "into the realm of something I can stand to look at" is not only a matter of shape; it is also a matter of the code being *functionally sound*. +**STATUS (2026-07-28): Q-W0 is COMPLETE — audit run, triage done, sign-off given.** Four parallel +tracks (T1 DSP, T2 architecture, T3 env-coupled constants, T4 sizing + placement), **59 +findings**; report: `docs/product/code-quality-audit.md`, appendices: +`docs/product/audit-notes/q-w0-t{1..4}-*.md`. Daniel approved **every disposition as proposed** on +2026-07-28. Six findings are fix-now *in Q-W0* — **T1-01** (linked-lag stereo splice alignment), +**T1-03** (Preserve prime bound + immediate `freezeTail()`), **T1-09** (`declickR_` dead-state +removal, riding the `sampler_core` edits), **T2-01(a)** (provenance wire-cursor hardening +backport), **T3-01** (gain-ramp seconds), **T3-03** (fade-ceiling seconds) — in flight on branch +`pq-w0-fixes`; **the Q-W1 sub-gate is satisfied once they land** (each with its module's CTest +target green; the audible DSP fixes with a stated before/after listening check). All other +fix-nows are assigned to the wave that already opens their file and are recorded in the wave specs +below. **The Q-11 question is answered:** the correlation-aligned SOLA pitch engine is **sound — +no technique replacement (phase-vocoder / WSOLA) warranted**; every pitch finding is a bounded +in-technique fix or a documented operating limit. + **Audit scope — the named surfaces:** 1. **DSP / audio, close eye on pitch.** Assess *algorithm quality* — correctness, artifacts, @@ -2395,10 +2413,11 @@ the subsystem map off the folders. ReaSampler adopts the *pattern* (directory = adapted to its own most load-bearing invariant — the pure/shell split — as the top level (see below). Vital is GPLv3; the borrowed artifact is the **structural pattern**, not code. -## Settled decisions (Q-1 settled; Q-2..Q-6 recommended — see `docs/product/code-organization.md` §6) +## Settled decisions (Q-1/Q-10/Q-11 settled 2026-07-27; Q-5/Q-6/Q-8/Q-9 + the audit's §4 forks settled 2026-07-28 — REC history kept; see `docs/product/code-organization.md` §6 and `docs/product/code-quality-audit.md` §4) - **Q-1 — namespace letter. SETTLED: `Q` (Quality).** Point-id family `Q1..Qn`, wave prefixes - `Q-W0` (the pre-restructure audit) then `Q-W1..Q-W6` (the structural reorg). `O` (Organization) + `Q-W0` (the pre-restructure audit) then `Q-W1..Q-W6` (the structural reorg; + **`Q-W2v`**, the + VST god-module wave added at the Q-W0 sign-off, 2026-07-28). `O` (Organization) was set aside: the glyph reads ambiguously against zero in point ids, and "Organization" undersells a phase measured against a *quality* bar. - **Q-2 — JSON extraction in scope + first. REC: yes.** The 4× duplicated `Parser` is the largest @@ -2413,17 +2432,43 @@ below). Vital is GPLv3; the borrowed artifact is the **structural pattern**, not `audio`/`ui`/`reclaim`/`version`/`json`. Directory and namespace agree; a symbol's home is unambiguous from either. - **Q-5 — god-module split granularity. REC: to the audit's named seams, no finer.** Well-factored, - not atomized. -- **Q-6 — OCP registration-table. REC: in scope, last (most droppable if narrowing).** + not atomized. **SETTLED (Daniel, 2026-07-28): seams-by-responsibility with the Q-W0 T4 seam + lists adopted** — `bank_panel` 6→8 seams (+ `panel_layout`, `panel_drag`; T4-01), + `capture_orchestrator` further split with `capture_batch` (T4-02) — **and the ~600-line file + ceiling is an acceptance criterion on every split wave**: seams are the method, the ceiling is + the bar; arbitrary bisection to hit the number is rejected. One documented exception: + `sampler_core.cpp` stays whole (T4-14/T4-27). +- **Q-6 — OCP registration-table. REC: in scope, last (most droppable if narrowing).** **SETTLED + (Daniel, 2026-07-28): in scope, last wave, as planned.** - **Q-7 — naming rides the relocation waves, no dedicated naming wave. REC: yes** (forced once Q-3/Q-4 settle — a rename is near-free during relocation, near-pure-churn standalone). - **Q-8 — class/module renames beyond the free namespace fix. REC: fix the two that actively mislead** — `BankIndex`→`BankModel` (the `bank_model.h`/`BankIndex` file↔class word-mismatch) and the unified JSON parser → `json::Reader`/`json::Writer` (or `json::Parser`) — **leave the merely-quirky** (`Book`/`Bank`/`Index`, `Sample`/`AudioSample`, `MinMax`, `KitBox`). Daniel's - to call. + to call. **SETTLED (Daniel, 2026-07-28): both renames** — `BankIndex`→`BankModel` (W1) and the + JSON parser minted as `json::Reader`/`json::Writer` (W1). From the audit, additionally: + **`ICaptureBackend` is deleted in Q-W3** (T4-26 — one deriver, zero polymorphic call sites; the + CLAUDE.md/CONTEXT "two backends behind one interface" correction **rides Q-W3's own commit**, + not earlier). - **Q-9 — align the `capture_realtime` (shell) / `realtime_record` (pure) word-order inversion. - REC: yes, during Q-W3** (a free rider — W3 already hoists the realtime lifecycle). + REC: yes, during Q-W3** (a free rider — W3 already hoists the realtime lifecycle). **SETTLED + (Daniel, 2026-07-28): yes** — the pure module takes the stem `capture_realtime`, the shell + takes the suffix (the `drag_out`↔`drag_out_win` model), during W3. +- **Audit §4a — VST placement. SETTLED (Daniel, 2026-07-28): T4-18** — `src/vst/` integrates into + the single `core/`/`shell/` top split as `core/instrument/{engine,map,ui}` + + `shell/instrument/`. One rule, no special case: a file's directory says whether it may touch a + *host* type (REAPER **or** VST3 SDK); the artifact boundary is a link-graph fact the sources + already straddle. The T4-19 artifact-first subtree was set aside. Directory map below updated. +- **Audit §4e — WAV/RIFF consolidation moment. SETTLED (Daniel, 2026-07-28): a named rider on + Q-W3** — one pure **`wav_codec`** owner (chunk walker + layout + build + patch), absorbing the + T4-10 ingest extraction (T2-08 / T4-23 / T4-10). The dedup-by-hash / null-test maintenance + surface gets exactly one implementation. +- **Audit §4f — Q-W2v scheduling. SETTLED (Daniel, 2026-07-28): parallel with Q-W2** (different + artifact, zero file overlap); the serial "Q-W7" alternative was set aside. +- **Audit §4b/§4c/§4d — the Q-W0 fix-now set. SETTLED (Daniel, 2026-07-28): all approved + fix-now** — T1-01 + T1-03 (with T1-09 riding), T2-01(a), T3-01 + T3-03; in flight on + `pq-w0-fixes`; landing them closes Q-W0 and opens Q-W1. ## The directory + namespace map (Q-3 / Q-4) @@ -2434,24 +2479,42 @@ directories. - `core/model/` (`::model`) — `bank_model`, `bank_book`, `owned_manifest`, `provenance` - `core/view/` (`::view`) — `view_mode_model`, `view_tree`, `lane_keys`, `mode_switch` - `core/capture/` (`::capture`) — `render_settings`, `batch_capture`, `tail_control`, - `capture_paths`, `wav_trim`, `insert_plan` + `capture_paths`, `wav_trim`, `insert_plan`; **post-Q-W3** `wav_codec` (the one pure WAV/RIFF + owner — audit §4e rider) - `core/audio/` (`::audio`) — `peaks` - `core/ui/` (`::ui`) — `theme`, `component_geometry`, `bank_grid`, `tab_strip`, `action_buttons`, `prune_button` - `core/reclaim/` (`::reclaim`) — `prune_reconcile` - `core/version/` (`::version`) — `app_version` -- `core/json/` (`::json`) — **NEW** — extracted parser/serializer (replaces the 4 duplicate - `Parser`s) +- `core/json/` (`::json`) — **NEW** — extracted parser/serializer, minted as + `json::Reader`/`json::Writer` (replaces the 5 duplicate hand-rolled decoders — the four + `Parser`s + `tail_control`'s, T2-02) +- `core/instrument/` — **T4-18 SETTLED (Daniel, 2026-07-28)** — the VST artifact's pure side + joins the one top split (directory = "may it touch a host type", REAPER *or* VST3 SDK): + - `core/instrument/engine/` — `sampler_core`, `pitch_shift`, `velocity_curve`, `master_gain` + - `core/instrument/map/` — `sample_map` (+ `component_state_io` post-Q-W2v), `bank_sync`, + `bridge_marshal`, `note_entry`, `trigger_seam` + - `core/instrument/ui/` — `editor_geometry`, `keyboard_strip`, `waveform_view`, + `capture_browser`, `browser_scroll`, `param_slider`, `knob_deck`, `curve_popup`, + `envelope_overlay`, `envelope_edit`, `embed_strip` **`shell/` (REAPER-facing — subdir by subsystem, namespace as house style prefers):** - `shell/capture/` — `capture`, `capture_realtime`, `provenance_shell`, `track_guid`, `item_read`; - **post-Q-W3** `capture_orchestrator`, `scope_resolve`, `realtime_lifecycle` + **post-Q-W3** `capture_orchestrator`, `capture_batch`, `scope_resolve`, `realtime_lifecycle`, + `capture_realtime_finalize` (with the Q-9 stem/suffix rename) - `shell/panel/` — `draw_kit`; **post-Q-W2** `panel_render`, `panel_thumbnails`, - `panel_audition`, `panel_input`, `panel_bank_ops`, `panel_window` (from `bank_panel`) + `panel_audition`, `panel_input`, `panel_layout`, `panel_drag`, `panel_bank_ops`, + `panel_window` (from `bank_panel` — eight seams, T4-01) - `shell/view/` — `view` - `shell/persist/` — **post-Q-W5** `session`, `ext_state_io`, `prune_fs` (from `persist`) - `shell/actions/` — `drag_out_win`; **post-Q-W4** `design_view_actions`, `bank_actions`, `prune_action` (from `actions`) +- `shell/instrument/` — `reaper_bridge`, `reasampler_embed`, `vst_entry`, `reasampler_vst.h` / + `reasampler_uid.h`; **post-Q-W2v** the editor TUs (`editor_session` / `editor_controls` / + `editor_paint_sample` / `editor_paint_browse_zone` / `editor_input_sample` / + `editor_input_browse_zone` / `editor_platform` — `editor_layout` hoists *pure* → + `core/instrument/ui/`, discharging T2-06) and the processor TUs (`processor_state` / + `processor_reload` / lifecycle+`process()`) **`app/`:** `main.cpp` (post-Q-W3: API-pointer ownership + `ReaperPluginEntry` + dispatch only). @@ -2459,37 +2522,83 @@ directories. unified `Parser` (`::json`) must not collide once flattened into granular namespaces; resolve by subsystem home. -## The god-module split seams (Q-5) +## The god-module split seams (Q-5 — SETTLED 2026-07-28: T4 seam lists adopted; ~600 ceiling is the bar) -Split each to the audit-validated seams, no finer: +Split each to the audit-validated seams, no finer — **and every shipped TU lands under the +~600-line ceiling** (the Q-5 settlement: seams are the method, the ceiling is the bar; arbitrary +bisection to hit the number is rejected; the one documented exception is `sampler_core.cpp`). +LOC figures updated to the Q-W0 T4 census (2026-07-28): -- **`bank_panel.cpp` (2424 LOC → `shell/panel/`, Q-W2):** `panel_render` (draw/paint) / - `panel_thumbnails` (compute+cache) / `panel_audition` (preview engine — **hot path, direct - call-through**) / `panel_input` (mouse/key/wheel + new-content detection) / `panel_bank_ops` - (bank CRUD — the single owner W4 dedupes against) / `panel_window` (lifecycle + OS - drag-out/drop-target). Split the fat `bank_panel.h` per seam (I). -- **`main.cpp` (1762 LOC → hoist to `shell/capture/`, Q-W3):** `capture_orchestrator` - (`RunCapture`/`captureAndIndexOne`/`renderOffline`/batch/recapture/realtime `Run*`) / - `scope_resolve` (range/razor/track resolution + provenance assembly inputs) / - `realtime_lifecycle` (state machine + globals + selection guards). `FxBypassGuard` moves out - but **stays stack RAII** (precision-critical). `main.cpp` → `app/`, reduced to pointers + entry - + dispatch. -- **`actions.cpp` (981 LOC → `shell/actions/`, Q-W4):** `design_view_actions` / +- **`bank_panel.cpp` (3459 LOC → `shell/panel/`, Q-W2 — eight seams, T4-01):** `panel_render` + (draw/paint) / `panel_thumbnails` (compute+cache) / `panel_audition` (preview engine — **hot + path, direct call-through**) / `panel_input` (mouse/key/wheel + new-content detection) / + **`panel_layout`** (toolbar/footer/menu rects + row/cluster builders + region geometry — new + seam) / **`panel_drag`** (the card-drag/hover state machine, pure mirror `card_drag` — new + seam) / `panel_bank_ops` (bank CRUD — the single owner W4 dedupes against) / `panel_window` + (lifecycle + OS drag-out/drop-target). Without the two new seams, `panel_render` (~700) and + `panel_input` (~800) would ship over the ceiling. Split the fat `bank_panel.h` per seam (I). +- **`main.cpp` (1897 LOC → hoist to `shell/capture/`, Q-W3 — four hoists, T4-02):** + `capture_orchestrator` (`RunCapture`/`captureAndIndexOne`/`renderOffline`/single-capture + + realtime/insert action bodies — lands ~450) / **`capture_batch`** (batch family + + `RunRecaptureFromSource` + the two RAII selection guards — new hoist; recapture is + planner-driven like batch and shares the guard machinery) / `scope_resolve` (range/razor/track + resolution + provenance assembly inputs) / `realtime_lifecycle` (state machine + globals). + `FxBypassGuard` moves out but **stays stack RAII** (precision-critical). `main.cpp` → `app/`, + reduced to pointers + entry + dispatch. **Wave riders (SETTLED 2026-07-28):** delete + `ICaptureBackend` + correct the CLAUDE.md/CONTEXT description in the same commit (T4-26); + shared `stampCaptureSample` epilogue dedupe (T2-09); `capture_realtime_finalize` split with + the Q-9 rename (T4-08); `makeUniqueTag` per-session monotonic counter (T1-11); the pure + `wav_codec` consolidation (audit §4e). +- **`actions.cpp` (1016 LOC → `shell/actions/`, Q-W4 — T4-03: seams unchanged, still + sub-600):** `design_view_actions` / `bank_actions` / `prune_action` (`doBankPruneFolder` — the single file-deletion action). Dedupe `promptText`/`mintBankId` + bank verbs against `panel_bank_ops`. -- **`persist.cpp` (766 LOC → `shell/persist/`, Q-W5):** `session` (lifecycle/poll + +- **`persist.cpp` (852 LOC → `shell/persist/`, Q-W5 — T4-04: seams unchanged; the pS-usage + growth landed exactly where this wave isolates it):** `session` (lifecycle/poll + `BeginLoadProjectState` reload hook) / `ext_state_io` (serialization bridge + GUID minting + folder relocation) / **`prune_fs`** (prune scan + `deleteOrphanFile` via `SHFileOperationW` — - the isolated single file-deletion authority). + the isolated single file-deletion authority). **Wave rider (T2-04):** the `GetProjExtState` + grow-loop ×3 dedupes onto `bridge_marshal`'s generalized retry policy — `usage_scan`'s + prune-safety-adjacent copy included. +- **`reasampler_editor.cpp` (3065 LOC — the largest file in the repo → `shell/instrument/`, + Q-W2v — eight TUs, T4-11; split axis = the Sample/Browse/Zone face structure):** + `editor_session` / `editor_controls` / `editor_layout` (**pure-candidate hoist** → + `core/instrument/ui/`, `editor_geometry` the named owner — discharges T2-06's + stranded-layout-math finding) / `editor_paint_sample` / `editor_paint_browse_zone` / + `editor_input_sample` / `editor_input_browse_zone` / `editor_platform`. Rider: adopt the pure + `ThumbnailKey` on the VST side while the editor is open (T2-10). +- **`reasampler_processor.cpp` (1164 LOC → `shell/instrument/`, Q-W2v — three TUs, T4-12):** + `processor_state` / `processor_reload` / lifecycle+`process()` kept whole. **No virtual seam + on the atomic-pointer-swap reload pattern (T4-29).** +- **`sample_map` (970 LOC + 708-line header → Q-W2v, T4-13 ≡ T2-07):** resolution core vs the + **`component_state_io`** binary ComponentState codec (+ matching header split) — the codec + grows every envelope bump (v6→v11 in one quarter); the extension stops linking the whole voice + engine to serialize one preset blob. The `core/wire` LE byte-codec template + (`putLE`/`readLE`, T4-20) lands with it. +- **`sampler_core.cpp` (968 LOC + 762-line header — Q-W2v, T4-14/T4-27): the TU stays WHOLE — + the documented hot-path exception to the ~600 ceiling.** Envelope `tick()`s run + per-voice-per-sample; same-TU definition is what lets the compiler inline the stack (no LTO in + the build); a by-class split is the exact heuristic-(3) dispatch blowout. The header splits + into `zone_params.h` + `sampler_core.h`. Recorded here so nobody "fixes" it later. ## The JSON extraction (Q-2 / Q-W1) -Extract one pure **`core/json`** (`::json`): parser (`parseString`/`parseInt`/`parseKey`/ +Extract one pure **`core/json`** (`::json` — minted as **`json::Reader`/`json::Writer`**, Q-8 +SETTLED 2026-07-28): parser (`parseString`/`parseInt`/`parseKey`/ `skipValue` + escape) + serialize/emit helpers. Rewire `bank_model`, `bank_book`, -`view_mode_model`, `owned_manifest` onto it and **delete their four hand-rolled `Parser`s**. +`view_mode_model`, `owned_manifest`, **and `tail_control` (T2-02 — the fifth hand-rolled decoder +the §2 audit undercounted)** onto it and **delete all five hand-rolled decoders**. Round-trip output must be **byte-identical** to before — this is a structural dedupe, not a format change. Off all hot paths (serialization runs at save/load, never per frame) — safe to abstract -freely. +freely. **W1 siblings (from the Q-W0 sign-off):** the shared `readFileBytes` pure helper +(T2-03); the length-prefixed wire-`Cursor` collapse into one shared wire codec beside +`core/json`, consumed by `provenance` / `assignment_request` / `sample_usage` / +`parseBankGeneration` (T2-01(b) — the structural half; the hardening backport lands in Q-W0); +the **concrete** `ui::Rect` unification + `contains()` + per-role aliases, retiring the +XYWH-vs-LTRB fork — NOT a template (T2-05 ≡ T4-21), with the `clamp01` rider (T4-24) and the +`hitIndex` template only as an opportunistic follow-on (T4-22); the `slot_map` extraction +(T4-05) and optional `view_mode_model` planner split (T4-06) riding files W1 already opens; the +relocation scope grows to the ~20 clean VST pure libs under `core/instrument/` (T4-18). ## The OCP registration-table (Q-6 / Q-W6) @@ -2515,7 +2624,7 @@ focus. The audit (2026-07-27) is grep-verified; the load-bearing findings: one `json::Parser` in Q-W1; the shared pure-UI rect types `FooterRect` / `ButtonRect` (defined in `prune_button.h`, reused by `footer_bar.h` under an explicit hand-collision "NAME NOTE") get one `ui::` owner; `Sample` (`model::`) vs `AudioSample` (`audio::`) de-collide by home. -- **Genuine renames (Q-8/Q-9 — Daniel's call):** `BankIndex`→`BankModel` (the `bank_model.h` +- **Genuine renames (Q-8/Q-9 — SETTLED, Daniel 2026-07-28: all land):** `BankIndex`→`BankModel` (the `bank_model.h` file↔class word-mismatch — the worst legibility wart, rec: rename the class so the model family reads `BankModel`/`BankBook`/`ViewModeModel`); the unified JSON parser named `json::Reader`/ `json::Writer` at W1 mint; align `capture_realtime`(shell)/`realtime_record`(pure) to the house @@ -2550,6 +2659,25 @@ the following are acceptance criteria on every point: **Net:** every recommended split falls on a cold path or preserves call/inline shape on the two hot ones. *A split that would add a hot-path indirection is out of scope — rework it or drop it.* +## Structural heuristics (Daniel, 2026-07-28 — acceptance criteria phase-wide) + +Three heuristics postdate the original framing and bind every wave. They **generalize** the +three-hot-path performance guardrail above — they do not replace it: + +1. **More directories is a must; more files is good; ~600-line file ceiling.** SRP applies to + namespaces, encapsulation, and file organization alike. The ceiling is the *bar*, the audit's + named seams are the *method*: a file landing over ~600 needs a responsibility seam, not an + arbitrary bisection (bisection-to-hit-the-number is rejected). A documented hot-path + exception (`sampler_core.cpp`, T4-14/T4-27) is legitimate; silent overshoot is not. +2. **Templates are the right tool for compile-time dedup — use them where earned.** The LE + byte-codec `putLE`/`readLE` (T4-20) is earned: compile-time dispatch, zero runtime cost, off + the hot paths. The rect family is NOT (T4-21): the types differ in name only, so one + **concrete** `ui::Rect` — a template there would model nothing. +3. **SOLID is great, but saved CPU is better.** No dispatch-stack blowouts *anywhere* — not just + the three named hot paths; prefer static polymorphism where the types are compile-time-known. + The T4-27 warning is the canonical case: a by-class `sampler_core` split would put virtual + envelope `tick()`s on the per-voice-per-sample path — exactly the blowout this forbids. + ## The GATE (load-bearing — Phase Q is last) Phase Q is **gated on the tree being otherwise quiescent.** Daniel's plain readiness target: @@ -2583,16 +2711,27 @@ every commit), a property only an *incremental* reorg uses. Risk-ordered: pre-restructure audit wave (Q-W0)"). Produces a written, triaged findings report; runs **first** and **gates Q-W1** — no structural point begins until the triage closes and Daniel signs off on every disposition. Fix-now findings are remediated here or folded into the wave that opens the - file; the report may add/reshape downstream Q-W1..Q-W6 points before they start. -- **Q-W1** — safe opener: `core/json` extract (delete 4 `Parser`s) + impose the directory/ + file; the report may add/reshape downstream Q-W1..Q-W6 points before they start. **COMPLETE + and signed off 2026-07-28; the sub-gate closes when the six fix-now remediations land + (`pq-w0-fixes`).** +- **Q-W1** — safe opener: `core/json` extract (delete the 5 hand-rolled decoders — T2-02 adds + `tail_control`) + impose the directory/ namespace layout on the 30 clean pure libs + clean shells (pure relocation, no logic change). All later waves assume this layout. **Carries the naming collision fixes + the model-class - renames (Q-8), which are free during this relocation.** -- **Q-W2..Q-W5** — the four god-module splits, one per wave, risk-ordered (`bank_panel` → - `main.cpp` → `actions.cpp` → `persist.cpp`). Q-W4 depends on Q-W2 (`panel_bank_ops` dedupe - target); Q-W5 best after Q-W4 (`prune_action` → `prune_fs` routing); otherwise parallel-safe. + renames (Q-8), which are free during this relocation.** **Scope grown by Q-W0 (2026-07-28):** + + `readFileBytes` (T2-03), the wire-`Cursor` codec (T2-01(b)), the concrete `ui::Rect` + unification (T2-05 ≡ T4-21) + `clamp01` (T4-24), the `slot_map`/planner riders (T4-05/T4-06), + and the ~20 clean VST pure libs under `core/instrument/` (T4-18). +- **Q-W2..Q-W5 (+ Q-W2v)** — the god-module splits, risk-ordered (`bank_panel` → + `main.cpp` → `actions.cpp` → `persist.cpp`), plus **Q-W2v** (NEW, added at the Q-W0 sign-off: + the VST god-modules — editor eight TUs / processor three TUs / `component_state_io`; + `sampler_core` TU whole, the documented exception), which **runs parallel with Q-W2** + (different artifact, zero file overlap — audit §4f SETTLED). Q-W4 depends on Q-W2 + (`panel_bank_ops` dedupe target); Q-W5 best after Q-W4 (`prune_action` → `prune_fs` routing); + otherwise parallel-safe. **Q-W2 carries the `panel_*` names; Q-W3 carries the `capture_realtime`/`realtime_record` - word-order fix (Q-9).** + word-order fix (Q-9) + the settled riders (`ICaptureBackend` deletion + doc correction, + `wav_codec`, `stampCaptureSample` dedupe, T1-11, `capture_realtime_finalize`).** - **Q-W6** — OCP registration-table + residual fat-header (I) splits. Depends on Q-W3 (registration code isolated first). Sequenced last; most droppable if narrowing. - **Naming (Q-7): no dedicated wave** — every rename rides the wave already relocating/splitting @@ -2624,7 +2763,9 @@ every commit), a property only an *incremental* reorg uses. Risk-ordered: realtime-tick — ever (a hard acceptance bar, not advice). - **No design-level (D-letter SOLID) rework.** `main`/`bank_panel` depending on concrete capture backends is a low-priority Dependency-Inversion concern — **out of scope** (a design change, not - a reorg). Phase Q reorganizes; it does not re-architect interfaces. + a reorg). Phase Q reorganizes; it does not re-architect interfaces. (Deleting the *dead* + `ICaptureBackend` abstraction in Q-W3 is the opposite move — removing a false interface with + one deriver and zero polymorphic call sites, T4-26 — and is in scope.) - **No `peaks` data-ownership change.** `peaks` forcing a whole-file `std::vector` copy on the thumbnail path is noted but **not touched** — reworking it risks the hot path. - **No big-bang commit.** Every wave is independently landable and CTest-green; reject a change set @@ -2632,5 +2773,7 @@ every commit), a property only an *incremental* reorg uses. Risk-ordered: - **Do not begin before the GATE.** Re-confirm the tree is quiescent (Phase S + L + D2 merged/closed; M9 abandoned) before any Q point. **And do not begin any structural point (Q-W1+) before the Q-W0 sub-gate:** the audit's triage is complete and Daniel has signed off on every finding's disposition. + (Sign-off given 2026-07-28; the sub-gate now closes when the six fix-now remediations land on + `pq-w0-fixes`.) - **Verify** the CMake `src/` path updates and the SWELL/LICE surfaces still resolve after relocation, as the existing build already requires. diff --git a/PLAN.md b/PLAN.md index 7d52c69..b907ed6 100644 --- a/PLAN.md +++ b/PLAN.md @@ -220,8 +220,21 @@ panel adopts knob deck + curve popup, `param_slider` slider rows retired on that > downstream Q-W1..Q-W6 points; fixes that Q-W0 classifies fix-now are remediated in Q-W0 (or folded > into the wave that already touches the file), **not** deferred silently into the structural waves. > +> **Q-W0 SIGN-OFF: COMPLETE (Daniel, 2026-07-28).** The audit ran as four parallel tracks (T1 DSP, +> T2 architecture, T3 env-coupled constants, T4 sizing/placement — **59 findings**; report +> `docs/product/code-quality-audit.md`, appendices `docs/product/audit-notes/`), and **all 59 +> findings' dispositions are approved as proposed.** The Q-W1 sub-gate is satisfied **once the six +> approved fix-now remediations land** (in flight on branch `pq-w0-fixes`, Q-W0-scoped): T1-01, +> T1-03, T1-09, T2-01(a), T3-01, T3-03. The audit's §3 plan reshape is **folded into the waves +> below** (Q-W2 6→8 seams; NEW wave **Q-W2v** parallel with Q-W2; Q-W3 3→4 hoists + riders; Q-W5 +> + the ext-state-loop dedupe), and its §4 decision list is settled — see the settlement block +> below. The Q-11 question is answered by the audit: the SOLA pitch engine is **sound — no +> technique replacement warranted**; every pitch finding is a bounded in-technique fix or a +> documented operating limit. +> > **Settled (Q-1, this-doc):** the phase is **`Q` (Quality)**; point-id family `Q1..Qn`, wave -> prefixes `Q-W0` (the pre-restructure audit) then `Q-W1..Q-W6` (the structural reorg). +> prefixes `Q-W0` (the pre-restructure audit) then `Q-W1..Q-W6` (the structural reorg; **+ +> `Q-W2v`**, the VST god-module wave added at the Q-W0 sign-off, 2026-07-28). > **Settled (Q-10/Q-11, Daniel 2026-07-27):** Q-10 audit-report home = a **committed doc** > (`docs/product/code-quality-audit.md`, not a tracked issue list); Q-11 pitch-remediation depth = > **defer to findings** (default document-and-defer; weigh a bounded OLA fix before a technique @@ -238,6 +251,25 @@ panel adopts knob deck + curve popup, `param_slider` slider rows retired on that > `BankModel`; the JSON `Parser`→`json::Reader`/`Writer`), leave the merely-quirky (rec); > Q-9 align the `capture_realtime`/`realtime_record` shell↔core word order during W3 (rec: yes).** > +> **SETTLED (Daniel, 2026-07-28 — with the Q-W0 sign-off; the REC record above kept as history):** +> **Q-5 SETTLED** — split to **seams-by-responsibility with the T4 seam lists adopted** +> (`bank_panel` 6→8 seams adding `panel_layout` + `panel_drag`, T4-01; `capture_orchestrator` +> further split with `capture_batch`, T4-02), and the **~600-line file ceiling is an acceptance +> criterion on every split wave** — seams are the method, the ceiling is the bar; arbitrary +> bisection to hit the number is rejected. **Q-6 SETTLED: in scope, last wave, as planned.** +> **Q-8 SETTLED: both renames** — `BankIndex`→`BankModel` (W1) and the JSON parser minted as +> `json::Reader`/`json::Writer` (W1); additionally from the audit, **`ICaptureBackend` is deleted +> in Q-W3** (T4-26 — one deriver, zero polymorphic call sites; the CLAUDE.md/CONTEXT "two +> backends behind one interface" correction **rides Q-W3 itself**, recorded as a rider — the docs +> are not edited before that wave). **Q-9 SETTLED: yes** — align to stem `capture_realtime`, +> shell suffixed, during W3. **VST placement (audit §4a) SETTLED: T4-18** — `src/vst/` integrates +> into the single `core/`/`shell/` top split as `core/instrument/{engine,map,ui}` + +> `shell/instrument/` (Q-3 directory map updated in CONTEXT.md §Phase Q). **WAV/RIFF +> consolidation (audit §4e) SETTLED:** a named rider on **Q-W3** — one pure **`wav_codec`** owner +> (walker + layout + build + patch), absorbing the T4-10 ingest extraction. **Q-W2v scheduling +> (audit §4f) SETTLED: parallel with Q-W2** (different artifact, zero file overlap; the serial +> "Q-W7" alternative set aside). +> > **HARD CONSTRAINT — performance (see CONTEXT.md §Phase Q, `docs/product/code-organization.md` > §3).** The reorg must cost **zero runtime.** On the three hot paths — `peaks` envelope > compute, audition/preview, the realtime-capture tick — **no added virtual dispatch, no @@ -247,6 +279,21 @@ panel adopts knob deck + curve popup, `param_slider` slider rows retired on that > an acceptance criterion on every point: *a split that would add a hot-path indirection is out > of scope — rework it or drop it.* > +> **STRUCTURAL HEURISTICS (Daniel, 2026-07-28 — acceptance criteria on every wave; these +> *generalize* the three-hot-path guardrail above, they do not replace it):** +> (1) **More directories is a must, more files is good, ~600-line file ceiling** — SRP applies to +> namespaces, encapsulation, and file organization alike. The ceiling is the *bar*, the audit's +> named seams are the *method*: a file landing over ~600 needs a responsibility seam, not an +> arbitrary bisection; a documented hot-path exception (`sampler_core.cpp`, T4-14/T4-27) is +> legitimate, silent overshoot is not. +> (2) **Templates are the right tool for compile-time dedup — use them where earned** (the LE +> byte codec `putLE`/`readLE`, T4-20), not for name-only unification (the rect family is one +> **concrete** `ui::Rect`, NOT a template — T4-21's ruling). +> (3) **SOLID is great but saved CPU is better** — no dispatch-stack blowouts *anywhere*, not +> just the three named hot paths; prefer static polymorphism where types are compile-time-known +> (T4-27's warning is the canonical case: a by-class `sampler_core` split would put virtual +> envelope `tick()`s on the per-voice-per-sample path). +> > **NAMING dimension (added 2026-07-27; grep-verified audit in `docs/product/code-organization.md` > §2b).** Beyond giving symbols a directory + namespace *home* (Q-3/Q-4), Phase Q also gives > poorly/inconsistently-named symbols a consistent *name*, against the same Vital bar. The audit @@ -265,9 +312,14 @@ panel adopts knob deck + curve popup, `param_slider` slider rows retired on that > per-module static-lib + per-module test-executable seams already draw the module boundaries; > a file move + namespace change is mechanically verifiable — `ctest --test-dir build` is green > or it isn't. **Green-CTest-at-every-point is an acceptance criterion.** Big-bang is rejected; -> the reorg is risk-ordered waves (W1 safe opener → W2–W5 god-module splits → W6 OCP finish). +> the reorg is risk-ordered waves (W1 safe opener → W2/W2v–W5 god-module splits → W6 OCP finish). ## Q-W0 — pre-restructure functional + DSP quality audit (runs FIRST; gates Q-W1) +**STATUS (2026-07-28): audit COMPLETE, triage COMPLETE, sign-off COMPLETE.** The findings report +is committed (`docs/product/code-quality-audit.md`; track appendices in +`docs/product/audit-notes/` — T1 DSP, T2 architecture, T3 env-constants, T4 sizing/placement; 59 +findings). Daniel approved every disposition 2026-07-28. **The Q-W1 sub-gate is satisfied once +the six approved fix-now remediations land** (final point below; in flight on `pq-w0-fixes`). **Goal:** Before a single structural point moves, perform a **thorough static/functional audit** of the codebase and produce a **written, triaged findings report**. This is the *functional-correctness and algorithm-quality* complement to the grep-verified SOLID/naming audit that already grounds @@ -322,19 +374,33 @@ before/after listening or null check. **The gate to Q-W1 is: triage complete + D are a decision, not an omission. - [ ] **Sign-off gate.** Daniel reviews the triage and signs off on each disposition. Q-W1 does not begin until this is done; fold any new/reshaped downstream points the audit surfaces into - Q-W1..Q-W6 before starting them. + Q-W1..Q-W6 before starting them. **DONE (Daniel, 2026-07-28): all 59 dispositions approved as + proposed; the §3 plan reshape and §4 decisions are folded into Q-W1..Q-W6 + Q-W2v below.** +- [ ] **Fix-now remediations (approved 2026-07-28; Q-W0-scoped; in flight on branch + `pq-w0-fixes`):** T1-01 linked-lag stereo splice alignment, T1-03 Preserve prime bound + + immediate `freezeTail()`, T1-09 `declickR_` dead-state removal (rider on the `sampler_core` + edits), T2-01(a) provenance wire-cursor hardening backport, T3-01 gain-ramp seconds, T3-03 + fade-ceiling seconds. Each lands with its module's CTest target green; the audible DSP fixes + with a stated before/after listening check. **Landing these closes Q-W0 and opens Q-W1.** ## Q-W1 — safe opener: extract `core/json` + impose the directory/namespace layout on clean modules **Goal:** The zero-god-module-risk opener. Two moves: (1) extract a pure **`core/json`** module -(parser + serializer) and **delete the four hand-rolled `Parser`s** in `bank_model` / -`bank_book` / `view_mode_model` / `owned_manifest` (the single largest DRY+SRP violation, and -entirely off the hot paths); (2) impose the settled `core/`/`shell/`/`app/` directory layout + -sub-namespaces (`reasampler::model`/`view`/`capture`/`audio`/`ui`/`reclaim`/`version`/`json`) on -the **30 clean pure libs + the clean shells that need no splitting** — pure relocation, no logic -change. Proves the wave discipline (relocate + encapsulate, CTest-green) before any god-module -surgery. CONTEXT.md §Phase Q (json extraction; directory + namespace map). -**Verify:** CTest green at every commit. The four duplicate `Parser`s are gone, replaced by one -`core/json` consumed by all four models; round-trip serialization is byte-identical to before +(parser + serializer) and **delete the five hand-rolled JSON decoders** — the four `Parser`s in +`bank_model` / `bank_book` / `view_mode_model` / `owned_manifest` **plus `tail_control`'s fifth +decoder** (T2-02, Q-W0's undercount fix — the wave's one-JSON-path goal is not met without it) — +the single largest DRY+SRP violation, entirely off the hot paths; (2) impose the settled +`core/`/`shell/`/`app/` directory layout + sub-namespaces +(`reasampler::model`/`view`/`capture`/`audio`/`ui`/`reclaim`/`version`/`json` + the `instrument` +family) on the **30 clean pure libs + the clean shells that need no splitting + the ~20 clean +VST pure libs** (T4 §1.5, under the settled T4-18 `core/instrument/{engine,map,ui}` + +`shell/instrument/` shape) — pure relocation, no logic change. **Q-W0 additions (SETTLED +2026-07-28):** the shared `readFileBytes` pure helper (T2-03); the wire-`Cursor` collapse into +one shared codec beside `core/json` (T2-01(b)); the rect unification — one **concrete** +`ui::Rect` + `contains()` + per-role aliases, NOT a template (T2-05 ≡ T4-21). Proves the wave +discipline (relocate + encapsulate, CTest-green) before any god-module surgery. CONTEXT.md +§Phase Q (json extraction; directory + namespace map). +**Verify:** CTest green at every commit. The five duplicate JSON decoders are gone, replaced by +one `core/json` consumed by all five consumers; round-trip serialization is byte-identical to before (no format change — a *structural* dedupe, not a behavior change). Every relocated clean module compiles and its test executable passes unmoved. `Sample` (model) vs `AudioSample` (audio) vs unified `Parser` (json) do not collide once sub-namespaced. No REAPER type crosses into any @@ -345,81 +411,174 @@ any fix-now findings the audit assigned to Q-W1 folded in). First structural wav - [ ] Extract `core/json` (pure parser + serializer: parseString/parseInt/parseKey/skipValue + escape, plus emit helpers); unify under `reasampler::json`; guard the `Parser` name against cross-lib collision. Off all hot paths — safe to abstract freely. -- [ ] Rewire `bank_model`, `bank_book`, `view_mode_model`, `owned_manifest` onto `core/json`; - **delete the four duplicate `Parser`s.** Round-trip output byte-identical (dedupe, not - reformat). +- [ ] Rewire `bank_model`, `bank_book`, `view_mode_model`, `owned_manifest`, **and + `tail_control` (T2-02)** onto `core/json`; **delete the five duplicate decoders.** Round-trip + output byte-identical (dedupe, not reformat). +- [ ] Add the shared `readFileBytes` pure helper to the `core/` utility home; both artifacts + link it (T2-03). +- [ ] Collapse the length-prefixed wire-`Cursor` family into one shared wire codec beside + `core/json`, consumed by `provenance` / `assignment_request` / `sample_usage` / + `parseBankGeneration` (T2-01(b) — the structural half; the hardening backport lands in Q-W0). +- [ ] Riders on files this wave already opens: `slot_map` extraction from `bank_book` (T4-05); + `view_mode_model` planner split (T4-06 — optional if the wave wants to stay minimal); + `view_lanes` split only if relocation touches `view.cpp` anyway (T4-09). - [ ] Relocate the 30 clean pure libs into `core/{model,view,capture,audio,ui,reclaim,version, - json}/` and the clean shells into `shell/{capture,panel,view,persist,actions}/`; move - `main.cpp` to `app/`. Update `CMakeLists.txt` `src/` paths only (no target-graph change). + json}/` **+ the ~20 clean VST pure libs into `core/instrument/{engine,map,ui}/` (T4-18 + SETTLED, Daniel 2026-07-28)**, and the clean shells into + `shell/{capture,panel,view,persist,actions,instrument}/`; move `main.cpp` to `app/`. Update + `CMakeLists.txt` `src/` paths only (no target-graph change). - [ ] Apply sub-namespaces matching the directories on every relocated *clean* module (the - god-modules re-namespace their own new TUs as they split, W2–W5). Resolve `Sample`/ + god-modules re-namespace their own new TUs as they split, W2/W2v–W5). Resolve `Sample`/ `AudioSample`/`Parser` homes. **This alone resolves the naming *collisions*** (§2b.2): the shared pure-UI rect types (`FooterRect`/`ButtonRect`/`Selection`/`CellRect`) get one `ui::` - owner — retire the hand-collision "NAME NOTE" in `footer_bar.h`. -- [ ] **Naming riders (Q-8, if settled):** rename the survivor JSON parser to `json::Parser` - (or `json::Reader`/`json::Writer`); if Daniel takes the `BankIndex`→`BankModel` rename, land - it here (mechanical class rename, verified by `bank_model_tests`). No rename on a file this - wave isn't already relocating (Q-7). + owner — one **concrete** `ui::Rect` + `contains()` + per-role aliases, retiring the + XYWH-vs-LTRB fork and folding in `editor_geometry`'s `Rect` (T2-05 ≡ T4-21 — explicitly NOT a + template); retire the hand-collision "NAME NOTE" in `footer_bar.h`. Riders: `clamp01` dedup + (T4-24); the `hitIndex` hit-test template only as an opportunistic follow-on once the rect + unification lands (T4-22). +- [ ] **Naming riders (Q-8 — SETTLED, Daniel 2026-07-28: both renames):** the survivor JSON + parser is minted as **`json::Reader`/`json::Writer`**; land **`BankIndex`→`BankModel`** here + (mechanical class rename, verified by `bank_model_tests`). No rename on a file this wave isn't + already relocating (Q-7). - [ ] Confirm CTest green + no hot-path change: `peaks`/audition/realtime-tick untouched by this wave (pure relocation of clean modules; `peaks` stays a free function). -## Q-W2 — split `bank_panel.cpp` (the biggest god-module, 2424 LOC) -**Goal:** Split the largest god-module (8+ responsibilities) along the audit's named seams: +## Q-W2 — split `bank_panel.cpp` (the biggest extension god-module — 3459 LOC at the Q-W0 census) +**Goal:** Split the largest extension god-module (8+ responsibilities) along the audit's named +seams — **eight TUs (Q-5 SETTLED with the T4-01 reshape, Daniel 2026-07-28)**: `panel_render` / `panel_thumbnails` / `panel_audition` / `panel_input` / `panel_bank_ops` / -`panel_window`. Split the fat `bank_panel.h` alongside (Interface Segregation). **Preserve the +`panel_window` **+ `panel_layout` (toolbar/footer/menu rects + row/cluster builders + region +geometry glue) + `panel_drag` (the card-drag/hover state machine — it already has a pure mirror, +`card_drag`)** — without the two new seams, `panel_render` (~700) and `panel_input` (~800) would +ship over the ~600 ceiling on day one. Split the fat `bank_panel.h` alongside (Interface +Segregation). **Preserve the audition hot path as a direct call-through, never virtual.** `panel_bank_ops` becomes the single home for the bank-CRUD verbs that W4 will dedupe `actions.cpp` against. CONTEXT.md §Phase Q (bank_panel split seams; hot-path audition guardrail). See `docs/product/code-organization.md` §2.1, §5. -**Verify:** CTest green at every commit. Each seam is its own TU under `shell/panel/`; the panel +**Verify:** CTest green at every commit. Each seam is its own TU under `shell/panel/` and +**every TU lands under the ~600-line ceiling** (the Q-5 acceptance bar); the panel draws, thumbnails, auditions, handles input, does bank ops, and manages its window exactly as before (no behavior change — verify in DAW that the panel is visually and interactively unchanged). Audition/preview call path stays a **direct call-through** (no virtual dispatch, no added header→TU indirection on the preview path). The ~20-function public API is now segmented across the split headers. **Depends on:** Q-W1 (directory/namespace layout established). Independently landable. +**Parallel-safe with Q-W2v** (different artifact, zero file overlap — §4f SETTLED). - [ ] Split rendering (`draw*`/`paint*`) → `panel_render`; thumbnail compute+cache → - `panel_thumbnails`. + `panel_thumbnails`; toolbar/footer/menu rect + row/cluster builders + region geometry → + **`panel_layout`** (new seam, T4-01). - [ ] Split the audio audition/preview engine → `panel_audition` — **direct call-through, not virtual; preview idle path unchanged.** - [ ] Split input handling (mouse/key/wheel) + new-content detection → `panel_input`; window - lifecycle + OS drag-out/drop-target → `panel_window`. + lifecycle + OS drag-out/drop-target → `panel_window`; the card-drag/hover state machine → + **`panel_drag`** (new seam, T4-01). Per-mouse-move work stays plain free-function calls + (T4-28). - [ ] Extract bank-CRUD verbs → `panel_bank_ops` (the future single owner; W4 dedupes `actions.cpp` against it). Split `bank_panel.h` into per-seam headers (I). -- [ ] Verify in DAW: panel unchanged; CTest green; no hot-path indirection added. +- [ ] Verify in DAW: panel unchanged; CTest green; no hot-path indirection added; all eight TUs + under the ~600 ceiling. + +## Q-W2v — split the VST god-modules (NEW wave — Q-W0 T4 §1.5; runs parallel with Q-W2) +**Goal:** Close the audit's structural scope gap: the VST artifact's god-modules had no owning +wave, and `reasampler_editor.cpp` (3065 LOC) is the largest file in the repo. Split the editor +into **eight TUs along the Sample/Browse/Zone face axis** (T4-11): `editor_session` / +`editor_controls` / `editor_layout` (**pure-candidate hoist** into the existing pure homes — +`editor_geometry` is the named owner; this discharges T2-06's stranded-layout-math finding) / +`editor_paint_sample` / `editor_paint_browse_zone` / `editor_input_sample` / +`editor_input_browse_zone` / `editor_platform`. Split `reasampler_processor.cpp` (1164 LOC) into +**three TUs** (T4-12): `processor_state` / `processor_reload` / lifecycle+`process()` kept +whole. Split `sample_map` into resolution core vs the **`component_state_io`** binary codec + +matching header split (T4-13 ≡ T2-07 — the codec grows every envelope bump; the extension stops +linking the whole voice engine to serialize one preset blob). **`sampler_core.cpp` stays whole +(968 LOC) — a DOCUMENTED hot-path exception to the ~600 ceiling** (T4-14/T4-27: envelope +`tick()`s run per-voice-per-sample; same-TU definition is what lets the compiler inline the +stack, no LTO in the build; a by-class split is the exact heuristic-(3) dispatch blowout); its +header splits into `zone_params.h` + `sampler_core.h`. The `core/wire` LE byte-codec template +(`putLE`/`readLE`, T4-20) lands here with its biggest consumer. CONTEXT.md §Phase Q (VST split +seams; `sampler_core` exception). +**Verify:** CTest green at every commit. Editor and processor behave identically in DAW (visual ++ interactive parity across all three faces; `process()` audio unchanged). Every new TU lands +under the ~600-line ceiling **except the one documented `sampler_core.cpp` exception**. +`process()` + its per-block helpers stay one TU; the atomic-pointer-swap reload pattern gains +**no virtual seam** (T4-29); no dispatch-stack blowout anywhere (heuristic 3). +**Depends on:** Q-W1 (layout + the T4-18 `instrument/` placement established). **Parallel-safe +with Q-W2** (different artifact, zero file overlap — §4f SETTLED, Daniel 2026-07-28; the serial +"Q-W7" alternative was set aside). + +- [ ] Split `reasampler_editor.cpp` → the eight face-axis TUs; hoist `editor_layout`'s pure + geometry into the existing pure homes (`editor_geometry` — discharges T2-06). +- [ ] Split `reasampler_processor.cpp` → `processor_state` / `processor_reload` / + lifecycle+`process()` whole; **no virtual seam on the atomic-swap pattern** (T4-29). +- [ ] Split `sample_map` → resolution core + `component_state_io` codec (+ header split); the + extension's preset-blob path stops linking the voice engine (T4-13 ≡ T2-07). +- [ ] `sampler_core`: split `zone_params.h` out of the header; **TU stays whole — documented + exception** (T4-14/T4-27), recorded in the wave brief so nobody "fixes" it later. +- [ ] Land the `core/wire` LE byte-codec template (`putLE`/`readLE`, T4-20) with + `component_state_io`; other consumers rewire opportunistically. +- [ ] Rider: adopt the pure `ThumbnailKey` on the VST editor side (T2-10). +- [ ] Verify in DAW: editor + processor unchanged; CTest green; ceiling met (one documented + exception); no added dispatch. ## Q-W3 — split `main.cpp` (hoist orchestration; leave main = pointers + entry + dispatch) -**Goal:** Reduce `main.cpp` (1762 LOC) to its actual job — API pointers + `ReaperPluginEntry` + -dispatch (~the owns-pointers ~120 lines) — by hoisting: `capture_orchestrator` (`RunCapture` / -`captureAndIndexOne` / `renderOffline` / batch/recapture/realtime `Run*`), `scope_resolve` +**Goal:** Reduce `main.cpp` (1897 LOC at the Q-W0 census) to its actual job — API pointers + +`ReaperPluginEntry` + dispatch — by hoisting **four** TUs (T4-02 reshape, SETTLED with Q-5, +Daniel 2026-07-28 — the planned three left `capture_orchestrator` at ~885, over the ceiling): +`capture_orchestrator` (`RunCapture` / `captureAndIndexOne` / `renderOffline` / single-capture + +realtime/insert action bodies — lands ~450), **`capture_batch`** (the batch family + +`RunRecaptureFromSource` + the two RAII selection guards — recapture is planner-driven like +batch and shares the guard machinery), `scope_resolve` (`resolveRange`/`resolveRazorRange`/`collectSelectedTracks` + provenance assembly inputs), and -`realtime_lifecycle` (the realtime-capture state machine + globals + selection guards). +`realtime_lifecycle` (the realtime-capture state machine + globals). **`FxBypassGuard` moves out but stays a stack RAII object (precision-critical); the realtime idle -tick stays a single pointer test.** CONTEXT.md §Phase Q (main split seams; FxBypassGuard + +tick stays a single pointer test.** **Q-W0 riders owned by this wave (all SETTLED 2026-07-28):** +delete `ICaptureBackend` (T4-26 — one deriver, zero polymorphic call sites; `OfflineRenderBackend` +becomes concrete; the CLAUDE.md/CONTEXT "two backends behind one interface" correction **rides +this wave's own commit**, not earlier); the shared `stampCaptureSample` capture-epilogue dedupe +(T2-09); the `capture_realtime_finalize` split riding the Q-9 rename (T4-08); the `makeUniqueTag` +per-session monotonic-counter fix (T1-11); and the **WAV/RIFF consolidation (audit §4e)** — one +pure **`wav_codec`** owner (walker + layout + build + patch), absorbing `ingest.cpp`'s pure WAV +build helpers (T2-08 / T4-23 / T4-10). CONTEXT.md §Phase Q (main split seams; FxBypassGuard + realtime-tick guardrails). See `docs/product/code-organization.md` §2.1, §3. **Verify:** CTest green at every commit. Capture (offline + realtime + batch + recapture) behaves identically in DAW; the null test still nulls, bit-identical repeats still match (the precision invariants `FxBypassGuard` protects are unchanged); capture ≠ placement holds (no hoisted `Run*` path gains an `InsertMedia` call). The realtime idle fast-path is still a single pointer test. -`main.cpp` is now pointers + entry + dispatch only. -**Depends on:** Q-W1. Independent of Q-W2. +`main.cpp` is now pointers + entry + dispatch only. The four hoisted TUs + `wav_codec` land +under the ~600 ceiling; the WAV/RIFF layout has **one** pure owner (the dedup-by-hash and +null-test invariants now rest on one implementation); `ICaptureBackend` is gone with no behavior +change and the CLAUDE.md/CONTEXT description is corrected in the same commit. +**Depends on:** Q-W1. Independent of Q-W2/Q-W2v. - [ ] Hoist capture orchestration → `capture_orchestrator` (`shell/capture/`); keep `FxBypassGuard` a **stack RAII** object as it moves (precision-invariant-critical). +- [ ] Hoist the batch family + `RunRecaptureFromSource` + the two RAII selection guards → + **`capture_batch`** (fourth hoist, T4-02) so `capture_orchestrator` lands ~450. - [ ] Hoist scope/source resolution + provenance assembly inputs → `scope_resolve`. -- [ ] Hoist the realtime-capture lifecycle state machine + globals + the two RAII selection - guards → `realtime_lifecycle`; **idle tick stays a single pointer test.** +- [ ] Hoist the realtime-capture lifecycle state machine + globals → `realtime_lifecycle`; + **idle tick stays a single pointer test.** - [ ] Leave `main.cpp` = API-pointer ownership + `ReaperPluginEntry` + dispatch; move to `app/`. -- [ ] **Naming rider (Q-9, if settled):** align the `capture_realtime` (shell) / `realtime_record` - (pure) word-order inversion to the house shell↔core convention (rec: stem `capture_realtime`, - shell suffixed) — a free rider since W3 already hoists the realtime lifecycle. No rename on a - file this wave isn't already touching (Q-7). +- [ ] **Naming rider (Q-9 — SETTLED, Daniel 2026-07-28: yes):** align the `capture_realtime` + (shell) / `realtime_record` (pure) word-order inversion to the house shell↔core convention — + the pure module takes the stem `capture_realtime`, the shell takes the suffix + (`drag_out`↔`drag_out_win` is the model). Split `capture_realtime_finalize` (async lifecycle + vs file-side finalize) in the same surgery (T4-08). No rename on a file this wave isn't + already touching (Q-7). +- [ ] Delete `ICaptureBackend` (T4-26): `OfflineRenderBackend` becomes concrete; correct the + CLAUDE.md/CONTEXT "two backends behind one interface" description **in the same commit**. +- [ ] Dedupe the capture-stamp epilogue → shared `stampCaptureSample` (T2-09 — the divergent + bits stay in the realtime caller); fix `makeUniqueTag` with a per-session monotonic counter, + both call sites (T1-11 — same-second batch captures currently collide silently). +- [ ] **WAV/RIFF consolidation rider (audit §4e — SETTLED, Daniel 2026-07-28):** one pure + `wav_codec` owner (chunk walker + layout + build + patch), absorbing `ingest.cpp`'s pure + WAV/PCM build (T4-10 — the ingest shell drops to ~500 and the WAV build gains a test target). - [ ] Verify in DAW: null test nulls, bit-identical repeats match, capture≠placement holds; CTest green; no realtime-tick branch-shape change. ## Q-W4 — split `actions.cpp` + dedupe bank verbs against `panel_bank_ops` -**Goal:** Split the two unrelated command-id families in one TU (981 LOC) into +**Goal:** Split the two unrelated command-id families in one TU (1016 LOC at the Q-W0 census — +T4-03: the planned seams still land sub-600, no reshape) into `design_view_actions` / `bank_actions` / `prune_action`, and **dedupe** `actions.cpp`'s own `promptText`/`mintBankId` and bank verbs against the `panel_bank_ops` single-owner established in Q-W2. `prune_action` keeps the `doBankPruneFolder` deletion authority contract intact (routes to @@ -442,12 +601,16 @@ contract — a reorg must not touch a shipped command id). no-undo/no-ext-state; CTest green. ## Q-W5 — split `persist.cpp` (isolate the single file-deletion authority into `prune_fs`) -**Goal:** Split `persist.cpp` (766 LOC, 5 responsibilities) into `session` (lifecycle+poll, -`BeginLoadProjectState` reload hook), `ext_state_io` (the ext-state ↔ JSON serialization bridge + -GUID minting + folder relocation), and **`prune_fs`** (prune scanning + `deleteOrphanFile` via -`SHFileOperationW`). The split **concentrates** the byte-deleting authority into one obvious -module — it must never spread it. CONTEXT.md §Phase Q (persist split seams; deletion-authority -isolation). See `docs/product/code-organization.md` §2.1, §7. +**Goal:** Split `persist.cpp` (852 LOC at the Q-W0 census — T4-04: seams unchanged; the +pS-usage growth landed exactly where this wave isolates it; 5 responsibilities) into `session` +(lifecycle+poll, `BeginLoadProjectState` reload hook), `ext_state_io` (the ext-state ↔ JSON +serialization bridge + GUID minting + folder relocation), and **`prune_fs`** (prune scanning + +`deleteOrphanFile` via `SHFileOperationW`). The split **concentrates** the byte-deleting +authority into one obvious module — it must never spread it. **Q-W0 rider (T2-04, SETTLED +2026-07-28):** generalize the `GetProjExtState` grow-loop retry policy into `bridge_marshal`'s +pure decode home (or its `core/` successor) and rewire all three hand-rolled copies — +`usage_scan`'s prune-safety-adjacent copy included. CONTEXT.md §Phase Q (persist split seams; +deletion-authority isolation). See `docs/product/code-organization.md` §2.1, §7. **Verify:** CTest green at every commit. Session save/load/undo-reload, ext-state round-trip, folder relocation, and prune deletion all behave identically in DAW. **File deletion lives in exactly one module (`prune_fs`)** — the single-file-deletion-authority invariant is *improved* @@ -459,6 +622,9 @@ independently landable. (serialization bridge + GUID minting + folder relocation). - [ ] Isolate prune scanning + `deleteOrphanFile` (`SHFileOperationW`) → **`prune_fs`** — the one file-deletion module; nothing else may delete bytes. +- [ ] Dedupe the `GetProjExtState` grow-loop ×3 (T2-04): one retry policy generalized from + `bridge_marshal`; rewire `usage_scan`'s prune-safety-adjacent copy with `sample_usage_tests` + green. - [ ] Verify in DAW: save/load/undo-reload/relocation/prune unchanged; deletion authority is one module; relative-paths-only holds; CTest green. @@ -466,7 +632,9 @@ independently landable. **Goal:** Close the last SOLID wart: replace the ~350-line hand-written **non-table** action registration blocks (now isolated in `app/main.cpp` after Q-W3) with a **registration table**, so adding an action edits one place, not four parallel ones (OCP). Split any remaining fat headers -(`capture.h`/`persist.h`) not already resolved by their TU splits (I). CONTEXT.md §Phase Q (OCP +(`capture.h`/`persist.h`) not already resolved by their TU splits (I). (Q-W0: no reshape — +T4-02 notes the ~385-line registration residue left in `app/main.cpp` after Q-W3 shrinks +further under the table.) CONTEXT.md §Phase Q (OCP registration-table). See `docs/product/code-organization.md` §2.3, §6 (Q-6). **Verify:** CTest green at every commit. Every action still registers, appears in the Actions list, and fires via `hookcommand` exactly as before; command-id + display strings unchanged @@ -488,27 +656,42 @@ GATE: Phase S + Phase L L3 merged to dev (D2 complete, M9 abandoned) — tree qu ("when Phase S and L3 are finished" — L1/L2/L3/L4–L7 all landed — GATE SATISFIED) │ ▼ -Q-W0 (pre-restructure functional + DSP quality audit — findings report + triage) - │ ── SUB-GATE: triage complete + Daniel signed off on every disposition ── - ▼ (fix-now findings remediated/assigned; downstream Q-W1..W6 reshaped as needed) -Q-W1 (safe opener: core/json extract + directory/namespace layout on clean modules) - ├─► Q-W2 (split bank_panel) ──► Q-W4 (split actions + dedupe bank verbs vs panel_bank_ops) - ├─► Q-W3 (split main.cpp; hoist orchestration) ──► Q-W6 (OCP registration-table + I splits) - └─► Q-W5 (split persist; isolate prune_fs) [best after Q-W4] +Q-W0 (audit + triage + report — COMPLETE; all 59 dispositions signed off 2026-07-28) + │ ── SUB-GATE: satisfied once the six approved fix-now remediations land ── + ▼ (T1-01 T1-03 T1-09 T2-01a T3-01 T3-03 — in flight on pq-w0-fixes) +Q-W1 (safe opener: core/json ×5 + wire codec + rect unification + relocation incl. ~20 VST + │ pure libs under core/instrument/{engine,map,ui} + riders) + ├─► Q-W2 (split bank_panel — 8 seams) ──► Q-W4 (split actions + dedupe vs panel_bank_ops) + ├─► Q-W2v (NEW: VST god-modules — editor 8 TUs / processor 3 TUs / component_state_io; + │ sampler_core TU whole — documented exception) [parallel with Q-W2: zero overlap] + ├─► Q-W3 (split main — 4 hoists incl. capture_batch; + wav_codec, ICaptureBackend deletion, + │ stamp dedupe, T1-11, capture_realtime_finalize) ──► Q-W6 (OCP registration-table) + └─► Q-W5 (split persist; + ext-state-loop dedupe) [best after Q-W4] ``` -Q-W0 is the **entry point** — the functional/DSP audit runs FIRST and gates Q-W1 (no structural -point begins until its triage closes and Daniel signs off). W1 is then the safe, high-leverage -structural opener (all later waves assume the layout it establishes). The four god-module splits -(W2–W5) are risk-ordered and mostly parallel-safe; W4 depends on W2's `panel_bank_ops`, W6 depends -on W3's isolated registration code. Big-bang is rejected — every wave is independently landable and -CTest-green. +Q-W0 has run and is signed off (2026-07-28); its sub-gate closes when the six fix-now +remediations land. W1 is the safe, high-leverage structural opener (all later waves assume the +layout — including the T4-18 `instrument/` placement — it establishes). The god-module splits +(W2, W2v, W3, W5) are risk-ordered and mostly parallel-safe; **Q-W2v runs parallel with Q-W2** +(different artifact, zero file overlap — audit §4f SETTLED); W4 depends on W2's +`panel_bank_ops`, W6 depends on W3's isolated registration code. Big-bang is rejected — every +wave is independently landable and CTest-green. ## Phase Q — must-verify-before-build - **Q-W0 closed before any structural point** — the functional/DSP audit's findings report exists, every finding is triaged (fix-now vs. document-and-defer, each with rationale), fix-now findings are remediated or assigned to the wave that opens their file, and **Daniel has signed off on every disposition.** Q-W1 does not begin otherwise. (CONTEXT.md §Phase Q Q-W0; naming/DSP smell - categories §2c of `docs/product/code-organization.md`.) + categories §2c of `docs/product/code-organization.md`.) **Status 2026-07-28: triage + sign-off + COMPLETE (all 59 dispositions); the sub-gate closes when the six fix-now remediations land + (`pq-w0-fixes`).** +- **~600-line ceiling on every split wave** — every TU a split wave ships lands under ~600 LOC, + with `sampler_core.cpp` the single documented exception (T4-14/T4-27). Seams are the method, + the ceiling is the bar; arbitrary bisection to hit the number is rejected (Q-5 settlement, + 2026-07-28). +- **No dispatch-stack blowouts anywhere** — heuristic (3) generalizes the hot-path guardrail + beyond the three named paths: prefer static polymorphism where types are compile-time-known; + templates only where earned for compile-time dedup (T4-20 yes; T4-21's rect NO-template + ruling). - **Hot-path call/inline shape** — before landing each split, confirm no virtual dispatch and no header→TU indirection was added on `peaks` envelope compute, audition/preview, or the realtime tick. `computeEnvelope` stays a free function on `const std::vector&`;