* 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:
parent
be668e55e8
commit
5f5a570e50
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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:" <<endl;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (slave_speed && _transport_speed) {
|
||||
|
||||
@ -621,9 +624,9 @@ Session::follow_slave (nframes_t nframes, nframes_t offset)
|
||||
|
||||
noroll:
|
||||
/* don't move at all */
|
||||
#ifdef DEBUG_SLAVES
|
||||
#ifdef DEBUG_SLAVES
|
||||
cerr << "reached no_roll:" <<endl;
|
||||
#endif
|
||||
#endif
|
||||
no_roll (nframes, 0);
|
||||
return false;
|
||||
}
|
||||
@ -701,9 +704,9 @@ Session::track_slave_state(
|
||||
|
||||
if (slave_state == Waiting) {
|
||||
|
||||
#ifdef DEBUG_SLAVES
|
||||
#ifdef DEBUG_SLAVES
|
||||
cerr << "waiting at " << slave_transport_frame << endl;
|
||||
#endif
|
||||
#endif
|
||||
if (slave_transport_frame >= 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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user