feat(capture): offline auto/manual tail via pure TailMode mapping
Add TailMode{None,Auto,Manual} + derived -72dB TRIMEND + 8s cap in render_settings;
wire into OfflineRenderBackend with snapshot/restore of the tail settings; add
CAPTURE_ITEM_TAIL/CAPTURE_TRACK_TAIL variant actions (plain actions stay exact-bounds).
This commit is contained in:
+81
-7
@@ -45,6 +45,75 @@ inline constexpr int kRenderRazorEdits = 4096; // &4096 render razor e
|
||||
// (post the FX that remain enabled); the scope decides which FX remain enabled.
|
||||
inline constexpr int kRenderSingleFile = (4 << 16); // items/razor -> one file
|
||||
|
||||
// --- Tail: RENDER_NORMALIZE / RENDER_TRIMEND bits + named constants ----------
|
||||
//
|
||||
// The capture-tail feature (docs/product/capture-tail.md) preserves reverb/release
|
||||
// decay past the range end. Every offline capture renders custom-time-bounds, so
|
||||
// the only tail-flag bit that ever applies is &1 (RENDER_TAILFLAG, header ~3047).
|
||||
// These values are the pure part — mode -> (RENDER_* values) — unit-tested outside
|
||||
// the DAW exactly like renderSettingsFor; the backend just applies them.
|
||||
//
|
||||
// RENDER_NORMALIZE bit meanings (verbatim from SDK header ~3051):
|
||||
// &32768 = trim ending silence (the surgical Auto path)
|
||||
// &(4<<16) = disable all render postprocessing (the None/Manual path)
|
||||
inline constexpr int kNormalizeTrimEnd = 32768; // &32768 trim ending silence
|
||||
inline constexpr int kNormalizeDisableAll = (4 << 16); // &(4<<16) = 262144, disable all
|
||||
|
||||
// RENDER_TAILFLAG &1 = apply tail for custom time bounds (header ~3047). We render
|
||||
// custom bounds unconditionally, so this is the only tail bit that ever applies.
|
||||
inline constexpr int kTailFlagNone = 0;
|
||||
inline constexpr int kTailFlagCustomBounds = 1; // &1
|
||||
|
||||
// Auto-trim trailing-silence threshold. -72 dB is quiet enough that the trimmed
|
||||
// region is inaudible decay, loud enough to not chase a reverb's infinite noise
|
||||
// floor. Daniel-set. Single source of truth: the RENDER_TRIMEND ratio derives from
|
||||
// this dB, never the reverse.
|
||||
inline constexpr double kAutoTrimThresholdDb = -72.0;
|
||||
|
||||
// Max tail rendered past the range end. The runaway guard: a non-decaying or
|
||||
// looping signal never crosses the trim threshold, so this caps the render.
|
||||
// Daniel-set. Shared by the offline (T1) and future realtime (T2) tail paths.
|
||||
inline constexpr double kMaxTailSeconds = 8.0;
|
||||
inline constexpr double kMaxTailMs = 8000.0;
|
||||
|
||||
// Derived linear amplitude ratio for RENDER_TRIMEND. The header (~3062) documents
|
||||
// RENDER_TRIMEND as an amplitude ratio ("0.5 means -6.02 dB"), i.e. 10^(dB/20).
|
||||
// Derived from kAutoTrimThresholdDb so the dB stays the single source of truth and
|
||||
// a future config change to the dB does not require hand-recomputing the ratio.
|
||||
//
|
||||
// std::pow is not constexpr before C++26, so this is a function, not a constant.
|
||||
// For -72 dB: 10^(-72/20) = 10^(-3.6) ~= 0.00025119 (the value the DAW confirm targets).
|
||||
double autoTrimEndRatio();
|
||||
|
||||
// The three tail states (docs/product/capture-tail.md §The three tail states):
|
||||
// None — exact bounds, no tail. Byte-identical to the pre-tail capture. The
|
||||
// default and the ONLY mode for null-test / verify captures.
|
||||
// Auto — generous 8 s tail then trim trailing silence to -72 dB (surgical
|
||||
// normalize). The user-facing "…with tail" default.
|
||||
// Manual — a fixed tail length (clamped to the 8 s cap), no trim.
|
||||
enum class TailMode {
|
||||
None,
|
||||
Auto,
|
||||
Manual,
|
||||
};
|
||||
|
||||
// The RENDER_* values a tail mode drives, in addition to the exact STARTPOS/ENDPOS
|
||||
// the backend already sets. `trimEnd` is meaningful only when the trim-end normalize
|
||||
// bit is set (Auto); it is 0 otherwise. This is the pure mapping — the backend reads
|
||||
// these four fields straight onto GetSetProjectInfo.
|
||||
struct TailRenderSettings {
|
||||
int tailFlag = kTailFlagNone; // RENDER_TAILFLAG (0 or &1)
|
||||
double tailMs = 0.0; // RENDER_TAILMS
|
||||
int normalize = kNormalizeDisableAll; // RENDER_NORMALIZE
|
||||
double trimEnd = 0.0; // RENDER_TRIMEND (only used when trim bit set)
|
||||
};
|
||||
|
||||
// Maps a tail mode (+ the requested manual tail ms) to its RENDER_* values.
|
||||
// `manualTailMs` is used ONLY for TailMode::Manual (ignored otherwise). Manual is
|
||||
// clamped to kMaxTailMs — the runaway guard applies whether the length came from
|
||||
// the Auto default or an explicit request (spec §Manual override). Pure + tested.
|
||||
TailRenderSettings tailRenderSettingsFor(TailMode mode, double manualTailMs);
|
||||
|
||||
// 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 {
|
||||
@@ -144,10 +213,13 @@ RazorRange razorUnionBounds(const std::vector<RazorRange>& ranges);
|
||||
|
||||
// --- Capture-action taxonomy (the bindable set main.cpp registers) -----------
|
||||
//
|
||||
// One row per bindable SCOPE action. Two scopes (item / track); 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).
|
||||
// One row per bindable SCOPE action. Two scopes (item / track), each in two tail
|
||||
// variants: an exact-bounds row (TailMode::None) and a paired "…with tail" row
|
||||
// (TailMode::Auto). The range each captures (razor-else-time) is inferred at fire
|
||||
// time, not a mode. The tail variants are additive — the None rows keep their
|
||||
// documented exact-bounds contract byte-for-byte; the Auto variants are a separate
|
||||
// opt-in action rather than a silent default flip (Daniel's lean, spec §Open
|
||||
// questions). 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).
|
||||
@@ -156,15 +228,17 @@ struct CaptureActionDef {
|
||||
const char* description; // Actions-list label
|
||||
const char* baseName; // file-stem base for this capture
|
||||
CaptureScope scope; // FX scope (item / track)
|
||||
TailMode tailMode; // None (exact bounds) or Auto ("…with tail")
|
||||
};
|
||||
|
||||
// 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.
|
||||
//
|
||||
// Two scope rows: CAPTURE_ITEM, CAPTURE_TRACK. There is no master capture — to
|
||||
// capture the master you render a track. Razor is an inferred range, not a mode,
|
||||
// and each scope enforces its FX-scope invariant via fxBypassPlanFor.
|
||||
// Four rows: CAPTURE_ITEM / CAPTURE_TRACK (exact bounds, TailMode::None) plus the
|
||||
// paired CAPTURE_ITEM_TAIL / CAPTURE_TRACK_TAIL variants (TailMode::Auto). There is
|
||||
// no master capture — to capture the master you render a track. Razor is 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