13
0

* 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
This commit is contained in:
Hans Baier 2009-01-05 09:15:08 +00:00
parent be668e55e8
commit 5f5a570e50
3 changed files with 119 additions and 106 deletions

View File

@ -148,6 +148,11 @@ class Slave {
* the slave returns * the slave returns
*/ */
virtual bool is_always_synced() const { return false; } 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 { struct SafeTime {
@ -219,6 +224,7 @@ class MIDIClock_Slave : public Slave, public sigc::trackable {
nframes_t resolution() const; nframes_t resolution() const;
bool requires_seekahead () const { return false; } bool requires_seekahead () const { return false; }
bool give_slave_full_control_over_transport_speed() const { return true; }
private: private:
Session& session; Session& session;
@ -231,20 +237,32 @@ class MIDIClock_Slave : public Slave, public sigc::trackable {
/// the duration of one ppqn in frame time /// the duration of one ppqn in frame time
double one_ppqn_in_frames; 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 /// the time stamp and transport position of the last inbound MIDI clock message
nframes_t last_timestamp; nframes_t last_timestamp;
double last_position; double last_position;
/// The duration of the current MIDI clock frame in frames //the delay locked loop (DLL), see www.kokkinizita.net/papers/usingdll.pdf
nframes_t current_midi_clock_frame_duration;
/// how many MIDI clock frames to average over /// time at the beginning of the MIDI clock frame
static const int32_t accumulator_size = 1; double t0;
double accumulator[accumulator_size];
int32_t accumulator_index;
/// the running average of current_midi_clock_frame_duration /// calculated end of the MIDI clock frame
double average_midi_clock_frame_duration; 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;
void reset (); void reset ();
void start (MIDI::Parser& parser, nframes_t timestamp); void start (MIDI::Parser& parser, nframes_t timestamp);
@ -252,6 +270,7 @@ class MIDIClock_Slave : public Slave, public sigc::trackable {
// we can't use continue because it is a C++ keyword // we can't use continue because it is a C++ keyword
void contineu (MIDI::Parser& parser, nframes_t timestamp); void contineu (MIDI::Parser& parser, nframes_t timestamp);
void calculate_one_ppqn_in_frames_at(nframes_t time); 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 update_midi_clock (MIDI::Parser& parser, nframes_t timestamp);
void read_current (SafeTime *) const; void read_current (SafeTime *) const;
bool stop_if_no_more_clock_events(nframes_t& pos, nframes_t now); bool stop_if_no_more_clock_events(nframes_t& pos, nframes_t now);

View File

@ -44,14 +44,10 @@ using namespace PBD;
MIDIClock_Slave::MIDIClock_Slave (Session& s, MIDI::Port& p, int ppqn) MIDIClock_Slave::MIDIClock_Slave (Session& s, MIDI::Port& p, int ppqn)
: session (s) : session (s)
, ppqn (ppqn) , ppqn (ppqn)
, accumulator_index (0) , bandwidth (30.0 / 60.0) // 1 BpM = 1 / 60 Hz
, average_midi_clock_frame_duration (0.0)
{ {
rebind (p); rebind (p);
reset (); reset ();
for(int i = 0; i < accumulator_size; i++)
accumulator[i]=0.0;
} }
MIDIClock_Slave::~MIDIClock_Slave() 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); 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 void
MIDIClock_Slave::update_midi_clock (Parser& parser, nframes_t timestamp) 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); calculate_one_ppqn_in_frames_at(last_position);
// for the first MIDI clock event we don't have any past nframes_t timestamp_relative_to_transport = timestamp - first_timestamp;
// 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
if (_starting) { if (_starting) {
midi_clock_count = 0;
assert(last_timestamp == 0); assert(last_timestamp == 0);
assert(last_position == 0); assert(last_position == 0);
last_position = 0; first_timestamp = timestamp;
last_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 // let ardour go after first MIDI Clock Event
_starting = false; _starting = false;
session.request_transport_speed (1.0); } else {
} else {; midi_clock_count++;
last_position += double(one_ppqn_in_frames); last_position += one_ppqn_in_frames;
last_timestamp = timestamp; 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 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; cerr << "MIDIClock_Slave got start message at time " << timestamp << " session time: " << session.engine().frame_time() << endl;
#endif #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_position = 0;
last_timestamp = 0; last_timestamp = 0;
@ -187,8 +194,6 @@ MIDIClock_Slave::stop (Parser& parser, nframes_t timestamp)
std::cerr << "MIDIClock_Slave got stop message" << endl; std::cerr << "MIDIClock_Slave got stop message" << endl;
#endif #endif
current_midi_clock_frame_duration = 0;
last_position = 0; last_position = 0;
last_timestamp = 0; last_timestamp = 0;
@ -250,18 +255,10 @@ MIDIClock_Slave::speed_and_position (float& speed, nframes_t& pos)
return false; return false;
} }
#ifdef DEBUG_MIDI_CLOCK
cerr << "speed_and_position: engine time: " << engine_now << " last message timestamp: " << last_timestamp;
#endif
// calculate speed // 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); speed = float(speed_double);
#ifdef DEBUG_MIDI_CLOCK
cerr << " final speed: " << speed;
#endif
// calculate position // calculate position
if (engine_now > last_timestamp) { if (engine_now > last_timestamp) {
// we are in between MIDI clock messages // we are in between MIDI clock messages
@ -273,12 +270,6 @@ MIDIClock_Slave::speed_and_position (float& speed, nframes_t& pos)
pos = last_position; pos = last_position;
} }
#ifdef DEBUG_MIDI_CLOCK
cerr << " transport position engine_now: " << session.transport_frame();
cerr << " calculated position: " << pos;
cerr << endl;
#endif
return true; return true;
} }

View File

@ -542,6 +542,10 @@ Session::follow_slave (nframes_t nframes, nframes_t offset)
float adjusted_speed = slave_speed + (delta / float(_current_frame_rate)); float adjusted_speed = slave_speed + (delta / float(_current_frame_rate));
if (_slave->give_slave_full_control_over_transport_speed()) {
request_transport_speed(slave_speed);
} else {
request_transport_speed(adjusted_speed);
#ifdef DEBUG_SLAVES #ifdef DEBUG_SLAVES
cerr << "adjust using " << delta cerr << "adjust using " << delta
<< " towards " << adjusted_speed << " towards " << adjusted_speed
@ -550,8 +554,7 @@ Session::follow_slave (nframes_t nframes, nframes_t offset)
<< " slave @ " << slave_speed << " slave @ " << slave_speed
<< endl; << endl;
#endif #endif
}
request_transport_speed (adjusted_speed);
if (abs(average_slave_delta) > (long) _slave->resolution()) { if (abs(average_slave_delta) > (long) _slave->resolution()) {
cerr << "average slave delta greater than slave resolution, going to silent motion\n"; cerr << "average slave delta greater than slave resolution, going to silent motion\n";