Merge with trunk R2920.
[ardour.git] / libs / ardour / session_transport.cc
index 678a03ad4e16abcb9853f7ccac6864399e789b0a..d422698f302c2e626c4b4961e13d31edea8bbdf4 100644 (file)
@@ -15,7 +15,6 @@
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-    $Id$
 */
 
 #include <cmath>
@@ -30,6 +29,7 @@
 #include <glibmm/thread.h>
 #include <pbd/pthread_utils.h>
 #include <pbd/memento_command.h>
+#include <pbd/stacktrace.h>
 
 #include <midi++/mmc.h>
 #include <midi++/port.h>
@@ -52,8 +52,10 @@ using namespace PBD;
 void
 Session::request_input_change_handling ()
 {
-       Event* ev = new Event (Event::InputConfigurationChange, Event::Add, Event::Immediate, 0, 0.0);
-       queue_event (ev);
+       if (!(_state_of_the_state & (InitialConnecting|Deletion))) {
+               Event* ev = new Event (Event::InputConfigurationChange, Event::Add, Event::Immediate, 0, 0.0);
+               queue_event (ev);
+       } 
 }
 
 void
@@ -125,7 +127,7 @@ Session::request_play_loop (bool yn)
        if (!yn && Config->get_seamless_loop() && transport_rolling()) {
                // request an immediate locate to refresh the diskstreams
                // after disabling looping
-               request_locate (_transport_frame-1, true);
+               request_locate (_transport_frame-1, false);
        }
 }
 
@@ -133,6 +135,11 @@ void
 Session::realtime_stop (bool abort)
 {
        /* assume that when we start, we'll be moving forwards */
+       
+       // FIXME: where should this really be? [DR]
+       //send_full_time_code();
+       deliver_mmc (MIDI::MachineControl::cmdStop, _transport_frame);
+       deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
 
        if (_transport_speed < 0.0f) {
                post_transport_work = PostTransportWork (post_transport_work | PostTransportStop | PostTransportReverse);
@@ -174,15 +181,20 @@ Session::realtime_stop (bool abort)
                waiting_for_sync_offset = true;
        }
 
-       transport_sub_state = (Config->get_auto_return() ? AutoReturning : 0);
+       transport_sub_state = ((Config->get_slave_source() == None && Config->get_auto_return()) ? AutoReturning : 0);
 }
 
 void
 Session::butler_transport_work ()
 {
+  restart:
+       bool finished;
        boost::shared_ptr<RouteList> r = routes.reader ();
        boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
 
+       int on_entry = g_atomic_int_get (&butler_should_do_transport_work);
+       finished = true;
+
        if (post_transport_work & PostTransportCurveRealloc) {
                for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                        (*i)->curve_reallocate();
@@ -206,30 +218,52 @@ Session::butler_transport_work ()
                cumulative_rf_motion = 0;
                reset_rf_scale (0);
 
-               for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
-                       if (!(*i)->hidden()) {
-                               if ((*i)->speed() != 1.0f || (*i)->speed() != -1.0f) {
-                                       (*i)->seek ((nframes_t) (_transport_frame * (double) (*i)->speed()));
+               /* don't seek if locate will take care of that in non_realtime_stop() */
+
+               if (!(post_transport_work & PostTransportLocate)) {
+                       
+                       for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
+                               if (!(*i)->hidden()) {
+                                       if ((*i)->speed() != 1.0f || (*i)->speed() != -1.0f) {
+                                               (*i)->seek ((nframes_t) (_transport_frame * (double) (*i)->speed()));
+                                       }
+                                       else {
+                                               (*i)->seek (_transport_frame);
+                                       }
                                }
-                               else {
-                                       (*i)->seek (_transport_frame);
+                               if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) {
+                                       /* new request, stop seeking, and start again */
+                                       g_atomic_int_dec_and_test (&butler_should_do_transport_work);
+                                       goto restart;
                                }
                        }
                }
        }
+       
+       if (post_transport_work & PostTransportLocate) {
+               non_realtime_locate ();
+       }
 
-       if (post_transport_work & (PostTransportStop|PostTransportLocate)) {
-               non_realtime_stop (post_transport_work & PostTransportAbort);
+       if (post_transport_work & PostTransportStop) {
+               non_realtime_stop (post_transport_work & PostTransportAbort, on_entry, finished);
+               if (!finished) {
+                       g_atomic_int_dec_and_test (&butler_should_do_transport_work);
+                       goto restart;
+               }
        }
 
        if (post_transport_work & PostTransportOverWrite) {
-               non_realtime_overwrite ();
+               non_realtime_overwrite (on_entry, finished);
+               if (!finished) {
+                       g_atomic_int_dec_and_test (&butler_should_do_transport_work);
+                       goto restart;
+               }
        }
 
        if (post_transport_work & PostTransportAudition) {
                non_realtime_set_audition ();
        }
-
+       
        g_atomic_int_dec_and_test (&butler_should_do_transport_work);
 }
 
@@ -244,7 +278,7 @@ Session::non_realtime_set_speed ()
 }
 
 void
-Session::non_realtime_overwrite ()
+Session::non_realtime_overwrite (int on_entry, bool& finished)
 {
        boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
 
@@ -252,11 +286,27 @@ Session::non_realtime_overwrite ()
                if ((*i)->pending_overwrite) {
                        (*i)->overwrite_existing_buffers ();
                }
+               if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) {
+                       finished = false;
+                       return;
+               }
+       }
+}
+
+       
+void
+Session::non_realtime_locate ()
+{
+       boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
+
+       for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
+               (*i)->non_realtime_locate (_transport_frame);
        }
 }
 
