Only show user-presets in favorite sidebar
[ardour.git] / libs / ardour / session_transport.cc
index 95a6683a05ef29d18771e55af95c2105dab622c0..49354dad60e873b68470ce66b76bb430cbda9149 100644 (file)
 #include "ardour/debug.h"
 #include "ardour/disk_reader.h"
 #include "ardour/location.h"
+#include "ardour/playlist.h"
 #include "ardour/profile.h"
 #include "ardour/scene_changer.h"
 #include "ardour/session.h"
-#include "ardour/slave.h"
+#include "ardour/transport_master.h"
+#include "ardour/transport_master_manager.h"
 #include "ardour/tempo.h"
 #include "ardour/operations.h"
 #include "ardour/vca.h"
@@ -77,33 +79,39 @@ Session::add_post_transport_work (PostTransportWork ptw)
        error << "Could not set post transport work! Crazy thread madness, call the programmers" << endmsg;
 }
 
-void
-Session::request_sync_source (Slave* new_slave)
+bool
+Session::should_ignore_transport_request (TransportRequestSource src, TransportRequestType type) const
 {
-       SessionEvent* ev = new SessionEvent (SessionEvent::SetSyncSource, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0);
-       bool seamless;
-
-       seamless = Config->get_seamless_loop ();
-
-       if (dynamic_cast<Engine_Slave*>(new_slave)) {
-               /* JACK cannot support seamless looping at present */
-               Config->set_seamless_loop (false);
-       } else {
-               /* reset to whatever the value was before we last switched slaves */
-               Config->set_seamless_loop (_was_seamless);
+       if (config.get_external_sync()) {
+               if (TransportMasterManager::instance().current()->allow_request (src, type)) {
+                       return false;
+               } else {
+                       return true;
+               }
        }
+       return false;
+}
 
-       /* save value of seamless from before the switch */
-       _was_seamless = seamless;
+bool
+Session::synced_to_engine() const {
+       return config.get_external_sync() && TransportMasterManager::instance().current()->type() == Engine;
+}
 
-       ev->slave = new_slave;
-       DEBUG_TRACE (DEBUG::Slave, "sent request for new slave\n");
+void
+Session::request_sync_source (boost::shared_ptr<TransportMaster> tm)
+{
+       SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportMaster, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0);
+       ev->transport_master = tm;
+       DEBUG_TRACE (DEBUG::Slave, "sent request for new transport master\n");
        queue_event (ev);
 }
 
 void
-Session::request_transport_speed (double speed, bool as_default)
+Session::request_transport_speed (double speed, bool as_default, TransportRequestSource origin)
 {
+       if (should_ignore_transport_request (origin, TR_Speed)) {
+               return;
+       }
        SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, speed);
        ev->third_yes_or_no = as_default; // as_default
        DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport speed = %1 as default = %2\n", speed, as_default));
@@ -115,8 +123,12 @@ Session::request_transport_speed (double speed, bool as_default)
  *  be used by callers who are varying transport speed but don't ever want to stop it.
  */
 void
-Session::request_transport_speed_nonzero (double speed, bool as_default)
+Session::request_transport_speed_nonzero (double speed, bool as_default, TransportRequestSource origin)
 {
+       if (should_ignore_transport_request (origin, TransportRequestType (TR_Speed|TR_Start))) {
+               return;
+       }
+
        if (speed == 0) {
                speed = DBL_EPSILON;
        }
@@ -125,16 +137,24 @@ Session::request_transport_speed_nonzero (double speed, bool as_default)
 }
 
 void
-Session::request_stop (bool abort, bool clear_state)
+Session::request_stop (bool abort, bool clear_state, TransportRequestSource origin)
 {
+       if (should_ignore_transport_request (origin, TR_Stop)) {
+               return;
+       }
+
        SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, audible_sample(), 0.0, abort, clear_state);
        DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport stop, audible %3 transport %4 abort = %1, clear state = %2\n", abort, clear_state, audible_sample(), _transport_sample));
        queue_event (ev);
 }
 
 void
-Session::request_locate (samplepos_t target_sample, bool with_roll)
+Session::request_locate (samplepos_t target_sample, bool with_roll, TransportRequestSource origin)
 {
+       if (should_ignore_transport_request (origin, TR_Locate)) {
+               return;
+       }
+
        SessionEvent *ev = new SessionEvent (with_roll ? SessionEvent::LocateRoll : SessionEvent::Locate, SessionEvent::Add, SessionEvent::Immediate, target_sample, 0, false);
        DEBUG_TRACE (DEBUG::Transport, string_compose ("Request locate to %1\n", target_sample));
        queue_event (ev);
@@ -148,49 +168,18 @@ Session::force_locate (samplepos_t target_sample, bool with_roll)
        queue_event (ev);
 }
 
