add left/right side trim cursors and use them for region trimming, as appropriate
[ardour.git] / libs / ardour / session.cc
index 9cf48b7154d83aba9e99c4b257aa1043e2f8f079..48d2f6982c4c9ab6d84d48a1e6a1f43d0a25c384 100644 (file)
@@ -17,6 +17,9 @@
 
 */
 
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+
 #include <algorithm>
 #include <string>
 #include <vector>
@@ -93,6 +96,7 @@
 #include "ardour/tape_file_matcher.h"
 #include "ardour/tempo.h"
 #include "ardour/utils.h"
+#include "ardour/graph.h"
 
 #include "midi++/jack.h"
 
@@ -141,6 +145,7 @@ Session::Session (AudioEngine &eng,
          _butler (new Butler (*this)),
          _post_transport_work (0),
          _send_timecode_update (false),
+         route_graph (new Graph(*this)),
          routes (new RouteList),
          _total_free_4k_blocks (0),
          _bundles (new BundleList),
@@ -169,7 +174,7 @@ Session::Session (AudioEngine &eng,
         _is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
 
        if (_is_new) {
-               if (create (mix_template, compute_initial_length(), bus_profile)) {
+               if (create (mix_template, bus_profile)) {
                        destroy ();
                        throw failed_constructor ();
                }
@@ -697,13 +702,7 @@ Session::hookup_io ()
 void
 Session::playlist_length_changed ()
 {
-       /* we can't just increase session_range_location->end() if pl->get_maximum_extent()
-          if larger. if the playlist used to be the longest playlist,
-          and its now shorter, we have to decrease session_range_location->end(). hence,
-          we have to iterate over all diskstreams and check the
-          playlists currently in use.
-       */
-       find_current_end ();
+       update_session_range_location_marker ();
 }
 
 void
@@ -720,8 +719,7 @@ Session::track_playlist_changed (boost::weak_ptr<Track> wp)
                playlist->LengthChanged.connect_same_thread (*this, boost::bind (&Session::playlist_length_changed, this));
        }
 
-       /* see comment in playlist_length_changed () */
-       find_current_end ();
+       update_session_range_location_marker ();
 }
 
 bool
@@ -1104,17 +1102,20 @@ Session::audible_frame () const
 
                /* MOVING */
 
-               /* check to see if we have passed the first guaranteed
+               /* 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 or changed transport direction.
                */
 
                if (_transport_speed > 0.0f) {
 
                        if (!play_loop || !have_looped) {
-                               if (tf < _last_roll_location + offset) {
-                                       return _last_roll_location;
+                               if (tf < _last_roll_or_reversal_location + offset) {
+                                       return _last_roll_or_reversal_location;
                                }
                        }
 
@@ -1126,8 +1127,8 @@ Session::audible_frame () const
 
                        /* XXX wot? no backward looping? */
 
-                       if (tf > _last_roll_location - offset) {
-                               return _last_roll_location;
+                       if (tf > _last_roll_or_reversal_location - offset) {
+                               return _last_roll_or_reversal_location;
                        } else {
                                /* backwards */
                                ret += offset;
@@ -1235,13 +1236,13 @@ Session::set_default_fade (float /*steepness*/, float /*fade_msecs*/)
 
 struct RouteSorter {
     bool operator() (boost::shared_ptr<Route> r1, boost::shared_ptr<Route> r2) {
-           if (r1->fed_by.find (r2) != r1->fed_by.end()) {
+           if (r2->feeds (r1)) {
                    return false;
-           } else if (r2->fed_by.find (r1) != r2->fed_by.end()) {
+           } else if (r1->feeds (r2)) {
                    return true;
            } else {
-                   if (r1->fed_by.empty()) {
-                           if (r2->fed_by.empty()) {
+                   if (r1->not_fed ()) {
+                           if (r2->not_fed ()) {
                                    /* no ardour-based connections inbound to either route. just use signal order */
                                    return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
                            } else {
@@ -1260,21 +1261,21 @@ trace_terminal (shared_ptr<Route> r1, shared_ptr<Route> rbase)
 {
        shared_ptr<Route> r2;
 
-       if ((r1->fed_by.find (rbase) != r1->fed_by.end()) && (rbase->fed_by.find (r1) != rbase->fed_by.end())) {
+       if (r1->feeds (rbase) && rbase->feeds (r1)) {
                info << string_compose(_("feedback loop setup between %1 and %2"), r1->name(), rbase->name()) << endmsg;
                return;
        }
 
        /* make a copy of the existing list of routes that feed r1 */
 
-       set<weak_ptr<Route> > existing = r1->fed_by;
-
+        Route::FedBy existing (r1->fed_by());
+                        
        /* for each route that feeds r1, recurse, marking it as feeding
           rbase as well.
        */
 
-       for (set<weak_ptr<Route> >::iterator i = existing.begin(); i != existing.end(); ++i) {
-               if (!(r2 = (*i).lock ())) {
+       for (Route::FedBy::iterator i = existing.begin(); i != existing.end(); ++i) {
+               if (!(r2 = i->r.lock ())) {
                        /* (*i) went away, ignore it */
                        continue;
                }
@@ -1283,7 +1284,7 @@ trace_terminal (shared_ptr<Route> r1, shared_ptr<Route> rbase)
                   base as being fed by r2
                */
 
-               rbase->fed_by.insert (r2);
+               rbase->add_fed_by (r2, i->sends_only);
 
                if (r2 != rbase) {
 
@@ -1291,7 +1292,7 @@ trace_terminal (shared_ptr<Route> r1, shared_ptr<Route> rbase)
                           stop here.
                        */
 
-                       if ((r1->fed_by.find (r2) != r1->fed_by.end()) && (r2->fed_by.find (r1) != r2->fed_by.end())) {
+                       if (r1->feeds (r2) && r2->feeds (r1)) {
                                continue;
                        }
 
@@ -1325,6 +1326,24 @@ Session::resort_routes ()
                /* writer goes out of scope and forces update */
        }
 
+       //route_graph->dump(1);
+
+#ifndef NDEBUG
+        boost::shared_ptr<RouteList> rl = routes.reader ();
+        for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+                DEBUG_TRACE (DEBUG::Graph, string_compose ("%1 fed by ...\n", (*i)->name()));
+                
+                const Route::FedBy& fb ((*i)->fed_by());
+
+                for (Route::FedBy::const_iterator f = fb.begin(); f != fb.end(); ++f) {
+                        boost::shared_ptr<Route> sf = f->r.lock();
+                        if (sf) {
+                                DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 (sends only ? %2)\n", sf->name(), f->sends_only));
+                        }
+                }
+        }
+#endif
+
 }
 void
 Session::resort_routes_using (shared_ptr<RouteList> r)
@@ -1333,7 +1352,7 @@ Session::resort_routes_using (shared_ptr<RouteList> r)
 
        for (i = r->begin(); i != r->end(); ++i) {
 
-               (*i)->fed_by.clear ();
+               (*i)->clear_fed_by ();
 
                for (j = r->begin(); j != r->end(); ++j) {
 
@@ -1347,8 +1366,10 @@ Session::resort_routes_using (shared_ptr<RouteList> r)
                                continue;
                        }
 
-                       if ((*j)->feeds (*i)) {
-                               (*i)->fed_by.insert (*j);
+                        bool via_sends_only;
+
+                       if ((*j)->direct_feeds (*i, &via_sends_only)) {
+                               (*i)->add_fed_by (*j, via_sends_only);
                        }
                }
        }
@@ -1360,13 +1381,14 @@ Session::resort_routes_using (shared_ptr<RouteList> r)
        RouteSorter cmp;
        r->sort (cmp);
 
-#if 0
-       cerr << "finished route resort\n";
+       route_graph->rechain( r );
 
+#ifndef NDEBUG
+        DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n");
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-               cerr << " " << (*i)->name() << " signal order = " << (*i)->order_key ("signal") << endl;
+               DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 signal order %2\n", 
+                                                           (*i)->name(), (*i)->order_key ("signal")));
        }
-       cerr << endl;
 #endif
 
 }
@@ -1805,13 +1827,19 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template
                        /*NOTREACHED*/
                }
 
-               IO::set_name_in_state (*node_copy.children().front(), name);
+               /* set IO children to use the new name */
+               XMLNodeList const & children = node_copy.children ();
+               for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
+                       if ((*i)->name() == IO::state_node_name) {
+                               IO::set_name_in_state (**i, name);
+                       }
+               }
 
                Track::zero_diskstream_id_in_xml (node_copy);
 
                try {
                        shared_ptr<Route> route (XMLRouteFactory (node_copy, 3000));
-           
+
                        if (route == 0) {
                                error << _("Session: cannot create track/bus from template description") << endmsg;
                                goto out;
@@ -1879,7 +1907,8 @@ Session::add_routes (RouteList& new_routes, bool save)
                boost::shared_ptr<Route> r (*x);
 
                r->listen_changed.connect_same_thread (*this, boost::bind (&Session::route_listen_changed, this, _1, wpr));
-               r->solo_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, wpr));
+               r->solo_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, _2, wpr));
+               r->solo_isolated_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_isolated_changed, this, _1, wpr));
                r->mute_changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this, _1));
                r->output()->changed.connect_same_thread (*this, boost::bind (&Session::set_worst_io_latencies_x, this, _1, _2));
                r->processors_changed.connect_same_thread (*this, boost::bind (&Session::route_processors_changed, this, _1));
@@ -2022,6 +2051,10 @@ Session::add_internal_sends (boost::shared_ptr<Route> dest, Placement p, boost::
 void
 Session::remove_route (shared_ptr<Route> route)
 {
+        if (((route == _master_out) || (route == _monitor_out)) && !Config->get_allow_special_bus_removal()) {
+                return;
+        }
+
        {
                RCUWriter<RouteList> writer (routes);
                shared_ptr<RouteList> rs = writer.get_copy ();
@@ -2048,14 +2081,13 @@ Session::remove_route (shared_ptr<Route> route)
                        _monitor_out.reset ();
                }
 
-               update_route_solo_state ();
-
                /* writer goes out of scope, forces route list update */
        }
+        
+        update_route_solo_state ();
+       update_session_range_location_marker ();
 
-       find_current_end ();
-
-       // We need to disconnect the routes inputs and outputs
+       // We need to disconnect the route's inputs and outputs
 
        route->input()->disconnect (0);
        route->output()->disconnect (0);
@@ -2112,15 +2144,63 @@ Session::route_listen_changed (void* /*src*/, boost::weak_ptr<Route> wpr)
        }
 
        if (route->listening()) {
+
+                if (Config->get_exclusive_solo()) {
+                        /* new listen: disable all other listen */
+                        shared_ptr<RouteList> r = routes.reader ();
+                        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+                                if ((*i) == route || (*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_hidden()) {
+                                        continue;
+                                } 
+                                (*i)->set_listen (false, this);
+                        }
+                }
+
                _listen_cnt++;
+
        } else if (_listen_cnt > 0) {
+
                _listen_cnt--;
        }
 }
+void
+Session::route_solo_isolated_changed (void* /*src*/, boost::weak_ptr<Route> wpr)
+{
+       boost::shared_ptr<Route> route = wpr.lock ();
+
+       if (!route) {
+               /* should not happen */
+               error << string_compose (_("programming error: %1"), X_("invalid route weak ptr passed to route_solo_changed")) << endmsg;
+               return;
+       }
+        
+        bool send_changed = false;
+
+        if (route->solo_isolated()) {
+                if (_solo_isolated_cnt == 0) {
+                        send_changed = true;
+                }
+                _solo_isolated_cnt++;
+        } else if (_solo_isolated_cnt > 0) {
+                _solo_isolated_cnt--;
+                if (_solo_isolated_cnt == 0) {
+                        send_changed = true;
+                }
+        }
 
+        if (send_changed) {
+                IsolatedChanged (); /* EMIT SIGNAL */
+        }
+}
+            
 void
-Session::route_solo_changed (void* /*src*/, boost::weak_ptr<Route> wpr)
+Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_ptr<Route> wpr)
 {
+        if (!self_solo_change) {
+                // session doesn't care about changes to soloed-by-others
+                return;
+        }
+
        if (solo_update_disabled) {
                // We know already
                return;
@@ -2133,7 +2213,7 @@ Session::route_solo_changed (void* /*src*/, boost::weak_ptr<Route> wpr)
                error << string_compose (_("programming error: %1"), X_("invalid route weak ptr passed to route_solo_changed")) << endmsg;
                return;
        }
-
+        
        shared_ptr<RouteList> r = routes.reader ();
        int32_t delta;
 
@@ -2142,27 +2222,61 @@ Session::route_solo_changed (void* /*src*/, boost::weak_ptr<Route> wpr)
        } else {
                delta = -1;
        }
-
-       /* now mod the solo level of all other routes except master/control outs/auditioner
-          so that they will be silent if appropriate.
-       */
+        if (delta == 1 && Config->get_exclusive_solo()) {
+                /* new solo: disable all other solos */
+                for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+                        if ((*i) == route || (*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_hidden()) {
+                                continue;
+                        } 
+                        (*i)->set_solo (false, this);
+                }
+        }
 
        solo_update_disabled = true;
-
+        
+        RouteList uninvolved;
+        
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                bool via_sends_only;
+                bool in_signal_flow;
 
                if ((*i) == route || (*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_hidden()) {
                        continue;
-               } else if ((*i)->feeds (route, &via_sends_only)) {
+               } 
+
+                in_signal_flow = false;
+
+                if ((*i)->feeds (route, &via_sends_only)) {
                        if (!via_sends_only) {
-                               (*i)->mod_solo_by_others (delta);
+                                if (!route->soloed_by_others_upstream()) {
+                                        (*i)->mod_solo_by_others_downstream (delta);
+                                }
+                                in_signal_flow = true;
                        }
                } 
+                
+                if (route->feeds (*i, &via_sends_only)) {
+                        (*i)->mod_solo_by_others_upstream (delta);
+                        in_signal_flow = true;
+                }
+
+                if (!in_signal_flow) {
+                        uninvolved.push_back (*i);
+                }
        }
 
        solo_update_disabled = false;
        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.
+        */
+
+        for (RouteList::iterator i = uninvolved.begin(); i != uninvolved.end(); ++i) {
+                (*i)->mute_changed (this);
+        }
+
        SoloChanged (); /* EMIT SIGNAL */
        set_dirty();
 }
@@ -2174,6 +2288,7 @@ Session::update_route_solo_state (boost::shared_ptr<RouteList> r)
 
        bool something_soloed = false;
         uint32_t listeners = 0;
+        uint32_t isolated = 0;
 
        if (!r) {
                r = routes.reader();
@@ -2182,7 +2297,6 @@ Session::update_route_solo_state (boost::shared_ptr<RouteList> r)
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                if (!(*i)->is_master() && !(*i)->is_monitor() && !(*i)->is_hidden() && (*i)->self_soloed()) {
                        something_soloed = true;
-                       break;
                }
 
                 if (!(*i)->is_hidden() && (*i)->listening()) {
@@ -2192,6 +2306,10 @@ Session::update_route_solo_state (boost::shared_ptr<RouteList> r)
                                 (*i)->set_listen (false, this);
                         }
                 }
+
+                if ((*i)->solo_isolated()) {
+                        isolated++;
+                }
        }
 
         if (something_soloed != _non_soloed_outs_muted) {
@@ -2199,8 +2317,11 @@ Session::update_route_solo_state (boost::shared_ptr<RouteList> r)
                 SoloActive (_non_soloed_outs_muted); /* EMIT SIGNAL */
         }
 
-        if (listeners) {
-                 _listen_cnt = listeners;
+        _listen_cnt = listeners;
+
+        if (isolated != _solo_isolated_cnt) {
+                _solo_isolated_cnt = isolated;
+                IsolatedChanged (); /* EMIT SIGNAL */
         }
 }
 
@@ -2278,43 +2399,64 @@ Session::route_by_remote_id (uint32_t id)
        return shared_ptr<Route> ((Route*) 0);
 }
 
+/** If either end of the session range location marker lies inside the current
+ *  session extent, move it to the corresponding session extent.
+ */
 void
-Session::find_current_end ()
+Session::update_session_range_location_marker ()
 {
        if (_state_of_the_state & Loading) {
                return;
        }
 
-       nframes_t max = get_maximum_extent ();
+       pair<nframes_t, nframes_t> const ext = get_extent ();
 
-       if (max > _session_range_location->end()) {
-               _session_range_location->set_end (max);
-               set_dirty();
-               DurationChanged(); /* EMIT SIGNAL */
+       if (_session_range_location == 0) {
+               /* we don't have a session range yet; use this one (provided it is valid) */
+               if (ext.first != max_frames) {
+                       add_session_range_location (ext.first, ext.second);
+               }
+       } else {
+               /* update the existing session range */
+               if (ext.first < _session_range_location->start()) {
+                       _session_range_location->set_start (ext.first);
+                       set_dirty ();
+               }
+               
+               if (ext.second > _session_range_location->end()) {
+                       _session_range_location->set_end (ext.second);
+                       set_dirty ();
+               }
+               
        }
 }
 
-nframes_t
-Session::get_maximum_extent () const
+/** @return Extent of the session's contents; if the session is empty, the first value of
+ *  the pair will equal max_frames.
+ */
+pair<nframes_t, nframes_t>
+Session::get_extent () const
 {
-       nframes_t max = 0;
-       nframes_t me;
+       pair<nframes_t, nframes_t> ext (max_frames, 0);
        
        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->destructive()) {
-                       //ignore tape tracks when getting max extents
+                       // ignore tape tracks when getting extents
                        continue;
                }
-               
-               boost::shared_ptr<Playlist> pl = tr->playlist();
-               if ((me = pl->get_maximum_extent()) > max) {
-                       max = me;
+
+               pair<nframes_t, nframes_t> e = tr->playlist()->get_extent ();
+               if (e.first < ext.first) {
+                       ext.first = e.first;
+               }
+               if (e.second > ext.second) {
+                       ext.second = e.second;
                }
        }
 
-       return max;
+       return ext;
 }
 
 /* Region management */
@@ -3575,12 +3717,6 @@ Session::add_automation_list(AutomationList *al)
        automation_lists[al->id()] = al;
 }
 
