Preserve's period detection: probes are placed by position, and a sustain loop is the span analysed

This commit is contained in:
2026-08-02 03:21:06 -04:00
parent 334022c0f1
commit 9228e93750
6 changed files with 299 additions and 22 deletions
+40
View File
@@ -926,6 +926,45 @@ static void testBuildSampleDataDetectsThirtyHertzSourcePeriod() {
CHECK(std::fabs(sd.sourcePeriodFrames - 1470.0) < 2.0); // 44100 / 30 Hz
}
// The span half of the same wire: buildSampleData must hand detection the LOOP region when the
// capture carries one, not the whole decoded PCM. Asserted through the real build for the same
// reason as the test above — period_detect's own coverage cannot see which span the loader picks.
static void testBuildSampleDataDetectsOverTheSustainLoopNotTheWholeSource() {
const int rate = 44100;
const std::size_t frames = 120000;
const std::int64_t loopStart = 60000;
const double kPi = 3.14159265358979323846;
const double loopPeriod = static_cast<double>(rate) / 30.0; // 1470 frames
// Head at 147 Hz, looped tail at 30 Hz: analysed whole, the probes split two-and-two and
// detection correctly refuses. Analysed over the loop, the 30 Hz sustain is unambiguous.
std::vector<AudioSample> pcm(frames);
double phase = 0.0;
for (std::size_t i = 0; i < frames; ++i) {
phase += 2.0 * kPi / (i < static_cast<std::size_t>(loopStart) ? 300.0 : loopPeriod);
pcm[i] = static_cast<float>(std::sin(phase));
}
InstrumentParams noLoop;
const SampleData bare = buildSampleData(resolveCapture(ref("b/a.wav", 60), noLoop),
DecodedPcm{pcm, rate, {}});
CHECK(bare.sourcePeriodFrames == 0.0); // no loop -> whole source -> no ONE period
InstrumentParams looped;
looped.loopOverride = SampleLoop{true, loopStart, static_cast<std::int64_t>(frames)};
const SampleData sd = buildSampleData(resolveCapture(ref("b/a.wav", 60), looped),
DecodedPcm{pcm, rate, {}});
CHECK(std::fabs(sd.sourcePeriodFrames - loopPeriod) < 2.0);
// A loop too short to host the full search band falls back to the whole source rather than
// to none — here that whole source has no one period, so the answer is the bare one above.
InstrumentParams shortLoop;
shortLoop.loopOverride = SampleLoop{true, 118000, static_cast<std::int64_t>(frames)};
const SampleData shortSd = buildSampleData(resolveCapture(ref("b/a.wav", 60), shortLoop),
DecodedPcm{pcm, rate, {}});
CHECK(shortSd.sourcePeriodFrames == bare.sourcePeriodFrames);
}
static void testBuildSampleDataCarriesTheVelocityCurve() {
InstrumentParams p;
p.velocityCurve = VelocityCurve::linear();
@@ -994,6 +1033,7 @@ int main() {
testBuildSampleDataEmptyPcmIsUnplayable();
testBuildSampleDataCarriesTheVelocityCurve();
testBuildSampleDataDetectsThirtyHertzSourcePeriod();
testBuildSampleDataDetectsOverTheSustainLoopNotTheWholeSource();
if (g_fail == 0) std::printf("sample_map: all tests passed\n");
return g_fail != 0;