fix(trigger-seam): thread startFrame into both envelope converters; extract pure triggerPlayLength module + tests
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
// Standalone tests for reasampler::vst::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); framesToFadeFraction (zero play length, basic ratio); fadeFractionToFrames
|
||||
// (zero play length, rounding); round-trip fidelity; the Finding 1 regression (start-point
|
||||
// set — the case that was broken before this module existed).
|
||||
|
||||
#include "../src/vst/trigger_seam.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace reasampler::vst;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// --- framesToFadeFraction -----------------------------------------------------
|
||||
|
||||
static void testFramesToFadeFractionBasic() {
|
||||
// 100 frames fade over 1000 play length -> 0.1.
|
||||
const double frac = framesToFadeFraction(100, 1000);
|
||||
CHECK(frac > 0.0999 && frac < 0.1001);
|
||||
}
|
||||
|
||||
static void testFramesToFadeFractionZeroPlayLength() {
|
||||
// Degenerate: zero play length -> 0.0 (no division by zero).
|
||||
CHECK(framesToFadeFraction(100, 0) == 0.0);
|
||||
CHECK(framesToFadeFraction(0, 0) == 0.0);
|
||||
}
|
||||
|
||||
static void testFramesToFadeFractionFullSpan() {
|
||||
// fadeFrames == playLength -> fraction 1.0.
|
||||
const double frac = framesToFadeFraction(500, 500);
|
||||
CHECK(frac > 0.9999 && frac < 1.0001);
|
||||
}
|
||||
|
||||
// --- fadeFractionToFrames -----------------------------------------------------
|
||||
|
||||
static void testFadeFractionToFramesBasic() {
|
||||
// 0.1 of 1000 play length -> round(100.0) = 100.
|
||||
CHECK(fadeFractionToFrames(0.1, 1000) == 100);
|
||||
}
|
||||
|
||||
static void testFadeFractionToFramesZeroPlayLength() {
|
||||
// Degenerate: play length 0 -> 0 frames.
|
||||
CHECK(fadeFractionToFrames(0.5, 0) == 0);
|
||||
}
|
||||
|
||||
static void testFadeFractionToFramesRounding() {
|
||||
// 0.333... of 3 -> round(1.0) = 1.
|
||||
CHECK(fadeFractionToFrames(1.0 / 3.0, 3) == 1);
|
||||
// 0.5 of 3 -> round(1.5) = 2.
|
||||
CHECK(fadeFractionToFrames(0.5, 3) == 2);
|
||||
}
|
||||
|
||||
// --- Round-trip ---------------------------------------------------------------
|
||||
|
||||
static void testRoundTripNoStartPoint() {
|
||||
// Pack then unpack: fadeInFrames should survive (within 1 frame of rounding).
|
||||
// frameCount=44100, startFrame=0, lengthFraction=1.0 -> playLength=44100.
|
||||
// fadeInFrames = 2205 (5% of 44100).
|
||||
const std::int64_t fadeIn = 2205;
|
||||
const std::int64_t playLen = triggerPlayLength(1.0, 44100, 0);
|
||||
const double frac = framesToFadeFraction(fadeIn, playLen);
|
||||
const std::int64_t recovered = fadeFractionToFrames(frac, playLen);
|
||||
// Should be exact (2205 / 44100 * 44100 = 2205.0).
|
||||
CHECK(recovered == fadeIn);
|
||||
}
|
||||
|
||||
static void testRoundTripWithStartPoint() {
|
||||
// The Finding 1 case: startFrame set. frameCount=44100, startFrame=8820 (20%).
|
||||
// postStart=35280, lengthFraction=1.0 -> playLength=35280.
|
||||
// fadeInFrames = 1764 (5% of 35280).
|
||||
const std::int64_t frameCount = 44100;
|
||||
const std::int64_t startFrame = 8820;
|
||||
const std::int64_t fadeIn = 1764;
|
||||
const std::int64_t playLen = triggerPlayLength(1.0, frameCount, startFrame);
|
||||
CHECK(playLen == 35280);
|
||||
const double frac = framesToFadeFraction(fadeIn, playLen);
|
||||
const std::int64_t recovered = fadeFractionToFrames(frac, playLen);
|
||||
CHECK(recovered == fadeIn);
|
||||
}
|
||||
|
||||
static void testRoundTripFadeGreaterThanSpan() {
|
||||
// fadeFrames > playLength -> fraction > 1 (returned unclamped; the overlay clamps at draw).
|
||||
// The shell is responsible for clamping before writing AmpEnvelope.
|
||||
const std::int64_t playLen = 100;
|
||||
const std::int64_t fadeIn = 150;
|
||||
const double frac = framesToFadeFraction(fadeIn, playLen);
|
||||
CHECK(frac > 1.0); // intentionally unclamped from this module's perspective
|
||||
// The round-trip still recovers the original fade, so the shell can clamp after.
|
||||
const std::int64_t recovered = fadeFractionToFrames(frac, playLen);
|
||||
CHECK(recovered == fadeIn);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testPlayLengthNoStartPoint();
|
||||
testPlayLengthWithStartPoint();
|
||||
testPlayLengthZeroFrameCount();
|
||||
testPlayLengthStartFramePastEnd();
|
||||
testPlayLengthRounding();
|
||||
|
||||
testFramesToFadeFractionBasic();
|
||||
testFramesToFadeFractionZeroPlayLength();
|
||||
testFramesToFadeFractionFullSpan();
|
||||
|
||||
testFadeFractionToFramesBasic();
|
||||
testFadeFractionToFramesZeroPlayLength();
|
||||
testFadeFractionToFramesRounding();
|
||||
|
||||
testRoundTripNoStartPoint();
|
||||
testRoundTripWithStartPoint();
|
||||
testRoundTripFadeGreaterThanSpan();
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user