* Refactor varispeed playback into own class, replace fixed-point arithmetic by double, fix unit tests for libardour

git-svn-id: svn://localhost/ardour2/branches/3.0@5145 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Hans Baier 2009-06-10 00:03:47 +00:00
parent f284d28d53
commit 43340cd37c
14 changed files with 140 additions and 237 deletions

View File

@ -39,6 +39,7 @@
#include "ardour/diskstream.h"
#include "ardour/audioplaylist.h"
#include "ardour/port.h"
#include "ardour/interpolation.h"
struct tm;
@ -144,50 +145,10 @@ class AudioDiskstream : public Diskstream
*last-- = tmp;
}
}
LinearInterpolation interpolation;
XMLNode* deprecated_io_node;
/**
* Calculate the playback distance during varispeed playback.
* Important for Session::process to know exactly, how many frames
* were passed by.
*/
static nframes_t calculate_varispeed_playback_distance(
nframes_t nframes,
uint64_t& the_last_phase,
uint64_t& the_phi,
uint64_t& the_target_phi)
{
// calculate playback distance in the same way
// as in AudioDiskstream::process_varispeed_playback
uint64_t phase = the_last_phase;
int64_t phi_delta;
nframes_t i = 0;
const int64_t fractional_part_mask = 0xFFFFFF;
const Sample binary_scaling_factor = 16777216.0f;
if (the_phi != the_target_phi) {
phi_delta = ((int64_t)(the_target_phi - the_phi)) / nframes;
} else {
phi_delta = 0;
}
Sample fractional_phase_part;
i = 0;
phase = the_last_phase;
for (nframes_t outsample = 0; outsample < nframes; ++outsample) {
i = phase >> 24;
fractional_phase_part = (phase & fractional_part_mask) / binary_scaling_factor;
phase += the_phi + phi_delta;
}
the_last_phase = (phase & fractional_part_mask);
the_phi = the_target_phi;
return i; // + 1;
}
protected:
friend class Session;

View File

@ -285,13 +285,8 @@ class Diskstream : public SessionObject, public boost::noncopyable
nframes_t wrap_buffer_size;
nframes_t speed_buffer_size;
uint64_t last_phase;
/// diskstream speed in 40.24 fixed point math
uint64_t phi;
/// target diskstream speed in 40.24 fixed point math
uint64_t target_phi;
double _speed;
double _target_speed;
nframes_t file_frame;
nframes_t playback_sample;

View File

@ -1,49 +1,32 @@
#include <math.h>
//#include "ardour/types.h"
#include "ardour/types.h"
typedef float Sample;
#define nframes_t uint32_t
#ifndef __interpolation_h__
#define __interpolation_h__
// 40.24 fixpoint math
#define FIXPOINT_ONE 0x1000000
namespace ARDOUR {
class Interpolation {
protected:
/// speed in fixed point math
uint64_t phi;
protected:
double _speed, _target_speed;
public:
Interpolation () : _speed(0.0L) {}
/// target speed in fixed point math
uint64_t target_phi;
uint64_t last_phase;
void set_speed (double new_speed) { _speed = new_speed; }
void set_target_speed (double new_speed) { _target_speed = new_speed; }
// 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; }
double target_speed() const { return _target_speed; }
double speed() const { return _speed; }
virtual nframes_t interpolate (nframes_t nframes, Sample* input, Sample* output) = 0;
};
class LinearInterpolation : public Interpolation {
public:
public:
nframes_t interpolate (nframes_t nframes, Sample* input, Sample* output);
};
} // namespace ARDOUR
#endif

View File

@ -55,6 +55,7 @@
#include "ardour/session_configuration.h"
#include "ardour/location.h"
#include "ardour/smpte.h"
#include "ardour/interpolation.h"
class XMLTree;
class XMLNode;
@ -1023,9 +1024,8 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
volatile double _transport_speed;
double _last_transport_speed;
// varispeed playback
uint64_t phi; // fixed point transport speed
uint64_t target_phi; // fixed point target transport speed
uint64_t phase; // fixed point phase
double _target_transport_speed;
LinearInterpolation interpolation;
bool auto_play_legal;
nframes_t _last_slave_transport_frame;
nframes_t maximum_output_latency;

