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
+13 -38
View File
@@ -1,5 +1,5 @@
// Standalone tests for reasampler::render_settings — no REAPER, no framework.
// Covers the pure pieces behind the three-scope capture family: the source-mode ->
// Covers the pure pieces behind the two-scope capture family: the source-mode ->
// RENDER_SETTINGS bit mapping, P_RAZOREDITS parsing -> ranges + union, scope ->
// source mode, range inference (razor-else-time), the FX-bypass plan (corrects
// the "items captured through parent FX" defect), and the capture-action taxonomy
@@ -111,15 +111,14 @@ static void testRazorUnionBounds() {
// --- sourceModeForScope: scope -> render source mode -------------------------
static void testScopeSourceModes() {
// Each scope drives a distinct render source. Item -> items, Track -> tracks,
// Master -> master mix. These feed renderSettingsFor and must be supported.
// Each scope drives a distinct render source. Item -> items, Track -> tracks.
// (There is no master scope — to capture the master you render a track.) These
// feed renderSettingsFor and must be supported.
CHECK(sourceModeForScope(CaptureScope::Item) == SourceMode::SelectedItems);
CHECK(sourceModeForScope(CaptureScope::Track) == SourceMode::SelectedTracks);
CHECK(sourceModeForScope(CaptureScope::Master) == SourceMode::MasterMix);
// Every scope's source mode is an offline-supported render source.
CHECK(renderSettingsFor(sourceModeForScope(CaptureScope::Item), 1.0).supported);
CHECK(renderSettingsFor(sourceModeForScope(CaptureScope::Track), 1.0).supported);
CHECK(renderSettingsFor(sourceModeForScope(CaptureScope::Master), 1.0).supported);
}
// --- inferRangeSource: razor-else-time (orthogonal to scope) -----------------
@@ -144,29 +143,22 @@ static void testItemScopeBypassesEverythingButTake() {
static void testTrackScopeKeepsSelfBypassesAncestorsAndMaster() {
// Track = item FX + the track's OWN FX. Keep self FX; bypass ancestors + master.
// Master stays a bypass target even though it is no longer a capture scope.
FxBypassPlan p = fxBypassPlanFor(CaptureScope::Track);
CHECK(!p.bypassSelfFx); // the whole point: the track's own FX stays live
CHECK(p.bypassAncestorFx); // no parent/folder FX
CHECK(p.bypassMaster); // no master FX
}
static void testMasterScopeBypassesNothing() {
// Master = whole chain. Nothing bypassed — the full signal path renders.
FxBypassPlan p = fxBypassPlanFor(CaptureScope::Master);
CHECK(!p.bypassSelfFx);
CHECK(!p.bypassAncestorFx);
CHECK(!p.bypassMaster);
}
// --- captureActionTable: the two-scope taxonomy ------------------------------
// --- captureActionTable: the three-scope taxonomy ----------------------------
static void testTableHasThreeScopeRows() {
static void testTableHasTwoScopeRows() {
const auto& table = captureActionTable();
// Exactly 3 scope rows: item, track, master.
CHECK(table.size() == 3);
// Exactly 2 scope rows: item, track. There is no master scope.
CHECK(table.size() == 2);
std::set<std::string> ids;
bool sawItem = false, sawTrack = false, sawMaster = false;
bool sawItem = false, sawTrack = false;
for (const auto& def : table) {
// Every id is a non-empty CEREBELLUM_REASAMPLER_ string and is UNIQUE
// (duplicate ids would collide on registration).
@@ -176,26 +168,11 @@ static void testTableHasThreeScopeRows() {
// Every scope resolves to a supported offline source.
CHECK(renderSettingsFor(sourceModeForScope(def.scope), 1.0).supported);
if (def.scope == CaptureScope::Item) sawItem = true;
if (def.scope == CaptureScope::Track) sawTrack = true;
if (def.scope == CaptureScope::Master) sawMaster = true;
if (def.scope == CaptureScope::Item) sawItem = true;
if (def.scope == CaptureScope::Track) sawTrack = true;
}
CHECK(sawItem);
CHECK(sawTrack);
CHECK(sawMaster);
}
static void testMasterCommandIdIsPreserved() {
// The master scope keeps its shipped M7 id string (user keybindings depend on
// it). Item/track mint NEW ids; master's must be exactly the old value.
bool foundMaster = false;
for (const auto& def : captureActionTable())
if (def.scope == CaptureScope::Master) {
CHECK(std::string(def.commandString) ==
"CEREBELLUM_REASAMPLER_CAPTURE_MASTER");
foundMaster = true;
}
CHECK(foundMaster);
}
int main() {
@@ -213,9 +190,7 @@ int main() {
testRangeInference();
testItemScopeBypassesEverythingButTake();
testTrackScopeKeepsSelfBypassesAncestorsAndMaster();
testMasterScopeBypassesNothing();
testTableHasThreeScopeRows();
testMasterCommandIdIsPreserved();
testTableHasTwoScopeRows();
if (g_fail == 0) std::printf("render_settings: all tests passed\n");
else std::printf("render_settings: %d CHECK(s) FAILED\n", g_fail);