test: S7xS11 compose — stereo+startFrame+loop share one read head; v4 stereo envelope + v2 zone overrides round-trip together

This commit is contained in:
2026-07-26 22:09:45 -04:00
parent 75f717bd47
commit 7a191bde61
2 changed files with 86 additions and 0 deletions
+47
View File
@@ -783,6 +783,52 @@ static void testStereoRenderNullBufferIsNoOp() {
for (float v : buf) CHECK(approx(v, 0.0, 1e-9)); // untouched
}
static void testStereoStartFrameLoopShareOneReadHead() {
// S7 x S11 compose: a STEREO sample with a startFrame AND a sustain loop must read BOTH
// channels from the SAME single read head — one offset, one loop wrap, applied to L and R
// identically (only the sampled value differs). A per-frame L/R ramp that is a fixed offset
// apart (R = L + 0.5) pins the read position on both channels: if the stereo path ever gave
// L and R independent heads, the constant L->R offset would break at the start jump or the
// loop seam.
SampleData s;
s.frames.resize(40);
s.framesR.resize(40);
for (int i = 0; i < 40; ++i) {
s.frames[i] = static_cast<float>(i) * 0.01f; // L: 0.00 .. 0.39
s.framesR[i] = static_cast<float>(i) * 0.01f + 0.5f; // R: L + 0.5, everywhere
}
s.rootNote = 60;
s.startFrame = 10; // begin BOTH channels at frame 10
s.loop.hasLoop = true;
s.loop.start = 20;
s.loop.end = 30; // loop [20,30): frames 20..29
CHECK(s.channelCount() == 2);
Keymap km = Keymap::singleSampleChromatic(std::move(s));
VoiceEngine eng(1, km, flatAdsr());
eng.noteOn(60, 127); // unity ratio, full velocity, flat gain
std::vector<AudioSample> left(200, 0.f), right(200, 0.f);
eng.render(left.data(), right.data(), 200);
// First frame: both channels start at frame 10 (L=0.10, R=0.60) — the shared start offset.
CHECK(approx(left[0], 0.10, 1e-4));
CHECK(approx(right[0], 0.60, 1e-4));
// The loop sustains the voice indefinitely.
CHECK(eng.activeVoiceCount() == 1);
// At every rendered frame R - L == 0.5 exactly: both channels read the SAME frame index
// (one read head) through the start jump and every loop wrap. A per-channel head drift would
// break this invariant at the seam.
for (std::size_t i = 0; i < left.size(); ++i) {
CHECK(approx(right[i] - left[i], 0.5, 1e-4));
}
// Once fully inside the loop (start=10 -> reaches loop.start=20 within a handful of unity-ratio
// frames), every L value sits in the loop band [0.20, 0.30): the shared head is sustaining the
// loop region on both channels, never running off the sample end.
for (std::size_t i = 15; i < left.size(); ++i) {
CHECK(left[i] >= 0.20 - 1e-4 && left[i] < 0.30 + 1e-4);
}
}
int main() {
testChromaticSingleRoot();
testZonedRangesBoundaries();
@@ -815,6 +861,7 @@ int main() {
testStereoRenderAdvancesLikeMonoRepitch();
testStereoRenderSumsVoicesPerChannel();
testStereoRenderNullBufferIsNoOp();
testStereoStartFrameLoopShareOneReadHead();
if (g_fail == 0) {
std::printf("all sampler_core tests passed\n");