View File

@ -793,7 +793,7 @@ AudioDiskstream::process (nframes_t transport_frame, nframes_t nframes, bool can
playback_distance = nframes;
}
phi = target_phi;
_speed = _target_speed;
}
@ -819,63 +819,16 @@ void
AudioDiskstream::process_varispeed_playback(nframes_t nframes, boost::shared_ptr<ChannelList> c)
{
ChannelList::iterator chan;
// 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;
// index in the input buffers
nframes_t i = 0;
// Linearly interpolate into the speed buffer
// using 40.24 fixed point math
//
// 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)
interpolation.set_target_speed (_target_speed);
interpolation.set_speed (_speed);
const int64_t fractional_part_mask = 0xFFFFFF;
const Sample binary_scaling_factor = 16777216.0f;
// phi = fixed point speed
if (phi != target_phi) {
phi_delta = ((int64_t)(target_phi - phi)) / nframes;
} else {
phi_delta = 0;
}
for (chan = c->begin(); chan != c->end(); ++chan) {
Sample fractional_phase_part;
ChannelInfo* chaninfo (*chan);
i = 0;
phase = last_phase;
for (nframes_t outsample = 0; outsample < nframes; ++outsample) {
i = phase >> 24;
fractional_phase_part = (phase & fractional_part_mask) / binary_scaling_factor;
chaninfo->speed_buffer[outsample] =
chaninfo->current_playback_buffer[i] * (1.0f - fractional_phase_part) +
chaninfo->current_playback_buffer[i+1] * fractional_phase_part;
phase += phi + phi_delta;
}
chaninfo->current_playback_buffer = chaninfo->speed_buffer;
}
playback_distance = i; // + 1;
last_phase = (phase & fractional_part_mask);
playback_distance = interpolation.interpolate (
nframes, chaninfo->current_playback_buffer, chaninfo->speed_buffer);
}
}
bool

View File

@ -111,10 +111,8 @@ Diskstream::init (Flag f)
loop_location = 0;
wrap_buffer_size = 0;
speed_buffer_size = 0;
last_phase = 0;
// speed = 1 in 40.24 fixed point math
phi = (uint64_t) (0x1000000);
target_phi = phi;
_speed = 1.0;
_target_speed = _speed;
file_frame = 0;
playback_sample = 0;
playback_distance = 0;
@ -198,7 +196,7 @@ Diskstream::realtime_set_speed (double sp, bool global)
}
_actual_speed = new_speed;
target_phi = (uint64_t) (0x1000000 * fabs(_actual_speed));
_target_speed = fabs(_actual_speed);
}
if (changed) {

View File

@ -1,43 +1,41 @@
#include <stdint.h>
#include "ardour/interpolation.h"
using namespace ARDOUR;
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
// the idea 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;
double acceleration;
double distance = 0.0;
if (_speed != _target_speed) {
acceleration = _target_speed - _speed;
} else {
acceleration = 0.0;
}
last_phase = (phase & fractional_part_mask);
for (nframes_t outsample = 0; outsample < nframes; ++outsample) {
i = distance;
Sample fractional_phase_part = distance - i;
if (input && output) {
// Linearly interpolate into the output buffer
output[outsample] =
input[i] * (1.0f - fractional_phase_part) +
input[i+1] * fractional_phase_part;
}
distance += _speed + acceleration;
}
i = (distance + 0.5L);
// playback distance
return i;
}

View File

@ -113,9 +113,7 @@ Session::Session (AudioEngine &eng,
string mix_template)
: _engine (eng),
phi (0),
target_phi (0),
phase (0),
_target_transport_speed (0.0),
_requested_return_frame (-1),
_scratch_buffers(new BufferSet()),
_silent_buffers(new BufferSet()),
@ -200,9 +198,7 @@ Session::Session (AudioEngine &eng,
nframes_t initial_length)
: _engine (eng),
phi (0),
target_phi (0),
phase (0),
_target_transport_speed (0.0),
_requested_return_frame (-1),
_scratch_buffers(new BufferSet()),
_silent_buffers(new BufferSet()),

View File

