Γ-W1-T7: make Preserve's splices pitch-synchronous — the jump is a whole number of the source's own period, detected once at load
30 Hz out-of-band energy 15.45% -> 0.00%; the 29 Hz rate-2.0 detune -133 -> +0 cents. An unknown period keeps the fixed-window geometry bit for bit. The detector cannot reach process(): sampler_core does not link it.
This commit is contained in:
@@ -3214,7 +3214,10 @@ static void testPreserveStretchThirtyTwoVoicesHoldUp() {
|
||||
const std::size_t blockFrames = 44100; // one second of audio
|
||||
const std::size_t voiceCount = 32;
|
||||
const int kWarmupReps = 2;
|
||||
const int kTimedReps = 7;
|
||||
// 5 rather than 7: the source-period rows below doubled the row count, and run-to-run
|
||||
// spread on this machine is ~5% either way, so the extra reps bought precision the number
|
||||
// does not carry while costing the Debug gate real seconds.
|
||||
const int kTimedReps = 5;
|
||||
SampleData s = stretchProbeSample(200000, true);
|
||||
s.loop.hasLoop = true; // held notes: all 32 sound for the whole run
|
||||
s.loop.start = 40000;
|
||||
@@ -3223,7 +3226,24 @@ static void testPreserveStretchThirtyTwoVoicesHoldUp() {
|
||||
|
||||
// 1.0 is the reference: it is the cost the shipped Preserve read already carries, so the
|
||||
// two stretched rows are read as a delta against it rather than in isolation.
|
||||
for (double rate : {1.0, 0.5, 2.0}) {
|
||||
//
|
||||
// The last two rows carry a SOURCE PERIOD, which is where the pitch-synchronous splice can
|
||||
// cost something: the jump becomes a whole number of periods, and when that is shorter than
|
||||
// the window the splice cadence rises in proportion — more correlation searches per second.
|
||||
// 1470 (30 Hz at 44.1k) is the worst realistic case in the audible band, forcing a jump of
|
||||
// 2/3 the window and therefore 1.5x the searches. 220.5 (200 Hz) is the typical one: ten
|
||||
// periods land exactly on the window, so the cadence is unchanged and the row should read
|
||||
// as the no-period one — which is the measurement that separates "the mechanism costs
|
||||
// something" from "a shorter jump costs something". There is no per-frame cost either way:
|
||||
// the jump is resolved once at note-on.
|
||||
struct Row { double rate; double period; };
|
||||
const Row rows[] = {
|
||||
{1.0, 0.0}, {0.5, 0.0}, {2.0, 0.0},
|
||||
{1.0, 220.5}, {1.0, 1470.0}, {2.0, 1470.0},
|
||||
};
|
||||
for (const Row& row : rows) {
|
||||
const double rate = row.rate;
|
||||
s.sourcePeriodFrames = row.period;
|
||||
std::vector<double> nsPerVoiceFrame;
|
||||
nsPerVoiceFrame.reserve(kTimedReps);
|
||||
for (int rep = 0; rep < kWarmupReps + kTimedReps; ++rep) {
|
||||
@@ -3263,10 +3283,11 @@ static void testPreserveStretchThirtyTwoVoicesHoldUp() {
|
||||
const double medianNs = nsPerVoiceFrame[nsPerVoiceFrame.size() / 2];
|
||||
const double secsAtMedian =
|
||||
medianNs * static_cast<double>(blockFrames) * static_cast<double>(voiceCount) / 1e9;
|
||||
std::printf(" [measure] 32 stereo Preserve voices @ rate %.2f: median %.1f ns/voice/"
|
||||
"frame [%.1f .. %.1f] over %d reps (%.1f%% of realtime at the median)\n",
|
||||
rate, medianNs, nsPerVoiceFrame.front(), nsPerVoiceFrame.back(), kTimedReps,
|
||||
100.0 * secsAtMedian);
|
||||
std::printf(" [measure] 32 stereo Preserve voices @ rate %.2f, source period %6.1f: "
|
||||
"median %.1f ns/voice/frame [%.1f .. %.1f] over %d reps (%.1f%% of realtime "
|
||||
"at the median)\n",
|
||||
rate, row.period, medianNs, nsPerVoiceFrame.front(), nsPerVoiceFrame.back(),
|
||||
kTimedReps, 100.0 * secsAtMedian);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user