Files
reasampler/CLAUDE.md
T
daniel 5ec84ab0eb docs: mark M9 abandoned in Phase Q framing; add L3 + bank_grid/tab_strip to CLAUDE.md
M9 deferred->abandoned in code-organization.md gate (disposition note removed).
CLAUDE.md: L3 identity clause, M9-abandoned line, bank_grid/tab_strip in module
list and targets table.
2026-07-27 12:51:38 -04:00

193 lines
37 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.
# 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-sdk``sdk/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_file``version_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_model``Sample` 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_file``version_generated.h`; no scattered `#ifdef`s 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:**
- `capture``ICaptureBackend` 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→assign (bindable action, single-file via `MediaExplorerGetLastPlayedFileInfo`), (3) file drop onto the bank panel→bank→assign (multi-file, assigns the first). All three write the `assignment_request` ext-state wire; 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"; wraps the whole operation in one REAPER undo block. 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_CachedFont`s (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.