From 94a4f6b350391a7daba047c0bc4d75f6dfe3e791 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 6 Oct 2025 06:15:50 +0200 Subject: [PATCH] Implement restoring hardware<>hardware connections for internal backends --- libs/ardour/ardour/port_engine.h | 7 +++ libs/ardour/ardour/port_engine_shared.h | 3 ++ .../ardour/ardour/rc_configuration_vars.inc.h | 1 + libs/ardour/ardour/session.h | 2 + libs/ardour/port_engine_shared.cc | 48 +++++++++++++++++++ libs/ardour/session.cc | 3 ++ libs/ardour/session_state.cc | 27 +++++++++++ libs/ardour/session_transport.cc | 11 +++++ libs/backends/alsa/alsa_audiobackend.cc | 44 +++++++++++++++++ libs/backends/alsa/alsa_audiobackend.h | 4 ++ libs/backends/dummy/dummy_audiobackend.cc | 45 +++++++++++++++++ libs/backends/dummy/dummy_audiobackend.h | 4 ++ libs/backends/portaudio/portaudio_backend.cc | 45 +++++++++++++++++ libs/backends/portaudio/portaudio_backend.h | 4 ++ 14 files changed, 248 insertions(+) diff --git a/libs/ardour/ardour/port_engine.h b/libs/ardour/ardour/port_engine.h index f560ebb44d..3801be264f 100644 --- a/libs/ardour/ardour/port_engine.h +++ b/libs/ardour/ardour/port_engine.h @@ -501,6 +501,13 @@ public: */ virtual samplepos_t sample_time_at_cycle_start () = 0; + /* external connections (hardware <> hardware) + * for internal backends + */ + virtual XMLNode* get_state () const { return nullptr; } + virtual int set_state (XMLNode const&, int version) { return 0; } + virtual bool match_state (XMLNode const&, int version) { return false; } + protected: PortManager& manager; }; diff --git a/libs/ardour/ardour/port_engine_shared.h b/libs/ardour/ardour/port_engine_shared.h index adef59cca6..40a2b31cb2 100644 --- a/libs/ardour/ardour/port_engine_shared.h +++ b/libs/ardour/ardour/port_engine_shared.h @@ -245,6 +245,9 @@ protected: virtual BackendPort* port_factory (std::string const& name, ARDOUR::DataType dt, ARDOUR::PortFlags flags) = 0; + XMLNode* get_state () const; + int set_state (XMLNode const&, int version); + #ifndef NDEBUG void list_ports () const; #endif diff --git a/libs/ardour/ardour/rc_configuration_vars.inc.h b/libs/ardour/ardour/rc_configuration_vars.inc.h index 676218b47e..4eb7ab09e8 100644 --- a/libs/ardour/ardour/rc_configuration_vars.inc.h +++ b/libs/ardour/ardour/rc_configuration_vars.inc.h @@ -34,6 +34,7 @@ CONFIG_VARIABLE (Temporal::TimeDomain, preferred_time_domain, "preferred_time_do /* IO connection */ +CONFIG_VARIABLE (bool, restore_hardware_connections, "restore-hardware-connections", true) CONFIG_VARIABLE (bool, auto_connect_standard_busses, "auto-connect-standard-busses", true) /* this variable is used to indicate output mode in Waves Tracks: "Multi Out" == AutoConnectPhysical and "Stereo Out" == AutoConnectMaster diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 61a61a4902..7a271bbfc9 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -2084,6 +2084,8 @@ private: XMLNode* _bundle_xml_node; int load_bundles (XMLNode const &); + mutable XMLNode* _engine_state; + int backend_sync_callback (TransportState, samplepos_t); void process_rtop (SessionEvent*); diff --git a/libs/ardour/port_engine_shared.cc b/libs/ardour/port_engine_shared.cc index 8eedac42c2..e3d0c1c14e 100644 --- a/libs/ardour/port_engine_shared.cc +++ b/libs/ardour/port_engine_shared.cc @@ -839,6 +839,54 @@ PortEngineSharedImpl::process_connection_queue_locked (PortManager& mgr) _port_connection_queue.clear (); } +XMLNode* +PortEngineSharedImpl::get_state () const +{ + XMLNode* node (new XMLNode (X_("PortEngine"))); + for (auto const& port : _system_inputs) { + assert (port->is_physical () && port->is_terminal ()); + const std::set& connected_ports = port->get_connections (); + for (auto const& other : connected_ports) { + if (!other->is_physical () || !other->is_terminal ()) { + continue; + } + XMLNode* child = node->add_child (X_("HWConnection")); + child->set_property (X_("source"), port->name ()); + child->set_property (X_("sink"), other->name ()); + } + } + for (auto const& port : _system_midi_in) { + assert (port->is_physical () && port->is_terminal ()); + const std::set& connected_ports = port->get_connections (); + for (auto const& other : connected_ports) { + if (!other->is_physical () || !other->is_terminal ()) { + continue; + } + XMLNode* child = node->add_child (X_("HWConnection")); + child->set_property (X_("source"), port->name ()); + child->set_property (X_("sink"), other->name ()); + } + } + + return node; +} + +int +PortEngineSharedImpl::set_state (XMLNode const & node, int) +{ + assert (node.name() == X_("PortEngine")); + const XMLNodeList& children (node.children()); + for (auto const* c : children) { + std::string src; + std::string dst; + if (c->name() != X_("HWConnection") || !c->get_property (X_("source"), src) || !c->get_property (X_("sink"), dst)) { + continue; + } + connect (src, dst); + } + return 0; +} + #ifndef NDEBUG void PortEngineSharedImpl::list_ports () const diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index a86b3708e9..7646a13321 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -322,6 +322,7 @@ Session::Session (AudioEngine &eng, , no_questions_about_missing_files (false) , _bundles (new BundleList) , _bundle_xml_node (0) + , _engine_state (0) , _clicking (false) , _click_rec_only (false) , click_data (0) @@ -713,6 +714,8 @@ Session::destroy () /* clear state tree so that no references to objects are held any more */ + delete _engine_state; + delete state_tree; state_tree = 0; diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index a8b785852a..9f2b427486 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -1238,6 +1238,9 @@ Session::state (bool save_template, snapshot_t snapshot_type, bool for_archive, node->set_property ("name", _name); node->set_property ("sample-rate", _base_sample_rate); + if (!_engine_state) { + _engine_state = new XMLNode (X_("EngineState")); + } /* store the last engine device we we can avoid autostarting on a different device with wrong i/o count */ std::shared_ptr backend = _engine.current_backend(); if (!for_archive && _engine.running () && backend && _engine.setup_required ()) { @@ -1250,7 +1253,21 @@ Session::state (bool save_template, snapshot_t snapshot_type, bool for_archive, child->set_property ("input-device", backend->device_name ()); child->set_property ("output-device", backend->device_name ()); } + /* store port-engine external connections */ + XMLNode* backend_state = backend->get_state(); + if (backend_state) { + XMLNode* engine_state = new XMLNode (X_("EngineState")); + for (auto const& s : _engine_state->children ()) { + if (!backend->match_state (*s, CURRENT_SESSION_FILE_VERSION)) { + engine_state->add_child_copy (*s); + } + } + engine_state->add_child_nocopy (*backend_state); + delete _engine_state; + _engine_state = engine_state; + } } + node->add_child_copy (*_engine_state); if (session_dirs.size() > 1) { @@ -1846,6 +1863,16 @@ Session::set_state (const XMLNode& node, int version) _midi_ports->set_midi_port_states (child->children()); } + if ((child = find_named_node (node, "EngineState")) != 0) { + _engine_state = new XMLNode (*child); + if (Config->get_restore_hardware_connections ()) { + std::shared_ptr backend = _engine.current_backend(); + for (auto const& s: _engine_state->children ()) { + backend->set_state (*s, version); + } + } + } + Stateful::save_extra_xml (node); if (((child = find_named_node (node, "Options")) != 0)) { /* old style */ diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index b3d39c920c..b82bd982db 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -1909,6 +1909,17 @@ Session::engine_running () { _transport_fsm->start (); reset_xrun_count (); + + if (_engine_state && Config->get_restore_hardware_connections ()) { + /* Note this restores the connections from the most recent [pending] save. + * Which may or may not be idendical to the ones used before the engine + * re-started. + */ + std::shared_ptr backend = _engine.current_backend(); + for (auto const& s: _engine_state->children ()) { + backend->set_state (*s, Stateful::loading_state_version); + } + } } void diff --git a/libs/backends/alsa/alsa_audiobackend.cc b/libs/backends/alsa/alsa_audiobackend.cc index db51c75502..f5e325a02b 100644 --- a/libs/backends/alsa/alsa_audiobackend.cc +++ b/libs/backends/alsa/alsa_audiobackend.cc @@ -2435,3 +2435,47 @@ AlsaDeviceReservation::reservation_stdout (std::string d, size_t /* s */) _reservation_succeeded = true; } } + +/* ****************************************************************************/ + +XMLNode* +AlsaAudioBackend::get_state () const { + XMLNode* node = PortEngineSharedImpl::get_state (); + node->set_property ("backend", name ()); + node->set_property ("driver", driver_name ()); + node->set_property ("input-device", input_device_name ()); + node->set_property ("output-device", output_device_name ()); + return node; +} + +int +AlsaAudioBackend::set_state (XMLNode const& node, int version) +{ + if (match_state (node, version)) { + return PortEngineSharedImpl::set_state (node, version); + } + return -1; +} + +bool +AlsaAudioBackend::match_state (XMLNode const& node, int version) +{ + if (node.name() != X_("PortEngine")) { + return false; + } + std::string val; + + if (!node.get_property ("backend", val) || val != name ()) { + return false; + } + if (!node.get_property ("driver", val) || val != driver_name ()) { + return false; + } + if (!node.get_property ("input-device", val) || val != input_device_name ()) { + return false; + } + if (!node.get_property ("output-device", val) || val != output_device_name ()) { + return false; + } + return true; +} diff --git a/libs/backends/alsa/alsa_audiobackend.h b/libs/backends/alsa/alsa_audiobackend.h index 1b593cf879..322bff1663 100644 --- a/libs/backends/alsa/alsa_audiobackend.h +++ b/libs/backends/alsa/alsa_audiobackend.h @@ -245,6 +245,10 @@ class AlsaAudioBackend : public AudioBackend, public PortEngineSharedImpl bool physically_connected (PortEngine::PortHandle ph, bool process_callback_safe) { return PortEngineSharedImpl::physically_connected (ph, process_callback_safe); } int get_connections (PortEngine::PortHandle ph, std::vector& results, bool process_callback_safe) { return PortEngineSharedImpl::get_connections (ph, results, process_callback_safe); } + XMLNode* get_state () const; + int set_state (XMLNode const& node, int version); + bool match_state (XMLNode const&, int version); + /* MIDI */ int midi_event_get (pframes_t& timestamp, size_t& size, uint8_t const** buf, void* port_buffer, uint32_t event_index); int midi_event_put (void* port_buffer, pframes_t timestamp, const uint8_t* buffer, size_t size); diff --git a/libs/backends/dummy/dummy_audiobackend.cc b/libs/backends/dummy/dummy_audiobackend.cc index 60b30b6460..a7a1dd033b 100644 --- a/libs/backends/dummy/dummy_audiobackend.cc +++ b/libs/backends/dummy/dummy_audiobackend.cc @@ -1916,3 +1916,48 @@ DummyMidiEvent::DummyMidiEvent (const DummyMidiEvent& other) DummyMidiEvent::~DummyMidiEvent () { free (_data); }; + + +/* ****************************************************************************/ + +XMLNode* +DummyAudioBackend::get_state () const { + XMLNode* node = PortEngineSharedImpl::get_state (); + node->set_property ("backend", name ()); + node->set_property ("driver", driver_name ()); + node->set_property ("device", device_name ()); + node->set_property ("instance", s_instance_name); + return node; +} + +int +DummyAudioBackend::set_state (XMLNode const& node, int version) +{ + if (match_state (node, version)) { + return PortEngineSharedImpl::set_state (node, version); + } + return -1; +} + +bool +DummyAudioBackend::match_state (XMLNode const& node, int version) +{ + if (node.name() != X_("PortEngine")) { + return false; + } + std::string val; + + if (!node.get_property ("backend", val) || val != name ()) { + return false; + } + if (!node.get_property ("driver", val) || val != driver_name ()) { + return false; + } + if (!node.get_property ("device", val) || val != device_name ()) { + return false; + } + if (!node.get_property ("instance", val) || val != s_instance_name) { + return false; + } + return true; +} diff --git a/libs/backends/dummy/dummy_audiobackend.h b/libs/backends/dummy/dummy_audiobackend.h index bde876e87c..610f05ef3d 100644 --- a/libs/backends/dummy/dummy_audiobackend.h +++ b/libs/backends/dummy/dummy_audiobackend.h @@ -306,6 +306,10 @@ class DummyAudioBackend : public AudioBackend, public PortEngineSharedImpl bool physically_connected (PortEngine::PortHandle ph, bool process_callback_safe) { return PortEngineSharedImpl::physically_connected (ph, process_callback_safe); } int get_connections (PortEngine::PortHandle ph, std::vector& results, bool process_callback_safe) { return PortEngineSharedImpl::get_connections (ph, results, process_callback_safe); } + XMLNode* get_state () const; + int set_state (XMLNode const&, int); + bool match_state (XMLNode const&, int version); + /* MIDI */ int midi_event_get (pframes_t& timestamp, size_t& size, uint8_t const** buf, void* port_buffer, uint32_t event_index); diff --git a/libs/backends/portaudio/portaudio_backend.cc b/libs/backends/portaudio/portaudio_backend.cc index b1feb1271d..c7ec7c9944 100644 --- a/libs/backends/portaudio/portaudio_backend.cc +++ b/libs/backends/portaudio/portaudio_backend.cc @@ -1949,3 +1949,48 @@ PortMidiEvent::PortMidiEvent (const PortMidiEvent& other) memcpy (_data, other._data, other._size); } }; + + +/* ****************************************************************************/ + +XMLNode* +PortAudioBackend::get_state () const { + XMLNode& node = PortEngineSharedImpl::get_state (); + node.set_property ("backend", name ()); + node.set_property ("driver", driver_name ()); + node.set_property ("input-device", input_device_name ()); + node.set_property ("output-device", output_device_name ()); + return node; +} + +int +PortAudioBackend::set_state (XMLNode const& node, int version) +{ + if (match_state (node, version)) { + return PortEngineSharedImpl::set_state (node, version); + } + return -1; +} + +bool +PortAudioBackend::match_state (XMLNode const& node, int version) +{ + if (node.name() != X_("PortEngine")) { + return false; + } + std::string val; + + if (!node.get_property ("backend", val) || val != name ()) { + return false; + } + if (!node.get_property ("driver", val) || val != driver_name ()) { + return false; + } + if (!node.get_property ("input-device", val) || val != input_device_name ()) { + return false; + } + if (!node.get_property ("output-device", val) || val != output_device_name ()) { + return false; + } + return true; +} diff --git a/libs/backends/portaudio/portaudio_backend.h b/libs/backends/portaudio/portaudio_backend.h index 4c82e87a76..f520d0b46a 100644 --- a/libs/backends/portaudio/portaudio_backend.h +++ b/libs/backends/portaudio/portaudio_backend.h @@ -220,6 +220,10 @@ class PortAudioBackend : public AudioBackend, public PortEngineSharedImpl { bool physically_connected (PortEngine::PortHandle ph, bool process_callback_safe) { return PortEngineSharedImpl::physically_connected (ph, process_callback_safe); } int get_connections (PortEngine::PortHandle ph, std::vector& results, bool process_callback_safe) { return PortEngineSharedImpl::get_connections (ph, results, process_callback_safe); } + XMLNode* get_state () const; + int set_state (XMLNode const& node, int version); + bool match_state (XMLNode const&, int version); + /* MIDI */ int midi_event_get (pframes_t& timestamp, size_t& size, uint8_t const** buf, void* port_buffer, uint32_t event_index); int midi_event_put (void* port_buffer, pframes_t timestamp, const uint8_t* buffer, size_t size);