// Standalone tests for reasampler::card_meta — no REAPER, no test framework. Asserts the // L7 decorative overlay formatters: bars.beats.subdivisions (F1 tempo+meter stamp) and // seconds.milliseconds. // // Covers: bar-1 origin (zero length), sub-bar, exact bar boundary rollover, multi-bar, // long capture, non-4/4 meters (3/4 and 6/8), unstamped meter -> blank, unknown tempo -> // blank (s.ms still derivable); s.ms zero / sub-second / multi-second / ms carry / negative. #include "../src/core/ui/card_meta.h" #include #include using namespace reasampler; using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // Beat length in 4/4 at 120 BPM: (60/120)*(4/4) = 0.5 s/beat, so a 4/4 bar = 2.0 s. // --- bars.beats.subdivisions ------------------------------------------------- static void testZeroLengthIsBarOneOrigin() { // Zero length -> the musical origin, "1.1.00". CHECK(formatBarsBeats(MusicalLength{0.0, 120.0, 4, 4}) == "1.1.00"); } static void testSubBeat() { // 0.25 s at 120 BPM 4/4 = 0.5 beats -> bar 1, beat 1, .50. CHECK(formatBarsBeats(MusicalLength{0.25, 120.0, 4, 4}) == "1.1.50"); } static void testWholeBeatWithinBar() { // 0.5 s = exactly 1 beat -> bar 1, beat 2, .00. CHECK(formatBarsBeats(MusicalLength{0.5, 120.0, 4, 4}) == "1.2.00"); } static void testExactBarBoundaryRollsOver() { // 2.0 s = exactly 4 beats = 1 bar in 4/4 -> rolls to bar 2, beat 1 (NOT "1.5.00"). CHECK(formatBarsBeats(MusicalLength{2.0, 120.0, 4, 4}) == "2.1.00"); } static void testMultiBar() { // 5.0 s at 120 4/4 = 10 beats = 2 bars + 2 beats -> "3.3.00". CHECK(formatBarsBeats(MusicalLength{5.0, 120.0, 4, 4}) == "3.3.00"); } static void testLongCaptureNoBarCap() { // 256 s at 120 4/4 = 512 beats = 128 bars exactly -> bar 129, beat 1. CHECK(formatBarsBeats(MusicalLength{256.0, 120.0, 4, 4}) == "129.1.00"); } static void testThreeFourMeter() { // 3/4 at 120 BPM: beat = (60/120)*(4/4)=0.5 s, bar = 3 beats = 1.5 s. 1.5 s -> bar 2,1. CHECK(formatBarsBeats(MusicalLength{1.5, 120.0, 3, 4}) == "2.1.00"); // 1.0 s = 2 beats -> bar 1, beat 3. CHECK(formatBarsBeats(MusicalLength{1.0, 120.0, 3, 4}) == "1.3.00"); } static void testSixEightMeter() { // 6/8 at 120 BPM: an eighth-beat = (60/120)*(4/8) = 0.25 s; bar = 6 beats = 1.5 s. // 1.5 s -> bar 2, beat 1. CHECK(formatBarsBeats(MusicalLength{1.5, 120.0, 6, 8}) == "2.1.00"); // 0.25 s = exactly 1 eighth-beat -> bar 1, beat 2. CHECK(formatBarsBeats(MusicalLength{0.25, 120.0, 6, 8}) == "1.2.00"); } static void testUnstampedMeterIsBlank() { // 0/0 (pre-L7 sample) -> no musical read-out. CHECK(formatBarsBeats(MusicalLength{3.0, 120.0, 0, 0}).empty()); CHECK(formatBarsBeats(MusicalLength{3.0, 120.0, 4, 0}).empty()); // partial stamp also blank CHECK(formatBarsBeats(MusicalLength{3.0, 120.0, 0, 4}).empty()); } static void testUnknownTempoIsBlank() { // Tempo 0 -> no musical read-out even with a meter (cannot derive beats). CHECK(formatBarsBeats(MusicalLength{3.0, 0.0, 4, 4}).empty()); } static void testNegativeLengthClampsToOrigin() { // Defensive: a negative length reads as the origin, not garbage. CHECK(formatBarsBeats(MusicalLength{-5.0, 120.0, 4, 4}) == "1.1.00"); } // --- seconds.milliseconds ---------------------------------------------------- static void testSecondsMsZero() { CHECK(formatSecondsMs(0.0) == "0.000"); } static void testSecondsMsSubSecond() { CHECK(formatSecondsMs(0.5) == "0.500"); } static void testSecondsMsMultiSecond() { CHECK(formatSecondsMs(1.5) == "1.500"); CHECK(formatSecondsMs(62.037) == "62.037"); } static void testSecondsMsNegativeClamps() { CHECK(formatSecondsMs(-1.0) == "0.000"); } static void testSecondsMsNearWholeSecondCarry() { // 0.9999 s: floor to ms could hit 999 or 1000; the carry keeps it well-formed. const std::string r = formatSecondsMs(0.9999); CHECK(r == "0.999" || r == "1.000"); } // --- the card's name strip ---------------------------------------------------- // The shipping cell size (panel_state.h's kGrid). static const Rect kCell{40, 100, 140, 84}; static void testNameStripSitsAcrossTheTopOfTheCell() { const Rect s = cardNameStrip(kCell); CHECK(s.x == kCell.x + kCardStripPad); CHECK(s.y == kCell.y + 1); // clear of the selection border CHECK(s.width == kCell.width - 2 * kCardStripPad); CHECK(s.height == kCardStripHeight); } static void testNameStripNeverOverlapsTheLengthReadOut() { // The read-out occupies the bottom kCardStripHeight of the same cell. const Rect s = cardNameStrip(kCell); CHECK(s.bottom() <= kCell.bottom() - kCardStripHeight); } static void testNameStripStaysInsideTheCell() { const Rect s = cardNameStrip(kCell); CHECK(s.x >= kCell.x); CHECK(s.right() <= kCell.right()); CHECK(s.y >= kCell.y); CHECK(s.bottom() <= kCell.bottom()); } static void testNameStripSuppressedOnATooShortCell() { // Below three strip-heights the card would be text with a sliver of waveform — draw // no name rather than bury the thumbnail. CHECK(cardNameStrip(Rect{0, 0, 140, 3 * kCardStripHeight - 1}).empty()); CHECK(!cardNameStrip(Rect{0, 0, 140, 3 * kCardStripHeight}).empty()); } static void testNameStripSuppressedOnATooNarrowCell() { CHECK(cardNameStrip(Rect{0, 0, 2 * kCardStripPad, 84}).empty()); CHECK(cardNameStrip(Rect{0, 0, 0, 84}).empty()); } int main() { testNameStripSitsAcrossTheTopOfTheCell(); testNameStripNeverOverlapsTheLengthReadOut(); testNameStripStaysInsideTheCell(); testNameStripSuppressedOnATooShortCell(); testNameStripSuppressedOnATooNarrowCell(); testZeroLengthIsBarOneOrigin(); testSubBeat(); testWholeBeatWithinBar(); testExactBarBoundaryRollsOver(); testMultiBar(); testLongCaptureNoBarCap(); testThreeFourMeter(); testSixEightMeter(); testUnstampedMeterIsBlank(); testUnknownTempoIsBlank(); testNegativeLengthClampsToOrigin(); testSecondsMsZero(); testSecondsMsSubSecond(); testSecondsMsMultiSecond(); testSecondsMsNegativeClamps(); testSecondsMsNearWholeSecondCarry(); if (g_fail == 0) std::printf("card_meta: all tests passed\n"); else std::printf("card_meta: %d CHECK(s) FAILED\n", g_fail); return g_fail == 0 ? 0 : 1; }