# ReaSampler — implementation briefing A native C++ REAPER extension that captures any arbitrary audio source into a per-project **sample bank** (cached files + a docked grid), decoupled from the arrange view, with keyboard/MIDI-bindable capture and placement. Built as a precision tool: deterministic, non-destructive, no clutter. This document is the spec. Read the existing scaffold first (`src/main.cpp` is the REAPER<->extension contract; the pure/testable-core split in `src/mpe_model.*` is the pattern to preserve — the MPE model is being replaced, the *discipline* is not). Verify every REAPER API name and signature against `vendor/reaper-sdk/sdk/reaper_plugin_functions.h` before use — the API names in this brief are correct-by-intent but treat them as hints, not gospel, and check argument order/types. ## 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. Placement is a distinct, on-demand action (insert / drag). Any code path that auto-inserts a capture into the timeline violates the entire point of the tool and must be rejected in review. This single rule is why the tool exists. ## Settled decisions - **Capture modes:** offline render (deterministic, the default) AND realtime record (for hardware / performed FX). Both sit behind one capture interface and produce identical bank entries. - **Bank scope:** per-project, travels with the `.rpp`. Files live in a project-relative subfolder; the index persists in project ext state. No absolute paths anywhere in the index. - **Material:** must handle full-mix/stem bounces, chops/one-shots, and single-cycle/wavetable grabs equally. That means exact sample-accurate bounds, explicit tail control, channel-count preservation, and loop/zero-crossing handling all matter from day one. ## Module architecture Preserve the scaffold's split: pure, REAPER-free logic in one set of files (unit-tested outside the DAW via the existing `tests/` + CTest harness), REAPER- facing shells in another. Pure (no REAPER types, fully unit-tested): - `bank_model` — the `Sample` metadata struct and the `BankIndex` (add / remove / query / tier moves / dedup-by-hash) plus JSON serialize/deserialize to a `std::string`. This is the heart; test it hard. - `peaks` — compute waveform min/max bins from raw PCM. Feed it a known signal (sine, ramp) and assert the envelope. We compute our own thumbnails from the captured file rather than depending on REAPER's peak API — we own the file format, so this is simpler, testable, and dependency-free. REAPER-facing: - `capture` — the `ICaptureBackend` interface plus `OfflineRenderBackend` and `RealtimeRecordBackend`. Input: a `CaptureRequest` (source mode, time range, wet/dry, tail, SR/bit-depth/channels, output path). Output: a finished file + a populated `Sample` handed to `bank_model`. - `insert` — placement via `InsertMedia`; conform-to-project-tempo vs literal, as an explicit flag (never silent stretching). - `bank_panel` — the docked grid: LICE-drawn thumbnails, audition, multi-select, keyboard navigation. Reuses the docking setup already in `mpe_view.cpp`. - `persist` — project ext state <-> `bank_model` JSON; project-relative path resolution (resolve bank folder from the current project path). - `actions` — registers the capture/placement/slot action family and routes each to the modules above (the `command_id` + `gaccel` + `hookcommand` pattern from `main.cpp`). ## Data model (sketch — refine in code) `Sample`: id, display name, relative file path, source mode, source range (start/ end in project time + PPQ), track GUID(s) if applicable, wet/dry, channel count, sample rate, length (seconds + musical/beats), capture tempo, optional key, peak/RMS/LUFS, clip flag, tier (scratch | archive), content hash, provenance (parent sample id + FX-chain snapshot string, when resampled from another sample), created timestamp. `BankIndex`: ordered collection of `Sample`, keyed by id; hash lookup for dedup; tier filtering; JSON round-trip. Scratch tier is auto-prunable; archive is kept. ## REAPER API surface (verify all signatures) Offline render (the crux — prototype this first): - Drive render settings with `GetSetProjectInfo` (`RENDER_BOUNDSFLAG`, `RENDER_STARTPOS`, `RENDER_ENDPOS`, `RENDER_TAILFLAG`, `RENDER_TAILMS`, `RENDER_SRATE`, `RENDER_CHANNELS`, `RENDER_SETTINGS` for source = master mix / selected tracks / selected items / time selection, wet vs dry) and `GetSetProjectInfo_String` (`RENDER_FILE`, `RENDER_PATTERN`, `RENDER_FORMAT`). - Trigger a no-dialog render via the appropriate render action / `RENDER_SETTINGS` bit. Confirm the exact command id and the "render without opening dialog" flag against current REAPER — do not assume; test that it runs headless. - Determinism is a hard requirement: two identical requests must produce bit-identical files (enables the null test below). Realtime record: - Standard "resample track" recipe: create a hidden track, set its record mode to record-output (latency-compensated) or route the source to it via a send, arm (`I_RECARM`), `CSurf_OnRecord`, run for the range, `CSurf_OnStop`, then move the recorded source file into the bank and delete the temp track. Verify `I_RECMODE` / `I_RECINPUT` values for output-recording. Sources & metadata: - Time selection: `GetSet_LoopTimeRange`. Razor edits: `GetSetMediaTrackInfo_String(track, "P_RAZOREDITS", ...)`. Tempo: `Master_GetTempo` / `TimeMap2_timeToBeats` / `GetProjectTimeSignature2`. Selected items/tracks: `CountSelectedMediaItems` / `GetSelectedTrack`. Placement: - `InsertMedia(path, mode)` at edit cursor / new track / replace selection (verify mode bits). `SetEditCurPos`. Wrap edits in `Undo_BeginBlock2` / `Undo_EndBlock2`. Persistence & paths: - `SetProjExtState` / `GetProjExtState` (namespace e.g. `"reasampler"`) for the index JSON. Resolve project folder via `EnumProjects` / `GetProjectPathEx`; store the bank under a project-relative subfolder; keep only relative paths in the index. ## Build order (each milestone independently testable) 1. `bank_model` + JSON round-trip + unit tests. (pure — no REAPER) 2. `peaks` + unit tests. (pure) 3. Offline capture of the time-selection master mix to a wav in the project bank folder; add a `Sample`; log it to the console. (the render-driving spike) 4. `persist`: write the index to proj ext state, reload on project open; confirm it survives Save / Save As. (bank travels with the .rpp) 5. `bank_panel`: docked grid with thumbnails, audition, selection. 6. `insert`: "insert selected sample at edit cursor" action via `InsertMedia`. 7. Capture action family: master / selected tracks / selected items / razor area, each with wet-dry and tail options, all registered as bindable actions. 8. `RealtimeRecordBackend` behind the same interface. 9. Slots: "capture to slot N" / "insert slot N", MIDI-bindable (MPC-style). 10. Provenance + "re-capture from source"; null-test verify action. 11. Polish: batch capture (per selected item / per razor area), resample-and-mute- source, conform-on-insert, drag-out to OS. ## Precision invariants (enforce, test) - **Null test:** a dry offline capture of a range, re-inserted at its source position, nulls to silence against the source. Ship this as a verification action; it is the tool's trust anchor. - **Bit-identical repeats:** identical offline requests produce identical files. - **Non-destructive:** capture never mutates source items or tracks (realtime's temp track is created and removed cleanly; 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 index. ## Non-goals / guardrails - No auto-insertion of captures into the arrange (see the load-bearing principle). - Native OS drag-out is deferred to the final milestone — `InsertMedia`-driven placement is the primary path and must work first. - Do not depend on REAPER's peak API for thumbnails; compute from the captured file. - Do not silently time-stretch on insert; conform is opt-in. - Do not trust this brief's API names blindly — verify against the SDK header. ## Open questions to resolve during build - Exact no-dialog render command/flag on the current REAPER build. - Realtime record routing that captures wet master output without altering the user's monitoring. - Audio format for the bank (wav bit depth default; allow float for wavetable fidelity). - Thumbnail cache: recompute vs store peak bins alongside the index.