-void
-Session::unset_preroll_record_punch ()
-{
-       if (_preroll_record_punch_pos >= 0) {
-               remove_event (_preroll_record_punch_pos, SessionEvent::RecordStart);
-       }
-       _preroll_record_punch_pos = -1;
-}
-
 void
 Session::unset_preroll_record_trim ()
 {
        _preroll_record_trim_len = 0;
 }
 
-void
-Session::request_preroll_record_punch (samplepos_t rec_in, samplecnt_t preroll)
-{
-       if (actively_recording ()) {
-               return;
-       }
-       unset_preroll_record_punch ();
-       unset_preroll_record_trim ();
-       samplepos_t start = std::max ((samplepos_t)0, rec_in - preroll);
-
-       _preroll_record_punch_pos = rec_in;
-       if (_preroll_record_punch_pos >= 0) {
-               replace_event (SessionEvent::RecordStart, _preroll_record_punch_pos);
-               config.set_punch_in (false);
-               config.set_punch_out (false);
-       }
-       maybe_enable_record ();
-       request_locate (start, true);
-       set_requested_return_sample (rec_in);
-}
-
 void
 Session::request_preroll_record_trim (samplepos_t rec_in, samplecnt_t preroll)
 {
        if (actively_recording ()) {
                return;
        }
-       unset_preroll_record_punch ();
        unset_preroll_record_trim ();
 
        config.set_punch_in (false);
@@ -220,7 +209,7 @@ Session::request_count_in_record ()
 void
 Session::request_play_loop (bool yn, bool change_transport_roll)
 {
-       if (_slave && yn) {
+       if (transport_master_is_external() && yn) {
                // don't attempt to loop when not using Internal Transport
                // see also gtk2_ardour/ardour_ui_options.cc parameter_changed()
                return;
@@ -298,6 +287,58 @@ Session::request_cancel_play_range ()
 }
 
 
+bool
+Session::solo_selection_active ()
+{
+       if ( _soloSelection.empty() ) {
+               return false;
+       }
+       return true;
+}
+
+void
+Session::solo_selection ( StripableList &list, bool new_state  )
+{
+       boost::shared_ptr<ControlList> solo_list (new ControlList);
+       boost::shared_ptr<ControlList> unsolo_list (new ControlList);
+
+       if (new_state)
+               _soloSelection = list;
+       else
+               _soloSelection.clear();
+
+       boost::shared_ptr<RouteList> rl = get_routes();
+
+       for (ARDOUR::RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+
+               if ( !(*i)->is_track() ) {
+                       continue;
+               }
+
+               boost::shared_ptr<Stripable> s (*i);
+
+               bool found = (std::find(list.begin(), list.end(), s) != list.end());
+               if ( new_state && found ) {
+
+                       solo_list->push_back (s->solo_control());
+
+                       //must invalidate playlists on selected tracks, so only selected regions get heard
+                       boost::shared_ptr<Track> track = boost::dynamic_pointer_cast<Track> (*i);
+                       if (track) {
+                               boost::shared_ptr<Playlist> playlist = track->playlist();
+                               if (playlist) {
+                                       playlist->ContentsChanged();
+                               }
+                       }
+               } else {
+                       unsolo_list->push_back (s->solo_control());
+               }
+       }
+
+       set_controls (solo_list, 1.0, Controllable::NoGroup);
+       set_controls (unsolo_list, 0.0, Controllable::NoGroup);
+}
+
 void
 Session::realtime_stop (bool abort, bool clear_state)
 {
@@ -343,6 +384,11 @@ Session::realtime_stop (bool abort, bool clear_state)
        _clear_event_type (SessionEvent::RangeStop);
        _clear_event_type (SessionEvent::RangeLocate);
 
+       //clear our solo-selection, if there is one
+       if ( solo_selection_active() ) {
+               solo_selection ( _soloSelection, false );
+       }
+
        /* if we're going to clear loop state, then force disabling record BUT only if we're not doing latched rec-enable */
        disable_record (true, (!Config->get_latched_record_enable() && clear_state));
 
@@ -354,6 +400,7 @@ Session::realtime_stop (bool abort, bool clear_state)
 
        _transport_speed = 0;
        _target_transport_speed = 0;
+       _engine_speed = 1.0;
 
        g_atomic_int_set (&_playback_load, 100);
        g_atomic_int_set (&_capture_load, 100);
@@ -445,6 +492,9 @@ Session::butler_transport_work ()
        }
 
        if (ptw & PostTransportAdjustCaptureBuffering) {
+               /* need to prevent concurrency with ARDOUR::DiskWriter::run(),
+                * DiskWriter::adjust_buffering() re-allocates the ringbuffer */
+               Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
                for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                        boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
                        if (tr) {
@@ -459,10 +509,6 @@ Session::butler_transport_work ()
                }
        }
 
-       if (ptw & PostTransportSpeed) {
-               non_realtime_set_speed ();
-       }
-
        if (ptw & PostTransportReverse) {
 
                clear_clicks();
@@ -518,18 +564,6 @@ Session::butler_transport_work ()
        DEBUG_TRACE (DEBUG::Transport, string_compose (X_("Butler transport work all done after %1 usecs @ %2 trw = %3\n"), g_get_monotonic_time() - before, _transport_sample, _butler->transport_work_requested()));
 }
 
-void
-Session::non_realtime_set_speed ()
-{
-       boost::shared_ptr<RouteList> rl = routes.reader();
-       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-               if (tr) {
-                       tr->non_realtime_speed_change ();
-               }
-       }
-}
-
 void
 Session::non_realtime_overwrite (int on_entry, bool& finished)
 {
@@ -780,12 +814,6 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
 
        boost::shared_ptr<RouteList> r = routes.reader ();
 
-       for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-               if (!(*i)->is_auditioner()) {
-                       (*i)->set_pending_declick (0);
-               }
-       }
-
        if (did_record) {
                commit_reversible_command ();
                /* increase take name */
@@ -799,11 +827,11 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
                PostTransportWork ptw = post_transport_work ();
 
                for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-                       (*i)->non_realtime_transport_stop (_transport_sample, !(ptw & PostTransportLocate) || pending_locate_flush);
+                       (*i)->non_realtime_transport_stop (_transport_sample, !(ptw & PostTransportLocate));
                }
                VCAList v = _vca_manager->vcas ();
                for (VCAList::const_iterator i = v.begin(); i != v.end(); ++i) {
-                       (*i)->non_realtime_transport_stop (_transport_sample, !(ptw & PostTransportLocate) || pending_locate_flush);
+                       (*i)->non_realtime_transport_stop (_transport_sample, !(ptw & PostTransportLocate));
                }
 
                update_latency_compensation ();
@@ -816,10 +844,6 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
            (_requested_return_sample >= 0) ||
            synced_to_engine()) {
 
-               if (pending_locate_flush) {
-                       flush_all_inserts ();
-               }
-
                // rg: what is the logic behind this case?
                // _requested_return_sample should be ignored when synced_to_engine/slaved.
                // currently worked around in MTC_Slave by forcing _requested_return_sample to -1
@@ -904,11 +928,11 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
           because there will be no process callbacks to deliver stuff from
        */
 
-       if (_engine.connected() && !_engine.freewheeling()) {
+       if (_engine.running() && !_engine.freewheeling()) {
                // need to queue this in the next RT cycle
                _send_timecode_update = true;
 
-               if (!dynamic_cast<MTC_Slave*>(_slave)) {
+               if (transport_master()->type() == MTC) {
                        send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop));
 
                        /* This (::non_realtime_stop()) gets called by main
@@ -929,7 +953,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
                 *
                 * save state only if there's no slave or if it's not yet locked.
                 */
-               if (!_slave || !_slave->locked()) {
+               if (!transport_master_is_external() || !transport_master()->locked()) {
                        DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: requests save\n"));
                        SaveSessionRequested (_current_snapshot_name);
                        saved = true;
@@ -960,44 +984,9 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
 
        /* and start it up again if relevant */
 
-       if ((ptw & PostTransportLocate) && !config.get_external_sync() && pending_locate_roll) {
+       if ((ptw & PostTransportLocate) && !config.get_external_sync()) {
                request_transport_speed (1.0);
        }
-
-       /* Even if we didn't do a pending locate roll this time, we don't want it hanging
-          around for next time.
-       */
-       pending_locate_roll = false;
-}
-
-void
-Session::check_declick_out ()
-{
-       bool locate_required = transport_sub_state & PendingLocate;
-
-       /* 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 maybe doing something else. this is where we do that "something else".
-
-          note: called from the audio thread.
-       */
-
-       if (transport_sub_state & PendingDeclickOut) {
-
-               if (locate_required) {
-                       start_locate (pending_locate_sample, pending_locate_roll, pending_locate_flush);
-                       transport_sub_state &= ~(PendingDeclickOut|PendingLocate);
-               } else {
-                       if (!(transport_sub_state & StopPendingCapture)) {
-                               stop_transport (pending_abort);
-                               transport_sub_state &= ~(PendingDeclickOut|PendingLocate);
-                       }
-               }
-
-       } else if (transport_sub_state & PendingLoopDeclickOut) {
-               /* Nothing else to do here; we've declicked, and the loop event will be along shortly */
-               transport_sub_state &= ~PendingLoopDeclickOut;
-       }
 }
 
 void
@@ -1006,7 +995,6 @@ Session::unset_play_loop ()
        if (play_loop) {
                play_loop = false;
                clear_events (SessionEvent::AutoLoop);
-               clear_events (SessionEvent::AutoLoopDeclick);
                set_track_loop (false);
 
 
@@ -1015,6 +1003,7 @@ Session::unset_play_loop ()
                        add_post_transport_work (PostTransportLocate);
                        _butler->schedule_transport_work ();
                }
+               TransportStateChange (); /* EMIT SIGNAL */
        }
 }
 
@@ -1030,9 +1019,8 @@ Session::set_track_loop (bool yn)
        boost::shared_ptr<RouteList> rl = routes.reader ();
 
        for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-               if (tr && !tr->is_private_route()) {
-                       tr->set_loop (yn ? loc : 0);
+               if (*i && !(*i)->is_private_route()) {
+                       (*i)->set_loop (yn ? loc : 0);
                }
        }
 }
@@ -1088,7 +1076,6 @@ Session::set_play_loop (bool yn, double speed)
                        samplepos_t dcp;
                        samplecnt_t dcl;
                        auto_loop_declick_range (loc, dcp, dcl);
-                       merge_event (new SessionEvent (SessionEvent::AutoLoopDeclick, SessionEvent::Replace, dcp, dcl, 0.0f));
                        merge_event (new SessionEvent (SessionEvent::AutoLoop, SessionEvent::Replace, loc->end(), loc->start(), 0.0f));
 
                        /* if requested to roll, locate to start of loop and
@@ -1141,8 +1128,9 @@ Session::start_locate (samplepos_t target_sample, bool with_roll, bool with_flus
 
                double sp;
                samplepos_t pos;
+               samplepos_t ignore1, ignore2;
 
-               _slave->speed_and_position (sp, pos);
+               transport_master()->speed_and_position (sp, pos, ignore1, ignore2, 0);
 
                if (target_sample != pos) {
 
@@ -1171,6 +1159,12 @@ Session::start_locate (samplepos_t target_sample, bool with_roll, bool with_flus
        }
 }
 
+samplecnt_t
+Session::worst_latency_preroll () const
+{
+       return _worst_output_latency + _worst_input_latency;
+}
+
 int
 Session::micro_locate (samplecnt_t distance)
 {
@@ -1182,6 +1176,8 @@ Session::micro_locate (samplecnt_t distance)
                }
        }
 
+       DEBUG_TRACE (DEBUG::Transport, string_compose ("micro-locate by %1\n", distance));
+
        for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
                boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
                if (tr) {
@@ -1226,22 +1222,6 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, boo
                return;
        }
 
-       if (_transport_speed && !(for_loop_enabled && Config->get_seamless_loop())) {
-               /* Schedule a declick.  We'll be called again when its done.
-                  We only do it this way for ordinary locates, not those
-                  due to **seamless** loops.
-               */
-
-               if (!(transport_sub_state & PendingDeclickOut)) {
-                       transport_sub_state |= (PendingDeclickOut|PendingLocate);
-                       pending_locate_sample = target_sample;
-                       pending_locate_roll = with_roll;
-                       pending_locate_flush = with_flush;
-                       cerr << "Declick scheduled ... back soon\n";
-                       return;
-               }
-       }
-
        cerr << "... now doing the actual locate\n";
 
        // Update Timecode time
@@ -1250,15 +1230,16 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, boo
        // thread(s?) can restart.
        g_atomic_int_inc (&_seek_counter);
        _last_roll_or_reversal_location = target_sample;
-       timecode_time(_transport_sample, transmitting_timecode_time);
+       _remaining_latency_preroll = worst_latency_preroll ();
+       timecode_time(_transport_sample, transmitting_timecode_time); // XXX here?
 
        /* do "stopped" stuff if:
         *
         * we are rolling AND
-        *    no autoplay in effect AND
-         *       we're not going to keep rolling after the locate AND
-         *           !(playing a loop with JACK sync)
-         *
+        * no autoplay in effect AND
+        * we're not going to keep rolling after the locate AND
+        * !(playing a loop with JACK sync)
+        *
         */
 
        bool transport_was_stopped = !transport_rolling();
@@ -1397,31 +1378,41 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
        DEBUG_TRACE (DEBUG::Transport, string_compose ("@ %5 Set transport speed to %1, abort = %2 clear_state = %3, current = %4 as_default %6\n",
                                                       speed, abort, clear_state, _transport_speed, _transport_sample, as_default));
 
-       if (_transport_speed == speed) {
+       /* 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.0, speed);
+       } else if (speed < 0) {
+               speed = max (-8.0, speed);
+       }
+
+       double new_engine_speed = 1.0;
+       if (speed != 0) {
+               new_engine_speed = fabs (speed);
+               if (speed < 0) speed = -1;
+               if (speed > 0) speed = 1;
+       }
+
+       if (_transport_speed == speed && new_engine_speed == _engine_speed) {
                if (as_default && speed == 0.0) { // => reset default transport speed. hacky or what?
                        _default_transport_speed = 1.0;
                }
                return;
        }
 
+#if 0 // TODO pref: allow vari-speed recording
        if (actively_recording() && speed != 1.0 && speed != 0.0) {
                /* no varispeed during recording */
                DEBUG_TRACE (DEBUG::Transport, string_compose ("No varispeed during recording cur_speed %1, sample %2\n",
                                                       _transport_speed, _transport_sample));
                return;
        }
+#endif
 
        _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.0, speed);
-       } else if (speed < 0) {
-               speed = max (-8.0, speed);
-       }
+       _engine_speed = new_engine_speed;
 
        if (transport_rolling() && speed == 0.0) {
 
@@ -1492,6 +1483,9 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
 
                /* not zero, not 1.0 ... varispeed */
 
+               // TODO handled transport start..  _remaining_latency_preroll
+               // and reversal of playback direction.
+
                if ((synced_to_engine()) && speed != 0.0 && speed != 1.0) {
                        warning << string_compose (
                                _("Global varispeed cannot be supported while %1 is connected to JACK transport control"),
@@ -1500,9 +1494,11 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
                        return;
                }
 
+#if 0
                if (actively_recording()) {
                        return;
                }
+#endif
 
                if (speed > 0.0 && _transport_sample == current_end_sample()) {
                        return;
@@ -1532,14 +1528,6 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
                        _default_transport_speed = speed;
                }
 
-               boost::shared_ptr<RouteList> rl = routes.reader();
-               for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-                       boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-                       if (tr && tr->realtime_speed_change()) {
-                               todo = PostTransportWork (todo | PostTransportSpeed);
-                       }
-               }
-
                if (todo) {
                        add_post_transport_work (todo);
                        _butler->schedule_transport_work ();
@@ -1559,14 +1547,14 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
                 * The 0.2% dead-zone is somewhat arbitrary. Main use-case
                 * for TransportStateChange() here is the ShuttleControl display.
                 */
-               if (fabs (_signalled_varispeed - speed) > .002
+               if (fabs (_signalled_varispeed - actual_speed ()) > .002
                    // still, signal hard changes to 1.0 and 0.0:
-                   || ( speed == 1.0 && _signalled_varispeed != 1.0)
-                   || ( speed == 0.0 && _signalled_varispeed != 0.0)
+                   || (actual_speed () == 1.0 && _signalled_varispeed != 1.0)
+                   || (actual_speed () == 0.0 && _signalled_varispeed != 0.0)
                   )
                {
                        TransportStateChange (); /* EMIT SIGNAL */
-                       _signalled_varispeed = speed;
+                       _signalled_varispeed = actual_speed ();
                }
        }
 }
@@ -1581,81 +1569,10 @@ Session::stop_transport (bool abort, bool clear_state)
                return;
        }
 
