Files
reasampler/PLAN.md
T
daniel 0b19d59f68 Add PLAN.md and TODO.md planning artifacts
PLAN.md: living 12-milestone roadmap from CONTEXT.md's build order,
each with a verify gate and its precision invariant; open questions
carried forward. TODO.md: near-term M0-M2 slice as ordered tasks.
2026-07-21 20:54:03 -04:00

11 KiB

PLAN.md — ReaSampler milestone roadmap

Living milestone roadmap for ReaSampler. Derived from CONTEXT.md's 11-step build order; CONTEXT.md remains the authoritative spec — this file is the tickable checklist, not a re-statement of the spec. When a point lands, doc-keeper removes it here and appends it to COMPLETED.md.

Conventions

  • One checkbox - [ ] = one discrete, independently-landable point.
  • Each milestone opens with a Goal (one line) and a Verify criterion (the acceptance gate; precision invariants pulled in where one applies).
  • Verify-in-DAW points require a manual REAPER run; pure points are gated by CTest.
  • "See CONTEXT.md §…" points at the authoritative detail — do not duplicate it here.

Milestone 0 — Transition scaffold: reaper_mpeview → ReaSampler

Goal: Retire the MPE scaffold and stand up the sampler's pure core in its place, preserving the pure-core / REAPER-shell split. Verify: cmake -B build -S . configures clean; cmake --build build builds the renamed extension target and the pure-core test target; ctest --test-dir build is green with the new bank_model + peaks suites present.

  • Delete src/mpe_model.{h,cpp} and src/mpe_view.{h,cpp}; remove tests/test_mpe_model.cpp.
  • Rename the CMake project() and the extension MODULE target from reaper_mpeview to reaper_reasampler (binary OUTPUT_NAME likewise); update PREFIX "" / platform SUFFIX blocks to the new target name.
  • Replace the pure mpe_model static lib + mpe_model_tests executable with bank_model (pure static lib) + bank_model_tests; keep the CTest wiring.
  • Repoint src/main.cpp: drop the mpe_view.h include and all MpeView_* calls (toggle / IsOpen / OnTimer / Cleanup); stub the extension entry so it loads, logs to console, and registers nothing MPE-specific. The command_id / gaccel / hookcommand registration pattern is preserved for reuse (CLAUDE.md §REAPER extension contract) — the MPE action string is removed.
  • Choose and record the persistent action-id prefix for the sampler family (replaces CEREBELLUM_MPEVIEW_TOGGLE); this string is forever-stable once shipped (CLAUDE.md §action registration).
  • Refresh README.md layout/next-step sections to the sampler module set. (Landed-work reflection is doc-keeper's; this point exists so the stale MPE README does not mislead the first implementer.)

After Milestone 0, Milestones 1 and 2 below are the pure-core heart. They are also the near-term slice tracked in TODO.md.

Milestone 1 — bank_model + JSON round-trip (pure)

Goal: The Sample metadata struct and BankIndex (add / remove / query / tier moves / dedup-by-hash) with JSON serialize/deserialize to std::string. CONTEXT.md §Data model, §Module architecture. Verify: CTest green. Round-trip is lossless (deserialize(serialize(x)) == x) across all fields; dedup-by-hash and tier filtering asserted; relative paths only invariant enforced at the model boundary (no absolute path accepted/stored).

  • Define Sample with the full field set (id, display name, relative path, source mode, source range in project time + PPQ, track GUID(s), wet/dry, channels, SR, length sec + beats, capture tempo, optional key, peak/RMS/LUFS, clip flag, tier, content hash, provenance, created ts). CONTEXT.md §Data model.
  • BankIndex: ordered collection keyed by id; add / remove / query.
  • Hash lookup for dedup-by-content-hash.
  • Tier model (scratch | archive) + tier-move + tier filtering; scratch marked auto-prunable.
  • JSON serialize/deserialize to/from std::string.
  • Tests: full-field round-trip lossless; dedup collapses equal-hash adds; tier filter/move correct; relative-path invariant rejects absolute paths; empty-index and malformed-JSON edge cases.

Milestone 2 — peaks (pure)

Goal: Compute waveform min/max bins from raw PCM, dependency-free (not REAPER's peak API). CONTEXT.md §Module architecture, §Non-goals. Verify: CTest green. Fed a known signal (full-scale sine, ramp), asserted min/max envelope per bin matches expected within tolerance; channel count preserved; bin count honored for arbitrary sample lengths (incl. remainder bin).

  • Min/max bin computation from interleaved PCM given a target bin count.
  • Multi-channel handling (per-channel envelope; no silent fold).
  • Tests: sine envelope ≈ ±amplitude; ramp envelope monotonic; DC/silence → zero envelope; short-buffer and non-divisible-length edge cases.

Milestone 3 — Offline capture spike (REAPER shell)

Goal: Offline-render the time-selection master mix to a wav in the project bank folder, add a Sample, log it. The render-driving spike. CONTEXT.md §REAPER API surface (offline render), Build order 3. Verify (in DAW): Headless (no-dialog) render runs; file lands in the project-relative bank folder; a Sample is added to the in-memory BankIndex. Bit-identical repeats: two identical requests produce byte-identical files. Exact bounds: rendered length matches the requested range (no rounding, no added silence without an explicit tail).

  • ICaptureBackend interface + CaptureRequest (source mode, time range, wet/dry, tail, SR/bit-depth/channels, output path). CONTEXT.md §capture.
  • OfflineRenderBackend: drive GetSetProjectInfo render settings + GetSetProjectInfo_String file/pattern/format; verify every flag against vendor/reaper-sdk/sdk/reaper_plugin_functions.h.
  • Resolve the no-dialog render command/flag on the current REAPER build (open question) and confirm it runs headless.
  • Populate a Sample from the finished file; hand to bank_model; console-log.
  • Verify bit-identical repeats and exact-bounds by hand on a known range.

Milestone 4 — persist (index ↔ project ext state)

Goal: Write the BankIndex JSON to project ext state, reload on project open; project-relative path resolution. CONTEXT.md §persist, §Persistence & paths. Verify (in DAW): Index survives Save / Save As / close+reopen; bank travels with the .rpp; relative paths only in the persisted index (Save As to a new folder still resolves the bank).

  • SetProjExtState / GetProjExtState under namespace "reasampler".
  • Bank-folder resolution from the current project path (EnumProjects / GetProjectPathEx); store under a project-relative subfolder.
  • Reload-on-open; confirm survival across Save / Save As.

Milestone 5 — bank_panel (docked grid)

Goal: Docked LICE-drawn grid: thumbnails (from peaks), audition, multi-select, keyboard navigation. Reuses the docking setup from the retired mpe_view.cpp. CONTEXT.md §bank_panel. Verify (in DAW): Grid docks; thumbnails render from computed peaks; audition plays selected sample; multi-select + keyboard nav work.

  • Docked window + LICE grid render loop.
  • Thumbnail draw from peaks bins.
  • Audition (play selected sample) + stop.
  • Multi-select + keyboard navigation.

Milestone 6 — insert (placement)

Goal: "Insert selected sample at edit cursor" via InsertMedia. CONTEXT.md §insert, Build order 6. Verify (in DAW): Selected sample inserts at the edit cursor wrapped in Undo_BeginBlock2 / Undo_EndBlock2; conform-to-tempo is an explicit flag — no silent time-stretch when off.

  • InsertMedia(path, mode) at edit cursor (verify mode bits against SDK).
  • Conform-to-project-tempo vs literal as an explicit flag (never silent).
  • Undo-block wrapping.

Milestone 7 — capture action family

Goal: Bindable capture actions for master / selected tracks / selected items / razor area, each with wet-dry + tail options. CONTEXT.md §actions, Build order 7. Verify (in DAW): Each action registered (bindable in Actions list), routes to the offline backend, and honors wet/dry + tail. Load-bearing principle: none auto-inserts into the arrange.

  • Source resolvers: master mix, selected tracks, selected items, razor area (GetSet_LoopTimeRange, P_RAZOREDITS, CountSelectedMediaItems, etc.).
  • Register each as a bindable action (command_id/gaccel/hookcommand).
  • Wet-dry + tail options per action.
  • Review gate: confirm no capture path touches the timeline.

Milestone 8 — RealtimeRecordBackend

Goal: Realtime record behind the same ICaptureBackend, producing identical bank entries. CONTEXT.md §capture (realtime), §Precision invariants. Verify (in DAW): Hidden temp track resamples wet output; recorded file moves into the bank; non-destructive — temp track removed cleanly, source routing and user monitoring restored unchanged.

  • Hidden-track resample recipe (I_RECMODE/I_RECINPUT/I_RECARM, CSurf_OnRecord/CSurf_OnStop); verify record-mode values against SDK.
  • Resolve wet-master routing that does not alter user monitoring (open question).
  • Move recorded source into bank; populate identical Sample; clean teardown.

Milestone 9 — slots (MPC-style)

Goal: "Capture to slot N" / "insert slot N", MIDI-bindable. CONTEXT.md Build order 9. Verify (in DAW): Slot capture and slot insert fire from MIDI bindings; slot state persists via the index.

  • Slot model + slot↔sample assignment.
  • "Capture to slot N" / "insert slot N" actions, MIDI-bindable.

Milestone 10 — provenance + null-test verify action

Goal: Provenance (parent sample id + FX-chain snapshot) and "re-capture from source"; ship the null-test verification action. CONTEXT.md §Precision invariants, Build order 10. Verify (in DAW): Null test — a dry offline capture of a range, re-inserted at its source position, nulls to silence against the source. This action is the tool's trust anchor and must pass.

  • Provenance fields populated on resample-from-sample (parent id + FX-chain snapshot string).
  • "Re-capture from source" action.
  • Null-test verification action (capture → re-insert at source pos → assert silence sum).

Milestone 11 — polish

Goal: Batch capture (per selected item / per razor area), resample-and-mute-source, conform-on-insert, native OS drag-out. CONTEXT.md Build order 11, §Non-goals (drag-out deferred to last). Verify (in DAW): Each polish action works without regressing the precision invariants; drag-out places a valid file in the OS target.

  • Batch capture: per selected item / per razor area.
  • Resample-and-mute-source.
  • Conform-on-insert (explicit).
  • Native OS drag-out (deferred final; InsertMedia path must already work).

Open questions to resolve during build

Carried from CONTEXT.md §Open questions — keep visible until each is closed by a landed milestone.

  • No-dialog render command/flag on the current REAPER build. (blocks M3)
  • Realtime wet-master routing that captures master output without altering the user's monitoring. (blocks M8)
  • Bank audio format: wav bit-depth default; allow float for wavetable fidelity. (touches M3, informs M1 Sample fields)
  • Thumbnail cache: recompute peaks vs store peak bins alongside the index. (touches M2/M4/M5)