refactor(capture): drop master scope; realtime taps selected track

Capture is now item + track only (master removed as a scope; still bypassed
as out-of-scope chain). Realtime records the selected track's own output via
per-track post-fader sends into a hidden temp track, fixing the silent file.
This commit is contained in:
2026-07-23 16:34:02 -04:00
parent 3791e6c119
commit 4ef41cf705
6 changed files with 215 additions and 192 deletions
+25 -14
View File
@@ -21,6 +21,12 @@
#include "bank_model.h"
// MediaTrack is forward-declared (like track_guid.h) so this header stays
// REAPER-free while RealtimeRecordBackend::begin can take the resolved source
// MediaTrack* to tap. The pointers are opaque here — never dereferenced in a
// pure/header context; only the REAPER-facing capture_realtime.cpp touches them.
class MediaTrack;
namespace reasampler {
// Audio bit-depth for the rendered wav. 32-bit float is the M3 default —
@@ -150,8 +156,9 @@ struct RealtimeTickResult {
};
// The opaque in-flight capture state. Owns the snapshot of everything to restore
// (temp track, other tracks' I_RECARM, master send/routing, transport, edit cursor,
// time selection) and the record's own project handle. Defined in
// (temp track + its receive sends from the source tracks, other tracks' I_RECARM,
// transport, edit cursor, time selection) and the record's own project handle.
// Defined in
// capture_realtime.cpp; the header stays REAPER-free (no MediaTrack*/ReaProject*
// leaks here) by holding it behind a forward-declared type + unique_ptr.
//
@@ -182,21 +189,25 @@ using RealtimeCaptureHandle =
// harder here than offline because the record spans ticks: the snapshot + restore
// live on RealtimeCaptureState, not a function-scope RAII destructor.
//
// SCOPE (this increment): MASTER scope only — records the master-mix output, which
// does not need the FxBypassGuard scope isolation (the whole chain is in scope).
// Track/Item scopes are a genuine routing fork surfaced to Daniel, NOT silently
// built (see capture_realtime.cpp §FORK). A Track/Item request is refused.
// SCOPE (this increment): TRACK scope only — records the selected track's OWN
// output (item + that track's own FX + its own fader/pan, PRE-parent), matching
// offline's track scope. This needs NO FxBypassGuard: a send tapping a track's
// output is naturally PRE-parent (the parent has not summed it yet), so the tap is
// chain-independent by construction. Item realtime is deferred (UnsupportedMode).
class RealtimeRecordBackend {
public:
// Starts a realtime record: validates the request (master scope, non-empty
// range, active + saved project, transport idle), snapshots all state to
// restore, creates the hidden temp track, routes the master send, arms, and
// CSurf_OnRecord — then returns IMMEDIATELY (no wait, no UI block). On success
// the returned unique_ptr owns the in-flight state; drive it with tick(). On a
// validation/setup failure returns nullptr and fills `outFailure` with the
// CaptureStatus + message (nothing was left mutated — begin() restores on its
// own failure paths).
// Starts a realtime record: validates the request (track scope, non-empty range,
// at least one source track, active + saved project, transport idle), snapshots
// all state to restore, creates the hidden temp track, routes a send FROM each
// source track INTO the temp track, arms, and CSurf_OnRecord — then returns
// IMMEDIATELY (no wait, no UI block). `sourceTracks` are the selected tracks to
// tap (resolved by the action layer — the CaptureRequest itself stays REAPER-free,
// carrying only the provenance GUIDs). On success the returned unique_ptr owns the
// in-flight state; drive it with tick(). On a validation/setup failure returns
// nullptr and fills `outFailure` with the CaptureStatus + message (nothing was
// left mutated — begin() restores on its own failure paths).
RealtimeCaptureHandle begin(const CaptureRequest& request,
const std::vector<MediaTrack*>& sourceTracks,
CaptureResult& outFailure);
// Advances the in-flight record one tick. Reads the transport (bound to the