Play cross-rate captures at their own pitch and length
The voice now converts the file's rate to the host's at note-on instead of assuming they match. Stage times resolve at the host rate, the filter solves at it, and the bake follows. Same-rate renders are unchanged bit for bit.
This commit is contained in:
@@ -11,6 +11,9 @@
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "tone_metrics.h"
|
||||
|
||||
using namespace reasampler;
|
||||
using namespace reasampler::instrument::bake;
|
||||
@@ -488,6 +491,54 @@ int main() {
|
||||
CHECK(hardPeak > softPeak * 2.0);
|
||||
}
|
||||
|
||||
// ============================ SOURCE RATE =======================================
|
||||
|
||||
// --- A capture at another rate bakes at its own pitch and its own wall-clock length --
|
||||
// 441 Hz for one second of a 44.1k file, baked at 48k: the window must hold 48000 frames
|
||||
// of sound (not the source's 44100), and the print must be 441 Hz at the render rate.
|
||||
for (PitchEngine engine : {PitchEngine::Varispeed, PitchEngine::Preserve}) {
|
||||
constexpr int kFileRate = 44100;
|
||||
constexpr double kHz = 441.0;
|
||||
SampleData s;
|
||||
s.frames.resize(static_cast<std::size_t>(kFileRate));
|
||||
for (std::size_t i = 0; i < s.frames.size(); ++i) {
|
||||
s.frames[i] = static_cast<float>(
|
||||
0.5 * std::sin(2.0 * 3.14159265358979323846 * kHz * static_cast<double>(i) /
|
||||
kFileRate));
|
||||
}
|
||||
s.sampleRate = kFileRate;
|
||||
s.renderRate = kRate;
|
||||
s.rootNote = 60;
|
||||
s.play.playMode = PlayMode::Trigger;
|
||||
s.play.pitchEngine = engine;
|
||||
s.play.trigAhd.holdFraction = 1.0;
|
||||
|
||||
CHECK(derivedFrames(s) == kRate + kPad);
|
||||
const BakeAudio audio = bakeWith(s, 0.0);
|
||||
CHECK(peakAt(audio, kRate - 1000, kRate - 10) > 0.4); // still sounding near 1 s
|
||||
CHECK(lastSoundingFrame(audio) < audio.frameCount() - 1); // and done inside the window
|
||||
|
||||
std::vector<double> mono(static_cast<std::size_t>(audio.frameCount()));
|
||||
for (std::size_t f = 0; f < mono.size(); ++f) {
|
||||
mono[f] = audio.interleaved[f * static_cast<std::size_t>(audio.channelCount)];
|
||||
}
|
||||
const double want = static_cast<double>(kRate) / kHz;
|
||||
const double got = reasampler::test_support::autocorrelationPeriod(
|
||||
mono, 10000, 8000, static_cast<std::int64_t>(want * 0.5),
|
||||
static_cast<std::int64_t>(want * 1.7));
|
||||
CHECK(std::fabs(got - want) < 0.005 * want);
|
||||
}
|
||||
|
||||
// --- A snapshot resolved for another output rate is refused, not printed off-pitch ---
|
||||
{
|
||||
SampleData s = dcSample(24000);
|
||||
s.renderRate = 44100; // resolved for 44.1k; the plan renders at 48k
|
||||
s.play.playMode = PlayMode::Trigger;
|
||||
const std::optional<BakePlan> plan = planOf(derivedProgram(s, 0.0));
|
||||
CHECK(plan.has_value());
|
||||
if (plan) CHECK(renderBake(s, *plan, kUnity, kNoLimiter).empty());
|
||||
}
|
||||
|
||||
// ============================== REFUSALS ========================================
|
||||
|
||||
// --- A legitimate dialed sound past the frame ceiling is REFUSED, and says so --------
|
||||
|
||||
Reference in New Issue
Block a user