@ -322,14 +322,12 @@ Session::process_with_events (nframes_t nframes)
return;
}
/// TODO: Figure out what happens to phi and phase, if transport speed momentarily becomes
/// 1.0 eg. during the adjustments of a slave. If that is a bug, then AudioDiskstream::process
/// is very likely broken too
if (_transport_speed == 1.0) {
frames_moved = (long) nframes;
} else {
frames_moved = (long) AudioDiskstream::
calculate_varispeed_playback_distance(nframes, phase, phi, target_phi);
interpolation.set_target_speed (_target_transport_speed);
interpolation.set_speed (_transport_speed);
frames_moved = (long) interpolation.interpolate (nframes, 0, 0);
}
end_frame = _transport_frame + (nframes_t)frames_moved;
@ -845,14 +843,12 @@ Session::process_without_events (nframes_t nframes)
prepare_diskstreams ();
/// TODO: Figure out what happens to phi and phase, if transport speed momentarily becomes
/// 1.0 eg. during the adjustments of a slave. If that is a bug, then AudioDiskstream::process
/// is very likely broken too
if (_transport_speed == 1.0) {
frames_moved = (long) nframes;
} else {
frames_moved = (long) AudioDiskstream::
calculate_varispeed_playback_distance(nframes, phase, phi, target_phi);
interpolation.set_target_speed (_target_transport_speed);
interpolation.set_speed (_transport_speed);
frames_moved = (long) interpolation.interpolate (nframes, 0, 0);
}
if (process_routes (nframes)) {

View File

@ -156,8 +156,7 @@ Session::first_stage_init (string fullpath, string snapshot_name)
insert_cnt = 0;
_transport_speed = 0;
_last_transport_speed = 0;
phi = (uint64_t) (0x1000000);
target_phi = phi;
_target_transport_speed = 0;
auto_play_legal = false;
transport_sub_state = 0;
_transport_frame = 0;

View File

@ -176,9 +176,7 @@ Session::realtime_stop (bool abort)
reset_slave_state ();
_transport_speed = 0;
phi = 0;
target_phi = 0;
phase = 0;
_target_transport_speed = 0;
if (config.get_use_video_sync()) {
waiting_for_sync_offset = true;
@ -804,7 +802,7 @@ Session::set_transport_speed (double speed, bool abort)
return;
}
target_phi = (uint64_t) (0x1000000 * fabs(speed));
_target_transport_speed = fabs(speed);
/* 8.0 max speed is somewhat arbitrary but based on guestimates regarding disk i/o capability
and user needs. We really need CD-style "skip" playback for ffwd and rewind.
@ -987,9 +985,7 @@ Session::start_transport ()
transport_sub_state |= PendingDeclickIn;
_transport_speed = 1.0;
target_phi = 0x1000000; // speed = 1
phi = target_phi;
phase = 0;
_target_transport_speed = 1.0;
boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {

View File

@ -3,44 +3,71 @@
CPPUNIT_TEST_SUITE_REGISTRATION( InterpolationTest );
using namespace std;
using namespace ARDOUR;
void
InterpolationTest::linearInterpolationTest ()
{
std::cout << "\nLinear Interpolation Test\n";
std::cout << "\nSpeed: 1.0";
cout << "\nLinear Interpolation Test\n";
cout << "\nSpeed: 1.0";
linear.set_speed (1.0);
linear.set_target_speed (linear.speed());
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);
CPPUNIT_ASSERT_EQUAL ((uint32_t)(NUM_SAMPLES * linear.speed()), result);
for (int i = 0; i < NUM_SAMPLES; i += INTERVAL) {
CPPUNIT_ASSERT_EQUAL (1.0f, output[i]);
}
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);
cout << "\nSpeed: 0.5";
linear.set_speed (0.5);
linear.set_target_speed (linear.speed());
result = linear.interpolate (NUM_SAMPLES, input, output);
CPPUNIT_ASSERT_EQUAL ((uint32_t)(NUM_SAMPLES * linear.speed()), result);
for (int i = 0; i < NUM_SAMPLES; i += (INTERVAL / linear.speed() +0.5)) {
CPPUNIT_ASSERT_EQUAL (1.0f, output[i]);
}
cout << "\nSpeed: 0.2";
linear.set_speed (0.2);
linear.set_target_speed (linear.speed());
result = linear.interpolate (NUM_SAMPLES, input, output);
CPPUNIT_ASSERT_EQUAL ((uint32_t)(NUM_SAMPLES * linear.speed()), 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";
cout << "\nSpeed: 0.02";
linear.set_speed (0.02);
linear.set_target_speed (linear.speed());
result = linear.interpolate (NUM_SAMPLES, input, output);
CPPUNIT_ASSERT_EQUAL ((uint32_t)(NUM_SAMPLES * linear.speed()), result);
cout << "\nSpeed: 0.002";
linear.set_speed (0.002);
linear.set_target_speed (linear.speed());
result = linear.interpolate (NUM_SAMPLES, input, output);
CPPUNIT_ASSERT_EQUAL ((uint32_t)(NUM_SAMPLES * linear.speed()), result);
cout << "\nSpeed: 2.0";
linear.set_speed (2.0);
linear.set_target_speed (linear.speed());
result = linear.interpolate (NUM_SAMPLES / 2, input, output);
CPPUNIT_ASSERT_EQUAL ((uint32_t)(NUM_SAMPLES / 2 * linear.speed()), result);
for (int i = 0; i < NUM_SAMPLES / 2; i += (INTERVAL / linear.speed() +0.5)) {
CPPUNIT_ASSERT_EQUAL (1.0f, output[i]);
}
cout << "\nSpeed: 10.0";
linear.set_speed (10.0);
linear.set_target_speed (linear.speed());
result = linear.interpolate (NUM_SAMPLES / 10, input, output);
CPPUNIT_ASSERT_EQUAL ((uint32_t)NUM_SAMPLES - 10, result);
CPPUNIT_ASSERT_EQUAL ((uint32_t)(NUM_SAMPLES / 10 * linear.speed()), result);
for (int i = 0; i < NUM_SAMPLES / 10; i += (INTERVAL / linear.speed() +0.5)) {
CPPUNIT_ASSERT_EQUAL (1.0f, output[i]);
}
/*
for (int i=0; i < NUM_SAMPLES; ++i) {
cout << "input[" << i << "] = " << input[i] << " output[" << i << "] = " << output[i] << endl;
}
*/
}

