test: S7xS11 compose — stereo+startFrame+loop share one read head; v4 stereo envelope + v2 zone overrides round-trip together
This commit is contained in:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user