Prove the render bounds at the boundary they cross, and name a short render whose count is exactly a millisecond-floored window
No truncation exists on our side of that boundary, so the read-back is the only evidence available for whether REAPER kept the window — and it fires on every tail mode, where only None was ever judged.
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
// 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), the verdict the offline backend refuses a capture on, and the predicate
|
||||
// rate), the verdict the offline backend refuses a capture on, the predicate
|
||||
// that decides whether REAPER's selected-items render source can express a
|
||||
// requested window at all.
|
||||
// requested window at all, and the two short-render diagnostics.
|
||||
|
||||
#include "../src/core/capture/render_window.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
using namespace reasampler::capture;
|
||||
|
||||
@@ -15,6 +16,10 @@ static int g_fail = 0;
|
||||
#define CHECK(cond) do { if(!(cond)) { \
|
||||
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
||||
|
||||
static bool contains(const std::string& haystack, const std::string& needle) {
|
||||
return haystack.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
// --- frameCountFor: the bounds equality, stated as a number ------------------
|
||||
|
||||
static void testFrameCountIsExactNotRounded() {
|
||||
@@ -226,6 +231,131 @@ static void testMultiItemUnionExtent() {
|
||||
CHECK(!itemExtentPrintsWindow(1.0, 4.0, 1.0, 9.0, 48000));
|
||||
}
|
||||
|
||||
// --- msFlooredEndFrameCount: the shape both live short renders had ------------
|
||||
|
||||
static void testMillisecondFlooredEndReproducesBothShortRenders() {
|
||||
// Both DAW observations, as arithmetic. 48 kHz, TailMode::None, start at 0: the
|
||||
// requested window's count, and the count its end floored to the millisecond
|
||||
// holds — which is what each render actually printed.
|
||||
CHECK(frameCountFor(0.0, 4.067797, 48000) == 195254);
|
||||
CHECK(msFlooredEndFrameCount(0.0, 4.067797, 48000) == 195216);
|
||||
CHECK(frameCountFor(0.0, 4.067797, 48000) -
|
||||
msFlooredEndFrameCount(0.0, 4.067797, 48000) == 38);
|
||||
|
||||
CHECK(frameCountFor(0.0, 1.655172, 48000) == 79448);
|
||||
CHECK(msFlooredEndFrameCount(0.0, 1.655172, 48000) == 79440);
|
||||
CHECK(frameCountFor(0.0, 1.655172, 48000) -
|
||||
msFlooredEndFrameCount(0.0, 1.655172, 48000) == 8);
|
||||
}
|
||||
|
||||
static void testTheSixDecimalDisplayDidNotCreateTheEffect() {
|
||||
// Both reported ends were printed to six decimals by the refusal. Each is one 4/4
|
||||
// bar — at 59 BPM and at 145 BPM — so the full-precision doubles behind them are
|
||||
// 240/59 and 240/145. Same counts either way: the display rounding is not what
|
||||
// produces the shortfall.
|
||||
CHECK(frameCountFor(0.0, 240.0 / 59.0, 48000) == 195254);
|
||||
CHECK(msFlooredEndFrameCount(0.0, 240.0 / 59.0, 48000) == 195216);
|
||||
CHECK(frameCountFor(0.0, 240.0 / 145.0, 48000) == 79448);
|
||||
CHECK(msFlooredEndFrameCount(0.0, 240.0 / 145.0, 48000) == 79440);
|
||||
}
|
||||
|
||||
static void testWindowAlreadyOnTheMillisecondGridLosesNothing() {
|
||||
// The "sometimes it works" case: a bar at 120 BPM is exactly 2 s.
|
||||
CHECK(msFlooredEndFrameCount(0.0, 2.0, 48000) == frameCountFor(0.0, 2.0, 48000));
|
||||
|
||||
// The binary-representation trap a bare floor would fall into. The premise, not an
|
||||
// outcome: 1.007 s is a whole millisecond that really does land BELOW 1007 ms in
|
||||
// double, so flooring it without a tolerance drops a millisecond from a window
|
||||
// already on the grid.
|
||||
CHECK(1.007 * 1000.0 < 1007.0);
|
||||
CHECK(frameCountFor(0.0, 1.007, 48000) == 48336);
|
||||
CHECK(msFlooredEndFrameCount(0.0, 1.007, 48000) == 48336);
|
||||
// Same end reached from a non-zero start, so nothing here rests on the window
|
||||
// beginning at 0.
|
||||
CHECK(msFlooredEndFrameCount(0.5, 1.007, 48000) ==
|
||||
frameCountFor(0.5, 1.007, 48000));
|
||||
}
|
||||
|
||||
static void testOneFrameOfRemainderStillFloors() {
|
||||
// The whole-millisecond tolerance must sit far below a frame, or it would swallow
|
||||
// the very remainder this diagnostic exists to find. One frame at 48 kHz is 20.8 us
|
||||
// — four orders of magnitude above the nanosecond tolerance.
|
||||
const double oneFrame = 1.0 / 48000.0;
|
||||
CHECK(frameCountFor(0.0, 1.0 + oneFrame, 48000) == 48001);
|
||||
CHECK(msFlooredEndFrameCount(0.0, 1.0 + oneFrame, 48000) == 48000);
|
||||
}
|
||||
|
||||
static void testMillisecondFloorAt44100WhereAMillisecondIsNotWholeFrames() {
|
||||
// 44.1 kHz: a millisecond is 44.1 frames, so a floored end cannot be described as
|
||||
// dropping a whole number of frames — the count still resolves exactly.
|
||||
CHECK(frameCountFor(0.0, 0.0105, 44100) == 463);
|
||||
CHECK(msFlooredEndFrameCount(0.0, 0.0105, 44100) == 441);
|
||||
// And a window that IS on the millisecond grid there is untouched, even though its
|
||||
// edge is not on a frame boundary.
|
||||
CHECK(frameCountFor(0.0, 0.010, 44100) == 441);
|
||||
CHECK(msFlooredEndFrameCount(0.0, 0.010, 44100) == 441);
|
||||
}
|
||||
|
||||
static void testASubMillisecondStartWouldNotHideItself() {
|
||||
// Both observations started at 0.000000s, the one value that hides a start-side
|
||||
// truncation. A window whose START carries a sub-millisecond remainder counts from
|
||||
// that exact start...
|
||||
const double start = 1.0001724, end = 2.0001724;
|
||||
CHECK(frameCountFor(start, end, 48000) == 48000);
|
||||
// ...so a start floored to the millisecond would print a DIFFERENT count — 8 frames
|
||||
// more, the same remainder the second observation lost off its end. A start-side
|
||||
// truncation is therefore visible to the same frame-count gate, not silent.
|
||||
CHECK(frameCountFor(1.000, end, 48000) == 48008);
|
||||
CHECK(!renderHonoredBounds(frameCountFor(start, end, 48000),
|
||||
frameCountFor(1.000, end, 48000)));
|
||||
}
|
||||
|
||||
// --- describeBoundsDrift: the read-back's verdict ------------------------------
|
||||
|
||||
static void testBoundsThatReadBackUnchangedDescribeNothing() {
|
||||
// The answer that proves the request crossed into REAPER intact — including for a
|
||||
// window whose end is nowhere near a millisecond boundary.
|
||||
CHECK(describeBoundsDrift(0.0, 4.067797, 0.0, 4.067797, 48000).empty());
|
||||
CHECK(describeBoundsDrift(1.0001724, 2.0001724, 1.0001724, 2.0001724, 48000).empty());
|
||||
}
|
||||
|
||||
static void testADriftedEndNamesBothWindowsAndBothCounts() {
|
||||
const std::string s =
|
||||
describeBoundsDrift(0.0, 4.067797, 0.0, 4.067, 48000);
|
||||
CHECK(!s.empty());
|
||||
// Both counts as literals from the DAW observation, not re-derived from the same
|
||||
// functions the sentence was built with.
|
||||
CHECK(contains(s, "195254")); // what the request asks for
|
||||
CHECK(contains(s, "195216")); // what the drifted window would hold
|
||||
CHECK(contains(s, "48000 Hz"));
|
||||
}
|
||||
|
||||
static void testTheReportPrintsEnoughDigitsToShowTheDrift() {
|
||||
// A report whose two numbers print identically is evidence of nothing. Two ends a
|
||||
// single ULP apart — far under the sixth decimal a shorter rendering would stop at
|
||||
// — must still read as two different numbers.
|
||||
const double asked = 4.067797;
|
||||
const double stored = std::nextafter(asked, 5.0);
|
||||
const std::string s = describeBoundsDrift(0.0, asked, 0.0, stored, 48000);
|
||||
CHECK(!s.empty());
|
||||
CHECK(!contains(s, "4.067797s, read back [0s, 4.067797s)"));
|
||||
}
|
||||
|
||||
static void testADriftedStartIsCaughtToo() {
|
||||
// The edge both observations could not test.
|
||||
const std::string s = describeBoundsDrift(1.0001724, 2.0, 1.000, 2.0, 48000);
|
||||
CHECK(!s.empty());
|
||||
CHECK(contains(s, "1.0001724"));
|
||||
}
|
||||
|
||||
static void testAnUnknownRateStillReportsTheDriftWithoutFrames() {
|
||||
// A project that never pinned a rate reads 0. The drift is still worth saying; a
|
||||
// frame count over an unknown rate is not.
|
||||
const std::string s = describeBoundsDrift(0.0, 4.067797, 0.0, 4.067, 0);
|
||||
CHECK(!s.empty());
|
||||
CHECK(!contains(s, "frames"));
|
||||
}
|
||||
|
||||
int main() {
|
||||
testFrameCountIsExactNotRounded();
|
||||
testFrameCountIsADifferenceOfIndicesNotADuration();
|
||||
@@ -244,6 +374,17 @@ int main() {
|
||||
testSubFrameDriftStillPrintsTheSameFrames();
|
||||
testUnknownRateFallsBackToExactEquality();
|
||||
testMultiItemUnionExtent();
|
||||
testMillisecondFlooredEndReproducesBothShortRenders();
|
||||
testTheSixDecimalDisplayDidNotCreateTheEffect();
|
||||
testWindowAlreadyOnTheMillisecondGridLosesNothing();
|
||||
testOneFrameOfRemainderStillFloors();
|
||||
testMillisecondFloorAt44100WhereAMillisecondIsNotWholeFrames();
|
||||
testASubMillisecondStartWouldNotHideItself();
|
||||
testBoundsThatReadBackUnchangedDescribeNothing();
|
||||
testADriftedEndNamesBothWindowsAndBothCounts();
|
||||
testTheReportPrintsEnoughDigitsToShowTheDrift();
|
||||
testADriftedStartIsCaughtToo();
|
||||
testAnUnknownRateStillReportsTheDriftWithoutFrames();
|
||||
|
||||
if (g_fail) { std::printf("%d check(s) FAILED\n", g_fail); return 1; }
|
||||
std::printf("render_window: all checks passed\n");
|
||||
|
||||
Reference in New Issue
Block a user