X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fio.cc;h=3805d7ac837eac14f16c13e876471e9643b6e49a;hb=0d820089de753b8ae54954664cc47c3907c25ba8;hp=2749b9f405a92d2d7dfb4109037ef3feeb0a6e09;hpb=0bf3eb77591afc4aa8d0d6b5efa72273f02b109e;p=ardour.git diff --git a/libs/ardour/io.cc b/libs/ardour/io.cc index 2749b9f405..3805d7ac83 100644 --- a/libs/ardour/io.cc +++ b/libs/ardour/io.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2000-2006 Paul Davis + Copyright (C) 2000-2006 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 @@ -18,12 +18,12 @@ #include #include +#include + #include #include #include -#include - #include #include @@ -34,6 +34,7 @@ #include "ardour/audioengine.h" #include "ardour/buffer.h" +#include "ardour/debug.h" #include "ardour/io.h" #include "ardour/route.h" #include "ardour/port.h" @@ -41,7 +42,6 @@ #include "ardour/midi_port.h" #include "ardour/session.h" #include "ardour/cycle_timer.h" -#include "ardour/panner.h" #include "ardour/buffer_set.h" #include "ardour/meter.h" #include "ardour/amp.h" @@ -49,19 +49,7 @@ #include "i18n.h" -#include - -/* - A bug in OS X's cmath that causes isnan() and isinf() to be - "undeclared". the following works around that -*/ - -#if defined(__APPLE__) && defined(__MACH__) -extern "C" int isnan (double); -extern "C" int isinf (double); -#endif - -#define BLOCK_PROCESS_CALLBACK() Glib::Mutex::Lock em (_session.engine().process_lock()) +#define BLOCK_PROCESS_CALLBACK() Glib::Mutex::Lock em (AudioEngine::instance()->process_lock()) using namespace std; using namespace ARDOUR; @@ -69,8 +57,8 @@ using namespace PBD; const string IO::state_node_name = "IO"; bool IO::connecting_legal = false; -sigc::signal IO::ConnectingLegal; -sigc::signal IO::PortCountChanged; +PBD::Signal0 IO::ConnectingLegal; +PBD::Signal1 IO::PortCountChanged; /** @param default_type The type of port that will be created by ensure_io * and friends if no type is explicitly requested (to avoid breakage). @@ -81,8 +69,9 @@ IO::IO (Session& s, const string& name, Direction dir, DataType default_type) , _default_type (default_type) { _active = true; + Port::PostDisconnect.connect_same_thread (*this, boost::bind (&IO::disconnect_check, this, _1, _2)); pending_state_node = 0; - setup_bundles (); + setup_bundle (); } IO::IO (Session& s, const XMLNode& node, DataType dt) @@ -92,9 +81,10 @@ IO::IO (Session& s, const XMLNode& node, DataType dt) { _active = true; pending_state_node = 0; + Port::PostDisconnect.connect_same_thread (*this, boost::bind (&IO::disconnect_check, this, _1, _2)); - set_state (node); - setup_bundles (); + set_state (node, Stateful::loading_state_version); + setup_bundle (); } IO::~IO () @@ -109,7 +99,44 @@ IO::~IO () } void -IO::silence (nframes_t nframes) +IO::disconnect_check (boost::shared_ptr a, boost::shared_ptr b) +{ + /* this could be called from within our own ::disconnect() method(s) + or from somewhere that operates directly on a port. so, we don't + know for sure if we can take this lock or not. if we fail, + we assume that its safely locked by our own ::disconnect(). + */ + + Glib::Mutex::Lock tm (io_lock, Glib::TRY_LOCK); + + if (tm.locked()) { + /* we took the lock, so we cannot be here from inside + * ::disconnect() + */ + if (_ports.contains (a) || _ports.contains (b)) { + changed (IOChange (IOChange::ConnectionsChanged), this); /* EMIT SIGNAL */ + } + } else { + /* we didn't get the lock, so assume that we're inside + * ::disconnect(), and it will call changed() appropriately. + */ + } +} + +void +IO::increment_port_buffer_offset (pframes_t offset) +{ + /* io_lock, not taken: function must be called from Session::process() calltree */ + + if (_direction == Output) { + for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) { + i->increment_port_buffer_offset (offset); + } + } +} + +void +IO::silence (framecnt_t nframes) { /* io_lock, not taken: function must be called from Session::process() calltree */ @@ -118,22 +145,19 @@ IO::silence (nframes_t nframes) } } +/** Set _bundles_connected to those bundles that are connected such that every + * port on every bundle channel x is connected to port x in _ports. + */ void IO::check_bundles_connected () { - check_bundles (_bundles_connected, ports()); -} + std::vector new_list; -void -IO::check_bundles (std::vector& list, const PortSet& ports) -{ - std::vector new_list; - - for (std::vector::iterator i = list.begin(); i != list.end(); ++i) { + for (std::vector::iterator i = _bundles_connected.begin(); i != _bundles_connected.end(); ++i) { - uint32_t const N = i->bundle->nchannels (); + uint32_t const N = (*i)->bundle->nchannels().n_total(); - if (_ports.num_ports (default_type()) < N) { + if (_ports.num_ports() < N) { continue; } @@ -141,9 +165,9 @@ IO::check_bundles (std::vector& list, const PortSet& ports) for (uint32_t j = 0; j < N; ++j) { /* Every port on bundle channel j must be connected to our input j */ - Bundle::PortList const pl = i->bundle->channel_ports (j); + Bundle::PortList const pl = (*i)->bundle->channel_ports (j); for (uint32_t k = 0; k < pl.size(); ++k) { - if (ports.port(j)->connected_to (pl[k]) == false) { + if (_ports.port(j)->connected_to (pl[k]) == false) { ok = false; break; } @@ -157,148 +181,166 @@ IO::check_bundles (std::vector& list, const PortSet& ports) if (ok) { new_list.push_back (*i); } else { - i->changed.disconnect (); + delete *i; } } - list = new_list; + _bundles_connected = new_list; } int -IO::disconnect (Port* our_port, string other_port, void* src) +IO::disconnect (boost::shared_ptr our_port, string other_port, void* src) { if (other_port.length() == 0 || our_port == 0) { return 0; } - { - BLOCK_PROCESS_CALLBACK (); - - { - Glib::Mutex::Lock lm (io_lock); - - /* check that our_port is really one of ours */ - - if ( ! _ports.contains(our_port)) { - return -1; - } - - /* disconnect it from the source */ - - if (our_port->disconnect (other_port)) { - error << string_compose(_("IO: cannot disconnect port %1 from %2"), our_port->name(), other_port) << endmsg; - return -1; - } + { + Glib::Mutex::Lock lm (io_lock); - check_bundles_connected (); - } - } + /* check that our_port is really one of ours */ + + if ( ! _ports.contains(our_port)) { + return -1; + } + + /* disconnect it from the source */ + + if (our_port->disconnect (other_port)) { + error << string_compose(_("IO: cannot disconnect port %1 from %2"), our_port->name(), other_port) << endmsg; + return -1; + } + + check_bundles_connected (); + } + + changed (IOChange (IOChange::ConnectionsChanged), src); /* EMIT SIGNAL */ - changed (ConnectionsChanged, src); /* EMIT SIGNAL */ _session.set_dirty (); return 0; } int -IO::connect (Port* our_port, string other_port, void* src) +IO::connect (boost::shared_ptr our_port, string other_port, void* src) { if (other_port.length() == 0 || our_port == 0) { return 0; } { - BLOCK_PROCESS_CALLBACK (); - - { - Glib::Mutex::Lock lm (io_lock); - - /* check that our_port is really one of ours */ - - if ( ! _ports.contains(our_port) ) { - return -1; - } - - /* connect it to the source */ + Glib::Mutex::Lock lm (io_lock); - if (our_port->connect (other_port)) { - return -1; - } + /* check that our_port is really one of ours */ + + if ( ! _ports.contains(our_port) ) { + return -1; } - } - changed (ConnectionsChanged, src); /* EMIT SIGNAL */ + /* connect it to the source */ + + if (our_port->connect (other_port)) { + return -1; + } + } + changed (IOChange (IOChange::ConnectionsChanged), src); /* EMIT SIGNAL */ _session.set_dirty (); return 0; } int -IO::remove_port (Port* port, void* src) +IO::remove_port (boost::shared_ptr port, void* src) { - IOChange change (NoChange); + ChanCount before = _ports.count (); + ChanCount after = before; + after.set (port->type(), after.get (port->type()) - 1); + + bool const r = PortCountChanging (after); /* EMIT SIGNAL */ + if (r) { + return -1; + } + + IOChange change; { BLOCK_PROCESS_CALLBACK (); - { Glib::Mutex::Lock lm (io_lock); if (_ports.remove(port)) { - change = IOChange (change|ConfigurationChanged); + change.type = IOChange::Type (change.type | IOChange::ConfigurationChanged); + change.before = before; + change.after = _ports.count (); if (port->connected()) { - change = IOChange (change|ConnectionsChanged); - } + change.type = IOChange::Type (change.type | IOChange::ConnectionsChanged); + } - _session.engine().unregister_port (*port); + _session.engine().unregister_port (port); check_bundles_connected (); } } PortCountChanged (n_ports()); /* EMIT SIGNAL */ + + if (change.type != IOChange::NoChange) { + changed (change, src); + _buffers.attach_buffers (_ports); + } } - if (change & ConfigurationChanged) { - setup_bundles (); + if (change.type & IOChange::ConfigurationChanged) { + setup_bundle (); } - if (change != NoChange) { - changed (change, src); - _session.set_dirty (); - return 0; + if (change.type == IOChange::NoChange) { + return -1; } - return -1; + _session.set_dirty (); + + return 0; } -/** Add an output port. +/** Add a port. * - * @param destination Name of input port to connect new port to. + * @param destination Name of port to connect new port to. * @param src Source for emitted ConfigurationChanged signal. * @param type Data type of port. Default value (NIL) will use this IO's default type. */ int IO::add_port (string destination, void* src, DataType type) { - Port* our_port; + boost::shared_ptr our_port; if (type == DataType::NIL) { type = _default_type; } + ChanCount before = _ports.count (); + ChanCount after = before; + after.set (type, after.get (type) + 1); + + bool const r = PortCountChanging (after); /* EMIT SIGNAL */ + if (r) { + return -1; + } + + IOChange change; + { BLOCK_PROCESS_CALLBACK (); - - { + + { Glib::Mutex::Lock lm (io_lock); - - /* Create a new output port */ - - string portname = build_legal_port_name (type); + /* Create a new port */ + + string portname = build_legal_port_name (type); + if (_direction == Input) { if ((our_port = _session.engine().register_input_port (type, portname)) == 0) { error << string_compose(_("IO: cannot register input port %1"), portname) << endmsg; @@ -311,21 +353,24 @@ IO::add_port (string destination, void* src, DataType type) } } + change.before = _ports.count (); _ports.add (our_port); } - + PortCountChanged (n_ports()); /* EMIT SIGNAL */ + change.type = IOChange::ConfigurationChanged; + change.after = _ports.count (); + changed (change, src); /* EMIT SIGNAL */ + _buffers.attach_buffers (_ports); } - if (destination.length()) { + if (!destination.empty()) { if (our_port->connect (destination)) { return -1; } } - - // pan_changed (src); /* EMIT SIGNAL */ - changed (ConfigurationChanged, src); /* EMIT SIGNAL */ - setup_bundles (); + + setup_bundle (); _session.set_dirty (); return 0; @@ -334,42 +379,42 @@ IO::add_port (string destination, void* src, DataType type) int IO::disconnect (void* src) { - { - BLOCK_PROCESS_CALLBACK (); - - { - Glib::Mutex::Lock lm (io_lock); - - for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) { - i->disconnect_all (); - } + { + Glib::Mutex::Lock lm (io_lock); - check_bundles_connected (); + for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) { + i->disconnect_all (); } + + check_bundles_connected (); } - - changed (ConnectionsChanged, src); /* EMIT SIGNAL */ - + + changed (IOChange (IOChange::ConnectionsChanged), src); /* EMIT SIGNAL */ + return 0; } -bool -IO::ensure_ports_locked (ChanCount count, bool clear, void* /*src*/) +/** Caller must hold process lock */ +int +IO::ensure_ports_locked (ChanCount count, bool clear, bool& changed) { - Port* port = 0; - bool changed = false; - + assert (!AudioEngine::instance()->process_lock().trylock()); + + boost::shared_ptr port; + + changed = false; + for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) { - + const size_t n = count.get(*t); - + /* remove unused ports */ for (size_t i = n_ports().get(*t); i > n; --i) { port = _ports.port(*t, i-1); - + assert(port); _ports.remove(port); - _session.engine().unregister_port (*port); + _session.engine().unregister_port (port); changed = true; } @@ -396,20 +441,20 @@ IO::ensure_ports_locked (ChanCount count, bool clear, void* /*src*/) catch (AudioEngine::PortRegistrationFailure& err) { /* pass it on */ - throw AudioEngine::PortRegistrationFailure(); + throw; } _ports.add (port); changed = true; } } - + if (changed) { check_bundles_connected (); PortCountChanged (n_ports()); /* EMIT SIGNAL */ _session.set_dirty (); } - + if (clear) { /* disconnect all existing ports so that we get a fresh start */ for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) { @@ -417,44 +462,55 @@ IO::ensure_ports_locked (ChanCount count, bool clear, void* /*src*/) } } - return changed; + return 0; } - +/** Caller must hold process lock */ int -IO::ensure_ports (ChanCount count, bool clear, bool lockit, void* src) +IO::ensure_ports (ChanCount count, bool clear, void* src) { + assert (!AudioEngine::instance()->process_lock().trylock()); + bool changed = false; if (count == n_ports() && !clear) { return 0; } - if (lockit) { - BLOCK_PROCESS_CALLBACK (); + IOChange change; + + change.before = _ports.count (); + + { Glib::Mutex::Lock im (io_lock); - changed = ensure_ports_locked (count, clear, src); - } else { - changed = ensure_ports_locked (count, clear, src); + if (ensure_ports_locked (count, clear, changed)) { + return -1; + } } if (changed) { - this->changed (ConfigurationChanged, src); /* EMIT SIGNAL */ - setup_bundles (); + change.after = _ports.count (); + change.type = IOChange::ConfigurationChanged; + this->changed (change, src); /* EMIT SIGNAL */ + _buffers.attach_buffers (_ports); + setup_bundle (); _session.set_dirty (); } return 0; } +/** Caller must hold process lock */ int IO::ensure_io (ChanCount count, bool clear, void* src) { - return ensure_ports (count, clear, true, src); + assert (!AudioEngine::instance()->process_lock().trylock()); + + return ensure_ports (count, clear, src); } XMLNode& -IO::get_state (void) +IO::get_state () { return state (true); } @@ -476,14 +532,14 @@ IO::state (bool /*full_state*/) node->add_property ("direction", enum_2_string (_direction)); node->add_property ("default-type", _default_type.to_string()); - for (std::vector::iterator i = _bundles_connected.begin(); i != _bundles_connected.end(); ++i) { + for (std::vector::iterator i = _bundles_connected.begin(); i != _bundles_connected.end(); ++i) { XMLNode* n = new XMLNode ("Bundle"); - n->add_property ("name", i->bundle->name ()); + n->add_property ("name", (*i)->bundle->name ()); node->add_child_nocopy (*n); } for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) { - + vector connections; XMLNode* pnode = new XMLNode (X_("Port")); @@ -500,23 +556,32 @@ IO::state (bool /*full_state*/) to be re-established even when our client name is different. */ - + XMLNode* cnode = new XMLNode (X_("Connection")); cnode->add_property (X_("other"), _session.engine().make_port_name_relative (*ci)); pnode->add_child_nocopy (*cnode); - } + } } - + node->add_child_nocopy (*pnode); } + snprintf (buf, sizeof (buf), "%" PRId64, _user_latency); + node->add_property (X_("user-latency"), buf); + return *node; } int -IO::set_state (const XMLNode& node) +IO::set_state (const XMLNode& node, int version) { + /* callers for version < 3000 need to call set_state_2X directly, as A3 IOs + * are input OR output, not both, so the direction needs to be specified + * by the caller. + */ + assert (version >= 3000); + const XMLProperty* prop; XMLNodeConstIterator iter; LocaleGuard lg (X_("POSIX")); @@ -529,7 +594,7 @@ IO::set_state (const XMLNode& node) error << string_compose(_("incorrect XML node \"%1\" passed to IO object"), node.name()) << endmsg; return -1; } - + if ((prop = node.property ("name")) != 0) { set_name (prop->value()); } @@ -539,30 +604,83 @@ IO::set_state (const XMLNode& node) assert(_default_type != DataType::NIL); } - if ((prop = node.property ("id")) != 0) { - _id = prop->value (); - } + set_id (node); if ((prop = node.property ("direction")) != 0) { _direction = (Direction) string_2_enum (prop->value(), _direction); } - if (create_ports (node)) { + if (create_ports (node, version)) { return -1; } if (connecting_legal) { - - if (make_connections (node)) { + + if (make_connections (node, version, false)) { return -1; } } else { pending_state_node = new XMLNode (node); - connection_legal_c = ConnectingLegal.connect (mem_fun (*this, &IO::connecting_became_legal)); + pending_state_node_version = version; + pending_state_node_in = false; + ConnectingLegal.connect_same_thread (connection_legal_c, boost::bind (&IO::connecting_became_legal, this)); + } + + if ((prop = node.property ("user-latency")) != 0) { + _user_latency = atoi (prop->value ()); } + return 0; +} + +int +IO::set_state_2X (const XMLNode& node, int version, bool in) +{ + const XMLProperty* prop; + XMLNodeConstIterator iter; + LocaleGuard lg (X_("POSIX")); + + /* force use of non-localized representation of decimal point, + since we use it a lot in XML files and so forth. + */ + + if (node.name() != state_node_name) { + error << string_compose(_("incorrect XML node \"%1\" passed to IO object"), node.name()) << endmsg; + return -1; + } + + if ((prop = node.property ("name")) != 0) { + set_name (prop->value()); + } + + if ((prop = node.property (X_("default-type"))) != 0) { + _default_type = DataType(prop->value()); + assert(_default_type != DataType::NIL); + } + + set_id (node); + + _direction = in ? Input : Output; + + if (create_ports (node, version)) { + return -1; + } + + if (connecting_legal) { + + if (make_connections_2X (node, version, in)) { + return -1; + } + + } else { + + pending_state_node = new XMLNode (node); + pending_state_node_version = version; + pending_state_node_in = in; + ConnectingLegal.connect_same_thread (connection_legal_c, boost::bind (&IO::connecting_became_legal, this)); + } return 0; } @@ -576,8 +694,8 @@ IO::connecting_became_legal () connection_legal_c.disconnect (); - ret = make_connections (*pending_state_node); - + ret = make_connections (*pending_state_node, pending_state_node_version, pending_state_node_in); + delete pending_state_node; pending_state_node = 0; @@ -590,7 +708,7 @@ IO::find_possible_bundle (const string &desired_name) static const string digits = "0123456789"; const string &default_name = (_direction == Input ? _("in") : _("out")); const string &bundle_type_name = (_direction == Input ? _("input") : _("output")); - + boost::shared_ptr c = _session.bundle_by_name (desired_name); if (!c) { @@ -604,7 +722,7 @@ IO::find_possible_bundle (const string &desired_name) // find numeric suffix of desired name bundle_number = 0; - + last_non_digit_pos = desired_name.find_last_not_of(digits); if (last_non_digit_pos != string::npos) { @@ -612,16 +730,16 @@ IO::find_possible_bundle (const string &desired_name) s << desired_name.substr(last_non_digit_pos); s >> bundle_number; } - + // see if it's a stereo connection e.g. "in 3+4" if (last_non_digit_pos > 1 && desired_name[last_non_digit_pos] == '+') { - int left_bundle_number = 0; string::size_type left_last_non_digit_pos; left_last_non_digit_pos = desired_name.find_last_not_of(digits, last_non_digit_pos-1); if (left_last_non_digit_pos != string::npos) { + int left_bundle_number = 0; stringstream s; s << desired_name.substr(left_last_non_digit_pos, last_non_digit_pos-1); s >> left_bundle_number; @@ -640,22 +758,22 @@ IO::find_possible_bundle (const string &desired_name) // find highest set bit mask = 1; while ((mask <= bundle_number) && (mask <<= 1)) {} - - // "wrap" bundle number into largest possible power of 2 + + // "wrap" bundle number into largest possible power of 2 // that works... while (mask) { if (bundle_number & mask) { bundle_number &= ~mask; - + stringstream s; s << default_name << " " << bundle_number + 1; if (stereo) { s << "+" << bundle_number + 2; } - + possible_name = s.str(); if ((c = _session.bundle_by_name (possible_name)) != 0) { @@ -679,8 +797,40 @@ IO::find_possible_bundle (const string &desired_name) } int -IO::get_port_counts (const XMLNode& node, ChanCount& n, boost::shared_ptr& c) +IO::get_port_counts_2X (XMLNode const & node, int /*version*/, ChanCount& n, boost::shared_ptr& /*c*/) +{ + XMLProperty const * prop; + XMLNodeList children = node.children (); + + uint32_t n_audio = 0; + + for (XMLNodeIterator i = children.begin(); i != children.end(); ++i) { + + if ((prop = node.property ("inputs")) != 0 && _direction == Input) { + n_audio = count (prop->value().begin(), prop->value().end(), '{'); + } else if ((prop = node.property ("input-connection")) != 0 && _direction == Input) { + n_audio = 1; + } else if ((prop = node.property ("outputs")) != 0 && _direction == Output) { + n_audio = count (prop->value().begin(), prop->value().end(), '{'); + } else if ((prop = node.property ("output-connection")) != 0 && _direction == Output) { + n_audio = 2; + } + } + + ChanCount cnt; + cnt.set_audio (n_audio); + n = ChanCount::max (n, cnt); + + return 0; +} + +int +IO::get_port_counts (const XMLNode& node, int version, ChanCount& n, boost::shared_ptr& c) { + if (version < 3000) { + return get_port_counts_2X (node, version, n, c); + } + XMLProperty const * prop; XMLNodeConstIterator iter; uint32_t n_audio = 0; @@ -692,16 +842,16 @@ IO::get_port_counts (const XMLNode& node, ChanCount& n, boost::shared_ptrvalue())) != 0) { - n = ChanCount::max (n, ChanCount(c->type(), c->nchannels())); + n = ChanCount::max (n, c->nchannels()); } return 0; } - + for (iter = node.children().begin(); iter != node.children().end(); ++iter) { if ((*iter)->name() == X_("Bundle")) { if ((c = find_possible_bundle (prop->value())) != 0) { - n = ChanCount::max (n, ChanCount(c->type(), c->nchannels())); + n = ChanCount::max (n, c->nchannels()); return 0; } else { return -1; @@ -722,22 +872,26 @@ IO::get_port_counts (const XMLNode& node, ChanCount& n, boost::shared_ptr c; - - get_port_counts (node, n, c); - - if (ensure_ports (n, true, true, this)) { - error << string_compose(_("%1: cannot create I/O ports"), _name) << endmsg; - return -1; + + get_port_counts (node, version, n, c); + + { + Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + + if (ensure_ports (n, true, this)) { + error << string_compose(_("%1: cannot create I/O ports"), _name) << endmsg; + return -1; + } } /* XXX use c */ @@ -746,8 +900,12 @@ IO::create_ports (const XMLNode& node) } int -IO::make_connections (const XMLNode& node) +IO::make_connections (const XMLNode& node, int version, bool in) { + if (version < 3000) { + return make_connections_2X (node, version, in); + } + const XMLProperty* prop; for (XMLNodeConstIterator i = node.children().begin(); i != node.children().end(); ++i) { @@ -771,30 +929,125 @@ IO::make_connections (const XMLNode& node) if (!prop) { continue; } - - Port* p = port_by_name (prop->value()); + + boost::shared_ptr p = port_by_name (prop->value()); if (p) { - for (XMLNodeConstIterator c = (*i)->children().begin(); c != (*i)->children().end(); ++c) { + for (XMLNodeConstIterator c = (*i)->children().begin(); c != (*i)->children().end(); ++c) { XMLNode* cnode = (*c); - + if (cnode->name() != X_("Connection")) { continue; } - + if ((prop = cnode->property (X_("other"))) == 0) { continue; } - + if (prop) { - p->connect (prop->value()); + connect (p, prop->value(), this); } } } } } - + + return 0; +} + + +int +IO::make_connections_2X (const XMLNode& node, int /*version*/, bool in) +{ + const XMLProperty* prop; + + /* XXX: bundles ("connections" as was) */ + + if ((prop = node.property ("inputs")) != 0 && in) { + + string::size_type ostart = 0; + string::size_type start = 0; + string::size_type end = 0; + int i = 0; + int n; + vector ports; + + string const str = prop->value (); + + while ((start = str.find_first_of ('{', ostart)) != string::npos) { + start += 1; + + if ((end = str.find_first_of ('}', start)) == string::npos) { + error << string_compose(_("IO: badly formed string in XML node for inputs \"%1\""), str) << endmsg; + return -1; + } + + if ((n = parse_io_string (str.substr (start, end - start), ports)) < 0) { + error << string_compose(_("bad input string in XML node \"%1\""), str) << endmsg; + + return -1; + + } else if (n > 0) { + + + for (int x = 0; x < n; ++x) { + /* XXX: this is a bit of a hack; need to check if it's always valid */ + string::size_type const p = ports[x].find ("/out"); + if (p != string::npos) { + ports[x].replace (p, 4, "/audio_out"); + } + nth(i)->connect (ports[x]); + } + } + + ostart = end+1; + i++; + } + + } + + if ((prop = node.property ("outputs")) != 0 && !in) { + + string::size_type ostart = 0; + string::size_type start = 0; + string::size_type end = 0; + int i = 0; + int n; + vector ports; + + string const str = prop->value (); + + while ((start = str.find_first_of ('{', ostart)) != string::npos) { + start += 1; + + if ((end = str.find_first_of ('}', start)) == string::npos) { + error << string_compose(_("IO: badly formed string in XML node for outputs \"%1\""), str) << endmsg; + return -1; + } + + if ((n = parse_io_string (str.substr (start, end - start), ports)) < 0) { + error << string_compose(_("IO: bad output string in XML node \"%1\""), str) << endmsg; + + return -1; + + } else if (n > 0) { + + for (int x = 0; x < n; ++x) { + /* XXX: this is a bit of a hack; need to check if it's always valid */ + string::size_type const p = ports[x].find ("/in"); + if (p != string::npos) { + ports[x].replace (p, 3, "/audio_in"); + } + nth(i)->connect (ports[x]); + } + } + + ostart = end+1; + i++; + } + } + return 0; } @@ -805,14 +1058,18 @@ IO::set_ports (const string& str) int i; int n; uint32_t nports; - + if ((nports = count (str.begin(), str.end(), '{')) == 0) { return 0; } - // FIXME: audio-only - if (ensure_ports (ChanCount(DataType::AUDIO, nports), true, true, this)) { - return -1; + { + Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + + // FIXME: audio-only + if (ensure_ports (ChanCount(DataType::AUDIO, nports), true, this)) { + return -1; + } } string::size_type start, end, ostart; @@ -834,7 +1091,7 @@ IO::set_ports (const string& str) error << string_compose(_("bad input string in XML node \"%1\""), str) << endmsg; return -1; - + } else if (n > 0) { for (int x = 0; x < n; ++x) { @@ -867,7 +1124,7 @@ IO::parse_io_string (const string& str, vector& ports) ports.push_back (str.substr (opos, pos - opos)); opos = pos + 1; } - + if (opos < str.length()) { ports.push_back (str.substr(opos)); } @@ -888,7 +1145,7 @@ IO::parse_gain_string (const string& str, vector& ports) ports.push_back (str.substr (opos, pos - opos)); opos = pos + 1; } - + if (opos < str.length()) { ports.push_back (str.substr(opos)); } @@ -901,73 +1158,59 @@ IO::set_name (const string& requested_name) { string name = requested_name; - if (name == _name) { + if (_name == name) { return true; } - + /* replace all colons in the name. i wish we didn't have to do this */ - if (replace_all (name, ":", "-")) { - warning << _("you cannot use colons to name objects with I/O connections") << endmsg; - } + replace_all (name, ":", "-"); for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) { string current_name = i->name(); - current_name.replace (current_name.find (_name), _name.length(), name); + current_name.replace (current_name.find (_name), _name.val().length(), name); i->set_name (current_name); } bool const r = SessionObject::set_name (name); - setup_bundles (); + setup_bundle (); return r; } -void -IO::set_port_latency (nframes_t nframes) -{ - Glib::Mutex::Lock lm (io_lock); - - for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) { - i->set_latency (nframes); - } -} - -nframes_t +framecnt_t IO::latency () const { - nframes_t max_latency; - nframes_t latency; + framecnt_t max_latency; + framecnt_t latency; max_latency = 0; /* io lock not taken - must be protected by other means */ for (PortSet::const_iterator i = _ports.begin(); i != _ports.end(); ++i) { - if ((latency = i->total_latency ()) > max_latency) { + if ((latency = i->private_latency_range (_direction == Output).max) > max_latency) { + DEBUG_TRACE (DEBUG::Latency, string_compose ("port %1 has %2 latency of %3 - use\n", + name(), + ((_direction == Output) ? "PLAYBACK" : "CAPTURE"), + latency)); max_latency = latency; - } + } } + DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: max %4 latency from %2 ports = %3\n", + name(), _ports.num_ports(), max_latency, + ((_direction == Output) ? "PLAYBACK" : "CAPTURE"))); return max_latency; } -void -IO::update_port_total_latencies () -{ - /* io_lock, not taken: function must be called from Session::process() calltree */ - - for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) { - _session.engine().update_total_latency (*i); - } -} - int IO::connect_ports_to_bundle (boost::shared_ptr c, void* src) { + BLOCK_PROCESS_CALLBACK (); + { - BLOCK_PROCESS_CALLBACK (); Glib::Mutex::Lock lm2 (io_lock); c->connect (_bundle, _session.engine()); @@ -978,48 +1221,50 @@ IO::connect_ports_to_bundle (boost::shared_ptr c, void* src) if (ub) { /* See if we already know about this one */ - std::vector::iterator i = _bundles_connected.begin(); - while (i != _bundles_connected.end() && i->bundle != ub) { + std::vector::iterator i = _bundles_connected.begin(); + while (i != _bundles_connected.end() && (*i)->bundle != ub) { ++i; } if (i == _bundles_connected.end()) { /* We don't, so make a note */ - _bundles_connected.push_back (UserBundleInfo (this, ub)); + _bundles_connected.push_back (new UserBundleInfo (this, ub)); } } } - changed (IOChange (ConfigurationChanged|ConnectionsChanged), src); /* EMIT SIGNAL */ + changed (IOChange (IOChange::ConnectionsChanged), src); /* EMIT SIGNAL */ return 0; } int IO::disconnect_ports_from_bundle (boost::shared_ptr c, void* src) { + BLOCK_PROCESS_CALLBACK (); + { - BLOCK_PROCESS_CALLBACK (); Glib::Mutex::Lock lm2 (io_lock); c->disconnect (_bundle, _session.engine()); - + /* If this is a UserBundle, make a note of what we've done */ boost::shared_ptr ub = boost::dynamic_pointer_cast (c); if (ub) { - std::vector::iterator i = _bundles_connected.begin(); - while (i != _bundles_connected.end() && i->bundle != ub) { + std::vector::iterator i = _bundles_connected.begin(); + while (i != _bundles_connected.end() && (*i)->bundle != ub) { ++i; } if (i != _bundles_connected.end()) { + delete *i; _bundles_connected.erase (i); } } } - changed (IOChange (ConfigurationChanged|ConnectionsChanged), src); /* EMIT SIGNAL */ + changed (IOChange (IOChange::ConnectionsChanged), src); /* EMIT SIGNAL */ return 0; } @@ -1034,14 +1279,16 @@ IO::disable_connecting () int IO::enable_connecting () { + Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock()); connecting_legal = true; - return ConnectingLegal (); + boost::optional r = ConnectingLegal (); + return r.get_value_or (0); } void IO::bundle_changed (Bundle::Change /*c*/) { - //XXX + /* XXX */ // connect_input_ports_to_bundle (_input_bundle, this); } @@ -1060,7 +1307,7 @@ IO::build_legal_port_name (DataType type) } else { throw unknown_type(); } - + /* note that if "in" or "out" are translated it will break a session across locale switches because a port's connection list will show (old) translated names, but the current port name will @@ -1079,9 +1326,14 @@ IO::build_legal_port_name (DataType type) char buf1[name_size+1]; char buf2[name_size+1]; - - snprintf (buf1, name_size+1, ("%.*s/%s"), limit, _name.c_str(), suffix.c_str()); - + + /* colons are illegal in port names, so fix that */ + + string nom = _name.val(); + replace_all (nom, ":", ";"); + + snprintf (buf1, name_size+1, ("%.*s/%s"), limit, nom.c_str(), suffix.c_str()); + int port_number = find_port_hole (buf1); snprintf (buf2, name_size+1, "%s %d", buf1, port_number); @@ -1122,25 +1374,25 @@ IO::find_port_hole (const char* base) } -AudioPort* +boost::shared_ptr IO::audio(uint32_t n) const { return _ports.nth_audio_port (n); } -MidiPort* +boost::shared_ptr IO::midi(uint32_t n) const { return _ports.nth_midi_port (n); } /** - * Setup bundles that describe our inputs and outputs. Also creates bundles if necessary. + * Setup a bundle that describe our inputs or outputs. Also creates the bundle if necessary. */ void -IO::setup_bundles () +IO::setup_bundle () { char buf[32]; @@ -1150,20 +1402,25 @@ IO::setup_bundles () _bundle->suspend_signals (); - _bundle->set_type (default_type ()); - _bundle->remove_channels (); if (_direction == Input) { - snprintf(buf, sizeof (buf), _("%s in"), _name.c_str()); + snprintf(buf, sizeof (buf), _("%s in"), _name.val().c_str()); } else { - snprintf(buf, sizeof (buf), _("%s out"), _name.c_str()); + snprintf(buf, sizeof (buf), _("%s out"), _name.val().c_str()); } _bundle->set_name (buf); - uint32_t const ni = _ports.num_ports(); - for (uint32_t i = 0; i < ni; ++i) { - _bundle->add_channel (bundle_channel_name (i, ni)); - _bundle->set_port (i, _session.engine().make_port_name_non_relative (_ports.port(i)->name())); + + int c = 0; + for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) { + + uint32_t const N = _ports.count().get (*i); + for (uint32_t j = 0; j < N; ++j) { + _bundle->add_channel (bundle_channel_name (j, N, *i), *i); + _bundle->set_port (c, _session.engine().make_port_name_non_relative (_ports.port(*i, j)->name())); + ++c; + } + } _bundle->resume_signals (); @@ -1176,8 +1433,8 @@ IO::bundles_connected () BundleList bundles; /* User bundles */ - for (std::vector::iterator i = _bundles_connected.begin(); i != _bundles_connected.end(); ++i) { - bundles.push_back (i->bundle); + for (std::vector::iterator i = _bundles_connected.begin(); i != _bundles_connected.end(); ++i) { + bundles.push_back ((*i)->bundle); } /* Session bundles */ @@ -1205,7 +1462,7 @@ IO::bundles_connected () } } } - + return bundles; } @@ -1213,24 +1470,31 @@ IO::bundles_connected () IO::UserBundleInfo::UserBundleInfo (IO* io, boost::shared_ptr b) { bundle = b; - changed = b->Changed.connect ( - sigc::mem_fun (*io, &IO::bundle_changed) - ); + b->Changed.connect_same_thread (changed, boost::bind (&IO::bundle_changed, io, _1)); } std::string -IO::bundle_channel_name (uint32_t c, uint32_t n) const +IO::bundle_channel_name (uint32_t c, uint32_t n, DataType t) const { char buf[32]; - - switch (n) { - case 1: - return _("mono"); - case 2: - return c == 0 ? _("L") : _("R"); - default: + + if (t == DataType::AUDIO) { + + switch (n) { + case 1: + return _("mono"); + case 2: + return c == 0 ? _("L") : _("R"); + default: + snprintf (buf, sizeof(buf), _("%d"), (c + 1)); + return buf; + } + + } else { + snprintf (buf, sizeof(buf), _("%d"), (c + 1)); return buf; + } return ""; @@ -1240,37 +1504,47 @@ string IO::name_from_state (const XMLNode& node) { const XMLProperty* prop; - + if ((prop = node.property ("name")) != 0) { return prop->value(); - } - + } + return string(); } void IO::set_name_in_state (XMLNode& node, const string& new_name) { - const XMLProperty* prop; - - if ((prop = node.property ("name")) != 0) { - node.add_property ("name", new_name); - } + node.add_property (X_("name"), new_name); + XMLNodeList children = node.children (); + for (XMLNodeIterator i = children.begin(); i != children.end(); ++i) { + if ((*i)->name() == X_("Port")) { + string const old_name = (*i)->property(X_("name"))->value(); + string const old_name_second_part = old_name.substr (old_name.find_first_of ("/") + 1); + (*i)->add_property (X_("name"), string_compose ("%1/%2", new_name, old_name_second_part)); + } + } } bool -IO::connected_to (boost::shared_ptr other) const +IO::connected () const { - if (!other) { - /* do we have any connections at all? */ + /* do we have any connections at all? */ - for (PortSet::const_iterator p = _ports.begin(); p != _ports.end(); ++p) { - if (p->connected()) { - return true; - } - } + for (PortSet::const_iterator p = _ports.begin(); p != _ports.end(); ++p) { + if (p->connected()) { + return true; + } + } + + return false; +} - return false; +bool +IO::connected_to (boost::shared_ptr other) const +{ + if (!other) { + return connected (); } assert (_direction != other->direction()); @@ -1278,7 +1552,7 @@ IO::connected_to (boost::shared_ptr other) const uint32_t i, j; uint32_t no = n_ports().n_total(); uint32_t ni = other->n_ports ().n_total(); - + for (i = 0; i < no; ++i) { for (j = 0; j < ni; ++j) { if (nth(i)->connected_to (other->nth(j)->name())) { @@ -1290,22 +1564,40 @@ IO::connected_to (boost::shared_ptr other) const return false; } -void -IO::process_input (boost::shared_ptr proc, sframes_t start_frame, sframes_t end_frame, nframes_t nframes) +bool +IO::connected_to (const string& str) const { - BufferSet bufs; + for (PortSet::const_iterator i = _ports.begin(); i != _ports.end(); ++i) { + if (i->connected_to (str)) { + return true; + } + } + + return false; +} +/** Call a processor's ::run() method, giving it our buffers + * Caller must hold process lock. + */ +void +IO::process_input (boost::shared_ptr proc, framepos_t start_frame, framepos_t end_frame, pframes_t nframes) +{ /* don't read the data into new buffers - just use the port buffers directly */ - bufs.attach_buffers (_ports, nframes, 0); - proc->run (bufs, start_frame, end_frame, nframes); + if (n_ports().n_total() == 0) { + /* We have no ports, so nothing to process */ + return; + } + + _buffers.get_jack_port_addresses (_ports, nframes); + proc->run (_buffers, start_frame, end_frame, nframes, true); } void -IO::collect_input (BufferSet& bufs, nframes_t nframes, ChanCount offset) +IO::collect_input (BufferSet& bufs, pframes_t nframes, ChanCount offset) { assert(bufs.available() >= _ports.count()); - + if (_ports.count() == ChanCount::ZERO) { return; } @@ -1330,10 +1622,10 @@ IO::collect_input (BufferSet& bufs, nframes_t nframes, ChanCount offset) } void -IO::copy_to_outputs (BufferSet& bufs, DataType type, nframes_t nframes, nframes_t offset) +IO::copy_to_outputs (BufferSet& bufs, DataType type, pframes_t nframes, framecnt_t offset) { // Copy any buffers 1:1 to outputs - + PortSet::iterator o = _ports.begin(type); BufferSet::iterator i = bufs.begin(type); BufferSet::iterator prev = i; @@ -1345,7 +1637,7 @@ IO::copy_to_outputs (BufferSet& bufs, DataType type, nframes_t nframes, nframes_ ++i; ++o; } - + // Copy last buffer to any extra outputs while (o != _ports.end(type)) { @@ -1355,19 +1647,36 @@ IO::copy_to_outputs (BufferSet& bufs, DataType type, nframes_t nframes, nframes_ } } -Port* +boost::shared_ptr IO::port_by_name (const std::string& str) const { /* to be called only from ::set_state() - no locking */ for (PortSet::const_iterator i = _ports.begin(); i != _ports.end(); ++i) { - const Port& p(*i); - - if (p.name() == str) { - return const_cast(&p); + if (i->name() == str) { + return boost::const_pointer_cast (*i); } } - return 0; + return boost::shared_ptr (); +} + +bool +IO::physically_connected () const +{ + for (PortSet::const_iterator i = _ports.begin(); i != _ports.end(); ++i) { + if (i->physically_connected()) { + return true; + } + } + + return false; +} + +bool +IO::has_port (boost::shared_ptr p) const +{ + Glib::Mutex::Lock lm (io_lock); + return _ports.contains (p); }