* added documentaion to libs/ardour/slave.h
* first roughly working midi clock slave git-svn-id: svn://localhost/ardour2/branches/3.0@4025 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
@@ -36,17 +36,119 @@ namespace MIDI {
|
||||
namespace ARDOUR {
|
||||
class Session;
|
||||
|
||||
/**
|
||||
* @class Slave
|
||||
*
|
||||
* @brief The class Slave can be used to sync ardours tempo to an external source
|
||||
* like MTC, MIDI Clock, etc.
|
||||
*
|
||||
* The name of the class may be a bit misleading: A subclass of Slave actually
|
||||
* acts as a master for Ardour, that means Ardour will try to follow the
|
||||
* speed and transport position of the implementation of Slave.
|
||||
* Therefor it is rather that class, that makes Ardour a slave by connecting it
|
||||
* to its external time master.
|
||||
*/
|
||||
class Slave {
|
||||
public:
|
||||
Slave() { }
|
||||
virtual ~Slave() {}
|
||||
|
||||
virtual bool speed_and_position (float&, nframes_t&) = 0;
|
||||
/**
|
||||
* This is the most important function to implement:
|
||||
* Each process cycle, Session::follow_slave will call this method.
|
||||
* and after the method call they should
|
||||
*
|
||||
* Session::follow_slave will then try to follow the given
|
||||
* <emph>position</emph> using a delay locked loop (DLL),
|
||||
* starting with the first given transport speed.
|
||||
* If the values of speed and position contradict each other,
|
||||
* ardour will always follow the position and disregard the speed.
|
||||
* Although, a correct speed is important so that ardour
|
||||
* can sync to the master time source quickly.
|
||||
*
|
||||
* For background information on delay locked loops,
|
||||
* see http://www.kokkinizita.net/papers/usingdll.pdf
|
||||
*
|
||||
* The method has the following precondition:
|
||||
* <ul>
|
||||
* <li>
|
||||
* Slave::ok() should return true, otherwise playback will stop
|
||||
* immediately and the method will not be called
|
||||
* </li>
|
||||
* <li>
|
||||
* when the references speed and position are passed into the Slave
|
||||
* they are uninitialized
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* After the method call the following postconditions should be met:
|
||||
* <ul>
|
||||
* <li>
|
||||
* The first position value on transport start should be 0,
|
||||
* otherwise ardour will try to locate to the new position
|
||||
* rather than move to it
|
||||
* </li>
|
||||
* <li>
|
||||
* the references speed and position should be assigned
|
||||
* to the Slaves current requested transport speed
|
||||
* and transport position.
|
||||
* </li>
|
||||
* <li>
|
||||
* Slave::resolution() should be greater than the maximum distance of
|
||||
* ardours transport position to the slaves requested transport position.
|
||||
* (Otherwise Session:average_slave_delta will become negative, and
|
||||
* the transport will move silently)
|
||||
* </li>
|
||||
* <li>Slave::locked() should return true, otherwise Session::no_roll will be called</li>
|
||||
* <li>Slave::starting() should be false, otherwise the transport will not move until it becomes true</li> *
|
||||
* </ul>
|
||||
*
|
||||
* @param speed - The transport speed requested
|
||||
* @param position - The transport position requested
|
||||
*/
|
||||
virtual bool speed_and_position (float& speed, nframes_t& position) = 0;
|
||||
|
||||
/**
|
||||
* reports to ardour whether the Slave is currently synced to its external
|
||||
* time source.
|
||||
*
|
||||
* @return - when returning false, the transport will stop rolling
|
||||
*/
|
||||
virtual bool locked() const = 0;
|
||||
|
||||
/**
|
||||
* reports to ardour whether the slave is in a sane state
|
||||
*
|
||||
* @return - when returning false, the transport will be stopped and the slave
|
||||
* disconnected from ardour.
|
||||
*/
|
||||
virtual bool ok() const = 0;
|
||||
|
||||
/**
|
||||
* reports to ardour whether the slave is in the process of starting
|
||||
* to roll
|
||||
*
|
||||
* @return - when returning false, transport will not move until this method returns true
|
||||
*/
|
||||
virtual bool starting() const { return false; }
|
||||
|
||||
/**
|
||||
* @return - the timing resolution of the Slave - If the distance of ardours transport
|
||||
* to the slave becomes negative or greater than the resolution, sound will stop
|
||||
* (Session::follow_slave label silent_motion)
|
||||
*/
|
||||
virtual nframes_t resolution() const = 0;
|
||||
|
||||
/**
|
||||
* @return - when returning true, ardour will wait for one second before transport
|
||||
* starts rolling
|
||||
*/
|
||||
virtual bool requires_seekahead () const = 0;
|
||||
|
||||
/**
|
||||
* @return - when returning true, ardour will use transport speed 1.0 no matter what
|
||||
* the slave returns
|
||||
*/
|
||||
virtual bool is_always_synced() const { return false; }
|
||||
};
|
||||
|
||||
@@ -124,18 +226,27 @@ class MIDIClock_Slave : public Slave, public sigc::trackable {
|
||||
MIDI::Port* port;
|
||||
std::vector<sigc::connection> connections;
|
||||
|
||||
/// pulses per quarter note for one MIDI clock frame (default 24)
|
||||
int ppqn;
|
||||
|
||||
/// the duration of one ppqn in frame time
|
||||
double one_ppqn_in_frames;
|
||||
|
||||
/// the time stamp and transport position of the last inbound MIDI clock message
|
||||
SafeTime current;
|
||||
nframes_t midi_clock_frame; /* current time */
|
||||
nframes_t last_inbound_frame; /* when we got it; audio clocked */
|
||||
|
||||
/// The duration of the current MIDI clock frame in frames
|
||||
nframes_t current_midi_clock_frame_duration;
|
||||
/// the timestamp of the last inbound MIDI clock message
|
||||
nframes_t last_inbound_frame;
|
||||
|
||||
/// how many MIDI clock frames to average over
|
||||
static const int32_t accumulator_size = 4;
|
||||
float accumulator[accumulator_size];
|
||||
int32_t accumulator_index;
|
||||
bool have_first_accumulated_speed;
|
||||
float average;
|
||||
|
||||
/// the running average of current_midi_clock_frame_duration
|
||||
float average_midi_clock_frame_duration;
|
||||
|
||||
void reset ();
|
||||
void start (MIDI::Parser& parser, nframes_t timestamp);
|
||||
@@ -143,7 +254,12 @@ class MIDIClock_Slave : public Slave, public sigc::trackable {
|
||||
void update_midi_clock (MIDI::Parser& parser, nframes_t timestamp);
|
||||
void read_current (SafeTime *) const;
|
||||
|
||||
/// whether transport should be rolling
|
||||
bool _started;
|
||||
|
||||
/// is true if the MIDI Start message has just been received until
|
||||
/// the first call of speed_and_position(...)
|
||||
bool _starting;
|
||||
};
|
||||
|
||||
class ADAT_Slave : public Slave
|
||||
|
||||
@@ -45,7 +45,7 @@ MIDIClock_Slave::MIDIClock_Slave (Session& s, MIDI::Port& p, int ppqn)
|
||||
: session (s)
|
||||
, ppqn (ppqn)
|
||||
, accumulator_index (0)
|
||||
, average (0.0)
|
||||
, average_midi_clock_frame_duration (0.0)
|
||||
{
|
||||
rebind (p);
|
||||
reset ();
|
||||
@@ -96,21 +96,21 @@ MIDIClock_Slave::update_midi_clock (Parser& parser, nframes_t timestamp)
|
||||
// for the first MIDI clock event we dont have any past
|
||||
// data, so we assume a sane tempo
|
||||
if(last.timestamp == 0) {
|
||||
midi_clock_frame = one_ppqn_in_frames;
|
||||
current_midi_clock_frame_duration = one_ppqn_in_frames;
|
||||
} else {
|
||||
midi_clock_frame = now - last.timestamp;
|
||||
current_midi_clock_frame_duration = now - last.timestamp;
|
||||
}
|
||||
|
||||
|
||||
// moving average over incoming intervals
|
||||
accumulator[accumulator_index++] = (float) midi_clock_frame;
|
||||
accumulator[accumulator_index++] = (float) current_midi_clock_frame_duration;
|
||||
if(accumulator_index == accumulator_size)
|
||||
accumulator_index = 0;
|
||||
|
||||
average = 0.0;
|
||||
average_midi_clock_frame_duration = 0.0;
|
||||
for(int i = 0; i < accumulator_size; i++)
|
||||
average += accumulator[i];
|
||||
average /= accumulator_size;
|
||||
average_midi_clock_frame_duration += accumulator[i];
|
||||
average_midi_clock_frame_duration /= accumulator_size;
|
||||
|
||||
|
||||
#if 1
|
||||
@@ -123,14 +123,16 @@ MIDIClock_Slave::update_midi_clock (Parser& parser, nframes_t timestamp)
|
||||
std::cerr
|
||||
<< " got MIDI Clock message at time " << now
|
||||
<< " session time: " << session.engine().frame_time()
|
||||
<< " real delta: " << midi_clock_frame
|
||||
<< " real delta: " << current_midi_clock_frame_duration
|
||||
<< " reference: " << one_ppqn_in_frames
|
||||
<< " average: " << average
|
||||
<< " average: " << average_midi_clock_frame_duration
|
||||
<< std::endl;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
current.guard1++;
|
||||
current.position += midi_clock_frame;
|
||||
current.position += current_midi_clock_frame_duration * (one_ppqn_in_frames / average_midi_clock_frame_duration);
|
||||
current.timestamp = now;
|
||||
current.guard2++;
|
||||
|
||||
@@ -150,15 +152,16 @@ MIDIClock_Slave::start (Parser& parser, nframes_t timestamp)
|
||||
return;
|
||||
}
|
||||
|
||||
midi_clock_frame = 0;
|
||||
current_midi_clock_frame_duration = 0;
|
||||
|
||||
session.request_transport_speed(one_ppqn_in_frames / average);
|
||||
session.request_transport_speed(one_ppqn_in_frames / average_midi_clock_frame_duration);
|
||||
current.guard1++;
|
||||
current.position = 0;
|
||||
current.timestamp = now;
|
||||
current.guard2++;
|
||||
|
||||
_started = true;
|
||||
_starting = true;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -166,7 +169,7 @@ MIDIClock_Slave::stop (Parser& parser, nframes_t timestamp)
|
||||
{
|
||||
std::cerr << "MIDIClock_Slave got stop message" << endl;
|
||||
|
||||
midi_clock_frame = 0;
|
||||
current_midi_clock_frame_duration = 0;
|
||||
|
||||
current.guard1++;
|
||||
current.position = 0;
|
||||
@@ -215,7 +218,7 @@ MIDIClock_Slave::speed_and_position (float& speed, nframes_t& pos)
|
||||
pos = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
nframes_t now = session.engine().frame_time();
|
||||
nframes_t frame_rate = session.frame_rate();
|
||||
|
||||
@@ -236,7 +239,7 @@ MIDIClock_Slave::speed_and_position (float& speed, nframes_t& pos)
|
||||
|
||||
cerr << " now: " << now << " last: " << last.timestamp;
|
||||
|
||||
speed = one_ppqn_in_frames / average;
|
||||
speed = one_ppqn_in_frames / average_midi_clock_frame_duration;
|
||||
cerr << " final speed: " << speed;
|
||||
|
||||
if (now > last.timestamp) {
|
||||
@@ -244,8 +247,8 @@ MIDIClock_Slave::speed_and_position (float& speed, nframes_t& pos)
|
||||
// so we interpolate position according to speed
|
||||
nframes_t elapsed = now - last.timestamp;
|
||||
cerr << " elapsed: " << elapsed << " elapsed (scaled) " << elapsed * speed;
|
||||
pos = last.position + elapsed * speed;
|
||||
last.position = pos;
|
||||
nframes_t delta = elapsed * speed;
|
||||
pos = last.position + delta;
|
||||
} else {
|
||||
// A new MIDI clock message has arrived this cycle
|
||||
pos = last.position;
|
||||
@@ -253,7 +256,13 @@ MIDIClock_Slave::speed_and_position (float& speed, nframes_t& pos)
|
||||
|
||||
cerr << " position: " << pos;
|
||||
cerr << endl;
|
||||
|
||||
|
||||
// we want start on frame 0 on the first call after a MIDI start
|
||||
if(_starting) {
|
||||
pos = 0;
|
||||
_starting = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user