instrument: one staged-envelope system — per-segment curves, the sustain-less AHD, and a shared overlay for all three envelopes

Trigger's fade pair folds into the AHD (and goes live); the release anchors right;
Preserve rings its synthetic tail out instead of cutting it. Payload v10.
This commit is contained in:
2026-07-31 08:37:57 -04:00
parent 87d7ceb066
commit 13e8c5c4d9
51 changed files with 3406 additions and 1812 deletions
+3 -95
View File
@@ -1,10 +1,9 @@
// 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); 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).
// 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).
#include "../src/core/instrument/map/trigger_seam.h"
@@ -58,85 +57,6 @@ static void testPlayLengthRounding() {
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();
@@ -144,18 +64,6 @@ int main() {
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;