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:
+95
-36
@@ -1,16 +1,22 @@
|
||||
#pragma once
|
||||
// render_settings — the REAPER-free logic behind the M7 capture action family.
|
||||
// render_settings — the REAPER-free logic behind the capture action family.
|
||||
//
|
||||
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
|
||||
// vendor/ includes. Standard library only. The capture shell (capture.cpp) reads
|
||||
// the actual DAW state (time selection, selected tracks/items, razor strings) and
|
||||
// hands the raw values here so the three genuinely-pure, easy-to-get-wrong pieces
|
||||
// are unit-tested outside the DAW:
|
||||
// vendor/ includes. Standard library only. The capture shell (capture.cpp) and
|
||||
// action layer (main.cpp) read the actual DAW state (time selection, selected
|
||||
// tracks/items, razor strings, the ancestor-track chain) and hand the raw values
|
||||
// here so the genuinely-pure, easy-to-get-wrong pieces are unit-tested outside
|
||||
// the DAW:
|
||||
//
|
||||
// 1. sourceMode -> the RENDER_SETTINGS integer bit value (wet only; M7 scope).
|
||||
// 1. sourceMode -> the RENDER_SETTINGS integer bit value (wet only).
|
||||
// 2. a P_RAZOREDITS string -> the list of (start,end) ranges + their union bound.
|
||||
// 3. the capture-action table (id string, description, source mode, wet/dry) —
|
||||
// the taxonomy, in one place so main.cpp iterates it instead of hand-listing.
|
||||
// 3. range inference: razor-present -> razor union, else time selection. Range
|
||||
// is a SOURCE choice orthogonal to the capture scope.
|
||||
// 4. the FX-scope bypass plan: given a scope + an ancestor-chain length, which
|
||||
// tracks' FX to bypass so each scope hears only the FX it should (the M7
|
||||
// "items captured through parent FX" defect is corrected here).
|
||||
// 5. the capture-action table (id string, description, scope) — the taxonomy,
|
||||
// in one place so main.cpp iterates it instead of hand-listing.
|
||||
//
|
||||
// The RENDER_SETTINGS bit MEANINGS are transcribed verbatim from
|
||||
// reaper_plugin_functions.h line ~3041 (see kRender* constants); the CHOICE of
|
||||
@@ -25,30 +31,31 @@ namespace reasampler {
|
||||
|
||||
// --- RENDER_SETTINGS source/processing bits (verbatim from SDK header ~3041) --
|
||||
//
|
||||
// Only the bits M7 actually uses are named. Values are the documented bit
|
||||
// Only the bits this module actually uses are named. Values are the documented bit
|
||||
// weights; the DOC of each is the SDK header's, not a guess.
|
||||
inline constexpr int kRenderMasterMix = 0; // (&(1|2))==0, no source bits
|
||||
inline constexpr int kRenderSelItems = 32; // &32 selected media items
|
||||
inline constexpr int kRenderSelItemsViaMaster = 64; // &64 selected media items via master
|
||||
inline constexpr int kRenderSelTracksViaMaster = 128; // &128 selected tracks via master
|
||||
inline constexpr int kRenderRazorEdits = 4096; // &4096 render razor edits
|
||||
// NOTE: kRenderPreFaderStems (&8192) is NOT used in M7. REAPER offline render has
|
||||
// no true pre-FX "dry" bit. Pre-fader stems are post-FX/pre-fader-volume — an
|
||||
// approximation, not a dry capture. True pre-FX dry requires FX-bypass-around-
|
||||
// render or the M8 realtime pre-FX path; it will be designed with the M10 null
|
||||
// test. All M7 capture actions are wet-only.
|
||||
// NOTE: kRenderPreFaderStems (&8192) is NOT used. REAPER offline render has no
|
||||
// true pre-FX "dry" bit. FX scoping is done by the FX-bypass-around-render
|
||||
// mechanism (see fxBypassPlan below) — bypassing the FX-enable of the tracks that
|
||||
// fall outside a scope — NOT by any render bit. All capture actions render wet
|
||||
// (post the FX that remain enabled); the scope decides which FX remain enabled.
|
||||
inline constexpr int kRenderSingleFile = (4 << 16); // items/razor -> one file
|
||||
|
||||
// The RENDER_SETTINGS value for a given source mode. `supported` is false only
|
||||
// for SourceMode::Realtime (that is the M8 backend, not offline render).
|
||||
struct RenderSettingsChoice {
|
||||
int settings = kRenderMasterMix;
|
||||
bool supported = true; // false => not an offline-render source in M7
|
||||
bool supported = true; // false => not an offline-render source (e.g. Realtime)
|
||||
};
|
||||
|
||||
// Maps a source mode to the wet RENDER_SETTINGS value for M7.
|
||||
// All M7 actions are wet-only (post-FX). `wetDry` is accepted but ignored for
|
||||
// the mapping — retained in CaptureRequest as the seam for future M10 dry work.
|
||||
// Maps a source mode to its RENDER_SETTINGS value (which content the render
|
||||
// covers). FX scoping is orthogonal — done by fxBypassPlan, not by these bits.
|
||||
// `wetDry` is accepted but ignored for the mapping — retained in CaptureRequest
|
||||
// as the seam for future dry work (M10 null test).
|
||||
//
|
||||
// CONFIRMED (SDK header ~3041):
|
||||
// MasterMix / TimeSelection -> master mix (0).
|
||||
@@ -57,8 +64,60 @@ struct RenderSettingsChoice {
|
||||
// RazorArea -> &4096| single-file.
|
||||
RenderSettingsChoice renderSettingsFor(SourceMode mode, double wetDry);
|
||||
|
||||
// --- Capture scope: the FX-scope invariant (Daniel, critical) ----------------
|
||||
//
|
||||
// Three FX scopes. The render RANGE (razor-else-time) is orthogonal to the scope.
|
||||
// Item -> item/take FX ONLY (no track, no parent/folder, no master FX).
|
||||
// Track -> item FX + the selected track's OWN track FX (no parent/folder/master).
|
||||
// Master -> the whole chain (nothing bypassed).
|
||||
enum class CaptureScope {
|
||||
Item,
|
||||
Track,
|
||||
Master,
|
||||
};
|
||||
|
||||
// The render source mode each scope drives. Item captures selected items, Track
|
||||
// captures selected tracks (via master), Master captures the master mix.
|
||||
SourceMode sourceModeForScope(CaptureScope scope);
|
||||
|
||||
// --- Range inference: razor-else-time (orthogonal to scope) -------------------
|
||||
//
|
||||
// Every scope action infers its render range the same way: if a razor area is
|
||||
// present, use the razor union; otherwise use the time selection. Razor is a
|
||||
// range SOURCE, not a capture mode (the M7 four-mode model conflated them).
|
||||
enum class RangeSource {
|
||||
Razor, // a razor area is present -> use its union bound
|
||||
TimeSelection, // no razor -> use the time selection
|
||||
};
|
||||
|
||||
// Picks the range source. Pure so the "razor wins when present" rule is tested
|
||||
// without a DAW; the shell supplies whether any razor area was found.
|
||||
RangeSource inferRangeSource(bool hasRazorArea);
|
||||
|
||||
// --- FX-bypass plan: which tracks' FX to bypass for a scope -------------------
|
||||
//
|
||||
// Given a CaptureScope, returns three boolean flags: whether to bypass (a) the
|
||||
// captured track's OWN FX, (b) each of its ancestor (parent/folder) tracks' FX,
|
||||
// and (c) the master FX. The caller (FxBypassGuard) resolves these flags to
|
||||
// concrete MediaTrack* by walking the ancestor chain via GetParentTrack and
|
||||
// clears I_FXEN on each flagged track, snapshotting first (RAII restore).
|
||||
//
|
||||
// SCOPE BOUNDARY (documented, DAW-confirm): I_FXEN bypasses a track's FX plugins
|
||||
// but NOT its volume/pan/routing. "No parent FX" is satisfied by bypassing parent
|
||||
// FX only — parent/master GAIN still applies to a Track capture rendered via
|
||||
// master (&128). Neutralizing parent gain would be a larger, surprising mutation
|
||||
// (and is not what "FX scope" means); the least-surprising default is FX-only
|
||||
// bypass. Flagged for Daniel's DAW confirmation.
|
||||
struct FxBypassPlan {
|
||||
bool bypassSelfFx = false; // the captured track's own FX
|
||||
bool bypassAncestorFx = false; // every ancestor (parent/folder) track's FX
|
||||
bool bypassMaster = false; // the master track's FX
|
||||
};
|
||||
|
||||
FxBypassPlan fxBypassPlanFor(CaptureScope scope);
|
||||
|
||||
// A single razor-edit area: a time range on one track (envelope GUID ignored —
|
||||
// M7 captures track-audio razor areas, not envelope lanes).
|
||||
// razor captures target track-audio areas, not envelope lanes).
|
||||
struct RazorRange {
|
||||
double startSeconds = 0.0;
|
||||
double endSeconds = 0.0;
|
||||
@@ -69,8 +128,8 @@ struct RazorRange {
|
||||
// quoted string, i.e. the literal two chars `""`) for a track-audio area and a
|
||||
// GUID like {…} for an envelope-lane area.
|
||||
//
|
||||
// Returns only the track-audio ranges (envelope-lane triples are skipped — M7
|
||||
// renders track audio). Malformed/short trailing tokens are ignored, not fatal.
|
||||
// Returns only the track-audio ranges (envelope-lane triples are skipped — razor
|
||||
// captures target track audio, not envelope lanes). Malformed/short trailing tokens are ignored, not fatal.
|
||||
// A range with end <= start is dropped (no negative/empty areas leak through).
|
||||
std::vector<RazorRange> parseRazorEdits(const std::string& razorString);
|
||||
|
||||
@@ -81,28 +140,28 @@ RazorRange razorUnionBounds(const std::vector<RazorRange>& ranges);
|
||||
|
||||
// --- Capture-action taxonomy (the bindable set main.cpp registers) -----------
|
||||
//
|
||||
// One row per bindable action. All M7 rows are wet-only (post-FX). Tail is OFF
|
||||
// for every row (exact bounds); a tail-on variant is a later opt-in, YAGNI now.
|
||||
// Yields a bounded, discoverable set with NO dialogs (the tool's no-clutter ethos).
|
||||
// One row per bindable SCOPE action. Three scopes (item / track / master); the
|
||||
// range each captures (razor-else-time) is inferred at fire time, not a mode.
|
||||
// Tail is OFF for every row (exact bounds); a tail-on variant is a later opt-in,
|
||||
// YAGNI now. Bounded, discoverable, NO dialogs (the tool's no-clutter ethos).
|
||||
//
|
||||
// commandString is FOREVER-STABLE (user keybindings key off it) — never change a
|
||||
// shipped value. baseName feeds the file stem (sanitized by capture_paths).
|
||||
// wetDry is retained as the M10 seam; all M7 rows set it to 1.0.
|
||||
struct CaptureActionDef {
|
||||
const char* commandString; // CEREBELLUM_REASAMPLER_… FOREVER-STABLE id string
|
||||
const char* description; // Actions-list label
|
||||
const char* baseName; // file-stem base for this capture
|
||||
SourceMode sourceMode;
|
||||
double wetDry; // 1.0 (wet) for all M7 rows; seam for M10 dry
|
||||
const char* commandString; // CEREBELLUM_REASAMPLER_… FOREVER-STABLE id string
|
||||
const char* description; // Actions-list label
|
||||
const char* baseName; // file-stem base for this capture
|
||||
CaptureScope scope; // FX scope (item / track / master)
|
||||
};
|
||||
|
||||
// The full M7 capture-action table. Iterated by main.cpp to register the family
|
||||
// and route each fired command back to its definition. Kept here (pure) so the
|
||||
// taxonomy is one testable list, not scattered registration code.
|
||||
// The capture-action table. Iterated by main.cpp to register the family and route
|
||||
// each fired command back to its definition. Kept here (pure) so the taxonomy is
|
||||
// one testable list, not scattered registration code.
|
||||
//
|
||||
// Four wet-only rows: CAPTURE_MASTER, CAPTURE_TRACKS_WET, CAPTURE_ITEMS_WET,
|
||||
// CAPTURE_RAZOR_WET. Dry variants are deferred to M10 (null-test work) — see the
|
||||
// kRenderPreFaderStems note above for why offline dry is non-trivial.
|
||||
// Three scope rows: CAPTURE_ITEM, CAPTURE_TRACK, CAPTURE_MASTER. This replaces the
|
||||
// M7 four-mode table (master / tracks / items / razor) — razor is now an inferred
|
||||
// range, not a mode, and each scope enforces its FX-scope invariant via
|
||||
// fxBypassPlanFor.
|
||||
const std::vector<CaptureActionDef>& captureActionTable();
|
||||
|
||||
} // namespace reasampler
|
||||
|
||||
Reference in New Issue
Block a user