diff options
-rw-r--r-- | gtk2_ardour/ardour_ui.cc | 16 | ||||
-rw-r--r-- | gtk2_ardour/ardour_ui.h | 2 | ||||
-rw-r--r-- | libs/ardour/ardour/graph.h | 3 | ||||
-rw-r--r-- | libs/ardour/ardour/route.h | 13 | ||||
-rw-r--r-- | libs/ardour/ardour/route_dag.h | 58 | ||||
-rw-r--r-- | libs/ardour/ardour/route_graph.h | 78 | ||||
-rw-r--r-- | libs/ardour/ardour/session.h | 14 | ||||
-rw-r--r-- | libs/ardour/graph.cc | 86 | ||||
-rw-r--r-- | libs/ardour/route.cc | 20 | ||||
-rw-r--r-- | libs/ardour/route_graph.cc (renamed from libs/ardour/route_dag.cc) | 96 | ||||
-rw-r--r-- | libs/ardour/session.cc | 101 | ||||
-rw-r--r-- | libs/ardour/session_process.cc | 12 | ||||
-rw-r--r-- | libs/ardour/wscript | 2 |
13 files changed, 305 insertions, 196 deletions
diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index 4fa21188b6..156125e01c 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -269,6 +269,10 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[]) ARDOUR::Session::Quit.connect (forever_connections, MISSING_INVALIDATOR, ui_bind (&ARDOUR_UI::finish, this), gui_context ()); + /* tell the user about feedback */ + + ARDOUR::Session::FeedbackDetected.connect (forever_connections, MISSING_INVALIDATOR, ui_bind (&ARDOUR_UI::feedback_detected, this), gui_context ()); + /* handle requests to deal with missing files */ ARDOUR::Session::MissingFile.connect_same_thread (forever_connections, boost::bind (&ARDOUR_UI::missing_file, this, _1, _2, _3)); @@ -3877,3 +3881,15 @@ ARDOUR_UI::drop_process_buffers () { _process_thread->drop_buffers (); } + +void +ARDOUR_UI::feedback_detected () +{ + MessageDialog d ( + _("Something you have just done has generated a feedback path within Ardour's " + "routing. Until this feedback is removed, Ardour's output will be as it was " + "before you made the feedback-generating connection.") + ); + + d.run (); +} diff --git a/gtk2_ardour/ardour_ui.h b/gtk2_ardour/ardour_ui.h index e8340f366d..720fa6c9e9 100644 --- a/gtk2_ardour/ardour_ui.h +++ b/gtk2_ardour/ardour_ui.h @@ -729,6 +729,8 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr * PluginEqGui::impulse_analysis (). */ ARDOUR::ProcessThread* _process_thread; + + void feedback_detected (); }; #endif /* __ardour_gui_h__ */ diff --git a/libs/ardour/ardour/graph.h b/libs/ardour/ardour/graph.h index 14ef353853..f1ebba698a 100644 --- a/libs/ardour/ardour/graph.h +++ b/libs/ardour/ardour/graph.h @@ -46,6 +46,7 @@ class Graph; class Route; class Session; +class GraphEdges; typedef boost::shared_ptr<GraphNode> node_ptr_t; @@ -61,7 +62,7 @@ public: void prep(); void trigger (GraphNode * n); - void rechain (boost::shared_ptr<RouteList> r); + void rechain (boost::shared_ptr<RouteList>, GraphEdges const &); void dump (int chain); void process(); diff --git a/libs/ardour/ardour/route.h b/libs/ardour/ardour/route.h index bc84fd9470..6df6934517 100644 --- a/libs/ardour/ardour/route.h +++ b/libs/ardour/ardour/route.h @@ -310,9 +310,17 @@ class Route : public SessionObject, public Automatable, public RouteGroupMember, /** * return true if this route feeds the first argument directly, via - * either its main outs or a send. + * either its main outs or a send. This is checked by the actual + * connections, rather than by what the graph is currently doing. */ - bool direct_feeds (boost::shared_ptr<Route>, bool* via_send_only = 0); + bool direct_feeds_according_to_reality (boost::shared_ptr<Route>, bool* via_send_only = 0); + + /** + * return true if this route feeds the first argument directly, via + * either its main outs or a send, according to the graph that + * is currently being processed. + */ + bool direct_feeds_according_to_graph (boost::shared_ptr<Route>, bool* via_send_only = 0); struct FeedRecord { boost::weak_ptr<Route> r; @@ -334,7 +342,6 @@ class Route : public SessionObject, public Automatable, public RouteGroupMember, const FedBy& fed_by() const { return _fed_by; } void clear_fed_by (); bool add_fed_by (boost::shared_ptr<Route>, bool sends_only); - bool not_fed() const { return _fed_by.empty(); } /* Controls (not all directly owned by the Route */ diff --git a/libs/ardour/ardour/route_dag.h b/libs/ardour/ardour/route_dag.h deleted file mode 100644 index c9ce08ba71..0000000000 --- a/libs/ardour/ardour/route_dag.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright (C) 2011 Paul Davis - Author: Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include <map> -#include <set> - -namespace ARDOUR { - -typedef boost::shared_ptr<Route> DAGVertex; - -/** A list of edges for a directed acyclic graph for routes */ -class DAGEdges -{ -public: - typedef std::map<DAGVertex, std::set<DAGVertex> > EdgeMap; - - void add (DAGVertex from, DAGVertex to); - std::set<DAGVertex> from (DAGVertex r) const; - void remove (DAGVertex from, DAGVertex to); - bool has_none_to (DAGVertex to) const; - bool empty () const; - void dump () const; - -private: - void insert (EdgeMap& e, DAGVertex a, DAGVertex b); - - /* Keep a map in both directions to speed lookups */ - - /** map of edges with from as `first' and to as `second' */ - EdgeMap _from_to; - /** map of the same edges with to as `first' and from as `second' */ - EdgeMap _to_from; -}; - -boost::shared_ptr<RouteList> topological_sort ( - boost::shared_ptr<RouteList>, - DAGEdges - ); - -} - diff --git a/libs/ardour/ardour/route_graph.h b/libs/ardour/ardour/route_graph.h new file mode 100644 index 0000000000..0b0af6c7dd --- /dev/null +++ b/libs/ardour/ardour/route_graph.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2011 Paul Davis + Author: Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef __ardour_route_graph_h__ +#define __ardour_route_graph_h__ + +#include <map> +#include <set> + +namespace ARDOUR { + +typedef boost::shared_ptr<Route> GraphVertex; + +/** A list of edges for a directed graph for routes. + * + * It keeps the same data in a few different ways, with add() adding edges + * to all different representations, remove() removing similarly, and the + * lookup method using whichever representation is most efficient for + * that particular lookup. + * + * This may be a premature optimisation... + */ +class GraphEdges +{ +public: + typedef std::map<GraphVertex, std::set<GraphVertex> > EdgeMap; + + void add (GraphVertex from, GraphVertex to, bool via_sends_only); + bool has (GraphVertex from, GraphVertex to, bool* via_sends_only); + std::set<GraphVertex> from (GraphVertex r) const; + void remove (GraphVertex from, GraphVertex to); + bool has_none_to (GraphVertex to) const; + bool empty () const; + void dump () const; + +private: + void insert (EdgeMap& e, GraphVertex a, GraphVertex b); + + typedef std::multimap<GraphVertex, std::pair<GraphVertex, bool> > EdgeMapWithSends; + + EdgeMapWithSends::iterator find_in_from_to_with_sends (GraphVertex, GraphVertex); + + /** map of edges with from as `first' and to as `second' */ + EdgeMap _from_to; + /** map of the same edges with to as `first' and from as `second' */ + EdgeMap _to_from; + /** map of edges with via-sends information; first part of the map is + the `from' vertex, second is the `to' vertex and a flag which is + true if the edge is via a send only. + */ + EdgeMapWithSends _from_to_with_sends; +}; + +boost::shared_ptr<RouteList> topological_sort ( + boost::shared_ptr<RouteList>, + GraphEdges + ); + +} + +#endif diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index ad42ee768b..c0e9e304fc 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -56,6 +56,7 @@ #include "ardour/session_event.h" #include "ardour/location.h" #include "ardour/interpolation.h" +#include "ardour/route_graph.h" #ifdef HAVE_JACK_SESSION #include <jack/session.h> @@ -826,6 +827,12 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi std::list<std::string> unknown_processors () const; + /** Emitted when a feedback cycle has been detected within Ardour's signal + processing path. Until it is fixed (by the user) some (unspecified) + routes will not be run. + */ + static PBD::Signal0<void> FeedbackDetected; + /* handlers can return an integer value: 0: config.set_audio_search_path() or config.set_midi_search_path() was used to modify the search path and we should try to find it again. @@ -1206,7 +1213,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi /* routes stuff */ - boost::shared_ptr<Graph> route_graph; + boost::shared_ptr<Graph> _process_graph; SerializedRCUManager<RouteList> routes; @@ -1500,6 +1507,11 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi boost::shared_ptr<Speakers> _speakers; void load_nested_sources (const XMLNode& node); + + /** The directed graph of routes that is currently being used for audio processing + and solo/mute computations. + */ + GraphEdges _current_route_graph; }; } // namespace ARDOUR diff --git a/libs/ardour/graph.cc b/libs/ardour/graph.cc index 8658d30828..7d75738e0b 100644 --- a/libs/ardour/graph.cc +++ b/libs/ardour/graph.cc @@ -276,45 +276,18 @@ Graph::restart_cycle() // starting with waking up the others. } -static bool -is_feedback (boost::shared_ptr<RouteList> routelist, Route* from, boost::shared_ptr<Route> to) -{ - for (RouteList::iterator ri=routelist->begin(); ri!=routelist->end(); ri++) { - if ((*ri).get() == from) { - return false; - } - if ((*ri) == to) { - return true; - } - } - - return false; -} - -static bool -is_feedback (boost::shared_ptr<RouteList> routelist, boost::shared_ptr<Route> from, Route* to) -{ - for (RouteList::iterator ri=routelist->begin(); ri!=routelist->end(); ri++) { - if ((*ri).get() == to) { - return true; - } - if ((*ri) == from) { - return false; - } - } - - return false; -} +/** Rechain our stuff using a list of routes (which can be in any order) and + * a directed graph of their interconnections, which is guaranteed to be + * acyclic. + */ void -Graph::rechain (boost::shared_ptr<RouteList> routelist) +Graph::rechain (boost::shared_ptr<RouteList> routelist, GraphEdges const & edges) { - node_list_t::iterator ni; Glib::Mutex::Lock ls (_swap_mutex); int chain = _setup_chain; DEBUG_TRACE (DEBUG::Graph, string_compose ("============== setup %1\n", chain)); - // set all refcounts to 0; /* This will become the number of nodes that do not feed any other node; once we have processed this number of those nodes, we have finished. @@ -337,53 +310,38 @@ Graph::rechain (boost::shared_ptr<RouteList> routelist) // now add refs for the connections. - for (ni=_nodes_rt[chain].begin(); ni!=_nodes_rt[chain].end(); ni++) { - - /* We will set this to true if the node *ni is directly or - indirectly fed by anything (without feedback) - */ - bool has_input = false; - - /* We will set this to true if the node *ni directly feeds - anything (without feedback) - */ - bool has_output = false; + for (node_list_t::iterator ni = _nodes_rt[chain].begin(); ni != _nodes_rt[chain].end(); ni++) { - /* We will also set up *ni's _activation_set to contain any nodes - that it directly feeds. - */ + boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (*ni); - boost::shared_ptr<Route> rp = boost::dynamic_pointer_cast<Route>( *ni); + /* The routes that are directly fed by r */ + set<GraphVertex> fed_from_r = edges.from (r); - for (RouteList::iterator ri=routelist->begin(); ri!=routelist->end(); ri++) { - if (rp->direct_feeds (*ri)) { - if (is_feedback (routelist, rp.get(), *ri)) { - continue; - } + /* Hence whether r has an output */ + bool const has_output = !fed_from_r.empty (); - has_output = true; - (*ni)->_activation_set[chain].insert (*ri); - } - } + /* Set up r's activation set */ + for (set<GraphVertex>::iterator i = fed_from_r.begin(); i != fed_from_r.end(); ++i) { + r->_activation_set[chain].insert (*i); + } - for (Route::FedBy::iterator fi=rp->fed_by().begin(); fi!=rp->fed_by().end(); fi++) { - if (boost::shared_ptr<Route> r = fi->r.lock()) { - if (!is_feedback (routelist, r, rp.get())) { - has_input = true; - } - } - } + /* r has an input if there are some incoming edges to r in the graph */ + bool const has_input = !edges.has_none_to (r); /* Increment the refcount of any route that we directly feed */ - for (node_set_t::iterator ai=(*ni)->_activation_set[chain].begin(); ai!=(*ni)->_activation_set[chain].end(); ai++) { + for (node_set_t::iterator ai = r->_activation_set[chain].begin(); ai != r->_activation_set[chain].end(); ai++) { (*ai)->_init_refcount[chain] += 1; } if (!has_input) { + /* no input, so this node needs to be triggered initially to get things going */ _init_trigger_list[chain].push_back (*ni); } if (!has_output) { + /* no output, so this is one of the nodes that we can count off to decide + if we've finished + */ _init_finished_refcount[chain] += 1; } } diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index 0b5ccdc53d..d07faf506c 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -81,7 +81,7 @@ PBD::Signal0<void> Route::RemoteControlIDChange; Route::Route (Session& sess, string name, Flag flg, DataType default_type) : SessionObject (sess, name) , Automatable (sess) - , GraphNode (sess.route_graph) + , GraphNode (sess._process_graph) , _active (true) , _signal_latency (0) , _initial_delay (0) @@ -744,7 +744,7 @@ Route::set_solo_isolated (bool yn, void *src) } bool sends_only; - bool does_feed = direct_feeds (*i, &sends_only); // we will recurse anyway, so don't use ::feeds() + bool does_feed = direct_feeds_according_to_graph (*i, &sends_only); // we will recurse anyway, so don't use ::feeds() if (does_feed && !sends_only) { (*i)->set_solo_isolated (yn, (*i)->route_group()); @@ -2644,14 +2644,14 @@ Route::feeds (boost::shared_ptr<Route> other, bool* via_sends_only) } bool -Route::direct_feeds (boost::shared_ptr<Route> other, bool* only_send) +Route::direct_feeds_according_to_reality (boost::shared_ptr<Route> other, bool* via_send_only) { DEBUG_TRACE (DEBUG::Graph, string_compose ("Feeds? %1\n", _name)); if (_output->connected_to (other->input())) { DEBUG_TRACE (DEBUG::Graph, string_compose ("\tdirect FEEDS %2\n", other->name())); - if (only_send) { - *only_send = false; + if (via_send_only) { + *via_send_only = false; } return true; @@ -2665,8 +2665,8 @@ Route::direct_feeds (boost::shared_ptr<Route> other, bool* only_send) if ((iop = boost::dynamic_pointer_cast<IOProcessor>(*r)) != 0) { if (iop->feeds (other)) { DEBUG_TRACE (DEBUG::Graph, string_compose ("\tIOP %1 does feed %2\n", iop->name(), other->name())); - if (only_send) { - *only_send = true; + if (via_send_only) { + *via_send_only = true; } return true; } else { @@ -2682,6 +2682,12 @@ Route::direct_feeds (boost::shared_ptr<Route> other, bool* only_send) return false; } +bool +Route::direct_feeds_according_to_graph (boost::shared_ptr<Route> other, bool* via_send_only) +{ + return _session._current_route_graph.has (shared_from_this (), other, via_send_only); +} + /** Called from the (non-realtime) butler thread when the transport is stopped */ void Route::nonrealtime_handle_transport_stopped (bool /*abort_ignored*/, bool did_locate, bool can_flush_processors) diff --git a/libs/ardour/route_dag.cc b/libs/ardour/route_graph.cc index 3b13cf9415..fbf406fd92 100644 --- a/libs/ardour/route_dag.cc +++ b/libs/ardour/route_graph.cc @@ -19,7 +19,7 @@ */ #include "ardour/route.h" -#include "ardour/route_dag.h" +#include "ardour/route_graph.h" #include "i18n.h" @@ -27,35 +27,71 @@ using namespace std; using namespace ARDOUR; void -DAGEdges::add (DAGVertex from, DAGVertex to) +GraphEdges::add (GraphVertex from, GraphVertex to, bool via_sends_only) { insert (_from_to, from, to); insert (_to_from, to, from); - - EdgeMap::iterator i = _from_to.find (from); - if (i != _from_to.end ()) { - i->second.insert (to); + + EdgeMapWithSends::iterator i = find_in_from_to_with_sends (from, to); + if (i != _from_to_with_sends.end ()) { + i->second.second = via_sends_only; } else { - set<DAGVertex> v; - v.insert (to); - _from_to.insert (make_pair (from, v)); + _from_to_with_sends.insert ( + make_pair (from, make_pair (to, via_sends_only)) + ); + } +} + +/** Find a from/to pair in the _from_to_with_sends map. + * @return iterator to the edge, or _from_to_with_sends.end(). + */ +GraphEdges::EdgeMapWithSends::iterator +GraphEdges::find_in_from_to_with_sends (GraphVertex from, GraphVertex to) +{ + typedef EdgeMapWithSends::iterator Iter; + pair<Iter, Iter> r = _from_to_with_sends.equal_range (from); + for (Iter i = r.first; i != r.second; ++i) { + if (i->second.first == to) { + return i; + } + } + + return _from_to_with_sends.end (); +} + +/** @param via_sends_only if non-0, filled in with true if the edge is a + * path via a send only. + * @return true if the given edge is present. + */ +bool +GraphEdges::has (GraphVertex from, GraphVertex to, bool* via_sends_only) +{ + EdgeMapWithSends::iterator i = find_in_from_to_with_sends (from, to); + if (i == _from_to_with_sends.end ()) { + return false; } + if (via_sends_only) { + *via_sends_only = i->second.second; + } + + return true; } -set<DAGVertex> -DAGEdges::from (DAGVertex r) const +/** @return the vertices that are fed from `r' */ +set<GraphVertex> +GraphEdges::from (GraphVertex r) const { EdgeMap::const_iterator i = _from_to.find (r); if (i == _from_to.end ()) { - return set<DAGVertex> (); + return set<GraphVertex> (); } return i->second; } void -DAGEdges::remove (DAGVertex from, DAGVertex to) +GraphEdges::remove (GraphVertex from, GraphVertex to) { EdgeMap::iterator i = _from_to.find (from); assert (i != _from_to.end ()); @@ -70,6 +106,10 @@ DAGEdges::remove (DAGVertex from, DAGVertex to) if (j->second.empty ()) { _to_from.erase (j); } + + EdgeMapWithSends::iterator k = find_in_from_to_with_sends (from, to); + assert (k != _from_to_with_sends.end ()); + _from_to_with_sends.erase (k); } /** @param to `To' route. @@ -77,24 +117,24 @@ DAGEdges::remove (DAGVertex from, DAGVertex to) */ bool -DAGEdges::has_none_to (DAGVertex to) const +GraphEdges::has_none_to (GraphVertex to) const { return _to_from.find (to) == _to_from.end (); } bool -DAGEdges::empty () const +GraphEdges::empty () const { assert (_from_to.empty () == _to_from.empty ()); return _from_to.empty (); } void -DAGEdges::dump () const +GraphEdges::dump () const { for (EdgeMap::const_iterator i = _from_to.begin(); i != _from_to.end(); ++i) { cout << "FROM: " << i->first->name() << " "; - for (set<DAGVertex>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) { + for (set<GraphVertex>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) { cout << (*j)->name() << " "; } cout << "\n"; @@ -102,21 +142,22 @@ DAGEdges::dump () const for (EdgeMap::const_iterator i = _to_from.begin(); i != _to_from.end(); ++i) { cout << "TO: " << i->first->name() << " "; - for (set<DAGVertex>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) { + for (set<GraphVertex>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) { cout << (*j)->name() << " "; } cout << "\n"; } } - + +/** Insert an edge into one of the EdgeMaps */ void -DAGEdges::insert (EdgeMap& e, DAGVertex a, DAGVertex b) +GraphEdges::insert (EdgeMap& e, GraphVertex a, GraphVertex b) { EdgeMap::iterator i = e.find (a); if (i != e.end ()) { i->second.insert (b); } else { - set<DAGVertex> v; + set<GraphVertex> v; v.insert (b); e.insert (make_pair (a, v)); } @@ -124,7 +165,7 @@ DAGEdges::insert (EdgeMap& e, DAGVertex a, DAGVertex b) struct RouteRecEnabledComparator { - bool operator () (DAGVertex r1, DAGVertex r2) const + bool operator () (GraphVertex r1, GraphVertex r2) const { if (r1->record_enabled()) { if (r2->record_enabled()) { @@ -152,7 +193,7 @@ struct RouteRecEnabledComparator boost::shared_ptr<RouteList> ARDOUR::topological_sort ( boost::shared_ptr<RouteList> routes, - DAGEdges edges + GraphEdges edges ) { boost::shared_ptr<RouteList> sorted_routes (new RouteList); @@ -162,7 +203,7 @@ ARDOUR::topological_sort ( /* initial queue has routes that are not fed by anything */ for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) { - if ((*i)->not_fed ()) { + if (edges.has_none_to (*i)) { queue.push_back (*i); } } @@ -178,11 +219,11 @@ ARDOUR::topological_sort ( */ while (!queue.empty ()) { - DAGVertex r = queue.front (); + GraphVertex r = queue.front (); queue.pop_front (); sorted_routes->push_back (r); - set<DAGVertex> e = edges.from (r); - for (set<DAGVertex>::iterator i = e.begin(); i != e.end(); ++i) { + set<GraphVertex> e = edges.from (r); + for (set<GraphVertex>::iterator i = e.begin(); i != e.end(); ++i) { edges.remove (r, *i); if (edges.has_none_to (*i)) { queue.push_back (*i); @@ -191,6 +232,7 @@ ARDOUR::topological_sort ( } if (!edges.empty ()) { + edges.dump (); /* There are cycles in the graph, so we can't do a topological sort */ return boost::shared_ptr<RouteList> (); } diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 9aed414b1c..e940009e94 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -86,7 +86,7 @@ #include "ardour/recent_sessions.h" #include "ardour/region_factory.h" #include "ardour/return.h" -#include "ardour/route_dag.h" +#include "ardour/route_graph.h" #include "ardour/route_group.h" #include "ardour/send.h" #include "ardour/session.h" @@ -129,6 +129,7 @@ PBD::Signal0<void> Session::AutoBindingOff; PBD::Signal2<void,std::string, std::string> Session::Exported; PBD::Signal1<int,boost::shared_ptr<Playlist> > Session::AskAboutPlaylistDeletion; PBD::Signal0<void> Session::Quit; +PBD::Signal0<void> Session::FeedbackDetected; static void clean_up_session_event (SessionEvent* ev) { delete ev; } const SessionEvent::RTeventCallback Session::rt_cleanup (clean_up_session_event); @@ -149,7 +150,7 @@ Session::Session (AudioEngine &eng, , _post_transport_work (0) , _send_timecode_update (false) , _all_route_group (new RouteGroup (*this, "all")) - , route_graph (new Graph(*this)) + , _process_graph (new Graph (*this)) , routes (new RouteList) , _total_free_4k_blocks (0) , _bundles (new BundleList) @@ -1293,7 +1294,7 @@ Session::resort_routes () /* writer goes out of scope and forces update */ } - //route_graph->dump(1); + //_process_graph->dump(1); #ifndef NDEBUG boost::shared_ptr<RouteList> rl = routes.reader (); @@ -1313,51 +1314,95 @@ Session::resort_routes () } +/** This is called whenever we need to rebuild the graph of how we will process + * routes. + * @param r List of routes, in any order. + */ + void Session::resort_routes_using (boost::shared_ptr<RouteList> r) { - DAGEdges edges; - + /* We are going to build a directed graph of our routes; + this is where the edges of that graph are put. + */ + + GraphEdges edges; + + /* Go through all routes doing two things: + * + * 1. Collect the edges of the route graph. Each of these edges + * is a pair of routes, one of which directly feeds the other + * either by a JACK connection or by an internal send. + * + * 2. Begin the process of making routes aware of which other + * routes directly or indirectly feed them. This information + * is used by the solo code. + */ + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + /* Clear out the route's list of direct or indirect feeds */ (*i)->clear_fed_by (); for (RouteList::iterator j = r->begin(); j != r->end(); ++j) { - /* although routes can feed themselves, it will - cause an endless recursive descent if we - detect it. so don't bother checking for - self-feeding. - */ - - if (*j == *i) { - continue; - } - bool via_sends_only; - if ((*j)->direct_feeds (*i, &via_sends_only)) { - edges.add (*j, *i); + /* See if this *j feeds *i according to the current state of the JACK + connections and internal sends. + */ + if ((*j)->direct_feeds_according_to_reality (*i, &via_sends_only)) { + /* add the edge to the graph (part #1) */ + edges.add (*j, *i, via_sends_only); + /* tell the route (for part #2) */ (*i)->add_fed_by (*j, via_sends_only); } } } - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - trace_terminal (*i, *i); - } - + /* Attempt a topological sort of the route graph */ boost::shared_ptr<RouteList> sorted_routes = topological_sort (r, edges); - route_graph->rechain (sorted_routes); + + if (sorted_routes) { + /* We got a satisfactory topological sort, so there is no feedback; + use this new graph. + + Note: the process graph rechain does not require a + topologically-sorted list, but hey ho. + */ + _process_graph->rechain (sorted_routes, edges); + _current_route_graph = edges; + + /* Complete the building of the routes' lists of what directly + or indirectly feeds them. + */ + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + trace_terminal (*i, *i); + } + + r = sorted_routes; #ifndef NDEBUG - DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n"); - for (RouteList::iterator i = sorted_routes->begin(); i != sorted_routes->end(); ++i) { - DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 signal order %2\n", - (*i)->name(), (*i)->order_key ("signal"))); - } + DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n"); + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 signal order %2\n", + (*i)->name(), (*i)->order_key ("signal"))); + } #endif + } else { + /* The topological sort failed, so we have a problem. Tell everyone + and stick to the old graph; this will continue to be processed, so + until the feedback is fixed, what is played back will not quite + reflect what is actually connected. Note also that we do not + do trace_terminal here, as it would fail due to an endless recursion, + so the solo code will think that everything is still connected + as it was before. + */ + + FeedbackDetected (); /* EMIT SIGNAL */ + } + } /** Find a route name starting with \a base, maybe followed by the @@ -2174,7 +2219,7 @@ Session::remove_route (boost::shared_ptr<Route> route) */ resort_routes (); - route_graph->clear_other_chain (); + _process_graph->clear_other_chain (); /* get rid of it from the dead wood collection in the route list manager */ diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc index 2384dd5fb3..33c4e5621c 100644 --- a/libs/ardour/session_process.cc +++ b/libs/ardour/session_process.cc @@ -107,9 +107,9 @@ Session::no_roll (pframes_t nframes) _click_io->silence (nframes); } - if (route_graph->threads_in_use() > 0) { + if (_process_graph->threads_in_use() > 0) { DEBUG_TRACE(DEBUG::ProcessThreads,"calling graph/no-roll\n"); - route_graph->routes_no_roll( nframes, _transport_frame, end_frame, non_realtime_work_pending(), declick); + _process_graph->routes_no_roll( nframes, _transport_frame, end_frame, non_realtime_work_pending(), declick); } else { for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { @@ -148,9 +148,9 @@ Session::process_routes (pframes_t nframes, bool& need_butler) using 1 thread. its needed because otherwise when we remove tracks, the graph never gets updated. */ - if (1 || route_graph->threads_in_use() > 0) { + if (1 || _process_graph->threads_in_use() > 0) { DEBUG_TRACE(DEBUG::ProcessThreads,"calling graph/process-routes\n"); - route_graph->process_routes (nframes, start_frame, end_frame, declick, need_butler); + _process_graph->process_routes (nframes, start_frame, end_frame, declick, need_butler); } else { for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { @@ -185,8 +185,8 @@ Session::silent_process_routes (pframes_t nframes, bool& need_butler) using 1 thread. its needed because otherwise when we remove tracks, the graph never gets updated. */ - if (1 || route_graph->threads_in_use() > 0) { - route_graph->silent_process_routes (nframes, start_frame, end_frame, need_butler); + if (1 || _process_graph->threads_in_use() > 0) { + _process_graph->silent_process_routes (nframes, start_frame, end_frame, need_butler); } else { for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 9bde8abab2..5836af03ee 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -170,7 +170,7 @@ libardour_sources = [ 'return.cc', 'reverse.cc', 'route.cc', - 'route_dag.cc', + 'route_graph.cc', 'route_group.cc', 'route_group_member.cc', 'rb_effect.cc', |