From f284d28d5306114e9badc9077835683e541420e0 Mon Sep 17 00:00:00 2001 From: Hans Baier Date: Wed, 10 Jun 2009 00:03:28 +0000 Subject: [PATCH] libardour: * Add basic classes for later support of multiple interpolation algorithms for varispeed * Add unit tests: Test which shows how the varispeed implementation in diskstream is broken. git-svn-id: svn://localhost/ardour2/branches/3.0@5144 d708f5d6-7413-0410-9779-e7cbd77b26cf --- libs/ardour/ardour/interpolation.h | 49 +++++++++++++++++++++ libs/ardour/interpolation.cc | 43 ++++++++++++++++++ libs/ardour/tests/interpolation-test.cc | 46 ++++++++++++++++++++ libs/ardour/tests/interpolation-test.h | 58 +++++++++++++++++++++++++ libs/ardour/tests/testrunner.cpp | 27 ++++++++++++ libs/ardour/wscript | 14 ++++++ 6 files changed, 237 insertions(+) create mode 100644 libs/ardour/ardour/interpolation.h create mode 100644 libs/ardour/interpolation.cc create mode 100644 libs/ardour/tests/interpolation-test.cc create mode 100644 libs/ardour/tests/interpolation-test.h create mode 100644 libs/ardour/tests/testrunner.cpp diff --git a/libs/ardour/ardour/interpolation.h b/libs/ardour/ardour/interpolation.h new file mode 100644 index 0000000000..09ae242490 --- /dev/null +++ b/libs/ardour/ardour/interpolation.h @@ -0,0 +1,49 @@ +#include +//#include "ardour/types.h" + +typedef float Sample; +#define nframes_t uint32_t + +// 40.24 fixpoint math +#define FIXPOINT_ONE 0x1000000 + +class Interpolation { + protected: + /// speed in fixed point math + uint64_t phi; + + /// target speed in fixed point math + uint64_t target_phi; + + uint64_t last_phase; + + // Fixed point is just an integer with an implied scaling factor. + // In 40.24 the scaling factor is 2^24 = 16777216, + // so a value of 10*2^24 (in integer space) is equivalent to 10.0. + // + // The advantage is that addition and modulus [like x = (x + y) % 2^40] + // have no rounding errors and no drift, and just require a single integer add. + // (swh) + + static const int64_t fractional_part_mask = 0xFFFFFF; + static const Sample binary_scaling_factor = 16777216.0f; + + public: + Interpolation () : phi (FIXPOINT_ONE), target_phi (FIXPOINT_ONE), last_phase (0) {} + + void set_speed (double new_speed) { + target_phi = (uint64_t) (FIXPOINT_ONE * fabs(new_speed)); + phi = target_phi; + } + + uint64_t get_phi () const { return phi; } + uint64_t get_target_phi () const { return target_phi; } + uint64_t get_last_phase () const { return last_phase; } + + virtual nframes_t interpolate (nframes_t nframes, Sample* input, Sample* output) = 0; +}; + +class LinearInterpolation : public Interpolation { + public: + nframes_t interpolate (nframes_t nframes, Sample* input, Sample* output); +}; diff --git a/libs/ardour/interpolation.cc b/libs/ardour/interpolation.cc new file mode 100644 index 0000000000..7aece6453c --- /dev/null +++ b/libs/ardour/interpolation.cc @@ -0,0 +1,43 @@ +#include +#include "ardour/interpolation.h" + +nframes_t +LinearInterpolation::interpolate (nframes_t nframes, Sample *input, Sample *output) +{ + // the idea behind phase is that when the speed is not 1.0, we have to + // interpolate between samples and then we have to store where we thought we were. + // rather than being at sample N or N+1, we were at N+0.8792922 + // so the "phase" element, if you want to think about this way, + // varies from 0 to 1, representing the "offset" between samples + uint64_t phase = last_phase; + + // acceleration + int64_t phi_delta; + + // phi = fixed point speed + if (phi != target_phi) { + phi_delta = ((int64_t)(target_phi - phi)) / nframes; + } else { + phi_delta = 0; + } + + // index in the input buffers + nframes_t i = 0; + + for (nframes_t outsample = 0; outsample < nframes; ++outsample) { + i = phase >> 24; + Sample fractional_phase_part = (phase & fractional_part_mask) / binary_scaling_factor; + + // Linearly interpolate into the output buffer + // using fixed point math + output[outsample] = + input[i] * (1.0f - fractional_phase_part) + + input[i+1] * fractional_phase_part; + phase += phi + phi_delta; + } + + last_phase = (phase & fractional_part_mask); + + // playback distance + return i; +} diff --git a/libs/ardour/tests/interpolation-test.cc b/libs/ardour/tests/interpolation-test.cc new file mode 100644 index 0000000000..8b4f1f840b --- /dev/null +++ b/libs/ardour/tests/interpolation-test.cc @@ -0,0 +1,46 @@ +#include +#include "interpolation-test.h" + +CPPUNIT_TEST_SUITE_REGISTRATION( InterpolationTest ); + +void +InterpolationTest::linearInterpolationTest () +{ + std::cout << "\nLinear Interpolation Test\n"; + std::cout << "\nSpeed: 1.0"; + linear.set_speed (1.0); + nframes_t result = linear.interpolate (NUM_SAMPLES, input, output); + CPPUNIT_ASSERT_EQUAL ((uint32_t)NUM_SAMPLES - 1, result); + + std::cout << "\nSpeed: 0.5"; + linear.set_speed (0.5); + result = linear.interpolate (NUM_SAMPLES, input, output); + CPPUNIT_ASSERT_EQUAL ((uint32_t)NUM_SAMPLES / 2 - 1, result); + + std::cout << "\nSpeed: 0.2"; + linear.set_speed (0.2); + result = linear.interpolate (NUM_SAMPLES, input, output); + CPPUNIT_ASSERT_EQUAL ((uint32_t)NUM_SAMPLES / 5 - 2, result); + + std::cout << "\nSpeed: 0.02"; + linear.set_speed (0.02); + result = linear.interpolate (NUM_SAMPLES, input, output); + CPPUNIT_ASSERT_EQUAL ((uint32_t)NUM_SAMPLES / 50 - 2, result); + + std::cout << "\nSpeed: 2.0"; + linear.set_speed (2.0); + result = linear.interpolate (NUM_SAMPLES / 2, input, output); + CPPUNIT_ASSERT_EQUAL ((uint32_t)NUM_SAMPLES - 2, result); + +/* + for (int i=0; i < NUM_SAMPLES / 5; ++i) { + std::cout << "input[" << i << "] = " << input[i] << " output[" << i << "] = " << output[i] << std::endl; + } +*/ + std::cout << "\nSpeed: 10.0"; + linear.set_speed (10.0); + result = linear.interpolate (NUM_SAMPLES / 10, input, output); + CPPUNIT_ASSERT_EQUAL ((uint32_t)NUM_SAMPLES - 10, result); +} + + diff --git a/libs/ardour/tests/interpolation-test.h b/libs/ardour/tests/interpolation-test.h new file mode 100644 index 0000000000..762ca2bcef --- /dev/null +++ b/libs/ardour/tests/interpolation-test.h @@ -0,0 +1,58 @@ +/* + * Copyright(C) 2000-2008 Paul Davis + * Author: Hans Baier + * + * Evoral is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or(at your option) any later + * version. + * + * Evoral is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#include "ardour/interpolation.h" + + +class InterpolationTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(InterpolationTest); + CPPUNIT_TEST(linearInterpolationTest); + CPPUNIT_TEST_SUITE_END(); + + #define NUM_SAMPLES 100000000 + + Sample input[NUM_SAMPLES]; + Sample output[NUM_SAMPLES]; + + LinearInterpolation linear; + + public: + + void setUp() { + for (int i = 0; i < NUM_SAMPLES; ++i) { + if (i % 100 == 0) { + input[i] = 1.0f; + } else { + input[i] = 0.0f; + } + output[i] = 0.0f; + } + } + + void tearDown() { + } + + void linearInterpolationTest(); + +}; diff --git a/libs/ardour/tests/testrunner.cpp b/libs/ardour/tests/testrunner.cpp new file mode 100644 index 0000000000..468af59ae4 --- /dev/null +++ b/libs/ardour/tests/testrunner.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include +#include + +int +main() +{ + CppUnit::TestResult testresult; + + CppUnit::TestResultCollector collectedresults; + testresult.addListener (&collectedresults); + + CppUnit::BriefTestProgressListener progress; + testresult.addListener (&progress); + + CppUnit::TestRunner testrunner; + testrunner.addTest (CppUnit::TestFactoryRegistry::getRegistry ().makeTest ()); + testrunner.run (testresult); + + CppUnit::CompilerOutputter compileroutputter (&collectedresults, std::cerr); + compileroutputter.write (); + + return collectedresults.wasSuccessful () ? 0 : 1; +} diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 98bc7106f3..ffdf75a277 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -48,6 +48,7 @@ def configure(conf): autowaf.check_pkg(conf, 'slv2', uselib_store='SLV2', atleast_version='0.6.4', mandatory=False) autowaf.check_pkg(conf, 'sndfile', uselib_store='SNDFILE', atleast_version='1.0.18') autowaf.check_pkg(conf, 'soundtouch-1.0', uselib_store='SOUNDTOUCH', mandatory=False) + autowaf.check_pkg(conf, 'cppunit', uselib_store='CPPUNIT', atleast_version='1.12.0', mandatory=False) conf.env.append_value('CXXFLAGS', '-DUSE_RUBBERBAND') conf.define('HAVE_RUBBERBAND', 1) @@ -241,6 +242,19 @@ def build(bld): obj.source += ' lv2_plugin.cc lv2_event_buffer.cc uri_map.cc ' obj.uselib += ' SLV2 ' obj.cxxflags += ['-DHAVE_SLV2'] + + if bld.env['HAVE_CPPUNIT']: + # Unit tests + obj = bld.new_task_gen('cxx', 'program') + obj.source = ''' + interpolation.cc + tests/interpolation-test.cc + tests/testrunner.cpp + ''' + obj.includes = ['.', './ardour'] + obj.uselib = 'CPPUNIT SIGCPP' + obj.target = 'run-tests' + obj.install_path = '' def shutdown(): autowaf.shutdown()