2nd Pass Render

This commit is contained in:
2026-08-05 16:21:17 -04:00
parent 2f96dd3da5
commit 67524206fb
23 changed files with 251 additions and 89 deletions
+4
View File
@@ -100,6 +100,10 @@ RenderSettingsChoice renderSettingsFor(SourceMode mode, double /*wetDry*/) {
return c;
}
void applySecondPassRenderSettings(const bool secondPass, RenderSettingsChoice& choice) {
if (secondPass) { choice.settings |= kSecondPassRender; }
}
const char* renderSourceLabel(SourceMode mode) {
switch (mode) {
// MasterMix and TimeSelection share this label because they ARE the same
+4
View File
@@ -20,6 +20,7 @@ inline constexpr int kRenderMasterMix = 0; // (&(1|2))==0, no sour
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 kSecondPassRender = 2048; // &2048 2nd Pass Render
inline constexpr int kRenderRazorEdits = 4096; // &4096 render razor edits
// kRenderPreFaderStems (&8192) is deliberately NOT used — REAPER offline render
// has no true pre-FX "dry" bit. FX scoping is done by the FX-bypass-around-render
@@ -113,6 +114,9 @@ struct RenderSettingsChoice {
// SelectedItems -> &32|single-file; RazorArea -> &4096|single-file.
RenderSettingsChoice renderSettingsFor(SourceMode mode, double wetDry);
// Apply the 2nd Pass Render setting to the RENDER_SETTINGS block
void applySecondPassRenderSettings(const bool secondPass, RenderSettingsChoice& choice);
// The render source a mode drives, in words. Exists for the offline backend's
// bounds refusal: the two ways a render can miss its window — a source that
// derives its own bounds (selected items, razor edits) versus a time-bounded
+12 -2
View File
@@ -18,6 +18,10 @@ TailMode cycleTailMode(TailMode current) {
return TailMode::None; // unreachable for a valid enum; fail to the safe default
}
bool toggleSecondPass(bool current) {
return !current; // wow
}
double clampManualMs(double manualMs) {
return std::clamp(manualMs, 0.0, kMaxTailMs);
}
@@ -77,6 +81,7 @@ std::string serializeTailSetting(const TailSetting& setting) {
json::Writer w(out);
w.keyRaw("mode", json::numToStr(modeToInt(setting.mode)));
w.keyRaw("manualMs", json::numToStr(setting.manualMs));
w.keyRaw("secondPass", json::boolToStr(setting.secondPass));
} // Writer closes the object here (see bank_model's NRVO note)
return out;
}
@@ -87,7 +92,8 @@ std::optional<TailSetting> deserializeTailSetting(const std::string& blob) {
int modeInt = 0;
double ms = 0.0;
bool haveMode = false, haveMs = false;
bool secondPass = false;
bool haveMode = false, haveMs = false, haveSecondPass = false;
r.skipWs();
if (!r.consume('}')) {
do {
@@ -99,13 +105,16 @@ std::optional<TailSetting> deserializeTailSetting(const std::string& blob) {
} else if (key == "manualMs") {
if (!r.parseDouble(ms)) return std::nullopt;
haveMs = true;
} else if (key == "secondPass") {
if (!r.parseBool(secondPass)) return std::nullopt;
haveSecondPass = true;
} else {
if (!r.skipValue()) return std::nullopt; // forward-compat
}
} while (r.consume(','));
if (!r.consume('}')) return std::nullopt;
}
if (!haveMode || !haveMs) return std::nullopt; // absent key -> malformed -> default
if (!haveMode || !haveMs || !haveSecondPass) return std::nullopt; // absent key -> malformed -> default
const std::optional<TailMode> mode = modeFromInt(modeInt);
if (!mode) return std::nullopt;
@@ -113,6 +122,7 @@ std::optional<TailSetting> deserializeTailSetting(const std::string& blob) {
TailSetting out;
out.mode = *mode;
out.manualMs = ms;
out.secondPass = secondPass;
return out;
}
+7 -2
View File
@@ -21,14 +21,19 @@ inline constexpr double kManualStepMs = 250.0;
// The panel's current tail setting: mode + the length used only when Manual.
// Default None so a capture with no explicit choice stays exact-bounds.
// `manualMs` is clamped to kMaxTailMs before it ever reaches a CaptureRequest.
// 2nd-Pass Render optional, off by default
struct TailSetting {
TailMode mode = TailMode::None;
double manualMs = kDefaultManualTailMs;
TailMode mode = TailMode::None;
double manualMs = kDefaultManualTailMs;
bool secondPass = false;
};
// Cycles the tail mode: None -> Auto -> Manual -> None.
TailMode cycleTailMode(TailMode current);
// Toggles the 2nd-Pass Render
bool toggleSecondPass(bool current);
// The effective manual length a Manual capture uses: clamped to [0, kMaxTailMs].
// Exposed so the panel can show the clamped value. Meaningful only for Manual.
double clampManualMs(double manualMs);
+6
View File
@@ -54,6 +54,12 @@ std::string numToStr(int v) {
return buf;
}
std::string boolToStr(bool v) {
char buf[6];
std::snprintf(buf, sizeof(buf), "%s", v ? "true" : "false");
return buf;
}
void writeStringArray(std::string& out, const std::vector<std::string>& v) {
out += '[';
for (std::size_t i = 0; i < v.size(); ++i) {
+1
View File
@@ -34,6 +34,7 @@ void writeEscaped(std::string& out, const std::string& s);
std::string numToStr(double v);
std::string numToStr(std::int64_t v);
std::string numToStr(int v);
std::string boolToStr(bool v);
// Flat homogeneous arrays: ["a","b"] / [1,2]. Empty vector -> "[]".
void writeStringArray(std::string& out, const std::vector<std::string>& v);
+17 -2
View File
@@ -2,6 +2,8 @@
#include "core/ui/footer_bar.h"
#include <assert.h>
namespace reasampler::ui {
namespace {
@@ -50,8 +52,18 @@ FooterBarLayout computeFooterBar(const FooterRect& footer, const FooterBarSpec&
}
// Tail button.
if (fitsLeftOf(cursorX, spec.tailWidth, rightBound))
if (fitsLeftOf(cursorX, spec.tailWidth, rightBound)) {
out.tail = FooterBarRect{cursorX, top, spec.tailWidth, boxH};
cursorX += spec.tailWidth + spec.gap;
}
// 2nd Pass Render button.
if (fitsLeftOf(cursorX, spec.secondPassWidth, rightBound)) {
out.secondPass = FooterBarRect{cursorX,top, spec.secondPassWidth, boxH};
// no need to increment the cursor for final element
}
// Package the whole footer bounds into the layout object
out.footer = FooterRect{footer};
return out;
}
@@ -61,7 +73,10 @@ FooterHit hitTestFooterBar(int px, int py, const FooterBarLayout& layout) {
// count label is a passive readout — never a hit target.
if (pointIn(px, py, layout.toggle)) return FooterHit::Toggle;
if (pointIn(px, py, layout.tail)) return FooterHit::Tail;
return FooterHit::None;
if (pointIn(px, py, layout.secondPass)) return FooterHit::SecondPass;
// Finally test if it's within the footer at all
if (pointIn(px, py, layout.footer)) return FooterHit::Invalid;
return FooterHit::Outside;
}
bool modeSegmentEnabled(bool isActiveSegment, bool transportRunning, bool routable) {
+9 -2
View File
@@ -25,18 +25,24 @@ using FooterBarRect = Rect;
// left of the reserved right margin; placement is greedy left-to-right (toggle survives longest,
// Tail drops first on a very narrow footer).
struct FooterBarLayout {
FooterRect footer;
FooterBarRect toggle;
FooterBarRect count;
FooterBarRect tail;
FooterBarRect secondPass;
bool operator==(const FooterBarLayout& o) const {
return toggle == o.toggle && count == o.count && tail == o.tail;
return footer == o.footer &&
toggle == o.toggle &&
count == o.count &&
tail == o.tail &&
secondPass == o.secondPass;
}
};
// Which footer LEFT-group affordance a point landed on. Prune is hit-tested separately via
// hitTestPruneButton.
enum class FooterHit { None, Toggle, Tail };
enum class FooterHit { Invalid, Toggle, Tail, SecondPass, Outside };
// Layout inputs, in pixels; defaults are the bank_panel footer metrics.
// * rightReserve — pixels reserved at the footer's right for the prune button + version
@@ -45,6 +51,7 @@ struct FooterBarSpec {
int toggleWidth = 132;
int countWidth = 64;
int tailWidth = 132;
int secondPassWidth = 96;
int gap = 6;
int leftPad = 8;
int verticalInset = 4;