2nd Pass Render
This commit is contained in:
@@ -367,7 +367,7 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
|
||||
|
||||
// SourceMode::Realtime is refused here — that's the realtime backend's job —
|
||||
// so the offline path never silently renders the wrong thing.
|
||||
const RenderSettingsChoice choice =
|
||||
RenderSettingsChoice choice =
|
||||
renderSettingsFor(request.sourceMode, request.wetDry);
|
||||
if (!choice.supported) {
|
||||
result.status = CaptureStatus::UnsupportedMode;
|
||||
@@ -487,8 +487,12 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
|
||||
static_cast<double>(tail.tailFlag), true);
|
||||
GetSetProjectInfo(proj, "RENDER_TAILMS", tail.tailMs, true);
|
||||
|
||||
// 2nd Pass Render
|
||||
applySecondPassRenderSettings(request.secondPass, choice);
|
||||
|
||||
// Source-selection bits for this mode (SDK header ~3041), all wet-only:
|
||||
// master mix = 0; tracks = &128; items = &32|single-file; razor = &4096|single-file.
|
||||
// Also is the door to control the 2nd Pass Render setting.
|
||||
GetSetProjectInfo(proj, "RENDER_SETTINGS",
|
||||
static_cast<double>(choice.settings), true);
|
||||
|
||||
|
||||
@@ -59,11 +59,13 @@ struct CaptureRequest {
|
||||
// bounds, no added silence — the only mode valid for null-test/verify captures.
|
||||
// tailMs applies only to Manual (clamped to 8s by the pure mapping); Auto uses
|
||||
// the 8s cap + -72 dB trim internally, None ignores it.
|
||||
TailMode tailMode = TailMode::None;
|
||||
double tailMs = 0.0;
|
||||
TailMode tailMode = TailMode::None;
|
||||
double tailMs = 0.0;
|
||||
bool secondPass = false;
|
||||
|
||||
// 0 sampleRate => follow project rate.
|
||||
int sampleRate = 0;
|
||||
|
||||
// What the RENDER is asked for (RENDER_CHANNELS / the realtime record mode), not
|
||||
// what the capture lands as: a dual-mono render is collapsed to 1 channel after
|
||||
// the fact, and the Sample's count comes from the produced file.
|
||||
|
||||
@@ -257,6 +257,7 @@ CaptureResult captureAndIndexOne(ReaSamplerSession& session,
|
||||
req.wetDry = 1.0; // wet post the FX left enabled by the scope
|
||||
req.tailMode = tail.mode; // None / Auto / Manual, from the panel toggle
|
||||
req.tailMs = tail.manualMs; // Manual-only (clamped); ignored for None/Auto
|
||||
req.secondPass = tail.secondPass; // Whether 2nd-Pass Render should be performed
|
||||
req.sampleRate = 0; // follow project rate
|
||||
req.channelCount = 2;
|
||||
req.bitDepth = WavBitDepth::Float32; // deterministic, no dither
|
||||
|
||||
@@ -101,6 +101,7 @@ void RunRenderTrackInPlace(ReaSamplerSession& session) {
|
||||
req.wetDry = 1.0;
|
||||
req.tailMode = tail.mode;
|
||||
req.tailMs = tail.manualMs;
|
||||
req.secondPass = tail.secondPass;
|
||||
req.sampleRate = 0; // follow project rate
|
||||
req.channelCount = 2;
|
||||
req.bitDepth = WavBitDepth::Float32;
|
||||
|
||||
@@ -102,7 +102,7 @@ void ReaSamplerEditor::paintChrome(LICE_IBitmap* bmp, const FaceLayout& fl, bool
|
||||
|
||||
// Toolbar: product name + live readout. The beta channel gets no distinct accent; the
|
||||
// channel-derived vstPluginName is the only beta-vs-stable signal.
|
||||
std::string title = version::vstPluginName();
|
||||
std::string title = version::vstPluginName() + " v" + version::appVersion();
|
||||
if (processor_ && processor_->bridge().isConnected()) {
|
||||
// The instance's own loaded state outranks bank availability (the bank is a browser
|
||||
// source, not the instrument's identity) — a self-contained instance names its sound
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "shell/panel/footer_input.h"
|
||||
#include "shell/panel/panel_state.h"
|
||||
#include "core/view/view_mode_model.h"
|
||||
#include "shell/actions/bank_actions.h"
|
||||
#include "shell/persist/session.h" // ReaSamplerSession — view/tail reads + mutation
|
||||
|
||||
#define REAPERAPI_MINIMAL
|
||||
#define REAPERAPI_WANT_EnumProjects
|
||||
#define REAPERAPI_WANT_MarkProjectDirty
|
||||
#define REAPERAPI_WANT_Main_OnCommand
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
namespace reasampler::panel {
|
||||
|
||||
namespace {
|
||||
// Commits the current tail setting to ext state and marks the active project dirty so the
|
||||
// change travels inside the .rpp on Ctrl+S — closes the gap where toggle/scroll would dirty
|
||||
// the project but never write the new value. No-ops cleanly on an unsaved project.
|
||||
void markTailDirty()
|
||||
{
|
||||
if (g_panel.session) g_panel.session->saveToActiveProject();
|
||||
ReaProject* proj = EnumProjects(-1, nullptr, 0);
|
||||
if (proj) MarkProjectDirty(proj);
|
||||
}
|
||||
|
||||
// click was confirmed on the toggle button;
|
||||
// detect which mode segment was hit (if any)
|
||||
bool clickToggle(int x, int y, ui::FooterBarRect toggleRect)
|
||||
{
|
||||
const int seg = hitTestSegment(x, y, toggleRect, modeCount());
|
||||
if (seg >= 0) {
|
||||
const ViewModeModel& view = g_panel.session->view();
|
||||
const std::vector<Mode>& modes = view.modes().all();
|
||||
|
||||
if (seg < static_cast<int>(modes.size())) {
|
||||
const std::string& id = modes[static_cast<std::size_t>(seg)].id;
|
||||
const bool isActive = id == view.activeModeId();
|
||||
const int cmd = modeActivateCommandId(id);
|
||||
if (modeSegmentEnabled(isActive, g_panel.modeSwitchBlocked, cmd != 0)) {
|
||||
if (cmd != 0 && Main_OnCommand) Main_OnCommand(cmd, 0);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// click was confirmed on the Tail button
|
||||
void clickTail()
|
||||
{
|
||||
TailSetting& tail = g_panel.session->tail();
|
||||
tail.mode = cycleTailMode(tail.mode);
|
||||
markTailDirty();
|
||||
invalidatePanel();
|
||||
}
|
||||
|
||||
void clickSecondPass()
|
||||
{
|
||||
TailSetting& tail = g_panel.session->tail();
|
||||
tail.secondPass = capture::toggleSecondPass(tail.secondPass);
|
||||
markTailDirty();
|
||||
invalidatePanel();
|
||||
}
|
||||
|
||||
// click was confirmed on the prune button
|
||||
void clickPrune()
|
||||
{
|
||||
// Fires through its registered command id (not the session directly) so the panel
|
||||
// affordance and the bindable action share the one guarded dry-run/confirm/delete
|
||||
// path in doBankPruneFolder.
|
||||
const int cmd = bankPruneCommandId();
|
||||
if (cmd != 0) Main_OnCommand(cmd, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Returns: true if the click was inside the footer bar,
|
||||
// false if the click was outside the footer bar.
|
||||
bool handleFooterClick(int x, int y, int w, int h)
|
||||
{
|
||||
|
||||
const ButtonRect pb = pruneButtonRectFor(w, h);
|
||||
if (hitTestPruneButton(x, y, pb)) {
|
||||
clickPrune();
|
||||
return true;
|
||||
}
|
||||
|
||||
const FooterBarLayout fb = footerBarLayoutFor(w, h);
|
||||
if (g_panel.session) {
|
||||
FooterHit clicked = hitTestFooterBar(x, y, fb);
|
||||
|
||||
// The short-circuit cases
|
||||
if (clicked == FooterHit::Outside) { return false; }
|
||||
if (clicked == FooterHit::Invalid) { return true; }
|
||||
|
||||
// Check actual clickable elements:
|
||||
|
||||
// Cycles None -> Auto -> Manual -> None
|
||||
if (clicked == FooterHit::Tail) { clickTail(); }
|
||||
|
||||
// Detect which segment was selected and activate that mode
|
||||
if (clicked == FooterHit::Toggle) { clickToggle(x, y, fb.toggle); }
|
||||
|
||||
// Toggle 2nd Pass Render
|
||||
if (clicked == FooterHit::SecondPass) { clickSecondPass(); }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we reach this point, no valid click target inside the footer was hit.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool handleFooterWheel(int x, int y, int delta) {
|
||||
if (!g_panel.session) return false;
|
||||
|
||||
TailSetting& tail = g_panel.session->tail();
|
||||
if (tail.mode != TailMode::Manual) return false; // fine-adjust is Manual-only
|
||||
|
||||
// One notch is WHEEL_DELTA (120); accumulate whole notches so a high-res trackpad
|
||||
// that sends fractional deltas still steps predictably. Sign carries direction.
|
||||
const int notches = delta / 120;
|
||||
if (notches == 0) return false; // sub-notch movement — nothing to apply yet
|
||||
|
||||
const double before = tail.manualMs;
|
||||
tail.manualMs = adjustManualMs(tail.manualMs, notches, kManualStepMs);
|
||||
if (tail.manualMs == before) return true; // already at a bound — consumed, no change
|
||||
|
||||
markTailDirty();
|
||||
invalidatePanel(); // label shows the new length live
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
namespace reasampler::panel {
|
||||
|
||||
bool handleFooterClick(int x, int y, int w, int h);
|
||||
|
||||
bool handleFooterWheel(int x, int y, int delta);
|
||||
|
||||
}
|
||||
@@ -274,7 +274,9 @@ Hover resolveHover(int x, int y) {
|
||||
const int seg = footerToggleSegmentHit(x, y, w, h);
|
||||
if (seg >= 0) return Hover{HoverKind::ModeSegment, seg};
|
||||
const FooterBarLayout fb = footerBarLayoutFor(w, h);
|
||||
if (hitTestFooterBar(x, y, fb) == FooterHit::Tail) return Hover{HoverKind::TailButton, -1};
|
||||
const FooterHit footerHit = hitTestFooterBar(x, y, fb);
|
||||
if (footerHit == FooterHit::Tail) return Hover{HoverKind::TailButton, -1};
|
||||
if (footerHit == FooterHit::SecondPass) return Hover{HoverKind::SecondPassButton, -1};
|
||||
const ButtonRect pb = pruneButtonRectFor(w, h);
|
||||
if (hitTestPruneButton(x, y, pb)) return Hover{HoverKind::PruneButton, -1};
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "shell/panel/panel_state.h"
|
||||
#include "shell/panel/panel_input.h"
|
||||
#include "shell/panel/footer_input.h"
|
||||
|
||||
#include "shell/actions/bank_actions.h" // bankPruneCommandId — the footer Prune dispatch
|
||||
#include "shell/persist/session.h" // ReaSamplerSession — view/tail reads + mutation
|
||||
@@ -61,15 +62,6 @@ int modeActivateCommandId(const std::string& modeId) {
|
||||
|
||||
namespace {
|
||||
|
||||
// Commits the current tail setting to ext state and marks the active project dirty so the
|
||||
// change travels inside the .rpp on Ctrl+S — closes the gap where toggle/scroll would dirty
|
||||
// the project but never write the new value. No-ops cleanly on an unsaved project.
|
||||
void markTailDirty() {
|
||||
if (g_panel.session) g_panel.session->saveToActiveProject();
|
||||
ReaProject* proj = EnumProjects(-1, nullptr, 0);
|
||||
if (proj) MarkProjectDirty(proj);
|
||||
}
|
||||
|
||||
// Routes a click in a toolbar to the hit button's action via Main_OnCommand. Returns true
|
||||
// iff the click was inside the bar band, so the caller stops before grid handling.
|
||||
bool handleToolbarClick(int x, int y, const ActionBarRect& bar,
|
||||
@@ -332,46 +324,9 @@ void handleClick(int x, int y) {
|
||||
// the More button) so a click there is inert chrome, never a fall-through to the grid.
|
||||
if (y >= 0 && y < kTopToolbarHeight && x >= 0 && x < w) return;
|
||||
|
||||
// Footer: mode toggle (left) -> Tail button -> Prune (right). Checked before the bottom
|
||||
// Footer: mode toggle (left) -> Tail button -> 2nd Pass Render -> Prune (right). Checked before the bottom
|
||||
// toolbar / grid so a footer click never selects a cell.
|
||||
{
|
||||
const int seg = footerToggleSegmentHit(x, y, w, h);
|
||||
if (seg >= 0) {
|
||||
const ViewModeModel& view = g_panel.session->view();
|
||||
const std::vector<Mode>& modes = view.modes().all();
|
||||
if (seg < static_cast<int>(modes.size())) {
|
||||
const std::string& id = modes[static_cast<std::size_t>(seg)].id;
|
||||
const bool isActive = id == view.activeModeId();
|
||||
const int cmd = modeActivateCommandId(id);
|
||||
// Disabled/dead rationale: core/ui/footer_bar.h. Claimed but inert, the
|
||||
// same shape a disabled toolbar row takes — never falls through to the grid.
|
||||
if (modeSegmentEnabled(isActive, g_panel.modeSwitchBlocked, cmd != 0)) {
|
||||
if (cmd != 0 && Main_OnCommand) Main_OnCommand(cmd, 0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const FooterBarLayout fb = footerBarLayoutFor(w, h);
|
||||
if (g_panel.session && hitTestFooterBar(x, y, fb) == FooterHit::Tail) {
|
||||
// Cycles None -> Auto -> Manual -> None; touches NOTHING in the bank/arrange.
|
||||
TailSetting& tail = g_panel.session->tail();
|
||||
tail.mode = cycleTailMode(tail.mode);
|
||||
markTailDirty();
|
||||
invalidatePanel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fires through its registered command id (not the session directly) so the panel
|
||||
// affordance and the bindable action share the one guarded dry-run/confirm/delete
|
||||
// path in doBankPruneFolder.
|
||||
const ButtonRect pb = pruneButtonRectFor(w, h);
|
||||
if (hitTestPruneButton(x, y, pb)) {
|
||||
const int cmd = bankPruneCommandId();
|
||||
if (cmd != 0) Main_OnCommand(cmd, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (handleFooterClick(x, y, w, h)) return;
|
||||
|
||||
if (handleToolbarClick(x, y, bottomToolbarRect(w, h), bottomBarRows())) return;
|
||||
|
||||
@@ -456,24 +411,10 @@ void handleClick(int x, int y) {
|
||||
// [0, kMaxTailMs]. Otherwise does nothing (returns false so the caller can let REAPER/the
|
||||
// docker handle the wheel normally). Returns true iff the wheel was consumed.
|
||||
bool handleWheel(int x, int y, int delta) {
|
||||
if (!g_panel.session) return false;
|
||||
if (!pointInFooter(x, y)) return false;
|
||||
if (pointInFooter(x, y)) return handleFooterWheel(x, y, delta);
|
||||
|
||||
TailSetting& tail = g_panel.session->tail();
|
||||
if (tail.mode != TailMode::Manual) return false; // fine-adjust is Manual-only
|
||||
|
||||
// One notch is WHEEL_DELTA (120); accumulate whole notches so a high-res trackpad
|
||||
// that sends fractional deltas still steps predictably. Sign carries direction.
|
||||
const int notches = delta / 120;
|
||||
if (notches == 0) return false; // sub-notch movement — nothing to apply yet
|
||||
|
||||
const double before = tail.manualMs;
|
||||
tail.manualMs = adjustManualMs(tail.manualMs, notches, kManualStepMs);
|
||||
if (tail.manualMs == before) return true; // already at a bound — consumed, no change
|
||||
|
||||
markTailDirty();
|
||||
invalidatePanel(); // label shows the new length live
|
||||
return true;
|
||||
// Unhandled
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -165,6 +165,19 @@ void drawFooter(LICE_IBitmap* bmp, int w, int h) {
|
||||
drawButton(bmp, box, label.c_str(), state, /*warn=*/false);
|
||||
}
|
||||
|
||||
// 2nd Pass Render BUTTON
|
||||
if (!fb.secondPass.empty()) {
|
||||
const TailSetting tail = currentTail();
|
||||
const InteractionState state = tail.secondPass
|
||||
? InteractionState::Active
|
||||
: hoverState(g_panel.hovered, HoverKind::SecondPassButton, -1);
|
||||
const std::string label = "2nd Pass";
|
||||
const KitButtonBox box{
|
||||
KitBox{fb.secondPass.x, fb.secondPass.y, fb.secondPass.width, fb.secondPass.height}
|
||||
};
|
||||
drawButton(bmp, box, label.c_str(), state, /*warn=*/false);
|
||||
}
|
||||
|
||||
// Version/channel readout. appVersion() renders the configured version string on
|
||||
// stable and that string plus "-beta" on beta, so a beta panel self-identifies.
|
||||
kitText(bmp, KitBox{f.left, f.top, (f.right - f.left) - 8, f.bottom - f.top},
|
||||
|
||||
@@ -272,6 +272,7 @@ enum class HoverKind {
|
||||
CreateBank,
|
||||
Tab, // index = tab ordinal
|
||||
TailButton,
|
||||
SecondPassButton,
|
||||
ModeSegment, // index = segment ordinal
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user