more loop/transport fixups; make visible PH track transport frame as an experiment...
[ardour.git] / libs / ardour / session_transport.cc
index 17a6806c526e18d458e3e018f49df30de1fc350b..f1464014ad019c5fa2bbd6fbfeefcc7b01569235 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 1999-2003 Paul Davis 
+    Copyright (C) 1999-2009 Paul Davis 
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -29,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>
@@ -49,13 +50,12 @@ using namespace sigc;
 using namespace PBD;
 
 void
-Session::request_input_change_handling (boost::shared_ptr<Diskstream> ds)
+Session::request_input_change_handling ()
 {
        if (!(_state_of_the_state & (InitialConnecting|Deletion))) {
                Event* ev = new Event (Event::InputConfigurationChange, Event::Add, Event::Immediate, 0, 0.0);
-               ev->diskstream = ds;
                queue_event (ev);
-       }
+       } 
 }
 
 void
@@ -66,9 +66,8 @@ Session::request_slave_source (SlaveSource src)
        if (src == JACK) {
                /* could set_seamless_loop() be disposed of entirely?*/
                Config->set_seamless_loop (false);
-       } else {
-               Config->set_seamless_loop (true);
-       }
+       } 
+
        ev->slave = src;
        queue_event (ev);
 }
@@ -110,7 +109,7 @@ Session::force_locate (nframes_t target_frame, bool with_roll)
 }
 
 void
-Session::request_play_loop (bool yn)
+Session::request_play_loop (bool yn, bool leave_rolling)
 {
        Event* ev;      
        Location *location = _locations.auto_loop_location();
@@ -121,14 +120,14 @@ Session::request_play_loop (bool yn)
                return;
        }
 
-       ev = new Event (Event::SetLoop, Event::Add, Event::Immediate, 0, 0.0, yn);
+       ev = new Event (Event::SetLoop, Event::Add, Event::Immediate, 0, (leave_rolling ? 1.0 : 0.0), yn);
        queue_event (ev);
 
-       if (!yn && Config->get_seamless_loop() && transport_rolling()) {
+       if (!leave_rolling && !yn && Config->get_seamless_loop() && transport_rolling()) {
                // request an immediate locate to refresh the diskstreams
                // after disabling looping
                request_locate (_transport_frame-1, false);
-       }
+       } 
 }
 
 void
@@ -197,27 +196,9 @@ Session::butler_transport_work ()
        }
 
        if (post_transport_work & PostTransportInputChange) {
-               {
-                       RCUWriter<DiskstreamList> input_writer (diskstreams_input_pending);
-                       boost::shared_ptr<DiskstreamList> input_list = input_writer.get_copy ();
-                       RCUWriter<DiskstreamList> dswriter (diskstreams);
-                       boost::shared_ptr<DiskstreamList> ds = dswriter.get_copy();
-
-                       for (DiskstreamList::iterator i = input_list->begin(); i != input_list->end(); ++i) {
-
-                               /* make the change */
-                               
-                               (*i)->non_realtime_input_change ();
-
-                               /* now transfer it back onto the regular diskstreams list */
-
-                               ds->push_back (*i);
-                       }
-                       
-                       input_list->clear();
+               for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
+                       (*i)->non_realtime_input_change ();
                }
-
-               diskstreams_input_pending.flush ();
        }
 
        if (post_transport_work & PostTransportSpeed) {
@@ -225,8 +206,7 @@ Session::butler_transport_work ()
        }
 
        if (post_transport_work & PostTransportReverse) {
-
-
+               
                clear_clicks();
                cumulative_rf_motion = 0;
                reset_rf_scale (0);
@@ -390,47 +370,101 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
                update_latency_compensation (true, abort);
        }
 
