capture: refuse the multi-track ranged item render, silence the track's children and receives for it, and gate every exact-bounds capture on its frame count

This commit is contained in:
2026-08-01 20:58:33 -04:00
parent 5e3ea6c851
commit 7dc80e7a4f
20 changed files with 525 additions and 27 deletions
+28 -1
View File
@@ -17,6 +17,7 @@
#include "shell/capture/insert.h" // runInsert / InsertRequest
#include "shell/capture/realtime_lifecycle.h" // the in-flight realtime state
#include "shell/capture/render_selection.h" // RenderTrackSelection
#include "shell/capture/render_isolation.h" // UpstreamIsolation
#include "reaper_plugin.h" // UNDO_STATE_MISCCFG
@@ -181,15 +182,41 @@ CaptureResult renderOffline(CaptureScope scope,
const std::vector<MediaTrack*>& sourceTracks,
const CaptureRequest& req)
{
// Refused BEFORE anything is touched, so the refusal path has nothing to
// restore. This is the seam BOTH a fresh capture and a recipe replay cross, so
// neither can land the multi-stem render the predicate names.
if (isMultiTrackRangedItemRender(scope, req.sourceMode,
static_cast<int>(sourceTracks.size())))
{
CaptureResult refused;
refused.status = CaptureStatus::MultiTrackRange;
refused.message =
"This range is narrower than the selected items, so it renders through "
"their tracks -- and those items span more than one track, which renders "
"one file per track. Capture one track's items at a time, or make the "
"range match the items' extent.";
return refused;
}
ReaProject* proj = EnumProjects(-1, nullptr, 0);
// A selected-tracks render prints the DAW's track selection, so the request's
// own tracks must BE that selection for the render — the plain track scope
// already resolved them from the live selection (identity), but a ranged item
// capture and a re-capture-from-source both name tracks the user has not
// selected. Both guards outlive the render call and restore on every path.
// selected. Every guard below outlives the render call and restores on every path.
std::optional<RenderTrackSelection> selection;
std::optional<UpstreamIsolation> isolation;
if (req.sourceMode == SourceMode::SelectedTracks)
{
selection.emplace(sourceTracks);
// An ITEM capture routed through the tracks source would otherwise print
// everything upstream of the track — folder children, receives — which the
// selected-items source excluded. The refusal above is what makes the single
// source track here the whole set. Track scope is left alone: a folder
// parent's own output IS its children summed.
if (scope == CaptureScope::Item && !sourceTracks.empty())
isolation.emplace(sourceTracks.front());
}
FxBypassGuard fxGuard(scope, sourceTracks, proj);
OfflineRenderBackend backend;
return backend.capture(req);