Fix MidiBuffer::merge_in_place and add aggressive correctness checking.
[ardour.git] / libs / ardour / session_transport.cc
index e9c4e3785fc53b228e20200b069c2bc194f17082..ff36e30743e7750453b48f4ab64d2c85e2262b0b 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 1999-2003 Paul Davis 
+    Copyright (C) 1999-2003 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
@@ -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>
 #include <sigc++/bind.h>
 #include <sigc++/retype.h>
 
-#include <pbd/undo.h>
-#include <pbd/error.h>
+#include "pbd/undo.h"
+#include "pbd/error.h"
 #include <glibmm/thread.h>
-#include <pbd/pthread_utils.h>
-#include <pbd/memento_command.h>
+#include "pbd/pthread_utils.h"
+#include "pbd/memento_command.h"
+#include "pbd/stacktrace.h"
 
-#include <midi++/mmc.h>
-#include <midi++/port.h>
+#include "midi++/mmc.h"
+#include "midi++/port.h"
 
-#include <ardour/ardour.h>
-#include <ardour/audioengine.h>
-#include <ardour/session.h>
-#include <ardour/audio_diskstream.h>
-#include <ardour/auditioner.h>
-#include <ardour/slave.h>
-#include <ardour/location.h>
+#include "ardour/ardour.h"
+#include "ardour/audioengine.h"
+#include "ardour/session.h"
+#include "ardour/audio_diskstream.h"
+#include "ardour/auditioner.h"
+#include "ardour/slave.h"
+#include "ardour/location.h"
 
 #include "i18n.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
@@ -72,14 +74,14 @@ Session::request_slave_source (SlaveSource src)
 }
 
 void
-Session::request_transport_speed (float speed)
+Session::request_transport_speed (double speed)
 {
        Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, speed);
        queue_event (ev);
 }
 
 void
