* MIDI clock slave implementation with delay locked loop (DLL) seems to work well
authorHans Baier <hansfbaier@googlemail.com>
Mon, 5 Jan 2009 09:15:08 +0000 (09:15 +0000)
committerHans Baier <hansfbaier@googlemail.com>
Mon, 5 Jan 2009 09:15:08 +0000 (09:15 +0000)
* 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
libs/ardour/midi_clock_slave.cc
libs/ardour/session_process.cc

index 2c9ef70cac4b8e3fe571c8546808302525feda2d..07d7a4cf6af261acfe43a83be27141cdf562a133 100644 (file)
@@ -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);
index ef900cededc113202e4b2540f7e124af8c066d08..b853ddcdf26ac6daf099a28555e8a3326b7555cf 100644 (file)
@@ -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)
-{                      
-       calculate_one_ppqn_in_frames_at(last_position);
+{                                      
+       // the number of midi clock messages (zero-based)
+       static long midi_clock_count;
        
-       // 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);
+       calculate_one_ppqn_in_frames_at(last_position);
        
-       #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;
 }
 
index 7a7e19ceb2a246ac5be863ce076f13ddb3578886..0be6c8abdc5a24d0b7fb4927bfbf4a1c676f6a9e 100644 (file)
@@ -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);
                }