Bake window: derived note lengths carry exact durations, not ladder rungs — a long take is no longer cut at 384 beats
Hold keeps its picker. Also: one home for the %-fold, duration-ordered Hold travel, and a corrupt tail degrades to absent rather than fabricating one.
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
|
||||
using namespace reasampler;
|
||||
using namespace reasampler::instrument::bake;
|
||||
@@ -34,12 +35,14 @@ constexpr std::int64_t kPad = kDeclickFrames;
|
||||
// expectations below read as arithmetic rather than as magic.
|
||||
Division oneBar() { return makeDivision(2, DivisionModifier::Straight); }
|
||||
|
||||
Tempo tempo() {
|
||||
const std::optional<Tempo> t = Tempo::fromBpm(kBpm);
|
||||
if (!t) { std::printf("FAIL: fixture tempo rejected\n"); ++g_fail; }
|
||||
Tempo tempoOf(double bpm) {
|
||||
const std::optional<Tempo> t = Tempo::fromBpm(bpm);
|
||||
if (!t) { std::printf("FAIL: fixture tempo %f rejected\n", bpm); ++g_fail; }
|
||||
return t.value_or(Tempo::fromBpm(120.0).value());
|
||||
}
|
||||
|
||||
Tempo tempo() { return tempoOf(kBpm); }
|
||||
|
||||
// Flat DC so a level reading is unambiguous: any departure from 0.5 is the envelope, the
|
||||
// filter or a ring-out, never the source's own shape.
|
||||
SampleData dcSample(std::size_t frames) {
|
||||
@@ -66,7 +69,7 @@ double peakAt(const BakeAudio& audio, std::int64_t from, std::int64_t to) {
|
||||
// comparison a measurement of the derived end rather than of a second derivation.
|
||||
NoteProgram derivedProgram(const SampleData& s, double extraMs, Division hold = oneBar(),
|
||||
int velocity = 100) {
|
||||
NoteProgram p = defaultBakeProgram(s, kRate, tempo(), hold, Velocity::of(velocity));
|
||||
NoteProgram p = defaultBakeProgram(s, kRate, hold, Velocity::of(velocity));
|
||||
if (extraMs != 0.0)
|
||||
p.end = EndOffset(offsetFromMs(offsetMs(p.end.amount(), tempo()) + extraMs));
|
||||
return p;
|
||||
@@ -136,6 +139,63 @@ int main() {
|
||||
CHECK(peakAt(wide, 192000, wide.frameCount()) == 0.0);
|
||||
}
|
||||
|
||||
// --- Gate, no loop, a source LONGER than the note-length ladder ---------------------
|
||||
// The regression the exact-duration seam exists for. A derived length used to be rounded
|
||||
// onto the musical-division ladder, whose longest rung is kMaxDivisionBeats (384 beats):
|
||||
// a source past that took the top rung, so note-off — and the window with it — landed
|
||||
// INSIDE the sound. Rendered at a low rate and a fast tempo so the case is 400 beats long
|
||||
// without being twenty million frames; nothing here depends on either number but the
|
||||
// beats it puts the source at.
|
||||
{
|
||||
constexpr int kSlowRate = 8000;
|
||||
const Tempo fast = tempoOf(480.0); // 384 beats == 48 s at this tempo
|
||||
constexpr std::int64_t kFrames = kSlowRate * 50; // 50 s == 400 beats: past the ladder
|
||||
SampleData s = dcSample(static_cast<std::size_t>(kFrames));
|
||||
s.sampleRate = kSlowRate;
|
||||
s.play.playMode = PlayMode::Gate;
|
||||
s.play.adsr.releaseFrames = 0;
|
||||
|
||||
const NoteProgram p =
|
||||
defaultBakeProgram(s, kSlowRate, oneBar(), Velocity::of(100));
|
||||
const ResolvedNote r = resolveNote(p, fast);
|
||||
CHECK(r.noteOffSeconds > 49.9 && r.noteOffSeconds < 50.1); // 50 s, not the 48 s rung
|
||||
const std::optional<BakePlan> plan = planBake(r, kSlowRate, 60).plan;
|
||||
CHECK(plan.has_value());
|
||||
if (plan) {
|
||||
CHECK(plan->totalFrames == kFrames + kPad);
|
||||
const BakeAudio whole = renderBake(s, *plan, kUnity);
|
||||
// Full level across the two seconds the saturated rung used to cut, and the file
|
||||
// still ends on the declick ramp rather than on a hard edge.
|
||||
CHECK(peakAt(whole, kSlowRate * 48, kFrames) > 0.4);
|
||||
CHECK(lastFrameLevel(whole) < 1e-3);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Gate, no loop, a START POINT under Varispeed: both terms of the exhaustion ------
|
||||
// The two derivation terms this branch has that the cases above do not exercise: the
|
||||
// window covers frameCount MINUS the start, and that remainder is stretched by the
|
||||
// deepest downward offset. Getting either wrong shortens the window.
|
||||
{
|
||||
SampleData s = dcSample(48000); // 1 s of source
|
||||
s.play.playMode = PlayMode::Gate;
|
||||
s.play.adsr.releaseFrames = 0;
|
||||
s.startFrame = 24000; // half of it left to play
|
||||
s.play.pitchEngine = PitchEngine::Varispeed;
|
||||
// A flat velocity->pitch curve at half depth: an octave down (the range is 24
|
||||
// semitones) for the note's whole lifetime, so the read really does run at half rate
|
||||
// to the end rather than for one envelope stage.
|
||||
s.play.pitchVelocityCurve = VelocityCurve::fromPoints(
|
||||
{{0.0, -0.5}, {127.0, -0.5}}, reasampler::instrument::engine::CurveDomain::Bipolar);
|
||||
|
||||
// (48000 - 24000) source frames at half rate == 48000 output frames. Reading either
|
||||
// term wrong halves or doubles this.
|
||||
CHECK(derivedFrames(s) == 48000 + kPad);
|
||||
const BakeAudio derived = bakeWith(s, 0.0);
|
||||
CHECK(peakAt(derived, 47000, 48000) > 0.4); // still sounding at the derived end
|
||||
const BakeAudio wide = bakeWith(s, /*extraMs=*/200.0);
|
||||
CHECK(peakAt(wide, 48000 + kPad, wide.frameCount()) == 0.0); // and nothing past it
|
||||
}
|
||||
|
||||
// --- Gate WITH a sustain loop: Hold is the note length, and the ONLY user input ------
|
||||
// A looped Gate voice sounds for as long as it is held, so no derivation supplies a
|
||||
// duration — this is the one case the predicate names, and the window follows Hold.
|
||||
@@ -254,7 +314,7 @@ int main() {
|
||||
|
||||
// --- Trigger + a DRAWN amp EG: the window holds the WHOLE take -----------------------
|
||||
// A drawn contour covers the full sample length, so the engine folds lengthFraction to
|
||||
// 1.0 (effectiveLengthFraction, trigger_seam.h). The stored %-knob is inert but still
|
||||
// 1.0 (effectiveLengthFraction, play_params.h). The stored %-knob is inert but still
|
||||
// saved, and reading it raw here cut this window to a quarter of the take.
|
||||
{
|
||||
SampleData s = dcSample(48000);
|
||||
|
||||
Reference in New Issue
Block a user