// 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 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 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; }