From 5f5a570e5094aa6165f44bb6dbcab4a28c617bfa Mon Sep 17 00:00:00 2001 From: Hans Baier Date: Mon, 5 Jan 2009 09:15:08 +0000 Subject: [PATCH] * MIDI clock slave implementation with delay locked loop (DLL) seems to work well * added option to class Slave / Session::process that a slave can have total control over transport speed git-svn-id: svn://localhost/ardour2/branches/3.0@4385 d708f5d6-7413-0410-9779-e7cbd77b26cf --- libs/ardour/ardour/slave.h | 41 +++++++--- libs/ardour/midi_clock_slave.cc | 137 +++++++++++++++----------------- libs/ardour/session_process.cc | 47 ++++++----- 3 files changed, 119 insertions(+), 106 deletions(-) diff --git a/libs/ardour/ardour/slave.h b/libs/ardour/ardour/slave.h index 2c9ef70cac..07d7a4cf6a 100644 --- a/libs/ardour/ardour/slave.h +++ b/libs/ardour/ardour/slave.h @@ -148,6 +148,11 @@ class Slave { * the slave returns */ virtual bool is_always_synced() const { return false; } + + /** + * @return - whether ARDOUR should use the slave speed without any adjustments + */ + virtual bool give_slave_full_control_over_transport_speed() const { return false; } }; struct SafeTime { @@ -219,7 +224,8 @@ class MIDIClock_Slave : public Slave, public sigc::trackable { nframes_t resolution() const; bool requires_seekahead () const { return false; } - + bool give_slave_full_control_over_transport_speed() const { return true; } + private: Session& session; MIDI::Port* port; @@ -231,27 +237,40 @@ class MIDIClock_Slave : public Slave, public sigc::trackable { /// the duration of one ppqn in frame time double one_ppqn_in_frames; + /// the timestamp of the first MIDI clock message + nframes_t first_timestamp; + /// the time stamp and transport position of the last inbound MIDI clock message nframes_t last_timestamp; double last_position; - /// The duration of the current MIDI clock frame in frames - nframes_t current_midi_clock_frame_duration; - - /// how many MIDI clock frames to average over - static const int32_t accumulator_size = 1; - double accumulator[accumulator_size]; - int32_t accumulator_index; + //the delay locked loop (DLL), see www.kokkinizita.net/papers/usingdll.pdf + + /// time at the beginning of the MIDI clock frame + double t0; + + /// calculated end of the MIDI clock frame + double t1; + + /// loop error = real value - expected value + double e; + + /// second order loop error + double e2; + + /// DLL filter bandwidth + double bandwidth; + + /// DLL filter coefficients + double b, c, omega; - /// the running average of current_midi_clock_frame_duration - double average_midi_clock_frame_duration; - void reset (); void start (MIDI::Parser& parser, nframes_t timestamp); void stop (MIDI::Parser& parser, nframes_t timestamp); // we can't use continue because it is a C++ keyword void contineu (MIDI::Parser& parser, nframes_t timestamp); void calculate_one_ppqn_in_frames_at(nframes_t time); + void calculate_filter_coefficients(); void update_midi_clock (MIDI::Parser& parser, nframes_t timestamp); void read_current (SafeTime *) const; bool stop_if_no_more_clock_events(nframes_t& pos, nframes_t now); diff --git a/libs/ardour/midi_clock_slave.cc b/libs/ardour/midi_clock_slave.cc index ef900ceded..b853ddcdf2 100644 --- a/libs/ardour/midi_clock_slave.cc +++ b/libs/ardour/midi_clock_slave.cc @@ -44,14 +44,10 @@ using namespace PBD; MIDIClock_Slave::MIDIClock_Slave (Session& s, MIDI::Port& p, int ppqn) : session (s) , ppqn (ppqn) - , accumulator_index (0) - , average_midi_clock_frame_duration (0.0) + , bandwidth (30.0 / 60.0) // 1 BpM = 1 / 60 Hz { rebind (p); reset (); - - for(int i = 0; i < accumulator_size; i++) - accumulator[i]=0.0; } MIDIClock_Slave::~MIDIClock_Slave() @@ -92,56 +88,79 @@ MIDIClock_Slave::calculate_one_ppqn_in_frames_at(nframes_t time) one_ppqn_in_frames = frames_per_quarter_note / double (ppqn); } +void +MIDIClock_Slave::calculate_filter_coefficients() +{ + // omega = 2 * PI * Bandwidth / MIDI clock frame frequency in Hz + omega = 2.0 * 3.14159265358979323846 * bandwidth * one_ppqn_in_frames / session.frame_rate(); + b = 1.4142135623730950488 * omega; + c = omega * omega; +} + void MIDIClock_Slave::update_midi_clock (Parser& parser, nframes_t timestamp) -{ +{ + // the number of midi clock messages (zero-based) + static long midi_clock_count; + calculate_one_ppqn_in_frames_at(last_position); - // for the first MIDI clock event we don't have any past - // data, so we assume a sane tempo - if(_starting) { - current_midi_clock_frame_duration = one_ppqn_in_frames; - } else { - current_midi_clock_frame_duration = timestamp - last_timestamp; - } - - // moving average over incoming intervals - accumulator[accumulator_index++] = current_midi_clock_frame_duration; - if(accumulator_index == accumulator_size) { - accumulator_index = 0; - } - average_midi_clock_frame_duration = 0.0; - for(int i = 0; i < accumulator_size; i++) { - average_midi_clock_frame_duration += accumulator[i]; - } - average_midi_clock_frame_duration /= double(accumulator_size); - - #ifdef DEBUG_MIDI_CLOCK - std::cerr - << " got MIDI Clock message at time " << timestamp - << " engine time: " << session.engine().frame_time() - << " transport position: " << session.transport_frame() - << " real delta: " << current_midi_clock_frame_duration - << " reference: " << one_ppqn_in_frames - << " average: " << average_midi_clock_frame_duration - << std::endl; - #endif // DEBUG_MIDI_CLOCK + nframes_t timestamp_relative_to_transport = timestamp - first_timestamp; if (_starting) { + midi_clock_count = 0; assert(last_timestamp == 0); assert(last_position == 0); - last_position = 0; - last_timestamp = timestamp; + first_timestamp = timestamp; + timestamp_relative_to_transport = 0; + + // calculate filter coefficients + calculate_filter_coefficients(); + + // initialize DLL + e2 = double(one_ppqn_in_frames) / double(session.frame_rate()); + t0 = double(timestamp_relative_to_transport) / double(session.frame_rate()); + t1 = t0 + e2; // let ardour go after first MIDI Clock Event _starting = false; - session.request_transport_speed (1.0); - } else {; - last_position += double(one_ppqn_in_frames); - last_timestamp = timestamp; - } + } else { + midi_clock_count++; + last_position += one_ppqn_in_frames; + calculate_filter_coefficients(); + // calculate loop error + // we use session.transport_frame() instead of t1 here + // because t1 is used to calculate the transport speed, and since this + // is float, the loop will compensate for accumulating rounding errors + e = (double(last_position) - double(session.transport_frame())) + / double(session.frame_rate()); + + // update DLL + t0 = t1; + t1 += b * e + e2; + e2 += c * e; + + } + + #ifdef DEBUG_MIDI_CLOCK + std::cerr + << "MIDI Clock #" << midi_clock_count + //<< "@" << timestamp + << " (transport-relative: " << timestamp_relative_to_transport << " should be: " << last_position << ", delta: " << (double(last_position) - double(session.transport_frame())) <<" )" + << " transport: " << session.transport_frame() + //<< " engine: " << session.engine().frame_time() + << " real delta: " << timestamp - last_timestamp + << " reference: " << one_ppqn_in_frames + << " t1-t0: " << (t1 -t0) * session.frame_rate() + << " t0: " << t0 * session.frame_rate() + << " t1: " << t1 * session.frame_rate() + << " frame-rate: " << session.frame_rate() + << std::endl; + #endif // DEBUG_MIDI_CLOCK + + last_timestamp = timestamp; } void @@ -151,18 +170,6 @@ MIDIClock_Slave::start (Parser& parser, nframes_t timestamp) cerr << "MIDIClock_Slave got start message at time " << timestamp << " session time: " << session.engine().frame_time() << endl; #endif - if(!locked()) { - cerr << "Did not start because not locked!" << endl; - return; - } - - // initialize accumulator to sane values - calculate_one_ppqn_in_frames_at(0); - - for(int i = 0; i < accumulator_size; i++) { - accumulator[i] = one_ppqn_in_frames; - } - last_position = 0; last_timestamp = 0; @@ -187,8 +194,6 @@ MIDIClock_Slave::stop (Parser& parser, nframes_t timestamp) std::cerr << "MIDIClock_Slave got stop message" << endl; #endif - current_midi_clock_frame_duration = 0; - last_position = 0; last_timestamp = 0; @@ -249,19 +254,11 @@ MIDIClock_Slave::speed_and_position (float& speed, nframes_t& pos) if (stop_if_no_more_clock_events(pos, engine_now)) { return false; } - - #ifdef DEBUG_MIDI_CLOCK - cerr << "speed_and_position: engine time: " << engine_now << " last message timestamp: " << last_timestamp; - #endif - + // calculate speed - double speed_double = one_ppqn_in_frames / average_midi_clock_frame_duration; + double speed_double = ((t1 - t0) * session.frame_rate()) / one_ppqn_in_frames; speed = float(speed_double); - #ifdef DEBUG_MIDI_CLOCK - cerr << " final speed: " << speed; - #endif - // calculate position if (engine_now > last_timestamp) { // we are in between MIDI clock messages @@ -272,13 +269,7 @@ MIDIClock_Slave::speed_and_position (float& speed, nframes_t& pos) // A new MIDI clock message has arrived this cycle pos = last_position; } - - #ifdef DEBUG_MIDI_CLOCK - cerr << " transport position engine_now: " << session.transport_frame(); - cerr << " calculated position: " << pos; - cerr << endl; - #endif - + return true; } diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc index 7a7e19ceb2..0be6c8abdc 100644 --- a/libs/ardour/session_process.cc +++ b/libs/ardour/session_process.cc @@ -542,16 +542,19 @@ Session::follow_slave (nframes_t nframes, nframes_t offset) float adjusted_speed = slave_speed + (delta / float(_current_frame_rate)); - #ifdef DEBUG_SLAVES - cerr << "adjust using " << delta - << " towards " << adjusted_speed - << " ratio = " << adjusted_speed / slave_speed - << " current = " << _transport_speed - << " slave @ " << slave_speed - << endl; - #endif - - request_transport_speed (adjusted_speed); + if (_slave->give_slave_full_control_over_transport_speed()) { + request_transport_speed(slave_speed); + } else { + request_transport_speed(adjusted_speed); + #ifdef DEBUG_SLAVES + cerr << "adjust using " << delta + << " towards " << adjusted_speed + << " ratio = " << adjusted_speed / slave_speed + << " current = " << _transport_speed + << " slave @ " << slave_speed + << endl; + #endif + } if (abs(average_slave_delta) > (long) _slave->resolution()) { cerr << "average slave delta greater than slave resolution, going to silent motion\n"; @@ -576,9 +579,9 @@ Session::follow_slave (nframes_t nframes, nframes_t offset) } silent_motion: -#ifdef DEBUG_SLAVES + #ifdef DEBUG_SLAVES cerr << "reached silent_motion:" <= slave_wait_end) { #ifdef DEBUG_SLAVES cerr << "\tstart at " << _transport_frame << endl; @@ -741,9 +744,9 @@ Session::track_slave_state( if (slave_state == Running && _transport_speed == 0.0f) { -#ifdef DEBUG_SLAVES + #ifdef DEBUG_SLAVES cerr << "slave starts transport\n"; -#endif + #endif start_transport (); } @@ -753,10 +756,10 @@ Session::track_slave_state( if (_transport_speed != 0.0f) { -#ifdef DEBUG_SLAVES + #ifdef DEBUG_SLAVES cerr << "slave stops transport: " << slave_speed << " frame: " << slave_transport_frame << " tf = " << _transport_frame << endl; -#endif + #endif if (Config->get_slave_source() == JACK) { last_stop_frame = _transport_frame; @@ -766,9 +769,9 @@ Session::track_slave_state( } if (slave_transport_frame != _transport_frame) { -#ifdef DEBUG_SLAVES + #ifdef DEBUG_SLAVES cerr << "slave stopped, move to " << slave_transport_frame << endl; -#endif + #endif force_locate (slave_transport_frame, false); }