Ξ-W2-T1: the resample bake chain — instrument renders, extension banks, one click re-points and resets

This commit is contained in:
2026-08-01 16:26:28 -04:00
parent 6c982cd617
commit 60308a3655
52 changed files with 2212 additions and 55 deletions
+105
View File
@@ -0,0 +1,105 @@
// Standalone tests for reasampler::instrument::bake::bake_plan — no VST3, no REAPER, no
// framework. Same fast assert loop as the sibling pure tests.
//
// Covers: the default program's window (a note length plus a release tail, so the bake is
// not truncated at note-off); the frame window and both event frames against hand-computed
// values; a capture opening BEFORE note-on; the refusals — a collapsed window, a
// non-positive rate, and a window that rounds to nothing; and root/velocity clamping.
#include "../src/core/instrument/bake/bake_plan.h"
#include <cstdio>
using namespace reasampler::instrument::bake;
using namespace reasampler::instrument::note;
static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
static Tempo at(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());
}
int main() {
// --- The default program opens the window past note-off -----------------------
{
const NoteProgram p = defaultBakeProgram();
const ResolvedNote r = resolveNote(p, at(120.0));
// A quarter note at 120 BPM is 0.5 s; the tail is kDefaultReleaseTailMs past it.
CHECK(r.noteOffSeconds > 0.499 && r.noteOffSeconds < 0.501);
CHECK(r.captureStartSeconds == 0.0);
// The whole point of the default: the window must extend past the release, or
// every bake would be cut at note-off.
CHECK(r.captureEndSeconds > r.noteOffSeconds);
CHECK(!r.windowCollapsed);
}
// --- The frame window and both event frames ----------------------------------
{
NoteProgram p; // 1/4 straight, velocity 100
p.end = EndOffset(offsetFromMs(250.0));
const ResolvedNote r = resolveNote(p, at(120.0)); // note-off 0.5 s, end 0.75 s
const auto plan = planBake(r, 48000, 60);
CHECK(plan.has_value());
CHECK(plan->totalFrames == 36000); // 0.75 s * 48 kHz
CHECK(plan->noteOnFrame == 0);
CHECK(plan->noteOffFrame == 24000); // 0.5 s * 48 kHz
CHECK(plan->sampleRate == 48000);
CHECK(plan->note == 60);
CHECK(plan->velocity == 100);
}
// --- A capture that opens BEFORE note-on -------------------------------------
{
NoteProgram p;
p.start = StartOffset(offsetFromMs(-100.0)); // negative = earlier
p.end = EndOffset(offsetFromMs(100.0));
const ResolvedNote r = resolveNote(p, at(120.0));
const auto plan = planBake(r, 44100, 60);
CHECK(plan.has_value());
// Window is [-0.1, 0.6] s = 0.7 s; note-on sits 0.1 s in, note-off 0.5 s after it.
CHECK(plan->totalFrames == 30870);
CHECK(plan->noteOnFrame == 4410);
CHECK(plan->noteOffFrame == 26460);
CHECK(plan->noteOffFrame - plan->noteOnFrame == 22050); // the note's own length
}
// --- Refusals -----------------------------------------------------------------
{
NoteProgram p;
// An end offset more negative than the note length inverts the window.
p.end = EndOffset(offsetFromMs(-10000.0));
const ResolvedNote r = resolveNote(p, at(120.0));
CHECK(r.windowCollapsed);
CHECK(!planBake(r, 48000, 60).has_value());
}
{
const ResolvedNote r = resolveNote(defaultBakeProgram(), at(120.0));
CHECK(!planBake(r, 0, 60).has_value());
CHECK(!planBake(r, -48000, 60).has_value());
}
{
// A legal but sub-frame window rounds to nothing and is refused rather than
// rendered as a degenerate buffer.
NoteProgram p;
p.end = EndOffset(offsetFromMs(-500.0)); // exactly cancels the 0.5 s note
const ResolvedNote r = resolveNote(p, at(120.0));
CHECK(!r.windowCollapsed);
CHECK(r.captureLengthSeconds() == 0.0);
CHECK(!planBake(r, 48000, 60).has_value());
}
// --- Domain clamps -------------------------------------------------------------
{
const ResolvedNote r = resolveNote(defaultBakeProgram(), at(120.0));
CHECK(planBake(r, 48000, -5)->note == 0);
CHECK(planBake(r, 48000, 900)->note == 127);
CHECK(planBake(r, 48000, 60)->note == 60);
}
if (g_fail == 0) std::printf("bake_plan: all tests passed\n");
return g_fail ? 1 : 0;
}