-       if ((Config->get_slave_source() == None && 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 ();
                }
                
-               if (((Config->get_slave_source() == None && Config->get_auto_return()) || synced_to_jack()) && !(post_transport_work & PostTransportLocate)) {
+               if (((Config->get_slave_source() == None && Config->get_auto_return()) || 
+                    synced_to_jack() ||
+                    _requested_return_frame >= 0) &&
+                   !(post_transport_work & PostTransportLocate)) {
+
+                       /* no explicit locate queued */
+
+                       bool do_locate = false;
+
+                       if (_requested_return_frame >= 0) {
+
+                               /* explicit return request pre-queued in event list. overrides everything else */
+                               
+                               cerr << "explicit auto-return to " << _requested_return_frame << endl;
+
+                               _transport_frame = _requested_return_frame;
+                               do_locate = true;
+
+                       } else {
+                               if (Config->get_auto_return()) {
+
+                                       if (play_loop) {
+
+                                               /* don't try to handle loop play when synced to JACK */
+
+                                               if (!synced_to_jack()) {
+
+                                                       Location *location = _locations.auto_loop_location();
+                                                       
+                                                       if (location != 0) {
+                                                               _transport_frame = location->start();
+                                                       } else {
+                                                               _transport_frame = _last_roll_location;
+                                                       }
+                                                       do_locate = true;
+                                               }
+
+                                       } else if (_play_range) {
+
+                                               /* return to start of range */
+
+                                               if (!current_audio_range.empty()) {
+                                                       _transport_frame = current_audio_range.front().start;
+                                                       do_locate = true;
+                                               }
 
-                       _transport_frame = last_stop_frame;
+                                       } else {
+                                               
+                                               /* regular auto-return */
+                                               
+                                               _transport_frame = _last_roll_location;
+                                               do_locate = true;
+                                       }
+                               } 
+                       }
+
+                       _requested_return_frame = -1;                   
 
-                       if (synced_to_jack()) {
+                       if (do_locate) {
                                _engine.transport_locate (_transport_frame);
                        }
                } 
 
-#ifndef LEAVE_TRANSPORT_UNADJUSTED
        }
-#endif
 
-               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);
-                               }
+       /* this for() block can be put inside the previous if() and has the effect of ... ??? what */
+
+       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()));
                        }
-                       if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) {
-                               finished = false;
-                               /* we will be back */
-                               return;
+                       else {
+                               (*i)->seek (_transport_frame);
                        }
                }
-#ifdef LEAVE_TRANSPORT_UNADJUSTED
+               if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) {
+                       finished = false;
+                       /* we will be back */
+                       return;
+               }
        }
-#endif
 
-       last_stop_frame = _transport_frame;
+        have_looped = false; 
 
-       send_full_time_code ();
+        send_full_time_code ();
        deliver_mmc (MIDI::MachineControl::cmdStop, 0);
        deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
 
@@ -439,14 +473,16 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
                /* 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()) {
@@ -471,12 +507,12 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
 
        if (post_transport_work & PostTransportStop) { 
                _play_range = false;
-
-               /* do not turn off autoloop on stop */
-               
+               play_loop = false;
        }
 
-       PositionChanged (_transport_frame); /* EMIT SIGNAL */
+        nframes_t tf = _transport_frame;
+
+        PositionChanged (tf); /* EMIT SIGNAL */
        TransportStateChange (); /* EMIT SIGNAL */
 
        /* and start it up again if relevant */
@@ -512,30 +548,34 @@ Session::check_declick_out ()
 }
 
 void
