OSC: Add /group/list so surface can get a list of groups
[ardour.git] / libs / ardour / session_transport.cc
index 95a6683a05ef29d18771e55af95c2105dab622c0..59c4e22f162f4f6b7e5f0f771b94b7faf0aad162 100644 (file)
@@ -43,6 +43,7 @@
 #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"
@@ -148,49 +149,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);
@@ -298,6 +268,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 +365,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 +381,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);
@@ -1030,9 +1058,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);
                }
        }
 }
@@ -1171,6 +1198,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)
 {
@@ -1250,15 +1283,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 +1431,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 +1536,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 +1547,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 +1581,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 +1600,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 ();
                }
        }
 }
@@ -1590,13 +1631,6 @@ Session::stop_transport (bool abort, bool clear_state)
                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 */
@@ -1666,6 +1700,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 +1710,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;
@@ -1695,14 +1737,6 @@ Session::start_transport ()
        _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);
@@ -1735,13 +1769,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;
+                       }
                }
        }
 
@@ -1872,9 +1915,6 @@ Session::use_sync_source (Slave* new_slave)
        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);
                }
        }