31 KiB
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 (420–487) |
~130 | fine |
panel_render |
drawCardMeta/drawThumbnail (487–547), kit adapters (547–570), drawFooter (685–780), drawToolbar/drawMoreButton/drawTooltip (992–1155), drawRegionGrid/drawCardDropTarget/drawRegionHeader/drawTabStrip/paintPanel (1334–1646) |
~700 | over — needs the layout cut below |
(unplanned) panel_layout |
toolbar/footer/menu rect + row/cluster builders (571–684, 785–991), split geometry + region rects + L7 slot-order display bridge (1156–1333) | ~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 (1874–1965) |
~90 | fine (keep the direct call-through guardrail) |
panel_input |
input helpers + regionAt (1965–2010), click routing handleBanksChromeClick…handleKey/accelerator (2384–2710), plus new-content detection (1647–1874, ~230) |
~800 | over — needs the drag cut below |
(unplanned) panel_drag |
updateDropTarget/dropTargetBankId/classifyCardDrag/applyDragCursor/resolveHover/updateHover/maybeShowTooltip/onMouseMove/doReorderDrop/doReplaceDrop/resetDragState/onLBtnUp/handleRightClick (2711–3185) |
~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 (2006–2231) + popup menus (2231–2384) |
~375 | fine (menus ride with bank_ops or input — either works; they invoke the ops) |
panel_window |
handleDropFiles/dlgProc/openPanel/closePanel (3185–3336) + public API (3336–3459) |
~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 (627–731, ~105), renderOffline +
captureAndIndexOne + RunCapture + RunCaptureItemAssign (731–909, ~180), batch family
(ItemSelectionGuard/selectOnlyItem/RunBatchCaptureItems/collectRazorAreas/
TrackSelectionGuard/RunBatchCaptureRazor, 909–1181, ~270), RunRecaptureFromSource (1200–1398,
~200), realtime + insert actions (1398–1512, ~115). scope_resolve (361–590) ≈ 230 ✓;
realtime_lifecycle (186–360) ≈ 175 ✓; registration/entry residue (1512–1897) ≈ 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 (59–421, ~360), bank_actions (422–1016 minus prune,
~490), prune_action (doBankPruneFolder 821–916 + 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,
112–288, ~180), prune_fs (scanPruneOrphans/pruneDryRun/pruneOrphanSet/deleteOrphanFile/
pruneReclaim, 288–539, ~250 — pS-usage growth landed here, exactly where the plan isolates it),
session (load/guid/poll/reload, 539–852, ~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 (27–143, ~115 — a self-contained ordered-slot container with its
own serialize at 614), BankBook registry/CRUD/transfer/slot-reconcile (145–545, ~400), and
JSON serialize + Parser (547–1109, ~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, 29–140), pure
planners (autoTagNewContent/planItemRetag/planLaneMinting/makeParkPlan/makeRestorePlan/
nextModeId, 143–326, ~185), ViewModeModel state + visibility/toggle planning (332–490), and
JSON serialize + Parser (494–1049, ~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, 25–145, ~120) and JSON
(ObjWriter/writeSample/Parser::parseSample/parseIndex, 148–767, ~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, 177–324 + 569–867, ~450) and the file-side finalize (readAllBytes/writeU32LE/
trimAutoTailInPlace/finalizeRecording, 324–566, ~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, 86–217, ~130 —
see T4-22: buildFloat32Wav is a pure function trapped in a shell TU), importFileIntoActiveBank
(249–420, ~170), and the three ingest surfaces + registration (420–636). 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 (146–400), PCM + thumbnail caches monoPcmFor/thumbnailFor (719–800) |
~420 |
editor_controls |
control-value map controlValue/applyControl (399–475), knob-deck plumbing zoneDeckGroupDescs/deckGroupDescs/deckControlNorm/applyDeckKnob/deckValueLabel (475–649), envelope pack/unpack + commitPickedMarkers (649–719), applyZoneControl (2576–2587) |
~470 |
editor_layout (pure candidate — see T4-23) |
anon-ns geometry: computeSampleBands/clusterRects/zone-panel areas/channelToggleRects (904–1076), computeBrowseModal (1756–1785), zoneContentArea (1917–1924) |
~250 |
editor_paint_sample |
paint dispatch (1169), drawTitleBand, paintSample/paintEnvelopeOverlay/paintVelocityCurve/paintKnobDeck/paintCurveButton/paintCurvePopup/paintEmptyState + drawKnobFace/drawSpectralStrip/drawRootMarker (1076–1756) |
~590 |
editor_paint_browse_zone |
paintBrowse (1785–1917), paintZone (1924–2041) |
~260 |
editor_input |
resolveHover (2041–2145), onMouseDown (2147–2576 — a 430-line per-face dispatch), popup/curve mouse (1637–1735), onMouseMove/onMouseUp/onMouseRDown/onMouseWheel/onSearchChar/onFilesDropped (2587–2929) |
~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 (800–904), wndProc + non-Windows stubs (2929–3065) |
~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, 118–209 + 492–506, ~120), component-state I/O (setState/getState +
legacyLiftShouldRun, 209–349 + 774–904, ~270), accessors/param setters (349–492, ~140), and
reload + drain + usage-publish + render (reloadInstrument/publishUsage/
publishBuiltLocked/rebuildVoiceEngine/retireIdleDrain 506–774 ~270, process 904–1164 ~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, 14–406, ~390) and binary component-state codec (putU32le/
putU64le/ByteReader, zones payload put/read, serializePerformance/deserializePerformance,
serializeComponentState/deserializeComponentState v3→v11 lift ladder, selection codec,
406–970, ~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 (15–30), Keymap (34–58), AdsrEnvelope/TriggerEnvelope/PitchEnvelope
(62–251, ~190), Voice (255–680 — advanceFrame alone is ~200), VoiceEngine (680–968, ~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— whatsample_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 aVoicemember 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:
- 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. - The artifact boundary is a link-graph fact, not a source-layout fact — and the sources already
straddle it. Verified cross-artifact consumers:
sample_maplinksbank_book+wav_trim+sampler_core; the editor includesdraw_kit/theme/component_geometry/capture_paths/peaks/wav_trim/app_version/ext_keys(extension-side modules); the extension's pureinstrument_dropincludesvst/reasampler_uid.h. An artifact-first subtree would either duplicate these or still reach across — the boundary it draws is already false. - Namespace map falls out:
reasampler::instrument::{engine,map,ui}besidereasampler::modeletc. — the Q-4 rule applied uniformly. - 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
406–500), 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, 116–179) 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 139–148, 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.