* made MidiClock_Slave conform more to to the Spec by starting transport
[ardour.git] / libs / ardour / midi_clock_slave.cc
index f570b5406a1f667e9982653d261237b320a07bdf..e6639446d58a78d82a7a992e4a7fe9a43c876bf7 100644 (file)
 #include <pbd/pthread_utils.h>
 
 #include <midi++/port.h>
+#include <midi++/jack.h>
 #include <ardour/slave.h>
 #include <ardour/session.h>
 #include <ardour/audioengine.h>
 #include <ardour/cycles.h>
 #include <ardour/tempo.h>
 
+
 #include "i18n.h"
 
 using namespace ARDOUR;
@@ -42,9 +44,14 @@ 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)
 {
        rebind (p);
        reset ();
+
+       for(int i = 0; i < accumulator_size; i++)
+               accumulator[i]=0.0;
 }
 
 MIDIClock_Slave::~MIDIClock_Slave()
@@ -60,27 +67,21 @@ MIDIClock_Slave::rebind (MIDI::Port& p)
 
        port = &p;
 
+#ifdef DEBUG_MIDI_CLOCK                
        std::cerr << "MIDIClock_Slave: connecting to port " << port->name() << std::endl;
+#endif
 
-       connections.push_back (port->input()->timing.connect (mem_fun (*this, &MIDIClock_Slave::update_midi_clock)));
-       connections.push_back (port->input()->start.connect  (mem_fun (*this, &MIDIClock_Slave::start)));
-       connections.push_back (port->input()->stop.connect   (mem_fun (*this, &MIDIClock_Slave::stop)));
+       connections.push_back (port->input()->timing.connect   (mem_fun (*this, &MIDIClock_Slave::update_midi_clock)));
+       connections.push_back (port->input()->start.connect    (mem_fun (*this, &MIDIClock_Slave::start)));
+       connections.push_back (port->input()->contineu.connect (mem_fun (*this, &MIDIClock_Slave::contineu)));
+       connections.push_back (port->input()->stop.connect     (mem_fun (*this, &MIDIClock_Slave::stop)));
 }
 
-void
-MIDIClock_Slave::update_midi_clock (Parser& parser)
+void 
+MIDIClock_Slave::calculate_one_ppqn_in_frames_at(nframes_t time)
 {
-       // ignore clock events if no start event received
-       if(!_started)
-               return;
-
-       nframes_t now = session.engine().frame_time();
-
-       SafeTime last;
-       read_current (&last);
-
-       const Tempo& current_tempo = session.tempo_map().tempo_at(now);
-       const Meter& current_meter = session.tempo_map().meter_at(now);
+       const Tempo& current_tempo = session.tempo_map().tempo_at(time);
+       const Meter& current_meter = session.tempo_map().meter_at(time);
        double frames_per_beat =
                current_tempo.frames_per_beat(session.nominal_frame_rate(),
                                              current_meter);
@@ -88,27 +89,65 @@ MIDIClock_Slave::update_midi_clock (Parser& parser)
        double quarter_notes_per_beat = 4.0 / current_tempo.note_type();
        double frames_per_quarter_note = frames_per_beat / quarter_notes_per_beat;
 
-       one_ppqn_in_frames = frames_per_quarter_note / ppqn;
-
-       /*
-          Also compensate for audio latency.
-       */
+       one_ppqn_in_frames = frames_per_quarter_note / double (ppqn);
+}
 
