#include "CurveTest.h" #include "evoral/ControlList.h" #include "evoral/Curve.h" #include CPPUNIT_TEST_SUITE_REGISTRATION (CurveTest); #if defined(PLATFORM_WINDOWS) && defined(COMPILER_MINGW) /* cppunit-1.13.2 uses assertion_traits * sprintf( , "%.*g", precision, x) * to format a double. The actual comparison is performed on a string. * This is problematic with mingw/windows|wine, "%.*g" formatting fails. * * This quick hack compares float, however float compatisons are at most Y.MMMM+eXX, * the max precision needs to be limited. to the last mantissa digit. * * Anyway, actual maths is verified with Linux and OSX unit-tests, * and this needs to go to https://sourceforge.net/p/cppunit/bugs/ */ #define MAXPREC(P) ((P) < .0005 ? .0005 : (P)) #define CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(M,A,B,P) CPPUNIT_ASSERT_EQUAL_MESSAGE(M, (float)rint ((A) / MAXPREC(P)),(float)rint ((B) / MAXPREC(P))) #define CPPUNIT_ASSERT_DOUBLES_EQUAL(A,B,P) CPPUNIT_ASSERT_EQUAL((float)rint ((A) / MAXPREC(P)),(float)rint ((B) / MAXPREC(P))) #endif using namespace Evoral; using namespace Temporal; // linear y = Y0 + YS * x ; with x = i * (X1 - X0) + X0; and i = [0..1023] #define VEC1024LINCMP(X0, X1, Y0, YS) \ cl->curve ().get_vector ((X0), (X1), vec, 1024); \ for (int i = 0; i < 1024; ++i) { \ char msg[64]; \ snprintf (msg, 64, "at i=%d (x0=%s, x1=%s, y0=%.1f, ys=%.3f)", \ i, (X0).str().c_str(), (X1).str().c_str(), Y0, YS); \ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE ( \ msg, \ (Y0) + i * (YS), vec[i], \ 1e-24 \ ); \ } void CurveTest::trivial () { float vec[1024]; boost::shared_ptr cl = TestCtrlList(); cl->create_curve (); timepos_t t1024 (1024.0); timepos_t t2047 (2047.0); // Empty curve cl->curve().get_vector (t1024, t2047, vec, 1024); for (int i = 0; i < 1024; ++i) { CPPUNIT_ASSERT_EQUAL (0.0f, vec[i]); } // Single point curve cl->fast_simple_add(timepos_t (0), 42.0); cl->curve().get_vector (t1024, t2047, vec, 1024); for (int i = 0; i < 1024; ++i) { CPPUNIT_ASSERT_EQUAL (42.0f, vec[i]); } } void CurveTest::rtGet () { float vec[1024]; timepos_t t1024 (1024.0); timepos_t t2047 (2047.0); // Create simple control list boost::shared_ptr cl = TestCtrlList(); cl->create_curve (); cl->fast_simple_add(timepos_t(0), 42.0); { // Write-lock list Glib::Threads::RWLock::WriterLock lm(cl->lock()); // Attempt to get vector in RT (expect failure) CPPUNIT_ASSERT (!cl->curve().rt_safe_get_vector (t1024, t2047, vec, 1024)); } // Attempt to get vector in RT (expect success) CPPUNIT_ASSERT (cl->curve().rt_safe_get_vector (t1024, t2047, vec, 1024)); for (int i = 0; i < 1024; ++i) { CPPUNIT_ASSERT_EQUAL (42.0f, vec[i]); } } void CurveTest::twoPointLinear () { float vec[1024]; boost::shared_ptr cl = TestCtrlList(); cl->create_curve (); cl->set_interpolation (ControlList::Linear); timepos_t t0 (0); timepos_t t1024 (1024); timepos_t t2048 (2048); timepos_t t2047 (2047); timepos_t t2049 (2049); timepos_t t2056 (2056); timepos_t t4092 (4092); timepos_t t8192 (8192); // add two points to curve cl->fast_simple_add (t0 , 2048.0); cl->fast_simple_add (t8192, 4096.0); cl->curve ().get_vector (t1024, t2047, vec, 1024); VEC1024LINCMP (t1024, t2047, 2304.f, .25f); //VEC1024LINCMP (t2048, timepos_t (2559.5), 2560.f, .125f); VEC1024LINCMP (t0, t4092, 2048.f, 1.f); // greetings to tartina cl->curve ().get_vector (t2048, t2048, vec, 1); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048..2048", 2560.f, vec[0]); /* XXX WHAT DO WE EXPECT WITH veclen=1 AND x1 > x0 ? */ #if 0 /* .. interpolated value at (x1+x0)/2 */ cl->curve ().get_vector (2048.0, t2049, vec, 1); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048-2049", 2560.125f, vec[0]); cl->curve ().get_vector (2048.0, 2056.0, vec, 1); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048-2049", 2561.f, vec[0]); #else /* .. value at x0 */ cl->curve ().get_vector (t2048, t2049, vec, 1); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 , 2048..2049", 2560.f, vec[0]); cl->curve ().get_vector (t2048, t2056, vec, 1); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 , 2048..2049", 2560.f, vec[0]); #endif cl->curve ().get_vector (t2048, t2048, vec, 2); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2048 @ 0", 2560.f, vec[0]); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2048 @ 1", 2560.f, vec[1]); cl->curve ().get_vector (t2048, t2056, vec, 2); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2056 @ 0", 2560.f, vec[0]); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2056 @ 0", 2562.f, vec[1]); cl->curve ().get_vector (t2048, t2056, vec, 3); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 0", 2560.f, vec[0]); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 1", 2561.f, vec[1]); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 2", 2562.f, vec[2]); /* check out-of range.. * we expect the first and last value - no interpolation */ timepos_t tm1 (-1); timepos_t tm999 (-999); timepos_t t9998 (9998); timepos_t t9999 (9999); cl->curve ().get_vector (tm1, tm1, vec, 1); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ -1", 2048.f, vec[0]); cl->curve ().get_vector (t9999, t9999, vec, 1); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 9999", 4096.f, vec[0]); cl->curve ().get_vector (tm999, t0, vec, 13); for (int i = 0; i < 13; ++i) { CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=13 @ -999..0", 2048.f, vec[i]); } cl->curve ().get_vector (t9998, t9999, vec, 8); for (int i = 0; i < 8; ++i) { CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=8 @ 9998..9999", 4096.f, vec[i]); } } void CurveTest::threePointLinear () { float vec[4]; boost::shared_ptr cl = TestCtrlList(); cl->create_curve (); cl->set_interpolation (ControlList::Linear); timepos_t t0 (0); timepos_t t50 (50); timepos_t t60 (60); timepos_t t80 (80); timepos_t t100 (100); timepos_t t130 (130); timepos_t t150 (150); timepos_t t160 (160); timepos_t t200 (200); // add 3 points to curve cl->fast_simple_add ( t0 , 2.0); cl->fast_simple_add (t100 , 4.0); cl->fast_simple_add (t200 , 0.0); cl->curve ().get_vector (t50, t60, vec, 1); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 50", 3.f, vec[0]); cl->curve ().get_vector (t100, t100, vec, 1); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 100", 4.f, vec[0]); cl->curve ().get_vector (t150, t150, vec, 1); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 150", 2.f, vec[0]); cl->curve ().get_vector (t130, t150, vec, 3); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 0", 2.8f, vec[0]); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 2", 2.4f, vec[1]); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 3", 2.0f, vec[2]); cl->curve ().get_vector (t80, t160, vec, 3); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 0", 3.6f, vec[0]); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 2", 3.2f, vec[1]); CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 3", 1.6f, vec[2]); } void CurveTest::threePointDiscete () { boost::shared_ptr cl = TestCtrlList(); cl->set_interpolation (ControlList::Discrete); timepos_t t0 (0); timepos_t t80 (80); timepos_t t100 (100); timepos_t t120 (120); timepos_t t160 (160); timepos_t t200 (200); // add 3 points to curve cl->fast_simple_add ( t0 , 2.0); cl->fast_simple_add (t100 , 4.0); cl->fast_simple_add (t200 , 0.0); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t160)); cl->set_interpolation (ControlList::Linear); CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(t160)); } void CurveTest::ctrlListEval () { boost::shared_ptr cl = TestCtrlList(); timepos_t t0 (0); timepos_t t80 (80); timepos_t t100 (100); timepos_t t120 (120); timepos_t t160 (160); timepos_t t200 (200); timepos_t t250 (250); timepos_t t300 (300); timepos_t t350 (350); timepos_t t400 (400); timepos_t t999 (999); cl->fast_simple_add (t0 , 2.0); cl->set_interpolation (ControlList::Discrete); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t160)); cl->set_interpolation (ControlList::Linear); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t160)); cl->fast_simple_add (t100 , 4.0); cl->set_interpolation (ControlList::Discrete); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t160)); cl->set_interpolation (ControlList::Linear); CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t160)); cl->fast_simple_add (t200 , 0.0); cl->set_interpolation (ControlList::Discrete); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t160)); cl->set_interpolation (ControlList::Linear); CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(t160)); cl->fast_simple_add (t300 , 8.0); cl->set_interpolation (ControlList::Discrete); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t160)); CPPUNIT_ASSERT_EQUAL(0.0, cl->unlocked_eval(t250)); CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(t999)); cl->set_interpolation (ControlList::Linear); CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(t160)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t250)); CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(t999)); cl->fast_simple_add (t400 , 9.0); cl->set_interpolation (ControlList::Discrete); CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t160)); CPPUNIT_ASSERT_EQUAL(0.0, cl->unlocked_eval(t250)); CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(t350)); CPPUNIT_ASSERT_EQUAL(9.0, cl->unlocked_eval(t999)); cl->set_interpolation (ControlList::Linear); CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(t80)); CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(t120)); CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(t160)); CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(t250)); CPPUNIT_ASSERT_EQUAL(8.5, cl->unlocked_eval(t350)); CPPUNIT_ASSERT_EQUAL(9.0, cl->unlocked_eval(t999)); } void CurveTest::constrainedCubic () { struct point { int x, y; }; static const struct point data[] = { /* values from worked example in www.korf.co.uk/spline.pdf */ { 0, 30 }, { 10, 130 }, { 30, 150 }, { 50, 150 }, { 70, 170 }, { 90, 220 }, { 100, 320 }, }; int32_t type = 0; Evoral::Parameter p(type); Evoral::ParameterDescriptor pd; pd.lower = 5; pd.upper = 325; Evoral::ControlList l(p, pd, AudioTime); size_t i; l.set_interpolation(Evoral::ControlList::Curved); for (i=0; i