-       DEBUG_TRACE (DEBUG::Transport, string_compose ("stop_transport, declick required? %1\n", get_transport_declick_required()));
-
-       if (!get_transport_declick_required()) {
-
-               /* stop has not yet been scheduled */
-
-               boost::shared_ptr<RouteList> rl = routes.reader();
-               samplepos_t stop_target = audible_sample();
-
-               for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-                       boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-                       if (tr) {
-                               tr->prepare_to_stop (_transport_sample, stop_target);
-                       }
-               }
-
-               SubState new_bits;
-
-               if (actively_recording() &&                           /* we are recording */
-                   worst_input_latency() > current_block_size) {     /* input latency exceeds block size, so simple 1 cycle delay before stop is not enough */
-
-                       /* we need to capture the audio that is still somewhere in the pipeline between
-                          wherever it was generated and the process callback. This means that even though
-                          the user (or something else)  has asked us to stop, we have to roll
-                          past this point and then reset the playhead/transport location to
-                          the position at which the stop was requested.
-
-                          we still need playback to "stop" now, however, which is why we schedule
-                          a declick below.
-                       */
-
-                       DEBUG_TRACE (DEBUG::Transport, string_compose ("stop transport requested @ %1, scheduled for + %2 = %3, abort = %4\n",
-                                                                      _transport_sample, _worst_input_latency,
-                                                                      _transport_sample + _worst_input_latency,
-                                                                      abort));
-
-                       SessionEvent *ev = new SessionEvent (SessionEvent::StopOnce, SessionEvent::Replace,
-                                                            _transport_sample + _worst_input_latency,
-                                                            0, 0, abort);
-
-                       merge_event (ev);
-
-                       /* request a declick at the start of the next process cycle() so that playback ceases.
-                          It will remain silent until we actually stop (at the StopOnce event somewhere in
-                          the future). The extra flag (StopPendingCapture) is set to ensure that check_declick_out()
-                          does not stop the transport too early.
-                        */
-                       new_bits = SubState (PendingDeclickOut|StopPendingCapture);
-
-               } else {
-
-                       /* Not recording, schedule a declick in the next process() cycle and then stop at its end */
-
-                       new_bits = PendingDeclickOut;
-                       DEBUG_TRACE (DEBUG::Transport, string_compose ("stop scheduled for next process cycle @ %1\n", _transport_sample));
-               }
-
-               /* we'll be called again after the declick */
-               transport_sub_state = SubState (transport_sub_state|new_bits);
-               pending_abort = abort;
-
-               return;
-
-       } else {
-
-               DEBUG_TRACE (DEBUG::Transport, "time to actually stop\n");
-
-               /* declick was scheduled, but we've been called again, which means it is really time to stop
-
-                  XXX: we should probably split this off into its own method and call it explicitly.
-               */
+       DEBUG_TRACE (DEBUG::Transport, "time to actually stop\n");
 
-               realtime_stop (abort, clear_state);
-               _butler->schedule_transport_work ();
-       }
+       realtime_stop (abort, clear_state);
+       _butler->schedule_transport_work ();
 }
 
 /** Called from the process thread */
