Files
reasampler/PLAN.md
T

620 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PLAN.md — ReaSampler milestone roadmap
Living milestone roadmap for ReaSampler. Derived from CONTEXT.md's 11-step build
order; CONTEXT.md remains the authoritative spec — this file is the tickable
checklist, not a re-statement of the spec. When a point lands, doc-keeper removes
it here and appends it to `COMPLETED.md`.
**Conventions**
- One checkbox `- [ ]` = one discrete, independently-landable point.
- Each milestone opens with a **Goal** (one line) and a **Verify** criterion
(the acceptance gate; precision invariants pulled in where one applies).
- Verify-in-DAW points require a manual REAPER run; pure points are gated by CTest.
- "See CONTEXT.md §…" points at the authoritative detail — do not duplicate it here.
---
## Open questions to resolve during build
Carried from CONTEXT.md §Open questions — keep visible until each is closed by a
landed milestone.
- **`parseInt` narrowing hardening:** `src/bank_model.cpp` `parseInt` casts
`int64_t → int` via `static_cast` without a range check; integers that fit
in int64 but exceed `INT_MAX` are implementation-defined. Hardening candidate
— add bounds check before the cast when integer-field validation is in scope.
- **Capture send/routing isolation (TODO):** The FX-scope capture neutralizes out-of-scope FX, gain, and pan — but NOT aux **sends**. So a downstream coloring send (e.g. a folder → reverb-track send) still routes and blends the reverb into an item/track capture, past the intended isolation point. A true item-level capture should be taken at the isolated graph point — the target scope's output before out-of-scope track FX/gain/pan **and** before out-of-scope aux/parallel sends. The hard part: distinguish **source routing that must be preserved** (e.g. a MIDI send T1→T2 where T2's synth is where a MIDI item's audio is actually produced — the "item level" for that MIDI item is T2's synth output) from **coloring sends that must be excluded** (folder→reverb). Repro: folder F1; T1 (MIDI) sends MIDI to T2 (synth); T1+T2 → F1; F1 sends to reverb T3; capturing the MIDI item on T1 currently includes the reverb, should be isolated to T2's synth output pre-F1 with the MIDI send preserved and the reverb send excluded. Likely approach: snapshot + mute out-of-scope tracks' aux sends during the render while preserving the main/source signal path — needs a rule for which sends are load-bearing.
---
# Phase D2 — Two-canvas (item-level mode projection; additive to D1)
> **Phase D2 is functionally complete** — D2-W1, D2-W2, D2-W3-A, D2-W3-B all landed; see `COMPLETED.md`.
>
> **Deferred:** panel UI indicator for per-track lane/mode state (a per-track
> lane-split marker). The mode switch already shows the active mode; no natural
> cheap home for a per-track indicator was found in the bank panel. Explicitly
> deferred — not silently dropped. Can be picked up later if wanted.
---
# Phase S — MIDI-playback instrument (native VST3 sampler; a second build artifact)
> **Landed on dev (merged 2026-07-27); DAW verification pending Daniel's smoke test.**
> S1S18 and the product-name/binary-rename work are all on dev. The cross-artifact
> ingest relay (S13 bullet) was explicitly DEGRADED and remains deferred — see below.
> Completed material archived in `COMPLETED.md`. Authoritative spec: **CONTEXT.md
> §MIDI-playback instrument — additive phase spec (Phase S)**. Product framing:
> `docs/product/midi-playback.md`.
>
## S13 — cross-artifact ingest relay (deferred)
> **SPIKE VERDICT (ps-w12, 2026-07-27): DEGRADED — relay deferred.** The instrument's
> REAPER bridge (`reaper_bridge`) is deliberately READ-ONLY; a relay would need a new
> instrument WRITE seam into ext-state and an extension-side timer poller servicing a
> drop-ingest inbox key with a claim/clear nonce — the same cross-process handshake race
> the S17 spec rejected for alternative (A). Both the read-only-instrument boundary and
> the new poller are load-bearing design calls, so the relay is deferred to a future wave.
> The shipped ingest gesture stays drop-onto-docked-panel (S8). The degrade path (editor
> shows a "drop files onto the ReaSampler bank panel to add them" affordance) landed as
> part of Phase S. See `COMPLETED.md` §S13 for full context.
- [ ] Cross-artifact ingest relay: the editor hands the dropped path + this instance's
identity to the extension as a bank-ingest request over an agreed seam. DEFERRED —
relay mechanism proved load-bearing to redesign; drop-onto-panel (S8) is the shipped
ingest path. Requires (a) a new instrument WRITE seam into ext-state and (b) an
extension-side timer poller + claim/clear nonce. A future wave when the design is ready.
## Phase S — product name (ReaSampler 9000)
The MIDI-playback instrument's product name is **ReaSampler 9000** (Daniel, 2026-07-26,
on DAW-testing the S1S6 instrument). The extension remains **ReaSampler**; the instrument
is **ReaSampler 9000**. Framing + propagation surfaces:
`docs/product/midi-playback.md` §Product name.
- [ ] **Compat verification (must-DAW-verify before shipping the rename):** the working
assumption is that REAPER **rebinds a saved instance by its VST3 class UID, not by the
module filename**, so a filename rename with an unchanged UID keeps saved projects working
(existing instances still resolve). **This is not yet confirmed from source** — a web
check surfaced a JUCE/VST3-replace-VST2 case suggesting REAPER's binding is more nuanced
than "UID only" (it can involve an FXID match), so treat UID-rebind as **to-verify, not
asserted fact**. **DAW-verify:** save a project with a ReaSampler 9000 instance under the
old filename, rename the module, reopen — confirm the instance rebinds and restores its
state. If REAPER does key partly on filename, fall back to keeping the current filename
(display-strings-only) and record that as the shipped choice.
## Phase S — held and optional-forever (noted, not specified)
- **Tier 2 — "expressive" (HELD).** Velocity layers, round-robin (anti-machine-gun),
full ADSR, per-sample tuning/gain trim, sustain loops. The next depth increment once
Tier 01 proves the instrument belongs — **its points are not drawn up here.**
- **Tier 3 — "instrument polish" (optional-forever).** Filters, filter/pitch
envelopes, LFOs, per-voice pan, choke groups, a modest FX slot. A direction to leave
room for, never a commitment. **Note:** S16 lands the *pitch* envelope + the Varispeed/
Preserve pitch-engine mode early (Daniel's directive) — the Tier-3 "filter/pitch envelopes"
line now means the *filter* envelope + LFOs remainder.
- **Sinc Varispeed-quality upgrade (HELD — WDL_Resampler).** `WDL_Resampler`'s sinc mode
beats the core's 2-point linear interp for **Varispeed** base-repitch quality (see the S16
WDL finding). An optional per-voice quality toggle (linear default / sinc), RT-suitable but
heavier. Held as a Tier-2/3 quality option — not needed for S15/S16, not scheduled. (A
resampler couples duration, so it is a Varispeed-quality option only, **not** a Preserve
engine.)
- **WDL_SimplePitchShifter swap (HELD — fork S16-F2 route a).** `WDL_SimplePitchShifter`
as a drop-in swap for the `pitch_shift` pure module if the hand-rolled OLA onset latency
or warble proves musically unacceptable. Same `PitchEngine::Preserve` contract behind the
seam. WDL excluded from the shipped build by include-chain (windows.h); held as the
quality/latency alternative.
- **Trigger choke-on-note-off (HELD — fork S15-F1).** A future option for Trigger mode to
*cut* (choke) on note-off or on a same-group re-trigger (hi-hat open/closed). Deliberately
out of S15 scope (Trigger ignores note-off entirely there); a Tier-3 choke-group direction.
## Phase S — editor view-model redesign (three views: Sample / Browse / Zone)
> **Additive Phase S sub-phase (S-VIEW; Daniel, 2026-07-27, r9).** Re-partitions the ReaSampler
> 9000 editor from a two-view toggle into a **three-view model where the loaded sample is the
> home** — Sample (default face), Browse (modal picker over Sample), Zone (dedicated keymap
> surface). Adds three performance parameters (key-tracking, preview velocity, and the r10
> velocity→amp transfer curve) and three visual components (envelope overlay, real piano-key
> pattern, and the r10 velocity-curve editor), and frames two engineering prerequisites
> (drop-to-FX bug, default window size). An **editor** redesign — the S3 voice engine, keymap
> resolution, and read-only-over-bank contract are **unchanged**; component state extends
> additively for key-tracking; VST3 class UID unchanged. Authoritative spec: **CONTEXT.md §Phase
> S — editor view-model redesign (S-VIEW)**. Product framing: `docs/product/midi-playback.md`
> §Addendum r9. When a point lands, doc-keeper moves it to `COMPLETED.md`.
>
> **Depends on Phase S being on dev** (this redesigns Phase S editor shells —
> `reasampler_editor.cpp` + the pure `src/vst/` geometry modules). **Phase L L3 is FINISHED and
> merged** (2026-07-27, commit `c53683e` — `reasampler_editor.cpp` + `reasampler_embed.cpp`
> restyled through the L1 kit). S-VIEW builds the new three-view layout **directly on the current
> L3 look-and-feel as its baseline — one implementation pass, styled correctly from the start.**
> There is **no two-pass "land S-VIEW then restyle through L3"** — L3 already happened; every new
> S-VIEW surface (Sample face, Browse modal, Zone surface, envelope overlay + node handles,
> piano-key strip, preview cluster) is drawn through the L1 kit at build time, inheriting the L3
> palette and component-draw grammar. Restyle-after is not a concern.
**Goal:** A three-view editor that makes "pick a capture, tune it, play it" fast, easy, and fun —
Sample is home, Browse is an easy-to-summon modal picker, Zone is the deliberate keymap surface.
**Verify (in DAW):** open on a 1080p screen → full Sample face (hero waveform + envelope overlay +
fenced root + preview-trigger + control strip) with no scroll; Browse opens as a modal over
Sample, select+confirm loads a new capture; Zone opens on its own button, key-tracking + the
piano-key pattern work; drop-a-capture-onto-FX instantiates a playing instance.
### Phase S editor Wave B — Sample-face recomposition (r11; Daniel, 2026-07-27)
> Daniel's post-landing DAW pass on the S-VIEW editor: **all linear sliders → small radial
> knobs** grouped into a fenced knob deck (envelope controls grouped as a unit); the **mode
> toggles compact**, not full-width; the **inline velocity-curve box → a miniature curve
> preview button + full-size popup editor** (right-click deletes a node in the popup); the
> **hero waveform full-width**. Authoritative layout spec: **CONTEXT.md §S-VIEW → "The
> Sample-face recomposition (r11)"** (band order, group taxonomy, cell metrics, popup
> geometry, inventory contract — nothing silently dropped). Product framing:
> `docs/product/midi-playback.md` §Addendum r11. All drawing through the L1 kit; all
> layout/hit-test in pure geometry modules.
>
> **S-VIEW-11, S-VIEW-12, and S-VIEW-13 have all landed** — FB1 (merged 2026-07-27, suite
> 55/55) brought the knob deck + master gain + curve popup + full-width hero; FB2 (merged
> 2026-07-28, suite 55/55) brought Zone-panel parity. **Phase S editor Wave B (r11) is
> complete.** See `COMPLETED.md` for both FB entries.
**Open forks****R11-F1** (hero height vs. default window) SETTLED at FB1 build: elastic
hero, 840×620 default kept. **R11-F2** (Zone-panel parity) SETTLED at FB2 build: knob deck
+ curve popup adopted on the Zone panel; `param_slider` slider rows retired on that surface.
### Phase S editor redesign — forks (Daniel's to call)
*(r9/r10 forks all settled: S-VIEW-F1 and S-VIEW-F2 SETTLED 2026-07-27 — folded into S-VIEW-4 /
S-VIEW-3. F1: preview velocity persists via envelope-v6 `ComponentState`. F2: envelope nodes are
draggable via the pure `envelope_edit` module. R10-F1 SETTLED 2026-07-27 — Option A flat y=1,
folded into S-VIEW-9. S-VIEW-F3 SETTLED — full-window overlay, implemented as Browse renders as a
full-window modal over Sample (landed in S-VIEW-1/S-VIEW-5). **r11 forks all settled: R11-F1
SETTLED at FB1 build (elastic hero, 840×620 default kept); R11-F2 SETTLED at FB2 build (Zone
panel adopts knob deck + curve popup, `param_slider` slider rows retired on that surface).**)*
---
# Phase Q — Quality (structural reorganization; zero-runtime-cost)
> **New pillar, own lettered namespace, and — uniquely — the LAST structural pillar.** Phase
> Q is a **pure structural refactor**: it reorganizes `src/` into a healthier shape (more
> encapsulation, granular namespaces, `core/`/`shell/`/`app/` subdirectories) against a stated
> quality bar — *"mtytel Vital is my code reference for quality"* — to bring the codebase
> "into the realm of something I can stand to look at." It ships **no feature and changes no
> behavior**: the test suite passing unchanged is the proof of correctness. Namespaced **`Q`
> (Quality)** — M/D/B/R/V/S/L are all taken; `Q` names the *end* (the quality bar), the reorg
> being the *means*. Authoritative spec: **CONTEXT.md §Phase Q — structural reorganization
> (reorg spec)**. Product framing, the Vital-grounded target shape, the grep-verified SOLID
> audit that is the evidence base, and the settled/recommended fork record (Q-1..Q-6):
> `docs/product/code-organization.md`. When a point lands, doc-keeper moves it to
> `COMPLETED.md`.
>
> **THE GATE (load-bearing — state first; reconciled to reality 2026-07-27).** Phase Q is
> **gated on the tree being otherwise quiescent.** Daniel's plain readiness target: **"when
> Phase S and L3 are finished."** **As of 2026-07-27 both gate conditions are satisfied:**
> Phase S merged to dev (2026-07-27); Phase L L3 (the VST restyle) merged to dev
> (2026-07-27, commit `c53683e`) — **Phase L is complete** (L1/L2/L3/L4/L5/L6/L7 all
> landed, see `COMPLETED.md`). **D2** is functionally complete (D2-W1..W3-B landed; the
> lone open item, a per-track lane-split panel indicator, is *explicitly deferred*, not a
> blocking residual). **M9** (slots) is **abandoned** (Daniel, 2026-07-27) — will not be
> built. D2 is named in the gate only so that *reactivating* its deferred panel indicator
> re-arms the quiescence condition; neither D2 nor M9 blocks the gate today. *Why the gate:* Phase Q
> touches **nearly every file in `src/`** (relocate into subdirectories, re-namespace every
> header, split the four largest TUs, plus the §2b renames). Every large in-flight branch
> (Phase S on its worktree, and L3 once it lands) is diffed against the *current flat
> layout*; landing a rename-and-relocate-everything reorg mid-flight forces every open
> branch through the worst conflict class (every hunk moved, every qualified reference
> changed) — a combinatorial re-resolution, not a linear one. Phase Q is *last* precisely
> because it reshapes the ground every other pillar stands on. Landing it early taxes every
> subsequent phase; landing it last taxes nothing. **The gate is now satisfied — Phase Q
> may begin, entering at the Q-W0 audit wave** (the structural waves Q-W1+ do not begin until
> Q-W0's triage closes and Daniel signs off — see the Q-W0 sub-gate above).
>
> **M9 disposition — resolved (Daniel, 2026-07-27): abandoned.** M9 is out; it will not be
> reactivated. The gate remains satisfied; no re-arm condition applies.
>
> **PRE-RESTRUCTURE AUDIT WAVE (Q-W0 — added 2026-07-27, Daniel's ask; runs FIRST).** Before any
> structural point (Q-W1+) begins, Phase Q now opens with a **functional + DSP quality audit**
> (`Q-W0`) — a thorough static analysis of the code from a *functional-correctness* and
> *algorithm-quality* point of view, complementary to (not a repeat of) the grep-verified
> SOLID/naming audit that already grounds Q-W1..Q-W6. Q-W0 hunts DSP smells (close eye on the pitch
> engine), reinvented wheels, duplicate code, poor-quality algorithms, leaky pure/shell boundaries,
> and domain-modeling smells around env-coupled constants; it produces a written, triaged findings
> report. **The gate to begin Q-W1 is that Q-W0's triage is complete and Daniel has signed off on
> each finding's disposition** (fix-now vs. document-and-defer). Q-W0's findings may add or reshape
> 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; **+
> `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
> replacement; a technique replacement is a Daniel decision at triage time, not an automatic Q-W0
> action).
> **Recommended, Daniel's to call (Q-2..Q-9, see
> `docs/product/code-organization.md` §6):** Q-2 JSON extraction in scope + first (rec: yes);
> Q-3 directory shape `core/`/`shell/`/`app/` top-split with subsystem dirs beneath (rec: this
> over pure-Vital subsystem-first — it makes the pure/shell invariant *structural*); Q-4
> sub-namespace to match sub-directory (rec: both); Q-5 split god-modules to the audit's named
> seams, no finer (rec: yes); Q-6 OCP registration-table as the final wave (rec: in, last);
> **Q-7 naming rides the relocation waves, no dedicated naming wave (rec: yes — forced once
> Q-3/Q-4 settle); Q-8 class/module renames — fix the two that actively mislead (`BankIndex`→
> `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
> header→TU indirection, no changed call/inline or branch shape.** `computeEnvelope` stays a
> free function on `const std::vector<float>&`; audition split stays a direct call-through;
> the realtime idle tick stays a single pointer test; `FxBypassGuard` stays stack RAII. This is
> 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
> found: four `class Parser` copies collapsing to one `json::Parser` (Q-W1); shared pure-UI rect
> types (`FooterRect`/`ButtonRect`) that the codebase already hand-checks for collision
> (`footer_bar.h`'s "NAME NOTE") — resolved by the Q-4 sub-namespaces for free; the
> `bank_model`/`BankIndex` file↔class word-mismatch (Q-8); and the `capture_realtime`/
> `realtime_record` shell↔core word-order inversion (Q-9). **Renames ride the wave that already
> relocates/splits the file — no dedicated naming wave (Q-7);** the geometry-mirror
> `compute*`/`hitTest*` verb vocabulary and the `_tests` suffix are already consistent and are
> preserved verbatim. Naming changes are zero-behavior-change like the rest of Phase Q, and the
> FOREVER-STABLE contract strings (command ids, action names, ext-state namespace/keys, VST3 UID)
> are **not** C++ symbols and are never renamed.
>
> **Every point is independently landable and CTest-green at EVERY step.** The CMake
> 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/W2vW5 god-module splits → W6 OCP finish).
>
> **PHASE STATUS (2026-07-29): all seven waves (Q-W0..Q-W6 incl. Q-W2v) are structurally
> COMPLETE.** Remaining before the phase closes and merges to `dev`: (1) Daniel's in-DAW
> verification batch — the full deferred list across all waves (panel parity, editor/processor
> parity, stereo Preserve listening, null test, bit-identical repeats, capture flows, action
> families, one-op-one-Ctrl-Z, prune fail-safes, save/load/relocation) — now unblocked since the
> tree is stable; (2) the phase-close CLAUDE.md architecture refresh (module map still describes
> some pre-Q homes); (3) the phase-q → dev merge on Daniel's sign-off. See `COMPLETED.md` for
> each wave's full landed narrative.
## Q-W0 — pre-restructure functional + DSP quality audit (runs FIRST; gates Q-W1)
**STATUS (2026-07-29): audit COMPLETE, triage COMPLETE, sign-off COMPLETE, fix-now
remediations LANDED — Q-W0 is fully closed.** 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 six approved fix-now remediations plus seven review riders
landed 2026-07-28 (merge `546927e`) — see `COMPLETED.md`. **Q-W1 has since landed on top of
this closure** (see `COMPLETED.md`).
**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
Q-W1..Q-W6 (§2/§2b of `docs/product/code-organization.md`) — it goes deeper on *does the code do the
right thing well*, not *where does the responsibility live*. It hunts, across the whole `src/` tree:
duplicate code, reinvented wheels, poor-quality algorithms (**close eye on the pitch engine**),
numerical-robustness and artifact hazards in the DSP paths, leaky pure/shell boundaries, and
**domain-modeling smells around env-coupled constants** (values stored in a frame/rate/DPI/tick-coupled
domain that should be stored rate-free and resolved at use). Every finding is **classified for
disposition**: *eliminate-before-restructure* (fix-now) vs. *document-and-defer* (with rationale).
Nothing structural (Q-W1+) begins until this wave closes and Daniel signs off on the disposition of
every finding. CONTEXT.md §Phase Q (Q-W0 audit scope + triage). See
`docs/product/code-organization.md` §2c.
**Verify:** A findings report exists and is complete over the named audit surfaces (DSP/audio incl.
pitch; architecture smells; env-coupled-constant domain-modeling smells). **Every finding is
triaged** (fix-now vs. documented-and-deferred, each with a one-line rationale). Fix-now findings are
either remediated in this wave or explicitly assigned to the downstream wave that already touches the
file (recorded per finding); no fix-now finding is left implicit. Any behavior-changing remediation
lands with its module's CTest executable green and (where a DSP path changes audibly) a stated
before/after listening or null check. **The gate to Q-W1 is: triage complete + Daniel signed off.**
**Depends on:** the GATE (tree quiescent). Precedes every other Q wave — Q-W1 depends on Q-W0.
- [ ] **DSP / audio audit — close eye on pitch.** Assess *algorithm quality* (correctness,
artifacts, numerical robustness, interpolation quality, reinvented-wheel vs. established DSP
technique) across: `src/vst/pitch_shift` (hand-rolled OLA pitch-preserve — window/overlap choice,
phase handling, transient/formant behavior, buffer edges), `sampler_core` (repitch ratio math,
interpolation order/quality, loop-point crossfade, voice-stealing correctness/click behavior),
`peaks` (envelope min/max binning), `wav_trim` (decay-scan threshold + truncate plan), and the
capture/tail paths. **Pitch-remediation depth (Q-11 SETTLED, Daniel 2026-07-27): defer to
findings** — default document-and-defer; only if the audit surfaces artifacts that matter, weigh a
bounded OLA fix (window/overlap/edge tuning) **before** a technique replacement. A technique
replacement (phase-vocoder / WSOLA) reshapes `pitch_shift`, spills a downstream Q-wave point, and is
a **Daniel decision at triage time, not an automatic Q-W0 action** — Q-W0 surfaces the
recommendation, Daniel calls it.
- [ ] **Architecture-smell audit.** Duplicate code, reinvented wheels, poor abstractions, and any
leaky pure/shell boundary (a `core/` module reaching a REAPER/host type, or geometry/algorithm
math living untestable in a shell). Cross-check against — but do not merely restate — the §2/§2b
SOLID/naming findings; Q-W0's contribution is the *functional* smells those audits didn't target.
- [ ] **Env-coupled-constant domain-modeling audit (explicit smell category).** Flag ANY value
stored in a frame/rate/DPI/tick-coupled domain that should be stored **rate-free and resolved at
the point of use** — per the load-bearing invariant that wall-clock times are rate-free SECONDS
resolved against the live project rate (`sample_map`), with NO hardcoded sample rates in `src/`.
Treat this as **domain-modeling** (store rate-free, resolve at use), **not** "rescale by rate."
There was a prior incident here — envelope times stored in the frame domain — so this is a
first-class category, not a footnote. Sweep envelope times, loop points, fade lengths, tail
lengths, and any UI geometry constant that silently assumes a DPI/rate.
- [ ] **Triage + report.** Write the findings report as a **committed doc,
`docs/product/code-quality-audit.md`** (Q-10 SETTLED, Daniel 2026-07-27 — beside the SOLID/naming
audit, not a tracked issue list); classify each finding fix-now vs. document-and-defer with a
one-line rationale; for each fix-now, note whether it is remediated in Q-W0 or assigned to the
downstream wave that already opens the file. Deferred findings carry a documented rationale so they
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. **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.**
## Q-W1 — safe opener: extract `core/json` + impose the directory/namespace layout on clean modules
> **Landed on `phase-q` (2026-07-29). Integrated suite 60/60 green.** `core/json`
> (`json::Reader`/`json::Writer`) extracted; the five hand-rolled JSON decoders (incl.
> `tail_control`'s) deleted; the wire-`Cursor` family collapsed into `core/wire`; the shared
> `readFileBytes` helper added; ~50 clean modules relocated into `core/{model,view,capture,
> audio,ui,reclaim,version,json,util,wire}/`, `core/instrument/{engine,map,ui}/`,
> `shell/{capture,panel,view,persist,actions,instrument}/`, `app/main.cpp`; sub-namespaces
> applied; one concrete `ui::Rect` + aliases (LTRB fork + `footer_bar` NAME NOTE retired);
> `slot_map` extracted from `bank_book`; `clamp01` deduped; `BankIndex`→`BankModel`;
> `reasampler_uid.h` relocated to `core/wire/`. See `COMPLETED.md` for the full narrative.
>
> **Skipped/deferred riders:** T4-22 (`hitIndex` hit-test template) — not trivial, deferred as
> an opportunistic follow-on once the rect unification is in use downstream; T4-06
> (`view_mode_model` planner split) — optional, deferred; T4-09 (`view_lanes` split) —
> deferred (in scope only if a later wave touches `view.cpp` anyway).
>
> **Open residual — `bank_book.cpp` still 737 LOC.** The serialize/deserialize seam is
> identified but blocked on a `nameKey` linkage design decision, escalated to Daniel and
> **pending** as of 2026-07-29. Downstream waves touching `bank_book` should check this
> residual before assuming the split is finished.
>
> An interim `core/namespaces.h` shim covers the six not-yet-split god TUs; each downstream
> split wave (Q-W2 onward) retires its own includes of it as that module splits.
## Q-W2 — split `bank_panel.cpp` (the biggest extension god-module — 3459 LOC at the Q-W0 census)
> **Landed on `phase-q` (2026-07-29, merge of `pq-w2-panel`). Integrated suite 61/61 green,
> reviewed-approved.** `bank_panel.cpp` (3459 LOC) split into eight TUs under `shell/panel/`:
> `panel_render` / `panel_thumbnails` / `panel_audition` / `panel_input` / `panel_bank_ops` /
> `panel_window` / `panel_layout` / `panel_drag`, plus per-seam public headers and internal
> `panel_state.h`; audition stays a direct call-through; the one-bank-op-one-undo invariant is
> preserved; ~50 TU-private helpers wrapped in anonymous namespaces (a review follow-up). See
> `COMPLETED.md` for the full narrative.
>
> **Recorded ceiling overages (reviewer-endorsed, preserved as a durable record per CONTEXT's
> "silent overshoot is not legitimate" rule):** `panel_input.cpp` 636, `panel_render.cpp` 613,
> `panel_state.h` 608 — the overage is comment volume; non-comment lines are ~322369 per file;
> no honest seam remains; bisection was rejected.
>
> **Review note for the Q-W4 planning record:** `panel_bank_ops`'s verbs still embed
> prompts/panel-state nudges — Q-W4's dedupe needs promptless inner verbs (`renameBank(id,name)`
> etc.), not a call-site swap; `promptText`/`mintBankId` are byte-identical twins with
> `actions.cpp` and are the cheapest first dedupe.
>
> **In-DAW verification (panel parity) is PENDING on `phase-q`** — deferred by design, not yet
> performed.
## Q-W2v — split the VST god-modules (NEW wave — Q-W0 T4 §1.5; runs parallel with Q-W2)
> **Landed on `phase-q` (2026-07-29, merge of `pq-w2v-vst`). Integrated suite 61/61 green,
> reviewed-approved.** `reasampler_editor.cpp` (3084 LOC, the largest file in the repo) split
> into eight face-axis TUs under `shell/instrument/`, with pure layout hoisted into
> `core/instrument/ui/editor_geometry` (discharges T2-06, newly tested); `reasampler_processor.cpp`
> split into `processor_state` / `processor_reload` / lifecycle+`process()` kept whole (no
> virtual seam, T4-29); `sample_map` split into a resolution core + `component_state_io` codec
> (the extension preset path no longer links the voice engine — link-proven; T4-13 ≡ T2-07);
> `sampler_core.cpp` stays whole with the documented hot-path exception comment (T4-14/T4-27);
> `zone_params.h` split out; `core/wire/bytes.h` (`putLE`/`ByteReader`) lands (T4-20);
> `ThumbnailKey` adopted (T2-10); a golden full-blob v11 fixture pins the codec bytes. The
> `src/vst/` directory is gone. See `COMPLETED.md` for the full narrative.
>
> **Deferred/known:** `component_state_io.h` still includes `sample_map.h`→`sampler_core.h`
> transitively (T2-07's header half — future work); the `engine` namespace is deferred
> (`sampler_core` stays flat `reasampler`); capture-side LE rewires are left for the capture
> family.
>
> **In-DAW verification (editor/processor parity) is PENDING on `phase-q`** — deferred by
> design, not yet performed.
## Q-W3 — split `main.cpp` (hoist orchestration; leave main = pointers + entry + dispatch)
> **Landed on `phase-q` (2026-07-29, merge of `pq-w3-main`). Integrated suite 61/61 green,
> reviewed-approved.** `app/main.cpp` reduced 1897 → 653 LOC (pointers + entry + dispatch; the
> remaining bulk is the registration residue Q-W6 dissolves) via four hoists into
> `shell/capture/`: `capture_orchestrator`, `capture_batch`, `scope_resolve`,
> `realtime_lifecycle`; `FxBypassGuard` moved intact as a stack RAII object; the realtime idle
> tick stays a single pointer test; `ICaptureBackend` deleted (T4-26) with the
> CLAUDE.md/CONTEXT-ARCHIVE corrections landed in the same commit; the Q-9 rename done (pure
> `core/capture/capture_realtime`, shell `capture_realtime_shell` + `capture_realtime_finalize`
> split, T4-08); `stampCaptureSample` dedupe (T2-09, divergent time-sig behavior preserved via
> caller arg); `makeUniqueTag` gains a per-session monotonic counter (T1-11 behavior fix — stems
> now `<epoch>-<n>` / `rt-<epoch>-<n>`; the per-process residual is documented in-code); one pure
> `wav_codec` RIFF owner absorbs `wav_trim` + `ingest`'s WAV build + content hashes, with golden
> hash literals pinned (`wav_codec_tests` replaces `wav_trim_tests`; `capture_realtime_tests`
> replaces `realtime_record_tests`). See `COMPLETED.md` for the full narrative.
>
> **Known open:** `wav_trim.h`'s transitional forwarding shim still has three live includers
> (`sample_map.h`, `editor_session.cpp`, `processor_reload.cpp`) — repoint-and-retire is a named
> follow-up; `ingest.cpp` is trimmed to 567 LOC but keeps the `namespaces.h` shim (`ingest` +
> `view` remain the shim's unowned consumers).
>
> **In-DAW verification (null test, bit-identical repeats) is PENDING on `phase-q`** — deferred
> by design, not yet performed.
## Q-W4 — split `actions.cpp` + dedupe bank verbs against `panel_bank_ops`
> **Landed on `phase-q` (2026-07-29, merge of `pq-w4-actions`). Integrated suite 61/61 green,
> reviewed-approved.** `actions.cpp` (1019 LOC) split into `design_view_actions` / `bank_actions`
> / `prune_action`, plus a fourth shared `action_registry` TU, all under `shell/actions/`;
> `promptText`/`mintBankId` deduped against `panel_bank_ops`; bank verbs reshaped to promptless
> inner verbs (one mutation home, two UX skins — panel and actions each keep their exact prior
> UX); command-id suffixes/display phrases verified byte-identical in review; `prune_action`
> stays a clean deletion-authority isolate (no `Undo_*`, no ext-state writes);
> `persistBankOp`/`persistBook` gain null-session guards; `promptText` renamed `promptBankName`.
> See `COMPLETED.md` for the full narrative.
>
> **Review note (🟡, resolved in Q-W6):** two session pointers / a null-session-as-model-rejection
> misreport (unreachable today) was resolved by Q-W6's `bank_ops` lift.
>
> **In-DAW verification (action families, one-op-one-Ctrl-Z, prune fail-safes) is PENDING on
> `phase-q`** — deferred by design, not yet performed.
## Q-W5 — split `persist.cpp` (isolate the single file-deletion authority into `prune_fs`)
> **Landed on `phase-q` (2026-07-29, merge of `pq-w5-persist`). Integrated suite 61/61 green,
> reviewed-approved.** `persist.cpp` (853 LOC) split into `session` / `ext_state_io` / `prune_fs`
> under `shell/persist/` + `persist_internal.h`; the file-deletion authority is concentrated —
> `SHFileOperationW`/orphan-remove lives in exactly one anonymous-namespace function in
> `prune_fs.cpp`, verified tree-wide; the prune fail-safe chain stays byte-intact. T2-04's
> `GetProjExtState` grow-loop is unified as a header-only template, with all three hand-rolled
> copies rewired (`usage_scan`'s start cap raised 4KB→64KB, allocation-only, verified
> equivalent). The Q-W1 `bank_book_json` residual lands via a private static `nameKey`
> (Daniel-approved option a) — `bank_book.cpp` is now ~462 LOC. `persist.h` is kept as a compat
> umbrella for parallel safety (retired in Q-W6); deletion-authority wording is scoped precisely
> in headers; the grow-loop gains a defensive NUL. See `COMPLETED.md` for the full narrative.
>
> **In-DAW verification (save/load/undo-reload, ext-state round-trip, folder relocation, prune
> deletion) is PENDING on `phase-q`** — deferred by design, not yet performed.
## Q-W6 — OCP registration-table + residual fat-header (I) splits
> **Landed on `phase-q` (2026-07-29). Integrated suite 61/61 green, reviewed-approved.** Action
> registration/gaccel/hookcommand-dispatch/mirror-unregister all iterate one `ActionTableRow`
> table (flat function-pointer dispatch, no `std::function`/virtual); adding a new action now
> touches one table row only; `main.cpp` shrinks 653→404. FOREVER-STABLE suffixes/phrases/
> retired-ids verified byte-identical row-by-row in review; capture rows derive their
> suffix+phrase from the pure `captureActionTable()` (the parallel-list risk is gone by
> construction). See `COMPLETED.md` for the full narrative.
>
> **Phase-end cleanup riders (landed in this wave):** `bankOp*` verbs + `persistBankOp` lifted to
> new `shell/bank_ops` taking `ReaSamplerSession&` (dissolves the Q-W4 🟡 review note);
> `persist.h` umbrella retired (13 callers repointed); `capture.h`'s realtime seam moved to
> `capture_realtime_shell.h`; the `wav_trim.h` shim + its INTERFACE target deleted;
> **`core/namespaces.h` DELETED** (the interim Q-W1 shim's contract fulfilled — ~26 includers
> rewired); the grow-loop rehomed to `core/wire/ext_state_read.h`; a stale-comment sweep
> (`persist.cpp`/`bank_panel.cpp` refs); CLAUDE.md's persist/bank_book/actions/wav_codec bullets
> corrected in-wave.
>
> **Review-noted follow-on (not landed, deferred):** extending the table pattern to the
> design_view/bank/ingest families' hand-registration; `channelIdFor`'s shared string-store scan
> is correct-by-prefix-disjointness — a suffix-keyed map would make it structural, but isn't
> required; `view_mode_model.h` (748 LOC) remains the largest header (T4-06's planner split
> stays optional/deferred).
>
> **In-DAW verification (all action families, registration/fire/unregister parity) is PENDING on
> `phase-q`** — deferred by design, not yet performed.
## Phase Q — sequencing
```
GATE: Phase S + Phase L L3 merged to dev (D2 complete, M9 abandoned) — tree quiescent
("when Phase S and L3 are finished" — L1/L2/L3/L4L7 all landed — GATE SATISFIED)
Q-W0 (audit + triage + report — COMPLETE; all 59 dispositions signed off 2026-07-28;
│ fix-now remediations LANDED 2026-07-28)
Q-W1 (safe opener: core/json ×5 + wire codec + rect unification + relocation incl. ~20 VST
│ pure libs under core/instrument/{engine,map,ui} + riders — LANDED 2026-07-29)
├─► Q-W2 (split bank_panel — 8 seams — LANDED 2026-07-29)
│ └─► Q-W4 (split actions + dedupe vs panel_bank_ops — LANDED 2026-07-29)
├─► Q-W2v (VST god-modules — editor 8 TUs / processor 3 TUs / component_state_io;
│ sampler_core TU whole — documented exception — LANDED 2026-07-29)
│ [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 — LANDED 2026-07-29)
│ └─► Q-W6 (OCP registration-table — LANDED 2026-07-29)
└─► Q-W5 (split persist; + ext-state-loop dedupe — LANDED 2026-07-29) [best after Q-W4]
STATUS (2026-07-29): all seven waves (Q-W0..Q-W6 incl. Q-W2v) structurally COMPLETE, 61/61
integrated suite green. Remaining: Daniel's in-DAW verification batch, the phase-close
CLAUDE.md architecture refresh, and the phase-q → dev merge on sign-off.
```
Q-W0 ran and closed 2026-07-28 (its six fix-now remediations landed the same day). W1 was the
safe, high-leverage structural opener (all later waves assumed the layout — including the T4-18
`instrument/` placement — it establishes). The god-module splits (W2, W2v, W3, W5) were
risk-ordered and mostly parallel-safe; **Q-W2v ran parallel with Q-W2** (different artifact, zero
file overlap — audit §4f SETTLED); W4 depended on W2's `panel_bank_ops`, W6 depended on W3's
isolated registration code. Big-bang was rejected — every wave landed independently,
CTest-green throughout. **All seven waves landed on `phase-q` by 2026-07-29 — Phase Q is
structurally complete** (see the phase preamble's PHASE STATUS block for what remains before the
phase closes and merges to `dev`).
## 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`.) **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<float>&`;
audition stays a direct call-through; the idle tick stays a single pointer test. (CONTEXT.md
§Phase Q, `docs/product/code-organization.md` §3.)
- **Command-id + display strings are FOREVER-STABLE** — a reorg must not change a shipped
`command_id` string, action display name, ext-state namespace, or VST3 class UID. Re-namespacing
C++ symbols is orthogonal to these on-the-wire/on-disk contract strings; keep them byte-identical
(per-channel, per the Phase V V4 contract). (CONTEXT.md §Phase Q.)
- **Name-collision sweep on sub-namespacing** — `Sample` (model) vs `AudioSample` (audio) vs the
unified `Parser` (json), plus the shared pure-UI rect types `FooterRect`/`ButtonRect` (already
hand-collision-checked in `footer_bar.h`), and any other cross-lib name that collides once
flattened into granular namespaces. Resolve by each symbol's new subsystem home before landing
W1. (naming audit §2b.2; `docs/product/code-organization.md` §6 Q-4/Q-8.)
- **Naming stays zero-behavior-change and off the wire** — the §2b renames touch C++ symbols only;
no `command_id` string, action display name, ext-state namespace/key, `reasampler:` lane prefix,
or VST3 class UID is renamed (naming audit §2b.5). A rename that would alter a shipped contract
literal is out of scope, full stop.
- **The GATE** — do not begin any Q point until **Phase S + Phase L L3** are merged to dev and the
tree is quiescent (D2 complete, M9 abandoned). Re-confirm quiescence against dev before W1.