# 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. The pure core (M0–M2) is complete: `bank_model` (Sample metadata + BankIndex with JSON round-trip) and `peaks` (waveform min/max bin computation) are both implemented and fully tested. The REAPER-facing shells (`capture`, `insert`, `bank_panel`, `persist`, `actions`) 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 headers - `vendor/WDL` — WDL utilities and the SWELL cross-platform Win32 layer ## Build and test cmake -B build -S . cmake --build build ctest --test-dir build Three targets: | 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. | | `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` — `Sample` metadata 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. **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. - `persist` — project ext state (`SetProjExtState` / `GetProjExtState`, namespace `"reasampler"`) ↔ `bank_model` JSON; project-relative path resolution. - `actions` — registers the capture/placement/slot action family and routes each to the modules above. ## 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. 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`.