-Session::set_play_loop (bool yn)
+Session::set_play_loop (bool yn, bool leave_rolling)
 {
        /* Called from event-handling context */
-       
-       if ((actively_recording() && yn) || _locations.auto_loop_location() == 0) {
+
+       Location *loc;
+
+       if (yn == play_loop) {
+               return;
+       }
+
+       if ((actively_recording() && yn) || (loc = _locations.auto_loop_location()) == 0) {
                return;
        }
        
        set_dirty();
-
+       
        if (yn && Config->get_seamless_loop() && synced_to_jack()) {
                warning << _("Seamless looping cannot be supported while Ardour is using JACK transport.\n"
                             "Recommend changing the configured options")
                        << endmsg;
                return;
        }
-
        
        if ((play_loop = yn)) {
 
-               Location *loc;
+               if (loc) {
 
-               
-               if ((loc = _locations.auto_loop_location()) != 0) {
+                       set_play_range (false, true);
 
                        if (Config->get_seamless_loop()) {
                                // set all diskstreams to use internal looping
@@ -556,25 +596,17 @@ Session::set_play_loop (bool yn)
                                }
                        }
                        
-                       /* stick in the loop event */
+                       /* put the loop event into the event list */
                        
                        Event* event = new Event (Event::AutoLoop, Event::Replace, loc->end(), loc->start(), 0.0f);
                        merge_event (event);
 
-                       /* locate to start of loop and roll if current pos is outside of the loop range */
-                       if (_transport_frame < loc->start() || _transport_frame > loc->end()) {
-                               event = new Event (Event::LocateRoll, Event::Add, Event::Immediate, loc->start(), 0, !synced_to_jack());
-                               merge_event (event);
-                       }
-                       else {
-                               // locate to current position (+ 1 to force reload)
-                               event = new Event (Event::LocateRoll, Event::Add, Event::Immediate, _transport_frame + 1, 0, !synced_to_jack());
-                               merge_event (event);
-                       }
+                       /* locate to start of loop and roll */
+                       event = new Event (Event::LocateRoll, Event::Add, Event::Immediate, loc->start(), 0, !synced_to_jack());
+                       merge_event (event);
                }
 
 
-
        } else {
                clear_events (Event::AutoLoop);
 
@@ -587,6 +619,8 @@ Session::set_play_loop (bool yn)
                }
                
        }
+
+       TransportStateChange ();
 }
 
 void
@@ -629,10 +663,29 @@ Session::start_locate (nframes_t target_frame, bool with_roll, bool with_flush,
        }
 }
 
+int
+Session::micro_locate (nframes_t distance)
+{
+       boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
+       
+       for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
+               if (!(*i)->can_internal_playback_seek (distance)) {
+                       return -1;
+               }
+       }
+
+       for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
+               (*i)->internal_playback_seek (distance);
+       }
+       
+       _transport_frame += distance;
+       return 0;
+}
+
 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;
        }
 
@@ -658,12 +711,12 @@ 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);
        } 
 
        if ( !with_loop || loop_changing) {
-
+               
                post_transport_work = PostTransportWork (post_transport_work | PostTransportLocate);
                
                if (with_roll) {
@@ -721,11 +774,29 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
                
                if (al && (_transport_frame < al->start() || _transport_frame > al->end())) {
                        // cancel looping directly, this is called from event handling context
-                       set_play_loop (false);
+                       set_play_loop (false, 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);
+                                       }
+                               }
+                       }
+                       have_looped = true;
+                       TransportLooped(); // EMIT SIGNAL
                }
        }
        
        loop_changing = false;
+
+       _send_smpte_update = true;
 }
 
 void
@@ -817,6 +888,7 @@ Session::set_transport_speed (float speed, bool abort)
 
                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);
+                       last_stop_frame = _transport_frame;
                }
                
                _last_transport_speed = _transport_speed;
@@ -880,6 +952,7 @@ void
 Session::start_transport ()
 {
        _last_roll_location = _transport_frame;
+       have_looped = false;
 
        /* if record status is Enabled, move it to Recording. if its
           already Recording, move it to Disabled. 
@@ -893,25 +966,15 @@ Session::start_transport ()
                break;
 
        case Recording:
-               disable_record (false);
+               if (!play_loop) {
+                       disable_record (false);
+               }
                break;
 
        default:
                break;
        }
 
-       if (!synced_to_jack() || _exporting) {
-               actually_start_transport ();
-       } else {
-               waiting_to_start = true;
-       }
-}
-
-void
-Session::actually_start_transport ()
-{
-       waiting_to_start = false;
-
        transport_sub_state |= PendingDeclickIn;
        _transport_speed = 1.0;
        
@@ -943,7 +1006,7 @@ Session::post_transport ()
 
        if (post_transport_work & PostTransportLocate) {
 
-               if (((Config->get_slave_source() == None && 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 {
@@ -977,7 +1040,7 @@ Session::reset_rf_scale (nframes_t motion)
 }
 
 void
-Session::set_slave_source (SlaveSource src)
+Session::set_slave_source (SlaveSource src, bool stop_the_transport)
 {
        bool reverse = false;
        bool non_rt_required = false;
@@ -1002,7 +1065,9 @@ Session::set_slave_source (SlaveSource src)
 
        switch (src) {
        case None:
-               stop_transport ();
+               if (stop_the_transport) {
+                       stop_transport ();
+               }
                break;
                
        case MTC:
@@ -1078,27 +1143,33 @@ Session::set_audio_range (list<AudioRange>& range)
 }
 
 void
-Session::request_play_range (bool yn)
+Session::request_play_range (bool yn, bool leave_rolling)
 {
-       Event* ev = new Event (Event::SetPlayRange, Event::Add, Event::Immediate, 0, 0.0f, yn);
+       Event* ev = new Event (Event::SetPlayRange, Event::Add, Event::Immediate, 0, (leave_rolling ? 1.0 : 0.0), yn);
        queue_event (ev);
 }
 
 void
-Session::set_play_range (bool yn)
+Session::set_play_range (bool yn, bool leave_rolling)
 {
        /* Called from event-processing context */
 
-       if (_play_range != yn) {
-               _play_range = yn;
-               setup_auto_play ();
+       if (yn) {
+               /* cancel loop play */
+               set_play_loop (false, true);
+       }
 
-               if (!_play_range) {
-                       /* stop transport */
-                       Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0f, false);
-                       merge_event (ev);
-               }
+       _play_range = yn;
+
+       setup_auto_play ();
+       
+       if (!_play_range && !leave_rolling) {
+               /* stop transport */
+               Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0f, false);
+               merge_event (ev);
        }
