Files
reasampler/tests/test_bake_reset.cpp
T

183 lines
8.6 KiB
C++

// 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 <cstdio>
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.playRate = 0.5;
p.play.pitchOffsetSemitones = -7.5;
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: play mode, to TRIGGER rather than to the struct's Gate default -------
// The bake's product is a finished one-shot; Trigger plays it back verbatim, Gate would
// re-gate its printed release tail and each iteration would truncate the last one's.
CHECK(after.play.playMode == PlayMode::Trigger);
CHECK(freshPlay.playMode == PlayMode::Gate); // and that really is NOT the default
// The Trigger face it lands on plays the whole file flat: full span, unity throughout.
CHECK(after.play.trigger.lengthFraction == 1.0);
CHECK(after.play.trigAhd.attackSeconds == 0.0);
CHECK(after.play.trigAhd.decaySeconds == 0.0);
{
// …and a GATE-dialed instrument lands there too: this is a reset to a chosen
// neutral, not the dialed value surviving.
InstrumentParams gated = dialed();
gated.play.playMode = PlayMode::Gate;
CHECK(resetAfterBake(gated).params.play.playMode == PlayMode::Trigger);
}
// --- RESET: the amp envelope, staged, every stage and every curve exponent ------
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: Rate and the baseline Pitch offset -----------------------------------
// Both are processing the bake already printed, so the whitelist leaves them at their
// defaults — the safe direction. A second bake of the result at a still-dialled rate would
// otherwise re-stretch what the first one baked in.
CHECK(after.play.playRate == 1.0);
CHECK(after.play.pitchOffsetSemitones == 0.0);
CHECK(after.play.playRate == freshPlay.playRate);
CHECK(after.play.pitchOffsetSemitones == freshPlay.pitchOffsetSemitones);
// --- 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;
}