From aaf3013211a34ec382b4b9be74dbcd77b91131df Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 24 May 2023 02:43:07 +0200 Subject: [PATCH] Save I/O connections per device (#9344) --- libs/ardour/ardour/port.h | 26 ++++-- libs/ardour/io.cc | 29 +----- libs/ardour/port.cc | 185 ++++++++++++++++++++++++++++++-------- 3 files changed, 168 insertions(+), 72 deletions(-) diff --git a/libs/ardour/ardour/port.h b/libs/ardour/ardour/port.h index c0e38ce580..9fea39bf20 100644 --- a/libs/ardour/ardour/port.h +++ b/libs/ardour/ardour/port.h @@ -134,11 +134,11 @@ public: uint32_t externally_connected () const { return _externally_connected; } uint32_t internally_connected () const { return _internally_connected; } - void increment_external_connections() { _externally_connected++; } - void decrement_external_connections() { if (_externally_connected) _externally_connected--; } + void increment_external_connections (); + void decrement_external_connections (); - void increment_internal_connections() { _internally_connected++; } - void decrement_internal_connections() { if (_internally_connected) _internally_connected--; } + void increment_internal_connections (); + void decrement_internal_connections (); PBD::Signal1 MonitorInputChanged; @@ -201,15 +201,23 @@ private: uint32_t _externally_connected; uint32_t _internally_connected; - /** ports that we are connected to, kept so that we can - reconnect to the backend when required - */ - std::set _connections; + typedef std::set ConnectionSet; + /* ports that we are connected to, kept so that we can + * reconnect to the backend when required + */ + mutable Glib::Threads::RWLock _connections_lock; + ConnectionSet _int_connections; + std::map _ext_connections; static uint32_t _resampler_quality; // 8 <= q <= 96 static uint32_t _resampler_latency; // = _resampler_quality - 1 - void port_connected_or_disconnected (std::weak_ptr, std::weak_ptr, bool); + void port_connected_or_disconnected (std::weak_ptr, std::string, std::weak_ptr, std::string, bool); + + int connect_internal (std::string const &); + void insert_connection (std::string const&); + void erase_connection (std::string const&); + void signal_drop (); void session_global_drop (); void drop (); diff --git a/libs/ardour/io.cc b/libs/ardour/io.cc index a193e33b32..c02f6eff49 100644 --- a/libs/ardour/io.cc +++ b/libs/ardour/io.cc @@ -548,34 +548,7 @@ IO::state () const } for (PortSet::const_iterator i = _ports.begin(); i != _ports.end(); ++i) { - - vector connections; - - XMLNode* pnode = new XMLNode (X_("Port")); - pnode->set_property (X_("type"), i->type()); - pnode->set_property (X_("name"), i->name()); - - if (i->get_connections (connections)) { - vector::const_iterator ci; - std::sort (connections.begin(), connections.end()); - - for (n = 0, ci = connections.begin(); ci != connections.end(); ++ci, ++n) { - - /* if its a connection to our own port, - return only the port name, not the - whole thing. this allows connections - to be re-established even when our - client name is different. - */ - - XMLNode* cnode = new XMLNode (X_("Connection")); - - cnode->set_property (X_("other"), _session.engine().make_port_name_relative (*ci)); - pnode->add_child_nocopy (*cnode); - } - } - - node->add_child_nocopy (*pnode); + node->add_child_nocopy (i->get_state ()); } return *node; diff --git a/libs/ardour/port.cc b/libs/ardour/port.cc index a8c8955a0c..0af0323f01 100644 --- a/libs/ardour/port.cc +++ b/libs/ardour/port.cc @@ -91,7 +91,7 @@ Port::Port (std::string const & n, DataType t, PortFlags f) PortDrop.connect_same_thread (drop_connection, boost::bind (&Port::session_global_drop, this)); PortSignalDrop.connect_same_thread (drop_connection, boost::bind (&Port::signal_drop, this)); - port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5)); + port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _2, _3, _4, _5)); } /** Port destructor */ @@ -160,7 +160,7 @@ Port::drop () } void -Port::port_connected_or_disconnected (std::weak_ptr w0, std::weak_ptr w1, bool con) +Port::port_connected_or_disconnected (std::weak_ptr w0, std::string n1, std::weak_ptr w1, std::string n2, bool con) { std::shared_ptr p0 = w0.lock (); std::shared_ptr p1 = w1.lock (); @@ -168,13 +168,88 @@ Port::port_connected_or_disconnected (std::weak_ptr w0, std::weak_ptr pself = AudioEngine::instance()->get_port_by_name (name()); if (p0 == pself) { + if (con) { + insert_connection (n2); + } else { + erase_connection (n2); + } ConnectedOrDisconnected (p0, p1, con); // emit signal } if (p1 == pself) { + if (con) { + insert_connection (n1); + } else { + erase_connection (n1); + } ConnectedOrDisconnected (p1, p0, con); // emit signal } } +void +Port::insert_connection (std::string const& pn) +{ +#if 1 // include external JACK clients + if (!AudioEngine::instance()->port_is_mine (pn)) +#else + if (port_manager->port_is_physical (pn)) +#endif + { + std::string const bid (AudioEngine::instance()->backend_id (receives_input ())); + Glib::Threads::RWLock::WriterLock lm (_connections_lock); + _ext_connections[bid].insert (pn); + _int_connections.erase (pn); // XXX + } else { + Glib::Threads::RWLock::WriterLock lm (_connections_lock); + _int_connections.insert (pn); + } +} + +void +Port::erase_connection (std::string const& pn) +{ +#if 1 // include external JACK clients + if (!AudioEngine::instance()->port_is_mine (pn)) +#else + if (port_manager->port_is_physical (pn)) +#endif + { + std::string const bid (AudioEngine::instance()->backend_id (receives_input ())); + Glib::Threads::RWLock::WriterLock lm (_connections_lock); + _ext_connections[bid].erase (pn); + } else { + Glib::Threads::RWLock::WriterLock lm (_connections_lock); + _int_connections.erase (pn); + } +} + +void +Port::increment_external_connections () +{ + _externally_connected++; +} + +void +Port::decrement_external_connections () +{ + if (_externally_connected) { + _externally_connected--; + } +} + +void +Port::increment_internal_connections () +{ + _internally_connected++; +} + +void +Port::decrement_internal_connections () +{ + if (_internally_connected) { + _internally_connected--; + } +} + /** @return true if this port is connected to anything */ bool Port::connected () const @@ -194,7 +269,12 @@ Port::disconnect_all () get_connections (connections); port_engine.disconnect_all (_port_handle); - _connections.clear (); + { + std::string const bid (AudioEngine::instance()->backend_id (receives_input ())); + Glib::Threads::RWLock::WriterLock lm (_connections_lock); + _int_connections.clear (); + _ext_connections[bid].clear (); + } /* a cheaper, less hacky way to do boost::shared_from_this() ... */ @@ -202,7 +282,7 @@ Port::disconnect_all () for (vector::const_iterator c = connections.begin(); c != connections.end() && pself; ++c) { std::shared_ptr pother = AudioEngine::instance()->get_port_by_name (*c); if (pother) { - pother->_connections.erase (_name); + pother->erase_connection (_name); ConnectedOrDisconnected (pself, pother, false); // emit signal } } @@ -229,11 +309,17 @@ Port::connected_to (std::string const & o) const } int -Port::get_connections (std::vector & c) const +Port::get_connections (std::vector& c) const { if (!port_manager->running()) { - c.insert (c.end(), _connections.begin(), _connections.end()); - return c.size(); + std::string const bid (AudioEngine::instance()->backend_id (receives_input ())); + Glib::Threads::RWLock::ReaderLock lm (_connections_lock); + c.insert (c.end(), _int_connections.begin(), _int_connections.end()); + try { + c.insert (c.end(), _ext_connections.at(bid).begin(), _ext_connections.at(bid).end()); + } catch (std::out_of_range&) { + } + return c.size (); } if (_port_handle) { @@ -244,7 +330,7 @@ Port::get_connections (std::vector & c) const } int -Port::connect (std::string const & other) +Port::connect_internal (std::string const & other) { std::string const other_name = AudioEngine::instance()->make_port_name_non_relative (other); std::string const our_name = AudioEngine::instance()->make_port_name_non_relative (_name); @@ -262,6 +348,13 @@ Port::connect (std::string const & other) DEBUG_TRACE (DEBUG::Ports, string_compose ("Connect %1 to %2\n", other_name, our_name)); r = port_engine.connect (other_name, our_name); } + return r; +} + +int +Port::connect (std::string const& other) +{ + int r = connect_internal (other); if (r == 0) { /* Connections can be saved on either or both sides. The code above works regardless @@ -274,11 +367,11 @@ Port::connect (std::string const & other) * * This is also nicer when reading the session file's . */ - _connections.insert (other); + insert_connection (other); std::shared_ptr pother = AudioEngine::instance()->get_port_by_name (other); if (pother) { - pother->_connections.insert (_name); + pother->insert_connection (_name); } } @@ -300,7 +393,7 @@ Port::disconnect (std::string const & other) } if (r == 0) { - _connections.erase (other); + erase_connection (other); } /* a cheaper, less hacky way to do boost::shared_from_this() ... */ @@ -308,7 +401,7 @@ Port::disconnect (std::string const & other) std::shared_ptr pother = AudioEngine::instance()->get_port_by_name (other); if (r == 0 && pother) { - pother->_connections.erase (_name); + pother->erase_connection (_name); } if (pself && pother) { @@ -601,7 +694,7 @@ Port::reestablish () reset (); - port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5)); + port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _2, _3, _4, _5)); return 0; } @@ -611,22 +704,36 @@ Port::reconnect () { /* caller must hold process lock; intended to be used only after reestablish() */ - if (_connections.empty ()) { + std::string const bid (AudioEngine::instance()->backend_id (receives_input ())); + + Glib::Threads::RWLock::WriterLock lm (_connections_lock); + + if (_int_connections.empty () && _ext_connections[bid].empty ()) { return 0; /* OK */ } - DEBUG_TRACE (DEBUG::Ports, string_compose ("Port::reconnect() Connect %1 to %2 destinations\n",name(), _connections.size())); + DEBUG_TRACE (DEBUG::Ports, string_compose ("Port::reconnect() Connect %1 to %2 destinations\n",name(), _int_connections.size() + _ext_connections[bid].size())); int count = 0; - std::set::iterator i = _connections.begin(); - while (i != _connections.end()) { - std::set::iterator current = i++; - if (connect (*current)) { + ConnectionSet::iterator i = _int_connections.begin(); + while (i != _int_connections.end()) { + ConnectionSet::iterator current = i++; + if (connect_internal (*current)) { DEBUG_TRACE (DEBUG::Ports, string_compose ("Port::reconnect() failed to connect %1 to %2\n", name(), (*current))); - _connections.erase (current); + _int_connections.erase (current); + } else { + ++count; } - else { + } + + i = _ext_connections[bid].begin(); + while (i != _ext_connections[bid].end()) { + ConnectionSet::iterator current = i++; + if (connect_internal (*current)) { + DEBUG_TRACE (DEBUG::Ports, string_compose ("Port::reconnect() failed to connect %1 to %2\n", name(), (*current))); + _ext_connections[bid].erase (current); + } else { ++count; } } @@ -669,23 +776,30 @@ Port::get_state () const XMLNode* root = new XMLNode (state_node_name); root->set_property (X_("name"), AudioEngine::instance()->make_port_name_relative (name())); + root->set_property (X_("type"), type ()); if (receives_input()) { - root->set_property (X_("direction"), X_("input")); + root->set_property (X_("direction"), X_("Input")); } else { - root->set_property (X_("direction"), X_("output")); + root->set_property (X_("direction"), X_("Output")); } - vector c; - - get_connections (c); - - for (vector::const_iterator i = c.begin(); i != c.end(); ++i) { + Glib::Threads::RWLock::ReaderLock lm (_connections_lock); + for (auto const& c : _int_connections) { XMLNode* child = new XMLNode (X_("Connection")); - child->set_property (X_("other"), *i); + child->set_property (X_("other"), AudioEngine::instance()->make_port_name_relative (c)); root->add_child_nocopy (*child); } + for (auto const& hwc : _ext_connections) { + for (auto const& c : hwc.second) { + XMLNode* child = new XMLNode (X_("ExtConnection")); + child->set_property (X_("for"), hwc.first); + child->set_property (X_("other"), c); + root->add_child_nocopy (*child); + } + } + return *root; } @@ -703,19 +817,20 @@ Port::set_state (const XMLNode& node, int) const XMLNodeList& children (node.children()); - _connections.clear (); + _int_connections.clear (); + _ext_connections.clear (); for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { - if ((*c)->name() != X_("Connection")) { + if ((*c)->name() == X_("Connection") && (*c)->get_property (X_("other"), str)) { + _int_connections.insert (AudioEngine::instance()->make_port_name_non_relative (str)); continue; } - if (!(*c)->get_property (X_("other"), str)) { - continue; + std::string hw; + if ((*c)->name() == X_("ExtConnection") && (*c)->get_property (X_("for"), hw) && (*c)->get_property (X_("other"), str)) { + _ext_connections[hw].insert (str); } - - _connections.insert (str); } return 0;