+
 void
-Session::non_realtime_stop (bool abort)
+Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
 {
        struct tm* now;
        time_t     xnow;
@@ -330,7 +380,7 @@ Session::non_realtime_stop (bool abort)
        boost::shared_ptr<RouteList> r = routes.reader ();
 
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-               if (!(*i)->hidden()) {
+               if (!(*i)->is_hidden()) {
                        (*i)->set_pending_declick (0);
                }
        }
@@ -343,17 +393,36 @@ Session::non_realtime_stop (bool abort)
                update_latency_compensation (true, abort);
        }
 
-       if (Config->get_auto_return() || (post_transport_work & PostTransportLocate) || synced_to_jack()) {
+       if ((Config->get_slave_source() == None && Config->get_auto_return()) || 
+           (post_transport_work & PostTransportLocate) || 
+           (_requested_return_frame >= 0) ||
+           synced_to_jack()) {
                
                if (pending_locate_flush) {
-                       flush_all_redirects ();
+                       flush_all_inserts ();
                }
+               
+               if (((Config->get_slave_source() == None && Config->get_auto_return()) || 
+                    synced_to_jack() ||
+                    _requested_return_frame >= 0) &&
+                   !(post_transport_work & PostTransportLocate)) {
 
-               if ((Config->get_auto_return() || synced_to_jack()) && !(post_transport_work & PostTransportLocate)) {
+                       bool do_locate = false;
 
-                       _transport_frame = last_stop_frame;
+                       if (_requested_return_frame >= 0) {
+                               _transport_frame = _requested_return_frame;
+                               _requested_return_frame = -1;
+                               do_locate = true;
+                       } else {
+                               _transport_frame = last_stop_frame;
+                       }
 
-                       if (synced_to_jack()) {
+                       if (synced_to_jack() && !play_loop) {
+                               do_locate = true;
+                       }
+                       
+                       if (do_locate) {
+                               // cerr << "non-realtimestop: transport locate to " << _transport_frame << endl;
                                _engine.transport_locate (_transport_frame);
                        }
                } 
@@ -371,9 +440,12 @@ Session::non_realtime_stop (bool abort)
                                        (*i)->seek (_transport_frame);
                                }
                        }
+                       if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) {
+                               finished = false;
+                               /* we will be back */
+                               return;
+                       }
                }
-               
-               deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
 
 #ifdef LEAVE_TRANSPORT_UNADJUSTED
        }
@@ -381,23 +453,21 @@ Session::non_realtime_stop (bool abort)
 
        last_stop_frame = _transport_frame;
 
-       send_full_time_code ();
-       deliver_mmc (MIDI::MachineControl::cmdStop, 0);
-       deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
-
        if (did_record) {
 
                /* XXX its a little odd that we're doing this here
                   when realtime_stop(), which has already executed,
                   will have done this.
+                  JLC - so let's not because it seems unnecessary and breaks loop record
                */
-               
+#if 0
                if (!Config->get_latched_record_enable()) {
                        g_atomic_int_set (&_record_status, Disabled);
                } else {
                        g_atomic_int_set (&_record_status, Enabled);
                }
                RecordStateChanged (); /* emit signal */
+#endif
        }
        
        if ((post_transport_work & PostTransportLocate) && get_record_enabled()) {
@@ -427,7 +497,9 @@ Session::non_realtime_stop (bool abort)
                
        }
 
