10 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. The MPE modules (mpe_model, mpe_view) have been removed. M0–M4 are complete: bank_model (Sample metadata + BankIndex with JSON round-trip), peaks (waveform min/max bin computation), the capture shell's offline-render path (DAW-verified spike: master-mix / time-selection capture to a project-relative bank at 32-bit float WAV, non-destructive), and persist (BankIndex ↔ project ext state: SetProjExtState/GetProjExtState under namespace "reasampler", GUID-based project identity, project-relative path resolution, Save-As bank relocation with copy semantics) are all implemented and tested. The remaining REAPER-facing shells (insert, bank_panel, actions) and the realtime backend are being built out per CONTEXT.md / PLAN.md. 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 two submodules (see .gitmodules):
vendor/reaper-sdk—sdk/reaper_plugin.h,sdk/reaper_plugin_functions.h, SWELL headersvendor/WDL— WDL utilities and the SWELL cross-platform Win32 layer
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_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. |
reaper_reasampler |
loadable module | The actual extension binary (.dll / .dylib / .so). |
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.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.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).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.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.
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.persist— project ext state (SetProjExtState/GetProjExtState, namespace"reasampler") ↔BankBookJSON ("banks"key) +ViewModeModelJSON ("view_state"key) +TailSettingJSON ("tail_setting"key) +OwnedManifestJSON ("owned_files"key); 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.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.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), and the multi-bank action family (create/rename/reorder/delete bank, evacuate, activate, move/copy/remove selected samples); 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.
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.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.