From ad40c561bf9dba317b0c0aec81bfeb1d8baf666d Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Tue, 21 Jul 2026 20:48:01 -0400 Subject: [PATCH] Add CLAUDE.md project guidance --- CLAUDE.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e173690 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,77 @@ +# 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 on-disk scaffold is named `reaper_mpeview` (CMake project name, binary prefix, source filenames). CONTEXT.md repurposes it into **ReaSampler** — a per-project audio sample-bank capture tool. The existing MPE modules (`mpe_model`, `mpe_view`) are being **replaced** by the sampler module set (`bank_model`, `peaks`, `capture`, `insert`, `bank_panel`, `persist`, `actions`). The *discipline* — pure REAPER-free testable core split from REAPER-facing shells — is being **preserved**, not replaced. When the code says "mpe" and the spec says "sampler", this is why. + +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 + +Two targets: + +| Target | Kind | Purpose | +|---|---|---| +| `mpe_model_tests` | executable | Pure unit tests — no REAPER, no DAW. This is the fast iteration loop. | +| `reaper_mpeview` | 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`.