diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2020-04-02 14:26:08 -0600 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2020-04-02 19:45:31 -0600 |
commit | 9461a30a97d83e21e253b152417b554bfbfc1989 (patch) | |
tree | 6db2c56a10840d3a445269b9c97a88fe5f159849 /libs/ardour/port_engine_shared.cc | |
parent | 9ac0d5b60f34682166954a5e70f52754ec4af671 (diff) |
refactor shared PortEngine implementation from AlsaAudioBackend
Diffstat (limited to 'libs/ardour/port_engine_shared.cc')
-rw-r--r-- | libs/ardour/port_engine_shared.cc | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/libs/ardour/port_engine_shared.cc b/libs/ardour/port_engine_shared.cc new file mode 100644 index 0000000000..2e236c769e --- /dev/null +++ b/libs/ardour/port_engine_shared.cc @@ -0,0 +1,652 @@ +/* + * Copyright (C) 2014-2015 Tim Mayberry <mojofunk@gmail.com> + * Copyright (C) 2014-2020 Paul Davis <paul@linuxaudiosystems.com> + * Copyright (C) 2014-2019 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 + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <regex.h> + +#include "pbd/error.h" +#include "pbd/i18n.h" + +#include "ardour/port_engine_shared.h" + +using namespace ARDOUR; + +BackendPort::BackendPort (PortEngineSharedImpl &b, const std::string& name, PortFlags flags) + : _backend (b) + , _name (name) + , _flags (flags) +{ + _capture_latency_range.min = 0; + _capture_latency_range.max = 0; + _playback_latency_range.min = 0; + _playback_latency_range.max = 0; +} + +BackendPort::~BackendPort () { + disconnect_all (); +} + +int BackendPort::connect (BackendPort *port) +{ + if (!port) { + PBD::error << _("BackendPort::connect (): invalid (null) port") << endmsg; + return -1; + } + + if (type () != port->type ()) { + PBD::error << _("BackendPort::connect (): wrong port-type") << endmsg; + return -1; + } + + if (is_output () && port->is_output ()) { + PBD::error << _("BackendPort::connect (): cannot inter-connect output ports.") << endmsg; + return -1; + } + + if (is_input () && port->is_input ()) { + PBD::error << _("BackendPort::connect (): cannot inter-connect input ports.") << endmsg; + return -1; + } + + if (this == port) { + PBD::error << _("BackendPort::connect (): cannot self-connect ports.") << endmsg; + return -1; + } + + if (is_connected (port)) { +#if 0 // don't bother to warn about this for now. just ignore it + PBD::error << _("BackendPort::connect (): ports are already connected:") + << " (" << name () << ") -> (" << port->name () << ")" + << endmsg; +#endif + return -1; + } + + _connect (port, true); + return 0; +} + +void BackendPort::_connect (BackendPort *port, bool callback) +{ + _connections.insert (port); + if (callback) { + port->_connect (this, false); + _backend.port_connect_callback (name(), port->name(), true); + } +} + +int BackendPort::disconnect (BackendPort *port) +{ + if (!port) { + PBD::error << _("BackendPort::disconnect (): invalid (null) port") << endmsg; + return -1; + } + + if (!is_connected (port)) { + PBD::error << _("BackendPort::disconnect (): ports are not connected:") + << " (" << name () << ") -> (" << port->name () << ")" + << endmsg; + return -1; + } + _disconnect (port, true); + return 0; +} + +void BackendPort::_disconnect (BackendPort *port, bool callback) +{ + std::set<BackendPort*>::iterator it = _connections.find (port); + assert (it != _connections.end ()); + _connections.erase (it); + if (callback) { + port->_disconnect (this, false); + _backend.port_connect_callback (name(), port->name(), false); + } +} + + +void BackendPort::disconnect_all () +{ + while (!_connections.empty ()) { + std::set<BackendPort*>::iterator it = _connections.begin (); + (*it)->_disconnect (this, false); + _backend.port_connect_callback (name(), (*it)->name(), false); + _connections.erase (it); + } +} + +bool +BackendPort::is_connected (const BackendPort *port) const +{ + return _connections.find (const_cast<BackendPort *>(port)) != _connections.end (); +} + +bool BackendPort::is_physically_connected () const +{ + for (std::set<BackendPort*>::const_iterator it = _connections.begin (); it != _connections.end (); ++it) { + if ((*it)->is_physical ()) { + return true; + } + } + return false; +} + +void +BackendPort::set_latency_range (const LatencyRange &latency_range, bool for_playback) +{ + if (for_playback) { + _playback_latency_range = latency_range; + } else { + _capture_latency_range = latency_range; + } + + for (std::set<BackendPort*>::const_iterator it = _connections.begin (); it != _connections.end (); ++it) { + if ((*it)->is_physical ()) { + (*it)->update_connected_latency (is_input ()); + } + } +} + +void +BackendPort::update_connected_latency (bool for_playback) +{ + LatencyRange lr; + lr.min = lr.max = 0; + for (std::set<BackendPort*>::const_iterator it = _connections.begin (); it != _connections.end (); ++it) { + LatencyRange l; + l = (*it)->latency_range (for_playback); + lr.min = std::max (lr.min, l.min); + lr.max = std::max (lr.max, l.max); + } + set_latency_range (lr, for_playback); +} + + + +PortEngineSharedImpl::PortEngineSharedImpl (PortManager& mgr, std::string const & str) + : _instance_name (str) + , _portmap (new PortMap) + , _ports (new PortIndex) +{ +} + +PortEngineSharedImpl::~PortEngineSharedImpl () +{ +} + +int +PortEngineSharedImpl::get_ports ( + const std::string& port_name_pattern, + DataType type, PortFlags flags, + std::vector<std::string>& port_names) const +{ + int rv = 0; + regex_t port_regex; + bool use_regexp = false; + if (port_name_pattern.size () > 0) { + if (!regcomp (&port_regex, port_name_pattern.c_str (), REG_EXTENDED|REG_NOSUB)) { + use_regexp = true; + } + } + + boost::shared_ptr<PortIndex> p = _ports.reader (); + + for (PortIndex::const_iterator i = p->begin (); i != p->end (); ++i) { + BackendPort* port = *i; + if ((port->type () == type) && flags == (port->flags () & flags)) { + if (!use_regexp || !regexec (&port_regex, port->name ().c_str (), 0, NULL, 0)) { + port_names.push_back (port->name ()); + ++rv; + } + } + } + if (use_regexp) { + regfree (&port_regex); + } + return rv; +} + +/* Discovering physical ports */ + +bool +PortEngineSharedImpl::port_is_physical (PortEngine::PortHandle port) const +{ + if (!valid_port (port)) { + PBD::error << _("BackendPort::port_is_physical (): invalid port.") << endmsg; + return false; + } + return static_cast<BackendPort*>(port)->is_physical (); +} + +void +PortEngineSharedImpl::get_physical_outputs (DataType type, std::vector<std::string>& port_names) +{ + boost::shared_ptr<PortIndex> p = _ports.reader(); + + for (PortIndex::iterator i = p->begin (); i != p->end (); ++i) { + BackendPort* port = *i; + if ((port->type () == type) && port->is_input () && port->is_physical ()) { + port_names.push_back (port->name ()); + } + } +} + +void +PortEngineSharedImpl::get_physical_inputs (DataType type, std::vector<std::string>& port_names) +{ + boost::shared_ptr<PortIndex> p = _ports.reader(); + + for (PortIndex::iterator i = p->begin (); i != p->end (); ++i) { + BackendPort* port = *i; + if ((port->type () == type) && port->is_output () && port->is_physical ()) { + port_names.push_back (port->name ()); + } + } +} + +ChanCount +PortEngineSharedImpl::n_physical_outputs () const +{ + int n_midi = 0; + int n_audio = 0; + + boost::shared_ptr<PortIndex> p = _ports.reader(); + + for (PortIndex::const_iterator i = p->begin (); i != p->end (); ++i) { + BackendPort* port = *i; + if (port->is_output () && port->is_physical ()) { + switch (port->type ()) { + case DataType::AUDIO: ++n_audio; break; + case DataType::MIDI: ++n_midi; break; + default: break; + } + } + } + ChanCount cc; + cc.set (DataType::AUDIO, n_audio); + cc.set (DataType::MIDI, n_midi); + return cc; +} + +ChanCount +PortEngineSharedImpl::n_physical_inputs () const +{ + int n_midi = 0; + int n_audio = 0; + + boost::shared_ptr<PortIndex> p = _ports.reader(); + + for (PortIndex::const_iterator i = p->begin (); i != p->end (); ++i) { + BackendPort* port = *i; + if (port->is_input () && port->is_physical ()) { + switch (port->type ()) { + case DataType::AUDIO: ++n_audio; break; + case DataType::MIDI: ++n_midi; break; + default: break; + } + } + } + ChanCount cc; + cc.set (DataType::AUDIO, n_audio); + cc.set (DataType::MIDI, n_midi); + return cc; +} + +PortEngine::PortHandle +PortEngineSharedImpl::add_port ( + const std::string& name, + ARDOUR::DataType type, + ARDOUR::PortFlags flags) +{ + assert(name.size ()); + if (find_port (name)) { + PBD::error << _("AlsaBackend::register_port: Port already exists:") + << " (" << name << ")" << endmsg; + return 0; + } + + BackendPort* port = port_factory (name, type, flags); + + if (!port) { + return 0; + } + + { + RCUWriter<PortIndex> index_writer (_ports); + RCUWriter<PortMap> map_writer (_portmap); + + boost::shared_ptr<PortIndex> ps = index_writer.get_copy (); + boost::shared_ptr<PortMap> pm = map_writer.get_copy (); + + ps->insert (port); + pm->insert (make_pair (name, port)); + } + + return port; +} + +void +PortEngineSharedImpl::unregister_port (PortEngine::PortHandle port_handle) +{ + BackendPort* port = static_cast<BackendPort*>(port_handle); + + { + RCUWriter<PortIndex> index_writer (_ports); + RCUWriter<PortMap> map_writer (_portmap); + + boost::shared_ptr<PortIndex> ps = index_writer.get_copy (); + boost::shared_ptr<PortMap> pm = map_writer.get_copy (); + + PortIndex::iterator i = std::find (ps->begin(), ps->end(), static_cast<BackendPort*>(port_handle)); + + if (i == ps->end ()) { + PBD::error << _("AlsaBackend::unregister_port: Failed to find port") << endmsg; + return; + } + + disconnect_all(port_handle); + + pm->erase (port->name()); + ps->erase (i); + } + + delete port; +} + + +void +PortEngineSharedImpl::unregister_ports (bool system_only) +{ + _system_inputs.clear(); + _system_outputs.clear(); + _system_midi_in.clear(); + _system_midi_out.clear(); + + RCUWriter<PortIndex> index_writer (_ports); + RCUWriter<PortMap> map_writer (_portmap); + + boost::shared_ptr<PortIndex> ps = index_writer.get_copy (); + boost::shared_ptr<PortMap> pm = map_writer.get_copy (); + + + for (PortIndex::iterator i = ps->begin (); i != ps->end ();) { + PortIndex::iterator cur = i++; + BackendPort* port = *cur; + if (! system_only || (port->is_physical () && port->is_terminal ())) { + port->disconnect_all (); + pm->erase (port->name()); + delete port; + ps->erase (cur); + } + } +} + +void +PortEngineSharedImpl::clear_ports () +{ + RCUWriter<PortIndex> index_writer (_ports); + RCUWriter<PortMap> map_writer (_portmap); + + boost::shared_ptr<PortIndex> ps = index_writer.get_copy(); + boost::shared_ptr<PortMap> pm = map_writer.get_copy (); + + if (ps->size () || pm->size ()) { + PBD::warning << _("PortEngineSharedImpl: recovering from unclean shutdown, port registry is not empty.") << endmsg; + _system_inputs.clear(); + _system_outputs.clear(); + _system_midi_in.clear(); + _system_midi_out.clear(); + ps->clear(); + pm->clear(); + } +} + +uint32_t +PortEngineSharedImpl::port_name_size () const +{ + return 256; +} + +int +PortEngineSharedImpl::set_port_name (PortEngine::PortHandle port, const std::string& name) +{ + std::string newname (_instance_name + ":" + name); + + if (!valid_port (port)) { + PBD::error << _("AlsaBackend::set_port_name: Invalid Port") << endmsg; + return -1; + } + + if (find_port (newname)) { + PBD::error << _("AlsaBackend::set_port_name: Port with given name already exists") << endmsg; + return -1; + } + + BackendPort* p = static_cast<BackendPort*>(port); + + { + RCUWriter<PortMap> map_writer (_portmap); + boost::shared_ptr<PortMap> pm = map_writer.get_copy (); + + pm->erase (p->name()); + pm->insert (make_pair (newname, p)); + } + + return p->set_name (newname); +} + +std::string +PortEngineSharedImpl::get_port_name (PortEngine::PortHandle port) const +{ + if (!valid_port (port)) { + PBD::warning << _("AlsaBackend::get_port_name: Invalid Port(s)") << endmsg; + return std::string (); + } + return static_cast<BackendPort*>(port)->name (); +} + +PortFlags +PortEngineSharedImpl::get_port_flags (PortEngine::PortHandle port) const +{ + if (!valid_port (port)) { + PBD::warning << _("AlsaBackend::get_port_flags: Invalid Port(s)") << endmsg; + return PortFlags (0); + } + return static_cast<BackendPort*>(port)->flags (); +} + +int +PortEngineSharedImpl::get_port_property (PortEngine::PortHandle port, const std::string& key, std::string& value, std::string& type) const +{ + if (!valid_port (port)) { + PBD::warning << _("AlsaBackend::get_port_property: Invalid Port(s)") << endmsg; + return -1; + } + if (key == "http://jackaudio.org/metadata/pretty-name") { + type = ""; + value = static_cast<BackendPort*>(port)->pretty_name (); + if (!value.empty()) { + return 0; + } + } + return -1; +} + +int +PortEngineSharedImpl::set_port_property (PortEngine::PortHandle port, const std::string& key, const std::string& value, const std::string& type) +{ + if (!valid_port (port)) { + PBD::warning << _("AlsaBackend::set_port_property: Invalid Port(s)") << endmsg; + return -1; + } + if (key == "http://jackaudio.org/metadata/pretty-name" && type.empty ()) { + static_cast<BackendPort*>(port)->set_pretty_name (value); + return 0; + } + return -1; +} + +PortEngine::PortHandle +PortEngineSharedImpl::get_port_by_name (const std::string& name) const +{ + PortEngine::PortHandle port = (PortEngine::PortHandle) find_port (name); + return port; +} + +DataType +PortEngineSharedImpl::port_data_type (PortEngine::PortHandle port) const +{ + if (!valid_port (port)) { + return DataType::NIL; + } + return static_cast<BackendPort*>(port)->type (); +} + +PortEngine::PortHandle +PortEngineSharedImpl::register_port ( + const std::string& name, + ARDOUR::DataType type, + ARDOUR::PortFlags flags) +{ + if (name.size () == 0) { return 0; } + if (flags & IsPhysical) { return 0; } + return add_port (_instance_name + ":" + name, type, flags); +} + + +int +PortEngineSharedImpl::connect (const std::string& src, const std::string& dst) +{ + BackendPort* src_port = find_port (src); + BackendPort* dst_port = find_port (dst); + + if (!src_port) { + PBD::error << _("AlsaBackend::connect: Invalid Source port:") + << " (" << src <<")" << endmsg; + return -1; + } + if (!dst_port) { + PBD::error << _("AlsaBackend::connect: Invalid Destination port:") + << " (" << dst <<")" << endmsg; + return -1; + } + return src_port->connect (dst_port); +} + +int +PortEngineSharedImpl::disconnect (const std::string& src, const std::string& dst) +{ + BackendPort* src_port = find_port (src); + BackendPort* dst_port = find_port (dst); + + if (!src_port || !dst_port) { + PBD::error << _("AlsaBackend::disconnect: Invalid Port(s)") << endmsg; + return -1; + } + return src_port->disconnect (dst_port); +} + +int +PortEngineSharedImpl::connect (PortEngine::PortHandle src, const std::string& dst) +{ + BackendPort* dst_port = find_port (dst); + if (!valid_port (src)) { + PBD::error << _("AlsaBackend::connect: Invalid Source Port Handle") << endmsg; + return -1; + } + if (!dst_port) { + PBD::error << _("AlsaBackend::connect: Invalid Destination Port") + << " (" << dst << ")" << endmsg; + return -1; + } + return static_cast<BackendPort*>(src)->connect (dst_port); +} + +int +PortEngineSharedImpl::disconnect (PortEngine::PortHandle src, const std::string& dst) +{ + BackendPort* dst_port = find_port (dst); + if (!valid_port (src) || !dst_port) { + PBD::error << _("AlsaBackend::disconnect: Invalid Port(s)") << endmsg; + return -1; + } + return static_cast<BackendPort*>(src)->disconnect (dst_port); +} + +int +PortEngineSharedImpl::disconnect_all (PortEngine::PortHandle port) +{ + if (!valid_port (port)) { + PBD::error << _("AlsaBackend::disconnect_all: Invalid Port") << endmsg; + return -1; + } + static_cast<BackendPort*>(port)->disconnect_all (); + return 0; +} + +bool +PortEngineSharedImpl::connected (PortEngine::PortHandle port, bool /* process_callback_safe*/) +{ + if (!valid_port (port)) { + PBD::error << _("AlsaBackend::disconnect_all: Invalid Port") << endmsg; + return false; + } + return static_cast<BackendPort*>(port)->is_connected (); +} + +bool +PortEngineSharedImpl::connected_to (PortEngine::PortHandle src, const std::string& dst, bool /*process_callback_safe*/) +{ + BackendPort* dst_port = find_port (dst); +#ifndef NDEBUG + if (!valid_port (src) || !dst_port) { + PBD::error << _("AlsaBackend::connected_to: Invalid Port") << endmsg; + return false; + } +#endif + return static_cast<BackendPort*>(src)->is_connected (dst_port); +} + +bool +PortEngineSharedImpl::physically_connected (PortEngine::PortHandle port, bool /*process_callback_safe*/) +{ + if (!valid_port (port)) { + PBD::error << _("AlsaBackend::physically_connected: Invalid Port") << endmsg; + return false; + } + return static_cast<BackendPort*>(port)->is_physically_connected (); +} + +int +PortEngineSharedImpl::get_connections (PortEngine::PortHandle port, std::vector<std::string>& names, bool /*process_callback_safe*/) +{ + if (!valid_port (port)) { + PBD::error << _("AlsaBackend::get_connections: Invalid Port") << endmsg; + return -1; + } + + assert (0 == names.size ()); + + const std::set<BackendPort*>& connected_ports = static_cast<BackendPort*>(port)->get_connections (); + + for (std::set<BackendPort*>::const_iterator i = connected_ports.begin (); i != connected_ports.end (); ++i) { + names.push_back ((*i)->name ()); + } + + return (int)names.size (); +} |