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:
2026-08-02 06:37:40 -04:00
parent 6e937b9c61
commit a91df760cc
9 changed files with 157 additions and 20 deletions
+24
View File
@@ -10,6 +10,7 @@
#include <cmath>
#include <cstdio>
#include <cstring>
#include <set>
#include <string>
@@ -63,6 +64,28 @@ static void testRealtimeIsUnsupportedOffline() {
CHECK(!renderSettingsFor(SourceMode::Realtime, 1.0).supported);
}
static void testEveryRenderSourceIsNameable() {
// The bounds refusal quotes this label to say WHICH render source missed the
// window, so every mode must name itself and no two may read alike.
const SourceMode all[] = {
SourceMode::MasterMix, SourceMode::TimeSelection, SourceMode::SelectedTracks,
SourceMode::SelectedItems, SourceMode::RazorArea, SourceMode::Realtime,
};
for (std::size_t i = 0; i < sizeof(all) / sizeof(all[0]); ++i) {
const char* label = renderSourceLabel(all[i]);
CHECK(label != nullptr && label[0] != '\0');
CHECK(std::strcmp(label, "unknown") != 0);
for (std::size_t j = i + 1; j < sizeof(all) / sizeof(all[0]); ++j)
CHECK(std::strcmp(label, renderSourceLabel(all[j])) != 0);
}
// The two the refusal must tell apart: a source that derives its own bounds vs
// the time-bounded render.
CHECK(std::strcmp(renderSourceLabel(SourceMode::SelectedItems),
"selected media items") == 0);
CHECK(std::strcmp(renderSourceLabel(SourceMode::SelectedTracks),
"selected tracks via master") == 0);
}
// --- tail: TailMode -> RENDER_* mapping (docs/product/capture-tail.md) --------
static void testTailNoneIsExactBounds() {
@@ -395,6 +418,7 @@ int main() {
testSelectedItemsSingleFile();
testRazorSingleFile();
testRealtimeIsUnsupportedOffline();
testEveryRenderSourceIsNameable();
testTailNoneIsExactBounds();
testTailAutoIsSurgicalTrim();
testAutoTrimRatioDerivesFromDb();
+78 -2
View File
@@ -1,10 +1,12 @@
// Standalone tests for reasampler::render_window — no REAPER, no framework.
// Covers the bounds-equality number (a window's exact frame count at the project
// rate) and the predicate that decides whether REAPER's selected-items render
// source can express a requested window at all.
// rate), the verdict the offline backend refuses a capture on, and the predicate
// that decides whether REAPER's selected-items render source can express a
// requested window at all.
#include "../src/core/capture/render_window.h"
#include <cmath>
#include <cstdio>
using namespace reasampler::capture;
@@ -42,6 +44,74 @@ static void testFrameCountRefusesEmptyInvertedAndUnknownRate() {
CHECK(frameCountFor(1.0, 2.0, -1) == 0); // rate nonsensical
}
static void testWindowStartingAtExactlyZero() {
CHECK(frameCountFor(0.0, 1.0, 48000) == 48000);
// The window from the reported blocker: it starts at 0 and its end lands a
// quarter of a frame off the grid at 48 kHz.
CHECK(frameCountFor(0.0, 4.067797, 48000) == 195254);
}
// --- renderHonoredBounds: the gate's verdict ---------------------------------
static void testNonFrameAlignedWindowAcceptsEveryEdgeConvention() {
// 4.067797 s at 48 kHz is 195254.26 frames — not a frame boundary. A correct
// render lands on 195254, and the neighbours a different edge convention would
// produce are inside the gate.
const long long expected = frameCountFor(0.0, 4.067797, 48000);
CHECK(expected == 195254);
CHECK(renderHonoredBounds(expected, 195254));
CHECK(renderHonoredBounds(expected, 195255));
CHECK(renderHonoredBounds(expected, 195253));
// The shortfall actually reported from the DAW is 38 frames — far outside any
// alignment slack, so it is a render that missed the window, and is refused.
CHECK(!renderHonoredBounds(expected, 195216));
}
static void testNonAlignmentCanNeverExceedOneFrame() {
// The provable content of the one-frame tolerance: frameCountFor rounds each
// edge, so it lands within a frame of the window's real length — and so does a
// renderer that floors, ceils or rounds that same length. Sweep both edges over
// every eighth of a frame; no pairing may fall outside the gate. The renderer's
// count is derived from the length here, independently of frameCountFor.
const int rate = 48000;
for (int s = 0; s < 8; ++s) {
for (int e = 0; e < 8; ++e) {
const double start = 3.0 + s / (8.0 * rate);
const double end = 7.5 + e / (8.0 * rate);
const long long expected = frameCountFor(start, end, rate);
const double length = (end - start) * rate;
CHECK(renderHonoredBounds(expected,
static_cast<long long>(std::floor(length))));
CHECK(renderHonoredBounds(expected,
static_cast<long long>(std::ceil(length))));
CHECK(renderHonoredBounds(expected, std::llround(length)));
}
}
}
static void testWholeItemWideningIsStillRefused() {
// The defect the gate was built for: a 1 s window inside a 30 s item printing
// the whole item.
const long long expected = frameCountFor(5.0, 6.0, 48000);
CHECK(expected == 48000);
CHECK(!renderHonoredBounds(expected, 30 * 48000));
}
static void testLargeShortfallIsStillRefused() {
const long long expected = frameCountFor(0.0, 4.067797, 48000);
CHECK(!renderHonoredBounds(expected, 190000));
// Two frames is the smallest miss outside the tolerance, in both directions —
// the tolerance is one frame and stays one frame.
CHECK(!renderHonoredBounds(expected, expected - 2));
CHECK(!renderHonoredBounds(expected, expected + 2));
}
static void testEmptyRenderIsRefusedAgainstARealWindow() {
// A render that produced nothing is a bounds miss like any other; the backend's
// own "did the file parse" guard is what keeps an unreadable render out of here.
CHECK(!renderHonoredBounds(48000, 0));
}
// --- itemExtentPrintsWindow: can the selected-items source express this? -----
static void testRangeInsideItemCannotBeExpressed() {
@@ -106,6 +176,12 @@ int main() {
testFrameCountIsExactNotRounded();
testFrameCountIsADifferenceOfIndicesNotADuration();
testFrameCountRefusesEmptyInvertedAndUnknownRate();
testWindowStartingAtExactlyZero();
testNonFrameAlignedWindowAcceptsEveryEdgeConvention();
testNonAlignmentCanNeverExceedOneFrame();
testWholeItemWideningIsStillRefused();
testLargeShortfallIsStillRefused();
testEmptyRenderIsRefusedAgainstARealWindow();
testRangeInsideItemCannotBeExpressed();
testRangeWiderThanItemCannotBeExpressedEither();
testEachEdgeAloneDisqualifies();