diff options
author | Hans Baier <hansfbaier@googlemail.com> | 2008-10-29 07:17:07 +0000 |
---|---|---|
committer | Hans Baier <hansfbaier@googlemail.com> | 2008-10-29 07:17:07 +0000 |
commit | 75e918080093a6ca88abe010c07d883afc697520 (patch) | |
tree | 0ac621b5dd7c863bce98871b69d60cee0e798d31 /libs | |
parent | a1a3dc05ad480ee7cf25ad628b3477461f5709d7 (diff) |
* 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
Diffstat (limited to 'libs')
-rw-r--r-- | libs/ardour/ardour/slave.h | 126 | ||||
-rw-r--r-- | libs/ardour/midi_clock_slave.cc | 45 |
2 files changed, 148 insertions, 23 deletions
diff --git a/libs/ardour/ardour/slave.h b/libs/ardour/ardour/slave.h index 0685e66436..80fac38a2a 100644 --- a/libs/ardour/ardour/slave.h +++ b/libs/ardour/ardour/slave.h @@ -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 diff --git a/libs/ardour/midi_clock_slave.cc b/libs/ardour/midi_clock_slave.cc index 888ead0805..c534c34a99 100644 --- a/libs/ardour/midi_clock_slave.cc +++ b/libs/ardour/midi_clock_slave.cc @@ -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; } |