capture: state the bounds tolerance as empirical, refuse unmeasurable renders, keep refused ones for diagnosis

The one-frame bound is not provable for a per-edge renderer; the test now shows where it breaks. Refused renders move out of the bank instead of being deleted, so the DAW experiment has something to read.
This commit is contained in:
2026-08-02 07:23:30 -04:00
parent a91df760cc
commit 2005f90c66
14 changed files with 339 additions and 126 deletions
+43 -19
View File
@@ -64,26 +64,49 @@ 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);
static void testEveryRenderSourceLabelIsPinnedVerbatim() {
// docs/VERIFICATION.md asks Daniel to report the refusal's `Render source:` line
// back verbatim, so every label is pinned to its literal — a typo in any of them
// breaks the report that quotes it, and only a literal catches that.
CHECK(std::strcmp(renderSourceLabel(SourceMode::MasterMix), "master mix") == 0);
CHECK(std::strcmp(renderSourceLabel(SourceMode::TimeSelection), "master mix") == 0);
CHECK(std::strcmp(renderSourceLabel(SourceMode::SelectedTracks),
"selected tracks via master") == 0);
CHECK(std::strcmp(renderSourceLabel(SourceMode::SelectedItems),
"selected media items") == 0);
CHECK(std::strcmp(renderSourceLabel(SourceMode::RazorArea), "razor edits") == 0);
CHECK(std::strcmp(renderSourceLabel(SourceMode::Realtime), "realtime record") == 0);
}
static void testLabelsSeparateExactlyWhatTheRenderSeparates() {
// The labels partition the offline modes the way RENDER_SETTINGS does, and no
// finer: same bits => same words (MasterMix/TimeSelection both render the master
// mix, custom-time-bounded), different bits => different words. Naming two modes
// apart that render identically would put a distinction in a bug report that does
// not exist in the render.
const SourceMode offline[] = {
SourceMode::MasterMix, SourceMode::TimeSelection, SourceMode::SelectedTracks,
SourceMode::SelectedItems, SourceMode::RazorArea,
};
constexpr std::size_t n = sizeof(offline) / sizeof(offline[0]);
for (std::size_t i = 0; i < n; ++i) {
const char* label = renderSourceLabel(offline[i]);
CHECK(label != nullptr && label[0] != '\0');
CHECK(std::strcmp(label, "unknown") != 0);
CHECK(renderSettingsFor(offline[i], 1.0).supported);
for (std::size_t j = i + 1; j < n; ++j) {
const bool sameRender = renderSettingsFor(offline[i], 1.0).settings ==
renderSettingsFor(offline[j], 1.0).settings;
const bool sameLabel =
std::strcmp(label, renderSourceLabel(offline[j])) == 0;
CHECK(sameRender == sameLabel);
}
}
// Realtime is not an offline render source at all, so it names its own mechanism
// rather than a RENDER_SETTINGS value — outside the partition above by design.
CHECK(!renderSettingsFor(SourceMode::Realtime, 1.0).supported);
CHECK(std::strcmp(renderSourceLabel(SourceMode::Realtime),
renderSourceLabel(SourceMode::MasterMix)) != 0);
}
// --- tail: TailMode -> RENDER_* mapping (docs/product/capture-tail.md) --------
@@ -418,7 +441,8 @@ int main() {
testSelectedItemsSingleFile();
testRazorSingleFile();
testRealtimeIsUnsupportedOffline();
testEveryRenderSourceIsNameable();
testEveryRenderSourceLabelIsPinnedVerbatim();
testLabelsSeparateExactlyWhatTheRenderSeparates();
testTailNoneIsExactBounds();
testTailAutoIsSurgicalTrim();
testAutoTrimRatioDerivesFromDb();
+80 -25
View File
@@ -53,10 +53,9 @@ static void testWindowStartingAtExactlyZero() {
// --- renderHonoredBounds: the gate's verdict ---------------------------------
static void testNonFrameAlignedWindowAcceptsEveryEdgeConvention() {
static void testNonFrameAlignedWindowAcceptsItsAdjacentCounts() {
// 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.
// render lands on 195254, and both adjacent counts are inside the gate.
const long long expected = frameCountFor(0.0, 4.067797, 48000);
CHECK(expected == 195254);
CHECK(renderHonoredBounds(expected, 195254));
@@ -67,28 +66,82 @@ static void testNonFrameAlignedWindowAcceptsEveryEdgeConvention() {
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 testLengthDerivedAndSameConventionRenderersStayWithinOneFrame() {
// What the one-frame tolerance is actually good for. Two families of renderer are
// inside it at every offset swept here: one that derives its count from the
// window's LENGTH (floor/ceil/round of (end-start)*rate), and one that resolves
// each EDGE to a frame using the SAME convention on both edges. Every count below
// is computed from the window, never from frameCountFor, so this compares two
// derivations rather than restating one. Round-both-edges is omitted deliberately:
// that IS frameCountFor's own convention, so asserting it would be tautological.
//
// 8192 is a power of two, so an eighth of a frame is exact in double there and the
// .5 rounding ties are really hit; at 48000/44100 (the shipping rates) they are
// only approached, which is why all three are swept.
struct Window { double start; double end; };
const int rates[] = {48000, 44100, 8192};
const Window windows[] = {{3.0, 7.5}, {0.0, 4.067797}, {10.25, 10.75}};
for (int rate : rates) {
for (const Window& w : windows) {
for (int s = 0; s < 8; ++s) {
for (int e = 0; e < 8; ++e) {
const double start = w.start + s / (8.0 * rate);
const double end = w.end + 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)));
const double startFrames = start * rate;
const double endFrames = end * rate;
CHECK(renderHonoredBounds(
expected, static_cast<long long>(std::floor(endFrames) -
std::floor(startFrames))));
CHECK(renderHonoredBounds(
expected, static_cast<long long>(std::ceil(endFrames) -
std::ceil(startFrames))));
}
}
}
}
}
static void testMixedEdgeConventionsCanMissByTwoAndAreRefused() {
// The hole in that bound, stated rather than hidden. A renderer that resolves the
// two edges by DIFFERENT conventions lands two frames from frameCountFor's answer
// whenever the start sits past mid-frame and the end before it (resolved outward),
// or the mirror image (resolved inward). The gate refuses both — correctly if
// REAPER derives its count from the window's length, wrongly if it resolves edges
// this way. No unit test can settle which; see render_window.h.
const int rate = 8192; // power of two: the eighth-frame offsets below are exact
// Outward: start .625 into a frame, end .375 into one.
const double start = 10.25 + 5.0 / (8.0 * rate);
const double end = 10.75 + 3.0 / (8.0 * rate);
CHECK(start * rate == 83968.625); // the premise, not an outcome — pinned so a
CHECK(end * rate == 88064.375); // representability slip can't fake the result
const long long expected = frameCountFor(start, end, rate);
CHECK(expected == 4095);
const long long outward = static_cast<long long>(std::ceil(end * rate) -
std::floor(start * rate));
CHECK(outward == 4097);
CHECK(!renderHonoredBounds(expected, outward));
// Inward, mirrored fractions.
const double start2 = 10.25 + 3.0 / (8.0 * rate);
const double end2 = 10.75 + 5.0 / (8.0 * rate);
const long long expected2 = frameCountFor(start2, end2, rate);
CHECK(expected2 == 4097);
const long long inward = static_cast<long long>(std::floor(end2 * rate) -
std::ceil(start2 * rate));
CHECK(inward == 4095);
CHECK(!renderHonoredBounds(expected2, inward));
}
static void testWholeItemWideningIsStillRefused() {
// The defect the gate was built for: a 1 s window inside a 30 s item printing
// the whole item.
@@ -107,8 +160,9 @@ static void testLargeShortfallIsStillRefused() {
}
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.
// A render that produced nothing is a bounds miss like any other. A render whose
// frames could not be MEASURED never reaches this predicate — shell/capture/
// render_bounds_gate refuses it before the comparison.
CHECK(!renderHonoredBounds(48000, 0));
}
@@ -177,8 +231,9 @@ int main() {
testFrameCountIsADifferenceOfIndicesNotADuration();
testFrameCountRefusesEmptyInvertedAndUnknownRate();
testWindowStartingAtExactlyZero();
testNonFrameAlignedWindowAcceptsEveryEdgeConvention();
testNonAlignmentCanNeverExceedOneFrame();
testNonFrameAlignedWindowAcceptsItsAdjacentCounts();
testLengthDerivedAndSameConventionRenderersStayWithinOneFrame();
testMixedEdgeConventionsCanMissByTwoAndAreRefused();
testWholeItemWideningIsStillRefused();
testLargeShortfallIsStillRefused();
testEmptyRenderIsRefusedAgainstARealWindow();