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:
+70
-61
@@ -35,7 +35,7 @@
|
||||
|
||||
// Persistent action-id prefix for the ReaSampler action family.
|
||||
// Every bindable action (capture / insert / slot / verify) mints its command id
|
||||
// from a string beginning with this prefix, e.g. "CEREBELLUM_REASAMPLER_CAPTURE_MASTER".
|
||||
// from a string beginning with this prefix, e.g. "CEREBELLUM_REASAMPLER_CAPTURE_TRACK".
|
||||
// FOREVER-STABLE once shipped: user keybindings key off these strings, so the
|
||||
// prefix and any minted id must never change after release.
|
||||
#define REASAMPLER_ACTION_PREFIX "CEREBELLUM_REASAMPLER_"
|
||||
@@ -44,17 +44,18 @@
|
||||
REAPER_PLUGIN_HINSTANCE g_hInst = nullptr; // this module's instance handle
|
||||
reaper_plugin_info_t* g_rec = nullptr; // REAPER's dispatch struct
|
||||
|
||||
// ---- Capture action family (three FX scopes) -------------------------------
|
||||
// Three bindable SCOPE actions from captureActionTable() (render_settings, pure):
|
||||
// capture item / track / master. Each infers its range (razor-else-time) and
|
||||
// enforces the FX-scope invariant via FX-bypass-around-render (FxBypassGuard):
|
||||
// ---- Capture action family (two FX scopes) ---------------------------------
|
||||
// Two bindable SCOPE actions from captureActionTable() (render_settings, pure):
|
||||
// capture item / track. Each infers its range (razor-else-time) and enforces the
|
||||
// FX-scope invariant via FX-bypass-around-render (FxBypassGuard):
|
||||
// Item -> take/item FX only (bypass the item's track + ancestors + master).
|
||||
// Track -> item FX + track's own FX (bypass ancestors + master).
|
||||
// Master -> whole chain (bypass nothing).
|
||||
// This REPLACES the retired M7 four-mode family (master / tracks / items / razor).
|
||||
// The retired CAPTURE_TRACKS_WET / CAPTURE_ITEMS_WET / CAPTURE_RAZOR_WET ids are
|
||||
// mirror-unregistered on unload so old keybindings clear cleanly; CAPTURE_MASTER's
|
||||
// id string is preserved.
|
||||
// There is NO master scope — to capture the master you render a track. (The master
|
||||
// track's FX/gain/pan are STILL neutralized for both scopes as the out-of-scope
|
||||
// chain — master is a bypass target, not a capture scope.) The retired M7
|
||||
// CAPTURE_TRACKS_WET / CAPTURE_ITEMS_WET / CAPTURE_RAZOR_WET ids AND the removed
|
||||
// CAPTURE_MASTER / CAPTURE_MASTER_REALTIME ids are mirror-unregistered on unload so
|
||||
// old keybindings clear cleanly.
|
||||
//
|
||||
// The minted command ids parallel the table rows 1:1 (same index). gaccel storage
|
||||
// must outlive registration (REAPER holds each pointer), so both vectors are file-
|
||||
@@ -62,14 +63,18 @@ reaper_plugin_info_t* g_rec = nullptr; // REAPER's dispatch struct
|
||||
static std::vector<int> g_captureCmdIds;
|
||||
static std::vector<gaccel_register_t> g_captureAccels;
|
||||
|
||||
// Retired capture-action command-id strings (M7 four-mode family). Kept ONLY to
|
||||
// mirror-unregister them on unload so a user's stale keybindings are cleaned up.
|
||||
// Never re-register these. CAPTURE_MASTER is NOT here — its id string carries over
|
||||
// to the new master scope action unchanged.
|
||||
// Retired capture-action command-id strings. Kept ONLY to mirror-unregister them on
|
||||
// unload so a user's stale keybindings are cleaned up. Never re-register these.
|
||||
// * The M7 four-mode ids (tracks/items/razor WET).
|
||||
// * CAPTURE_MASTER and CAPTURE_MASTER_REALTIME — the master offline scope and the
|
||||
// master realtime action are REMOVED (capture is now item + track only; realtime
|
||||
// taps the selected track). Their shipped ids are retired so old keybindings clear.
|
||||
static const char* const kRetiredCaptureCmdStrings[] = {
|
||||
"CEREBELLUM_REASAMPLER_CAPTURE_TRACKS_WET",
|
||||
"CEREBELLUM_REASAMPLER_CAPTURE_ITEMS_WET",
|
||||
"CEREBELLUM_REASAMPLER_CAPTURE_RAZOR_WET",
|
||||
"CEREBELLUM_REASAMPLER_CAPTURE_MASTER",
|
||||
"CEREBELLUM_REASAMPLER_CAPTURE_MASTER_REALTIME",
|
||||
};
|
||||
|
||||
// Command id for "ReaSampler: toggle bank panel" (M5). FOREVER-STABLE string.
|
||||
@@ -85,12 +90,13 @@ static int g_cmdToggleBankPanel = 0;
|
||||
static int g_cmdInsertSelected = 0;
|
||||
static int g_cmdInsertSelectedConform = 0;
|
||||
|
||||
// Command id for the M8 "capture master (realtime)" action. FOREVER-STABLE string.
|
||||
// Records the master output in realtime (transport-driven) into a hidden temp track
|
||||
// via RealtimeRecordBackend, then moves the recorded file into the bank. The
|
||||
// realtime SIBLING of the offline CAPTURE_MASTER scope action: same range logic
|
||||
// (razor-else-time), same bank/persist path, different backend. Dialog-free.
|
||||
static int g_cmdCaptureMasterRealtime = 0;
|
||||
// Command id for the "capture selected track (realtime)" action. NEW FOREVER-STABLE
|
||||
// string. Records the selected track's OWN output in realtime (transport-driven) into
|
||||
// a hidden temp track via RealtimeRecordBackend, then moves the recorded file into the
|
||||
// bank. The realtime SIBLING of the offline CAPTURE_TRACK scope action: same range
|
||||
// logic (razor-else-time), same track selection, same bank/persist path, different
|
||||
// backend. Dialog-free. (Replaces the removed CAPTURE_MASTER_REALTIME action.)
|
||||
static int g_cmdCaptureTrackRealtime = 0;
|
||||
|
||||
// Command id for the M8 "cancel realtime capture" action. FOREVER-STABLE string.
|
||||
// Aborts the in-flight realtime capture (stop + restore, non-destructive) so a user
|
||||
@@ -223,12 +229,13 @@ static void OnTimer()
|
||||
// range); the caller reports it and writes nothing.
|
||||
|
||||
// The resolved source: exact bounds + the source tracks (for FX-bypass + Sample
|
||||
// provenance GUIDs). `sourceTracks` is empty for Master scope.
|
||||
// provenance GUIDs). `sourceTracks` holds the item-owning tracks (Item scope) or the
|
||||
// selected tracks (Track scope).
|
||||
struct ResolvedSource
|
||||
{
|
||||
double startSeconds = 0.0;
|
||||
double endSeconds = 0.0;
|
||||
std::vector<MediaTrack*> sourceTracks; // item's/selected tracks; empty for master
|
||||
std::vector<MediaTrack*> sourceTracks; // item-owning tracks / selected tracks
|
||||
std::vector<std::string> trackGuids; // canonical GUIDs of sourceTracks
|
||||
};
|
||||
|
||||
@@ -325,8 +332,8 @@ static bool collectSelectedItemTracks(ResolvedSource& out)
|
||||
return !out.sourceTracks.empty();
|
||||
}
|
||||
|
||||
// Resolves the source for a scope: the selection tracks (item/track) or none
|
||||
// (master), plus the inferred range. Returns false with a reason on nothing to do.
|
||||
// Resolves the source for a scope: the selection tracks (item/track), plus the
|
||||
// inferred range. Returns false with a reason on nothing to do.
|
||||
static bool ResolveScopeSource(reasampler::CaptureScope scope,
|
||||
ResolvedSource& out, std::string& why)
|
||||
{
|
||||
@@ -343,8 +350,6 @@ static bool ResolveScopeSource(reasampler::CaptureScope scope,
|
||||
why = "select at least one track"; return false;
|
||||
}
|
||||
break;
|
||||
case CaptureScope::Master:
|
||||
break; // whole chain — no source-track collection
|
||||
}
|
||||
return resolveRange(out.startSeconds, out.endSeconds, why);
|
||||
}
|
||||
@@ -359,7 +364,7 @@ static bool ResolveScopeSource(reasampler::CaptureScope scope,
|
||||
// neutralize set is IDENTICAL to the FX-bypass set:
|
||||
// Item -> own track + all ancestors + master (take vol/pan kept: item content).
|
||||
// Track -> all ancestors + master (selected track's OWN vol/pan kept).
|
||||
// Master-> nothing (full chain, unchanged).
|
||||
// (Master is a bypass TARGET for both scopes — never a scope of its own.)
|
||||
//
|
||||
// Per track in that set we snapshot & set the full parent-chain-independence set,
|
||||
// so a Track/Item capture is uncolored by the parent/folder/master it renders
|
||||
@@ -545,22 +550,23 @@ static void RunCapture(const reasampler::CaptureActionDef& def)
|
||||
ShowConsoleMsg(log.c_str());
|
||||
}
|
||||
|
||||
// STARTS the M8 REALTIME master capture and returns immediately — the record runs
|
||||
// across timer ticks (DriveRealtimeCapture), so REAPER's UI stays responsive. Infers
|
||||
// the range (razor-else-time, the same orthogonal range logic as the offline scopes)
|
||||
// and starts recording the master output into a hidden temp track via
|
||||
// RealtimeRecordBackend::begin; OnTimer drives it to completion, then adds the Sample
|
||||
// and persists. MASTER scope only this increment (track/item realtime routing is a
|
||||
// surfaced fork — see capture_realtime.cpp §FORK). Dialog-free. Non-bit-identical by
|
||||
// nature (it is realtime) — offline stays the deterministic default. FxBypassGuard is
|
||||
// NOT used here (it neutralizes the live chain, altering the user's monitoring). The
|
||||
// STARTS the REALTIME track capture and returns immediately — the record runs across
|
||||
// timer ticks (DriveRealtimeCapture), so REAPER's UI stays responsive. Resolves the
|
||||
// selected tracks + the range (razor-else-time, the same orthogonal range logic as the
|
||||
// offline scopes) and starts recording each selected track's OWN output into a hidden
|
||||
// temp track via RealtimeRecordBackend::begin (a send FROM each source track INTO the
|
||||
// temp — see capture_realtime.cpp §TAP); OnTimer drives it to completion, then adds the
|
||||
// Sample and persists. TRACK scope only this increment (item realtime is deferred).
|
||||
// Dialog-free. Non-bit-identical by nature (it is realtime) — offline stays the
|
||||
// deterministic default. FxBypassGuard is NOT used here — the track-output tap is
|
||||
// PRE-parent by construction (§TAP), so there is no live chain to neutralize. The
|
||||
// load-bearing principle holds structurally — this writes a file + a bank entry ONLY;
|
||||
// the temp track is a transient sink removed by the backend, nothing lands in arrange.
|
||||
//
|
||||
// A SECOND realtime capture requested while one is in progress is REJECTED — the
|
||||
// first keeps running (we own the transport for its window; starting a second would
|
||||
// collide on the transport and the temp-track/arm snapshot).
|
||||
static void RunCaptureRealtimeMaster()
|
||||
static void RunCaptureRealtimeTrack()
|
||||
{
|
||||
if (g_rtCapture)
|
||||
{
|
||||
@@ -569,29 +575,32 @@ static void RunCaptureRealtimeMaster()
|
||||
return;
|
||||
}
|
||||
|
||||
double start = 0.0, end = 0.0;
|
||||
// Resolve the selected tracks + range exactly as the offline Track scope does.
|
||||
// No track selected -> refuse (same no-op as offline track scope).
|
||||
ResolvedSource src;
|
||||
std::string why;
|
||||
if (!resolveRange(start, end, why))
|
||||
if (!ResolveScopeSource(reasampler::CaptureScope::Track, src, why))
|
||||
{
|
||||
ShowConsoleMsg(("ReaSampler realtime capture: " + why + ".\n").c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
reasampler::CaptureRequest req;
|
||||
req.sourceMode = reasampler::SourceMode::MasterMix; // realtime master scope
|
||||
req.startSeconds = start; // exact bounds — no rounding
|
||||
req.endSeconds = end;
|
||||
req.wetDry = 1.0; // fully wet (post-fader tap)
|
||||
req.sourceMode = reasampler::SourceMode::SelectedTracks; // realtime track scope
|
||||
req.startSeconds = src.startSeconds; // exact bounds — no rounding
|
||||
req.endSeconds = src.endSeconds;
|
||||
req.wetDry = 1.0; // fully wet (post-fader tap)
|
||||
req.renderTail = false;
|
||||
req.tailMs = 0.0;
|
||||
req.sampleRate = 0; // follow project rate
|
||||
req.sampleRate = 0; // follow project rate
|
||||
req.channelCount = 2;
|
||||
req.bitDepth = reasampler::WavBitDepth::Float32;
|
||||
req.baseName = "realtime";
|
||||
// No trackGuids — master scope is not track-provenanced.
|
||||
req.trackGuids = src.trackGuids; // provenance on the Sample
|
||||
|
||||
reasampler::CaptureResult failure;
|
||||
reasampler::RealtimeCaptureHandle st = g_rtBackend.begin(req, failure);
|
||||
reasampler::RealtimeCaptureHandle st =
|
||||
g_rtBackend.begin(req, src.sourceTracks, failure);
|
||||
if (!st)
|
||||
{
|
||||
// begin() validated/failed and already restored anything it touched.
|
||||
@@ -685,7 +694,7 @@ static bool OnHookCommand(int command, int /*flag*/)
|
||||
if (command == g_cmdToggleBankPanel) { reasampler::bankPanelToggle(); return true; }
|
||||
if (command == g_cmdInsertSelected) { RunInsertSelected(false); return true; }
|
||||
if (command == g_cmdInsertSelectedConform) { RunInsertSelected(true); return true; }
|
||||
if (command == g_cmdCaptureMasterRealtime) { RunCaptureRealtimeMaster(); return true; }
|
||||
if (command == g_cmdCaptureTrackRealtime) { RunCaptureRealtimeTrack(); return true; }
|
||||
if (command == g_cmdCancelRealtime) { RunCancelRealtime(); return true; }
|
||||
// Design View action family (D4). Claims only its own ids; returns false for the
|
||||
// rest so this hook keeps looking (per the contract).
|
||||
@@ -707,7 +716,7 @@ static int OnToggleAction(int command)
|
||||
static gaccel_register_t g_accelToggleBankPanel{};
|
||||
static gaccel_register_t g_accelInsertSelected{};
|
||||
static gaccel_register_t g_accelInsertSelectedConform{};
|
||||
static gaccel_register_t g_accelCaptureMasterRealtime{};
|
||||
static gaccel_register_t g_accelCaptureTrackRealtime{};
|
||||
static gaccel_register_t g_accelCancelRealtime{};
|
||||
|
||||
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
@@ -740,9 +749,9 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
g_rec->Register("-gaccel", (void*)&g_accelCancelRealtime);
|
||||
g_rec->Register("-command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "CANCEL_REALTIME_CAPTURE"));
|
||||
g_rec->Register("-gaccel", (void*)&g_accelCaptureMasterRealtime);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelCaptureTrackRealtime);
|
||||
g_rec->Register("-command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "CAPTURE_MASTER_REALTIME"));
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "CAPTURE_TRACK_REALTIME"));
|
||||
g_rec->Register("-gaccel", (void*)&g_accelInsertSelectedConform);
|
||||
g_rec->Register("-command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "INSERT_SELECTED_CONFORM"));
|
||||
@@ -853,19 +862,19 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
rec->Register("gaccel", (void*)&g_accelInsertSelectedConform);
|
||||
}
|
||||
|
||||
// Register the M8 "capture master (realtime)" action (command_id -> gaccel ->
|
||||
// hookcommand). Realtime sibling of the offline CAPTURE_MASTER scope: records
|
||||
// the master output in realtime into a hidden temp track, moves it into the
|
||||
// bank. Dialog-free. FOREVER-STABLE id string.
|
||||
g_cmdCaptureMasterRealtime = rec->Register(
|
||||
// Register the "capture selected track (realtime)" action (command_id -> gaccel ->
|
||||
// hookcommand). Realtime sibling of the offline CAPTURE_TRACK scope: records the
|
||||
// selected track's own output in realtime into a hidden temp track, moves it into
|
||||
// the bank. Dialog-free. NEW FOREVER-STABLE id string.
|
||||
g_cmdCaptureTrackRealtime = rec->Register(
|
||||
"command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "CAPTURE_MASTER_REALTIME"));
|
||||
if (g_cmdCaptureMasterRealtime)
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "CAPTURE_TRACK_REALTIME"));
|
||||
if (g_cmdCaptureTrackRealtime)
|
||||
{
|
||||
g_accelCaptureMasterRealtime.accel.cmd = g_cmdCaptureMasterRealtime;
|
||||
g_accelCaptureMasterRealtime.desc =
|
||||
"ReaSampler: capture master (realtime)";
|
||||
rec->Register("gaccel", (void*)&g_accelCaptureMasterRealtime);
|
||||
g_accelCaptureTrackRealtime.accel.cmd = g_cmdCaptureTrackRealtime;
|
||||
g_accelCaptureTrackRealtime.desc =
|
||||
"ReaSampler: capture selected track (realtime)";
|
||||
rec->Register("gaccel", (void*)&g_accelCaptureTrackRealtime);
|
||||
}
|
||||
|
||||
// Cancel-in-flight sibling: aborts a running realtime capture (stop + restore).
|
||||
|
||||
Reference in New Issue
Block a user