S11: waveform view with draggable start/loop markers + zero-crossing snap
Pure waveform_view module (frame<->pixel markers, zero-crossing snap); per-zone loop/start overrides on PerformanceZone with self-versioning zone payload; core start-point read offset; editor waveform surface with drag machine.
This commit is contained in:
@@ -520,6 +520,78 @@ static void testAbsentLoopGoesSilent() {
|
||||
for (std::size_t i = 60; i < out.size(); ++i) CHECK(approx(out[i], 0.0, 1e-6));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// start point (S11): the voice's initial read position is SampleData::startFrame.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void testStartFrameOffsetsInitialRead() {
|
||||
// A per-frame ramp (frame i holds i*0.01) so the first rendered value pinpoints the
|
||||
// read position. startFrame = 30 -> the first output frame reads frame 30 (0.30).
|
||||
SampleData s;
|
||||
s.frames.resize(100);
|
||||
for (int i = 0; i < 100; ++i) s.frames[i] = static_cast<float>(i) * 0.01f;
|
||||
s.rootNote = 60;
|
||||
s.startFrame = 30;
|
||||
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> out;
|
||||
eng.render(out, 3);
|
||||
CHECK(approx(out[0], 0.30, 1e-4)); // starts at frame 30, not 0
|
||||
CHECK(approx(out[1], 0.31, 1e-4)); // advances by unity ratio
|
||||
CHECK(approx(out[2], 0.32, 1e-4));
|
||||
}
|
||||
|
||||
static void testStartFrameZeroIsUnchanged() {
|
||||
// startFrame default 0 is exactly the pre-S11 behavior: read begins at frame 0.
|
||||
SampleData s;
|
||||
s.frames.resize(20);
|
||||
for (int i = 0; i < 20; ++i) s.frames[i] = static_cast<float>(i) * 0.05f;
|
||||
s.rootNote = 60; // startFrame stays 0
|
||||
Keymap km = Keymap::singleSampleChromatic(std::move(s));
|
||||
VoiceEngine eng(1, km, flatAdsr());
|
||||
eng.noteOn(60, 127);
|
||||
std::vector<AudioSample> out;
|
||||
eng.render(out, 1);
|
||||
CHECK(approx(out[0], 0.0, 1e-6)); // frame 0
|
||||
}
|
||||
|
||||
static void testStartFrameOutOfRangeClampsToZero() {
|
||||
// A start point at/past the sample end degrades to frame 0 (play from the top), never an
|
||||
// out-of-bounds read that would start the voice already exhausted.
|
||||
SampleData s = dcSample(10, 60); // 10 frames of 1.0
|
||||
s.startFrame = 10; // == frameCount: out of range
|
||||
Keymap km = Keymap::singleSampleChromatic(std::move(s));
|
||||
VoiceEngine eng(1, km, flatAdsr());
|
||||
eng.noteOn(60, 127);
|
||||
std::vector<AudioSample> out;
|
||||
eng.render(out, 5);
|
||||
// Reads from frame 0: the DC sample plays its 1.0 body rather than an immediate idle.
|
||||
CHECK(eng.activeVoiceCount() == 1);
|
||||
CHECK(approx(out[0], 1.0, 1e-4));
|
||||
}
|
||||
|
||||
static void testStartFrameWithLoop() {
|
||||
// Start point and loop compose: begin reading mid-sample, then sustain the loop region.
|
||||
SampleData s;
|
||||
s.frames.resize(40);
|
||||
for (int i = 0; i < 40; ++i) s.frames[i] = static_cast<float>(i) * 0.01f;
|
||||
for (int i = 20; i < 40; ++i) s.frames[i] = 0.5f; // loop body
|
||||
s.rootNote = 60;
|
||||
s.startFrame = 10; // begin at frame 10
|
||||
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);
|
||||
std::vector<AudioSample> out;
|
||||
eng.render(out, 200);
|
||||
CHECK(approx(out[0], 0.10, 1e-4)); // started at frame 10
|
||||
CHECK(eng.activeVoiceCount() == 1); // loop sustains it
|
||||
for (std::size_t i = 60; i < out.size(); ++i) CHECK(approx(out[i], 0.5, 1e-4));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// velocity -> volume.
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -580,6 +652,10 @@ int main() {
|
||||
testZeroLengthLoopGoesSilent();
|
||||
testSingleFrameLoop();
|
||||
testAbsentLoopGoesSilent();
|
||||
testStartFrameOffsetsInitialRead();
|
||||
testStartFrameZeroIsUnchanged();
|
||||
testStartFrameOutOfRangeClampsToZero();
|
||||
testStartFrameWithLoop();
|
||||
testVelocityToVolume();
|
||||
testPolyphonyMixesAdditively();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user