// Standalone tests for reasampler::instrument::map::trigger_seam — no VST3, no REAPER, no framework. // Same fast assert loop as the sibling pure tests. // // Covers triggerPlayLength: zero play length, startFrame set, startFrame past frameCount, // rounding, and the Finding 1 regression (start-point set — the case that was broken before // this module existed); plus effectiveLengthFraction, the spline fold every consumer of the // span must go through. #include "../src/core/instrument/map/trigger_seam.h" #include using namespace reasampler; using namespace reasampler::instrument::map; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // --- triggerPlayLength -------------------------------------------------------- static void testPlayLengthNoStartPoint() { // startFrame == 0 (no start marker): postStart = frameCount. // 1000 frames, lengthFraction 1.0 -> 1000. CHECK(triggerPlayLength(1.0, 1000, 0) == 1000); // 1000 frames, lengthFraction 0.5 -> round(500.0) = 500. CHECK(triggerPlayLength(0.5, 1000, 0) == 500); // 1000 frames, lengthFraction 0.333 -> round(333.0) = 333. CHECK(triggerPlayLength(0.333, 1000, 0) == 333); } static void testPlayLengthWithStartPoint() { // The Finding 1 regression: startFrame set, play length must be shorter. // frameCount=1000, startFrame=200 -> postStart=800. // lengthFraction 1.0 -> 800 (NOT 1000 as the pre-fix code produced). CHECK(triggerPlayLength(1.0, 1000, 200) == 800); // lengthFraction 0.5 -> round(400.0) = 400. CHECK(triggerPlayLength(0.5, 1000, 200) == 400); } static void testPlayLengthZeroFrameCount() { // No decoded audio -> 0. CHECK(triggerPlayLength(1.0, 0, 0) == 0); } static void testPlayLengthStartFramePastEnd() { // startFrame >= frameCount -> postStart clamped to 0 -> play length 0. CHECK(triggerPlayLength(1.0, 500, 500) == 0); CHECK(triggerPlayLength(1.0, 500, 600) == 0); } static void testPlayLengthRounding() { // round(0.5) = 1 (round half up via +0.5 truncation: 0.5 + 0.5 = 1.0 -> 1). CHECK(triggerPlayLength(0.5, 1, 0) == 1); // round(lengthFraction * 3): 0.4 * 3 = 1.2 -> 1. CHECK(triggerPlayLength(0.4, 3, 0) == 1); // 0.6 * 3 = 1.8 -> 2. CHECK(triggerPlayLength(0.6, 3, 0) == 2); } // --- effectiveLengthFraction -------------------------------------------------- // A drawn contour covers the FULL sample length, so the stored %-knob goes inert. It is not // cleared, though — a pre-spline value survives in the record, and every reader that takes it // raw plays, draws or bakes a fraction of the take. static void testDrawnEnvelopeFoldsTheFractionToOne() { PlayParams p; p.trigger.lengthFraction = 0.25; CHECK(effectiveLengthFraction(p) == 0.25); // staged: the knob is what it says p.ampSpline.mode = EnvMode::Spline; CHECK(effectiveLengthFraction(p) == 1.0); // drawn: the whole take CHECK(p.trigger.lengthFraction == 0.25); // …and the stored value is untouched // Flipping back restores the stored fraction, which is why the fold cannot be a write. p.ampSpline.mode = EnvMode::Staged; CHECK(effectiveLengthFraction(p) == 0.25); } // The fold reads the SAME predicate the engine's Gate refusal does, gating flags included: a // Spline mode on a DISABLED pitch/filter envelope binds no cursor, so it must not fold. static void testDisabledEnvelopesDoNotFoldTheFraction() { PlayParams p; p.trigger.lengthFraction = 0.5; p.pitchSpline.mode = EnvMode::Spline; CHECK(effectiveLengthFraction(p) == 0.5); // pitchEnv.enabled is still false p.pitchEnv.enabled = true; CHECK(effectiveLengthFraction(p) == 1.0); p.pitchEnv.enabled = false; p.filterSpline.mode = EnvMode::Spline; CHECK(effectiveLengthFraction(p) == 0.5); // filter.enabled is still false p.filter.enabled = true; CHECK(effectiveLengthFraction(p) == 1.0); } int main() { testDrawnEnvelopeFoldsTheFractionToOne(); testDisabledEnvelopesDoNotFoldTheFraction(); testPlayLengthNoStartPoint(); testPlayLengthWithStartPoint(); testPlayLengthZeroFrameCount(); testPlayLengthStartFramePastEnd(); testPlayLengthRounding(); if (g_fail == 0) std::printf("trigger_seam: all tests passed\n"); else std::printf("trigger_seam: %d FAILED\n", g_fail); return g_fail == 0 ? 0 : 1; }