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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user