@@ -1666,6 +1583,7 @@ Session::start_transport ()
 
        _last_roll_location = _transport_sample;
        _last_roll_or_reversal_location = _transport_sample;
+       _remaining_latency_preroll = worst_latency_preroll ();
 
        have_looped = false;
 
@@ -1675,7 +1593,14 @@ Session::start_transport ()
 
        switch (record_status()) {
        case Enabled:
-               if (!config.get_punch_in() && !preroll_record_punch_enabled()) {
+               if (!config.get_punch_in()) {
+                       /* This is only for UIs (keep blinking rec-en before
+                        * punch-in, don't show rec-region etc). The UI still
+                        * depends on SessionEvent::PunchIn and ensuing signals.
+                        *
+                        * The disk-writers handle punch in/out internally
+                        * in their local delay-compensated timeframe.
+                        */
                        enable_record ();
                }
                break;
@@ -1690,23 +1615,13 @@ Session::start_transport ()
                break;
        }
 
-       transport_sub_state |= PendingDeclickIn;
-
        _transport_speed = _default_transport_speed;
        _target_transport_speed = _transport_speed;
 
-       boost::shared_ptr<RouteList> rl = routes.reader();
-       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-               if (tr) {
-                       tr->realtime_speed_change ();
-               }
-       }
-
        if (!_engine.freewheeling()) {
                Timecode::Time time;
                timecode_time_subframes (_transport_sample, time);
-               if (!dynamic_cast<MTC_Slave*>(_slave)) {
+               if (transport_master()->type() == MTC) {
                        send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay));
                }
 