-Session::request_diskstream_speed (Diskstream& ds, float speed)
+Session::request_diskstream_speed (Diskstream& ds, double speed)
 {
        Event* ev = new Event (Event::SetDiskstreamSpeed, Event::Add, Event::Immediate, 0, speed);
        ev->set_ptr (&ds);
@@ -110,7 +112,7 @@ Session::force_locate (nframes_t target_frame, bool with_roll)
 void
 Session::request_play_loop (bool yn)
 {
-       Event* ev;      
+       Event* ev;
        Location *location = _locations.auto_loop_location();
 
        if (location == 0 && yn) {
@@ -133,10 +135,10 @@ 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::cmdStop, 0);
        deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
 
        if (_transport_speed < 0.0f) {
@@ -172,22 +174,28 @@ Session::realtime_stop (bool abort)
        disable_record (true);
 
        reset_slave_state ();
-               
+
        _transport_speed = 0;
+       _target_transport_speed = 0;
 
-       if (Config->get_use_video_sync()) {
+       if (config.get_use_video_sync()) {
                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,29 +214,45 @@ Session::butler_transport_work ()
 
        if (post_transport_work & PostTransportReverse) {
 
-
                clear_clicks();
                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()) {
+                                       (*i)->non_realtime_locate (_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 & (PostTransportStop|PostTransportLocate)) {
-               non_realtime_stop (post_transport_work & PostTransportAbort);
+       if (post_transport_work & PostTransportLocate) {
+               non_realtime_locate ();
+       }
+
+       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) {
@@ -249,7 +273,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();
 
@@ -257,11 +281,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_stop (bool abort)
+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, int on_entry, bool& finished)
 {
        struct tm* now;
        time_t     xnow;
@@ -272,7 +312,7 @@ Session::non_realtime_stop (bool abort)
        saved = false;
 
        boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
-       
+
        for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
                if ((*i)->get_captured_frames () != 0) {
                        did_record = true;
@@ -281,7 +321,7 @@ Session::non_realtime_stop (bool abort)
        }
 
        /* stop and locate are merged here because they share a lot of common stuff */
-       
+
        time (&xnow);
        now = localtime (&xnow);
 
@@ -295,28 +335,28 @@ Session::non_realtime_stop (bool abort)
 
        if (did_record) {
                begin_reversible_command ("capture");
-               
+
                Location* loc = _locations.end_location();
                bool change_end = false;
-               
+
                if (_transport_frame < loc->end()) {
 
                        /* stopped recording before current end */
 
-                       if (_end_location_is_free) {
+                       if (config.get_end_marker_is_free()) {
 
                                /* first capture for this session, move end back to where we are */
 
                                change_end = true;
-                       } 
+                       }
 
                } else if (_transport_frame > loc->end()) {
-                       
+
                        /* stopped recording after the current end, extend it */
 
                        change_end = true;
                }
-               
+
                if (change_end) {
                         XMLNode &before = loc->get_state();
                         loc->set_end(_transport_frame);
@@ -324,44 +364,64 @@ Session::non_realtime_stop (bool abort)
                         add_command (new MementoCommand<Location>(*loc, &before, &after));
                }
 
-               _end_location_is_free = false;
+               config.set_end_marker_is_free (false);
                _have_captured = true;
        }
 
        for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
                (*i)->transport_stopped (*now, xnow, 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);
                }
        }
-       
+
        if (did_record) {
                commit_reversible_command ();
-       }       
-       
+       }
+
        if (_engine.running()) {
                update_latency_compensation (true, abort);
        }
 
-       if (Config->get_auto_return() || (post_transport_work & PostTransportLocate) || synced_to_jack()) {
-               
+       bool const auto_return_enabled =
+               (Config->get_slave_source() == None && config.get_auto_return());
+
+       if (auto_return_enabled ||
+           (post_transport_work & PostTransportLocate) ||
+           (_requested_return_frame >= 0) ||
+           synced_to_jack()) {
+
                if (pending_locate_flush) {
-                       flush_all_redirects ();
+                       flush_all_inserts ();
                }
 
-               if ((Config->get_auto_return() || synced_to_jack()) && !(post_transport_work & PostTransportLocate)) {
+               if ((auto_return_enabled || synced_to_jack() || _requested_return_frame >= 0) &&
+                   !(post_transport_work & PostTransportLocate)) {
+
+                       bool do_locate = false;
+
+                       if (_requested_return_frame >= 0) {
+                               _transport_frame = _requested_return_frame;
+                               _requested_return_frame = -1;
+                               do_locate = true;
+                       } else {
+                               _transport_frame = _last_roll_location;
+                       }
 
-                       _transport_frame = last_stop_frame;
+                       if (synced_to_jack() && !play_loop) {
+                               do_locate = true;
+                       }
 
-                       if (synced_to_jack()) {
+                       if (do_locate) {
+                               // cerr << "non-realtimestop: transport locate to " << _transport_frame << endl;
                                _engine.transport_locate (_transport_frame);
                        }
-               } 
+               }
 
 #ifndef LEAVE_TRANSPORT_UNADJUSTED
        }
@@ -369,12 +429,12 @@ Session::non_realtime_stop (bool abort)
 
                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);
-                               }
+                               (*i)->non_realtime_locate (_transport_frame);
+                       }
+                       if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) {
+                               finished = false;
+                               /* we will be back */
+                               return;
                        }
                }
 
@@ -382,23 +442,29 @@ Session::non_realtime_stop (bool abort)
        }
 #endif
 
-       last_stop_frame = _transport_frame;
+        have_looped = false;
+
+        send_full_time_code (0);
+       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()) {
                /* capture start has been changed, so save pending state */
                save_state ("", true);
@@ -408,7 +474,7 @@ Session::non_realtime_stop (bool abort)
         /* always try to get rid of this */
 
         remove_pending_capture_state ();
-       
+
        /* save the current state of things if appropriate */
 
        if (did_record && !saved) {
@@ -419,14 +485,16 @@ Session::non_realtime_stop (bool abort)
                DurationChanged (); /* EMIT SIGNAL */
        }
 
-       if (post_transport_work & PostTransportStop) { 
+       if (post_transport_work & PostTransportStop) {
                _play_range = false;
 
                /* do not turn off autoloop on stop */
-               
+
        }
 
-       PositionChanged (_transport_frame); /* EMIT SIGNAL */
+        nframes_t tf = _transport_frame;
+
+        PositionChanged (tf); /* EMIT SIGNAL */
        TransportStateChange (); /* EMIT SIGNAL */
 
        /* and start it up again if relevant */
@@ -445,7 +513,7 @@ Session::check_declick_out ()
        /* this is called after a process() iteration. if PendingDeclickOut was set,
           it means that we were waiting to declick the output (which has just been
           done) before doing something else. this is where we do that "something else".
-          
+
           note: called from the audio thread.
        */
 
@@ -465,11 +533,11 @@ void
 Session::set_play_loop (bool yn)
 {
        /* Called from event-handling context */
-       
+
        if ((actively_recording() && yn) || _locations.auto_loop_location() == 0) {
                return;
        }
-       
+
        set_dirty();
 
        if (yn && Config->get_seamless_loop() && synced_to_jack()) {
@@ -479,12 +547,12 @@ Session::set_play_loop (bool yn)
                return;
        }
 
-       
+
        if ((play_loop = yn)) {
 
                Location *loc;
 
-               
+
                if ((loc = _locations.auto_loop_location()) != 0) {
 
                        if (Config->get_seamless_loop()) {
@@ -505,9 +573,9 @@ Session::set_play_loop (bool yn)
                                        }
                                }
                        }
-                       
+
                        /* stick in the loop event */
-                       
+
                        Event* event = new Event (Event::AutoLoop, Event::Replace, loc->end(), loc->start(), 0.0f);
                        merge_event (event);
 
@@ -535,17 +603,17 @@ Session::set_play_loop (bool yn)
                                (*i)->set_loop (0);
                        }
                }
-               
+
        }
 }
 
 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 ();
        }
 }
 
