43ca5e4069
Archive D5 (mode switch) to COMPLETED.md and remove the completed Phase D section from PLAN.md; add mode_switch to CLAUDE.md architecture + targets.
88 lines
7.5 KiB
Markdown
88 lines
7.5 KiB
Markdown
# 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 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
|
||
|
||
Seven 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. |
|
||
| `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. |
|
||
| `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.
|
||
- `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 of `bank_model` for the Design View phase.
|
||
- `view_tree` — pure `I_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 of `bank_grid`.
|
||
|
||
**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 + `ViewModeModel` JSON (`"view_state"` key); project-relative path resolution.
|
||
- `view` — Design View shell: reads the folder tree via `view_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 or `B_MUTE`/`I_SOLO`.
|
||
- `track_guid` — shared `MediaTrack*` → 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 and the Design View action family (toggle active mode, activate Arrange/Design, tag/untag selected tracks, show-both); routes each to the modules above via the `command_id`/`gaccel`/`hookcommand` contract.
|
||
|
||
## 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`.
|