PBD::Signal0<void> Route::SyncOrderKeys;
PBD::Signal0<void> Route::RemoteControlIDChange;
+/** Base class for all routable/mixable objects (tracks and busses) */
Route::Route (Session& sess, string name, Flag flg, DataType default_type)
: SessionObject (sess, name)
, Automatable (sess)
, _track_number (0)
, _in_configure_processors (false)
, _initial_io_setup (false)
+ , _in_sidechain_setup (false)
, _strict_io (false)
, _custom_meter_position_noted (false)
{
processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
set_processor_positions ();
+ boost::shared_ptr<Send> send;
+ if ((send = boost::dynamic_pointer_cast<Send> (processor))) {
+ send->SelfDestruct.connect_same_thread (*this,
+ boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr<Processor> (processor)));
+ }
+
return 0;
}
+void
+Route::processor_selfdestruct (boost::weak_ptr<Processor> wp)
+{
+ /* We cannot destruct the processor here (usually RT-thread
+ * with various locks held - in case of sends also io_locks).
+ * Queue for deletion in low-priority thread.
+ */
+ Glib::Threads::Mutex::Lock lx (selfdestruct_lock);
+ selfdestruct_sequence.push_back (wp);
+}
+
bool
Route::add_processor_from_xml_2X (const XMLNode& node, int version)
{
processor.reset (new UnknownProcessor (_session, node));
} else {
processor.reset (new PluginInsert (_session));
+ processor->set_owner (this);
}
} else {
boost::shared_ptr<PluginInsert> pi;
if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*i)) != 0) {
- pi->set_count (1); // why? configure_processors_unlocked() will re-do this
pi->set_strict_io (_strict_io);
}
run.
*/
- boost::shared_ptr<IOProcessor> iop;
+ boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor> (*i);
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*i);
- if ((iop = boost::dynamic_pointer_cast<IOProcessor> (*i)) != 0) {
+ if (pi != 0) {
+ assert (iop == 0);
+ iop = pi->sidechain();
+ }
+
+ if (iop != 0) {
iop->disconnect ();
}
run.
*/
- boost::shared_ptr<IOProcessor> iop;
+ boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(processor);
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(processor);
+ if (pi != 0) {
+ assert (iop == 0);
+ iop = pi->sidechain();
+ }
- if ((iop = boost::dynamic_pointer_cast<IOProcessor> (processor)) != 0) {
+ if (iop != 0) {
iop->disconnect ();
}
for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p, ++index) {
if ((*p)->can_support_io_configuration(in, out)) {
+
+ if (boost::dynamic_pointer_cast<Delivery> (*p)
+ && boost::dynamic_pointer_cast<Delivery> (*p)->role() == Delivery::Main
+ && !(is_monitor() || is_auditioner())
+ && ( _strict_io || Profile->get_mixbus ())) {
+ /* with strict I/O the panner + output are forced to
+ * follow the last processor's output.
+ *
+ * Delivery::can_support_io_configuration() will only add ports,
+ * but not remove excess ports.
+ *
+ * This works because the delivery only requires
+ * as many outputs as there are inputs.
+ * Delivery::configure_io() will do the actual removal
+ * by calling _output->ensure_io()
+ */
+ if (!is_master() && _session.master_out ()) {
+ /* ..but at least as many as there are master-inputs */
+ // XXX this may need special-casing for mixbus (master-outputs)
+ // and should maybe be a preference anyway ?!
+ out = ChanCount::max (in, _session.master_out ()->n_inputs ());
+ } else {
+ out = in;
+ }
+ }
+
DEBUG_TRACE (DEBUG::Processors, string_compose ("\t%1 ID=%2 in=%3 out=%4\n",(*p)->name(), (*p)->id(), in, out));
configuration.push_back(make_pair(in, out));
if (!(*p)->configure_io(c->first, c->second)) {
DEBUG_TRACE (DEBUG::Processors, string_compose ("%1: configuration failed\n", _name));
+ _in_configure_processors = false;
+ return -1;
}
processor_max_streams = ChanCount::max(processor_max_streams, c->first);
processor_max_streams = ChanCount::max(processor_max_streams, c->second);
+ boost::shared_ptr<IOProcessor> iop;
boost::shared_ptr<PluginInsert> pi;
if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
/* plugins connected via Split or Hide Match may have more channels.
* route/scratch buffers are needed for all of them
* The configuration may only be a subset (both input and output)
*/
- processor_max_streams = ChanCount::max(processor_max_streams, pi->input_streams());
- processor_max_streams = ChanCount::max(processor_max_streams, pi->output_streams());
- processor_max_streams = ChanCount::max(processor_max_streams, pi->natural_input_streams() * pi->get_count());
- processor_max_streams = ChanCount::max(processor_max_streams, pi->natural_output_streams() * pi->get_count());
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->required_buffers());
+ }
+ else if ((iop = boost::dynamic_pointer_cast<IOProcessor>(*p)) != 0) {
+ processor_max_streams = ChanCount::max(processor_max_streams, iop->natural_input_streams());
+ processor_max_streams = ChanCount::max(processor_max_streams, iop->natural_output_streams());
}
out = c->second;
}
bool
-Route::reset_plugin_insert (boost::shared_ptr<Processor> proc)
+Route::add_remove_sidechain (boost::shared_ptr<Processor> proc, bool add)
{
- ChanCount unused;
- return customize_plugin_insert (proc, 0, unused);
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
+ return false;
+ }
+
+ if (pi->has_sidechain () == add) {
+ return true; // ?? call failed, but result is as expected.
+ }
+
+ {
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc);
+ if (i == _processors.end ()) {
+ return false;
+ }
+ }
+
+ {
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); // take before Writerlock to avoid deadlock
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+ PBD::Unwinder<bool> uw (_in_sidechain_setup, true);
+
+ lx.release (); // IO::add_port() and ~IO takes process lock - XXX check if this is safe
+ if (add) {
+ if (!pi->add_sidechain ()) {
+ return false;
+ }
+ } else {
+ if (!pi->del_sidechain ()) {
+ return false;
+ }
+ }
+
+ lx.acquire ();
+ list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
+ lx.release ();
+
+ if (c.empty()) {
+ if (add) {
+ pi->del_sidechain ();
+ } else {
+ pi->add_sidechain ();
+ // TODO restore side-chain's state.
+ }
+ return false;
+ }
+ lx.acquire ();
+ configure_processors_unlocked (0);
+ }
+
+ if (pi->has_sidechain ()) {
+ pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2));
+ }
+
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return true;
}
bool
-Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t count, ChanCount outs)
+Route::plugin_preset_output (boost::shared_ptr<Processor> proc, ChanCount outs)
{
- if (_strict_io) {
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
return false;
}
+ {
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc);
+ if (i == _processors.end ()) {
+ return false;
+ }
+ }
+
+ {
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+
+ const ChanCount& old (pi->preset_out ());
+ if (!pi->set_preset_out (outs)) {
+ return true; // no change, OK
+ }
+
+ list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
+ if (c.empty()) {
+ /* not possible */
+ pi->set_preset_out (old);
+ return false;
+ }
+ configure_processors_unlocked (0);
+ }
+
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return true;
+}
+
+bool
+Route::reset_plugin_insert (boost::shared_ptr<Processor> proc)
+{
+ ChanCount unused;
+ return customize_plugin_insert (proc, 0, unused, unused);
+}
+
+bool
+Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t count, ChanCount outs, ChanCount sinks)
+{
boost::shared_ptr<PluginInsert> pi;
if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
return false;
}
{
- bool found = false;
Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
- for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p) {
- if (*p == proc) {
- found = true;
- break;
- }
- }
- if (!found) {
+ ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc);
+ if (i == _processors.end ()) {
return false;
}
}
{
Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
Glib::Threads::RWLock::WriterLock lm (_processor_lock);
- ProcessorState pstate (this);
- assert (!pi->strict_io ());
- bool old_cust = pi->custom_cfg ();
- uint32_t old_cnt = pi->get_count ();
- ChanCount old_chan = pi->output_streams ();
+ bool old_cust = pi->custom_cfg ();
+ uint32_t old_cnt = pi->get_count ();
+ ChanCount old_chan = pi->output_streams ();
+ ChanCount old_sinks = pi->natural_input_streams ();
if (count == 0) {
pi->set_custom_cfg (false);
pi->set_custom_cfg (true);
pi->set_count (count);
pi->set_outputs (outs);
+ pi->set_sinks (sinks);
}
list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
/* not possible */
pi->set_count (old_cnt);
+ pi->set_sinks (old_sinks);
pi->set_outputs (old_chan);
pi->set_custom_cfg (old_cust);
bool
Route::set_strict_io (const bool enable)
{
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+
if (_strict_io != enable) {
_strict_io = enable;
Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
}
lm.release ();
- {
- Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
- configure_processors (0);
- }
+ configure_processors (0);
+ lx.release ();
+
processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
_session.set_dirty ();
}
processor.reset (new UnknownProcessor (_session, **niter));
} else {
processor.reset (new PluginInsert (_session));
+ processor->set_owner (this);
if (_strict_io) {
boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(processor);
pi->set_strict_io (true);
} else if (prop->value() == "send") {
processor.reset (new Send (_session, _pannable, _mute_master, Delivery::Send, true));
+ boost::shared_ptr<Send> send = boost::dynamic_pointer_cast<Send> (processor);
+ send->SelfDestruct.connect_same_thread (*this,
+ boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr<Processor> (processor)));
} else {
error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg;
processor.reset (new UnknownProcessor (_session, **niter));
}
+ /* subscribe to Sidechain IO changes */
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (processor);
+ if (pi && pi->has_sidechain ()) {
+ pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2));
+ }
+
/* we have to note the monitor send here, otherwise a new one will be created
and the state of this one will be lost.
*/
return false;
}
+IOVector
+Route::all_inputs () const
+{
+ /* TODO, if this works as expected,
+ * cache the IOVector and maintain it via
+ * input_change_handler(), sidechain_change_handler() etc
+ */
+ IOVector ios;
+ ios.push_back (_input);
+
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ for (ProcessorList::const_iterator r = _processors.begin(); r != _processors.end(); ++r) {
+
+ boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(*r);
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*r);
+ if (pi != 0) {
+ assert (iop == 0);
+ iop = pi->sidechain();
+ }
+
+ if (iop != 0 && iop->input()) {
+ ios.push_back (iop->input());
+ }
+ }
+ return ios;
+}
+
+IOVector
+Route::all_outputs () const
+{
+ IOVector ios;
+ // _output is included via Delivery
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ for (ProcessorList::const_iterator r = _processors.begin(); r != _processors.end(); ++r) {
+ boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(*r);
+ if (iop != 0 && iop->output()) {
+ ios.push_back (iop->output());
+ }
+ }
+ return ios;
+}
+
bool
Route::direct_feeds_according_to_reality (boost::shared_ptr<Route> other, bool* via_send_only)
{
DEBUG_TRACE (DEBUG::Graph, string_compose ("Feeds? %1\n", _name));
-
- if (_output->connected_to (other->input())) {
+ if (other->all_inputs().fed_by (_output)) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\tdirect FEEDS %2\n", other->name()));
if (via_send_only) {
*via_send_only = false;
}
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock); // XXX
for (ProcessorList::iterator r = _processors.begin(); r != _processors.end(); ++r) {
- boost::shared_ptr<IOProcessor> iop;
+ boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(*r);
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*r);
+ if (pi != 0) {
+ assert (iop == 0);
+ iop = pi->sidechain();
+ }
- if ((iop = boost::dynamic_pointer_cast<IOProcessor>(*r)) != 0) {
- if (iop->feeds (other)) {
+ if (iop != 0) {
+ boost::shared_ptr<const IO> iop_out = iop->output();
+ if ((iop_out && other->all_inputs().fed_by (iop_out)) || iop->feeds (other)) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\tIOP %1 does feed %2\n", iop->name(), other->name()));
if (via_send_only) {
*via_send_only = true;
return _session._current_route_graph.has (shared_from_this (), other, via_send_only);
}
+bool
+Route::feeds_according_to_graph (boost::shared_ptr<Route> other)
+{
+ return _session._current_route_graph.feeds (shared_from_this (), other);
+}
+
/** Called from the (non-realtime) butler thread when the transport is stopped */
void
Route::nonrealtime_handle_transport_stopped (bool /*abort_ignored*/, bool /*did_locate*/, bool can_flush_processors)
}
}
+void
+Route::sidechain_change_handler (IOChange change, void* src)
+{
+ if (_initial_io_setup || _in_sidechain_setup) {
+ return;
+ }
+
+ input_change_handler (change, src);
+}
+
uint32_t
Route::pans_required () const
{
g_atomic_int_set (&_pending_signals, emissions);
return true;
}
- return false;
+ return (!selfdestruct_sequence.empty ());
}
void
Route::emit_pending_signals ()
{
-
int sig = g_atomic_int_and (&_pending_signals, 0);
if (sig & EmitMeterChanged) {
_meter->emit_configuration_changed();
if (sig & EmitRtProcessorChange) {
processors_changed (RouteProcessorChange (RouteProcessorChange::RealTimeChange)); /* EMIT SIGNAL */
}
+
+ /* this would be a job for the butler.
+ * Conceptually we should not take processe/processor locks here.
+ * OTOH its more efficient (less overhead for summoning the butler and
+ * telling her what do do) and signal emission is called
+ * directly after the process callback, which decreases the chance
+ * of x-runs when taking the locks.
+ */
+ while (!selfdestruct_sequence.empty ()) {
+ Glib::Threads::Mutex::Lock lx (selfdestruct_lock);
+ if (selfdestruct_sequence.empty ()) { break; } // re-check with lock
+ boost::shared_ptr<Processor> proc = selfdestruct_sequence.back ().lock ();
+ selfdestruct_sequence.pop_back ();
+ lx.release ();
+ if (proc) {
+ remove_processor (proc);
+ }
+ }
}
void
bool
Route::output_port_count_changing (ChanCount to)
{
+ if (_strict_io && !_in_configure_processors) {
+ return true;
+ }
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
if (processor_out_streams.get(*t) > to.get(*t)) {
return true;