@@ -1735,13 +1650,22 @@ Session::start_transport ()
                                _count_in_samples *= 1. + bar_fract;
                        }
 
+                       if (_count_in_samples > _remaining_latency_preroll) {
+                               _remaining_latency_preroll = _count_in_samples;
+                       }
+
                        int clickbeat = 0;
                        samplepos_t cf = _transport_sample - _count_in_samples;
-                       while (cf < _transport_sample) {
-                               add_click (cf - _worst_track_latency, clickbeat == 0);
+                       samplecnt_t offset = _click_io->connected_latency (true);
+                       while (cf < _transport_sample + offset) {
+                               add_click (cf, clickbeat == 0);
                                cf += dt;
                                clickbeat = fmod (clickbeat + 1, num);
                        }
+
+                       if (_count_in_samples < _remaining_latency_preroll) {
+                               _count_in_samples = _remaining_latency_preroll;
+                       }
                }
        }
 
@@ -1807,163 +1731,6 @@ Session::reset_rf_scale (samplecnt_t motion)
        }
 }
 
-void
-Session::mtc_status_changed (bool yn)
-{
-       g_atomic_int_set (&_mtc_active, yn);
-       MTCSyncStateChanged( yn );
-}
-
-void
-Session::ltc_status_changed (bool yn)
-{
-       g_atomic_int_set (&_ltc_active, yn);
-       LTCSyncStateChanged( yn );
-}
-
-void
-Session::use_sync_source (Slave* new_slave)
-{
-       /* Runs in process() context */
-
-       bool non_rt_required = false;
-
-       /* XXX this deletion is problematic because we're in RT context */
-
-       delete _slave;
-       _slave = new_slave;
-
-
-       /* slave change, reset any DiskIO block on disk output because it is no
-          longer valid with a new slave.
-       */
-       DiskReader::set_no_disk_output (false);
-
-       MTC_Slave* mtc_slave = dynamic_cast<MTC_Slave*>(_slave);
-       if (mtc_slave) {
-               mtc_slave->ActiveChanged.connect_same_thread (mtc_status_connection, boost::bind (&Session::mtc_status_changed, this, _1));
-               MTCSyncStateChanged(mtc_slave->locked() );
-       } else {
-               if (g_atomic_int_get (&_mtc_active) ){
-                       g_atomic_int_set (&_mtc_active, 0);
-                       MTCSyncStateChanged( false );
-               }
-               mtc_status_connection.disconnect ();
-       }
-
-       LTC_Slave* ltc_slave = dynamic_cast<LTC_Slave*> (_slave);
-       if (ltc_slave) {
-               ltc_slave->ActiveChanged.connect_same_thread (ltc_status_connection, boost::bind (&Session::ltc_status_changed, this, _1));
-               LTCSyncStateChanged (ltc_slave->locked() );
-       } else {
-               if (g_atomic_int_get (&_ltc_active) ){
-                       g_atomic_int_set (&_ltc_active, 0);
-                       LTCSyncStateChanged( false );
-               }
-               ltc_status_connection.disconnect ();
-       }
-
-       DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", _slave));
-
-       // need to queue this for next process() cycle
-       _send_timecode_update = true;
-
-       boost::shared_ptr<RouteList> rl = routes.reader();
-       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-               if (tr && !tr->is_private_route()) {
-                       if (tr->realtime_speed_change()) {
-                               non_rt_required = true;
-                       }
-                       tr->set_slaved (_slave != 0);
-               }
-       }
-
-       if (non_rt_required) {
-               add_post_transport_work (PostTransportSpeed);
-               _butler->schedule_transport_work ();
-       }
-
-       set_dirty();
-}
-
-void
-Session::drop_sync_source ()
-{
-       request_sync_source (0);
-}
-
-void
-Session::switch_to_sync_source (SyncSource src)
-{
-       Slave* new_slave;
-
-       DEBUG_TRACE (DEBUG::Slave, string_compose ("Setting up sync source %1\n", enum_2_string (src)));
-
-       switch (src) {
-       case MTC:
-               if (_slave && dynamic_cast<MTC_Slave*>(_slave)) {
-                       return;
-               }
-
-               try {
-                       new_slave = new MTC_Slave (*this, *_midi_ports->mtc_input_port());
-               }
-
-               catch (failed_constructor& err) {
-                       return;
-               }
-               break;
-
-       case LTC:
-               if (_slave && dynamic_cast<LTC_Slave*>(_slave)) {
-                       return;
-               }
-
-               try {
-                       new_slave = new LTC_Slave (*this);
-               }
-
-               catch (failed_constructor& err) {
-                       return;
-               }
-
-               break;
-
-       case MIDIClock:
-               if (_slave && dynamic_cast<MIDIClock_Slave*>(_slave)) {
-                       return;
-               }
-
-               try {
-                       new_slave = new MIDIClock_Slave (*this, *_midi_ports->midi_clock_input_port(), 24);
-               }
-
-               catch (failed_constructor& err) {
-                       return;
-               }
-               break;
-
-       case Engine:
-               if (_slave && dynamic_cast<Engine_Slave*>(_slave)) {
-                       return;
-               }
-
-               if (config.get_video_pullup() != 0.0f) {
-                       return;
-               }
-
-               new_slave = new Engine_Slave (*AudioEngine::instance());
-               break;
-
-       default:
-               new_slave = 0;
-               break;
-       };
-
-       request_sync_source (new_slave);
-}
-
 void
 Session::unset_play_range ()
 {
@@ -2197,3 +1964,91 @@ Session::timecode_transmission_suspended () const
 {
        return g_atomic_int_get (&_suspend_timecode_transmission) == 1;
 }
+
+boost::shared_ptr<TransportMaster>
+Session::transport_master() const
+{
+       return TransportMasterManager::instance().current();
+}
+
+bool
+Session::transport_master_is_external () const
+{
+       return config.get_external_sync();
+}
+
+void
+Session::sync_source_changed (SyncSource type, samplepos_t pos, pframes_t cycle_nframes)
+{
+       /* Runs in process() context */
+
+       boost::shared_ptr<TransportMaster> master = TransportMasterManager::instance().current();
+
+       /* save value of seamless from before the switch */
+       _was_seamless = Config->get_seamless_loop ();
+
+       if (type == Engine) {
+               /* JACK cannot support seamless looping at present */
+               Config->set_seamless_loop (false);
+       } else {
+               /* reset to whatever the value was before we last switched slaves */
+               Config->set_seamless_loop (_was_seamless);
+       }
+
+       if (master->can_loop()) {
+               request_play_loop (false);
+       } else if (master->has_loop()) {
+               request_play_loop (true);
+       }
+
+       /* slave change, reset any DiskIO block on disk output because it is no
+          longer valid with a new slave.
+       */
+
+       DiskReader::set_no_disk_output (false);
+
+#if 0
+       we should not be treating specific transport masters as special cases because there maybe > 1 of a particular type
+
+       boost::shared_ptr<MTC_TransportMaster> mtc_master = boost::dynamic_pointer_cast<MTC_TransportMaster> (master);
+
+       if (mtc_master) {
+               mtc_master->ActiveChanged.connect_same_thread (mtc_status_connection, boost::bind (&Session::mtc_status_changed, this, _1));
+               MTCSyncStateChanged(mtc_master->locked() );
+       } else {
+               if (g_atomic_int_compare_and_exchange (&_mtc_active, 1, 0)) {
+                       MTCSyncStateChanged( false );
+               }
+               mtc_status_connection.disconnect ();
+       }
+
+       boost::shared_ptr<LTC_TransportMaster> ltc_master = boost::dynamic_pointer_cast<LTC_TransportMaster> (master);
+
+       if (ltc_master) {
+               ltc_master->ActiveChanged.connect_same_thread (ltc_status_connection, boost::bind (&Session::ltc_status_changed, this, _1));
+               LTCSyncStateChanged (ltc_master->locked() );
+       } else {
+               if (g_atomic_int_compare_and_exchange (&_ltc_active, 1, 0)) {
+                       LTCSyncStateChanged( false );
+               }
+               ltc_status_connection.disconnect ();
+       }
+#endif
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", master));
+
+       // need to queue this for next process() cycle
+       _send_timecode_update = true;
+
+       boost::shared_ptr<RouteList> rl = routes.reader();
+       const bool externally_slaved = transport_master_is_external();
+
+       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+               if (tr && !tr->is_private_route()) {
+                       tr->set_slaved (externally_slaved);
+               }
+       }
+
+       set_dirty();
+}