-       PositionChanged (_transport_frame); /* EMIT SIGNAL */
+        nframes_t tf = _transport_frame;
+
+        PositionChanged (tf); /* EMIT SIGNAL */
        TransportStateChange (); /* EMIT SIGNAL */
 
        /* and start it up again if relevant */
@@ -541,12 +613,12 @@ Session::set_play_loop (bool yn)
 }
 
 void
-Session::flush_all_redirects ()
+Session::flush_all_inserts ()
 {
        boost::shared_ptr<RouteList> r = routes.reader ();
 
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-               (*i)->flush_redirects ();
+               (*i)->flush_processors ();
        }
 }
 
@@ -583,7 +655,7 @@ Session::start_locate (nframes_t target_frame, bool with_roll, bool with_flush,
 void
 Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop)
 {
-       if (actively_recording()) {
+       if (actively_recording() && !with_loop) {
                return;
        }
 
@@ -595,7 +667,12 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
                return;
        }
 
+       // Update SMPTE time
+       // [DR] FIXME: find out exactly where this should go below
        _transport_frame = target_frame;
+       smpte_time(_transport_frame, transmitting_smpte_time);
+       outbound_mtc_smpte_frame = _transport_frame;
+       next_quarter_frame_to_send = 0;
 
        if (_transport_speed && (!with_loop || loop_changing)) {
                /* schedule a declick. we'll be called again when its done */
@@ -609,7 +686,7 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
                } 
        }
 
-       if (transport_rolling() && !Config->get_auto_play() && !with_roll && !(synced_to_jack() && play_loop)) {
+       if (transport_rolling() && (!auto_play_legal || !Config->get_auto_play()) && !with_roll && !(synced_to_jack() && play_loop)) {
                realtime_stop (false);
        } 
 
@@ -674,11 +751,33 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
                        // cancel looping directly, this is called from event handling context
                        set_play_loop (false);
                }
+               else if (al && _transport_frame == al->start()) {
+                       if (with_loop) {
+                               // this is only necessary for seamless looping
+
+                               boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
+                               
+                               for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
+                                       if ((*i)->record_enabled ()) {
+                                               // tell it we've looped, so it can deal with the record state
+                                               (*i)->transport_looped(_transport_frame);
+                                       }
+                               }
+                       }
+
+                       TransportLooped(); // EMIT SIGNAL
+               }
        }
        
        loop_changing = false;
+
+       _send_smpte_update = true;
 }
 
+/** Set the transport speed.
+ * @param speed New speed
+ * @param abort
+ */
 void
 Session::set_transport_speed (float speed, bool abort)
 {
@@ -694,6 +793,8 @@ Session::set_transport_speed (float speed, bool abort)
 
        if (transport_rolling() && speed == 0.0) {
 
+               /* we are rolling and we want to stop */
+
                if (Config->get_monitoring_model() == HardwareMonitoring)
                {
                        boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
@@ -714,6 +815,8 @@ Session::set_transport_speed (float speed, bool abort)
                
        } else if (transport_stopped() && speed == 1.0) {
 
+               /* we are stopped and we want to start rolling at speed 1 */
+
                if (!get_record_enabled() && Config->get_stop_at_session_end() && _transport_frame >= current_end_frame()) {
                        return;
                }
@@ -766,7 +869,7 @@ Session::set_transport_speed (float speed, bool abort)
                   before the last stop, then we have to do extra work.
                */
 
-               if ((_transport_speed && speed * _transport_speed < 0.0f) || (_last_transport_speed * speed < 0.0f)) {
+               if ((_transport_speed && speed * _transport_speed < 0.0f) || (_last_transport_speed * speed < 0.0f) || (_last_transport_speed == 0.0f && speed < 0.0f)) {
                        post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse);
                }
                
@@ -786,6 +889,8 @@ Session::set_transport_speed (float speed, bool abort)
        }
 }
 
+
+/** Stop the transport.  */
 void
 Session::stop_transport (bool abort)
 {
@@ -844,7 +949,9 @@ Session::start_transport ()
                break;
 
        case Recording:
-               disable_record (false);
+               if (!play_loop) {
+                       disable_record (false);
+               }
                break;
 
        default:
@@ -871,11 +978,14 @@ Session::actually_start_transport ()
                (*i)->realtime_set_speed ((*i)->speed(), true);
        }
 
-       send_mmc_in_another_thread (MIDI::MachineControl::cmdDeferredPlay, 0);
-       
+       deliver_mmc(MIDI::MachineControl::cmdDeferredPlay, _transport_frame);
+
        TransportStateChange (); /* EMIT SIGNAL */
 }
 
