make the session do the right thing when clearing solo state during non-loading condi...
[ardour.git] / libs / ardour / session.cc
index 444989789866504a85257be2c0b044fc9ef84cc0..f9a616936367e83f00976d22f9162c11dc48d517 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"
@@ -214,6 +213,7 @@ Session::Session (AudioEngine &eng,
        , _exporting (false)
        , _export_rolling (false)
        , _realtime_export (false)
+       , _region_export (false)
        , _export_preroll (0)
        , _export_latency (0)
        , _pre_export_mmc_enabled (false)
@@ -911,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 ()
 {
@@ -929,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
@@ -1015,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;
@@ -2030,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
@@ -2372,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;
        }
@@ -2522,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);
-
                        }
                }
        }
@@ -2610,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);
                                }
@@ -3162,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;
 
@@ -3170,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;
@@ -3344,9 +3363,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 ();
        }
@@ -3442,6 +3461,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)));
                        }
                }
 
@@ -3688,6 +3708,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 ();
        }
 
@@ -3769,8 +3790,6 @@ Session::route_listen_changed (Controllable::GroupControlDisposition group_overr
 
                _listen_cnt--;
        }
-
-       update_route_solo_state ();
 }
 
 void
@@ -3804,7 +3823,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());
 
@@ -3820,21 +3839,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 ();
 
@@ -3857,6 +3868,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 */
@@ -3903,6 +3916,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;
                }
 
@@ -3968,8 +3983,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.
        */
@@ -3977,11 +3990,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
@@ -4001,13 +4013,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;
                                }
                        }
@@ -4037,6 +4049,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
@@ -4246,6 +4262,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;
@@ -6182,25 +6216,32 @@ Session::update_route_record_state ()
 void
 Session::listen_position_changed ()
 {
-       boost::shared_ptr<RouteList> r = routes.reader ();
-
-       for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-               (*i)->listen_position_changed ();
+       {
+               boost::shared_ptr<RouteList> r = routes.reader ();
+               PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+               for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+                       (*i)->listen_position_changed ();
+               }
        }
+       route_processors_changed (RouteProcessorChange ());
 }
 
 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());
+               }
        }
 }
 
@@ -6224,6 +6265,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>
@@ -6278,12 +6323,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);
        }
 }
 
@@ -6349,6 +6394,7 @@ Session::start_time_changed (framepos_t old)
        if (l && l->start() == old) {
                l->set_start (s->start(), true);
        }
+       set_dirty ();
 }
 
 void
@@ -6368,6 +6414,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>
@@ -6843,6 +6890,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);
@@ -6853,10 +6906,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
@@ -6895,8 +6945,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();
@@ -6979,10 +7033,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);
@@ -7038,6 +7089,8 @@ 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);