# 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 + owned-manifest 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).
- `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.
