diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7ed0d32a..eb28bb5c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,6 +23,7 @@ set(SFIZZ_TEST_SOURCES EventEnvelopesT.cpp MainT.cpp SynthT.cpp + CurveT.cpp RegionTriggersT.cpp FloatHelpersT.cpp ) diff --git a/tests/CurveT.cpp b/tests/CurveT.cpp new file mode 100644 index 00000000..b83a6b8d --- /dev/null +++ b/tests/CurveT.cpp @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "sfizz/Curve.h" +#include "sfizz/Opcode.h" +#include "catch2/catch.hpp" +#include +using namespace Catch::literals; + +TEST_CASE("[Curve] Bipolar 0 to 1") +{ + auto curve = sfz::Curve::buildPredefinedCurve(0); + REQUIRE( curve.evalCC7(0) == 0.0f ); + REQUIRE( curve.evalCC7(127) == 1.0f ); + REQUIRE( curve.evalCC7(2) == Approx(0.0157).margin(1e-3) ); + REQUIRE( curve.evalCC7(63) == Approx(0.496).margin(1e-3) ); + REQUIRE( curve.evalCC7(85) == Approx(0.669).margin(1e-3) ); + REQUIRE( curve.evalNormalized(0.0f) == 0.0f ); + REQUIRE( curve.evalNormalized(1.0f) == 1.0f ); + REQUIRE( curve.evalNormalized(0.3f) == Approx(0.299).margin(1e-3) ); +} + +TEST_CASE("[Curve] Bipolar -1 to 1") +{ + auto curve = sfz::Curve::buildPredefinedCurve(1); + REQUIRE( curve.evalCC7(0) == -1.0f ); + REQUIRE( curve.evalCC7(127) == 1.0f ); + REQUIRE( curve.evalCC7(2) == Approx(-0.9685).margin(1e-3) ); + REQUIRE( curve.evalCC7(63) == Approx(-0.00787).margin(1e-5) ); + REQUIRE( curve.evalCC7(85) == Approx(0.3386).margin(1e-3) ); + REQUIRE( curve.evalNormalized(0.0f) == -1.0f ); + REQUIRE( curve.evalNormalized(1.0f) == 1.0f ); + REQUIRE( curve.evalNormalized(0.3f) == Approx(-0.402).margin(1e-3) ); +} + +TEST_CASE("[Curve] Bipolar 1 to 0") +{ + auto curve = sfz::Curve::buildPredefinedCurve(2); + REQUIRE( curve.evalCC7(0) == 1.0f ); + REQUIRE( curve.evalCC7(127) == 0.0f ); + REQUIRE( curve.evalCC7(2) == Approx(0.984).margin(1e-3) ); + REQUIRE( curve.evalCC7(63) == Approx(0.504).margin(1e-3) ); + REQUIRE( curve.evalCC7(85) == Approx(0.331).margin(1e-3) ); + REQUIRE( curve.evalNormalized(0.0f) == 1.0f ); + REQUIRE( curve.evalNormalized(1.0f) == 0.0f ); + REQUIRE( curve.evalNormalized(0.3f) == Approx(0.701).margin(1e-3) ); +} + +TEST_CASE("[Curve] Bipolar 1 to -1") +{ + auto curve = sfz::Curve::buildPredefinedCurve(3); + REQUIRE( curve.evalCC7(0) == 1.0f ); + REQUIRE( curve.evalCC7(127) == -1.0f ); + REQUIRE( curve.evalCC7(2) == Approx(0.9685).margin(1e-3) ); + REQUIRE( curve.evalCC7(63) == Approx(0.00787).margin(1e-5) ); + REQUIRE( curve.evalCC7(85) == Approx(-0.3386).margin(1e-3) ); + REQUIRE( curve.evalNormalized(0.0f) == 1.0f ); + REQUIRE( curve.evalNormalized(1.0f) == -1.0f ); + REQUIRE( curve.evalNormalized(0.3f) == Approx(0.402).margin(1e-3) ); +} + +TEST_CASE("[Curve] x**2") +{ + auto curve = sfz::Curve::buildPredefinedCurve(4); + REQUIRE( curve.evalCC7(0) == 0.0f ); + REQUIRE( curve.evalCC7(127) == 1.0f ); + REQUIRE( curve.evalCC7(2) == Approx(0.000248).margin(1e-5) ); + REQUIRE( curve.evalCC7(63) == Approx(0.246).margin(1e-3) ); + REQUIRE( curve.evalCC7(85) == Approx(0.448).margin(1e-3) ); + REQUIRE( curve.evalNormalized(0.0f) == 0.0f ); + REQUIRE( curve.evalNormalized(1.0f) == 1.0f ); + REQUIRE( curve.evalNormalized(0.3f) == Approx(0.09).margin(1e-3) ); +} + + +TEST_CASE("[Curve] sqrt(x)") +{ + auto curve = sfz::Curve::buildPredefinedCurve(5); + REQUIRE( curve.evalCC7(0) == 0.0f ); + REQUIRE( curve.evalCC7(127) == 1.0f ); + REQUIRE( curve.evalCC7(2) == Approx(0.125).margin(1e-3) ); + REQUIRE( curve.evalCC7(63) == Approx(0.704).margin(1e-3) ); + REQUIRE( curve.evalCC7(85) == Approx(0.818).margin(1e-3) ); + REQUIRE( curve.evalNormalized(0.0f) == 0.0f ); + REQUIRE( curve.evalNormalized(1.0f) == 1.0f ); + REQUIRE( curve.evalNormalized(0.3f) == Approx(0.5477).margin(1e-3) ); +} + +TEST_CASE("[Curve] sqrt(1-x)") +{ + auto curve = sfz::Curve::buildPredefinedCurve(6); + REQUIRE( curve.evalCC7(0) == 1.0f ); + REQUIRE( curve.evalCC7(127) == 0.0f ); + REQUIRE( curve.evalCC7(2) == Approx(0.992).margin(1e-3) ); + REQUIRE( curve.evalCC7(63) == Approx(0.710).margin(1e-3) ); + REQUIRE( curve.evalCC7(85) == Approx(0.575).margin(1e-3) ); + REQUIRE( curve.evalNormalized(0.0f) == 1.0f ); + REQUIRE( curve.evalNormalized(1.0f) == 0.0f ); + REQUIRE( curve.evalNormalized(0.3f) == Approx(0.837).margin(1e-3) ); +} + +TEST_CASE("[Curve] Custom") +{ + auto curve = sfz::Curve::buildCurveFromHeader({ + { "v000", "0" }, + { "v063", "1" }, + { "v127", "0" } + }, sfz::Curve::Interpolator::Linear); + REQUIRE( curve.evalCC7(0) == 0.0f ); + REQUIRE( curve.evalCC7(127) == 0.0f ); + REQUIRE( curve.evalCC7(63) == 1.0f ); + REQUIRE( curve.evalCC7(2) == Approx(0.032).margin(1e-3) ); + REQUIRE( curve.evalCC7(70) == Approx(0.891).margin(1e-3) ); +} + +TEST_CASE("[Curve] Custom 2") +{ + auto curve = sfz::Curve::buildCurveFromHeader({ + { "v063", "1" } + }, sfz::Curve::Interpolator::Linear); + REQUIRE( curve.evalCC7(0) == 0.0f ); + REQUIRE( curve.evalCC7(127) == 1.0f ); + REQUIRE( curve.evalCC7(63) == 1.0f ); + REQUIRE( curve.evalCC7(2) == Approx(0.032).margin(1e-3) ); + REQUIRE( curve.evalCC7(70) == 1.0f ); +} +TEST_CASE("[Curve] Custom 3") +{ + auto curve = sfz::Curve::buildCurveFromHeader({ + { "v063", "1" }, + { "v064", "0.5" } + }, sfz::Curve::Interpolator::Linear); + REQUIRE( curve.evalCC7(0) == 0.0f ); + REQUIRE( curve.evalCC7(127) == 1.0f ); + REQUIRE( curve.evalCC7(63) == 1.0f ); + REQUIRE( curve.evalCC7(2) == Approx(0.032).margin(1e-3) ); + REQUIRE( curve.evalCC7(70) == Approx(0.548).margin(1e-3) ); +} + + +TEST_CASE("[Curve] Add curves to CurveSet") +{ + sfz::CurveSet curveSet; + curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0)); + curveSet.addCurve(sfz::Curve::buildPredefinedCurve(2)); + REQUIRE( curveSet.getNumCurves() == 2 ); + REQUIRE( curveSet.getCurve(0).evalCC7(0) == 0.0f ); + REQUIRE( curveSet.getCurve(1).evalCC7(0) == 1.0f ); + curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), 1); + REQUIRE(curveSet.getNumCurves() == 2); + REQUIRE( curveSet.getCurve(1).evalCC7(0) == 0.0f ); + // Can't add an implicit curve after the explicit one + curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0)); + REQUIRE(curveSet.getNumCurves() == 2); + curveSet.addCurve(sfz::Curve::buildPredefinedCurve(2), 4); + REQUIRE(curveSet.getNumCurves() == 5); + REQUIRE( curveSet.getCurve(2).evalCC7(0) == 0.0f ); // Default "empty" curve + REQUIRE( curveSet.getCurve(3).evalCC7(0) == 0.0f ); // Default "empty" curve + REQUIRE( curveSet.getCurve(4).evalCC7(0) == 1.0f ); +} + +TEST_CASE("[Curve] Default CurveSet") +{ + auto curveSet = sfz::CurveSet::createPredefined(); + REQUIRE(curveSet.getNumCurves() == 7); + REQUIRE( curveSet.getCurve(0).evalNormalized(0.0f) == 0.0f ); + REQUIRE( curveSet.getCurve(0).evalNormalized(1.0f) == 1.0f ); + REQUIRE( curveSet.getCurve(0).evalNormalized(0.3f) == Approx(0.299).margin(1e-3) ); + + REQUIRE( curveSet.getCurve(1).evalNormalized(0.0f) == -1.0f ); + REQUIRE( curveSet.getCurve(1).evalNormalized(1.0f) == 1.0f ); + REQUIRE( curveSet.getCurve(1).evalNormalized(0.3f) == Approx(-0.402).margin(1e-3) ); + + REQUIRE( curveSet.getCurve(2).evalNormalized(0.0f) == 1.0f ); + REQUIRE( curveSet.getCurve(2).evalNormalized(1.0f) == 0.0f ); + REQUIRE( curveSet.getCurve(2).evalNormalized(0.3f) == Approx(0.701).margin(1e-3) ); + + REQUIRE( curveSet.getCurve(3).evalNormalized(0.0f) == 1.0f ); + REQUIRE( curveSet.getCurve(3).evalNormalized(1.0f) == -1.0f ); + REQUIRE( curveSet.getCurve(3).evalNormalized(0.3f) == Approx(0.402).margin(1e-3) ); + + REQUIRE( curveSet.getCurve(4).evalNormalized(0.0f) == 0.0f ); + REQUIRE( curveSet.getCurve(4).evalNormalized(1.0f) == 1.0f ); + REQUIRE( curveSet.getCurve(4).evalNormalized(0.3f) == Approx(0.09).margin(1e-3) ); + + REQUIRE( curveSet.getCurve(5).evalNormalized(0.0f) == 0.0f ); + REQUIRE( curveSet.getCurve(5).evalNormalized(1.0f) == 1.0f ); + REQUIRE( curveSet.getCurve(5).evalNormalized(0.3f) == Approx(0.5477).margin(1e-3) ); + + REQUIRE( curveSet.getCurve(6).evalNormalized(0.0f) == 1.0f ); + REQUIRE( curveSet.getCurve(6).evalNormalized(1.0f) == 0.0f ); + REQUIRE( curveSet.getCurve(6).evalNormalized(0.3f) == Approx(0.837).margin(1e-3) ); +}