Move S-VIEW-1..9 to COMPLETED (three-view editor + velocity spline curve v7); F3 settled (full-window Browse modal); S-VIEW-10 (curve editor UI) remains the lone open Phase S item. Add trigger_seam + velocity_curve to the module inventory.
41 KiB
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. M0–M8 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 (B1–B5, B-cap), Phase D1/D2 Design View (D1–D5, D2-W1–W3-B), Phase V versioning/beta-channel (V1/V3, V4), M10 provenance + re-capture from source, Phase R Reclaim (R1–R3: 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 (S1–S18 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). Phase S S-VIEW Wave 1 + Wave 2 fully landed (2026-07-27, Merge pS-w2-t1-shell + Merge pS-w2-t2-velcurve, 52/52 green): three-view editor (Sample home face — hero waveform + envelope overlay + draggable nodes + fenced root + preview-trigger + velocity knob + Modes-and-down control strip; Browse full-window modal picker with confirm/cancel; Zone view retained + wired with piano-key pattern + key-track control); envelope_overlay (params→polyline) + envelope_edit (node hit-test + clamped/monotonic inverse map) wired in reasampler_editor.cpp; trigger_seam pure module (Trigger frames↔fraction converter, startFrame-aware, unit-tested); preview button + velocity knob UI wired (RT-safe off-thread preview-note mailbox); key-track UI control on Zone + Sample; two-tone piano-key overlay drawn; velocity_curve pure module (Fritsch–Carlson monotone cubic Hermite spline eval + control-point editing + hit-test/inverse-map; velocity_curve_tests); zones payload bumped to v7 (velocityCurve on PerformanceZone, pre-v7 blobs lift to flat y=1); Voice::start() now applies curve.eval(velocity) — default is flat y=1 (replaces prior linear velocity/127, deliberate non-back-compat change, Daniel-approved). S-VIEW-10 (velocity-curve editor UI, Wave 3) remains open. 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.sdkThe VST3 target (
reasampler_vst) is gated onEXISTS .../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. |
trigger_seam_tests |
executable | Pure unit tests for trigger_seam (VST3 Trigger frames↔fraction converter, S-VIEW-3) — no REAPER, no DAW. |
velocity_curve_tests |
executable | Pure unit tests for velocity_curve (VST3 velocity→amp monotone-spline transfer curve, S-VIEW-9) — 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—Samplemetadata struct +BankIndex(add/remove/query/tier/dedup-by-hash + JSON round-trip). Test it hard — it is the heart.SamplecarriescaptureTimeSigNum/captureTimeSigDenom(L7: stamped at capture viaTimeMap_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 ofbank_modelfor the Design View phase.view_tree— pureI_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,Selectionmutators (applyClick,navigate),ThumbnailKey/thumbnailKeyString, andcompressAmplitudeForDisplay. 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 ofbank_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 ofbank_grid.bank_book— multi-bank registry (Phase B): an ordered set of banks (pool seeded as bank-zero + named banks), each wrapping aBankIndex. 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,hashReferencedElsewherecross-bank reference query, JSON round-trip + legacy-bank_index→pool migration. WrapsBankIndex(bank_model untouched; nobankIdonSample). L7 adds: per-Bankgap-preservingSlotMap(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 frombank_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 oneREASAMPLER_CHANNEL_IS_BETAbit threaded in viaconfigure_file→version_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 onpeaksfor theAudioSamplefloat alias.provenance— capture-recipe fingerprint (M10): build/encode/compare arsprov1length-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. DrivesBankIndex::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);buildPruneReporttallies count/bytes/display-capped file list;pruneDeletePlanproduces 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 tobank_book.prune_button— Phase R pure layout/hit-test for thebank_panelfooter Prune button:computePruneButton(right-anchored, suppressed gracefully when footer is too narrow) +hitTestPruneButton. Mirror ofmode_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 sharedcaptureAndIndexOnehelper, 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,ActionButtonRectstruct. Mirror ofmode_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 gestureInstrumentDrop: 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/primarypastel lime#B0E098= live/active/selected,accent/secondarypastel teal#84D6D0+accent/tertiarypastel purple#C2AAE8= categorical distinctions,accent/hotlighter lime = hover/drag feedback);Roleenum carries the three accent roles;roleColor/roleColorStateupdated (Active/Dragging/Focus → primary accent);spectralColoris 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 ofmode_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::TaggingandActionCluster::Switchingadded in L4 for the bottom Design-View toolbar. No LICE or REAPER types. Mirror ofmode_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 ofmode_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 ofprune_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 theReaSampler: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. SparsecomputeSlotRects/hitTestSlot. No LICE or REAPER types. Mirror ofdrag_out/prune_button.card_meta— Phase L pure card-metadata formatters (L7): bars.beats.subdivisions fromlengthSeconds+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-configuredreasampler_9000instrument viaTrackFX_AddByName+ chunk inject; reuses the VST3 serializer. All-or-nothing contract — caller rolls back viaTrackFX_Deleteon 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 theingestshell through to the VST3 bridge. No REAPER types at the boundary.
REAPER-facing shells:
capture—ICaptureBackendinterface;OfflineRenderBackend(deterministic default) andRealtimeRecordBackend. Input:CaptureRequest. Output: finished file + populatedSamplehanded tobank_model.insert— placement viaInsertMedia; 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) viaaction_bar, each button showing a single-row short label, firing viaNamedCommandLookup+Main_OnCommand; a right-anchored More (⋯) overflow menu button (overflow_menu) opens aTrackPopupMenulisting Batch Items / Batch Razor / Capture RT / Cancel RT; hover tooltips (tooltip) show the full action name with theReaSampler: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,Disabledotherwise viamode_enable) + set-apart Show Both (VIEW_SHOW_BOTH); Toggle and Activate-Arrange/Activate-Design buttons removed (actions stay registered); groupingclusterGap24 /buttonGap4; footer = narrow[Arrange|Design]toggle · per-mode count · Tail button (cycles tail on click, proper kit button states) · set-apartwarnPrune (rightmost) viafooter_bar; entire panel draws through the L1 kit by palette role with hover on interactive elements, singleKitColor→LICE_pixelboundary viadraw_kit; OS drag-out hook (M11) initiating an OS-level copy drag when the pointer leaves the panel client rect, viadrag_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 viaSetCursorper thecard_dragresolved gesture; metadata overlay (bars.beats bottom-left Micro, s.ms bottom-right ValueMono,TextDim, decorative); selection = normal cell +accent/tertiarypurple border, inversion removed, focus ring distinct.persist— project ext state (SetProjExtState/GetProjExtState, namespace"reasampler") ↔BankBookJSON ("banks"key — L7: per-BankSlotMaprides inside this blob; a pre-L7 blob with no slot data migrates viareconcileSlots()to dense insertion order on load) +ViewModeModelJSON ("view_state"key) +TailSettingJSON ("tail_setting"key) +OwnedManifestJSON ("owned_files"key) + writing-version stamp ("version"key, written viastampVersion()on everysaveToActiveProject()); project-relative path resolution. Aprojectconfighook (BeginLoadProjectState(isUndo)) triggers a deferred session reload on undo/redo so Ctrl-Z/redo visibly restores book/view/tail/manifest in-session. HostsReaSamplerSession::pruneDryRun()(read-only orphan enumeration via M4 project-relative resolution) andpruneOrphanSet()(full-set query for the R3 delete path); suppliesreferencedPaths()+owned().paths()to theprune_reconcilepure core.view— Design View shell: reads the folder tree viaview_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 orB_MUTE/I_SOLO.track_guid— sharedMediaTrack*→ 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 viaTrackFX_*/TakeFX_*APIs; collects source-item paths and parent-detection inputs to feed the pureprovenancefingerprint builder. StampsSample.provenanceon 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 OLEDoDragDrop/CF_HDROP, copy-only structurally (DROPEFFECT_MOVEnot offered, no source-deletion path); macOS/Linux viaSWELL_InitiateDragDropOfFileList(copy-semantics caveat documented — SWELL does not expose a drop-effect query). Driven by thedrag_outpure 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→instrument on the SELECTED TRACK (bindable action, single-file viaMediaExplorerGetLastPlayedFileInfo; adds RS9k to the user's currently selected track vialoadInstrumentOntoTrack— no new track created, no routing changed; if no track is selected the sound still lands in the bank but no instrument is placed, with a console message; noassignment_requestwritten), (3) file drop onto the bank panel→bank only (multi-file, bank-fill; noassignment_requestwritten, no effect on any live instance's selection — the bank-generation bump is retained so open instances' browsers refresh). Only surface (1) writes theassignment_requestext-state wire viaingestAssignActiveInstance. 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 viaTrackFX_SetNamedConfigParm"vst_chunk". Exposes two entry points:loadInstrumentOntoTrack(FX add+inject, no own undo block — the shared inner half used by callers that own the undo grouping; also the ME-import placement path),performInstrumentDrop(wrapsloadInstrumentOntoTrackin its own undo block — the S17 drag-drop gesture 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-fonttext()over fourLICE_CachedFonts (kit-owned lifecycle), full interaction-state model, double-buffer preserved. Consumestheme+component_geometry. First consumer:bank_panel(GDIDrawTextpath 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, thenpruneDeletePlan-guarded deletion; the ONLY file-deletion authority in the system); routes each to the modules above via thecommand_id/gaccel/hookcommandcontract. 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 (S1–S16): polyphonic voice allocation with bounded stealing; per-zoneZonePlayParams(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 insrc/(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 theGetProjExtStateint return against its filled buffer — the read-decode arithmetic split out of thereaper_bridgeshell. 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 sharedRecttype +contains()hit-test used across all editor geometry modules; provides the spike editor'sEditorLayout(titleBar / button / canvas) andlayoutEditor(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 viabrowser_scroll). No REAPER or VST3 types at the boundary.browser_scroll— Phase S scroll + type-to-filter pure module (S12) layered overcapture_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.trigger_seam— Phase S S-VIEW-3 pure Trigger frames↔fraction converter: owns the shared formula for converting between the engine's source-frame fade counts and the overlay's fractional representation (fraction of the played span, itself derived fromlengthFraction × (frameCount − startFrame));startFrameis threaded correctly through both the pack (frames→fraction, draw path) and unpack (fraction→frames, commit path) directions. Unit-tested independently of the shell. No REAPER or VST3 types at the boundary.velocity_curve— Phase S S-VIEW-9 pure velocity→amp transfer curve:VelocityCurve— an x-ordered list ofVelocityPointknots evaluated by a Fritsch–Carlson monotone cubic Hermite spline (no overshoot outside [0,1]; collinear knots reduce to the exact linear ramp);eval(velocity)called once per note-on inVoice::start(); control-point editing (addPoint/movePoint/deletePoint) with box-clamp and x-order invariant; hit-test (pointAtPixel) + drag inverse-map (resolvePointDrag);flat()default (y=1, every velocity→unity — zones-payload v7 default) andlinear()(y=velocity/127, retained for tests). Replaces the prior fixedvelocity/127engine path; default flat y=1 is a deliberate non-back-compat behavior change (Daniel-approved). 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 VST3SingleComponentEffectshell: declares an event-input bus + stereo audio output bus, marshals host MIDI note-on/off into the VoiceEngine, renders audio via the loadedsample_mapzones; owns the off-audio-threadreloadFromBank+ atomic pointer swap so process() does no allocation, no file I/O, no bridge calls.reasampler_editor— Phase S VST3IPlugViewLICE editor shell (S10): subclassesCPluginView, 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): implementsIReaperUIEmbedInterfaceso 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 toembed_strip.vst_entry— Phase S VST3 entry point:GetPluginFactoryexport, class registration, channel-forked class UIDs (S18).
REAPER extension contract (src/main.cpp)
- Exactly one translation unit defines
REAPERAPI_IMPLEMENT— that ismain.cpp. Every other.cppincludesreaper_plugin_functions.hwithout the define and getsexterndeclarations for the global API function pointers. - REAPER dlopen()s any
reaper_*.dll|dylib|sofound inUserPlugins/and calls theReaperPluginEntryexport (produced byREAPER_PLUGIN_ENTRYPOINT).rec->GetFuncresolves API pointers;rec->Registerplugs extension callbacks in. - Action registration pattern (preserve this for all new actions):
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 viachannelCommandId(suffix)andchannelActionName(phrase)fromapp_version— the FOREVER-STABLE contract applies per channel (stable and beta each have their own permanent id family).rec->Register("gaccel", &accel)— puts the action in the Actions list.rec->Register("hookcommand", ...)— receives every action fired; claim only your own id, returnfalseotherwise.- 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.