// 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 // that decides whether REAPER's selected-items render source can express a // requested window at all. #include "../src/core/capture/render_window.h" #include #include using namespace reasampler::capture; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // --- frameCountFor: the bounds equality, stated as a number ------------------ static void testFrameCountIsExactNotRounded() { // A 1.5 s window at 48 kHz is exactly 72000 frames — the number a capture of // that range must produce. No rounding slack in either direction. CHECK(frameCountFor(2.0, 3.5, 48000) == 72000); // The same duration at a different offset still counts the same frames when // both edges are frame-aligned. CHECK(frameCountFor(10.0, 11.5, 48000) == 72000); // 44.1 kHz: 0.5 s = 22050 frames. CHECK(frameCountFor(1.0, 1.5, 44100) == 22050); } static void testFrameCountIsADifferenceOfIndicesNotADuration() { // Both edges land mid-frame at 100 Hz (0.005 s = half a frame). Rounding the // DURATION would give 1 frame; rounding each EDGE gives 0.005 -> frame 1 and // 0.015 -> frame 2, i.e. 1 frame. Shift the window so the edges round apart // and the count changes — the property that makes this a window, not a length. CHECK(frameCountFor(0.005, 0.015, 100) == 1); CHECK(frameCountFor(0.004, 0.016, 100) == 2); } static void testFrameCountRefusesEmptyInvertedAndUnknownRate() { CHECK(frameCountFor(3.0, 3.0, 48000) == 0); // empty CHECK(frameCountFor(3.0, 1.0, 48000) == 0); // inverted CHECK(frameCountFor(1.0, 2.0, 0) == 0); // rate unknown 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(std::floor(length)))); CHECK(renderHonoredBounds(expected, static_cast(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() { // The defect this whole module exists for: a 1 s selection inside a 30 s item. // The selected-items source would print the item's 30 s, not the 1 s asked for, // so the capture must NOT take that path. CHECK(!itemExtentPrintsWindow(5.0, 6.0, /*item*/ 0.0, 30.0, 48000)); } static void testRangeWiderThanItemCannotBeExpressedEither() { // The same violation in the other direction: a 10 s selection over a 6 s item // would print 6 s. Under-printing is a bounds violation exactly as much as // over-printing is. CHECK(!itemExtentPrintsWindow(0.0, 10.0, /*item*/ 2.0, 8.0, 48000)); } static void testEachEdgeAloneDisqualifies() { // Matching start, drifting end. CHECK(!itemExtentPrintsWindow(2.0, 8.0, 2.0, 9.0, 48000)); // Matching end, drifting start. CHECK(!itemExtentPrintsWindow(2.0, 8.0, 1.0, 8.0, 48000)); } static void testExtentEqualToWindowIsExpressible() { // The regression floor: a capture whose range IS the item's extent keeps the // selected-items render, byte-identical to what it produces today. CHECK(itemExtentPrintsWindow(2.0, 8.0, 2.0, 8.0, 48000)); } static void testSubFrameDriftStillPrintsTheSameFrames() { // A time selection snapped a fraction of a sample off the item edge prints the // identical frames, so it must NOT be pushed onto the time-bounded path — that // would swap the render mechanism under a capture that was already exact. const double eighthOfAFrameAt48k = 1.0 / (48000.0 * 8.0); CHECK(itemExtentPrintsWindow(2.0 + eighthOfAFrameAt48k, 8.0 - eighthOfAFrameAt48k, 2.0, 8.0, 48000)); // A full frame of drift is a real difference and must disqualify. const double oneFrameAt48k = 1.0 / 48000.0; CHECK(!itemExtentPrintsWindow(2.0 + oneFrameAt48k, 8.0, 2.0, 8.0, 48000)); } static void testUnknownRateFallsBackToExactEquality() { // With no project rate there is no frame grid to compare on. Exact equality // still recognizes the regression floor... CHECK(itemExtentPrintsWindow(2.0, 8.0, 2.0, 8.0, 0)); // ...and anything else takes the time-bounded render, which honors the request // whatever the rate turns out to be. const double eighthOfAFrameAt48k = 1.0 / (48000.0 * 8.0); CHECK(!itemExtentPrintsWindow(2.0 + eighthOfAFrameAt48k, 8.0, 2.0, 8.0, 0)); CHECK(!itemExtentPrintsWindow(5.0, 6.0, 0.0, 30.0, 0)); } static void testMultiItemUnionExtent() { // Two items spanning 1..4 and 6..9 present a 1..9 union extent to the render. // A selection over the whole union is expressible; one over only the first // item's half is not. CHECK(itemExtentPrintsWindow(1.0, 9.0, 1.0, 9.0, 48000)); CHECK(!itemExtentPrintsWindow(1.0, 4.0, 1.0, 9.0, 48000)); } int main() { testFrameCountIsExactNotRounded(); testFrameCountIsADifferenceOfIndicesNotADuration(); testFrameCountRefusesEmptyInvertedAndUnknownRate(); testWindowStartingAtExactlyZero(); testNonFrameAlignedWindowAcceptsEveryEdgeConvention(); testNonAlignmentCanNeverExceedOneFrame(); testWholeItemWideningIsStillRefused(); testLargeShortfallIsStillRefused(); testEmptyRenderIsRefusedAgainstARealWindow(); testRangeInsideItemCannotBeExpressed(); testRangeWiderThanItemCannotBeExpressedEither(); testEachEdgeAloneDisqualifies(); testExtentEqualToWindowIsExpressible(); testSubFrameDriftStillPrintsTheSameFrames(); testUnknownRateFallsBackToExactEquality(); testMultiItemUnionExtent(); if (g_fail) { std::printf("%d check(s) FAILED\n", g_fail); return 1; } std::printf("render_window: all checks passed\n"); return 0; }