@@ -554,7 +622,7 @@ Session::start_locate (nframes_t target_frame, bool with_roll, bool with_flush,
 {
        if (synced_to_jack()) {
 
-               float sp;
+               double sp;
                nframes_t pos;
 
                _slave->speed_and_position (sp, pos);
@@ -574,15 +642,33 @@ Session::start_locate (nframes_t target_frame, bool with_roll, bool with_flush,
                }
 
        } else {
-
                locate (target_frame, with_roll, with_flush, with_loop);
        }
 }
 
+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;
        }
 
@@ -610,17 +696,17 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
                        pending_locate_roll = with_roll;
                        pending_locate_flush = with_flush;
                        return;
-               } 
+               }
        }
 
-       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) {
                        post_transport_work = PostTransportWork (post_transport_work | PostTransportRoll);
                }
@@ -632,13 +718,13 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
                /* this is functionally what clear_clicks() does but with a tentative lock */
 
                Glib::RWLock::WriterLock clickm (click_lock, Glib::TRY_LOCK);
-       
+
                if (clickm.locked()) {
-                       
+
                        for (Clicks::iterator i = clicks.begin(); i != clicks.end(); ++i) {
                                delete *i;
                        }
-               
+
                        clicks.clear ();
                }
        }
@@ -652,7 +738,7 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
                        for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
                                if ((*i)->record_enabled ()) {
                                        //cerr << "switching from input" << __FILE__ << __LINE__ << endl << endl;
-                                       (*i)->monitor_input (!Config->get_auto_input());
+                                       (*i)->monitor_input (!config.get_auto_input());
                                }
                        }
                }
@@ -673,33 +759,63 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
        /* cancel looped playback if transport pos outside of loop range */
        if (play_loop) {
                Location* al = _locations.auto_loop_location();
-               
+
                if (al && (_transport_frame < al->start() || _transport_frame > al->end())) {
                        // 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);
+                                       }
+                               }
+                       }
+                       have_looped = true;
+                       TransportLooped(); // EMIT SIGNAL
+               }
        }
-       
+
        loop_changing = false;
 
        _send_smpte_update = true;
+
+       Located (); /* EMIT SIGNAL */
 }
 
+/** Set the transport speed.
+ * @param speed New speed
+ * @param abort
+ */
 void
-Session::set_transport_speed (float speed, bool abort)
+Session::set_transport_speed (double speed, bool abort)
 {
        if (_transport_speed == speed) {
                return;
        }
 
+       _target_transport_speed = fabs(speed);
+
+       /* 8.0 max speed is somewhat arbitrary but based on guestimates regarding disk i/o capability
+          and user needs. We really need CD-style "skip" playback for ffwd and rewind.
+       */
+
        if (speed > 0) {
-               speed = min (8.0f, speed);
+               speed = min (8.0, speed);
        } else if (speed < 0) {
-               speed = max (-8.0f, speed);
+               speed = max (-8.0, speed);
        }
 
        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();
@@ -707,7 +823,7 @@ Session::set_transport_speed (float speed, bool abort)
                        for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
                                if ((*i)->record_enabled ()) {
                                        //cerr << "switching to input" << __FILE__ << __LINE__ << endl << endl;
-                                       (*i)->monitor_input (true);     
+                                       (*i)->monitor_input (true);
                                }
                        }
                }