+
+       TransportStateChange ();
 }
 
 void
@@ -1150,7 +1221,7 @@ Session::setup_auto_play ()
                }
                
        } else if (sz == 1) {
-               
+
                ev = new Event (Event::RangeStop, Event::Add, current_audio_range.front().end, 0, 0.0f);
                merge_event (ev);
                
@@ -1162,6 +1233,14 @@ Session::setup_auto_play ()
        merge_event (ev);
 }
 
+void
+Session::request_roll_at_and_return (nframes_t start, nframes_t return_to)
+{
+       Event *ev = new Event (Event::LocateRollLocate, Event::Add, Event::Immediate, return_to, 1.0);
+       ev->target2_frame = start;
+       queue_event (ev);
+}
+
 void
 Session::request_bounded_roll (nframes_t start, nframes_t end)
 {
@@ -1186,11 +1265,16 @@ Session::engine_halted ()
        g_atomic_int_set (&butler_should_do_transport_work, 0);
        post_transport_work = PostTransportWork (0);
        stop_butler ();
-       
+
        realtime_stop (false);
        non_realtime_stop (false, 0, ignored);
        transport_sub_state = 0;
 
+       if (synced_to_jack()) {
+               /* transport is already stopped, hence the second argument */
+               set_slave_source (None, false);
+       }
+
        TransportStateChange (); /* EMIT SIGNAL */
 }
 
@@ -1198,9 +1282,9 @@ Session::engine_halted ()
 void
 Session::xrun_recovery ()
 {
-       if (Config->get_stop_recording_on_xrun() && actively_recording()) {
+       Xrun (transport_frame()); //EMIT SIGNAL
 
-                HaltOnXrun (); /* EMIT SIGNAL */
+       if (Config->get_stop_recording_on_xrun() && actively_recording()) {
 
                /* it didn't actually halt, but we need
                   to handle things in the same way.
@@ -1221,6 +1305,11 @@ Session::update_latency_compensation (bool with_stop, bool abort)
 
        _worst_track_latency = 0;
 
+#undef DEBUG_LATENCY
+#ifdef DEBUG_LATENCY
+       cerr << "\n---------------------------------\nUPDATE LATENCY\n";
+#endif
+
        boost::shared_ptr<RouteList> r = routes.reader ();
 
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
@@ -1241,6 +1330,10 @@ Session::update_latency_compensation (bool with_stop, bool abort)
                }
        }
 
+#ifdef DEBUG_LATENCY
+       cerr << "\tworst was " << _worst_track_latency << endl;
+#endif
+
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                (*i)->set_latency_delay (_worst_track_latency);
        }
@@ -1268,3 +1361,19 @@ Session::update_latency_compensation_proxy (void* ignored)
 {
        update_latency_compensation (false, false);
 }
+
+void
+Session::allow_auto_play (bool yn)
+{
+       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);
+       }
+}