Files
reasampler/CLAUDE.md
T
daniel c40458fc53 docs: reflect ME-import → new-instrument behavior
Update ingest surface (2) + instrument_drop_win entry points in CLAUDE.md;
update the Ingest-through-the-bank spec in CONTEXT.md to shipped behavior.
2026-07-27 13:42:14 -04:00

38 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Repo identity and current state

The CMake project and binary are now named reaper_reasampler. This is ReaSampler — a per-project audio sample-bank capture tool that now builds TWO artifacts: the REAPER extension (reaper_reasampler) AND ReaSampler 9000, a Windows-only VST3 sampler instrument (reasampler_9000.vst3, src/vst/, second CMake target reasampler_vst, gated on the vendored vendor/vst3sdk submodule slice). The MPE modules (mpe_model, mpe_view) have been removed. M0M8 are complete (bank_model, peaks, capture offline+realtime, persist, bank_panel, insert, capture action family, RealtimeRecordBackend, tail T1+T2+T1-followons). Phase B multi-bank (B1B5, B-cap), Phase D1/D2 Design View (D1D5, D2-W1W3-B), Phase V versioning/beta-channel (V1/V3, V4), M10 provenance + re-capture from source, Phase R Reclaim (R1R3: prune-reconcile core, dry-run shell, guarded deletion + action + panel button), M11 in full (batch capture, action-button strip + keybinding labels, conform-on-insert verified-extant, native OS drag-out), Phase L L1 (shared LICE drawing kit: theme/palette module, component_geometry geometry helpers, draw_kit shell, GDI DrawText retired in bank_panel), Phase L L2 (dock-panel layout redesign: action_bar pure task-grouped layout module, full M11-aware button inventory placed by task cluster, bank_panel redesigned through the L1 kit), Phase L L4 (dock-panel button layout enhancement: three-zone layout — top capture/placement/maintenance toolbar, bottom Design-View tagging/switching toolbar, footer narrow-toggle + Tail-button + Prune; footer_bar pure module + footer_bar_tests; ActionCluster::Tagging/Switching in action_bar), Phase L L5 (dock-panel button refinements: top-bar overflow menu for Batch Items/Batch Razor/Capture RT via overflow_menu pure module; custom LICE-kit hover-delay tooltips via tooltip pure module; four opposite-mode Item/Track tag buttons + Show Both on the bottom toolbar; Toggle + Activate-Arrange/Design buttons removed; grouping spacing widened — mode_enable pure opposite-mode enablement predicate), Phase L L6 (toolbar polish: single-row short-label button faces — keybinding micro sub-row removed from ActionBarSlot, keybinding now shown in hover tooltip as "phrase — binding"; Cancel RT moved from visible bar into the overflow menu — More (⋯) now lists Batch Items / Batch Razor / Capture RT / Cancel RT; visible top-bar cluster order Capture → Maintenance → Placement; toolbar height 40→28 px), Phase L L7 (capture ordering, card metadata, and selection styling: per-bank SlotMap (id→slot) in bank_book — gap-preserving persisted display order, reorderSample/replaceSample/orderedSampleIds/reconcileSlots mutators; captureTimeSigNum/captureTimeSigDenom stamped on Sample at capture via TimeMap_GetTimeSigAtTime; card_drag pure module — gesture precedence + SWELL stock cursor cues; card_meta pure module — bars.beats.subdivisions + s.ms formatters; bank_panel sparse-grid rendering with gap cells, drop dispatch + cursor cues, metadata overlay, tertiary-border selection), Phase L L3 (VST editor + embed-strip restyle: reasampler_editor.cpp + reasampler_embed.cpp restyled through the L1 kit — REAPER-grey neutrals + three pastel accents, pastel spectral keyboard strip + zone bars as the signature surface with active zone lifting to accent/primary + static glow, hover/pressed/drag states throughout; local kCol* forest-green palette retired; GDI DrawTextA path retired in both VST shells; VST3 class UID unchanged; Phase L now complete), and Phase S (S1S18 complete; S13 cross-artifact relay deferred): VST3 sampler instrument reasampler_9000.vst3 — voice engine (sampler_core), zone payload (sample_map), pitch-preserve OLA (pitch_shift), bank change-detection (bank_sync), IPC bridge (bridge_marshal), editor UI modules (editor_geometry, keyboard_strip, waveform_view, capture_browser, browser_scroll, note_entry, param_slider, embed_strip), VST3 shells (reasampler_processor, reasampler_editor, reasampler_embed, reaper_bridge READ-ONLY bank consumer, vst_entry); extension additions: instrument_drop pure FX-button blob builder (S17), assignment_request pure ingest-assign wire (S8), ingest shell (S8: docked-panel drop → capture/import + assign), instrument_drop_win shell (S17: TrackFX_AddByName + vst_chunk inject, rollback on fail); drag_out extended with InstrumentDrop third gesture; S18 channel isolation forks VST3 binary to reasampler_9000_beta.vst3 alongside the beta extension have all landed. M9 slots abandoned (Daniel, 2026-07-27). The discipline — pure REAPER-free testable core split from REAPER-facing shells — is preserved throughout.