-nframes_t
-Session::compute_initial_length ()
-{
-       return _engine.frame_rate() * 60 * 5;
-}
-
 void
 Session::sync_order_keys (std::string const & base)
 {
@@ -3720,3 +3856,62 @@ Session::get_routes_with_regions_at (nframes64_t const p) const
 
        return rl;
 }
+
+void
+Session::goto_end ()
+{
+       if (_session_range_location) {
+               request_locate (_session_range_location->end(), false);
+       } else {
+               request_locate (0, false);
+       }
+}
+
+void
+Session::goto_start ()
+{
+       if (_session_range_location) {
+               request_locate (_session_range_location->start(), false);
+       } else {
+               request_locate (0, false);
+       }
+}
+
+void
+Session::set_session_start (nframes_t start)
+{
+       if (_session_range_location) {
+               _session_range_location->set_start (start);
+       } else {
+               add_session_range_location (start, start);
+       }
+}
+             
+void
+Session::set_session_end (nframes_t end)
+{
+       if (_session_range_location) {
+               _session_range_location->set_end (end);
+       } else {
+               add_session_range_location (end, end);
+       }
+}
+
+nframes_t
+Session::current_start_frame () const
+{
+       return _session_range_location ? _session_range_location->start() : 0;
+}
+
+nframes_t
+Session::current_end_frame () const
+{
+       return _session_range_location ? _session_range_location->end() : 0;
+}
+
+void
+Session::add_session_range_location (nframes_t start, nframes_t end)
+{
+       _session_range_location = new Location (start, end, _("session"), Location::IsSessionRange);
+       _locations.add (_session_range_location);
+}