// Standalone tests for reasampler::instrument::bake::bake_reset — no VST3, no REAPER, no // framework. Same fast assert loop as the sibling pure tests. // // Covers the ratified reset scope PER PARAMETER, in both directions: every control whose // effect the render printed comes back at its default, and every mapping fact comes back // untouched. Asserted field by field rather than by struct equality on purpose — a // whole-struct compare would pass while silently resetting a survivor, or vice versa. #include "../src/core/instrument/bake/bake_reset.h" #include using namespace reasampler; using namespace reasampler::instrument::bake; using reasampler::instrument::map::InstrumentParams; using reasampler::instrument::map::PlaySeconds; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) namespace { // Every field moved off its default, so a reset that misses one is visible. InstrumentParams dialed() { InstrumentParams p; p.rootOverride = 43; p.loopOverride = SampleLoop{true, 111, 222}; p.startPoint = 4321; p.loopCrossfadeFrames = 512; p.keyTrack = 0.5; p.velocityCurve = VelocityCurve::linear(); p.play.playMode = PlayMode::Trigger; p.play.adsr.attackSeconds = 0.4; p.play.adsr.holdSeconds = 0.3; p.play.adsr.decaySeconds = 0.2; p.play.adsr.sustainLevel = 0.1; p.play.adsr.releaseSeconds = 0.9; p.play.adsr.attackCurve = 2.5; p.play.adsr.decayCurve = 0.4; p.play.adsr.releaseCurve = 3.0; p.play.trigger.lengthFraction = 0.25; p.play.trigAhd.attackSeconds = 0.11; p.play.trigAhd.decaySeconds = 0.22; p.play.trigAhd.holdFraction = 0.33; p.play.trigAhd.attackCurve = 1.7; p.play.pitchEngine = PitchEngine::Varispeed; p.play.pitchEnv.enabled = true; p.play.pitchEnv.peakSemitones = -7.0; p.play.pitchEnv.shape.attackSeconds = 0.05; p.play.pitchVelocityCurve = VelocityCurve::linear(); p.play.filter.enabled = true; p.play.filter.modAmount = -0.8; p.play.filter.velAmount = 0.6; p.play.filter.keyTrack = 1.5; p.play.filter.env.attackSeconds = 0.7; p.play.filter.trigEnv.decaySeconds = 0.8; p.play.filter.velocityCurve = VelocityCurve::linear(); p.play.ampSpline.mode = EnvMode::Spline; p.play.ampSpline.contour = VelocityCurve::linear(); p.play.pitchSpline.mode = EnvMode::Spline; p.play.filterSpline.mode = EnvMode::Spline; return p; } bool sameCurve(const VelocityCurve& a, const VelocityCurve& b) { if (a.domain() != b.domain() || a.size() != b.size()) return false; for (std::size_t i = 0; i < a.size(); ++i) { if (a.points()[i].velocity != b.points()[i].velocity) return false; if (a.points()[i].value != b.points()[i].value) return false; } return true; } } // namespace int main() { const InstrumentParams before = dialed(); const BakeReset reset = resetAfterBake(before); const InstrumentParams& after = reset.params; const InstrumentParams fresh; // the defaults every reset control must land on const PlaySeconds freshPlay; // --- SURVIVE: mapping facts, absent from the printed audio ---------------------- CHECK(after.rootOverride.has_value()); CHECK(after.rootOverride == before.rootOverride); CHECK(after.keyTrack == before.keyTrack); CHECK(after.keyTrack == 0.5); // and it is the dialed value, not the default 1.0 CHECK(fresh.keyTrack != before.keyTrack); // the fixture really did move it // --- RESET: loop points, start point, crossfade --------------------------------- CHECK(!after.loopOverride.has_value()); CHECK(!after.startPoint.has_value()); CHECK(after.loopCrossfadeFrames == 0); // --- RESET: the velocity transfer curves ---------------------------------------- CHECK(sameCurve(after.velocityCurve, VelocityCurve::flat())); CHECK(!sameCurve(after.velocityCurve, before.velocityCurve)); CHECK(sameCurve(after.play.pitchVelocityCurve, VelocityCurve::zero())); CHECK(sameCurve(after.play.filter.velocityCurve, VelocityCurve::zero())); // --- RESET: the amp envelope, staged, every stage and every curve exponent ------ CHECK(after.play.playMode == freshPlay.playMode); CHECK(after.play.adsr.attackSeconds == freshPlay.adsr.attackSeconds); CHECK(after.play.adsr.holdSeconds == freshPlay.adsr.holdSeconds); CHECK(after.play.adsr.decaySeconds == freshPlay.adsr.decaySeconds); CHECK(after.play.adsr.sustainLevel == freshPlay.adsr.sustainLevel); CHECK(after.play.adsr.releaseSeconds == freshPlay.adsr.releaseSeconds); CHECK(after.play.adsr.attackCurve == freshPlay.adsr.attackCurve); CHECK(after.play.adsr.decayCurve == freshPlay.adsr.decayCurve); CHECK(after.play.adsr.releaseCurve == freshPlay.adsr.releaseCurve); CHECK(after.play.trigger.lengthFraction == freshPlay.trigger.lengthFraction); CHECK(after.play.trigAhd.attackSeconds == freshPlay.trigAhd.attackSeconds); CHECK(after.play.trigAhd.decaySeconds == freshPlay.trigAhd.decaySeconds); CHECK(after.play.trigAhd.holdFraction == freshPlay.trigAhd.holdFraction); CHECK(after.play.trigAhd.attackCurve == freshPlay.trigAhd.attackCurve); // --- RESET: pitch engine + pitch envelope --------------------------------------- CHECK(after.play.pitchEngine == freshPlay.pitchEngine); CHECK(after.play.pitchEngine == PitchEngine::Preserve); // the product default CHECK(!after.play.pitchEnv.enabled); CHECK(after.play.pitchEnv.peakSemitones == 0.0); CHECK(after.play.pitchEnv.shape.attackSeconds == freshPlay.pitchEnv.shape.attackSeconds); // --- RESET: the filter, including its velocity/key-tracking mod ----------------- CHECK(!after.play.filter.enabled); CHECK(after.play.filter.modAmount == 0.0); CHECK(after.play.filter.velAmount == 0.0); CHECK(after.play.filter.keyTrack == 0.0); CHECK(after.play.filter.env.attackSeconds == freshPlay.filter.env.attackSeconds); CHECK(after.play.filter.trigEnv.decaySeconds == freshPlay.filter.trigEnv.decaySeconds); // --- RESET: the three spline contours AND their mode flags ---------------------- // The flag selects which shape ran, so the shape it selected is in the audio; with // both contours reset it also has nothing left to preserve. CHECK(after.play.ampSpline.mode == EnvMode::Staged); CHECK(after.play.pitchSpline.mode == EnvMode::Staged); CHECK(after.play.filterSpline.mode == EnvMode::Staged); CHECK(sameCurve(after.play.ampSpline.contour, VelocityCurve::rampDown())); CHECK(!sameCurve(after.play.ampSpline.contour, before.play.ampSpline.contour)); // --- RESET: master gain ---------------------------------------------------------- CHECK(reset.masterGainLinear == 1.0); // --- An absent root override stays absent (nothing is invented) ------------------ { InstrumentParams noRoot = dialed(); noRoot.rootOverride.reset(); CHECK(!resetAfterBake(noRoot).params.rootOverride.has_value()); } if (g_fail == 0) std::printf("bake_reset: all tests passed\n"); return g_fail ? 1 : 0; }