docs(context): reconcile capture spec to two-scope model

Update CONTEXT.md capture sections to two FX scopes (item/track,
chain-independent, no master, always wet): build-order step 7, offline/realtime
render API surface, the capture module sketch, and mark the realtime
wet-master-routing open question resolved (track-tap).
This commit is contained in:
2026-07-23 16:51:33 -04:00
parent 92762e2b6c
commit 68d3a3dba0
+36 -15
View File
@@ -51,9 +51,12 @@ Pure (no REAPER types, fully unit-tested):
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`.
`RealtimeRecordBackend`. Input: a `CaptureRequest` (capture scope — item or
track, time range, tail, SR/bit-depth/channels, output path). Output: a finished
file + a populated `Sample` handed to `bank_model`. Capture is always wet; the FX
*scope* (not a wet/dry dial) is the control — the pure `render_settings` module
maps a scope to its render-source bits and its FX-bypass plan (see §Capture FX
scope below).
- `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,
@@ -81,21 +84,31 @@ tier filtering; JSON round-trip. Scratch tier is auto-prunable; archive is kept.
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
`RENDER_SRATE`, `RENDER_CHANNELS`, `RENDER_SETTINGS`) and
`GetSetProjectInfo_String` (`RENDER_FILE`, `RENDER_PATTERN`, `RENDER_FORMAT`).
The `RENDER_SETTINGS` bit choice is driven by capture scope: **item** scope renders
selected media items (single-file), **track** scope renders selected tracks via
master. There is **no master-mix scope** (to capture the master, render a track).
Render is always wet — REAPER has no true pre-FX "dry" render bit; FX scoping is done
by the FX-bypass-around-render mechanism, not a render bit (see §Capture FX scope).
- 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.
Realtime record (**track scope** — item realtime is deferred):
- Taps the **selected track**, not the master. Recipe: create a hidden temp track,
route a **post-fader send from each selected source track into it**
(`CreateTrackSend`, default post-fader/full-stereo — captures each track's own
output *before* the parent/master sums it, so the tap is chain-independent by
construction and needs no FX bypass), set the temp's record mode to record-output
(latency-compensated, `B_MAINSEND=0` so it does not sum back), arm (`I_RECARM`),
`CSurf_OnRecord`, run for the range, `OnStopButtonEx`, then move the recorded file
into the bank and delete the temp track (which drops the sends — no source track is
left mutated). Verify `I_RECMODE` values for output-recording.
- Master→track sends are refused by REAPER as feedback loops — this is *why* the tap
is the selected track's post-fader output, not the master.
Sources & metadata:
- Time selection: `GetSet_LoopTimeRange`. Razor edits:
@@ -124,8 +137,13 @@ Persistence & paths:
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.
7. Capture action family: two FX scopes — **item** (item/take FX only) and **track**
(item FX + the selected track's own track FX), each chain-independent (the
out-of-scope chain is neutralized to unity for the render). No master scope — to
capture the master, render a track. Each scope captures over a **time-or-razor
range** (razor is a range source, not a mode: razor-when-present, else time
selection). Capture is always wet; the scope is the control. All registered as
bindable actions (tail off; a tail-on variant is a later opt-in).
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.
@@ -157,8 +175,11 @@ Persistence & paths:
## 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.
- ~~Realtime record routing that captures wet master output without altering the
user's monitoring.~~ **Resolved:** realtime taps the *selected track* (track scope),
not the master — a post-fader track→temp send is chain-independent by construction,
so there is no wet-master-routing problem and monitoring is untouched (see the
Realtime record API surface above).
- Audio format for the bank (wav bit depth default; allow float for wavetable
fidelity).
- Thumbnail cache: recompute vs store peak bins alongside the index.