Files
reasampler/docs/product/audit-notes/q-w0-t4-sizing.md
T

400 lines
31 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.
# Q-W0 Track 4 — structural sizing + placement audit
Date: 2026-07-28 · Branch: `pq-w0-audit` · READ-ONLY static analysis (no build, no code edits)
**Method.** Line counts measured with `wc -l` on the worktree; seams derived from function-definition
skeletons (`grep` for top-level definitions + section markers) plus targeted reads. Acceptance bar =
Daniel's three heuristics: (a) more directories a must, files ≤ ~600 lines, SRP applies to files and
namespaces; (b) templates are good where they dedup at compile time; (c) saved CPU beats abstraction —
no dispatch-stack blowouts, prefer static polymorphism where types are compile-time-known.
**Measured sizes differ from the wave brief in several places** (the tree moved after the brief was
drafted — GA/pS/pS-usage landed): `reasampler_editor.cpp` 3065 (brief said 3035),
`reasampler_processor.cpp` 1164 (1004), `sampler_core.cpp` 968 (1049), `sample_map.cpp` 970 (807),
`sample_map.h` 708 (600), `sampler_core.h` 762 (820), `actions.cpp` 1016 (996), `persist.cpp` 852
(812). All numbers below are the measured ones.
---
## 1. Oversize census + seams
Every `.cpp`/`.h` in `src/` (both sides) over ~600 lines, with the *real* responsibility clusters.
Where a file is genuinely one responsibility, I say so and recommend leaving it.
### 1.1 The four planned splits — do the plan's seams still land sub-600?
**T4-01 — `src/bank_panel.cpp` (3459; plan assumed 2424).**
The plan's six seams (`panel_render` / `panel_thumbnails` / `panel_audition` / `panel_input` /
`panel_bank_ops` / `panel_window`) no longer all land sub-600 at current size. Tally against the
skeleton:
| Planned TU | Functions (line spans) | Est. LOC | Verdict |
|---|---|---|---|
| `panel_thumbnails` | `computeThumbnail`/`thumbnailFor` (420487) | ~130 | fine |
| `panel_render` | `drawCardMeta`/`drawThumbnail` (487547), kit adapters (547570), `drawFooter` (685780), `drawToolbar`/`drawMoreButton`/`drawTooltip` (9921155), `drawRegionGrid`/`drawCardDropTarget`/`drawRegionHeader`/`drawTabStrip`/`paintPanel` (13341646) | ~700 | **over — needs the layout cut below** |
| *(unplanned)* **`panel_layout`** | toolbar/footer/menu rect + row/cluster builders (571684, 785991), split geometry + region rects + L7 slot-order display bridge (11561333) | ~500 | **new TU required** — this is pure-ish geometry glue, distinct from LICE drawing; extracting it puts `panel_render` at ~550 |
| `panel_audition` | `initPreview`/`startAudition`/`stopAudition`/`deinitPreview` (18741965) | ~90 | fine (keep the direct call-through guardrail) |
| `panel_input` | input helpers + `regionAt` (19652010), click routing `handleBanksChromeClick``handleKey`/accelerator (23842710), plus new-content detection (16471874, ~230) | ~800 | **over — needs the drag cut below** |
| *(unplanned)* **`panel_drag`** | `updateDropTarget`/`dropTargetBankId`/`classifyCardDrag`/`applyDragCursor`/`resolveHover`/`updateHover`/`maybeShowTooltip`/`onMouseMove`/`doReorderDrop`/`doReplaceDrop`/`resetDragState`/`onLBtnUp`/`handleRightClick` (27113185) | ~475 | **new TU required** — the card-drag/hover state machine is a cohesive cluster of its own (it already has a pure mirror, `card_drag`); extracting it puts `panel_input` at ~550 |
| `panel_bank_ops` | `promptText`/`mintBankId`/`doCreateBank``removeSamples`/`focusedSelectionIds`/`resolveDragPathsForOs` (20062231) + popup menus (22312384) | ~375 | fine (menus ride with bank_ops or input — either works; they invoke the ops) |
| `panel_window` | `handleDropFiles`/`dlgProc`/`openPanel`/`closePanel` (31853336) + public API (33363459) | ~275 | fine |
**Proposal:** eight TUs, not six — add `panel_layout` and `panel_drag`.
**Severity:** high (it is the biggest file in the repo). **Disposition: reshapes wave Q-W2**
the wave brief must name eight seams, or two of its six TUs ship >600 on day one.
**T4-02 — `src/main.cpp` (1897; plan assumed 1762).**
The plan's three hoists are the right seams, but `capture_orchestrator` as specced lands **~885
lines** — over by half again. Tally: `FxBypassGuard` (627731, ~105), `renderOffline` +
`captureAndIndexOne` + `RunCapture` + `RunCaptureItemAssign` (731909, ~180), batch family
(`ItemSelectionGuard`/`selectOnlyItem`/`RunBatchCaptureItems`/`collectRazorAreas`/
`TrackSelectionGuard`/`RunBatchCaptureRazor`, 9091181, ~270), `RunRecaptureFromSource` (12001398,
~200), realtime + insert actions (13981512, ~115). `scope_resolve` (361590) ≈ 230 ✓;
`realtime_lifecycle` (186360) ≈ 175 ✓; registration/entry residue (15121897) ≈ 385 ✓ (shrinks
further under Q-W6's table).
**Proposal:** split the orchestrator seam once more: `capture_orchestrator` (FxBypassGuard +
single-capture path + realtime/insert action bodies, ~450) and **`capture_batch`** (batch family +
`RunRecaptureFromSource` + the two selection guards, ~470). Recapture is planner-driven like batch
and shares the selection-guard machinery — it belongs with batch, not the single-shot path.
**Severity:** high. **Disposition: reshapes wave Q-W3** — add the fourth TU to the brief.
**T4-03 — `src/actions.cpp` (1016; plan assumed 981).**
Plan's seams still land: `design_view_actions` (59421, ~360), `bank_actions` (4221016 minus prune,
~490), `prune_action` (`doBankPruneFolder` 821916 + registration share, ~130). All sub-600.
**Disposition: no change to Q-W4.**
**T4-04 — `src/persist.cpp` (852; plan assumed 766).**
Plan's seams still land: `ext_state_io` (helpers + `saveToActiveProject` + `writeAssignmentRequest`,
112288, ~180), `prune_fs` (`scanPruneOrphans`/`pruneDryRun`/`pruneOrphanSet`/`deleteOrphanFile`/
`pruneReclaim`, 288539, ~250 — pS-usage growth landed here, exactly where the plan isolates it),
`session` (load/guid/poll/reload, 539852, ~315). All sub-600.
**Disposition: no change to Q-W5.**
### 1.2 Known offenders beyond the planned four — extension side
**T4-05 — `src/bank_book.cpp` (1109) + `bank_book.h` (457).**
Three genuine seams: **`SlotMap`** (27143, ~115 — a self-contained ordered-slot container with its
own serialize at 614), **`BankBook`** registry/CRUD/transfer/slot-reconcile (145545, ~400), and
**JSON serialize + `Parser`** (5471109, ~560). Q-W1 deletes the Parser + `ObjWriter`/`writeEscaped`
copies; what remains of serialization rewired onto `core/json` is ~150.
**Proposal:** after Q-W1, split `slot_map` into its own TU/header pair (it is a distinct type with
its own tests-worthy invariants); `bank_book.cpp` lands ~550. **Severity:** medium.
**Disposition:** fold into Q-W1 (the JSON rewire already opens this file; the `slot_map` file split
is one `git mv`-shaped extraction on top).
**T4-06 — `src/view_mode_model.cpp` (1049) + `view_mode_model.h` (748).**
Four seams: **indexes** (`ModeRegistry`/`MembershipIndex`/`LaneOwnershipIndex`, 29140), **pure
planners** (`autoTagNewContent`/`planItemRetag`/`planLaneMinting`/`makeParkPlan`/`makeRestorePlan`/
`nextModeId`, 143326, ~185), **`ViewModeModel`** state + visibility/toggle planning (332490), and
**JSON serialize + `Parser`** (4941049, ~555). Q-W1 deletes the Parser (~390); remainder ~660.
**Proposal:** split planners (`view_plan.cpp`) from model+indexes (`view_mode_model.cpp`, ~450 after
JSON extraction). The header's 26 structs split the same way: mode/membership/lane types + model
class vs. the plan-record structs (`TrackPlan`/`TogglePlan`/`AutoTag`/`ItemRetagOp`/`LaneMint*`).
**Severity:** medium. **Disposition:** fold into Q-W1 (JSON rewire opens the file; planner split
rides it). If the wave wants to stay minimal, the planner split can defer — post-extraction ~660 is
marginal, not pathological.
**T4-07 — `src/bank_model.cpp` (767).**
Two seams only: the model (`Sample` equality + `BankIndex` verbs, 25145, ~120) and JSON
(`ObjWriter`/`writeSample`/`Parser::parseSample`/`parseIndex`, 148767, ~620). This file is the
poster child for Q-W1: after the extraction it is ~250 total (model + thin serialize using
`core/json`). **Disposition: already owned by Q-W1; no new seam needed.**
**T4-08 — `src/capture_realtime.cpp` (867).**
Two seams: the **async record lifecycle** (`RealtimeCaptureState` snapshot/restore, `begin`/`tick`/
`abort`, 177324 + 569867, ~450) and the **file-side finalize** (`readAllBytes`/`writeU32LE`/
`trimAutoTailInPlace`/`finalizeRecording`, 324566, ~240). The lifecycle is genuinely one
responsibility (the header itself documents why the restore lives on the state object). The finalize
half — WAV byte-patching, decay-scan trim, move-into-bank — is a distinct concern that talks to
`wav_trim`, not to the transport.
**Proposal:** split `capture_realtime_finalize.cpp` (~240); lifecycle TU lands ~600 with the file
banner. Both stay in `shell/capture/`. **Severity:** low-medium. **Disposition:** ride Q-W3 (the
wave already renames this family per the Q-9 naming rider — same-wave file surgery is free).
**T4-09 — `src/view.cpp` (677).**
Two halves: **park/restore + flag application** (`snapshotTrack`/`applyFlags`/`parkFxOffline`/
`restoreFxOffline`/`applyMode`, ~350) and **lane management** (`laneName`/`managedLaneOrdinals`/
`applyLanePlays`/`applyLaneOps`/`readLaneTracks`/`assignItemToLane`/`applyMintPlan`/
`mintManagedLanes`/`reconcileManagedLanes`, ~330). The D2 lane machinery arrived after the file's
original charter and is a separable concern.
**Proposal:** split `view_lanes.cpp`. **Severity:** low (677 is barely over). **Disposition:**
document-and-defer unless Q-W1's relocation is already touching it — then take the free split.
**T4-10 — `src/ingest.cpp` (636).**
Seams: **pure-ish WAV/PCM helpers** (`buildFloat32Wav`/`decodePcmSource` + byte I/O, 86217, ~130 —
see T4-22: `buildFloat32Wav` is a pure function trapped in a shell TU), **`importFileIntoActiveBank`**
(249420, ~170), and the three ingest surfaces + registration (420636). Extracting the pure WAV
build into a testable core module (`wav_write` beside `wav_trim`, or one `wav_codec`) drops the shell
to ~500 and gains a test target.
**Severity:** low-medium. **Disposition:** fix in whichever wave lands the WAV consolidation
(T4-22); the file split itself is a rider.
### 1.3 Known offenders — VST side (entirely absent from the current plan)
**T4-11 — `src/vst/reasampler_editor.cpp` (3065) + `reasampler_editor.h` (539).**
The single biggest unplanned file — it grew past `main.cpp` in the r11 recomposition, *after* the
plan was written. It is now the `bank_panel.cpp` of the VST artifact, with the same god-module
profile. Real seams, from the skeleton:
| Proposed TU | Functions (line spans) | Est. LOC |
|---|---|---|
| `editor_session` | ctor/dtor, `refreshFromBank`/`rebuildVisible`/`onSyncTimer`/`commitAndReload`/`loadSelection`/`upsertPickedOverride`/effective-zone helpers (146400), PCM + thumbnail caches `monoPcmFor`/`thumbnailFor` (719800) | ~420 |
| `editor_controls` | control-value map `controlValue`/`applyControl` (399475), knob-deck plumbing `zoneDeckGroupDescs`/`deckGroupDescs`/`deckControlNorm`/`applyDeckKnob`/`deckValueLabel` (475649), envelope pack/unpack + `commitPickedMarkers` (649719), `applyZoneControl` (25762587) | ~470 |
| `editor_layout` (pure candidate — see T4-23) | anon-ns geometry: `computeSampleBands`/`clusterRects`/zone-panel areas/`channelToggleRects` (9041076), `computeBrowseModal` (17561785), `zoneContentArea` (19171924) | ~250 |
| `editor_paint_sample` | `paint` dispatch (1169), `drawTitleBand`, `paintSample`/`paintEnvelopeOverlay`/`paintVelocityCurve`/`paintKnobDeck`/`paintCurveButton`/`paintCurvePopup`/`paintEmptyState` + `drawKnobFace`/`drawSpectralStrip`/`drawRootMarker` (10761756) | ~590 |
| `editor_paint_browse_zone` | `paintBrowse` (17851917), `paintZone` (19242041) | ~260 |
| `editor_input` | `resolveHover` (20412145), `onMouseDown` (21472576 — a 430-line per-face dispatch), popup/curve mouse (16371735), `onMouseMove`/`onMouseUp`/`onMouseRDown`/`onMouseWheel`/`onSearchChar`/`onFilesDropped` (25872929) | ~880 → **split by face**: `editor_input_sample` (~500: sample-face hit branches + drag state + curve popup) and `editor_input_browse_zone` (~380: browser cards/scroll/search + zone strip/note entry) |
| `editor_platform` | IPlugView overrides `isPlatformTypeSupported`/`canResize`/`checkSizeConstraint`/`attachedToParent`/`removedFromParent`/`onSize`/`invalidate` (800904), `wndProc` + non-Windows stubs (29293065) | ~280 |
Seven-to-eight TUs, all sub-600, each a real cohesive cluster (session/bridge state · param
plumbing · layout · paint × 2 · input × 2 · platform). The face structure (Sample / Browse / Zone)
is the natural input/paint split axis — it mirrors how the code already dispatches.
**Severity: highest of the audit** — this is the largest unowned file in the repo.
**Disposition: reshapes the wave plan — needs a new owning wave** (see §1.5 / summary table).
**T4-12 — `src/vst/reasampler_processor.cpp` (1164) + `reasampler_processor.h` (502).**
Four seams: **VST3 lifecycle + bus boilerplate** (`queryInterface``setupProcessing`,
`setBusArrangements`, 118209 + 492506, ~120), **component-state I/O** (`setState`/`getState` +
`legacyLiftShouldRun`, 209349 + 774904, ~270), **accessors/param setters** (349492, ~140), and
**reload + drain + usage-publish + render** (`reloadInstrument`/`publishUsage`/
`publishBuiltLocked`/`rebuildVoiceEngine`/`retireIdleDrain` 506774 ~270, `process` 9041164 ~260).
**Proposal:** three TUs — `processor_state` (state I/O + accessors, ~410), `processor_reload`
(reload/drain/usage, ~290), `reasampler_processor.cpp` (lifecycle + `process()`, ~400). Keep
`process()` and its block-render helpers in one TU (heuristic c — see T4-27). All are member
functions of one class; partial-class-across-TUs is the same pattern the Q-W2 panel split uses.
**Severity:** medium-high. **Disposition:** new VST wave (see §1.5).
**T4-13 — `src/vst/sample_map.cpp` (970) + `sample_map.h` (708).**
Two clean halves plus a small third: **resolution core** (bank-JSON distill/select/list, SampleRefs
management, PCM downmix/extract/decode, `resolvePlay`, keymap builders, performance-map resolution +
`reconcileSingleCaptureZones`, 14406, ~390) and **binary component-state codec** (`putU32le`/
`putU64le`/`ByteReader`, zones payload put/read, `serializePerformance`/`deserializePerformance`,
`serializeComponentState`/`deserializeComponentState` v3→v11 lift ladder, selection codec,
406970, ~565).
**Proposal:** split `component_state_io.cpp` (the codec, ~565) from `sample_map.cpp` (resolution,
~400). The header splits the same way: wire/state structs (`ComponentState`/`SampleRefEntry`/codec
decls) vs. resolution API (`SelectedSample`/`PerformanceMap`/`ZonePlaySeconds`/resolvers). This is
the same shape as the extension's model-vs-JSON split and directly reduces rebuild fan-out — the
editor and processor both include `sample_map.h` today and recompile on every codec tweak.
**Severity:** medium-high (the codec grows every ComponentState version bump — v6→v11 in one
quarter; it will cross 600 on its own soon). **Disposition:** new VST wave.
**T4-14 — `src/vst/sampler_core.cpp` (968) + `sampler_core.h` (762).**
Contents: pitch math (1530), `Keymap` (3458), `AdsrEnvelope`/`TriggerEnvelope`/`PitchEnvelope`
(62251, ~190), `Voice` (255680 — `advanceFrame` alone is ~200), `VoiceEngine` (680968, ~290).
**This TU is a genuine single responsibility — the realtime voice engine — and it is the hottest
code in the repo.** Every function in it sits on the per-sample render path; the envelope `tick()`s
and `Voice::advanceFrame` benefit from same-TU inlining (no LTO assumption in the build). Splitting
the .cpp along class lines would put per-sample calls across TU boundaries — precisely the
heuristic-(c) violation the phase forbids.
**Proposal:** **leave the TU whole at 968** (documented exception to the 600 bar, justified by the
hot path), but **split the header**, which is where the pain actually is: `zone_params.h` (the enums
+ `AdsrParams`/`TriggerParams`/`PitchEnvParams`/`ZonePlayParams`/`SampleLoop`/`SampleData` — what
`sample_map`, the editor, and the codec actually need, ~250) vs. `sampler_core.h` (Keymap + the
engine classes, ~500). Today every UI TU that reads a param struct recompiles when a `Voice` member
changes. **Severity:** medium. **Disposition:** header split in the new VST wave; TU stays —
recommend recording the exception in the wave brief so nobody "fixes" it later.
**T4-15 — `src/view_mode_model.h` (748)** — covered under T4-06 (splits with its TU).
**T4-16 — `src/vst/sample_map.h` (708)** — covered under T4-13.
**T4-17 — `src/vst/sampler_core.h` (762)** — covered under T4-14.
### 1.4 Borderline (no action, for the record)
`capture.cpp` (549), `reasampler_editor.h` (539 — shrinks when the editor splits move private
helpers into their TUs), `reasampler_processor.h` (502 — same), `bank_book.h` (457), `pitch_shift.cpp`
(371 — single responsibility, hot, leave), `persist.h`/`capture.h` (341/234 — Q-W6 fat-header pass
already owns them). None need action beyond what their TU splits imply.
### 1.5 The structural conclusion for the wave plan
The existing plan splits 4 files, all extension-side. The census says **9 files need splitting and 2
need header-only splits — 5 of them VST-side, which currently have no owning wave.** The VST work is
the same kind and size as Q-W2 (the editor alone ≈ the old bank_panel). Recommendation: add one VST
god-module wave (call it **Q-W2v**, runnable in parallel with Q-W2 — different artifact, zero file
overlap; or sequence after W5 as Q-W7 if Daniel wants serial waves). Q-W1's relocation scope also
grows: the ~20 clean VST pure libs relocate + namespace in W1 alongside the extension's 30.
---
## 2. `src/vst/` placement in the Q-3 directory map
The settled Q-3 map (`core/{model,view,capture,audio,ui,reclaim,version,json}`,
`shell/{capture,panel,view,persist,actions}`, `app/`) covers only the extension. Two viable shapes
for the VST artifact; **this is Daniel's fork to call at triage.**
**T4-18 — Leading recommendation: integrate into the same `core/`/`shell/` top split, with
`instrument/` subsystem dirs beneath.**
```
core/instrument/engine/ sampler_core, pitch_shift, velocity_curve, master_gain
core/instrument/map/ sample_map (+component_state_io), bank_sync, bridge_marshal, note_entry, trigger_seam
core/instrument/ui/ editor_geometry, keyboard_strip, waveform_view, capture_browser,
browser_scroll, param_slider, knob_deck, curve_popup,
envelope_overlay, envelope_edit, embed_strip
shell/instrument/ reaper_bridge, processor TUs, editor TUs, reasampler_embed, vst_entry,
reasampler_vst.h / reasampler_uid.h
```
Rationale:
1. **One rule, no special case.** Q-3's settled reasoning is "top-level by the load-bearing
discipline, because the pure/shell split is the invariant worth making structural." That reasoning
is artifact-agnostic — a file's directory should tell you whether it may touch a *host* type
(REAPER or VST3 SDK), and `shell/instrument/` says exactly that.
2. **The artifact boundary is a link-graph fact, not a source-layout fact — and the sources already
straddle it.** Verified cross-artifact consumers: `sample_map` links `bank_book` + `wav_trim` +
`sampler_core`; the editor includes `draw_kit`/`theme`/`component_geometry`/`capture_paths`/
`peaks`/`wav_trim`/`app_version`/`ext_keys` (extension-side modules); the extension's pure
`instrument_drop` includes `vst/reasampler_uid.h`. An artifact-first subtree would either
duplicate these or still reach across — the boundary it draws is already false.
3. **Namespace map falls out:** `reasampler::instrument::{engine,map,ui}` beside
`reasampler::model` etc. — the Q-4 rule applied uniformly.
4. **CMake impact: path edits only.** Targets, links, and test executables are unchanged; the
VST3-gate (`EXISTS pluginfactory.cpp`) already guards targets, not directories.
Cost to name: the VST3-gated targets stay interleaved through the top-level `CMakeLists.txt` rather
than being isolatable behind one `add_subdirectory`. Mitigable by grouping the instrument targets
into one guarded block (or one `include()`d .cmake file) without moving sources.
**T4-19 — Alternative: parallel artifact-first subtree**`src/vst/core/{engine,map,ui}` +
`src/vst/shell/`, extension keeps `src/core|shell|app`. Pros: the artifact boundary is visible at
top level; the whole VST tree (sources *and* a dedicated `src/vst/CMakeLists.txt`) can sit behind
one SDK-gated `add_subdirectory`, which is the cleanest possible expression of "this half only
exists on Windows with the submodule slice". Cons: two parallel `core/` trees dilute the "directory
= may it touch a host type" invariant into "check which subtree first"; the shared-module reality
(point 2 above) means the subtree is not actually self-contained — its purity is cosmetic; and the
gated-`add_subdirectory` win is achievable under T4-18 with an `include()` anyway. **Recommend
T4-18; T4-19 is defensible if Daniel weighs artifact legibility above discipline uniformity.**
**Disposition: reshapes Q-W1** (the relocation wave executes whichever shape is chosen).
---
## 3. Template-collapse opportunities
Judged per heuristic (b) — proposed only where duplication is real and the template earns it; two
anti-recommendations included, because a forced template is the worse smell.
**T4-20 — Little-endian byte codec: real template win.**
Five hand-rolled copies, verified: `putU32le`/`putU64le` + `ByteReader` (`vst/sample_map.cpp`
406500), `writeU32LE` (`capture_realtime.cpp` 342), `readU32LE` lambda (`capture_paths.cpp` 49),
`putU32` lambda (`ingest.cpp` 134), `appendU32LE` (`instrument_drop.cpp` 18). One header —
`core/wire/bytes.h` (or beside `core/json`): `template <class T> void putLE(std::vector<uint8_t>&,
T)` / `template <class T> bool readLE(ByteReader&, T&)` with the double↔bits helpers — replaces all
five, compile-time dispatched, zero runtime cost, and gives the ComponentState codec (T4-13) a
tested primitive. Entirely off hot paths (serialization/file I/O only).
**Severity:** medium (each new ComponentState version re-duplicates today). **Disposition:** fix in
the wave that lands `component_state_io` (the biggest consumer); consumers rewire opportunistically.
**T4-21 — Rect family: unify, but with a concrete type, NOT a template.**
Verified 12+ byte-identical `{int x,y,width,height}` structs (`ActionBarRect`/`CellRect`/
`PanelClientRect`/`FooterBarRect`/`HeaderRect`/`SegmentRect`/`MenuBarRect`/`MenuButtonRect`/
`FooterRect`/`ButtonRect`/`TabStripRect`/`KitBox`…) plus a *second grammar* on the VST side
(`editor_geometry`'s `Rect` is LTRB with `left/top/right/bottom` + `height()`). The right tool is
one concrete `ui::Rect` + `contains()` with per-role type aliases (`using ButtonRect = ui::Rect;`)
so call sites keep their semantic names — Q-W1's "one `ui::` owner" note already points here; this
finding extends it: (a) retire the XYWH-vs-LTRB fork by picking one grammar (LTRB has the live
`contains`/`height` users; either works — pick once), and (b) the per-type `contains`/`hitTest*`
one-liners collapse for free. A template rect would model nothing — the types differ in name only.
**Watch:** cross-lib name collisions (extension `Rect` vs vst `Rect`) surface only when both headers
meet in one TU — `sample_map` and the editor are exactly such TUs; the Q-4 sub-namespaces are the fix.
**Severity:** medium. **Disposition:** ride Q-W1 (it is the settled `ui::` unification, widened to
include `editor_geometry::Rect`).
**T4-22 — Linear rect-scan hit-tests: small template, real but modest.**
`hitTestCell` (`bank_grid`), `hitTestSlot` (`card_drag`), and the tab/segment scans are the same
first-rect-containing-point loop over records that carry a rect plus extra fields (`SlotCellRect`
adds `slot`). After T4-21, a single `template <class R> int hitIndex(int px, int py,
span<const R>)` (requiring `r.rect.contains(px,py)` or a rect accessor) collapses them. Earns its
keep only if T4-21 lands first; alone it would be a forced template.
**Severity:** low. **Disposition:** document-and-defer; opportunistic rider on Q-W1.
**T4-23 — WAV build/parse consolidation (dedup, not template).**
`buildFloat32Wav` (ingest, 116179) hand-writes the float32 header that `wav_trim` hand-parses and
`capture_paths`/`capture_realtime` chunk-scan/byte-patch. One pure `wav_codec` (or fold build into
`wav_trim`, renamed) gives one tested owner of the RIFF layout. Concrete functions; nothing to
template. **Severity:** low-medium. **Disposition:** fix-now-sized, but assign to the wave that
opens `ingest.cpp` (T4-10) to avoid a standalone churn commit.
**T4-24 — `clamp01`: dedup with one inline, anti-template.**
Six verified copies (`envelope_overlay`, `master_gain`, `param_slider`, `reasampler_editor`,
`velocity_curve` + `envelope_edit`'s `clamp`). One `constexpr inline double clamp01(double)` in a
shared core header (or just `std::clamp` at call sites). Not a template candidate — `std::clamp`
already is one. **Severity:** trivial. **Disposition:** rider on Q-W1 relocation.
**T4-25 — JSON `Parser`/`ObjWriter`/`writeEscaped`/`intToStr` ×4 — already owned by Q-W1;
confirmed still accurate** (verified in `bank_model`/`bank_book`/`view_mode_model`/
`owned_manifest`; `sample_usage` uses its own `rsusage` k/v wire, *not* a fifth JSON parser — no
scope growth). Concrete class, not a template. **Disposition:** no change.
---
## 4. Indirection audit (heuristic c)
**T4-26 — `ICaptureBackend` is now a dead abstraction: one implementation, zero polymorphic call
sites. The brief's "two implementations — earning its keep" assumption is FALSE at current state.**
Verified: `capture.h` itself documents (lines 139148, the "SEAM CHOICE" comment) that
`RealtimeRecordBackend` **deliberately does not implement** `ICaptureBackend` — it has a bespoke
async `begin/tick/abort` seam. `OfflineRenderBackend` is the sole deriver, and the only
construction site (`main.cpp:738`) instantiates the concrete type; nobody anywhere holds an
`ICaptureBackend*`/`&`. The interface costs a vtable and models nothing.
**Proposal:** delete `ICaptureBackend`; `OfflineRenderBackend` becomes a plain concrete class. Note
CLAUDE.md/CONTEXT still describe the module as "`ICaptureBackend` interface; two backends" — the doc
should be corrected in the same commit. **Severity:** low runtime, medium hygiene (it misleads —
this audit's own brief was misled). **Disposition:** fix in Q-W3 (the wave that rehomes the capture
orchestration and touches every call site).
**T4-27 — Warning to the Q-W2v brief (T4-14): do not split `sampler_core.cpp` along class lines.**
`AdsrEnvelope::tick`/`TriggerEnvelope::amplitudeAt`/`PitchEnvelope::tick` are called per-voice
per-sample from `Voice::advanceFrame`, which is called per-sample from `VoiceEngine::render`.
Same-TU definition is what lets the compiler inline this stack today (no LTO configured). A
by-class TU split converts the hottest inner loop into cross-TU calls — the exact dispatch-stack
blowout heuristic (c) forbids. If a split is ever wanted, the envelopes must move as
header-defined (inline) classes, not to a TU. The engine TU staying whole at 968 is the correct
trade.
**T4-28 — Warning to the Q-W2 brief (reaffirming the plan's own guardrail).** `panel_audition` and
the preview idle path must stay direct call-throughs after the 8-TU split — the plan already says
this; the two *added* TUs (T4-01: `panel_layout`, `panel_drag`) introduce no new risk (layout is
paint-time, drag is input-time), but the split of `onMouseMove` (which calls hover + drag + tooltip)
should keep per-mouse-move work as plain free-function calls, no interface.
**T4-29 — Warning to the processor split (T4-12).** Keep `process()` and any per-block helpers it
calls in one TU. The state/reload/accessor TUs are UI-thread or setup-time — safe to move freely.
The atomic-pointer-swap pattern (`publishBuiltLocked`) must not gain a virtual seam.
**T4-30 — No other gratuitous indirection found (verified, not assumed).** The only extension-side
`virtual` is T4-26. VST-side virtuals are all VST3-SDK-mandated overrides (`SingleComponentEffect`,
`CPluginView`, `IReaperUIEmbedInterface`) — not ours to remove. The layered pure→shell pairs
(`card_drag``bank_panel`, `realtime_record``capture_realtime`, `drag_out``drag_out_win`,
`prune_reconcile``persist`) are the load-bearing discipline, not forwarding waste — each layer
adds the decision/side-effect split, and all are direct calls. `bank_panel`'s ~20-function free-API
is a module boundary, not a dispatch chain; Q-W2's header segmentation thins it.
---
## Summary table — every oversize file → proposed seams → owning wave
| File (LOC) | Proposed TUs/headers | Owning wave |
|---|---|---|
| `bank_panel.cpp` (3459) | 8 TUs: render / **layout (new)** / thumbnails / audition / input / **drag (new)** / bank_ops / window | **Q-W2 (reshaped: 6→8 seams)** |
| `vst/reasampler_editor.cpp` (3065) | 8 TUs: session / controls / layout (pure candidate) / paint_sample / paint_browse_zone / input_sample / input_browse_zone / platform | **NEW wave Q-W2v** |
| `main.cpp` (1897) | orchestrator / **batch+recapture (new)** / scope_resolve / realtime_lifecycle / app-entry residue | **Q-W3 (reshaped: 3→4 hoists)** + Q-W6 |
| `vst/reasampler_processor.cpp` (1164) | processor_state / processor_reload / lifecycle+process (whole) | **NEW wave Q-W2v** |
| `bank_book.cpp` (1109) | slot_map / bank_book / JSON→`core/json` | Q-W1 (+slot_map rider) |
| `view_mode_model.cpp` (1049) + `.h` (748) | indexes+model / planners / JSON→`core/json`; header splits likewise | Q-W1 (+planner rider) |
| `actions.cpp` (1016) | design_view_actions / bank_actions / prune_action (unchanged) | Q-W4 (no change) |
| `vst/sample_map.cpp` (970) + `.h` (708) | sample_map (resolution) / component_state_io (codec); header splits likewise | **NEW wave Q-W2v** |
| `vst/sampler_core.cpp` (968) + `.h` (762) | **TU stays whole (hot-path exception, T4-27)**; header → zone_params.h + sampler_core.h | **NEW wave Q-W2v** (header only) |
| `capture_realtime.cpp` (867) | lifecycle / finalize | Q-W3 (rides the Q-9 rename) |
| `persist.cpp` (852) | session / ext_state_io / prune_fs (unchanged) | Q-W5 (no change) |
| `bank_model.cpp` (767) | model / JSON→`core/json` (unchanged) | Q-W1 (no change) |
| `view.cpp` (677) | apply / lanes | defer, or Q-W1 rider |
| `ingest.cpp` (636) | wav helpers→core / import / surfaces | wave owning T4-23 |
**Wave-plan deltas requested of triage:** (1) Q-W2 grows to 8 seams; (2) Q-W3 grows to 4 hoists +
the `ICaptureBackend` deletion; (3) a **new VST god-module wave (Q-W2v)** owns the editor /
processor / sample_map splits + the sampler_core header split — parallel-safe with Q-W2 (zero file
overlap); (4) Q-W1's relocation scope includes the VST pure libs under the placement shape chosen
at the T4-18/T4-19 fork; (5) the `core/wire/bytes.h` LE-codec template (T4-20) lands with
`component_state_io`.