Move S-VIEW-1..9 to COMPLETED (three-view editor + velocity spline curve v7); F3 settled (full-window Browse modal); S-VIEW-10 (curve editor UI) remains the lone open Phase S item. Add trigger_seam + velocity_curve to the module inventory.
52 KiB
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.
Milestone 9 — slots (MPC-style)
Abandoned (Daniel, 2026-07-27) — will not be built.
Goal: "Capture to slot N" / "insert slot N", MIDI-bindable. CONTEXT.md Build order 9. Verify (in DAW): Slot capture and slot insert fire from MIDI bindings; slot state persists via the index.
- Slot model + slot↔sample assignment.
- "Capture to slot N" / "insert slot N" actions, MIDI-bindable.
Open questions to resolve during build
Carried from CONTEXT.md §Open questions — keep visible until each is closed by a landed milestone.
parseIntnarrowing hardening:src/bank_model.cppparseIntcastsint64_t → intviastatic_castwithout a range check; integers that fit in int64 but exceedINT_MAXare 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)
Design View sub-phase. Extends D1's track-level mode projection to item level via REAPER 7 fixed lanes: on a track present in both stances, each mode owns a fixed lane — the active mode's lane shows and plays, the inactive mode's is hidden and silenced — so a Design take and an Arrange take can share the same track and time position without colliding on the view. Nothing in D1 changes. Runtime floor rises to REAPER 7 for this sub-phase (no version-gate branch; below v7 it is simply unavailable). Authoritative spec: CONTEXT.md §Two-canvas sub-phase (Phase D2 / Phase E) and the surrounding §Design View — additive phase spec. Product framing:
docs/product/design-view.md§Two-canvas direction. When a point lands, doc-keeper moves it toCOMPLETED.md.D2-W1 (pure lane extension), D2-W2 (shell: lane application + new-content detection), D2-W3-A (lane minting + item→lane assignment + persist round-trip), and D2-W3-B (item-level mode actions + W3-A polish) have all landed — see
COMPLETED.md. Phase D2 is functionally complete.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. S1–S18 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. SeeCOMPLETED.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 — held and optional-forever (noted, not specified)
S3–S11 have landed; archived in
COMPLETED.md. S12 and S13 (partial) have also landed; archived inCOMPLETED.md. The cross-artifact relay (S13) remains deferred — see §S13 above.
S8 has landed; archived in
COMPLETED.md.
S9 has landed; archived in
COMPLETED.md.
S0-DS moved to Phase L (2026-07-26). The shared LICE drawing kit that stood here is now Phase L point L1 on
dev— see PLAN.md §Phase L + CONTEXT.md §Phase L anddocs/product/visual-design-language.md(all ondev). S10–S13 below build with the current drawing and adopt the L1 kit when it lands — not gated on Phase L.
S10, S10-Z, and S11 have landed; archived in
COMPLETED.md.
S12 has landed; archived in
COMPLETED.md.
S13 (drop-to-load) has partially landed; archived in
COMPLETED.md. Cross-artifact relay remains deferred — see §S13 above.
S15 has landed; archived in
COMPLETED.md.
S16 has landed; archived in
COMPLETED.md.
S17 has landed; archived in
COMPLETED.md.
S18 has landed; archived in
COMPLETED.md.
S14 moved to Phase L (2026-07-26). The dock-panel refresh that stood here is now Phase L point L2 on
dev— and, per Daniel's DS-3 call, expanded from a light re-skin into a thorough dock-panel layout redesign that lays out the full M11-aware button inventory before applying the kit. See PLAN.md §Phase L + CONTEXT.md §Phase L anddocs/product/visual-design-language.md(all ondev). The design-system forks DS-1/ DS-2/DS-3 are all SETTLED (2026-07-26) and recorded in the Phase L docs ondev.
Phase S — product name (ReaSampler 9000)
The MIDI-playback instrument's product name is ReaSampler 9000 (Daniel, 2026-07-26,
on DAW-testing the S1–S6 instrument). The extension remains ReaSampler; the instrument
is ReaSampler 9000. Framing + propagation surfaces:
docs/product/midi-playback.md §Product name.
-
Propagate the display name ReaSampler 9000 across user-visible surfaces: the VST3 class display name string (in the factory registration), the
IPlugVieweditor title band (currently "ReaSampler Instrument"), the S6 embed-strip label, and the Phase S docs. Do NOT change the VST3 class UID — instances in already-saved projects key off it; a UID change orphans every existing instance. -
Rename the binary filename too (S-NAME-1 SETTLED, Daniel 2026-07-26): rename the built VST3 module (CMake
OUTPUT_NAME/ target artifact — e.g.reasampler_9000.vst3) alongside the display strings, so the on-disk name matches the product name. Record the full rename surface: CMake output name (the second VST3 target's artifact name), the factory vendor/name strings, theIPlugVieweditor title, and the S6 embed label. Do NOT touch the VST3 class UID (unchanged — the compat anchor). -
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.
-
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 0–1 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_SimplePitchShifteras a drop-in swap for thepitch_shiftpure module if the hand-rolled OLA onset latency or warble proves musically unacceptable. SamePitchEngine::Preservecontract 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 toCOMPLETED.md.Depends on Phase S being on dev (this redesigns Phase S editor shells —
reasampler_editor.cpp+ the puresrc/vst/geometry modules). Phase L L3 is FINISHED and merged (2026-07-27, commitc53683e—reasampler_editor.cpp+reasampler_embed.cpprestyled 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.
- S-VIEW-1 through S-VIEW-9 — all landed (Wave 1 core + Wave 2 shell). See
COMPLETED.md§S-VIEW for the full entry set. - S-VIEW-10 — velocity-curve editor UI (shell, Sample + Zone views, r10). A compact transfer-
curve editor band on the Sample view adjacent to the envelope overlay (X = velocity 0–127, Y =
amp 0–1; spline curve traced in an accent hue; draggable node markers per control point; add on
empty-space click, delete on modifier-click/drag-off), drawn through the L1 kit; the same
editor in the Zone per-zone param panel (one curve per zone). Shell (
reasampler_editor.cpp) does the LICE draw + mouse routing; all geometry/hit-test/clamp math in the purevelocity_curvemodule. Foundation (S-VIEW-9) is landed — unblocked on the module; needs the Sample-view curve-editor UI.
Phase S editor prerequisites — routed to staff-engineer (bug + default; NOT design forks)
Framed in CONTEXT.md §S-VIEW; both are functional prerequisites with the SDK swept and no platform ceiling — a diagnosis and a one-line default, not product decisions. S-VIEW-BUG-1 and S-VIEW-SIZE-1 have both landed (Wave 1) — see
COMPLETED.md.
Phase S editor redesign — forks (Daniel's to call)
(All forks 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). No open forks remain.)
Phase L — Look-and-feel (system-wide visual design language)
New pillar, own lettered namespace, taken up by a parallel team. Phase L is the whole-system look-and-feel effort: a shared LICE drawing kit and the surfaces that adopt it, so ReaSampler and ReaSampler 9000 shed the flat "temple os" drawing for a modern, sleek 2026 dark synth look. It answers Daniel's post-DAW-test verdict on the instrument ("this looks like temple os… the VST is dogshit… scope it for the whole system… does Cockos have a toolkit?"). Namespaced
L(Look-and-feel) so it is orthogonal to and ungated by the M/D/B/R/V/S pillars — a parallel team owns it while Phase S feature work proceeds independently. Authoritative spec: CONTEXT.md §Phase L — visual design language (design-system spec). Product framing, the settled decision record (DS-1/DS-2/DS-3 all SETTLED 2026-07-26), palette, and the three visual directions:docs/product/visual-design-language.md. When a point lands, doc-keeper moves it toCOMPLETED.md.L1 (shared LICE drawing kit — the foundation), L2 (dock-panel layout redesign), L3 (VST editor + embed-strip restyle), L4 (dock-panel button layout enhancement), L5 (dock-panel button refinements), L6 (toolbar polish), and L7 (capture ordering, card metadata, and selection styling) have all landed —
theme/palette module,component_geometrygeometry/hit-test helpers,draw_kitshell, GDIDrawTextretirement inbank_panel(L1);action_barpure task-grouped layout module, full M11-aware button inventory placed by task cluster,bank_panelredesigned through the L1 kit (L2); VST editor (reasampler_editor.cpp) + embed strip (reasampler_embed.cpp) restyled through the L1 kit — REAPER-grey neutrals + three pastel accents, pastel spectral keyboard strip + zone bars, hover/pressed/drag states, localkCol*forest-green palette retired (L3); three-zone layout (top capture/placement/maintenance toolbar, bottom Design-View toolbar, footer toggle + Tail button + Prune),footer_barpure module,ActionCluster::Tagging/Switchinginaction_bar(L4); top-bar overflow menu (overflow_menupure module), custom LICE-kit hover-delay tooltips (tooltippure module), opposite-mode Item/Track tag buttons + Show Both, Toggle + Activate-Arrange/Design buttons removed, grouping spacing widened (mode_enablepure module) (L5); single-row button faces, keybinding in tooltip, Cancel RT moved to overflow, top-bar cluster order tidied (L6); per-bankSlotMap(id→slot) inbank_book, sparse-grid rendering,card_drag+card_metapure modules,captureTimeSigNum/captureTimeSigDenomonSample, tertiary-border selection (L7). SeeCOMPLETED.md. Phase L is complete.This section is self-contained for a team without Phase S context. Where a point touches a Phase S surface (the VST editor, the embed strip, the keyboard strip), the gate is stated explicitly so the team does not chase files that are not on dev yet.
Settled decisions (Daniel, 2026-07-26 — see
docs/product/visual-design-language.md§6):
- DS-1 — toolkit: LICE + WDL free game, no external frameworks. Draw the modern look with LICE directly; reuse any useful WDL/vwnd piece (skin/image helpers, draw idioms, a control like the scroll listbox) where it beats re-deriving — "don't reinvent the wheel." Reject iPlug2 / JUCE / VSTGUI (external frameworks re-opening the settled bare-SDK+LICE build shape). Keep hit-test geometry in pure CTest-covered modules — do not import vwnd's retained-mode object model wholesale.
- DS-2 — visual direction: Direction B ("Neon Console") + Direction C's spectral keyboard strip. SETTLED 2026-07-26, REVISED 2026-07-26 (Daniel) — palette-only. Neutral surfaces (revised): the neutral ladder moved from near-black up into REAPER's mid-grey theme family so the dock reads as part of REAPER, not a black slab —
bg/base≈#2b2b2b,bg/panel≈#333333,bg/cell≈#3a3a3a,line/hairline≈#4a4a4a,text/primary≈#dcdcdc,text/dim≈~#a0a0a0+ (elevation-ladder discipline unchanged). Accent layer (revised): now a three-accent pastel system —accent/primarypastel lime green (live/active/selected),accent/secondarypastel teal
accent/tertiarypastel purple (categorical distinctions) — replacing the original single electric cyan. The spectral keyboard strip is a pastel sweep anchored on the three accents. Tight WCAG pairs to re-verify against the grey ladder:text/dim-on-grey (mid-grey-on-mid-grey, AA 4.5:1) and the three pastels-as-indicators onbg/cell(shrunk from ~15:1 to ~6:1–7:1). A stylish/bundled-font upgrade was considered and DECLINED (Daniel): no font bundling/redistribution — the kit keeps its current cached-font face, no new typeface. The kit palette stays abstract (roles, one constants block — three accent roles + grey neutrals), so the direction is a single-file change; final hex is locked against the theme WCAG tests within the pastel intent. Detail:docs/product/visual-design-language.md§2.1/§4/§6 + CONTEXT.md §Phase L.- DS-3 — dock-panel scope: a thorough layout redesign, not a light re-skin. L2 lays out the full button inventory (including M11's action-button additions) intuitively, uncluttered, and useful — then applies the kit. Sequenced after M11 merges.
Phase L — sequencing
L1 (shared kit) ──► L2 (dock-panel layout redesign) [LANDED]
├─────────► L3 (VST editor + embed-strip restyle) [LANDED]
└─────────► L4 (dock-panel button layout enhancement) [LANDED]
└────► L5 (dock-panel button refinements; ungated, after L4) [LANDED]
└────► L6 (toolbar polish; ungated, after L5) [LANDED]
└────► L7 (capture ordering + card metadata + selection styling; ungated, after L6) [LANDED]
L1, L2, L3, L4, L5, L6, and L7 have all landed. Phase L is complete.
Phase L — must-verify-before-build
- LICE design-kit surfaces (L1) —
LICE_GradRect,LICE_RoundRect, AALICE_Line/LICE_FLine/LICE_ThickFLine/LICE_Circle/LICE_FillCircle/LICE_DrawCBezier,LICE_FillTriangle/FillTrapezoid/FillConvexPolygon, and theLICE_CachedFont/LICE_IFontfont engine (SetFromHFont, AADrawText, shadow/outline/ glow FX flags). Verified present invendor/WDL/WDL/lice/lice.h+lice_text.h(design-language doc §1.1); confirm exact signatures + theLICE_CachedFont↔HFONTlifecycle at build. - WDL/vwnd reuse assessment (DS-1) — at build time, evaluate whether a vwnd piece beats
re-deriving it:
virtwnd-slider.cpp/vwnd_slider_drawknobstackas the slider/knob drawing reference,virtwnd-listbox.cppas a candidate scroll listbox,virtwnd-controls.hforWDL_STYLE_*gradient hooks,virtwnd-skin.hfor image-skin helpers. Reuse where useful; keep hit-test geometry pure regardless. - M11 button inventory (L2) — resolved at L2 build: inventory taken against dev after
M11 merged; all buttons placed by task cluster in the landed
action_barmodule. - L4 re-home surface (L4) — resolved at L4 build: three-zone layout confirmed against the
post-palette-revision
bank_panel;action_barextended withActionCluster::Tagging+Switching; new purefooter_barmodule covers footer layout/hit-test. - L5 refinement surface (L5) — resolved at L5 build:
TrackPopupMenuoverflow menu confirmed; tooltip mechanism resolved as custom LICE-kit hover-delay tooltip (tooltippure module; sourced from the registered action phrase, prefix stripped at draw time); item-move and track-tag action ids confirmed; active-mode read confirmed via the sameview().activeModeId()the footer toggle uses. - L7 model + tempo surface (L7) — RESOLVED at build (L7 landed). Forks F1/F2/F3 all
confirmed:
TimeMap_GetTimeSigAtTimeconfirmed at build for the meter stamp; SWELL stock cursors chosen for drop-result cues (Reorder→IDC_SIZEALL, Move→IDC_HAND, Copy→IDC_UPARROW, Replace→IDC_SIZEWE); existing pool-privilege guard reused as-is for Alt-replace. Gap navigation in the grid = skip gaps (arrow keys skip empty slots). SeeCOMPLETED.md§L7.
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. NamespacedQ(Quality) — M/D/B/R/V/S/L are all taken;Qnames 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 toCOMPLETED.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, seeCOMPLETED.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 insrc/(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.Settled (Q-1, this-doc): the phase is
Q(Quality); point-id familyQ1..Qn, wave prefixesQ-W0(the pre-restructure audit) thenQ-W1..Q-W6(the structural reorg). 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, seedocs/product/code-organization.md§6): Q-2 JSON extraction in scope + first (rec: yes); Q-3 directory shapecore//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 JSONParser→json::Reader/Writer), leave the merely-quirky (rec); Q-9 align thecapture_realtime/realtime_recordshell↔core word order during W3 (rec: yes).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 —peaksenvelope compute, audition/preview, the realtime-capture tick — no added virtual dispatch, no header→TU indirection, no changed call/inline or branch shape.computeEnvelopestays a free function onconst std::vector<float>&; audition split stays a direct call-through; the realtime idle tick stays a single pointer test;FxBypassGuardstays 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.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: fourclass Parsercopies collapsing to onejson::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; thebank_model/BankIndexfile↔class word-mismatch (Q-8); and thecapture_realtime/realtime_recordshell↔core word-order inversion (Q-9). Renames ride the wave that already relocates/splits the file — no dedicated naming wave (Q-7); the geometry-mirrorcompute*/hitTest*verb vocabulary and the_testssuffix 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 buildis 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).
Q-W0 — pre-restructure functional + DSP quality audit (runs FIRST; gates Q-W1)
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) reshapespitch_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 insrc/. 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.
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 Parsers 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 Parsers are gone, replaced by one
core/json consumed by all four models; 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
core/ file; the CMake pure/shell enforcement still holds.
Depends on: the GATE (tree quiescent) and Q-W0 closed (audit triaged + Daniel signed off;
any fix-now findings the audit assigned to Q-W1 folded in). First structural wave.
- Extract
core/json(pure parser + serializer: parseString/parseInt/parseKey/skipValue + escape, plus emit helpers); unify underreasampler::json; guard theParsername against cross-lib collision. Off all hot paths — safe to abstract freely. - Rewire
bank_model,bank_book,view_mode_model,owned_manifestontocore/json; delete the four duplicateParsers. Round-trip output byte-identical (dedupe, not reformat). - Relocate the 30 clean pure libs into
core/{model,view,capture,audio,ui,reclaim,version, json}/and the clean shells intoshell/{capture,panel,view,persist,actions}/; movemain.cpptoapp/. UpdateCMakeLists.txtsrc/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/AudioSample/Parserhomes. This alone resolves the naming collisions (§2b.2): the shared pure-UI rect types (FooterRect/ButtonRect/Selection/CellRect) get oneui::owner — retire the hand-collision "NAME NOTE" infooter_bar.h. - Naming riders (Q-8, if settled): rename the survivor JSON parser to
json::Parser(orjson::Reader/json::Writer); if Daniel takes theBankIndex→BankModelrename, land it here (mechanical class rename, verified bybank_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;peaksstays 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:
panel_render / panel_thumbnails / panel_audition / panel_input / panel_bank_ops /
panel_window. 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
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.
- Split rendering (
draw*/paint*) →panel_render; thumbnail compute+cache →panel_thumbnails. - 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. - Extract bank-CRUD verbs →
panel_bank_ops(the future single owner; W4 dedupesactions.cppagainst it). Splitbank_panel.hinto per-seam headers (I). - Verify in DAW: panel unchanged; CTest green; no hot-path indirection added.
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
(resolveRange/resolveRazorRange/collectSelectedTracks + provenance assembly inputs), and
realtime_lifecycle (the realtime-capture state machine + globals + selection guards).
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 +
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.
- Hoist capture orchestration →
capture_orchestrator(shell/capture/); keepFxBypassGuarda stack RAII object as it moves (precision-invariant-critical). - 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. - Leave
main.cpp= API-pointer ownership +ReaperPluginEntry+ dispatch; move toapp/. - Naming rider (Q-9, if settled): align the
capture_realtime(shell) /realtime_record(pure) word-order inversion to the house shell↔core convention (rec: stemcapture_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). - 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
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
persist's prune_fs after W5). CONTEXT.md §Phase Q (actions split seams; bank-verb dedupe).
See docs/product/code-organization.md §2.1, §2.4.
Verify: CTest green at every commit. Every action fires identically in DAW (Design View
family; multi-bank create/rename/reorder/delete/evacuate/activate/move/copy/remove; prune). The
bank-CRUD verbs have one implementation home (no bank_panel/actions duplication). Each
bank verb still wraps its mutation in one batched undo point; the prune action still writes no
ext state and opens no undo point. Command-id strings are unchanged (FOREVER-STABLE
contract — a reorg must not touch a shipped command id).
Depends on: Q-W2 (panel_bank_ops is the dedupe target). Independent of Q-W3.
- Split →
design_view_actions(toggle/activate/tag/untag/showBoth/moveItems),bank_actions(bank CRUD family),prune_action(doBankPruneFolder— the single file-deletion action). - Dedupe
actions.cpp'spromptText/mintBankId+ bank verbs againstpanel_bank_ops(one owner); do not change any command-id string. - Verify in DAW: all action families fire unchanged; one bank op = one Ctrl-Z; prune still 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.
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
(concentrated), never diluted. Relative-paths-only persistence is unchanged.
Depends on: Q-W1. Best after Q-W4 (so prune_action routes cleanly to prune_fs), but
independently landable.
- Split →
session(lifecycle/poll +projectconfigreload hook),ext_state_io(serialization bridge + GUID minting + folder relocation). - Isolate prune scanning +
deleteOrphanFile(SHFileOperationW) →prune_fs— the one file-deletion module; nothing else may delete bytes. - Verify in DAW: save/load/undo-reload/relocation/prune unchanged; deletion authority is one module; relative-paths-only holds; CTest green.
Q-W6 — OCP registration-table + residual fat-header (I) splits
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
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
(FOREVER-STABLE, per-channel); unload still mirror-unregisters everything. Adding a hypothetical
new action now touches the table only (demonstrated in review, not shipped). Remaining fat
headers are segmented.
Depends on: Q-W3 (registration code must be isolated first). Sequenced last; the most
droppable point if the phase needs narrowing (Q-6).
- Convert the hand-written
Register("command_id"/"gaccel"/"hookcommand")blocks to a data-driven registration table; unload mirror-unregisters from the same table. - Split residual fat headers (
capture.h/persist.hand any other) alongside their TUs (I). - Verify: all actions register/fire/unregister unchanged; command-id strings untouched; CTest green.
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/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 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.
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.) - Hot-path call/inline shape — before landing each split, confirm no virtual dispatch and no
header→TU indirection was added on
peaksenvelope compute, audition/preview, or the realtime tick.computeEnvelopestays a free function onconst 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_idstring, 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) vsAudioSample(audio) vs the unifiedParser(json), plus the shared pure-UI rect typesFooterRect/ButtonRect(already hand-collision-checked infooter_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_idstring, 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.