CONTEXT.md is the authoritative spec and build roadmap. Read it first for any non-trivial task. Every REAPER API name cited there is correct-by-intent; verify argument order, types, and flag values against vendor/reaper-sdk/sdk/reaper_plugin_functions.h before use.

One-time submodule setup

git submodule update --init

Vendors three submodules (see .gitmodules):

  • vendor/reaper-sdksdk/reaper_plugin.h, sdk/reaper_plugin_functions.h, SWELL headers

  • vendor/WDL — WDL utilities and the SWELL cross-platform Win32 layer

  • vendor/vst3sdk — Steinberg VST3 SDK (Windows-only; requires a nested init after the top-level init):

    git submodule update --init vendor/vst3sdk
    cd vendor/vst3sdk && git submodule update --init pluginterfaces base public.sdk
    

    The VST3 target (reasampler_vst) is gated on EXISTS .../pluginfactory.cpp — configure quietly omits it if the slice is absent.

Build and test

cmake -B build -S .
cmake --build build
ctest --test-dir build

Key targets (see CMakeLists.txt for the full list):

Target Kind Purpose
bank_model_tests executable Pure unit tests for bank_model — no REAPER, no DAW.
peaks_tests executable Pure unit tests for peaks — no REAPER, no DAW.
capture_paths_tests executable Pure unit tests for capture_paths — no REAPER, no DAW.
view_mode_model_tests executable Pure unit tests for view_mode_model — no REAPER, no DAW.
view_tree_tests executable Pure unit tests for view_tree — no REAPER, no DAW.
mode_switch_tests executable Pure unit tests for mode_switch — no REAPER, no DAW.
bank_grid_tests executable Pure unit tests for bank_grid — no REAPER, no DAW.
tab_strip_tests executable Pure unit tests for tab_strip — no REAPER, no DAW.
bank_book_tests executable Pure unit tests for bank_book — no REAPER, no DAW.
wav_trim_tests executable Pure unit tests for wav_trim — no REAPER, no DAW.
owned_manifest_tests executable Pure unit tests for owned_manifest — no REAPER, no DAW.
app_version_tests executable Pure unit tests for app_version — no REAPER, no DAW.
provenance_tests executable Pure unit tests for provenance — no REAPER, no DAW.
prune_reconcile_tests executable Pure unit tests for prune_reconcile — no REAPER, no DAW.
prune_button_tests executable Pure unit tests for prune_button — no REAPER, no DAW.
batch_capture_tests executable Pure unit tests for batch_capture — no REAPER, no DAW.
action_buttons_tests executable Pure unit tests for action_buttons — no REAPER, no DAW.
drag_out_tests executable Pure unit tests for drag_out — no REAPER, no DAW.
theme_tests executable Pure unit tests for theme — no REAPER, no DAW.
component_geometry_tests executable Pure unit tests for component_geometry — no REAPER, no DAW.
action_bar_tests executable Pure unit tests for action_bar — no REAPER, no DAW.
footer_bar_tests executable Pure unit tests for footer_bar — no REAPER, no DAW.
overflow_menu_tests executable Pure unit tests for overflow_menu — no REAPER, no DAW.
mode_enable_tests executable Pure unit tests for mode_enable — no REAPER, no DAW.
tooltip_tests executable Pure unit tests for tooltip — no REAPER, no DAW.
card_drag_tests executable Pure unit tests for card_drag — no REAPER, no DAW.
card_meta_tests executable Pure unit tests for card_meta — no REAPER, no DAW.
pitch_shift_tests executable Pure unit tests for pitch_shift (S16 Preserve engine) — no REAPER, no DAW.
instrument_drop_tests executable Pure unit tests for instrument_drop (S17 FX-button blob) — no REAPER, no DAW.
assignment_request_tests executable Pure unit tests for assignment_request (S8 ingest-assign wire) — no REAPER, no DAW.
sampler_core_tests executable Pure unit tests for sampler_core (VST3 voice engine) — no REAPER, no DAW.
editor_geometry_tests executable Pure unit tests for editor_geometry (VST3 editor layout) — no REAPER, no DAW.
bridge_marshal_tests executable Pure unit tests for bridge_marshal (bridge read-decode) — no REAPER, no DAW.
embed_strip_tests executable Pure unit tests for embed_strip (VST3 embed layout, S6) — no REAPER, no DAW.
sample_map_tests executable Pure unit tests for sample_map (VST3 zone payload v5) — no REAPER, no DAW.
capture_browser_tests executable Pure unit tests for capture_browser (VST3 capture browser, S10) — no REAPER, no DAW.
keyboard_strip_tests executable Pure unit tests for keyboard_strip (VST3 keyboard strip) — no REAPER, no DAW.
waveform_view_tests executable Pure unit tests for waveform_view (VST3 waveform view) — no REAPER, no DAW.
bank_sync_tests executable Pure unit tests for bank_sync (VST3 generation change-detection, S9) — no REAPER, no DAW.
browser_scroll_tests executable Pure unit tests for browser_scroll (VST3 browser scroll, S12) — no REAPER, no DAW.
note_entry_tests executable Pure unit tests for note_entry (VST3 note entry, S12) — no REAPER, no DAW.
param_slider_tests executable Pure unit tests for param_slider (VST3 param slider, S12) — no REAPER, no DAW.
reaper_reasampler loadable module The REAPER extension binary (.dll / .dylib / .so).
reasampler_vst loadable module The VST3 sampler instrument (reasampler_9000.vst3); Windows-only, omitted if vendor/vst3sdk slice is absent.

