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:
2026-07-23 13:12:36 -04:00
parent c78c7e3ddc
commit df03b5e759
7 changed files with 470 additions and 215 deletions
+67 -25
View File
@@ -1,4 +1,4 @@
// render_settings.cpp — pure logic for the M7 capture action family. See header.
// render_settings.cpp — pure logic for the three-scope capture action family. See header.
// NO REAPER types; unit-tested by tests/test_render_settings.cpp.
#include "render_settings.h"
@@ -8,8 +8,9 @@
namespace reasampler {
RenderSettingsChoice renderSettingsFor(SourceMode mode, double /*wetDry*/) {
// All M7 actions are wet-only. `wetDry` is accepted so CaptureRequest.wetDry
// remains the seam for future M10 dry work, but it does not affect the mapping.
// `wetDry` is accepted so CaptureRequest.wetDry remains the seam for future
// dry work (M10 null test), but it does not affect this mapping. FX scoping is
// handled by fxBypassPlanFor, not by these render bits.
RenderSettingsChoice c;
switch (mode) {
@@ -50,6 +51,49 @@ RenderSettingsChoice renderSettingsFor(SourceMode mode, double /*wetDry*/) {
return c;
}
SourceMode sourceModeForScope(CaptureScope scope) {
switch (scope) {
case CaptureScope::Item: return SourceMode::SelectedItems;
case CaptureScope::Track: return SourceMode::SelectedTracks;
case CaptureScope::Master: return SourceMode::MasterMix;
}
return SourceMode::MasterMix; // unreachable for a valid enum; fail to master
}
RangeSource inferRangeSource(bool hasRazorArea) {
// Razor wins when present; otherwise the time selection. Orthogonal to scope.
return hasRazorArea ? RangeSource::Razor : RangeSource::TimeSelection;
}
FxBypassPlan fxBypassPlanFor(CaptureScope scope) {
FxBypassPlan p;
switch (scope) {
case CaptureScope::Item:
// Item = take/item FX ONLY. Bypass the item's own track FX, every
// ancestor's FX, and the master's FX. (Take FX live in the item and
// are always rendered — there is no track to bypass them from.)
p.bypassSelfFx = true;
p.bypassAncestorFx = true;
p.bypassMaster = true;
return p;
case CaptureScope::Track:
// Track = item FX + the selected track's OWN FX. Keep self FX; bypass
// every ancestor (parent/folder) and the master. Parent/master GAIN
// still applies (I_FXEN is FX-only) — documented boundary.
p.bypassSelfFx = false;
p.bypassAncestorFx = true;
p.bypassMaster = true;
return p;
case CaptureScope::Master:
// Master = whole chain. Bypass nothing.
p.bypassSelfFx = false;
p.bypassAncestorFx = false;
p.bypassMaster = false;
return p;
}
return p; // unreachable; bypass nothing (fail to full-chain, never over-bypass)
}
std::vector<RazorRange> parseRazorEdits(const std::string& razorString) {
std::vector<RazorRange> ranges;
std::istringstream in(razorString);
@@ -59,7 +103,7 @@ std::vector<RazorRange> parseRazorEdits(const std::string& razorString) {
// envelope-lane area's is a GUID `{…}`. We keep only track-audio triples.
std::string startTok, endTok, guidTok;
while (in >> startTok >> endTok >> guidTok) {
// Envelope-lane areas carry a real GUID; skip them (M7 = track audio).
// Envelope-lane areas carry a real GUID; skip them (razor captures track audio only).
// A track-audio area's GUID token is the empty quoted string `""`.
if (guidTok != "\"\"") continue;
@@ -93,29 +137,27 @@ RazorRange razorUnionBounds(const std::vector<RazorRange>& ranges) {
}
const std::vector<CaptureActionDef>& captureActionTable() {
// Built once (function-local static): four wet-only actions. Tail OFF for all
// (exact bounds). Ids are FOREVER-STABLE — never edit a shipped string.
// Dry variants deferred to M10; see kRenderPreFaderStems note in the header.
// Built once (function-local static): three SCOPE actions. Tail OFF for all
// (exact bounds). Ids are FOREVER-STABLE — never edit a shipped string. Each
// action infers its range (razor-else-time) at fire time and enforces its
// FX-scope invariant via fxBypassPlanFor. The M7 CAPTURE_TRACKS_WET /
// CAPTURE_ITEMS_WET / CAPTURE_RAZOR_WET ids are RETIRED (mirror-unregistered in
// main.cpp); CAPTURE_MASTER keeps its shipped id string.
static const std::vector<CaptureActionDef> table = {
// Master mix — wet only (master IS the mix; no pre-FX concept applies).
// Item scope — item/take FX only. NEW forever-stable id.
{"CEREBELLUM_REASAMPLER_CAPTURE_ITEM",
"ReaSampler: capture selected item(s)", "item",
CaptureScope::Item},
// Track scope — item FX + the track's own FX. NEW forever-stable id.
{"CEREBELLUM_REASAMPLER_CAPTURE_TRACK",
"ReaSampler: capture selected track(s)", "track",
CaptureScope::Track},
// Master scope — whole chain. Id string unchanged from M7 (already shipped).
{"CEREBELLUM_REASAMPLER_CAPTURE_MASTER",
"ReaSampler: capture master mix", "master_mix",
SourceMode::MasterMix, 1.0},
// Selected tracks — wet (via master, &128).
{"CEREBELLUM_REASAMPLER_CAPTURE_TRACKS_WET",
"ReaSampler: capture selected tracks", "tracks_wet",
SourceMode::SelectedTracks, 1.0},
// Selected items — wet, single file (&32 | single-file).
{"CEREBELLUM_REASAMPLER_CAPTURE_ITEMS_WET",
"ReaSampler: capture selected items", "items_wet",
SourceMode::SelectedItems, 1.0},
// Razor area — wet, single file (&4096 | single-file).
{"CEREBELLUM_REASAMPLER_CAPTURE_RAZOR_WET",
"ReaSampler: capture razor area", "razor_wet",
SourceMode::RazorArea, 1.0},
"ReaSampler: capture master", "master",
CaptureScope::Master},
};
return table;
}