fix S11 review: extract upsertPickedOverride, restore map_ on drag-cancel, clamp start to frames-1, pin startFrame>loopEnd wrap test

This commit is contained in:
2026-07-26 21:56:06 -04:00
parent 9743547dff
commit 38238c66ac
3 changed files with 68 additions and 33 deletions
+38
View File
@@ -592,6 +592,43 @@ static void testStartFrameWithLoop() {
for (std::size_t i = 60; i < out.size(); ++i) CHECK(approx(out[i], 0.5, 1e-4));
}
static void testStartAfterLoopEndWrapsIntoLoop() {
// Regression (S11 reviewer finding): if startFrame > loop.end (but still < frameCount),
// the voice's initial read head is past the loop end. The wrap-while in renderFrame must
// pull it back into [loopStart, loopEnd) on the very first frame, so the note sounds from
// somewhere inside the loop rather than running off the sample end silently.
//
// Setup: 100-frame sample; loop is [20, 40); startFrame = 60 (past loop.end = 40).
// loop body is a constant 0.5 so every frame inside it reads 0.5.
// After wrap: readPos starts inside [20, 40), first output frame == 0.5.
// Voice must stay active (loop sustains it) and emit the loop value, NOT go silent.
SampleData s;
s.frames.resize(100, 0.0f);
for (int i = 20; i < 40; ++i) s.frames[i] = 0.5f; // loop body
s.rootNote = 60;
s.startFrame = 60; // > loop.end (40), < frameCount (100)
s.loop.hasLoop = true;
s.loop.start = 20;
s.loop.end = 40;
Keymap km = Keymap::singleSampleChromatic(std::move(s));
VoiceEngine eng(1, km, flatAdsr());
eng.noteOn(60, 127); // unity ratio, full velocity
std::vector<AudioSample> out;
eng.render(out, 50);
// Voice must still be active — the usable loop keeps it alive indefinitely.
CHECK(eng.activeVoiceCount() == 1);
// Every frame after the first wrap must read 0.5 (the loop body). We skip the very
// first frame because the fractional-position wrap lands somewhere in [20,40) and the
// exact offset depends on how many loop lengths fit into 60; what matters is that the
// voice is alive and emitting the loop value, not 0.0 (pre-loop region).
for (std::size_t i = 5; i < out.size(); ++i) {
CHECK(approx(out[i], 0.5, 1e-4));
}
}
// ---------------------------------------------------------------------------
// velocity -> volume.
// ---------------------------------------------------------------------------
@@ -656,6 +693,7 @@ int main() {
testStartFrameZeroIsUnchanged();
testStartFrameOutOfRangeClampsToZero();
testStartFrameWithLoop();
testStartAfterLoopEndWrapsIntoLoop();
testVelocityToVolume();
testPolyphonyMixesAdditively();