Beta channel build (Phase V, V4)

To build the fully isolated beta binary (reaper_reasampler_beta), pass the channel flag at configure time:

cmake -B build-beta -S . -DREASAMPLER_CHANNEL=beta
cmake --build build-beta

The flag threads through configure_fileversion_generated.h and fans out via app_version into the binary name, ext-state namespace ("reasampler_beta"), command-id prefix (CEREBELLUM_REASAMPLER_BETA_), action-name prefix ("ReaSampler beta: "), dock ident, and version display ("0.9.01-beta"). The default build (no flag) is byte-identical to the pre-V4 stable identity.

The VST3 target forks identically (S18 channel isolation): REASAMPLER_CHANNEL=beta produces reasampler_9000_beta.vst3; the default produces reasampler_9000.vst3. The beta VST pairs only with the beta extension — each channel carries its own per-channel VST3 class UID, preventing a saved instance from rebinding across channels.

macOS / Linux: SWELL dialog resources

src/resource.rc must be pre-processed by SWELL's resgen once per platform:

php vendor/WDL/WDL/swell/mac_resgen.php src/resource.rc   # macOS; Linux reuses the output

Add the generated file to the appropriate APPLE / Linux target_sources block in CMakeLists.txt. The SWS extension build is the canonical reference for this step.

Install / reload

There is no hot-reload. Copy the built binary into REAPER's UserPlugins/ folder (Options → Show REAPER resource path) and restart REAPER. Extensions load at startup only.

Architecture: the load-bearing split

