5.4 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 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 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
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—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.
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") ↔bank_modelJSON; 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 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.