capture: name the render source in the exact-bounds refusal, and put its one-frame tolerance under test
The tolerance is unchanged and now derived, not assumed: frameCountFor lands in {floor(L), ceil(L)}, so a non-frame-aligned window can never miss by more than a frame. Naming the source is what tells a self-bounding render from a short one.
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
#include "core/capture/wav_codec.h" // hashWavContent / collapseToMono — the one WAV/RIFF owner
|
||||
#include "core/util/file_bytes.h"
|
||||
#include "core/capture/render_settings.h"
|
||||
#include "core/capture/render_window.h" // frameCountFor — the exact-bounds number
|
||||
#include "core/capture/render_window.h" // frameCountFor / renderHonoredBounds — the exact-bounds gate
|
||||
|
||||
#define REAPERAPI_MINIMAL
|
||||
#define REAPERAPI_WANT_EnumProjects
|
||||
@@ -499,9 +499,10 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
|
||||
}
|
||||
|
||||
// Exact bounds, made structural: with no tail requested the file must contain
|
||||
// (within a tolerance, see below) the requested window's frames, so a source
|
||||
// mode that silently widened the render fails loudly here instead of landing as
|
||||
// a successful capture. Auto and Manual add frames by design and are skipped.
|
||||
// the requested window's frames (renderHonoredBounds owns the tolerance and the
|
||||
// reasoning behind it), so a render that printed something other than the window
|
||||
// fails loudly here instead of landing as a successful capture. Auto and Manual
|
||||
// add frames by design and are skipped.
|
||||
// (On TailMode::None the landed file is read three times on this path — this gate,
|
||||
// the mono collapse, and stampCaptureSample — plus one rewrite when the collapse
|
||||
// fires; Auto/Manual skip this gate entirely, so they read it twice. A
|
||||
@@ -518,24 +519,20 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
|
||||
static_cast<int>(layout.sampleRate))
|
||||
: 0;
|
||||
const long long actualFrames = static_cast<long long>(layout.frameCount());
|
||||
// frameCountFor is a difference of frame indices, not a rounded duration
|
||||
// (see render_window.h) — REAPER's own edge-rounding can legitimately land
|
||||
// one frame off that, so the gate tolerates +/-1 rather than exact equality.
|
||||
// The defect this refuses is a whole-item widening (seconds of extra audio,
|
||||
// thousands of frames), which a 1-frame tolerance still catches with
|
||||
// certainty. Tightening to exact equality needs a DAW pass confirming REAPER
|
||||
// resolves the window's two edges to frame indices the same way this does.
|
||||
const long long frameDelta = actualFrames > expectedFrames
|
||||
? actualFrames - expectedFrames
|
||||
: expectedFrames - actualFrames;
|
||||
if (expectedFrames > 0 && frameDelta > 1) {
|
||||
if (expectedFrames > 0 &&
|
||||
!renderHonoredBounds(expectedFrames, actualFrames)) {
|
||||
result.status = CaptureStatus::BoundsMismatch;
|
||||
// Naming the render source is load-bearing, not decoration: a source that
|
||||
// derives its own bounds and a time-bounded render that came up short
|
||||
// produce the same frame count, and only one of them is a routing defect.
|
||||
result.message = "Render produced " + std::to_string(actualFrames) +
|
||||
" frames but the requested range is " +
|
||||
std::to_string(expectedFrames) + " at " +
|
||||
std::to_string(layout.sampleRate) +
|
||||
" Hz -- the render did not honor the requested bounds. "
|
||||
"Requested [" + std::to_string(request.startSeconds) +
|
||||
"Render source: " +
|
||||
renderSourceLabel(request.sourceMode) +
|
||||
". Requested [" + std::to_string(request.startSeconds) +
|
||||
"s, " + std::to_string(request.endSeconds) +
|
||||
"s) -> frame indices [" +
|
||||
std::to_string(std::llround(request.startSeconds *
|
||||
|
||||
Reference in New Issue
Block a user