# src/shell/capture — REAPER-facing capture backends and action bodies

## Scope

Everything that turns a capture request into a rendered file + populated `Sample`,
plus the action bodies that drive capture from REAPER's UI/action list: the two
concrete capture backends (offline render, realtime record), scope/source
resolution, the realtime in-flight state machine, single- and batch-capture
orchestration, insert-to-timeline, provenance stamping, and the shared
`MediaItem*`/`MediaTrack*` GUID-read helpers. Pure decision logic (what counts as
an orphan, how a range maps to capture units, etc.) lives in the corresponding
`core/` modules this shell calls into — this directory is the REAPER API surface
only.

## Invariants

This directory implements, but does not restate, the repo-wide capture precision
invariants (null test, bit-identical repeats, non-destructive, exact bounds,
relative-paths-only) and the load-bearing capture/placement separation — see root
`CLAUDE.md` §Precision invariants and §The load-bearing principle. Shell-specific
detail not covered there:

- **FX-bypass guard ordering.** `scope_resolve` reads the M10 provenance-assembly
  inputs (track/item selection, FX-chain identity) BEFORE the FX-bypass guard
  neutralizes the in-scope chain — provenance must see the chain as it really is,
  not as capture temporarily leaves it.
- **Realtime capture drives off REAPER's transport across timer ticks** —
  `capture_realtime_shell` cannot block REAPER's UI for the duration of a realtime
  record, so `begin`/`tick`/`abort` are async by construction and the temp-track +
  send recipe lives in the shell, not the pure core.
- **`RunInsertSelected` is the one deliberate exception to capture-never-places**
  (see `capture_orchestrator` below) — every other capture entry point writes only
  a file + index entry.

## Modules

- `capture` — two CONCRETE backends with deliberately different lifecycles (no shared interface — the former `ICaptureBackend` was deleted in Q-W3, T4-26: one deriver, zero polymorphic call sites): `OfflineRenderBackend` (deterministic default, synchronous) and `RealtimeRecordBackend` (async begin/tick/abort). Input: `CaptureRequest`. Output: finished file + populated `Sample` handed to `bank_model`.
- `scope_resolve` (`shell/capture`) — scope/source resolution shared by every capture entry point (Q-W3 hoist out of `main.cpp`): razor-else-time range inference, selected-track/selected-item-owning-track collection with canonical GUIDs, and the M10 provenance-assembly inputs (read BEFORE the FX-bypass guard neutralizes the in-scope chain).
- `capture_orchestrator` (`shell/capture`) — single-capture orchestration + the realtime/insert action bodies (Q-W3 hoist, T4-02): `renderOffline` (one offline render under the scope's FX-bypass guard), `captureAndIndexOne` (render + provenance stamp + bank add + tracking-ledger record, unpersisted), `RunCapture`/`RunCaptureItemAssign`, `RunCaptureRealtimeTrack`/`RunCancelRealtime` (the realtime action bodies — the in-flight state lives in `realtime_lifecycle`), and `RunInsertSelected` (the ONE deliberate exception to capture-never-places).
- `bake_land` (`shell/capture`) — the EXTENSION's half of the resample chain: scans every open project tab for pending `rsbake_*` requests, lands the ones belonging to the project this session has loaded, and refuses the rest with `WrongProject` — one undo point for the batch, each answered over its own key inside the invoking instance's synchronous action call. It RENDERS NOTHING — the instrument already did, through its own engine in its own process, which is what makes the baked audio the sound the user approved and what keeps the voice engine out of the extension's link graph. Replace-vs-add comes from `tracking::resampleLanding`; a replace keeps the entry's id and slot and never deletes the superseded file. Hash-dedup applies on the add path only, before the disk write, matching `updateSampleInPlace`'s "an in-place refresh is not an insert". A refused index withdraws the bytes this call had just written — the self-cleanup carve-out from prune's deletion authority, stated in `prune_fs.cpp`'s header.
- `capture_batch` (`shell/capture`) — the batch-capture family + re-capture-from-source (Q-W3 hoist, T4-02): `RunBatchCaptureItems` (one sample per selected item), `RunBatchCaptureRazor` (one sample per razor area), `RunRecaptureFromSource` (regenerate a provenanced sample from its recorded source's current state, bank-only). Every unit routes through `capture_orchestrator` so every precision invariant holds; persist is batched to one ext-state write per action.
- `realtime_lifecycle` (`shell/capture`) — the in-flight realtime-capture state machine + globals (Q-W3 hoist): the action starts it, `OnTimer` drives it per tick via `DriveRealtimeCapture` (a single-pointer-test idle fast path — load-bearing hot-path guardrail), `CommitRealtimeResult` lands a finished capture in the bank, `AbortRealtimeCaptureForUnload` tears down cleanly on extension unload.
- `capture_realtime_shell` (`shell/capture`) — the async realtime-record backend surface (Q-W6 split of the former fat `capture.h`): `RealtimeRecordBackend::begin`/`tick`/`abort`, transport-driven across timer ticks (a realtime record cannot block REAPER's UI for its own duration). Deliberately shares NO interface with the offline backend — the lifecycles genuinely differ (the former `ICaptureBackend` interface was deleted in Q-W3, T4-26).
- `capture_realtime_finalize` (`shell/capture`) — the file-side half of the realtime-record shell (Q-W3, T4-08): discovers the file REAPER actually recorded, moves it into the bank, runs the Auto-tail PCM decay-scan trim, and populates the finished `Sample`.
- `insert` — placement via `InsertMedia`. **Conform-to-project-tempo is an explicit opt-in flag, never silent stretching.**
- `provenance_shell` — FX-chain identity queries via `TrackFX_*`/`TakeFX_*` APIs; feeds the pure `provenance` fingerprint builder. Stamps `Sample.provenance` on capture; ambiguous/mixed cases record nothing conservatively.
- `track_guid` — shared `MediaTrack*` → canonical GUID-string formatter; single source of truth for membership keys.
- `item_read` — the ONE place a `MediaItem*` is read for its canonical GUID string (`itemGuid`) and for the durable `P_LANENAME` of the fixed lane it sits on (`itemLaneName`); extracted from previously-duplicated `itemGuid`/`itemLaneName` pairs in `view.cpp` and `bank_panel.cpp` — the item-read analog of `track_guid`'s single `MediaTrack*`→GUID-key formatter. Callers must already know the track is fixed-lane (`I_FREEMODE==2`) before calling `itemLaneName`; the pure `isOnManualLane` predicate handles the non-fixed-lane case separately.

## Gotchas

- This directory's governing precision invariants are the repo-wide capture
  invariants in root `CLAUDE.md`, not a standalone spec block here.
- `capture` and `capture_realtime_shell` deliberately share NO common interface with
  each other (the former `ICaptureBackend` was removed) — do not reintroduce one
  without a real second polymorphic call site.
