Untangle Graph from Route
The process-graph should only be concerned with GraphNodes, which may or may not be Routes. This also removes intrinsic connection information from the graph-node. Connection information is to be kept separate from the nodes. When the graph is re-calculated in the background, old information has to be retained until the new graph becomes active. Previously *new* information was already stored in the nodes while the graph is sorted, even though the new graph was not active.
This commit is contained in:
parent
3862d13e21
commit
9ad154f265
|
@ -59,7 +59,7 @@ public:
|
|||
Graph (Session& session);
|
||||
|
||||
void trigger (GraphNode* n);
|
||||
void rechain (boost::shared_ptr<RouteList>, GraphEdges const&);
|
||||
void rechain (GraphNodeList const&, GraphEdges const&);
|
||||
bool plot (std::string const& file_name) const;
|
||||
|
||||
void plot (int chain);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Robin Gareus <robin@gareus.org>
|
||||
* Copyright (C) 2015-2022 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -22,9 +22,15 @@
|
|||
#include <map>
|
||||
#include <set>
|
||||
|
||||
namespace ARDOUR {
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
typedef boost::shared_ptr<Route> GraphVertex;
|
||||
#include "ardour/libardour_visibility.h"
|
||||
#include "ardour/types.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
class GraphNode;
|
||||
|
||||
typedef boost::shared_ptr<GraphNode> GraphVertex;
|
||||
|
||||
/** A list of edges for a directed graph for routes.
|
||||
*
|
||||
|
@ -38,40 +44,43 @@ typedef boost::shared_ptr<Route> GraphVertex;
|
|||
class LIBARDOUR_API 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);
|
||||
bool feeds (GraphVertex from, GraphVertex to);
|
||||
std::set<GraphVertex> from (GraphVertex r) const;
|
||||
void remove (GraphVertex from, GraphVertex to);
|
||||
|
||||
bool has (GraphVertex from, GraphVertex to, bool* via_sends_only);
|
||||
bool feeds (GraphVertex from, GraphVertex to) const;
|
||||
/** @return the vertices that are directly fed from `r' */
|
||||
std::set<GraphVertex> from (GraphVertex r) const;
|
||||
/** @return all nodes that feed `r' (`r` is fed-by rv) */
|
||||
std::set<GraphVertex> to (GraphVertex r, bool via_sends_only = false) const;
|
||||
bool has_none_to (GraphVertex to) const;
|
||||
bool empty () const;
|
||||
void dump () const;
|
||||
|
||||
private:
|
||||
void insert (EdgeMap& e, GraphVertex a, GraphVertex b);
|
||||
|
||||
typedef std::map<GraphVertex, std::set<GraphVertex> > EdgeMap;
|
||||
typedef std::multimap<GraphVertex, std::pair<GraphVertex, bool> > EdgeMapWithSends;
|
||||
|
||||
void insert (EdgeMap& e, GraphVertex a, GraphVertex b);
|
||||
|
||||
EdgeMapWithSends::iterator find_in_from_to_with_sends (GraphVertex, GraphVertex);
|
||||
EdgeMapWithSends::iterator find_recursively_in_from_to_with_sends (GraphVertex, GraphVertex);
|
||||
EdgeMapWithSends::iterator find_in_to_from_with_sends (GraphVertex, GraphVertex);
|
||||
|
||||
EdgeMapWithSends::const_iterator find_recursively_in_from_to_with_sends (GraphVertex, GraphVertex) const;
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
* 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;
|
||||
EdgeMapWithSends _to_from_with_sends;
|
||||
};
|
||||
|
||||
boost::shared_ptr<RouteList> topological_sort (
|
||||
boost::shared_ptr<RouteList>,
|
||||
GraphEdges
|
||||
);
|
||||
bool topological_sort (GraphNodeList&, GraphEdges&);
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright (C) 2010-2011 David Robillard <d@drobilla.net>
|
||||
* Copyright (C) 2011 Carl Hetherington <carl@carlh.net>
|
||||
* Copyright (C) 2015-2019 Robin Gareus <robin@gareus.org>
|
||||
* Copyright (C) 2015-2022 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -65,13 +65,20 @@ public:
|
|||
finish (chain);
|
||||
}
|
||||
|
||||
private:
|
||||
void finish (int chain);
|
||||
void process ();
|
||||
virtual std::string graph_node_name () const = 0;
|
||||
virtual bool direct_feeds_according_to_reality (boost::shared_ptr<GraphNode>, bool* via_send_only = 0) = 0;
|
||||
|
||||
protected:
|
||||
virtual void process () = 0;
|
||||
|
||||
boost::shared_ptr<Graph> _graph;
|
||||
GATOMIC_QUAL gint _refcount;
|
||||
|
||||
private:
|
||||
void finish (int chain);
|
||||
|
||||
GATOMIC_QUAL gint _refcount;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -431,54 +431,41 @@ public:
|
|||
int add_foldback_send (boost::shared_ptr<Route>, bool post_fader);
|
||||
void remove_monitor_send ();
|
||||
|
||||
/**
|
||||
* return true if this route feeds the first argument via at least one
|
||||
* (arbitrarily long) signal pathway.
|
||||
*/
|
||||
bool feeds (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. This is checked by the actual
|
||||
* connections, rather than by what the graph is currently doing.
|
||||
*/
|
||||
bool direct_feeds_according_to_reality (boost::shared_ptr<Route>, bool* via_send_only = 0);
|
||||
bool direct_feeds_according_to_reality (boost::shared_ptr<GraphNode>, bool* via_send_only = 0);
|
||||
|
||||
std::string graph_node_name () const {
|
||||
return name ();
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if this route feeds the first argument directly, via
|
||||
* @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);
|
||||
|
||||
bool feeds_according_to_graph (boost::shared_ptr<Route>);
|
||||
/**
|
||||
* @return true if this node feeds the first argument via at least one
|
||||
* (arbitrarily long) signal pathway.
|
||||
*/
|
||||
bool feeds (boost::shared_ptr<Route>);
|
||||
|
||||
/**
|
||||
* @return a list of all routes that eventually may feed a signal
|
||||
* into this route.
|
||||
*/
|
||||
std::set<boost::shared_ptr<Route>> signal_sources (bool via_sends_only = false);
|
||||
|
||||
/** Follow output port connections and check if the output *port*
|
||||
* of any downstream routes is connected.
|
||||
*/
|
||||
bool output_effectively_connected () const;
|
||||
|
||||
struct FeedRecord {
|
||||
boost::weak_ptr<Route> r;
|
||||
bool sends_only;
|
||||
|
||||
FeedRecord (boost::shared_ptr<Route> rp, bool sendsonly)
|
||||
: r (rp)
|
||||
, sends_only (sendsonly) {}
|
||||
};
|
||||
|
||||
struct FeedRecordCompare {
|
||||
bool operator() (const FeedRecord& a, const FeedRecord& b) const {
|
||||
return a.r < b.r;
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::set<FeedRecord,FeedRecordCompare> FedBy;
|
||||
|
||||
const FedBy& fed_by() const { return _fed_by; }
|
||||
void clear_fed_by ();
|
||||
bool add_fed_by (boost::shared_ptr<Route>, bool sends_only);
|
||||
|
||||
/* Controls (not all directly owned by the Route) */
|
||||
|
||||
boost::shared_ptr<AutomationControl> get_control (const Evoral::Parameter& param);
|
||||
|
@ -604,6 +591,8 @@ public:
|
|||
protected:
|
||||
friend class Session;
|
||||
|
||||
void process ();
|
||||
|
||||
void catch_up_on_solo_mute_override ();
|
||||
void set_listen (bool);
|
||||
|
||||
|
@ -684,7 +673,6 @@ protected:
|
|||
std::string _comment;
|
||||
bool _have_internal_generator;
|
||||
DataType _default_type;
|
||||
FedBy _fed_by;
|
||||
|
||||
InstrumentInfo _instrument_info;
|
||||
bool _instrument_fanned_out;
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
#include "ardour/plugin.h"
|
||||
#include "ardour/presentation_info.h"
|
||||
#include "ardour/route.h"
|
||||
#include "ardour/route_graph.h"
|
||||
#include "ardour/graph_edges.h"
|
||||
#include "ardour/transport_api.h"
|
||||
#include "ardour/triggerbox.h"
|
||||
|
||||
|
@ -760,7 +760,6 @@ public:
|
|||
void remove_route (boost::shared_ptr<Route>);
|
||||
|
||||
void resort_routes ();
|
||||
void resort_routes_using (boost::shared_ptr<RouteList>);
|
||||
|
||||
AudioEngine & engine() { return _engine; }
|
||||
AudioEngine const & engine () const { return _engine; }
|
||||
|
@ -1903,8 +1902,6 @@ private:
|
|||
|
||||
/* routes stuff */
|
||||
|
||||
boost::shared_ptr<Graph> _process_graph;
|
||||
|
||||
SerializedRCUManager<RouteList> routes;
|
||||
|
||||
void add_routes (RouteList&, bool input_auto_connect, bool output_auto_connect, PresentationInfo::order_t);
|
||||
|
@ -2252,10 +2249,15 @@ private:
|
|||
void load_nested_sources (const XMLNode& node);
|
||||
|
||||
/** The directed graph of routes that is currently being used for audio processing
|
||||
and solo/mute computations.
|
||||
*/
|
||||
* and solo/mute computations.
|
||||
*/
|
||||
GraphEdges _current_route_graph;
|
||||
|
||||
boost::shared_ptr<Graph> _process_graph;
|
||||
|
||||
void resort_routes_using (boost::shared_ptr<RouteList>);
|
||||
bool rechain_process_graph (GraphNodeList&);
|
||||
|
||||
void ensure_route_presentation_info_gap (PresentationInfo::order_t, uint32_t gap_size);
|
||||
|
||||
friend class ProcessorChangeBlocker;
|
||||
|
|
|
@ -69,6 +69,7 @@ namespace ARDOUR {
|
|||
|
||||
class Source;
|
||||
class AudioSource;
|
||||
class GraphNode;
|
||||
class Route;
|
||||
class Region;
|
||||
class Stripable;
|
||||
|
@ -625,6 +626,7 @@ typedef std::list<samplepos_t> AnalysisFeatureList;
|
|||
typedef std::vector<samplepos_t> XrunPositions;
|
||||
|
||||
typedef std::list<boost::shared_ptr<Route> > RouteList;
|
||||
typedef std::list<boost::shared_ptr<GraphNode> > GraphNodeList;
|
||||
typedef std::list<boost::shared_ptr<Stripable> > StripableList;
|
||||
typedef std::list<boost::weak_ptr <Route> > WeakRouteList;
|
||||
typedef std::list<boost::weak_ptr <Stripable> > WeakStripableList;
|
||||
|
|
|
@ -353,7 +353,7 @@ Graph::reached_terminal_node ()
|
|||
* acyclic.
|
||||
*/
|
||||
void
|
||||
Graph::rechain (boost::shared_ptr<RouteList> routelist, GraphEdges const& edges)
|
||||
Graph::rechain (GraphNodeList const& nodelist, GraphEdges const& edges)
|
||||
{
|
||||
Glib::Threads::Mutex::Lock ls (_swap_mutex);
|
||||
|
||||
|
@ -372,40 +372,38 @@ Graph::rechain (boost::shared_ptr<RouteList> routelist, GraphEdges const& edges)
|
|||
|
||||
_nodes_rt[chain].clear ();
|
||||
|
||||
/* Clear things out, and make _nodes_rt[chain] a copy of routelist */
|
||||
for (RouteList::iterator ri = routelist->begin (); ri != routelist->end (); ri++) {
|
||||
(*ri)->_init_refcount[chain] = 0;
|
||||
(*ri)->_activation_set[chain].clear ();
|
||||
_nodes_rt[chain].push_back (*ri);
|
||||
/* Clear things out, and make _nodes_rt[chain] a copy of nodelist */
|
||||
for (auto const& ri : nodelist) {
|
||||
ri->_init_refcount[chain] = 0;
|
||||
ri->_activation_set[chain].clear ();
|
||||
_nodes_rt[chain].push_back (ri);
|
||||
}
|
||||
|
||||
// now add refs for the connections.
|
||||
|
||||
for (node_list_t::iterator ni = _nodes_rt[chain].begin (); ni != _nodes_rt[chain].end (); ni++) {
|
||||
boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (*ni);
|
||||
|
||||
for (auto const& ni : _nodes_rt[chain]) {
|
||||
/* The routes that are directly fed by r */
|
||||
set<GraphVertex> fed_from_r = edges.from (r);
|
||||
set<GraphVertex> fed_from_r = edges.from (ni);
|
||||
|
||||
/* Hence whether r has an output */
|
||||
bool const has_output = !fed_from_r.empty ();
|
||||
|
||||
/* 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);
|
||||
ni->_activation_set[chain].insert (*i);
|
||||
}
|
||||
|
||||
/* r has an input if there are some incoming edges to r in the graph */
|
||||
bool const has_input = !edges.has_none_to (r);
|
||||
/* ni has an input if there are some incoming edges to r in the graph */
|
||||
bool const has_input = !edges.has_none_to (ni);
|
||||
|
||||
/* Increment the refcount of any route that we directly feed */
|
||||
for (node_set_t::iterator ai = r->_activation_set[chain].begin (); ai != r->_activation_set[chain].end (); ai++) {
|
||||
(*ai)->_init_refcount[chain] += 1;
|
||||
for (auto const& ai : ni->_activation_set[chain]) {
|
||||
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);
|
||||
_init_trigger_list[chain].push_back (ni);
|
||||
}
|
||||
|
||||
if (!has_output) {
|
||||
|
@ -574,17 +572,16 @@ Graph::dump (int chain) const
|
|||
chain = _pending_chain;
|
||||
|
||||
DEBUG_TRACE (DEBUG::Graph, "--------------------------------------------Graph dump:\n");
|
||||
for (ni = _nodes_rt[chain].begin (); ni != _nodes_rt[chain].end (); ni++) {
|
||||
boost::shared_ptr<Route> rp = boost::dynamic_pointer_cast<Route> (*ni);
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", rp->name ().c_str (), (*ni)->_init_refcount[chain]));
|
||||
for (ai = (*ni)->_activation_set[chain].begin (); ai != (*ni)->_activation_set[chain].end (); ai++) {
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose (" triggers: %1\n", boost::dynamic_pointer_cast<Route> (*ai)->name ().c_str ()));
|
||||
for (auto const& ni : _nodes_rt[chain]) {
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", ni->graph_node_name (), ni->_init_refcount[chain]));
|
||||
for (auto const& ai : ni->_activation_set[chain]) {
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose (" triggers: %1\n", ai->graph_node_name ()));
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_TRACE (DEBUG::Graph, "------------- trigger list:\n");
|
||||
for (ni = _init_trigger_list[chain].begin (); ni != _init_trigger_list[chain].end (); ni++) {
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", boost::dynamic_pointer_cast<Route> (*ni)->name ().c_str (), (*ni)->_init_refcount[chain]));
|
||||
for (auto const& ni : _init_trigger_list[chain]) {
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", ni->graph_node_name (), ni->_init_refcount[chain]));
|
||||
}
|
||||
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose ("final activation refcount: %1\n", _n_terminal_nodes[chain]));
|
||||
|
@ -604,21 +601,19 @@ Graph::plot (std::string const& file_name) const
|
|||
ss << "digraph {\n";
|
||||
ss << " node [shape = ellipse];\n";
|
||||
|
||||
for (ni = _nodes_rt[chain].begin (); ni != _nodes_rt[chain].end (); ni++) {
|
||||
boost::shared_ptr<Route> sr = boost::dynamic_pointer_cast<Route> (*ni);
|
||||
std::string sn = string_compose ("%1 (%2)", sr->name (), (*ni)->_init_refcount[chain]);
|
||||
if ((*ni)->_init_refcount[chain] == 0 && (*ni)->_activation_set[chain].size() == 0) {
|
||||
for (auto const& ni : _nodes_rt[chain]) {
|
||||
std::string sn = string_compose ("%1 (%2)", ni->graph_node_name (), ni->_init_refcount[chain]);
|
||||
if (ni->_init_refcount[chain] == 0 && ni->_activation_set[chain].size() == 0) {
|
||||
ss << " \"" << sn << "\"[style=filled,fillcolor=gold1];\n";
|
||||
} else if ((*ni)->_init_refcount[chain] == 0) {
|
||||
} else if (ni->_init_refcount[chain] == 0) {
|
||||
ss << " \"" << sn << "\"[style=filled,fillcolor=lightskyblue1];\n";
|
||||
} else if ((*ni)->_activation_set[chain].size() == 0) {
|
||||
} else if (ni->_activation_set[chain].size() == 0) {
|
||||
ss << " \"" << sn << "\"[style=filled,fillcolor=aquamarine2];\n";
|
||||
}
|
||||
for (ai = (*ni)->_activation_set[chain].begin (); ai != (*ni)->_activation_set[chain].end (); ai++) {
|
||||
boost::shared_ptr<Route> dr = boost::dynamic_pointer_cast<Route> (*ai);
|
||||
std::string dn = string_compose ("%1 (%2)", dr->name (), (*ai)->_init_refcount[chain]);
|
||||
for (auto const& ai : ni->_activation_set[chain]) {
|
||||
std::string dn = string_compose ("%1 (%2)", ai->graph_node_name (), ai->_init_refcount[chain]);
|
||||
bool sends_only = false;
|
||||
sr->direct_feeds_according_to_reality (dr, &sends_only);
|
||||
ni->direct_feeds_according_to_reality (ai, &sends_only);
|
||||
if (sends_only) {
|
||||
ss << " edge [style=dashed];\n";
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (C) 2012-2016 Paul Davis <paul@linuxaudiosystems.com>
|
||||
* Copyright (C) 2015-2016 Robin Gareus <robin@gareus.org>
|
||||
* Copyright (C) 2015-2022 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -17,8 +17,8 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "ardour/graph_edges.h"
|
||||
#include "ardour/route.h"
|
||||
#include "ardour/route_graph.h"
|
||||
#include "ardour/track.h"
|
||||
|
||||
#include "pbd/i18n.h"
|
||||
|
@ -34,11 +34,16 @@ GraphEdges::add (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 ()) {
|
||||
i->second.second = via_sends_only;
|
||||
i->second.second |= via_sends_only;
|
||||
} else {
|
||||
_from_to_with_sends.insert (
|
||||
make_pair (from, make_pair (to, via_sends_only))
|
||||
);
|
||||
_from_to_with_sends.insert (make_pair (from, make_pair (to, via_sends_only)));
|
||||
}
|
||||
|
||||
i = find_in_to_from_with_sends (to, from);
|
||||
if (i != _to_from_with_sends.end ()) {
|
||||
i->second.second |= via_sends_only;
|
||||
} else {
|
||||
_to_from_with_sends.insert (make_pair (to, make_pair (from, via_sends_only)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,20 +60,32 @@ GraphEdges::find_in_from_to_with_sends (GraphVertex from, GraphVertex to)
|
|||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return _from_to_with_sends.end ();
|
||||
}
|
||||
|
||||
GraphEdges::EdgeMapWithSends::iterator
|
||||
GraphEdges::find_recursively_in_from_to_with_sends (GraphVertex from, GraphVertex to)
|
||||
GraphEdges::find_in_to_from_with_sends (GraphVertex to, GraphVertex from)
|
||||
{
|
||||
typedef EdgeMapWithSends::iterator Iter;
|
||||
pair<Iter, Iter> r = _to_from_with_sends.equal_range (to);
|
||||
for (Iter i = r.first; i != r.second; ++i) {
|
||||
if (i->second.first == from) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return _to_from_with_sends.end ();
|
||||
}
|
||||
|
||||
GraphEdges::EdgeMapWithSends::const_iterator
|
||||
GraphEdges::find_recursively_in_from_to_with_sends (GraphVertex from, GraphVertex to) const
|
||||
{
|
||||
typedef EdgeMapWithSends::const_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;
|
||||
}
|
||||
GraphEdges::EdgeMapWithSends::iterator t = find_recursively_in_from_to_with_sends (i->second.first, to);
|
||||
Iter t = find_recursively_in_from_to_with_sends (i->second.first, to);
|
||||
if (t != _from_to_with_sends.end ()) {
|
||||
return t;
|
||||
}
|
||||
|
@ -97,16 +114,15 @@ GraphEdges::has (GraphVertex from, GraphVertex to, bool* via_sends_only)
|
|||
}
|
||||
|
||||
bool
|
||||
GraphEdges::feeds (GraphVertex from, GraphVertex to)
|
||||
GraphEdges::feeds (GraphVertex from, GraphVertex to) const
|
||||
{
|
||||
EdgeMapWithSends::iterator i = find_recursively_in_from_to_with_sends (from, to);
|
||||
EdgeMapWithSends::const_iterator i = find_recursively_in_from_to_with_sends (from, to);
|
||||
if (i == _from_to_with_sends.end ()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return the vertices that are fed from `r' */
|
||||
set<GraphVertex>
|
||||
GraphEdges::from (GraphVertex r) const
|
||||
{
|
||||
|
@ -118,6 +134,26 @@ GraphEdges::from (GraphVertex r) const
|
|||
return i->second;
|
||||
}
|
||||
|
||||
set<GraphVertex>
|
||||
GraphEdges::to (GraphVertex t, bool via_sends_only) const
|
||||
{
|
||||
set<GraphVertex> rv;
|
||||
typedef EdgeMapWithSends::const_iterator Iter;
|
||||
pair<Iter, Iter> r = _to_from_with_sends.equal_range (t);
|
||||
for (Iter i = r.first; i != r.second; ++i) {
|
||||
if (via_sends_only) {
|
||||
if (!i->second.second) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
rv.insert (i->second.first);
|
||||
for (auto const& j: GraphEdges::to (i->second.first, i->second.second ? false : via_sends_only)) {
|
||||
rv.insert (j);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
GraphEdges::remove (GraphVertex from, GraphVertex to)
|
||||
{
|
||||
|
@ -160,18 +196,18 @@ GraphEdges::empty () const
|
|||
void
|
||||
GraphEdges::dump () const
|
||||
{
|
||||
for (EdgeMap::const_iterator i = _from_to.begin(); i != _from_to.end(); ++i) {
|
||||
cout << "FROM: " << i->first->name() << " ";
|
||||
for (set<GraphVertex>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
cout << (*j)->name() << " ";
|
||||
for (auto const& i : _from_to) {
|
||||
cout << "FROM: " << i.first->graph_node_name () << " ";
|
||||
for (auto const& j : i.second) {
|
||||
cout << j->graph_node_name () << " ";
|
||||
}
|
||||
cout << "\n";
|
||||
}
|
||||
|
||||
for (EdgeMap::const_iterator i = _to_from.begin(); i != _to_from.end(); ++i) {
|
||||
cout << "TO: " << i->first->name() << " ";
|
||||
for (set<GraphVertex>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
cout << (*j)->name() << " ";
|
||||
for (auto const& i : _to_from) {
|
||||
cout << "TO: " << i.first->graph_node_name () << " ";
|
||||
for (auto const& j : i.second) {
|
||||
cout << j->graph_node_name () << " ";
|
||||
}
|
||||
cout << "\n";
|
||||
}
|
||||
|
@ -193,12 +229,15 @@ GraphEdges::insert (EdgeMap& e, GraphVertex a, GraphVertex b)
|
|||
|
||||
struct RouteRecEnabledComparator
|
||||
{
|
||||
bool operator () (GraphVertex r1, GraphVertex r2) const
|
||||
bool operator () (GraphVertex n1, GraphVertex n2) const
|
||||
{
|
||||
boost::shared_ptr<Track> t1 (boost::dynamic_pointer_cast<Track>(r1));
|
||||
boost::shared_ptr<Track> t2 (boost::dynamic_pointer_cast<Track>(r2));
|
||||
PresentationInfo::order_t r1o = r1->presentation_info().order();
|
||||
PresentationInfo::order_t r2o = r2->presentation_info().order();
|
||||
boost::shared_ptr<Track> t1 (boost::dynamic_pointer_cast<Track>(n1));
|
||||
boost::shared_ptr<Track> t2 (boost::dynamic_pointer_cast<Track>(n2));
|
||||
boost::shared_ptr<Route> r1 (boost::dynamic_pointer_cast<Route>(n1));
|
||||
boost::shared_ptr<Route> r2 (boost::dynamic_pointer_cast<Route>(n2));
|
||||
|
||||
PresentationInfo::order_t r1o = r1 ? r1->presentation_info().order() : 0;
|
||||
PresentationInfo::order_t r2o = r2 ? r2->presentation_info().order() : 0;
|
||||
|
||||
if (!t1) {
|
||||
if (!t2) {
|
||||
|
@ -238,52 +277,70 @@ struct RouteRecEnabledComparator
|
|||
/** Perform a topological sort of a list of routes using a directed graph representing connections.
|
||||
* @return Sorted list of routes, or 0 if the graph contains cycles (feedback loops).
|
||||
*/
|
||||
boost::shared_ptr<RouteList>
|
||||
ARDOUR::topological_sort (
|
||||
boost::shared_ptr<RouteList> routes,
|
||||
GraphEdges edges
|
||||
)
|
||||
bool
|
||||
ARDOUR::topological_sort (GraphNodeList& nodes, GraphEdges& edges)
|
||||
{
|
||||
boost::shared_ptr<RouteList> sorted_routes (new RouteList);
|
||||
/* Collect the edges of the graph. Each of these edges
|
||||
* is a pair of nodes, one of which directly feeds the other
|
||||
* either by a port connection or by an internal send.
|
||||
*/
|
||||
|
||||
/* queue of routes to process */
|
||||
RouteList queue;
|
||||
for (auto const& i : nodes) {
|
||||
|
||||
for (auto const& j : nodes) {
|
||||
|
||||
bool via_sends_only = false;
|
||||
|
||||
/* See if this *j feeds *i according to the current state of
|
||||
* port 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GraphNodeList queue;
|
||||
|
||||
/* initial queue has routes that are not fed by anything */
|
||||
for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
|
||||
if (edges.has_none_to (*i)) {
|
||||
queue.push_back (*i);
|
||||
for (auto const& i : nodes) {
|
||||
if (edges.has_none_to (i)) {
|
||||
queue.push_back (i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Sort the initial queue so that non-rec-enabled routes are run first.
|
||||
This is so that routes can record things coming from other routes
|
||||
via external connections.
|
||||
*/
|
||||
* This is so that routes can record things coming from other routes
|
||||
* via external connections.
|
||||
*/
|
||||
queue.sort (RouteRecEnabledComparator ());
|
||||
|
||||
/* Do the sort: algorithm is Kahn's from Wikipedia.
|
||||
`Topological sorting of large networks', Communications of the ACM 5(11):558-562.
|
||||
*/
|
||||
* `Topological sorting of large networks', Communications of the ACM 5(11):558-562.
|
||||
*/
|
||||
|
||||
nodes.clear ();
|
||||
GraphEdges remaining_edges (edges);
|
||||
|
||||
while (!queue.empty ()) {
|
||||
GraphVertex r = queue.front ();
|
||||
queue.pop_front ();
|
||||
sorted_routes->push_back (r);
|
||||
set<GraphVertex> e = edges.from (r);
|
||||
nodes.push_back (r);
|
||||
set<GraphVertex> e = remaining_edges.from (r);
|
||||
for (set<GraphVertex>::iterator i = e.begin(); i != e.end(); ++i) {
|
||||
edges.remove (r, *i);
|
||||
if (edges.has_none_to (*i)) {
|
||||
remaining_edges.remove (r, *i);
|
||||
if (remaining_edges.has_none_to (*i)) {
|
||||
queue.push_back (*i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!edges.empty ()) {
|
||||
edges.dump ();
|
||||
if (!remaining_edges.empty ()) {
|
||||
remaining_edges.dump ();
|
||||
/* There are cycles in the graph, so we can't do a topological sort */
|
||||
return boost::shared_ptr<RouteList> ();
|
||||
return false;
|
||||
}
|
||||
|
||||
return sorted_routes;
|
||||
return true;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright (C) 2010-2011 David Robillard <d@drobilla.net>
|
||||
* Copyright (C) 2011 Carl Hetherington <carl@carlh.net>
|
||||
* Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
|
||||
* Copyright (C) 2017-2022 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -74,9 +74,3 @@ GraphNode::finish (int chain)
|
|||
_graph->reached_terminal_node ();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GraphNode::process ()
|
||||
{
|
||||
_graph->process_one_route (dynamic_cast<Route*> (this));
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
#include "ardour/disk_writer.h"
|
||||
#include "ardour/event_type_map.h"
|
||||
#include "ardour/gain_control.h"
|
||||
#include "ardour/graph.h"
|
||||
#include "ardour/internal_return.h"
|
||||
#include "ardour/internal_send.h"
|
||||
#include "ardour/meter.h"
|
||||
|
@ -697,6 +698,12 @@ Route::monitor_run (samplepos_t start_sample, samplepos_t end_sample, pframes_t
|
|||
run_route (start_sample, end_sample, nframes, true, false);
|
||||
}
|
||||
|
||||
void
|
||||
Route::process ()
|
||||
{
|
||||
_graph->process_one_route (this);
|
||||
}
|
||||
|
||||
void
|
||||
Route::run_route (samplepos_t start_sample, samplepos_t end_sample, pframes_t nframes, bool gain_automation_ok, bool run_disk_reader)
|
||||
{
|
||||
|
@ -780,10 +787,10 @@ void
|
|||
Route::push_solo_upstream (int delta)
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::Solo, string_compose("\t ... INVERT push from %1\n", _name));
|
||||
for (FedBy::iterator i = _fed_by.begin(); i != _fed_by.end(); ++i) {
|
||||
boost::shared_ptr<Route> sr (i->r.lock());
|
||||
if (sr) {
|
||||
sr->solo_control()->mod_solo_by_others_downstream (-delta);
|
||||
for (auto const& i : _session._current_route_graph.to (boost::dynamic_pointer_cast<Route> (shared_from_this ()))) {
|
||||
boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (i);
|
||||
if (r) {
|
||||
r->solo_control()->mod_solo_by_others_downstream (-delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3525,52 +3532,6 @@ Route::set_comment (string cmt, void *src)
|
|||
_session.set_dirty ();
|
||||
}
|
||||
|
||||
bool
|
||||
Route::add_fed_by (boost::shared_ptr<Route> other, bool via_sends_only)
|
||||
{
|
||||
FeedRecord fr (other, via_sends_only);
|
||||
|
||||
pair<FedBy::iterator,bool> result = _fed_by.insert (fr);
|
||||
|
||||
if (!result.second) {
|
||||
|
||||
/* already a record for "other" - make sure sends-only information is correct */
|
||||
if (!via_sends_only && result.first->sends_only) {
|
||||
FeedRecord* frp = const_cast<FeedRecord*>(&(*result.first));
|
||||
frp->sends_only = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result.second;
|
||||
}
|
||||
|
||||
void
|
||||
Route::clear_fed_by ()
|
||||
{
|
||||
_fed_by.clear ();
|
||||
}
|
||||
|
||||
bool
|
||||
Route::feeds (boost::shared_ptr<Route> other, bool* via_sends_only)
|
||||
{
|
||||
const FedBy& fed_by (other->fed_by());
|
||||
|
||||
for (FedBy::const_iterator f = fed_by.begin(); f != fed_by.end(); ++f) {
|
||||
boost::shared_ptr<Route> sr = f->r.lock();
|
||||
|
||||
if (sr && (sr.get() == this)) {
|
||||
|
||||
if (via_sends_only) {
|
||||
*via_sends_only = f->sends_only;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
IOVector
|
||||
Route::all_inputs () const
|
||||
{
|
||||
|
@ -3614,8 +3575,11 @@ Route::all_outputs () const
|
|||
}
|
||||
|
||||
bool
|
||||
Route::direct_feeds_according_to_reality (boost::shared_ptr<Route> other, bool* via_send_only)
|
||||
Route::direct_feeds_according_to_reality (boost::shared_ptr<GraphNode> node, bool* via_send_only)
|
||||
{
|
||||
boost::shared_ptr<Route> other (boost::dynamic_pointer_cast<Route> (node));
|
||||
assert (other);
|
||||
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose ("Feeds from %1 (-> %2)?\n", _name, other->name()));
|
||||
if (other->all_inputs().fed_by (_output)) {
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose ("\tdirect FEEDS to %1\n", other->name()));
|
||||
|
@ -3667,11 +3631,24 @@ Route::direct_feeds_according_to_graph (boost::shared_ptr<Route> other, bool* vi
|
|||
}
|
||||
|
||||
bool
|
||||
Route::feeds_according_to_graph (boost::shared_ptr<Route> other)
|
||||
Route::feeds (boost::shared_ptr<Route> other)
|
||||
{
|
||||
return _session._current_route_graph.feeds (boost::dynamic_pointer_cast<Route> (shared_from_this ()), other);
|
||||
}
|
||||
|
||||
std::set<boost::shared_ptr<Route>>
|
||||
Route::signal_sources (bool via_sends_only)
|
||||
{
|
||||
std::set<boost::shared_ptr<Route>> rv;
|
||||
for (auto const& i : _session._current_route_graph.to (boost::dynamic_pointer_cast<Route> (shared_from_this ()), via_sends_only)) {
|
||||
boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (i);
|
||||
if (r) {
|
||||
rv.insert (r);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool
|
||||
Route::output_effectively_connected () const
|
||||
{
|
||||
|
|
|
@ -101,7 +101,6 @@
|
|||
#include "ardour/region.h"
|
||||
#include "ardour/region_factory.h"
|
||||
#include "ardour/revision.h"
|
||||
#include "ardour/route_graph.h"
|
||||
#include "ardour/route_group.h"
|
||||
#include "ardour/rt_tasklist.h"
|
||||
#include "ardour/rt_safe_delete.h"
|
||||
|
@ -2134,56 +2133,6 @@ Session::set_block_size (pframes_t nframes)
|
|||
}
|
||||
|
||||
|
||||
static void
|
||||
trace_terminal (boost::shared_ptr<Route> r1, boost::shared_ptr<Route> rbase, bool sends_only)
|
||||
{
|
||||
boost::shared_ptr<Route> r2;
|
||||
|
||||
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 */
|
||||
|
||||
Route::FedBy existing (r1->fed_by());
|
||||
|
||||
/* for each route that feeds r1, recurse, marking it as feeding
|
||||
rbase as well.
|
||||
*/
|
||||
|
||||
for (Route::FedBy::iterator i = existing.begin(); i != existing.end(); ++i) {
|
||||
if (!(r2 = i->r.lock ())) {
|
||||
/* (*i) went away, ignore it */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* r2 is a route that feeds r1 which somehow feeds base. mark
|
||||
base as being fed by r2
|
||||
*/
|
||||
|
||||
rbase->add_fed_by (r2, i->sends_only || sends_only);
|
||||
|
||||
if (r2 != rbase) {
|
||||
|
||||
/* 2nd level feedback loop detection. if r1 feeds or is fed by r2,
|
||||
stop here.
|
||||
*/
|
||||
|
||||
if (r1->feeds (r2) && r2->feeds (r1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* now recurse, so that we can mark base as being fed by
|
||||
all routes that feed r2
|
||||
*/
|
||||
|
||||
trace_terminal (r2, rbase, i->sends_only || sends_only);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Session::resort_routes ()
|
||||
{
|
||||
|
@ -2212,22 +2161,16 @@ Session::resort_routes ()
|
|||
|
||||
#ifndef NDEBUG
|
||||
if (DEBUG_ENABLED(DEBUG::Graph)) {
|
||||
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));
|
||||
}
|
||||
DEBUG_TRACE (DEBUG::Graph, "---- Session::resort_routes ----\n");
|
||||
for (auto const& i : *routes.reader ()) {
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose ("%1 fed by ...\n", i->name()));
|
||||
for (auto const& f : i->signal_sources ()) {
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1\n", f->graph_node_name ()));
|
||||
}
|
||||
}
|
||||
DEBUG_TRACE (DEBUG::Graph, "---- EOF ----\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/** This is called whenever we need to rebuild the graph of how we will process
|
||||
|
@ -2238,91 +2181,61 @@ Session::resort_routes ()
|
|||
void
|
||||
Session::resort_routes_using (boost::shared_ptr<RouteList> r)
|
||||
{
|
||||
/* We are going to build a directed graph of our routes;
|
||||
this is where the edges of that graph are put.
|
||||
*/
|
||||
GraphNodeList gnl;
|
||||
for (auto const& rt : *r) {
|
||||
gnl.push_back (rt);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
bool via_sends_only = false;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
if (rechain_process_graph (gnl)) {
|
||||
r->clear ();
|
||||
for (auto const& nd : gnl) {
|
||||
r->push_back (boost::dynamic_pointer_cast<Route> (nd));
|
||||
}
|
||||
}
|
||||
|
||||
/* Attempt a topological sort of the route graph */
|
||||
boost::shared_ptr<RouteList> sorted_routes = topological_sort (r, edges);
|
||||
#ifndef NDEBUG
|
||||
DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n");
|
||||
for (auto const& i : *r) {
|
||||
DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 (presentation order %2)\n", i->name(), i->presentation_info().order()));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (sorted_routes) {
|
||||
bool
|
||||
Session::rechain_process_graph (GraphNodeList& g)
|
||||
{
|
||||
/* This may be called from the GUI thread (concurrrently with processing),
|
||||
* when a user adds/removes routes.
|
||||
*
|
||||
* Or it may be called from the engine when connections are changed.
|
||||
* In that case processing is blocked until the graph change is handled.
|
||||
*/
|
||||
GraphEdges edges;
|
||||
if (topological_sort (g, edges)) {
|
||||
/* 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.
|
||||
*/
|
||||
* use this new graph.
|
||||
*
|
||||
* Note: the process graph chain does not require a
|
||||
* topologically-sorted list, but hey ho.
|
||||
*/
|
||||
if (_process_graph) {
|
||||
_process_graph->rechain (sorted_routes, edges);
|
||||
_process_graph->rechain (g, 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, false);
|
||||
}
|
||||
|
||||
*r = *sorted_routes;
|
||||
|
||||
#ifndef NDEBUG
|
||||
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 presentation order %2\n", (*i)->name(), (*i)->presentation_info().order()));
|
||||
}
|
||||
#endif
|
||||
|
||||
SuccessfulGraphSort (); /* EMIT SIGNAL */
|
||||
|
||||
} 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 */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
FeedbackDetected (); /* EMIT SIGNAL */
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Find a route name starting with \a base, maybe followed by the
|
||||
|
@ -5412,6 +5325,10 @@ Session::graph_reordered (bool called_from_backend)
|
|||
|
||||
/* force all diskstreams to update their capture offset values to
|
||||
* reflect any changes in latencies within the graph.
|
||||
*
|
||||
* XXX: Is this required? When the graph-order callback
|
||||
* is initiated by the backend, it is always followed by
|
||||
* a latency callback.
|
||||
*/
|
||||
update_latency_compensation (true, called_from_backend);
|
||||
}
|
||||
|
|
|
@ -106,6 +106,7 @@ libardour_sources = [
|
|||
'globals.cc',
|
||||
'graph.cc',
|
||||
'graphnode.cc',
|
||||
'graph_edges.cc',
|
||||
'iec1ppmdsp.cc',
|
||||
'iec2ppmdsp.cc',
|
||||
'import.cc',
|
||||
|
@ -203,7 +204,6 @@ libardour_sources = [
|
|||
'return.cc',
|
||||
'reverse.cc',
|
||||
'route.cc',
|
||||
'route_graph.cc',
|
||||
'route_group.cc',
|
||||
'route_group_member.cc',
|
||||
'rb_effect.cc',
|
||||
|
|
Loading…
Reference in New Issue
Block a user