+/** Do any transport work in the audio thread that needs to be done after the
+ * transport thread is finished.  Audio thread, realtime safe.
+ */
 void
 Session::post_transport ()
 {
@@ -894,8 +1004,7 @@ Session::post_transport ()
 
        if (post_transport_work & PostTransportLocate) {
 
-               if ((Config->get_auto_play() && !_exporting) || (post_transport_work & PostTransportRoll)) {
-                       
+               if (((Config->get_slave_source() == None && (auto_play_legal && Config->get_auto_play())) && !_exporting) || (post_transport_work & PostTransportRoll)) {
                        start_transport ();
                        
                } else {
@@ -1114,11 +1223,19 @@ Session::setup_auto_play ()
        merge_event (ev);
 }
 
+void
+Session::request_roll_at_and_return (nframes_t start, nframes_t return_to)
+{
+       request_locate (start, false);
+       Event *ev = new Event (Event::LocateRollLocate, Event::Add, Event::Immediate, return_to, 1.0);
+       queue_event (ev);
+}
+
 void
 Session::request_bounded_roll (nframes_t start, nframes_t end)
 {
        request_stop ();
-       Event *ev = new Event (Event::StopOnce, Event::Replace, Event::Immediate, end, 0.0);
+       Event *ev = new Event (Event::StopOnce, Event::Replace, end, Event::Immediate, 0.0);
        queue_event (ev);
        request_locate (start, true);
 }
@@ -1126,6 +1243,8 @@ Session::request_bounded_roll (nframes_t start, nframes_t end)
 void
 Session::engine_halted ()
 {
+       bool ignored;
+
        /* there will be no more calls to process(), so
           we'd better clean up for ourselves, right now.
 
@@ -1138,7 +1257,7 @@ Session::engine_halted ()
        stop_butler ();
        
        realtime_stop (false);
-       non_realtime_stop (false);
+       non_realtime_stop (false, 0, ignored);
        transport_sub_state = 0;
 
        TransportStateChange (); /* EMIT SIGNAL */
@@ -1174,33 +1293,33 @@ Session::update_latency_compensation (bool with_stop, bool abort)
        boost::shared_ptr<RouteList> r = routes.reader ();
 
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+               
                if (with_stop) {
                        (*i)->handle_transport_stopped (abort, (post_transport_work & PostTransportLocate), 
                                                        (!(post_transport_work & PostTransportLocate) || pending_locate_flush));
                }
-
+               
                nframes_t old_latency = (*i)->signal_latency ();
                nframes_t track_latency = (*i)->update_total_latency ();
-
+               
                if (old_latency != track_latency) {
+                       (*i)->update_port_total_latencies ();
                        update_jack = true;
                }
-               
-               if (!(*i)->hidden() && ((*i)->active())) {
+
+               if (!(*i)->is_hidden() && ((*i)->active())) {
                        _worst_track_latency = max (_worst_track_latency, track_latency);
                }
+       } 
+
+       if (update_jack) {
+               _engine.update_total_latencies ();
        }
 
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                (*i)->set_latency_delay (_worst_track_latency);
        }
 
-       /* tell JACK to play catch up */
-
-       if (update_jack) {
-               _engine.update_total_latencies ();
-       }
-
        set_worst_io_latencies ();
 
        /* reflect any changes in latencies into capture offsets
@@ -1214,7 +1333,17 @@ Session::update_latency_compensation (bool with_stop, bool abort)
 }
 
 void
-Session::update_latency_compensation_proxy (void* ignored)
+Session::allow_auto_play (bool yn)
 {
-       update_latency_compensation (false, false);
+       auto_play_legal = yn;
+}
+
+void
+Session::reset_jack_connection (jack_client_t* jack)
+{
+       JACK_Slave* js;
+
+       if (_slave && ((js = dynamic_cast<JACK_Slave*> (_slave)) != 0)) {
+               js->reset_client (jack);
+       }
 }