Implement restoring hardware<>hardware connections for internal backends

This commit is contained in:
Robin Gareus 2025-10-06 06:15:50 +02:00
parent 4a51f4f350
commit 94a4f6b350
No known key found for this signature in database
GPG key ID: A090BCE02CF57F04
14 changed files with 248 additions and 0 deletions

View file

@ -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;
};

View file

@ -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

View file

@ -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

View file

@ -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*);

View file

@ -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<BackendPortPtr>& 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<BackendPortPtr>& 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

View file

@ -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;

View file

@ -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<AudioBackend> 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<AudioBackend> 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 */

View file

@ -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<AudioBackend> backend = _engine.current_backend();
for (auto const& s: _engine_state->children ()) {
backend->set_state (*s, Stateful::loading_state_version);
}
}
}
void

View file

@ -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;
}

View file

@ -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<std::string>& 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);

View file

@ -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;
}

View file

@ -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<std::string>& 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);

View file

@ -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;
}

View file

@ -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<std::string>& 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);