-       midi_clock_frame += (long) (one_ppqn_in_frames)
-                           + session.worst_output_latency();
+void
+MIDIClock_Slave::update_midi_clock (Parser& parser, nframes_t timestamp)
+{      
+       nframes_t now = timestamp;
 
-       /*
-       std::cerr << "got MIDI Clock message at time " << now  
-                 << " midi_clock_frame: " << midi_clock_frame 
-                 << " one_ppqn_in_frames: " << one_ppqn_in_frames << std::endl;
-       */
-       if (first_midi_clock_frame == 0) {
-               first_midi_clock_frame = midi_clock_frame;
-               first_midi_clock_time = now;
+       SafeTime last;
+       read_current (&last);
+       
+       if (_starting) {
+               assert(last.timestamp == 0);
+               // let ardour go after first MIDI Clock Event
+               _starting = false;
        }
-
+               
+       calculate_one_ppqn_in_frames_at(now);
+       
+       // for the first MIDI clock event we don't have any past
+       // data, so we assume a sane tempo
+       if(last.timestamp == 0) {
+               current_midi_clock_frame_duration = one_ppqn_in_frames;
+       } else {
+               current_midi_clock_frame_duration = now - 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                
+#ifdef WITH_JACK_MIDI
+       JACK_MidiPort* jack_port = dynamic_cast<JACK_MidiPort*>(port);
+       pthread_t process_thread_id = 0;
+       if(jack_port) {
+               process_thread_id = jack_port->get_process_thread();
+       }
+       
+       std::cerr 
+                 << " got MIDI Clock message at time " << now  
+                 << " session 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
+#endif // WITH_JACK_MIDI
+       
        current.guard1++;
-       current.position = midi_clock_frame;
+       current.position += one_ppqn_in_frames;
+       current_position += one_ppqn_in_frames;
        current.timestamp = now;
        current.guard2++;
 
@@ -116,25 +155,57 @@ MIDIClock_Slave::update_midi_clock (Parser& parser)
 }
 
 void
-MIDIClock_Slave::start (Parser& parser)
+MIDIClock_Slave::start (Parser& parser, nframes_t timestamp)
 {
-       std::cerr << "MIDIClock_Slave got start message" << endl;
-
-       session.request_transport_speed (1.0);
-       midi_clock_speed = 1.0;
+       
+       nframes_t now = timestamp;
+       
+#ifdef DEBUG_MIDI_CLOCK        
+       cerr << "MIDIClock_Slave got start message at time "  <<  now << " session time: " << session.engine().frame_time() << endl;
+#endif
+       
+       if(!locked()) {
+               cerr << "Did not start because not locked!" << endl;
+               return;
+       }
+       
+       current_midi_clock_frame_duration = 0;
+       
+       current.guard1++;
+       current.position = 0;
+       current_position = 0;   
+       current.timestamp = 0;
+       current.guard2++;
+       
+       _started = true;
        _starting = true;
-       _started  = true;
 }
 
 void
-MIDIClock_Slave::stop (Parser& parser)
+MIDIClock_Slave::contineu (Parser& parser, nframes_t timestamp)
 {
+#ifdef DEBUG_MIDI_CLOCK        
+       std::cerr << "MIDIClock_Slave got continue message" << endl;
+#endif
+       start(parser, timestamp);
+}
+
+
+void
+MIDIClock_Slave::stop (Parser& parser, nframes_t timestamp)
+{
+#ifdef DEBUG_MIDI_CLOCK        
        std::cerr << "MIDIClock_Slave got stop message" << endl;
+#endif
+       
+       current_midi_clock_frame_duration = 0;
 
-       session.request_transport_speed (0);
-       midi_clock_speed = 0.0f;
-       midi_clock_frame = 0;
-       _starting = false;
+       current.guard1++;
+       current.position = 0;
+       current_position = 0;   
+       current.timestamp = 0;
+       current.guard2++;
+       
        _started = false;
        reset();
 }
@@ -169,112 +240,92 @@ MIDIClock_Slave::ok() const
 }
 
 bool
-MIDIClock_Slave::speed_and_position (float& speed, nframes_t& pos)
+MIDIClock_Slave::starting() const
 {
-       //std::cerr << "MIDIClock_Slave speed and position() called" << endl;
-       nframes_t now = session.engine().frame_time();
-       nframes_t frame_rate = session.frame_rate();
-       nframes_t elapsed;
-       float speed_now;
-
-       SafeTime last;
-       read_current (&last);
-
-       if (first_midi_clock_time == 0) {
-               speed = 0;
-               pos = last.position;
-               return true;
-       }
+       return _starting;
+}
 
+bool
+MIDIClock_Slave::stop_if_no_more_clock_events(nframes_t& pos, nframes_t now, SafeTime& last)
+{
        /* no timecode for 1/4 second ? conclude that its stopped */
-
-       if (last_inbound_frame && now > last_inbound_frame && now - last_inbound_frame > frame_rate / 4) {
-               midi_clock_speed = 0;
+       if (last_inbound_frame && 
+           now > last_inbound_frame && 
+           now - last_inbound_frame > session.frame_rate() / 4) {
+#ifdef DEBUG_MIDI_CLOCK                        
+               cerr << "No MIDI Clock frames received for some time, stopping!" << endl;
+#endif         
                pos = last.position;
                session.request_locate (pos, false);
                session.request_transport_speed (0);
-               this->stop(*port->input());
+               this->stop(*port->input(), now);
                reset();
-               return false;
-       }
-
-       if(_starting) {
-               speed_now = 1.0;
-               _starting = false;
+               return true;
        } else {
-               speed_now = (float) ((last.position - first_midi_clock_frame) / (double) (now - first_midi_clock_time));
+               return false;
        }
+}
 
-       //cerr << "speed_and_position: speed_now: " << speed_now ;
-       
-       accumulator[accumulator_index++] = speed_now;
-
-       if (accumulator_index >= accumulator_size) {
-               have_first_accumulated_speed = true;
-               accumulator_index = 0;
+bool
+MIDIClock_Slave::speed_and_position (float& speed, nframes_t& pos)
+{
+       if (!_started) {
+               speed = 0.0;
+               pos = 0;
+               return true;
        }
+               
+       nframes_t now = session.engine().frame_time();
+       SafeTime last;
+       read_current (&last);
 
-       if (have_first_accumulated_speed) {
-               float total = 0;
-
-               for (int32_t i = 0; i < accumulator_size; ++i) {
-                       total += accumulator[i];
-               }
-
-               midi_clock_speed = total / accumulator_size;
-
-       } else {
-
-               midi_clock_speed = speed_now;
-
+       if (stop_if_no_more_clock_events(pos, now, last)) {
+               return false;
        }
+       //cerr << " now: " << now << " last: " << last.timestamp;
 
-       if (midi_clock_speed == 0.0f) {
-
-               elapsed = 0;
-
+       // calculate speed
+       double speed_double = one_ppqn_in_frames / average_midi_clock_frame_duration;
+       speed = float(speed_double);
+       //cerr << " final speed: " << speed;
+       
+       // calculate position
+       if (now > last.timestamp) {
+               // we are in between MIDI clock messages
+               // so we interpolate position according to speed
+               nframes_t elapsed = now - last.timestamp;
+               pos = nframes_t (current_position + double(elapsed) * speed_double);
        } else {
-
-               /* scale elapsed time by the current MIDI Clock speed */
-
-               if (last.timestamp && (now > last.timestamp)) {
-                       elapsed = (nframes_t) floor (midi_clock_speed * (now - last.timestamp));
-               } else {
-                       elapsed = 0; /* XXX is this right? */
-               }
+               // A new MIDI clock message has arrived this cycle
+               pos = current_position;
        }
-
-       /* now add the most recent timecode value plus the estimated elapsed interval */
-
-       pos =  elapsed + last.position;
-
-       speed = midi_clock_speed;
        
-       //cerr << " final speed: " << speed << " position: " << pos << endl;
+       /*
+   cerr << " transport position now: " <<  session.transport_frame(); 
+   cerr << " calculated position: " << pos; 
+   cerr << endl;
+   */
+   
        return true;
 }
 
 ARDOUR::nframes_t
 MIDIClock_Slave::resolution() const
 {
-       return (nframes_t) one_ppqn_in_frames;
+       // one beat
+       return (nframes_t) one_ppqn_in_frames * ppqn;
 }
 
 void
 MIDIClock_Slave::reset ()
 {
-       /* XXX massive thread safety issue here. MTC could
-          be being updated as we call this. but this
-          supposed to be a realtime-safe call.
-       */
 
        last_inbound_frame = 0;
        current.guard1++;
        current.position = 0;
+       current_position = 0;           
        current.timestamp = 0;
        current.guard2++;
-       first_midi_clock_frame = 0;
-       first_midi_clock_time = 0;
-
-       midi_clock_speed = 0;
+       
+       session.request_locate(0, false);
 }