@@ -717,9 +833,11 @@ Session::set_transport_speed (float speed, bool abort)
                } else {
                        stop_transport (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;
                }
@@ -729,9 +847,9 @@ Session::set_transport_speed (float speed, bool abort)
                        boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
 
                        for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
-                               if (Config->get_auto_input() && (*i)->record_enabled ()) {
+                               if (config.get_auto_input() && (*i)->record_enabled ()) {
                                        //cerr << "switching from input" << __FILE__ << __LINE__ << endl << endl;
-                                       (*i)->monitor_input (false);    
+                                       (*i)->monitor_input (false);
                                }
                        }
                }
@@ -758,51 +876,53 @@ Session::set_transport_speed (float speed, bool abort)
                        return;
                }
 
-               if (speed > 0.0f && _transport_frame == current_end_frame()) {
+               if (speed > 0.0 && _transport_frame == current_end_frame()) {
                        return;
                }
 
-               if (speed < 0.0f && _transport_frame == 0) {
+               if (speed < 0.0 && _transport_frame == 0) {
                        return;
                }
-               
+
                clear_clicks ();
 
                /* if we are reversing relative to the current speed, or relative to the speed
                   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.0) || (_last_transport_speed * speed < 0.0) || (_last_transport_speed == 0.0f && speed < 0.0f)) {
                        post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse);
                }
-               
+
                _last_transport_speed = _transport_speed;
                _transport_speed = speed;
-               
+
                boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
                for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
                        if ((*i)->realtime_set_speed ((*i)->speed(), true)) {
                                post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed);
                        }
                }
-               
+
                if (post_transport_work & (PostTransportSpeed|PostTransportReverse)) {
                        schedule_butler_transport_work ();
                }
        }
 }
 
+
+/** Stop the transport.  */
 void
 Session::stop_transport (bool abort)
 {
        if (_transport_speed == 0.0f) {
                return;
        }
-       
-       if (actively_recording() && !(transport_sub_state & StopPendingCapture) && 
-           _worst_output_latency > current_block_size) 
+
+       if (actively_recording() && !(transport_sub_state & StopPendingCapture) &&
+           _worst_output_latency > current_block_size)
        {
-               
+
                /* we need to capture the audio that has still not yet been received by the system
                   at the time the stop is requested, so we have to roll past that time.
 
@@ -810,16 +930,16 @@ Session::stop_transport (bool abort)
                   block before the actual end. we'll declick in the subsequent block,
                   and then we'll really be stopped.
                */
-               
-               Event *ev = new Event (Event::StopOnce, Event::Replace, 
+
+               Event *ev = new Event (Event::StopOnce, Event::Replace,
                                       _transport_frame + _worst_output_latency - current_block_size,
                                       0, 0, abort);
-               
+
                merge_event (ev);
                transport_sub_state |= StopPendingCapture;
                pending_abort = abort;
                return;
-       } 
+       }
 
 
        if ((transport_sub_state & PendingDeclickOut) == 0) {
@@ -837,41 +957,34 @@ 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. 
+          already Recording, move it to Disabled.
        */
 
        switch (record_status()) {
        case Enabled:
-               if (!Config->get_punch_in()) {
+               if (!config.get_punch_in()) {
                        enable_record ();
                }
                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;
-       
+       _target_transport_speed = 1.0;
+
        boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
        for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
                (*i)->realtime_set_speed ((*i)->speed(), true);
@@ -903,10 +1016,9 @@ 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 {
                        transport_sub_state = 0;
                }
@@ -948,14 +1060,12 @@ Session::set_slave_source (SlaveSource src)
                return;
        }
 
-//     if (src == JACK && Config->get_jack_time_master()) {
-//             return;
-//     }
-       
-       if (_slave) {
-               delete _slave;
-               _slave = 0;
-       }
+//     if (src == JACK && Config->get_jack_time_master()) {
+//             return;
+//     }
+
+       delete _slave;
+       _slave = 0;
 
        if (_transport_speed < 0.0) {
                reverse = true;
@@ -965,7 +1075,7 @@ Session::set_slave_source (SlaveSource src)
        case None:
                stop_transport ();
                break;
-               
+
        case MTC:
                if (_mtc_port) {
                        try {
@@ -980,17 +1090,32 @@ Session::set_slave_source (SlaveSource src)
                        error << _("No MTC port defined: MTC slaving is impossible.") << endmsg;
                        return;
                }
-               _desired_transport_speed = _transport_speed;
                break;
-               
+
+       case MIDIClock:
+               if (_midi_clock_port) {
+                       try {
+                               _slave = new MIDIClock_Slave (*this, *_midi_clock_port, 24);
+                       }
+
+                       catch (failed_constructor& err) {
+                               return;
+                       }
+
+               } else {
+                       error << _("No MIDI Clock port defined: MIDI Clock slaving is impossible.") << endmsg;
+                       return;
+               }
+               break;
+
        case JACK:
                _slave = new JACK_Slave (_engine.jack());
-               _desired_transport_speed = _transport_speed;
                break;
+
        };
 
        Config->set_slave_source (src);
-       
+
        boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
        for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
                if (!(*i)->hidden()) {
@@ -1021,7 +1146,7 @@ Session::reverse_diskstream_buffers ()
 }
 
 void
-Session::set_diskstream_speed (Diskstream* stream, float speed)
+Session::set_diskstream_speed (Diskstream* stream, double speed)
 {
        if (stream->realtime_set_speed (speed, false)) {
                post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed);
@@ -1068,7 +1193,7 @@ Session::setup_auto_play ()
        /* Called from event-processing context */
 
        Event* ev;
-       
+
        _clear_event_type (Event::RangeStop);
        _clear_event_type (Event::RangeLocate);
 
@@ -1077,57 +1202,65 @@ Session::setup_auto_play ()
        }
 
        list<AudioRange>::size_type sz = current_audio_range.size();
-       
+
        if (sz > 1) {
-               
-               list<AudioRange>::iterator i = current_audio_range.begin(); 
+
+               list<AudioRange>::iterator i = current_audio_range.begin();
                list<AudioRange>::iterator next;
-               
+
                while (i != current_audio_range.end()) {
-                       
+
                        next = i;
                        ++next;
-                       
+
                        /* locating/stopping is subject to delays for declicking.
                         */
-                       
+
                        nframes_t requested_frame = (*i).end;
-                       
+
                        if (requested_frame > current_block_size) {
                                requested_frame -= current_block_size;
                        } else {
                                requested_frame = 0;
                        }
-                       
+
                        if (next == current_audio_range.end()) {
                                ev = new Event (Event::RangeStop, Event::Add, requested_frame, 0, 0.0f);
                        } else {
                                ev = new Event (Event::RangeLocate, Event::Add, requested_frame, (*next).start, 0.0f);
                        }
-                       
+
                        merge_event (ev);
-                       
+
                        i = next;
                }
-               
+
        } else if (sz == 1) {
-               
+
                ev = new Event (Event::RangeStop, Event::Add, current_audio_range.front().end, 0, 0.0f);
                merge_event (ev);
-               
-       } 
+
+       }
 
        /* now start rolling at the right place */
-       
+
        ev = new Event (Event::LocateRoll, Event::Add, Event::Immediate, current_audio_range.front().start, 0.0f, false);
        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)
 {
        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);
 }
@@ -1135,19 +1268,21 @@ 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.
 
-          but first, make sure the butler is out of 
+          but first, make sure the butler is out of
           the picture.
        */
 
        g_atomic_int_set (&butler_should_do_transport_work, 0);
        post_transport_work = PostTransportWork (0);
        stop_butler ();
-       
+
        realtime_stop (false);
-       non_realtime_stop (false);
+       non_realtime_stop (false, 0, ignored);
        transport_sub_state = 0;
 
        TransportStateChange (); /* EMIT SIGNAL */
@@ -1157,16 +1292,16 @@ 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.
                */
 
                engine_halted();
-       } 
+       }
 }
 
 void
@@ -1180,41 +1315,51 @@ 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) {
+
                if (with_stop) {
-                       (*i)->handle_transport_stopped (abort, (post_transport_work & PostTransportLocate), 
+                       (*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 old_latency = (*i)->output()->signal_latency ();
                nframes_t track_latency = (*i)->update_total_latency ();
 
                if (old_latency != track_latency) {
+                       (*i)->input()->update_port_total_latencies ();
+                       (*i)->output()->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);
                }
        }
 
-       for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-               (*i)->set_latency_delay (_worst_track_latency);
+       if (update_jack) {
+               _engine.update_total_latencies ();
        }
 
-       /* tell JACK to play catch up */
+#ifdef DEBUG_LATENCY
+       cerr << "\tworst was " << _worst_track_latency << endl;
+#endif
 
-       if (update_jack) {
-               _engine.update_total_latencies ();
+       for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+               (*i)->set_latency_delay (_worst_track_latency);
        }
 
        set_worst_io_latencies ();
 
        /* reflect any changes in latencies into capture offsets
        */
-       
+
        boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
 
        for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
@@ -1223,7 +1368,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);
+       }
 }