/* 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 "pbd/convert.h" #include "pbd/error.h" #include "ardour/async_midi_port.h" #include "ardour/audio_backend.h" #include "ardour/audio_port.h" #include "ardour/debug.h" #include "ardour/midi_port.h" #include "ardour/midiport_manager.h" #include "ardour/port_manager.h" #include "ardour/profile.h" #include "i18n.h" using namespace ARDOUR; using namespace PBD; using std::string; using std::vector; PortManager::PortManager () : ports (new Ports) , _port_remove_in_progress (false) { } void PortManager::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 writer (ports); boost::shared_ptr ps = writer.get_copy (); ps->clear (); } /* clear dead wood list in RCU */ ports.flush (); _port_remove_in_progress = false; } string PortManager::make_port_name_relative (const string& portname) const { if (!_backend) { return portname; } string::size_type len; string::size_type n; string self = _backend->my_name(); len = portname.length(); for (n = 0; n < len; ++n) { if (portname[n] == ':') { break; } } if ((n != len) && (portname.substr (0, n) == self)) { return portname.substr (n+1); } return portname; } string PortManager::make_port_name_non_relative (const string& portname) const { string str; if (portname.find_first_of (':') != string::npos) { return portname; } str = _backend->my_name(); str += ':'; str += portname; return str; } std::string PortManager::get_pretty_name_by_name(const std::string& portname) const { PortEngine::PortHandle ph = _backend->get_port_by_name (portname); if (ph) { std::string value; std::string type; if (0 == _backend->get_port_property (ph, "http://jackaudio.org/metadata/pretty-name", value, type)) { return value; } } return ""; } bool PortManager::port_is_mine (const string& portname) const { if (!_backend) { return true; } string self = _backend->my_name(); if (portname.find_first_of (':') != string::npos) { if (portname.substr (0, self.length ()) != self) { return false; } } return true; } bool PortManager::port_is_physical (const std::string& portname) const { if (!_backend) { return false; } PortEngine::PortHandle ph = _backend->get_port_by_name (portname); if (!ph) { return false; } return _backend->port_is_physical (ph); } void PortManager::get_physical_outputs (DataType type, std::vector& s) { if (!_backend) { return; } _backend->get_physical_outputs (type, s); } void PortManager::get_physical_inputs (DataType type, std::vector& s) { if (!_backend) { return; } _backend->get_physical_inputs (type, s); } ChanCount PortManager::n_physical_outputs () const { if (!_backend) { return ChanCount::ZERO; } return _backend->n_physical_outputs (); } ChanCount PortManager::n_physical_inputs () const { if (!_backend) { return ChanCount::ZERO; } return _backend->n_physical_inputs (); } bool PortManager::port_name_prefix_is_unique (const string& first_part_of_port_name) const { if (!_backend) { return boost::shared_ptr(); } boost::shared_ptr pr = ports.reader(); const string::size_type len = first_part_of_port_name.length(); for (Ports::const_iterator x = pr->begin(); x != pr->end(); ++x) { string prefix = x->first.substr (0, len); if (strings_equal_ignore_case (prefix, first_part_of_port_name)) { return false; } } return true; } /** @param name Full or short name of port * @return Corresponding Port or 0. */ boost::shared_ptr PortManager::get_port_by_name (const string& portname) { if (!_backend) { return boost::shared_ptr(); } if (!port_is_mine (portname)) { /* not an ardour port */ return boost::shared_ptr (); } boost::shared_ptr 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 (_backend->get_port_name (x->second->port_handle())); if (check != rel) { x->second->set_name (check); } return x->second; } return boost::shared_ptr (); } void PortManager::port_renamed (const std::string& old_relative_name, const std::string& new_relative_name) { RCUWriter writer (ports); boost::shared_ptr p = writer.get_copy(); Ports::iterator x = p->find (old_relative_name); if (x != p->end()) { boost::shared_ptr port = x->second; p->erase (x); p->insert (make_pair (new_relative_name, port)); } } int PortManager::get_ports (DataType type, PortList& pl) { boost::shared_ptr plist = ports.reader(); for (Ports::iterator p = plist->begin(); p != plist->end(); ++p) { if (p->second->type() == type) { pl.push_back (p->second); } } return pl.size(); } int PortManager::get_ports (const string& port_name_pattern, DataType type, PortFlags flags, vector& s) { s.clear(); if (!_backend) { return 0; } return _backend->get_ports (port_name_pattern, type, flags, s); } void PortManager::port_registration_failure (const std::string& portname) { if (!_backend) { return; } string full_portname = _backend->my_name(); full_portname += ':'; full_portname += portname; PortEngine::PortHandle p = _backend->get_port_by_name (full_portname); 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 ports are available. You will need to stop %1 and restart 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 PortManager::register_port (DataType dtype, const string& portname, bool input, bool async) { boost::shared_ptr newport; try { if (dtype == DataType::AUDIO) { DEBUG_TRACE (DEBUG::Ports, string_compose ("registering AUDIO port %1, input %2\n", portname, input)); newport.reset (new AudioPort (portname, (input ? IsInput : IsOutput))); } else if (dtype == DataType::MIDI) { if (async) { DEBUG_TRACE (DEBUG::Ports, string_compose ("registering ASYNC MIDI port %1, input %2\n", portname, input)); newport.reset (new AsyncMIDIPort (portname, (input ? IsInput : IsOutput))); } else { DEBUG_TRACE (DEBUG::Ports, string_compose ("registering MIDI port %1, input %2\n", portname, input)); newport.reset (new MidiPort (portname, (input ? IsInput : IsOutput))); } } else { throw PortRegistrationFailure("unable to create port (unknown type)"); } RCUWriter writer (ports); boost::shared_ptr ps = writer.get_copy (); ps->insert (make_pair (make_port_name_relative (portname), newport)); /* writer goes out of scope, forces update */ } 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)"); } DEBUG_TRACE (DEBUG::Ports, string_compose ("\t%2 port registration success, ports now = %1\n", ports.reader()->size(), this)); return newport; } boost::shared_ptr PortManager::register_input_port (DataType type, const string& portname, bool async) { return register_port (type, portname, true, async); } boost::shared_ptr PortManager::register_output_port (DataType type, const string& portname, bool async) { return register_port (type, portname, false, async); } int PortManager::unregister_port (boost::shared_ptr port) { /* caller must hold process lock */ { RCUWriter writer (ports); boost::shared_ptr 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) { if (!_backend) { return false; } PortEngine::PortHandle handle = _backend->get_port_by_name (port_name); if (!handle) { return false; } return _backend->connected (handle); } int PortManager::connect (const string& source, const string& destination) { int ret; string s = make_port_name_non_relative (source); string d = make_port_name_non_relative (destination); boost::shared_ptr src = get_port_by_name (s); boost::shared_ptr 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 ...hand-off to the PortEngine */ if (_backend) { ret = _backend->connect (s, d); } else { 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 PortManager::disconnect (const string& source, const string& destination) { int ret; string s = make_port_name_non_relative (source); string d = make_port_name_non_relative (destination); boost::shared_ptr src = get_port_by_name (s); boost::shared_ptr 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 ...hand-off to the PortEngine */ if (_backend) { ret = _backend->disconnect (s, d); } else { ret = -1; } } return ret; } int PortManager::disconnect (boost::shared_ptr port) { return port->disconnect_all (); } int PortManager::reestablish_ports () { Ports::iterator i; boost::shared_ptr p = ports.reader (); DEBUG_TRACE (DEBUG::Ports, string_compose ("reestablish %1 ports\n", p->size())); for (i = p->begin(); i != p->end(); ++i) { if (i->second->reestablish ()) { error << string_compose (_("Re-establising port %1 failed"), i->second->name()) << endmsg; std::cerr << string_compose (_("Re-establising port %1 failed"), i->second->name()) << std::endl; break; } } if (i != p->end()) { /* failed */ remove_all_ports (); return -1; } return 0; } int PortManager::reconnect_ports () { boost::shared_ptr p = ports.reader (); if (!Profile->get_trx()) { /* re-establish connections */ DEBUG_TRACE (DEBUG::Ports, string_compose ("reconnect %1 ports\n", p->size())); for (Ports::iterator i = p->begin(); i != p->end(); ++i) { i->second->reconnect (); } } return 0; } void PortManager::connect_callback (const string& a, const string& b, bool conn) { boost::shared_ptr port_a; boost::shared_ptr port_b; Ports::iterator x; boost::shared_ptr pr = ports.reader (); x = pr->find (make_port_name_relative (a)); if (x != pr->end()) { port_a = x->second; } x = pr->find (make_port_name_relative (b)); if (x != pr->end()) { port_b = x->second; } PortConnectedOrDisconnected ( port_a, a, port_b, b, conn ); /* EMIT SIGNAL */ } void PortManager::registration_callback () { if (!_port_remove_in_progress) { PortRegisteredOrUnregistered (); /* EMIT SIGNAL */ } } bool PortManager::can_request_input_monitoring () const { if (!_backend) { return false; } return _backend->can_monitor_input (); } void PortManager::request_input_monitoring (const string& name, bool yn) const { if (!_backend) { return; } PortEngine::PortHandle ph = _backend->get_port_by_name (name); if (ph) { _backend->request_input_monitoring (ph, yn); } } void PortManager::ensure_input_monitoring (const string& name, bool yn) const { if (!_backend) { return; } PortEngine::PortHandle ph = _backend->get_port_by_name (name); if (ph) { _backend->ensure_input_monitoring (ph, yn); } } uint32_t PortManager::port_name_size() const { if (!_backend) { return 0; } return _backend->port_name_size (); } string PortManager::my_name() const { if (!_backend) { return string(); } return _backend->my_name(); } int PortManager::graph_order_callback () { if (!_port_remove_in_progress) { GraphReordered(); /* EMIT SIGNAL */ } return 0; } void PortManager::cycle_start (pframes_t nframes) { Port::set_global_port_buffer_offset (0); Port::set_cycle_framecnt (nframes); _cycle_ports = ports.reader (); for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { p->second->cycle_start (nframes); } } void PortManager::cycle_end (pframes_t nframes) { for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { p->second->cycle_end (nframes); } for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { p->second->flush_buffers (nframes); } _cycle_ports.reset (); /* we are done */ } void PortManager::silence (pframes_t nframes) { for (Ports::iterator i = _cycle_ports->begin(); i != _cycle_ports->end(); ++i) { if (i->second->sends_output()) { i->second->get_buffer(nframes).silence(nframes); } } } void PortManager::silence_outputs (pframes_t nframes) { std::vector port_names; if (get_ports("", DataType::AUDIO, IsOutput, port_names)) { for (std::vector::iterator p = port_names.begin(); p != port_names.end(); ++p) { if (!port_is_mine(*p)) { continue; } PortEngine::PortHandle ph = _backend->get_port_by_name (*p); if (!ph) { continue; } void *buf = _backend->get_buffer(ph, nframes); if (!buf) { continue; } memset (buf, 0, sizeof(float) * nframes); } } if (get_ports("", DataType::MIDI, IsOutput, port_names)) { for (std::vector::iterator p = port_names.begin(); p != port_names.end(); ++p) { if (!port_is_mine(*p)) { continue; } PortEngine::PortHandle ph = _backend->get_port_by_name (*p); if (!ph) { continue; } void *buf = _backend->get_buffer(ph, nframes); if (!buf) { continue; } _backend->midi_clear (buf); } } } void PortManager::check_monitoring () { for (Ports::iterator i = _cycle_ports->begin(); i != _cycle_ports->end(); ++i) { bool x; if (i->second->last_monitor() != (x = i->second->monitoring_input ())) { i->second->set_last_monitor (x); /* XXX I think this is dangerous, due to a likely mutex in the signal handlers ... */ i->second->MonitorInputChanged (x); /* EMIT SIGNAL */ } } } void PortManager::fade_out (gain_t base_gain, gain_t gain_step, pframes_t nframes) { for (Ports::iterator i = _cycle_ports->begin(); i != _cycle_ports->end(); ++i) { if (i->second->sends_output()) { boost::shared_ptr ap = boost::dynamic_pointer_cast (i->second); if (ap) { Sample* s = ap->engine_get_whole_audio_buffer (); gain_t g = base_gain; for (pframes_t n = 0; n < nframes; ++n) { *s++ *= g; g -= gain_step; } } } } } PortEngine& PortManager::port_engine() { assert (_backend); return *_backend; }