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:
+31
-23
@@ -74,10 +74,9 @@ constexpr int kActionRenderUsingMostRecentSettings = 42230;
|
||||
// ourselves for exact, unrounded bounds). Verified: SDK header line ~3042.
|
||||
constexpr double kBoundsCustom = 0.0;
|
||||
|
||||
// RENDER_TAILFLAG bit &1 = apply tail for custom time bounds. We clear it for
|
||||
// the spike (exact bounds, no added silence — precision invariant).
|
||||
constexpr double kTailFlagNone = 0.0;
|
||||
constexpr double kTailFlagCustomBounds = 1.0; // &1, used only if renderTail set
|
||||
// RENDER_TAILFLAG / RENDER_TAILMS / RENDER_NORMALIZE / RENDER_TRIMEND for the tail
|
||||
// are driven from the pure tailRenderSettingsFor mapping (render_settings.h),
|
||||
// unit-tested outside the DAW. See the tail-driving block in capture() below.
|
||||
|
||||
// RENDER_DITHER disable-all: &16 = disable all dither/noise-shaping.
|
||||
// Verified: SDK header line ~3050: "&16=disable all".
|
||||
@@ -85,12 +84,6 @@ constexpr double kTailFlagCustomBounds = 1.0; // &1, used only if renderTail se
|
||||
// enabled the render would obey it, breaking bit-identical repeats. Force off.
|
||||
constexpr double kDitherDisableAll = 16.0;
|
||||
|
||||
// RENDER_NORMALIZE disable-all: &(4<<16) = disable all render postprocessing.
|
||||
// Verified: SDK header line ~3051: "(&(4<<16))==disable all render postprocessing".
|
||||
// This masks out normalization, brickwall, fades, pad/trim — every post-process
|
||||
// that is nondeterministic relative to the source signal.
|
||||
constexpr double kNormalizeDisableAll = static_cast<double>(4 << 16); // 262144
|
||||
|
||||
// --- WAV render sink configuration ------------------------------------------
|
||||
//
|
||||
// FORMAT CHOICE (CONTEXT.md open question — surfaced for Daniel to confirm):
|
||||
@@ -147,6 +140,7 @@ struct RenderSettingsSnapshot {
|
||||
double addToProj = 0.0;
|
||||
double dither = 0.0; // RENDER_DITHER — snapshotted so user's setting is restored
|
||||
double normalize = 0.0; // RENDER_NORMALIZE — snapshotted so user's setting is restored
|
||||
double trimEnd = 0.0; // RENDER_TRIMEND — snapshotted so the Auto trim threshold is restored
|
||||
|
||||
// String settings (GetSetProjectInfo_String). Big buffers: REAPER writes the
|
||||
// full value in, and RENDER_FORMAT is a base64 blob that can be long.
|
||||
@@ -183,6 +177,7 @@ void snapshotRenderSettings(RenderSettingsSnapshot& s, ReaProject* proj) {
|
||||
s.addToProj = GetSetProjectInfo(proj, "RENDER_ADDTOPROJ", 0.0, false);
|
||||
s.dither = GetSetProjectInfo(proj, "RENDER_DITHER", 0.0, false);
|
||||
s.normalize = GetSetProjectInfo(proj, "RENDER_NORMALIZE", 0.0, false);
|
||||
s.trimEnd = GetSetProjectInfo(proj, "RENDER_TRIMEND", 0.0, false);
|
||||
s.renderFile = getProjString(proj, "RENDER_FILE");
|
||||
s.renderPattern = getProjString(proj, "RENDER_PATTERN");
|
||||
s.renderFormat = getProjString(proj, "RENDER_FORMAT");
|
||||
@@ -207,6 +202,7 @@ void restoreRenderSettings(const RenderSettingsSnapshot& s) {
|
||||
GetSetProjectInfo(s.proj, "RENDER_ADDTOPROJ", s.addToProj, true);
|
||||
GetSetProjectInfo(s.proj, "RENDER_DITHER", s.dither, true);
|
||||
GetSetProjectInfo(s.proj, "RENDER_NORMALIZE", s.normalize, true);
|
||||
GetSetProjectInfo(s.proj, "RENDER_TRIMEND", s.trimEnd, true);
|
||||
}
|
||||
|
||||
// RAII wrapper: guarantees restore on every return path from capture().
|
||||
@@ -353,13 +349,18 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
|
||||
GetSetProjectInfo(proj, "RENDER_STARTPOS", request.startSeconds, true);
|
||||
GetSetProjectInfo(proj, "RENDER_ENDPOS", request.endSeconds, true);
|
||||
|
||||
if (request.renderTail) {
|
||||
GetSetProjectInfo(proj, "RENDER_TAILFLAG", kTailFlagCustomBounds, true);
|
||||
GetSetProjectInfo(proj, "RENDER_TAILMS", request.tailMs, true);
|
||||
} else {
|
||||
GetSetProjectInfo(proj, "RENDER_TAILFLAG", kTailFlagNone, true);
|
||||
GetSetProjectInfo(proj, "RENDER_TAILMS", 0.0, true);
|
||||
}
|
||||
// Tail: TAILFLAG / TAILMS / NORMALIZE / TRIMEND all come from the pure mapping
|
||||
// (render_settings.h, unit-tested). None -> exact bounds + disable-all normalize
|
||||
// (byte-identical to the pre-tail path); Auto -> 8 s tail + surgical trim-end
|
||||
// normalize + -72 dB TRIMEND; Manual -> clamped fixed tail + disable-all, no trim.
|
||||
// RENDER_NORMALIZE is driven HERE from the mapping (not the determinism block
|
||||
// below) so the Auto surgical value is not clobbered — the snapshot guard restores
|
||||
// the user's original RENDER_NORMALIZE / RENDER_TRIMEND on every exit path.
|
||||
const TailRenderSettings tail =
|
||||
tailRenderSettingsFor(request.tailMode, request.tailMs);
|
||||
GetSetProjectInfo(proj, "RENDER_TAILFLAG",
|
||||
static_cast<double>(tail.tailFlag), true);
|
||||
GetSetProjectInfo(proj, "RENDER_TAILMS", tail.tailMs, true);
|
||||
|
||||
// Source-selection bits for this mode, from the pure render_settings mapping
|
||||
// (verified against SDK header ~3041). All M7 actions are wet-only:
|
||||
@@ -394,13 +395,20 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
|
||||
// item. Clearing RENDER_ADDTOPROJ&1 keeps capture out of the arrange.
|
||||
GetSetProjectInfo(proj, "RENDER_ADDTOPROJ", 0.0, true);
|
||||
|
||||
// Determinism: disable dither and all render post-processing so identical
|
||||
// inputs produce bit-identical files and a dry capture nulls to silence.
|
||||
// RENDER_DITHER &16 = disable all dither/noise-shaping (SDK header line ~3050).
|
||||
// RENDER_NORMALIZE &(4<<16) = disable all render postprocessing (line ~3051).
|
||||
// Both are snapshotted above and restored by the RAII guard on every path.
|
||||
// Determinism: disable dither so identical inputs produce bit-identical files
|
||||
// and a dry capture nulls to silence. RENDER_DITHER &16 = disable all dither/
|
||||
// noise-shaping (SDK header line ~3050). Snapshotted above; restored by the guard.
|
||||
GetSetProjectInfo(proj, "RENDER_DITHER", kDitherDisableAll, true);
|
||||
GetSetProjectInfo(proj, "RENDER_NORMALIZE", kNormalizeDisableAll, true);
|
||||
|
||||
// RENDER_NORMALIZE + RENDER_TRIMEND come from the tail mapping (above). None /
|
||||
// Manual -> disable-all (byte-identical to the pre-tail path); Auto -> surgical
|
||||
// trim-end (only &32768) + the -72 dB TRIMEND. A fixed-threshold trailing-silence
|
||||
// trim scales/limits/fades nothing, so Auto stays deterministic and un-coloring
|
||||
// (spec §surgical normalize). TRIMEND is only consulted when the trim bit is set,
|
||||
// but we write it unconditionally (harmless when clear) so the value is explicit.
|
||||
GetSetProjectInfo(proj, "RENDER_NORMALIZE",
|
||||
static_cast<double>(tail.normalize), true);
|
||||
GetSetProjectInfo(proj, "RENDER_TRIMEND", tail.trimEnd, true);
|
||||
|
||||
// Output location: directory (RENDER_FILE) + file stem (RENDER_PATTERN).
|
||||
// RENDER_PATTERN with no wildcards is a literal stem; REAPER appends the
|
||||
|
||||
Reference in New Issue
Block a user