View File

@ -23,7 +23,6 @@
#include "ardour/interpolation.h"
class InterpolationTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(InterpolationTest);
@ -31,17 +30,18 @@ class InterpolationTest : public CppUnit::TestFixture
CPPUNIT_TEST_SUITE_END();
#define NUM_SAMPLES 100000000
#define INTERVAL 100
Sample input[NUM_SAMPLES];
Sample output[NUM_SAMPLES];
ARDOUR::Sample input[NUM_SAMPLES];
ARDOUR::Sample output[NUM_SAMPLES];
LinearInterpolation linear;
ARDOUR::LinearInterpolation linear;
public:
void setUp() {
for (int i = 0; i < NUM_SAMPLES; ++i) {
if (i % 100 == 0) {
if (i % INTERVAL == 0) {
input[i] = 1.0f;
} else {
input[i] = 0.0f;

View File

@ -136,7 +136,8 @@ def build(bld):
gdither.cc
globals.cc
import.cc
io.cc
interpolation.cc
io.cc
io_processor.cc
jack_slave.cc
ladspa_plugin.cc
@ -245,16 +246,16 @@ def build(bld):
if bld.env['HAVE_CPPUNIT']:
# Unit tests
obj = bld.new_task_gen('cxx', 'program')
obj.source = '''
testobj = bld.new_task_gen('cxx', 'program')
testobj.source = '''
interpolation.cc
tests/interpolation-test.cc
tests/testrunner.cpp
'''
obj.includes = ['.', './ardour']
obj.uselib = 'CPPUNIT SIGCPP'
obj.target = 'run-tests'
obj.install_path = ''
testobj.includes = obj.includes + ['../pbd/']
testobj.uselib = 'CPPUNIT SIGCPP JACK GLIBMM'
testobj.target = 'run-tests'
testobj.install_path = ''
def shutdown():
autowaf.shutdown()