Rework capture into three FX-scope actions with inferred range
Replace the four capture modes with item/track/master scope actions. Each infers its range (razor-else-time) and enforces FX scope via non-destructive FX-bypass-around-render (RAII I_FXEN snapshot/restore over ancestors + master). Corrects the defect of items captured through parent FX.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
// Standalone tests for reasampler::render_settings — no REAPER, no framework.
|
||||
// Covers the three pure pieces behind the M7 capture family: the source-mode ->
|
||||
// RENDER_SETTINGS bit mapping (wet-only), P_RAZOREDITS parsing -> ranges + union,
|
||||
// and the capture-action taxonomy table (stable ids, coverage of every mode).
|
||||
// Covers the pure pieces behind the three-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
|
||||
// table (stable ids, one row per scope).
|
||||
|
||||
#include "../src/render_settings.h"
|
||||
|
||||
@@ -24,7 +26,7 @@ static void testMasterMixWet() {
|
||||
CHECK(c.supported);
|
||||
// TimeSelection aliases master mix — same result.
|
||||
CHECK(renderSettingsFor(SourceMode::TimeSelection, 1.0).settings == kRenderMasterMix);
|
||||
// wetDry argument is irrelevant for M7 (all actions are wet); passing 0.0
|
||||
// wetDry argument is irrelevant (all three-scope capture actions are wet); passing 0.0
|
||||
// must still yield the same wet master-mix bits.
|
||||
CHECK(renderSettingsFor(SourceMode::MasterMix, 0.0).settings == kRenderMasterMix);
|
||||
}
|
||||
@@ -77,7 +79,7 @@ static void testParseMultipleAreas() {
|
||||
|
||||
static void testParseSkipsEnvelopeLaneAreas() {
|
||||
// A triple whose GUID is a real {…} is an ENVELOPE-lane area — skipped, since
|
||||
// M7 renders track audio. Only the track-audio triple survives.
|
||||
// razor captures render track audio only. Only the track-audio triple survives.
|
||||
auto r = parseRazorEdits(
|
||||
"1.0 2.0 \"\" 3.0 4.0 {AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}");
|
||||
CHECK(r.size() == 1);
|
||||
@@ -106,40 +108,94 @@ static void testRazorUnionBounds() {
|
||||
CHECK(empty.startSeconds == 0.0 && empty.endSeconds == 0.0);
|
||||
}
|
||||
|
||||
// --- captureActionTable: the taxonomy ----------------------------------------
|
||||
// --- sourceModeForScope: scope -> render source mode -------------------------
|
||||
|
||||
static void testTableHasFourWetOnlyRows() {
|
||||
static void testScopeSourceModes() {
|
||||
// Each scope drives a distinct render source. Item -> items, Track -> tracks,
|
||||
// Master -> master mix. 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) -----------------
|
||||
|
||||
static void testRangeInference() {
|
||||
// Razor present -> razor union wins; no razor -> time selection.
|
||||
CHECK(inferRangeSource(true) == RangeSource::Razor);
|
||||
CHECK(inferRangeSource(false) == RangeSource::TimeSelection);
|
||||
}
|
||||
|
||||
// --- fxBypassPlanFor: the FX-scope invariant ----------------------------------
|
||||
|
||||
static void testItemScopeBypassesEverythingButTake() {
|
||||
// Item = take/item FX ONLY. Bypass the item's own track FX, its ancestors, and
|
||||
// the master. (If this returned bypassSelfFx=false the M7 defect — items heard
|
||||
// through the track's FX — would recur; the assertion pins the fix.)
|
||||
FxBypassPlan p = fxBypassPlanFor(CaptureScope::Item);
|
||||
CHECK(p.bypassSelfFx);
|
||||
CHECK(p.bypassAncestorFx);
|
||||
CHECK(p.bypassMaster);
|
||||
}
|
||||
|
||||
static void testTrackScopeKeepsSelfBypassesAncestorsAndMaster() {
|
||||
// Track = item FX + the track's OWN FX. Keep self FX; bypass ancestors + master.
|
||||
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 three-scope taxonomy ----------------------------
|
||||
|
||||
static void testTableHasThreeScopeRows() {
|
||||
const auto& table = captureActionTable();
|
||||
// Exactly 4 wet-only rows: MASTER, TRACKS_WET, ITEMS_WET, RAZOR_WET.
|
||||
CHECK(table.size() == 4);
|
||||
// Exactly 3 scope rows: item, track, master.
|
||||
CHECK(table.size() == 3);
|
||||
|
||||
std::set<std::string> ids;
|
||||
bool sawMaster = false, sawTracks = false, sawItems = false, sawRazor = false;
|
||||
bool sawItem = false, sawTrack = false, sawMaster = false;
|
||||
for (const auto& def : table) {
|
||||
// Every id is a non-empty CEREBELLUM_REASAMPLER_ string and is UNIQUE
|
||||
// (duplicate ids would collide on registration).
|
||||
std::string id = def.commandString;
|
||||
CHECK(id.rfind("CEREBELLUM_REASAMPLER_", 0) == 0);
|
||||
CHECK(ids.insert(id).second); // false if duplicate
|
||||
// Every row is wet (>=0.5) and a real offline source.
|
||||
CHECK(def.wetDry >= 0.5);
|
||||
CHECK(renderSettingsFor(def.sourceMode, def.wetDry).supported);
|
||||
// Every scope resolves to a supported offline source.
|
||||
CHECK(renderSettingsFor(sourceModeForScope(def.scope), 1.0).supported);
|
||||
|
||||
if (def.sourceMode == SourceMode::MasterMix) sawMaster = true;
|
||||
if (def.sourceMode == SourceMode::SelectedTracks) sawTracks = true;
|
||||
if (def.sourceMode == SourceMode::SelectedItems) sawItems = true;
|
||||
if (def.sourceMode == SourceMode::RazorArea) sawRazor = true;
|
||||
if (def.scope == CaptureScope::Item) sawItem = true;
|
||||
if (def.scope == CaptureScope::Track) sawTrack = true;
|
||||
if (def.scope == CaptureScope::Master) sawMaster = true;
|
||||
}
|
||||
CHECK(sawItem);
|
||||
CHECK(sawTrack);
|
||||
CHECK(sawMaster);
|
||||
CHECK(sawTracks);
|
||||
CHECK(sawItems);
|
||||
CHECK(sawRazor);
|
||||
}
|
||||
|
||||
static void testNoDryRowsInTable() {
|
||||
// M7 ships wet-only. No table row must have wetDry < 0.5.
|
||||
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())
|
||||
CHECK(def.wetDry >= 0.5);
|
||||
if (def.scope == CaptureScope::Master) {
|
||||
CHECK(std::string(def.commandString) ==
|
||||
"CEREBELLUM_REASAMPLER_CAPTURE_MASTER");
|
||||
foundMaster = true;
|
||||
}
|
||||
CHECK(foundMaster);
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -153,8 +209,13 @@ int main() {
|
||||
testParseSkipsEnvelopeLaneAreas();
|
||||
testParseEmptyAndMalformed();
|
||||
testRazorUnionBounds();
|
||||
testTableHasFourWetOnlyRows();
|
||||
testNoDryRowsInTable();
|
||||
testScopeSourceModes();
|
||||
testRangeInference();
|
||||
testItemScopeBypassesEverythingButTake();
|
||||
testTrackScopeKeepsSelfBypassesAncestorsAndMaster();
|
||||
testMasterScopeBypassesNothing();
|
||||
testTableHasThreeScopeRows();
|
||||
testMasterCommandIdIsPreserved();
|
||||
|
||||
if (g_fail == 0) std::printf("render_settings: all tests passed\n");
|
||||
else std::printf("render_settings: %d CHECK(s) FAILED\n", g_fail);
|
||||
|
||||
Reference in New Issue
Block a user