Pure core (no REAPER types, unit-testable outside the DAW):

  • bank_modelSample metadata struct + BankIndex (add/remove/query/tier/dedup-by-hash + JSON round-trip). Test it hard — it is the heart. Sample carries captureTimeSigNum/captureTimeSigDenom (L7: stamped at capture via TimeMap_GetTimeSigAtTime, refreshed on re-capture).
  • peaks — waveform min/max bin computation from raw PCM. Fed a known signal, asserts envelope. Does not depend on REAPER's peak API.
  • view_mode_model — Design View mode system: mode registry, GUID-keyed membership, folder-tree-aware visibility derivation, snapshot-based park/restore planner, JSON round-trip. Mirror of bank_model for the Design View phase.
  • view_tree — pure I_FOLDERDEPTH→FolderTree helper for the Design View shell; no REAPER types at the boundary.
  • bank_grid — REAPER-free grid layout, selection, keyboard-nav, and thumbnail-cache-key logic for the docked bank panel: computeCellRects (N cells tiled left-to-right/top-to-bottom), hitTestCell, Selection mutators (applyClick, navigate), ThumbnailKey/thumbnailKeyString, and compressAmplitudeForDisplay. The originating geometry module that later pure modules mirror.
  • tab_strip — REAPER-free scrollable tab-strip layout + hit-test for the named-banks strip (Phase B): computeTabStripLayout (overflow + chevron geometry), computeTabRects (clipped, scroll-offset-shifted tab rects), hitTestTabStrip (Tab / ScrollLeft / ScrollRight / None). Mirror of bank_grid.
  • mode_switch — REAPER-free segment layout + hit-test math for the bank_panel's Design View mode switch; divides a header rectangle into N equal segments and hit-tests a point to a segment. Mirror of bank_grid.
  • bank_book — multi-bank registry (Phase B): an ordered set of banks (pool seeded as bank-zero + named banks), each wrapping a BankIndex. Owns create/rename/reorder/delete of named banks, pool privileges (un-deletable/un-renamable/un-evacuable, never zero banks) enforced in-model, active-bank id, index-only move/copy/remove of a sample between or from banks, hashReferencedElsewhere cross-bank reference query, JSON round-trip + legacy-bank_index→pool migration. Wraps BankIndex (bank_model untouched; no bankId on Sample). L7 adds: per-Bank gap-preserving SlotMap (id→slot) for deterministic display order — persisted inside the existing "banks" blob; reorderSample (insert-before-shift; same-slot = no-op), replaceSample (occupant index-removal via standard remove path + pool-guard inheritance; no-op on reject), orderedSampleIds, reconcileSlots (pre-L7 migration seeds dense insertion order on load).
  • owned_manifest — owned-file manifest seam (Phase B B-cap): the set of project-relative files the capture path itself created, persisted under the "owned_files" ext-state key, so Phase R prune can distinguish the bank system's own orphans from hand-dropped files. Deliberately decoupled from bank_book — tracks files created, not index membership. Phase R (R1/R2) consumes it; no prune logic here.
  • app_version — REAPER-free version/channel identity (Phase V, V1+V4): CMake-sourced semver constant (appVersion()), ext-state stamp value (stampVersion() — numeric triple only, no channel suffix), parseVersion/versionLess/classifyWritingVersion, and the full set of channel-derived identity accessors (extStateNamespace(), commandIdPrefix(), actionDisplayPrefix(), binaryName(), dockTitle(), dockIdent(), channelCommandId(), channelActionName()). All channel strings derive from the one REASAMPLER_CHANNEL_IS_BETA bit threaded in via configure_fileversion_generated.h; no scattered #ifdefs in the shells.
  • wav_trim — 32-bit-float WAV parse + header-aware truncate plan (RIFF/data size rewrite) for the realtime tail's PCM decay-scan trim (T2). Rejects WAVE_FORMAT_EXTENSIBLE with non-float SubFormat GUID. Depends on peaks for the AudioSample float alias.
  • provenance — capture-recipe fingerprint (M10): build/encode/compare a rsprov1 length-prefixed fingerprint of scope, exact range, tail, rate/channels, track GUIDs, and order-sensitive FX-chain identity; parse/compare for drift detection on re-capture. A thin reproducibility fingerprint — NOT a serialized chain to restore. Drives BankIndex::updateInPlace / BankBook::updateSampleInPlace (order-preserving, id-stable) on re-capture.
  • prune_reconcile — Phase R pure prune core: pruneOrphans(present, referenced, owned) computes (owned ∩ present) referenced (exact-string path match); buildPruneReport tallies count/bytes/display-capped file list; pruneDeletePlan produces the confirm-time staleness intersection (confirmed ∩ freshOrphans). REAPER-free, filesystem-free. The safety-critical "which files are orphans" decision — hard-tested here before any I/O exists. Also: BankBook::referencedPaths() additive const union query (all banks incl. pool, de-duped) added to bank_book.
  • prune_button — Phase R pure layout/hit-test for the bank_panel footer Prune button: computePruneButton (right-anchored, suppressed gracefully when footer is too narrow) + hitTestPruneButton. Mirror of mode_switch / tab_strip.
  • batch_capture — M11 pure batch-capture planner: maps a list of source ranges to ordinal capture units, drives per-unit capture via a shared captureAndIndexOne helper, and aggregates mixed results. No REAPER types at the boundary.
  • action_buttons — M11 pure action-button strip layout/hit-test: divides a strip rect into N action buttons, min-width overflow-hiding, label formatting with "(unbound)" fallback, ActionButtonRect struct. Mirror of mode_switch / bank_grid.
  • drag_out — M11 pure OS drag-out module: gesture-boundary decision (internal drag becomes OS-bound when the pointer leaves the panel client rect), path-list assembly with dedupe and missing-file skip. S17 adds a third gesture InstrumentDrop: pointer is outside the panel but still over the REAPER UI, and exactly one sample is selected — signals that the shell should execute an instrument-drop rather than a file-copy drag. No REAPER types at the boundary.
  • theme — Phase L pure palette module (L1): role→color mapping via one constants block (DS-2 revised — REAPER-grey neutral ladder: bg/base #2b2b2b / bg/panel #333333 / bg/cell #3a3a3a / line/hairline #4a4a4a / text/primary #dcdcdc / text/dim #a8a8a8; three-accent pastel system: accent/primary pastel lime #B0E098 = live/active/selected, accent/secondary pastel teal #84D6D0 + accent/tertiary pastel purple #C2AAE8 = categorical distinctions, accent/hot lighter lime = hover/drag feedback); Role enum carries the three accent roles; roleColor/roleColorState updated (Active/Dragging/Focus → primary accent); spectralColor is a pastel three-stop sweep anchored on the three accents (lime → teal → purple); WCAG contrast-floor helpers; full interaction-state color model. No LICE or REAPER types.
  • component_geometry — Phase L pure component geometry/hit-test helpers (L1): button/slider/list-row geometry + hover hit-test. No LICE or REAPER types. Mirror of mode_switch/bank_grid.
  • action_bar — Phase L pure task-grouped action-bar layout/hit-test (L2 + L4): clusters (Capture / Placement / Maintenance / Tagging / Switching), per-button label rects, whole-trailing-button overflow, point→index hit-test. ActionCluster::Tagging and ActionCluster::Switching added in L4 for the bottom Design-View toolbar. No LICE or REAPER types. Mirror of mode_switch/bank_grid/action_buttons.
  • footer_bar — Phase L pure footer layout/hit-test (L4): narrow [Arrange|Design] mode-toggle geometry + per-mode count label, Tail button, and Prune placement in the panel footer strip. No LICE or REAPER types. Mirror of mode_switch / prune_button.
  • overflow_menu — Phase L pure overflow-menu-button geometry/reserve/hit-test (L5): right-anchored More (⋯) button rect computation and hit-test for the top-toolbar overflow. No LICE or REAPER types. Mirror of prune_button.
  • mode_enable — Phase L pure opposite-mode enablement predicate (L5): given the active mode, computes per-button live/disabled state for the four Item/Track × Arrange/Design tag buttons on the bottom toolbar. No LICE or REAPER types.
  • tooltip — Phase L pure tooltip placement + prefix-strip (L5): custom LICE-kit hover-delay tooltip geometry and text derivation — strips the ReaSampler: display prefix from the registered action phrase; tooltip box width clamped to the client rect. No LICE or REAPER types.
  • card_drag — Phase L pure drag-gesture precedence + slot hit-test (L7): leave-client → OS drag-out; other-bank → move/copy; same-bank → reorder / Alt-over-occupied → replace. Returns the resolved gesture so the shell can map it to a SWELL stock cursor cue. Sparse computeSlotRects / hitTestSlot. No LICE or REAPER types. Mirror of drag_out / prune_button.
  • card_meta — Phase L pure card-metadata formatters (L7): bars.beats.subdivisions from lengthSeconds + captureTempo + capture-time-signature stamp; seconds.milliseconds rounded. Both blank when the sample is unstamped. No LICE or REAPER types.
  • instrument_drop — Phase S pure FX-button drop blob builder (S17): constructs the base64-encoded vst_chunk blob needed to inject a pre-configured reasampler_9000 instrument via TrackFX_AddByName + chunk inject; reuses the VST3 serializer. All-or-nothing contract — caller rolls back via TrackFX_Delete on any failure. No REAPER types at the boundary.
  • assignment_request — Phase S pure ingest-assign wire (S8): typed request record carrying the drop payload (file path, zone assignment hint) from the ingest shell through to the VST3 bridge. No REAPER types at the boundary.

REAPER-facing shells:

  • captureICaptureBackend interface; OfflineRenderBackend (deterministic default) and RealtimeRecordBackend. Input: CaptureRequest. Output: finished file + populated Sample handed to bank_model.
  • insert — placement via InsertMedia; conform-to-project-tempo is an explicit opt-in flag, never silent stretching.
  • bank_panel — docked LICE-drawn grid: thumbnails, audition, multi-select, keyboard navigation; three-zone layout (L4/L5/L6): top toolbar = visible bar in cluster order Capture (Capture Item, Capture Track) → Maintenance (Re-capture) → Placement (Insert, Insert Conform) via action_bar, each button showing a single-row short label, firing via NamedCommandLookup + Main_OnCommand; a right-anchored More (⋯) overflow menu button (overflow_menu) opens a TrackPopupMenu listing Batch Items / Batch Razor / Capture RT / Cancel RT; hover tooltips (tooltip) show the full action name with the ReaSampler: prefix stripped plus the live keybinding ("phrase — binding" when bound, bare phrase when unbound), sourced from the registered action phrase + kbd_getTextFromCmd; toolbar height 28 px; bottom toolbar = four opposite-mode tag buttons (Item: Arrange / Item: Design / Track: Arrange / Track: Design — live only for the opposite of the active mode, Disabled otherwise via mode_enable) + set-apart Show Both (VIEW_SHOW_BOTH); Toggle and Activate-Arrange/Activate-Design buttons removed (actions stay registered); grouping clusterGap 24 / buttonGap 4; footer = narrow [Arrange|Design] toggle · per-mode count · Tail button (cycles tail on click, proper kit button states) · set-apart warn Prune (rightmost) via footer_bar; entire panel draws through the L1 kit by palette role with hover on interactive elements, single KitColor→LICE_pixel boundary via draw_kit; OS drag-out hook (M11) initiating an OS-level copy drag when the pointer leaves the panel client rect, via drag_out + drag_out_win. L7 adds: grid renders in sparse slot order (decorative empty-gap cells for unoccupied slots; every cell↔sample consumer remapped to id-based occupied-ordinal space — selection, keyboard nav with gap-skip arrows, audition, multi-select, delete/re-capture resolution, drags); drop dispatch (reorder / Alt-replace / move-copy) with one-Ctrl-Z undo, per-slot drop highlight (accent/hot, doubled outline for replace), SWELL stock cursor cues via SetCursor per the card_drag resolved gesture; metadata overlay (bars.beats bottom-left Micro, s.ms bottom-right ValueMono, TextDim, decorative); selection = normal cell + accent/tertiary purple border, inversion removed, focus ring distinct.
  • persist — project ext state (SetProjExtState / GetProjExtState, namespace "reasampler") ↔ BankBook JSON ("banks" key — L7: per-Bank SlotMap rides inside this blob; a pre-L7 blob with no slot data migrates via reconcileSlots() to dense insertion order on load) + ViewModeModel JSON ("view_state" key) + TailSetting JSON ("tail_setting" key) + OwnedManifest JSON ("owned_files" key) + writing-version stamp ("version" key, written via stampVersion() on every saveToActiveProject()); project-relative path resolution. A projectconfig hook (BeginLoadProjectState(isUndo)) triggers a deferred session reload on undo/redo so Ctrl-Z/redo visibly restores book/view/tail/manifest in-session. Hosts ReaSamplerSession::pruneDryRun() (read-only orphan enumeration via M4 project-relative resolution) and pruneOrphanSet() (full-set query for the R3 delete path); supplies referencedPaths() + owned().paths() to the prune_reconcile pure core.
  • view — Design View shell: reads the folder tree via view_tree, snapshots flag values before parking, drives hide + CPU-park on inactive-mode leaves (B_SHOWINTCP/B_SHOWINMIXER/B_MAINSEND/I_FXEN + per-FX offline) and derived visibility on parents; restores from snapshot. Never touches master or B_MUTE/I_SOLO.
  • track_guid — shared MediaTrack* → canonical GUID-string formatter; single source of truth for membership keys used by both the view shell and the actions layer.
  • provenance_shell — FX-chain identity queries via TrackFX_* / TakeFX_* APIs; collects source-item paths and parent-detection inputs to feed the pure provenance fingerprint builder. Stamps Sample.provenance on capture when every resolving source item maps by exact normalized path (case-folded on Windows) to exactly one bank sample; ambiguous/mixed cases record nothing conservatively.
  • drag_out_win — M11 OS drag-out shell: Windows OLE DoDragDrop / CF_HDROP, copy-only structurally (DROPEFFECT_MOVE not offered, no source-deletion path); macOS/Linux via SWELL_InitiateDragDropOfFileList (copy-semantics caveat documented — SWELL does not expose a drop-effect query). Driven by the drag_out pure module.
  • ingest — Phase S ingest-through-the-bank shell (S8) on the EXTENSION side: three surfaces — (1) arrange capture→bank→assign (bindable action), (2) Media-Explorer import→bank→new instrument on a new track (bindable action, single-file via MediaExplorerGetLastPlayedFileInfo; RS5k "load into a new sampler" parity — no assignment_request written; calls createTrackWithInstrument), (3) file drop onto the bank panel→bank→assign (multi-file, assigns the first). Surfaces (1) and (3) write the assignment_request ext-state wire via ingestAssignActiveInstance; surface (2) does not. ingest NEVER inserts a timeline item.
  • instrument_drop_win — Phase S FX-button drop shell (S17): (a) resolves a screen point to a track + its TCP FX-button hotspot via REAPER's hit-test API (GetThingFromPoint, TrackFX_*), and (b) on release adds a ReaSampler 9000 instance to that track and injects the dragged capture as its component state via TrackFX_SetNamedConfigParm "vst_chunk". Exposes three entry points: loadInstrumentOntoTrack (FX add+inject, no own undo block — the shared inner half used by callers that own the undo grouping), performInstrumentDrop (wraps loadInstrumentOntoTrack in its own undo block — the S17 drag-drop gesture path), and createTrackWithInstrument (appends a new track, names it, then calls loadInstrumentOntoTrack; rolls back via DeleteTrack on failure — the ME-import new-instrument path). Never captures, never writes the bank, never inserts a timeline item.
  • draw_kit — Phase L shared LICE draw shell (L1): fillSurface (micro-gradient + inner highlight/shadow), drawButton/drawSlider/drawListRow/drawWaveform, cached-font text() over four LICE_CachedFonts (kit-owned lifecycle), full interaction-state model, double-buffer preserved. Consumes theme + component_geometry. First consumer: bank_panel (GDI DrawText path retired in L1).
  • actions — registers the capture/placement/slot action family, the Design View action family (toggle active mode, activate Arrange/Design, tag/untag selected tracks, show-both), the multi-bank action family (create/rename/reorder/delete bank, evacuate, activate, move/copy/remove selected samples), and the Phase R prune action (BANK_PRUNE_FOLDER — dry-run-first, confirm-with-manifest, then pruneDeletePlan-guarded deletion; the ONLY file-deletion authority in the system); routes each to the modules above via the command_id/gaccel/hookcommand contract. Every bank index verb wraps its mutation in a batched REAPER undo point (Undo_BeginBlock2/EndBlock2, UNDO_STATE_MISCCFG) so one bank operation is one Ctrl-Z. The prune action writes no ext state and opens no undo point (file deletion is not REAPER-undoable).

VST3 instrument (src/vst/) — pure core:

  • sampler_core — Phase S voice engine (S1S16): polyphonic voice allocation with bounded stealing; per-zone ZonePlayParams (PlayMode Gate/Trigger, AHDSR envelope times in frames, Trigger %-length + fades, pitch engine Varispeed/Preserve, AD pitch modulation envelope); repitch/interpolation from a root note with loop-point-aware sustain; no file I/O. No REAPER types at the boundary.
  • sample_map — Phase S zone payload v5 (S3): zones keyed by note range; wall-clock times stored as rate-free SECONDS, resolved against the live project rate — NO hardcoded sample rates in src/ (Daniel's standing ruling, load-bearing). JSON round-trip.
  • pitch_shift — Phase S OLA pitch-preserve engine (S16): hand-rolled overlap-add pitch shifter for the Preserve playback mode; no third-party dependencies. No REAPER types at the boundary.
  • bank_sync — Phase S generation change-detection + assignment-request consume (S9/S8): owns the yes/no decision logic — parses the generation stamp, decides whether it differs from what was last seen, decides whether a decoded assignment request is new-and-resolvable-and-worth-applying. The processor shell owns the cadence and side effects (reloadFromBank, setSelectedSampleId); this module owns only the maths so the rules are provable without a host. No REAPER types at the boundary.
  • bridge_marshal — Phase S pure marshalling helper for the REAPER VST-host bridge read (S7): interprets the GetProjExtState int return against its filled buffer — the read-decode arithmetic split out of the reaper_bridge shell. The S1 string-scan JSON reader was retired in S4 when the instrument moved to the shared bank_book/bank_model JSON path. No IPC, no shared memory. No REAPER types at the boundary.
  • editor_geometry — Phase S VST3 editor layout pure module (S1): defines the shared Rect type + contains() hit-test used across all editor geometry modules; provides the spike editor's EditorLayout (titleBar / button / canvas) and layoutEditor(w,h). No REAPER or VST3 types at the boundary.
  • keyboard_strip — Phase S piano-keyboard strip pure module: MIDI-note→key rect mapping, black/white key layout, hit-test, zone highlight overlay geometry. No REAPER or VST3 types at the boundary.
  • waveform_view — Phase S waveform/marker geometry pure module (S11): maps a sample's full frame span [0, frameCount] linearly across a horizontal rect; generic named draggable markers (start, loop start, loop end) with drag-delta resolver, clamp, and zero-crossing snap. No REAPER or VST3 types at the boundary.
  • capture_browser — Phase S capture browser pure module (S10): card-grid layout + bank-filter tab strip geometry and hit-test; knows only counts and rects — holds no sample data, draws nothing. Scroll is NOT here (S12 layers it over this module via browser_scroll). No REAPER or VST3 types at the boundary.
  • browser_scroll — Phase S scroll + type-to-filter pure module (S12) layered over capture_browser: vertical scroll offset, max-offset clamp, visible-row window, scrollbar thumb rect, thumb-drag↔offset mapping; AND name-substring search (case-insensitive, composing with the bank filter). No REAPER or VST3 types at the boundary.
  • note_entry — Phase S note-entry pure module (S12): parses a raw string into a clamped MIDI note [0,127] for zone low/high/root note editing; accepts plain decimal integers or note names (C4==60, DAW convention); out-of-range clamps, unparseable returns nullopt. No REAPER or VST3 types at the boundary.
  • param_slider — Phase S parameter control-panel pure module (S12): lays out a vertical stack of fixed-height control rows in two shapes — TOGGLE (two-segment selector) and SLIDER (horizontal track with draggable 0..1 handle); maps normalized value to/from handle pixel; the shell owns domain-value conversion and control-id→engine-param binding. No REAPER or VST3 types at the boundary.
  • embed_strip — Phase S embed-mode strip layout pure module (S6): compact single-row control layout for when the editor is embedded in the track FX chain. No REAPER or VST3 types at the boundary.

VST3 instrument (src/vst/) — shells:

  • reaper_bridge — Phase S READ-ONLY bank consumer (S7): receives bank snapshots from the extension over the IPC bridge and exposes them to the VST3 processor/editor as a read-only view. Never writes to the extension's bank — this is a load-bearing invariant; no mutation path exists in this module.
  • reasampler_processor — Phase S VST3 SingleComponentEffect shell: declares an event-input bus + stereo audio output bus, marshals host MIDI note-on/off into the VoiceEngine, renders audio via the loaded sample_map zones; owns the off-audio-thread reloadFromBank + atomic pointer swap so process() does no allocation, no file I/O, no bridge calls.
  • reasampler_editor — Phase S VST3 IPlugView LICE editor shell (S10): subclasses CPluginView, hosts a LICE-drawn child window inside the host's IPlugView seat; default face is the capture browser (bank-filter tabs + card grid), then single-capture setup (keyboard strip + root marker), with opt-in zones panel (S10-Z). Accepts file drops and shows a drop affordance (S13 relay to the extension's ingest path is deferred — drop-onto-editor ingest is NOT shipped).
  • reasampler_embed — Phase S embed shell (S6): implements IReaperUIEmbedInterface so the instrument draws a compact keymap/level strip INLINE in the track/mixer control panel (TCP/MCP), without a plugin-owned HWND — REAPER hands a LICE bitmap per paint; delegates layout/hit-test to embed_strip.
  • vst_entry — Phase S VST3 entry point: GetPluginFactory export, class registration, channel-forked class UIDs (S18).

REAPER extension contract (src/main.cpp)

  • Exactly one translation unit defines REAPERAPI_IMPLEMENT — that is main.cpp. Every other .cpp includes reaper_plugin_functions.h without the define and gets extern declarations for the global API function pointers.
  • REAPER dlopen()s any reaper_*.dll|dylib|so found in UserPlugins/ and calls the ReaperPluginEntry export (produced by REAPER_PLUGIN_ENTRYPOINT). rec->GetFunc resolves API pointers; rec->Register plugs extension callbacks in.
  • Action registration pattern (preserve this for all new actions):
    1. rec->Register("command_id", (void*)"STABLE_FOREVER_STRING") — mints a persistent command id. Never change this string after shipping; user keybindings key off it. Since Phase V (V4), ids and display names are composed via channelCommandId(suffix) and channelActionName(phrase) from app_version — the FOREVER-STABLE contract applies per channel (stable and beta each have their own permanent id family).
    2. rec->Register("gaccel", &accel) — puts the action in the Actions list.
    3. rec->Register("hookcommand", ...) — receives every action fired; claim only your own id, return false otherwise.
    4. On unload (rec == nullptr), mirror-unregister everything with the same strings prefixed by '-'.

The load-bearing principle

Capture and placement are separate acts. Capturing audio writes a file to the bank and adds an index entry. It never puts an item in the arrange view. Placement is a distinct, on-demand action (insert module / InsertMedia). Any code path that auto-inserts a capture into the timeline violates the purpose of the tool and must be rejected in review.

Precision invariants — required before any feature ships

  • Null test: a dry offline capture of a range, re-inserted at its source position, nulls to silence against the source. Ship as a verification action.
  • Bit-identical repeats: identical offline capture requests produce identical files.
  • Non-destructive: capture never mutates source items or tracks; the realtime backend's temp track is created and removed cleanly, and source routing is restored.
  • Exact bounds: no rounding of the requested range; no added silence unless a tail is explicitly requested; channel count preserved (no silent stereo fold).
  • Relative paths only in the persisted BankIndex.
  • Capture FX scope: two scopes only — item = item/take FX only; track = item FX + the selected track's own track FX. There is no master scope (to capture the master, render a track instead). For both scopes, the out-of-scope chain (ancestors + master track, plus the item's own track for item scope) has its FX, gain, and pan/width/pan-law/mode neutralized to unity — the master track is bypassed as out-of-scope chain, not captured as a scope. Range (time selection or razor) is orthogonal.