mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-31 02:47:43 +01:00
new file
This commit is contained in:
parent
9ac6bb9bef
commit
2af6491c1c
1 changed files with 475 additions and 0 deletions
475
libs/ardour/port_manager.cc
Normal file
475
libs/ardour/port_manager.cc
Normal file
|
|
@ -0,0 +1,475 @@
|
|||
/*
|
||||
Copyright (C) 2013 Paul Davis
|
||||
|
||||
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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include "ardour/port_manager.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
|
||||
PortManager::PortManager ()
|
||||
, ports (new Ports)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
AudioEngine::remove_all_ports ()
|
||||
{
|
||||
/* make sure that JACK callbacks that will be invoked as we cleanup
|
||||
* ports know that they have nothing to do.
|
||||
*/
|
||||
|
||||
port_remove_in_progress = true;
|
||||
|
||||
/* process lock MUST be held by caller
|
||||
*/
|
||||
|
||||
{
|
||||
RCUWriter<Ports> writer (ports);
|
||||
boost::shared_ptr<Ports> ps = writer.get_copy ();
|
||||
ps->clear ();
|
||||
}
|
||||
|
||||
/* clear dead wood list in RCU */
|
||||
|
||||
ports.flush ();
|
||||
|
||||
port_remove_in_progress = false;
|
||||
}
|
||||
|
||||
|
||||
string
|
||||
AudioEngine::make_port_name_relative (const string& portname) const
|
||||
{
|
||||
string::size_type len;
|
||||
string::size_type n;
|
||||
|
||||
len = portname.length();
|
||||
|
||||
for (n = 0; n < len; ++n) {
|
||||
if (portname[n] == ':') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((n != len) && (portname.substr (0, n) == jack_client_name)) {
|
||||
return portname.substr (n+1);
|
||||
}
|
||||
|
||||
return portname;
|
||||
}
|
||||
|
||||
string
|
||||
AudioEngine::make_port_name_non_relative (const string& portname) const
|
||||
{
|
||||
string str;
|
||||
|
||||
if (portname.find_first_of (':') != string::npos) {
|
||||
return portname;
|
||||
}
|
||||
|
||||
str = jack_client_name;
|
||||
str += ':';
|
||||
str += portname;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioEngine::port_is_mine (const string& portname) const
|
||||
{
|
||||
if (portname.find_first_of (':') != string::npos) {
|
||||
if (portname.substr (0, jack_client_name.length ()) != jack_client_name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioEngine::port_is_physical (const std::string& portname) const
|
||||
{
|
||||
GET_PRIVATE_JACK_POINTER_RET(_jack, false);
|
||||
|
||||
jack_port_t *port = jack_port_by_name (_priv_jack, portname.c_str());
|
||||
|
||||
if (!port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return jack_port_flags (port) & JackPortIsPhysical;
|
||||
}
|
||||
|
||||
ChanCount
|
||||
AudioEngine::n_physical (unsigned long flags) const
|
||||
{
|
||||
ChanCount c;
|
||||
|
||||
GET_PRIVATE_JACK_POINTER_RET (_jack, c);
|
||||
|
||||
const char ** ports = jack_get_ports (_priv_jack, NULL, NULL, JackPortIsPhysical | flags);
|
||||
if (ports == 0) {
|
||||
return c;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; ports[i]; ++i) {
|
||||
if (!strstr (ports[i], "Midi-Through")) {
|
||||
DataType t (jack_port_type (jack_port_by_name (_jack, ports[i])));
|
||||
c.set (t, c.get (t) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
free (ports);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
ChanCount
|
||||
AudioEngine::n_physical_inputs () const
|
||||
{
|
||||
return n_physical (JackPortIsInput);
|
||||
}
|
||||
|
||||
ChanCount
|
||||
AudioEngine::n_physical_outputs () const
|
||||
{
|
||||
return n_physical (JackPortIsOutput);
|
||||
}
|
||||
|
||||
void
|
||||
AudioEngine::get_physical (DataType type, unsigned long flags, vector<string>& phy)
|
||||
{
|
||||
GET_PRIVATE_JACK_POINTER (_jack);
|
||||
const char ** ports;
|
||||
|
||||
if ((ports = jack_get_ports (_priv_jack, NULL, type.to_jack_type(), JackPortIsPhysical | flags)) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ports) {
|
||||
for (uint32_t i = 0; ports[i]; ++i) {
|
||||
if (strstr (ports[i], "Midi-Through")) {
|
||||
continue;
|
||||
}
|
||||
phy.push_back (ports[i]);
|
||||
}
|
||||
free (ports);
|
||||
}
|
||||
}
|
||||
|
||||
/** Get physical ports for which JackPortIsOutput is set; ie those that correspond to
|
||||
* a physical input connector.
|
||||
*/
|
||||
void
|
||||
AudioEngine::get_physical_inputs (DataType type, vector<string>& ins)
|
||||
{
|
||||
get_physical (type, JackPortIsOutput, ins);
|
||||
}
|
||||
|
||||
/** Get physical ports for which JackPortIsInput is set; ie those that correspond to
|
||||
* a physical output connector.
|
||||
*/
|
||||
void
|
||||
AudioEngine::get_physical_outputs (DataType type, vector<string>& outs)
|
||||
{
|
||||
get_physical (type, JackPortIsInput, outs);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AudioEngine::can_request_hardware_monitoring ()
|
||||
{
|
||||
GET_PRIVATE_JACK_POINTER_RET (_jack,false);
|
||||
const char ** ports;
|
||||
|
||||
if ((ports = jack_get_ports (_priv_jack, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortCanMonitor)) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
free (ports);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** @param name Full or short name of port
|
||||
* @return Corresponding Port or 0.
|
||||
*/
|
||||
|
||||
boost::shared_ptr<Port>
|
||||
AudioEngine::get_port_by_name (const string& portname)
|
||||
{
|
||||
if (!_running) {
|
||||
if (!_has_run) {
|
||||
fatal << _("get_port_by_name() called before engine was started") << endmsg;
|
||||
/*NOTREACHED*/
|
||||
} else {
|
||||
boost::shared_ptr<Port> ();
|
||||
}
|
||||
}
|
||||
|
||||
if (!port_is_mine (portname)) {
|
||||
/* not an ardour port */
|
||||
return boost::shared_ptr<Port> ();
|
||||
}
|
||||
|
||||
boost::shared_ptr<Ports> pr = ports.reader();
|
||||
std::string rel = make_port_name_relative (portname);
|
||||
Ports::iterator x = pr->find (rel);
|
||||
|
||||
if (x != pr->end()) {
|
||||
/* its possible that the port was renamed by some 3rd party and
|
||||
we don't know about it. check for this (the check is quick
|
||||
and cheap), and if so, rename the port (which will alter
|
||||
the port map as a side effect).
|
||||
*/
|
||||
const std::string check = make_port_name_relative (jack_port_name (x->second->jack_port()));
|
||||
if (check != rel) {
|
||||
x->second->set_name (check);
|
||||
}
|
||||
return x->second;
|
||||
}
|
||||
|
||||
return boost::shared_ptr<Port> ();
|
||||
}
|
||||
|
||||
void
|
||||
AudioEngine::port_renamed (const std::string& old_relative_name, const std::string& new_relative_name)
|
||||
{
|
||||
RCUWriter<Ports> writer (ports);
|
||||
boost::shared_ptr<Ports> p = writer.get_copy();
|
||||
Ports::iterator x = p->find (old_relative_name);
|
||||
|
||||
if (x != p->end()) {
|
||||
boost::shared_ptr<Port> port = x->second;
|
||||
p->erase (x);
|
||||
p->insert (make_pair (new_relative_name, port));
|
||||
}
|
||||
}
|
||||
|
||||
const char **
|
||||
AudioEngine::get_ports (const string& port_name_pattern, const string& type_name_pattern, uint32_t flags)
|
||||
{
|
||||
GET_PRIVATE_JACK_POINTER_RET (_jack,0);
|
||||
if (!_running) {
|
||||
if (!_has_run) {
|
||||
fatal << _("get_ports called before engine was started") << endmsg;
|
||||
/*NOTREACHED*/
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return jack_get_ports (_priv_jack, port_name_pattern.c_str(), type_name_pattern.c_str(), flags);
|
||||
}
|
||||
|
||||
void
|
||||
AudioEngine::port_registration_failure (const std::string& portname)
|
||||
{
|
||||
GET_PRIVATE_JACK_POINTER (_jack);
|
||||
string full_portname = jack_client_name;
|
||||
full_portname += ':';
|
||||
full_portname += portname;
|
||||
|
||||
|
||||
jack_port_t* p = jack_port_by_name (_priv_jack, full_portname.c_str());
|
||||
string reason;
|
||||
|
||||
if (p) {
|
||||
reason = string_compose (_("a port with the name \"%1\" already exists: check for duplicated track/bus names"), portname);
|
||||
} else {
|
||||
reason = string_compose (_("No more JACK ports are available. You will need to stop %1 and restart JACK with more ports if you need this many tracks."), PROGRAM_NAME);
|
||||
}
|
||||
|
||||
throw PortRegistrationFailure (string_compose (_("AudioEngine: cannot register port \"%1\": %2"), portname, reason).c_str());
|
||||
}
|
||||
|
||||
boost::shared_ptr<Port>
|
||||
AudioEngine::register_port (DataType dtype, const string& portname, bool input)
|
||||
{
|
||||
boost::shared_ptr<Port> newport;
|
||||
|
||||
try {
|
||||
if (dtype == DataType::AUDIO) {
|
||||
newport.reset (new AudioPort (portname, (input ? Port::IsInput : Port::IsOutput)));
|
||||
} else if (dtype == DataType::MIDI) {
|
||||
newport.reset (new MidiPort (portname, (input ? Port::IsInput : Port::IsOutput)));
|
||||
} else {
|
||||
throw PortRegistrationFailure("unable to create port (unknown type)");
|
||||
}
|
||||
|
||||
RCUWriter<Ports> writer (ports);
|
||||
boost::shared_ptr<Ports> ps = writer.get_copy ();
|
||||
ps->insert (make_pair (make_port_name_relative (portname), newport));
|
||||
|
||||
/* writer goes out of scope, forces update */
|
||||
|
||||
return newport;
|
||||
}
|
||||
|
||||
catch (PortRegistrationFailure& err) {
|
||||
throw err;
|
||||
} catch (std::exception& e) {
|
||||
throw PortRegistrationFailure(string_compose(
|
||||
_("unable to create port: %1"), e.what()).c_str());
|
||||
} catch (...) {
|
||||
throw PortRegistrationFailure("unable to create port (unknown error)");
|
||||
}
|
||||
}
|
||||
|
||||
boost::shared_ptr<Port>
|
||||
AudioEngine::register_input_port (DataType type, const string& portname)
|
||||
{
|
||||
return register_port (type, portname, true);
|
||||
}
|
||||
|
||||
boost::shared_ptr<Port>
|
||||
AudioEngine::register_output_port (DataType type, const string& portname)
|
||||
{
|
||||
return register_port (type, portname, false);
|
||||
}
|
||||
|
||||
int
|
||||
AudioEngine::unregister_port (boost::shared_ptr<Port> port)
|
||||
{
|
||||
/* caller must hold process lock */
|
||||
|
||||
if (!_running) {
|
||||
/* probably happening when the engine has been halted by JACK,
|
||||
in which case, there is nothing we can do here.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
RCUWriter<Ports> writer (ports);
|
||||
boost::shared_ptr<Ports> ps = writer.get_copy ();
|
||||
Ports::iterator x = ps->find (make_port_name_relative (port->name()));
|
||||
|
||||
if (x != ps->end()) {
|
||||
ps->erase (x);
|
||||
}
|
||||
|
||||
/* writer goes out of scope, forces update */
|
||||
}
|
||||
|
||||
ports.flush ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
PortManager::connected (const string& port_name)
|
||||
{
|
||||
PortEngine::PortHandle handle = _impl->get_port_by_name (port_name);
|
||||
|
||||
if (!handle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _impl->connected (handle);
|
||||
}
|
||||
|
||||
int
|
||||
AudioEngine::connect (const string& source, const string& destination)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!_running) {
|
||||
if (!_has_run) {
|
||||
fatal << _("connect called before engine was started") << endmsg;
|
||||
/*NOTREACHED*/
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
string s = make_port_name_non_relative (source);
|
||||
string d = make_port_name_non_relative (destination);
|
||||
|
||||
|
||||
boost::shared_ptr<Port> src = get_port_by_name (s);
|
||||
boost::shared_ptr<Port> dst = get_port_by_name (d);
|
||||
|
||||
if (src) {
|
||||
ret = src->connect (d);
|
||||
} else if (dst) {
|
||||
ret = dst->connect (s);
|
||||
} else {
|
||||
/* neither port is known to us, and this API isn't intended for use as a general patch bay */
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
if (ret > 0) {
|
||||
/* already exists - no error, no warning */
|
||||
} else if (ret < 0) {
|
||||
error << string_compose(_("AudioEngine: cannot connect %1 (%2) to %3 (%4)"),
|
||||
source, s, destination, d)
|
||||
<< endmsg;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
AudioEngine::disconnect (const string& source, const string& destination)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!_running) {
|
||||
if (!_has_run) {
|
||||
fatal << _("disconnect called before engine was started") << endmsg;
|
||||
/*NOTREACHED*/
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
string s = make_port_name_non_relative (source);
|
||||
string d = make_port_name_non_relative (destination);
|
||||
|
||||
boost::shared_ptr<Port> src = get_port_by_name (s);
|
||||
boost::shared_ptr<Port> dst = get_port_by_name (d);
|
||||
|
||||
if (src) {
|
||||
ret = src->disconnect (d);
|
||||
} else if (dst) {
|
||||
ret = dst->disconnect (s);
|
||||
} else {
|
||||
/* neither port is known to us, and this API isn't intended for use as a general patch bay */
|
||||
ret = -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
AudioEngine::disconnect (boost::shared_ptr<Port> port)
|
||||
{
|
||||
GET_PRIVATE_JACK_POINTER_RET (_jack,-1);
|
||||
|
||||
if (!_running) {
|
||||
if (!_has_run) {
|
||||
fatal << _("disconnect called before engine was started") << endmsg;
|
||||
/*NOTREACHED*/
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return port->disconnect_all ();
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue