Fix duplicate track with new playlist so only one playlist is created
[ardour.git] / libs / ardour / session.cc
index fb73ecbd3662bc8fbae70f53b40cbfa61e9b90a1..127367483b4a402e2eb31951008fb240a80fae7d 100644 (file)
@@ -37,7 +37,6 @@
 
 #include "pbd/basename.h"
 #include "pbd/convert.h"
-#include "pbd/convert.h"
 #include "pbd/error.h"
 #include "pbd/file_utils.h"
 #include "pbd/md5.h"
 
 #include "LuaBridge/LuaBridge.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 #include <glibmm/checksum.h>
 
@@ -178,6 +177,7 @@ Session::Session (AudioEngine &eng,
        , _record_status (Disabled)
        , _transport_frame (0)
        , _session_range_location (0)
+       , _session_range_end_is_free (true)
        , _slave (0)
        , _silent (false)
        , _transport_speed (0)
@@ -212,6 +212,8 @@ Session::Session (AudioEngine &eng,
        , post_export_position (0)
        , _exporting (false)
        , _export_rolling (false)
+       , _realtime_export (false)
+       , _region_export (false)
        , _export_preroll (0)
        , _export_latency (0)
        , _pre_export_mmc_enabled (false)
@@ -310,7 +312,7 @@ Session::Session (AudioEngine &eng,
        , _step_editors (0)
        , _suspend_timecode_transmission (0)
        ,  _speakers (new Speakers)
-       , ignore_route_processor_changes (false)
+       , _ignore_route_processor_changes (0)
        , midi_clock (0)
        , _scene_changer (0)
        , _midi_ports (0)
@@ -909,6 +911,14 @@ Session::setup_click_state (const XMLNode* node)
        }
 }
 
+void
+Session::get_physical_ports (vector<string>& inputs, vector<string>& outputs, DataType type,
+                             MidiPortFlags include, MidiPortFlags exclude)
+{
+       _engine.get_physical_inputs (type, inputs, include, exclude);
+       _engine.get_physical_outputs (type, outputs, include, exclude);
+}
+
 void
 Session::setup_bundles ()
 {
@@ -927,9 +937,12 @@ Session::setup_bundles ()
 
        vector<string> inputs[DataType::num_types];
        vector<string> outputs[DataType::num_types];
+
        for (uint32_t i = 0; i < DataType::num_types; ++i) {
-               _engine.get_physical_inputs (DataType (DataType::Symbol (i)), inputs[i]);
-               _engine.get_physical_outputs (DataType (DataType::Symbol (i)), outputs[i]);
+               get_physical_ports (inputs[i], outputs[i], DataType (DataType::Symbol (i)),
+                                   MidiPortFlags (0), /* no specific inclusions */
+                                   MidiPortFlags (MidiPortControl|MidiPortVirtual) /* exclude control & virtual ports */
+                       );
        }
 
        /* Create a set of Bundle objects that map
@@ -1013,6 +1026,7 @@ Session::setup_bundles ()
 
        for (uint32_t np = 0; np < inputs[DataType::MIDI].size(); ++np) {
                string n = inputs[DataType::MIDI][np];
+
                std::string pn = _engine.get_pretty_name_by_name (n);
                if (!pn.empty()) {
                        n = pn;
@@ -1115,7 +1129,7 @@ Session::remove_monitor_section ()
 
 
                boost::shared_ptr<RouteList> r = routes.reader ();
-               PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+               ProcessorChangeBlocker  pcb (this, false);
 
                for (RouteList::iterator x = r->begin(); x != r->end(); ++x) {
 
@@ -1275,7 +1289,7 @@ Session::add_monitor_section ()
 
        boost::shared_ptr<RouteList> rls = routes.reader ();
 
-       PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+       ProcessorChangeBlocker  pcb (this, false /* XXX */);
 
        for (RouteList::iterator x = rls->begin(); x != rls->end(); ++x) {
 
@@ -1399,7 +1413,7 @@ Session::reset_monitor_section ()
 
        boost::shared_ptr<RouteList> rls = routes.reader ();
 
-       PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+       ProcessorChangeBlocker pcb (this, false);
 
        for (RouteList::iterator x = rls->begin(); x != rls->end(); ++x) {
 
@@ -2028,59 +2042,55 @@ framepos_t
 Session::audible_frame () const
 {
        framepos_t ret;
-       framepos_t tf;
-       framecnt_t offset;
 
-       offset = worst_playback_latency ();
+       frameoffset_t offset = worst_playback_latency (); // - _engine.samples_since_cycle_start ();
+       offset *= transport_speed ();
 
        if (synced_to_engine()) {
                /* Note: this is basically just sync-to-JACK */
-               tf = _engine.transport_frame();
+               ret = _engine.transport_frame();
        } else {
-               tf = _transport_frame;
+               ret = _transport_frame;
        }
 
-       ret = tf;
-
-       if (!non_realtime_work_pending()) {
-
-               /* MOVING */
+       if (transport_rolling()) {
+               ret -= offset;
 
                /* Check to see if we have passed the first guaranteed
-                  audible frame past our last start position. if not,
-                  return that last start point because in terms
-                  of audible frames, we have not moved yet.
-
-                  `Start position' in this context means the time we last
-                  either started, located, or changed transport direction.
-               */
+                * audible frame past our last start position. if not,
+                * return that last start point because in terms
+                * of audible frames, we have not moved yet.
+                *
+                * `Start position' in this context means the time we last
+                * either started, located, or changed transport direction.
+                */
 
                if (_transport_speed > 0.0f) {
 
                        if (!play_loop || !have_looped) {
-                               if (tf < _last_roll_or_reversal_location + offset) {
+                               if (ret < _last_roll_or_reversal_location) {
                                        return _last_roll_or_reversal_location;
                                }
+                       } else {
+                               // latent loops
+                               Location *location = _locations->auto_loop_location();
+                               frameoffset_t lo = location->start() - ret;
+                               if (lo > 0) {
+                                       ret = location->end () - lo;
+                               }
                        }
 
-
-                       /* forwards */
-                       ret -= offset;
-
                } else if (_transport_speed < 0.0f) {
 
                        /* XXX wot? no backward looping? */
 
-                       if (tf > _last_roll_or_reversal_location - offset) {
+                       if (ret > _last_roll_or_reversal_location) {
                                return _last_roll_or_reversal_location;
-                       } else {
-                               /* backwards */
-                               ret += offset;
                        }
                }
        }
 
-       return ret;
+       return std::max ((framepos_t)0, ret);
 }
 
 void
@@ -2370,8 +2380,11 @@ Session::find_route_name (string const & base, uint32_t& id, string& name, bool
                }
        }
 
-       if (!definitely_add_number && route_by_name (base) == 0) {
-               /* juse use the base */
+       /* if we have "base 1" already, it doesn't make sense to add "base"
+        * if "base 1" has been deleted, adding "base" is no worse than "base 1"
+        */
+       if (!definitely_add_number && route_by_name (base) == 0 && (route_by_name (string_compose("%1 1", base)) == 0)) {
+               /* just use the base */
                name = base;
                return true;
        }
@@ -2520,12 +2533,15 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output,
                if (instrument) {
                        for (RouteList::iterator r = new_routes.begin(); r != new_routes.end(); ++r) {
                                PluginPtr plugin = instrument->load (*this);
+                               if (!plugin) {
+                                       warning << "Failed to add Synth Plugin to newly created track." << endmsg;
+                                       continue;
+                               }
                                if (pset) {
                                        plugin->load_preset (*pset);
                                }
                                boost::shared_ptr<Processor> p (new PluginInsert (*this, plugin));
                                (*r)->add_processor (p, PreFader);
-
                        }
                }
        }
@@ -2608,6 +2624,10 @@ Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name
                if (instrument) {
                        for (RouteList::iterator r = ret.begin(); r != ret.end(); ++r) {
                                PluginPtr plugin = instrument->load (*this);
+                               if (!plugin) {
+                                       warning << "Failed to add Synth Plugin to newly created track." << endmsg;
+                                       continue;
+                               }
                                if (pset) {
                                        plugin->load_preset (*pset);
                                }
@@ -3160,7 +3180,8 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r
 }
 
 RouteList
-Session::new_route_from_template (uint32_t how_many, const std::string& template_path, const std::string& name_base, PlaylistDisposition pd)
+Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t insert_at, const std::string& template_path, const std::string& name_base,
+                                  PlaylistDisposition pd)
 {
        XMLTree tree;
 
@@ -3168,11 +3189,11 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template
                return RouteList();
        }
 
-       return new_route_from_template (how_many, *tree.root(), name_base, pd);
+       return new_route_from_template (how_many, insert_at, *tree.root(), name_base, pd);
 }
 
 RouteList
-Session::new_route_from_template (uint32_t how_many, XMLNode& node, const std::string& name_base, PlaylistDisposition pd)
+Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t insert_at, XMLNode& node, const std::string& name_base, PlaylistDisposition pd)
 {
        RouteList ret;
        uint32_t number = 0;
@@ -3222,10 +3243,10 @@ Session::new_route_from_template (uint32_t how_many, XMLNode& node, const std::s
                        bool rename_playlist;
                        switch (pd) {
                        case NewPlaylist:
+                       case CopyPlaylist:
                                rename_playlist = true;
                                break;
                        default:
-                       case CopyPlaylist:
                        case SharePlaylist:
                                rename_playlist = false;
                        }
@@ -3312,7 +3333,6 @@ Session::new_route_from_template (uint32_t how_many, XMLNode& node, const std::s
                        if ((track = boost::dynamic_pointer_cast<Track> (route))) {
                                switch (pd) {
                                case NewPlaylist:
-                                       track->use_new_playlist ();
                                        break;
                                case CopyPlaylist:
                                        track->use_copy_playlist ();
@@ -3342,9 +3362,9 @@ Session::new_route_from_template (uint32_t how_many, XMLNode& node, const std::s
        if (!ret.empty()) {
                StateProtector sp (this);
                if (Profile->get_trx()) {
-                       add_routes (ret, false, false, false, PresentationInfo::max_order);
+                       add_routes (ret, false, false, false, insert_at);
                } else {
-                       add_routes (ret, true, true, false, PresentationInfo::max_order);
+                       add_routes (ret, true, true, false, insert_at);
                }
                IO::enable_connecting ();
        }
@@ -3440,6 +3460,7 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool
                        if (mt) {
                                mt->StepEditStatusChange.connect_same_thread (*this, boost::bind (&Session::step_edit_status_change, this, _1));
                                mt->output()->changed.connect_same_thread (*this, boost::bind (&Session::midi_output_change_handler, this, _1, _2, boost::weak_ptr<Route>(mt)));
+                               mt->presentation_info().PropertyChanged.connect_same_thread (*this, boost::bind (&Session::midi_track_presentation_info_changed, this, _1, boost::weak_ptr<MidiTrack>(mt)));
                        }
                }
 
@@ -3453,7 +3474,7 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool
 
                                if (order == PresentationInfo::max_order) {
                                        /* just add to the end */
-                                       r->set_presentation_order_explicit (n_routes + added);
+                                       r->set_presentation_order (n_routes + added, false);
                                        DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to NR %1 + %2 = %3\n", n_routes, added, n_routes + added));
                                } else {
                                        r->set_presentation_order (order + added);
@@ -3640,7 +3661,7 @@ Session::remove_routes (boost::shared_ptr<RouteList> routes_to_remove)
                        /* if the monitoring section had a pointer to this route, remove it */
                        if (_monitor_out && !(*iter)->is_master() && !(*iter)->is_monitor()) {
                                Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
-                               PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+                               ProcessorChangeBlocker pcb (this, false);
                                (*iter)->remove_aux_or_listen (_monitor_out);
                        }
 
@@ -3686,6 +3707,7 @@ Session::remove_routes (boost::shared_ptr<RouteList> routes_to_remove)
         */
 
        for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) {
+               cerr << "Drop references to " << (*iter)->name() << endl;
                (*iter)->drop_references ();
        }
 
@@ -3767,8 +3789,6 @@ Session::route_listen_changed (Controllable::GroupControlDisposition group_overr
 
                _listen_cnt--;
        }
-
-       update_route_solo_state ();
 }
 
 void
@@ -3802,7 +3822,7 @@ Session::route_solo_isolated_changed (boost::weak_ptr<Route> wpr)
 void
 Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlDisposition group_override,  boost::weak_ptr<Route> wpr)
 {
-       DEBUG_TRACE (DEBUG::Solo, string_compose ("route solo change, self = %1\n", self_solo_changed));
+       DEBUG_TRACE (DEBUG::Solo, string_compose ("route solo change, self = %1, update\n", self_solo_changed));
 
        boost::shared_ptr<Route> route (wpr.lock());
 
@@ -3818,21 +3838,13 @@ Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlD
        DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: self %2 masters %3 transition %4\n", route->name(), route->self_soloed(), route->solo_control()->get_masters_value(), route->solo_control()->transitioned_into_solo()));
 
        if (route->solo_control()->transitioned_into_solo() == 0) {
-               /* route solo changed by upstream/downstream; not interesting
+               /* route solo changed by upstream/downstream or clear all solo state; not interesting
                   to Session.
                */
                DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 not self-soloed nor soloed by master (%2), ignoring\n", route->name(), route->solo_control()->get_masters_value()));
                return;
        }
 
-       if (route->solo_control()->transitioned_into_solo() == 0) {
-               /* reason for being soloed changed (e.g. master went away, we
-                * took over the master state), but actual status did
-                * not. nothing to do.
-                */
-               DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: solo change was change in reason, not status\n", route->name()));
-       }
-
        boost::shared_ptr<RouteList> r = routes.reader ();
        int32_t delta = route->solo_control()->transitioned_into_solo ();
 
@@ -3855,6 +3867,8 @@ Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlD
        RouteGroup* rg = route->route_group ();
        const bool group_already_accounted_for = (group_override == Controllable::ForGroup);
 
+       DEBUG_TRACE (DEBUG::Solo, string_compose ("propagate to session, group accounted for ? %1\n", group_already_accounted_for));
+
        if (delta == 1 && Config->get_exclusive_solo()) {
 
                /* new solo: disable all other solos, but not the group if its solo-enabled */
@@ -3901,6 +3915,8 @@ Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlD
 
                if ((*i)->solo_isolate_control()->solo_isolated() || !(*i)->can_solo()) {
                        /* route does not get solo propagated to it */
+                       DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 excluded from solo because iso = %2 can_solo = %3\n", (*i)->name(), (*i)->solo_isolate_control()->solo_isolated(),
+                                                                 (*i)->can_solo()));
                        continue;
                }
 
@@ -3966,8 +3982,6 @@ Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlD
 
        DEBUG_TRACE (DEBUG::Solo, "propagation complete\n");
 
-       update_route_solo_state (r);
-
        /* now notify that the mute state of the routes not involved in the signal
           pathway of the just-solo-changed route may have altered.
        */
@@ -3975,11 +3989,10 @@ Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlD
        for (RouteList::iterator i = uninvolved.begin(); i != uninvolved.end(); ++i) {
                DEBUG_TRACE (DEBUG::Solo, string_compose ("mute change for %1, which neither feeds or is fed by %2\n", (*i)->name(), route->name()));
                (*i)->act_on_mute ();
-               (*i)->mute_control()->Changed (false, Controllable::NoGroup);
+               /* Session will emit SoloChanged() after all solo changes are
+                * complete, which should be used by UIs to update mute status
+                */
        }
-
-       SoloChanged (); /* EMIT SIGNAL */
-       set_dirty();
 }
 
 void
@@ -3999,13 +4012,13 @@ Session::update_route_solo_state (boost::shared_ptr<RouteList> r)
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                if ((*i)->can_solo()) {
                        if (Config->get_solo_control_is_listen_control()) {
-                               if ((*i)->self_soloed() || (*i)->solo_control()->get_masters_value()) {
+                               if ((*i)->solo_control()->soloed_by_self_or_masters()) {
                                        listeners++;
                                        something_listening = true;
                                }
                        } else {
                                (*i)->set_listen (false);
-                               if ((*i)->can_solo() && ((*i)->self_soloed() || (*i)->solo_control()->get_masters_value())) {
+                               if ((*i)->can_solo() && (*i)->solo_control()->soloed_by_self_or_masters()) {
                                        something_soloed = true;
                                }
                        }
@@ -4035,6 +4048,10 @@ Session::update_route_solo_state (boost::shared_ptr<RouteList> r)
 
        DEBUG_TRACE (DEBUG::Solo, string_compose ("solo state updated by session, soloed? %1 listeners %2 isolated %3\n",
                                                  something_soloed, listeners, isolated));
+
+
+       SoloChanged (); /* EMIT SIGNAL */
+       set_dirty();
 }
 
 void
@@ -4244,6 +4261,24 @@ Session::get_remote_nth_stripable (PresentationInfo::order_t n, PresentationInfo
        sl.sort (Stripable::PresentationOrderSorter());
 
        for (StripableList::const_iterator s = sl.begin(); s != sl.end(); ++s) {
+
+               if ((*s)->presentation_info().hidden()) {
+                       /* if the caller didn't explicitly ask for hidden
+                          stripables, ignore hidden ones. This matches
+                          the semantics of the pre-PresentationOrder
+                          "get by RID" logic of Ardour 4.x and earlier.
+
+                          XXX at some point we should likely reverse
+                          the logic of the flags, because asking for "the
+                          hidden stripables" is not going to be common,
+                          whereas asking for visible ones is normal.
+                       */
+
+                       if (! (flags & PresentationInfo::Hidden)) {
+                               continue;
+                       }
+               }
+
                if ((*s)->presentation_info().flag_match (flags)) {
                        if (match_cnt++ == n) {
                                return *s;
@@ -4389,12 +4424,18 @@ Session::maybe_update_session_range (framepos_t a, framepos_t b)
                        _session_range_location->set_start (a);
                }
 
-               if (b > _session_range_location->end()) {
+               if (_session_range_end_is_free && (b > _session_range_location->end())) {
                        _session_range_location->set_end (b);
                }
        }
 }
 
+void
+Session::set_end_is_free (bool yn)
+{
+       _session_range_end_is_free = yn;
+}
+
 void
 Session::playlist_ranges_moved (list<Evoral::RangeMove<framepos_t> > const & ranges)
 {
@@ -6174,8 +6215,8 @@ Session::update_route_record_state ()
 void
 Session::listen_position_changed ()
 {
+       ProcessorChangeBlocker pcb (this);
        boost::shared_ptr<RouteList> r = routes.reader ();
-
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                (*i)->listen_position_changed ();
        }
@@ -6185,14 +6226,18 @@ void
 Session::solo_control_mode_changed ()
 {
        if (soloing() || listening()) {
-               /* We can't use ::clear_all_solo_state() here because during
-                  session loading at program startup, that will queue a call
-                  to rt_clear_all_solo_state() that will not execute until
-                  AFTER solo states have been established (thus throwing away
-                  the session's saved solo state). So just explicitly turn
-                  them all off.
-               */
-               set_controls (route_list_to_control_list (get_routes(), &Stripable::solo_control), 0.0, Controllable::NoGroup);
+               if (loading()) {
+                       /* We can't use ::clear_all_solo_state() here because during
+                          session loading at program startup, that will queue a call
+                          to rt_clear_all_solo_state() that will not execute until
+                          AFTER solo states have been established (thus throwing away
+                          the session's saved solo state). So just explicitly turn
+                          them all off.
+                       */
+                       set_controls (route_list_to_control_list (get_routes(), &Stripable::solo_control), 0.0, Controllable::NoGroup);
+               } else {
+                       clear_all_solo_state (get_routes());
+               }
        }
 }
 
@@ -6216,6 +6261,10 @@ Session::route_removed_from_route_group (RouteGroup* rg, boost::weak_ptr<Route>
 {
        update_route_record_state ();
        RouteRemovedFromRouteGroup (rg, r); /* EMIT SIGNAL */
+
+       if (!rg->has_control_master () && !rg->has_subgroup () && rg->empty()) {
+               remove_route_group (*rg);
+       }
 }
 
 boost::shared_ptr<RouteList>
@@ -6270,12 +6319,12 @@ Session::goto_end ()
 }
 
 void
-Session::goto_start ()
+Session::goto_start (bool and_roll)
 {
        if (_session_range_location) {
-               request_locate (_session_range_location->start(), false);
+               request_locate (_session_range_location->start(), and_roll);
        } else {
-               request_locate (0, false);
+               request_locate (0, and_roll);
        }
 }
 
@@ -6341,6 +6390,7 @@ Session::start_time_changed (framepos_t old)
        if (l && l->start() == old) {
                l->set_start (s->start(), true);
        }
+       set_dirty ();
 }
 
 void
@@ -6360,6 +6410,7 @@ Session::end_time_changed (framepos_t old)
        if (l && l->end() == old) {
                l->set_end (s->end(), true);
        }
+       set_dirty ();
 }
 
 std::vector<std::string>
@@ -6835,6 +6886,12 @@ Session::auto_connect_route (boost::shared_ptr<Route> route, bool connect_inputs
                                input_start, output_start,
                                input_offset, output_offset));
 
+       auto_connect_thread_wakeup ();
+}
+
+void
+Session::auto_connect_thread_wakeup ()
+{
        if (pthread_mutex_trylock (&_auto_connect_mutex) == 0) {
                pthread_cond_signal (&_auto_connect_cond);
                pthread_mutex_unlock (&_auto_connect_mutex);
@@ -6845,10 +6902,7 @@ void
 Session::queue_latency_recompute ()
 {
        g_atomic_int_inc (&_latency_recompute_pending);
-       if (pthread_mutex_trylock (&_auto_connect_mutex) == 0) {
-               pthread_cond_signal (&_auto_connect_cond);
-               pthread_mutex_unlock (&_auto_connect_mutex);
-       }
+       auto_connect_thread_wakeup ();
 }
 
 void
@@ -6887,8 +6941,12 @@ Session::auto_connect (const AutoConnectRequest& ar)
                vector<string> physinputs;
                vector<string> physoutputs;
 
-               _engine.get_physical_outputs (*t, physoutputs);
-               _engine.get_physical_inputs (*t, physinputs);
+
+               /* for connecting track inputs we only want MIDI ports marked
+                * for "music".
+                */
+
+               get_physical_ports (physinputs, physoutputs, *t, MidiPortMusic);
 
                if (!physinputs.empty() && ar.connect_inputs) {
                        uint32_t nphysical_in = physinputs.size();
@@ -6971,10 +7029,7 @@ Session::auto_connect_thread_terminate ()
                }
        }
 
-       if (pthread_mutex_lock (&_auto_connect_mutex) == 0) {
-               pthread_cond_signal (&_auto_connect_cond);
-               pthread_mutex_unlock (&_auto_connect_mutex);
-       }
+       auto_connect_thread_wakeup ();
 
        void *status;
        pthread_join (_auto_connect_thread, &status);
@@ -7030,7 +7085,20 @@ Session::auto_connect_thread_run ()
                        }
                }
 
+               AudioEngine::instance()->clear_pending_port_deletions ();
+
                pthread_cond_wait (&_auto_connect_cond, &_auto_connect_mutex);
        }
        pthread_mutex_unlock (&_auto_connect_mutex);
 }
+
+void
+Session::cancel_all_solo ()
+{
+       StripableList sl;
+
+       get_stripables (sl);
+
+       set_controls (stripable_list_to_control_list (sl, &Stripable::solo_control), 0.0, Controllable::NoGroup);
+       clear_all_solo_state (routes.reader());
+}