X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fport_manager.cc;h=f5304f4961a59d4505e96fc51947f07269061dbe;hb=3d366053e2719ca0f129c16575ce481fcd214f61;hp=7a13756daba7639464e84c4422e3b9256fe5e471;hpb=5b22e673878c7670356a259f3b80f0bd22b9d6a8;p=ardour.git diff --git a/libs/ardour/port_manager.cc b/libs/ardour/port_manager.cc index 7a13756dab..f5304f4961 100644 --- a/libs/ardour/port_manager.cc +++ b/libs/ardour/port_manager.cc @@ -24,18 +24,23 @@ #include #endif -#include "pbd/convert.h" +#include +#include + #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/filesystem_paths.h" #include "ardour/midi_port.h" #include "ardour/midiport_manager.h" #include "ardour/port_manager.h" #include "ardour/profile.h" +#include "ardour/rt_tasklist.h" #include "ardour/session.h" +#include "ardour/types_convert.h" #include "pbd/i18n.h" @@ -48,7 +53,9 @@ PortManager::PortManager () : ports (new Ports) , _port_remove_in_progress (false) , _port_deletions_pending (8192) /* ick, arbitrary sizing */ + , midi_info_dirty (true) { + load_midi_port_info (); } void @@ -160,11 +167,11 @@ PortManager::port_is_mine (const string& portname) const if (portname.find_first_of (':') != string::npos) { if (portname.substr (0, self.length ()) != self) { - return false; - } - } + return false; + } + } - return true; + return true; } bool @@ -183,17 +190,69 @@ PortManager::port_is_physical (const std::string& portname) const } void -PortManager::get_physical_outputs (DataType type, std::vector& s) +PortManager::filter_midi_ports (vector& ports, MidiPortFlags include, MidiPortFlags exclude) +{ + + if (!include && !exclude) { + return; + } + + { + Glib::Threads::Mutex::Lock lm (midi_port_info_mutex); + + fill_midi_port_info_locked (); + + for (vector::iterator si = ports.begin(); si != ports.end(); ) { + + MidiPortInfo::iterator x = midi_port_info.find (*si); + + if (x == midi_port_info.end()) { + ++si; + continue; + } + + MidiPortInformation& mpi (x->second); + + if (mpi.pretty_name.empty()) { + /* no information !!! */ + ++si; + continue; + } + + if (include) { + if ((mpi.properties & include) != include) { + /* properties do not include requested ones */ + si = ports.erase (si); + continue; + } + } + + if (exclude) { + if ((mpi.properties & exclude)) { + /* properties include ones to avoid */ + si = ports.erase (si); + continue; + } + } + + ++si; + } + } +} + +void +PortManager::get_physical_outputs (DataType type, std::vector& s, MidiPortFlags include, MidiPortFlags exclude) { if (!_backend) { s.clear (); return; } _backend->get_physical_outputs (type, s); + filter_midi_ports (s, include, exclude); } void -PortManager::get_physical_inputs (DataType type, std::vector& s) +PortManager::get_physical_inputs (DataType type, std::vector& s, MidiPortFlags include, MidiPortFlags exclude) { if (!_backend) { s.clear (); @@ -201,6 +260,7 @@ PortManager::get_physical_inputs (DataType type, std::vector& s) } _backend->get_physical_inputs (type, s); + filter_midi_ports (s, include, exclude); } ChanCount @@ -225,7 +285,6 @@ PortManager::n_physical_inputs () const /** @param name Full or short name of port * @return Corresponding Port or 0. */ - boost::shared_ptr PortManager::get_port_by_name (const string& portname) { @@ -233,10 +292,10 @@ PortManager::get_port_by_name (const string& portname) return boost::shared_ptr(); } - if (!port_is_mine (portname)) { - /* not an ardour port */ - 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); @@ -244,10 +303,10 @@ PortManager::get_port_by_name (const string& portname) 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). - */ + * 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); @@ -255,7 +314,7 @@ PortManager::get_port_by_name (const string& portname) return x->second; } - return boost::shared_ptr (); + return boost::shared_ptr (); } void @@ -539,6 +598,16 @@ PortManager::disconnect (boost::shared_ptr port) return port->disconnect_all (); } +int +PortManager::disconnect (std::string const & name) +{ + PortEngine::PortHandle ph = _backend->get_port_by_name (name); + if (ph) { + return _backend->disconnect_all (ph); + } + return -2; +} + int PortManager::reestablish_ports () { @@ -601,6 +670,20 @@ PortManager::connect_callback (const string& a, const string& b, bool conn) port_b = x->second; } + if (conn) { + if (port_a && !port_b) { + port_a->increment_external_connections (); + } else if (port_b && !port_a) { + port_b->increment_external_connections (); + } + } else { + if (port_a && !port_b) { + port_a->decrement_external_connections (); + } else if (port_b && !port_a) { + port_b->decrement_external_connections (); + } + } + PortConnectedOrDisconnected ( port_a, a, port_b, b, @@ -612,6 +695,12 @@ void PortManager::registration_callback () { if (!_port_remove_in_progress) { + + { + Glib::Threads::Mutex::Lock lm (midi_port_info_mutex); + midi_info_dirty = true; + } + PortRegisteredOrUnregistered (); /* EMIT SIGNAL */ } } @@ -685,23 +774,59 @@ PortManager::graph_order_callback () } void -PortManager::cycle_start (pframes_t nframes) +PortManager::cycle_start (pframes_t nframes, Session* s) { Port::set_global_port_buffer_offset (0); - Port::set_cycle_framecnt (nframes); + Port::set_cycle_samplecnt (nframes); _cycle_ports = ports.reader (); - for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { - p->second->cycle_start (nframes); + /* TODO optimize + * - when speed == 1.0, the resampler copies data without processing + * it may (or may not) be more efficient to just run all in sequence. + * + * - single sequential task for 'lightweight' tasks would make sense + * (run it in parallel with 'heavy' resampling. + * * output ports (sends_output()) only set a flag + * * midi-ports only scale event timestamps + * + * - a threshold parallel vs searial processing may be appropriate. + * amount of work (how many connected ports are there, how + * many resamplers need to run) vs. available CPU cores and semaphore + * synchronization overhead. + * + * - input ports: it would make sense to resample each input only once + * (rather than resample into each ardour-owned input port). + * A single external source-port may be connected to many ardour + * input-ports. Currently re-sampling is per input. + */ + if (s && s->rt_tasklist () && fabs (Port::speed_ratio ()) != 1.0) { + RTTaskList::TaskList tl; + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + tl.push_back (boost::bind (&Port::cycle_start, p->second, nframes)); + } + s->rt_tasklist()->process (tl); + } else { + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + p->second->cycle_start (nframes); + } } } void -PortManager::cycle_end (pframes_t nframes) +PortManager::cycle_end (pframes_t nframes, Session* s) { - for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { - p->second->cycle_end (nframes); + // see optimzation note in ::cycle_start() + if (s && s->rt_tasklist () && fabs (Port::speed_ratio ()) != 1.0) { + RTTaskList::TaskList tl; + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + tl.push_back (boost::bind (&Port::cycle_end, p->second, nframes)); + } + s->rt_tasklist()->process (tl); + } else { + 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) { @@ -792,13 +917,27 @@ PortManager::check_monitoring () } void -PortManager::fade_out (gain_t base_gain, gain_t gain_step, pframes_t nframes) +PortManager::cycle_end_fade_out (gain_t base_gain, gain_t gain_step, pframes_t nframes, Session* s) { - for (Ports::iterator i = _cycle_ports->begin(); i != _cycle_ports->end(); ++i) { + // see optimzation note in ::cycle_start() + if (s && s->rt_tasklist () && fabs (Port::speed_ratio ()) != 1.0) { + RTTaskList::TaskList tl; + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + tl.push_back (boost::bind (&Port::cycle_end, p->second, nframes)); + } + s->rt_tasklist()->process (tl); + } else { + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + p->second->cycle_end (nframes); + } + } - if (i->second->sends_output()) { + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + p->second->flush_buffers (nframes); + + if (p->second->sends_output()) { - boost::shared_ptr ap = boost::dynamic_pointer_cast (i->second); + boost::shared_ptr ap = boost::dynamic_pointer_cast (p->second); if (ap) { Sample* s = ap->engine_get_whole_audio_buffer (); gain_t g = base_gain; @@ -810,6 +949,8 @@ PortManager::fade_out (gain_t base_gain, gain_t gain_step, pframes_t nframes) } } } + _cycle_ports.reset (); + /* we are done */ } PortEngine& @@ -835,6 +976,11 @@ PortManager::port_is_control_only (std::string const& name) const char * const control_only_ports[] = { X_(".*Ableton Push.*"), X_(".*FaderPort .*"), + X_(".*FaderPort8 .*"), + X_(".*FaderPort16 .*"), + X_(".*FaderPort2 .*"), + X_(".*US-2400 .*"), + X_(".*Mackie .*"), }; pattern = "("; @@ -851,3 +997,312 @@ PortManager::port_is_control_only (std::string const& name) return regexec (&compiled_pattern, name.c_str(), 0, 0, 0) == 0; } + +PortManager::MidiPortInformation +PortManager::midi_port_information (std::string const & name) +{ + Glib::Threads::Mutex::Lock lm (midi_port_info_mutex); + + fill_midi_port_info_locked (); + + MidiPortInfo::iterator x = midi_port_info.find (name); + + if (x != midi_port_info.end()) { + return x->second; + } + + return MidiPortInformation (); +} + +void +PortManager::get_known_midi_ports (vector& copy) +{ + Glib::Threads::Mutex::Lock lm (midi_port_info_mutex); + + fill_midi_port_info_locked (); + + for (MidiPortInfo::const_iterator x = midi_port_info.begin(); x != midi_port_info.end(); ++x) { + copy.push_back (x->first); + } +} + +void +PortManager::get_midi_selection_ports (vector& copy) +{ + Glib::Threads::Mutex::Lock lm (midi_port_info_mutex); + + fill_midi_port_info_locked (); + + for (MidiPortInfo::const_iterator x = midi_port_info.begin(); x != midi_port_info.end(); ++x) { + if (x->second.properties & MidiPortSelection) { + copy.push_back (x->first); + } + } +} + +void +PortManager::set_midi_port_pretty_name (string const & port, string const & pretty) +{ + { + Glib::Threads::Mutex::Lock lm (midi_port_info_mutex); + + fill_midi_port_info_locked (); + + MidiPortInfo::iterator x = midi_port_info.find (port); + if (x == midi_port_info.end()) { + return; + } + x->second.pretty_name = pretty; + } + + /* push into back end */ + + PortEngine::PortHandle ph = _backend->get_port_by_name (port); + + if (ph) { + _backend->set_port_property (ph, "http://jackaudio.org/metadata/pretty-name", pretty, string()); + } + + MidiPortInfoChanged (); /* EMIT SIGNAL*/ +} + +void +PortManager::add_midi_port_flags (string const & port, MidiPortFlags flags) +{ + bool emit = false; + + { + Glib::Threads::Mutex::Lock lm (midi_port_info_mutex); + + fill_midi_port_info_locked (); + + MidiPortInfo::iterator x = midi_port_info.find (port); + if (x != midi_port_info.end()) { + if ((x->second.properties & flags) != flags) { // at least one missing + x->second.properties = MidiPortFlags (x->second.properties | flags); + emit = true; + } + } + } + + if (emit) { + if (flags & MidiPortSelection) { + MidiSelectionPortsChanged (); /* EMIT SIGNAL */ + } + + if (flags != MidiPortSelection) { + MidiPortInfoChanged (); /* EMIT SIGNAL */ + } + + save_midi_port_info (); + } +} + +void +PortManager::remove_midi_port_flags (string const & port, MidiPortFlags flags) +{ + bool emit = false; + + { + Glib::Threads::Mutex::Lock lm (midi_port_info_mutex); + + fill_midi_port_info_locked (); + + MidiPortInfo::iterator x = midi_port_info.find (port); + if (x != midi_port_info.end()) { + if (x->second.properties & flags) { // at least one is set + x->second.properties = MidiPortFlags (x->second.properties & ~flags); + emit = true; + } + } + } + + if (emit) { + if (flags & MidiPortSelection) { + MidiSelectionPortsChanged (); /* EMIT SIGNAL */ + } + + if (flags != MidiPortSelection) { + MidiPortInfoChanged (); /* EMIT SIGNAL */ + } + + save_midi_port_info (); + } +} + +string +PortManager::midi_port_info_file () +{ + return Glib::build_filename (user_config_directory(), X_("midi_port_info")); +} + +void +PortManager::save_midi_port_info () +{ + string path = midi_port_info_file (); + + XMLNode* root = new XMLNode (X_("MidiPortInfo")); + + { + Glib::Threads::Mutex::Lock lm (midi_port_info_mutex); + + if (midi_port_info.empty()) { + delete root; + return; + } + + for (MidiPortInfo::iterator i = midi_port_info.begin(); i != midi_port_info.end(); ++i) { + XMLNode* node = new XMLNode (X_("port")); + node->set_property (X_("name"), i->first); + node->set_property (X_("input"), i->second.input); + node->set_property (X_("properties"), i->second.properties); + root->add_child_nocopy (*node); + } + } + + XMLTree tree; + + tree.set_root (root); + + if (!tree.write (path)) { + error << string_compose (_("Could not save MIDI port info to %1"), path) << endmsg; + } +} + +void +PortManager::load_midi_port_info () +{ + string path = midi_port_info_file (); + XMLTree tree; + + if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) { + return; + } + + if (!tree.read (path)) { + error << string_compose (_("Cannot load MIDI port info from %1"), path) << endmsg; + return; + } + + midi_port_info.clear (); + + for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i) { + MidiPortInformation mpi; + string name; + + if (!(*i)->get_property (X_("name"), name) || + !(*i)->get_property (X_("input"), mpi.input) || + !(*i)->get_property (X_("properties"), mpi.properties)) { + continue; + } + + midi_port_info.insert (make_pair (name, mpi)); + } +} + +void +PortManager::fill_midi_port_info () +{ + Glib::Threads::Mutex::Lock lm (midi_port_info_mutex); + fill_midi_port_info_locked (); +} + +void +PortManager::fill_midi_port_info_locked () +{ + /* MIDI info mutex MUST be held */ + + if (!midi_info_dirty) { + return; + } + + std::vector ports; + + AudioEngine::instance()->get_ports (string(), DataType::MIDI, IsOutput, ports); + + for (vector::iterator p = ports.begin(); p != ports.end(); ++p) { + + if (port_is_mine (*p)) { + continue; + } + + if (midi_port_info.find (*p) == midi_port_info.end()) { + MidiPortInformation mpi; + mpi.pretty_name = *p; + mpi.input = true; + + if (port_is_control_only (*p)) { + mpi.properties = MidiPortFlags (mpi.properties | MidiPortControl); + } +#ifdef LINUX + if ((*p.find (X_("Midi Through")) != string::npos || + (*p).find (X_("Midi-Through")) != string::npos)) + { + mpi.properties = MidiPortFlags (mpi.properties | MidiPortVirtual); + } +#endif + midi_port_info.insert (make_pair (*p, mpi)); + } + } + + AudioEngine::instance()->get_ports (string(), DataType::MIDI, IsInput, ports); + + for (vector::iterator p = ports.begin(); p != ports.end(); ++p) { + + if (port_is_mine (*p)) { + continue; + } + + if (midi_port_info.find (*p) == midi_port_info.end()) { + MidiPortInformation mpi; + mpi.pretty_name = *p; + mpi.input = false; + + if (port_is_control_only (*p)) { + mpi.properties = MidiPortFlags (mpi.properties | MidiPortControl); + } +#ifdef LINUX + if ((*p.find (X_("Midi Through")) != string::npos || + (*p).find (X_("Midi-Through")) != string::npos)) + { + mpi.properties = MidiPortFlags (mpi.properties | MidiPortVirtual); + } +#endif + midi_port_info.insert (make_pair (*p, mpi)); + } + } + + /* now push/pull pretty name information between backend and the + * PortManager + */ + + // rg: I don't understand what this attempts to solve + // + // Naming ports should be left to the backend: + // Ardour cannot associate numeric IDs with corresponding hardware. + // (see also 7dde6c3b) + + for (MidiPortInfo::iterator x = midi_port_info.begin(); x != midi_port_info.end(); ++x) { + PortEngine::PortHandle ph = _backend->get_port_by_name (x->first); + + if (!ph) { + /* port info saved from some condition where this port + * existed, but no longer does (i.e. device unplugged + * at present). We don't remove it from midi_port_info. + */ + continue; + } + + /* check with backend for pre-existing pretty name */ + string value; + string type; + + if (0 == _backend->get_port_property (ph, + "http://jackaudio.org/metadata/pretty-name", + value, type)) { + x->second.pretty_name = value; + } + } + + midi_info_dirty = false; +}