if (plug) {
add_plugin (plug);
create_automatable_parameters ();
+ const ChanCount& sc (sidechain_input_pins ());
+ if (sc.n_audio () > 0) {
+ add_sidechain (sc.n_audio ());
+ }
}
}
for (uint32_t n = 0; n < diff; ++n) {
boost::shared_ptr<Plugin> p = plugin_factory (_plugins[0]);
add_plugin (p);
- if (active ()) {
- p->activate ();
- }
if (require_state) {
- /* XXX do something */
+ XMLNode& state = _plugins[0]->get_state ();
+ p->set_state (state, Stateful::loading_state_version);
+ }
+
+ if (active ()) {
+ p->activate ();
}
}
PluginConfigChanged (); /* EMIT SIGNAL */
}
}
+bool
+PluginInsert::set_preset_out (const ChanCount& c)
+{
+ bool changed = _preset_out != c;
+ _preset_out = c;
+ if (changed && !_custom_cfg) {
+ PluginConfigChanged (); /* EMIT SIGNAL */
+ }
+ return changed;
+}
+
+bool
+PluginInsert::add_sidechain (uint32_t n_audio)
+{
+ // caller must hold process lock
+ if (_sidechain) {
+ return false;
+ }
+ std::ostringstream n;
+ if (n_audio > 0) {
+ n << "Sidechain " << Session::next_name_id ();
+ } else {
+ n << "TO BE RESET FROM XML";
+ }
+ SideChain *sc = new SideChain (_session, n.str ());
+ _sidechain = boost::shared_ptr<SideChain> (sc);
+ _sidechain->activate ();
+ for (uint32_t n = 0; n < n_audio; ++n) {
+ _sidechain->input()->add_port ("", owner()); // add a port, don't connect.
+ }
+ PluginConfigChanged (); /* EMIT SIGNAL */
+ return true;
+}
+
+bool
+PluginInsert::del_sidechain ()
+{
+ if (!_sidechain) {
+ return false;
+ }
+ _sidechain.reset ();
+ PluginConfigChanged (); /* EMIT SIGNAL */
+ return true;
+}
+
void
PluginInsert::control_list_automation_state_changed (Evoral::Parameter which, AutoState s)
{
return _configured_in;
}
+ChanCount
+PluginInsert::internal_streams() const
+{
+ assert (_configured);
+ return _configured_internal;
+}
+
ChanCount
PluginInsert::internal_output_streams() const
{
PluginInfoPtr info = _plugins.front()->get_info();
if (info->reconfigurable_io()) {
- assert (_plugins.size() == 1);
in = _plugins.front()->input_streams();
} else {
in = info->n_inputs;
ChanCount
PluginInsert::natural_output_streams() const
{
+#ifdef MIXBUS
+ if (is_channelstrip ()) {
+ return _configured_out;
+ }
+#endif
return _plugins[0]->get_info()->n_outputs;
}
ChanCount
PluginInsert::natural_input_streams() const
{
+#ifdef MIXBUS
+ if (is_channelstrip ()) {
+ return _configured_in;
+ }
+#endif
return _plugins[0]->get_info()->n_inputs;
}
+ChanCount
+PluginInsert::sidechain_input_pins() const
+{
+ return _cached_sidechain_pins;
+}
+
bool
PluginInsert::has_no_inputs() const
{
return _plugins[0]->get_info()->n_inputs.n_audio() == 0;
}
+framecnt_t
+PluginInsert::plugin_latency () const {
+ return _plugins.front()->signal_latency ();
+}
+
bool
-PluginInsert::is_midi_instrument() const
+PluginInsert::needs_midi_input() const
{
- /* XXX more finesse is possible here. VST plugins have a
- a specific "instrument" flag, for example.
- */
- PluginInfoPtr pi = _plugins[0]->get_info();
-
- return pi->n_inputs.n_midi() != 0 &&
- pi->n_outputs.n_audio() > 0;
+ PluginInfoPtr pip = _plugins[0]->get_info();
+ if (pip->needs_midi_input ()) {
+ return true;
+ }
+ return pip->n_inputs.n_midi() != 0 && pip->n_outputs.n_audio() != 0;
}
void
void
PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t offset, bool with_auto, framepos_t now)
{
+ // TODO: atomically copy maps & _no_inplace
PinMappings in_map (_in_map);
PinMappings out_map (_out_map);
+ ChanMapping thru_map (_thru_map);
+ if (_mapping_changed) { // ToDo use a counters, increment until match.
+ _no_inplace = check_inplace ();
+ _mapping_changed = false;
+ }
+
+ if (_latency_changed) {
+ /* delaylines are configured with the max possible latency (as reported by the plugin)
+ * so this won't allocate memory (unless the plugin lied about its max latency)
+ * It may still 'click' though, since the fixed delaylines are not de-clicked.
+ * Then again plugin-latency changes are not click-free to begin with.
+ *
+ * This is also worst case, there is currently no concept of per-stream latency.
+ *
+ * e.g. Two identical latent plugins:
+ * 1st plugin: process left (latent), bypass right.
+ * 2nd plugin: bypass left, process right (latent).
+ * -> currently this yields 2 times latency of the plugin,
+ */
+ _latency_changed = false;
+ _delaybuffers.set (ChanCount::max(bufs.count(), _configured_out), plugin_latency ());
+ }
-#if 1
- // TODO optimize special case.
- // Currently this never triggers because the in_map for "Split" triggeres no_inplace.
if (_match.method == Split && !_no_inplace) {
- assert (in_map.size () == 1);
- in_map[0] = ChanMapping (ChanCount::max (natural_input_streams (), _configured_in));
- ChanCount const in_streams = internal_input_streams ();
- /* copy the first stream's audio buffer contents to the others */
- bool valid;
- uint32_t first_idx = in_map[0].get (DataType::AUDIO, 0, &valid);
- if (valid) {
- for (uint32_t i = in_streams.n_audio(); i < natural_input_streams().n_audio(); ++i) {
- uint32_t idx = in_map[0].get (DataType::AUDIO, i, &valid);
+ // TODO: also use this optimization if one source-buffer
+ // feeds _all_ *connected* inputs.
+ // currently this is *first* buffer to all only --
+ // see PluginInsert::check_inplace
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ if (_configured_internal.get (*t) == 0) {
+ continue;
+ }
+ bool valid;
+ uint32_t first_idx = in_map[0].get (*t, 0, &valid);
+ assert (valid && first_idx == 0); // check_inplace ensures this
+ /* copy the first stream's buffer contents to the others */
+ for (uint32_t i = 1; i < natural_input_streams ().get (*t); ++i) {
+ uint32_t idx = in_map[0].get (*t, i, &valid);
if (valid) {
- bufs.get_audio(idx).read_from(bufs.get_audio(first_idx), nframes, offset, offset);
+ assert (idx == 0);
+ bufs.get (*t, i).read_from (bufs.get (*t, first_idx), nframes, offset, offset);
}
}
}
+ /* the copy operation produces a linear monotonic input map */
+ in_map[0] = ChanMapping (natural_input_streams ());
}
-#endif
- bufs.set_count(ChanCount::max(bufs.count(), _configured_in));
+ bufs.set_count(ChanCount::max(bufs.count(), _configured_internal));
bufs.set_count(ChanCount::max(bufs.count(), _configured_out));
if (with_auto) {
}
#ifdef MIXBUS
- if (_plugins.front()->is_channelstrip() ) {
+ if (is_channelstrip ()) {
if (_configured_in.n_audio() > 0) {
ChanMapping mb_in_map (ChanCount::min (_configured_in, ChanCount (DataType::AUDIO, 2)));
ChanMapping mb_out_map (ChanCount::min (_configured_out, ChanCount (DataType::AUDIO, 2)));
} else
#endif
if (_no_inplace) {
+ // TODO optimize -- build maps once.
+ uint32_t pc = 0;
BufferSet& inplace_bufs = _session.get_noinplace_buffers();
ARDOUR::ChanMapping used_outputs;
- uint32_t pc = 0;
- // TODO optimize this flow. prepare during configure_io()
+ assert (inplace_bufs.count () >= natural_input_streams () + _configured_out);
+
+ /* build used-output map */
+ for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ for (uint32_t out = 0; out < natural_output_streams().get (*t); ++out) {
+ bool valid;
+ uint32_t out_idx = out_map[pc].get (*t, out, &valid);
+ if (valid) {
+ used_outputs.set (*t, out_idx, 1); // mark as used
+ }
+ }
+ }
+ }
+ /* copy thru data to outputs before processing in-place */
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ for (uint32_t out = 0; out < bufs.count().get (*t); ++out) {
+ bool valid;
+ uint32_t in_idx = thru_map.get (*t, out, &valid);
+ if (valid) {
+ uint32_t m = out + natural_input_streams ().get (*t);
+ _delaybuffers.delay (*t, out, inplace_bufs.get (*t, m), bufs.get (*t, in_idx), nframes, offset, offset);
+ used_outputs.set (*t, out, 1); // mark as used
+ }
+ }
+ }
+
+ pc = 0;
for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
ARDOUR::ChanMapping i_in_map (natural_input_streams());
- ARDOUR::ChanMapping i_out_map;
+ ARDOUR::ChanMapping i_out_map (out_map[pc]);
ARDOUR::ChanCount mapped;
- ARDOUR::ChanCount backmap;
- // map inputs sequentially
+ /* map inputs sequentially */
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
for (uint32_t in = 0; in < natural_input_streams().get (*t); ++in) {
bool valid;
}
}
- // TODO use map_offset_to() instead ??
- backmap = mapped;
-
- // map outputs
+ /* outputs are mapped to inplace_bufs after the inputs */
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
- for (uint32_t out = 0; out < natural_output_streams().get (*t); ++out) {
- uint32_t m = mapped.get (*t);
- inplace_bufs.get (*t, m).silence (nframes, offset);
- i_out_map.set (*t, out, m);
- mapped.set (*t, m + 1);
- }
+ i_out_map.offset_to (*t, natural_input_streams ().get (*t));
}
- if ((*i)->connect_and_run(inplace_bufs, i_in_map, i_out_map, nframes, offset)) {
+ if ((*i)->connect_and_run (inplace_bufs, i_in_map, i_out_map, nframes, offset)) {
deactivate ();
}
-
- // copy back outputs
- for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
- for (uint32_t out = 0; out < natural_output_streams().get (*t); ++out) {
- uint32_t m = backmap.get (*t);
- bool valid;
- uint32_t out_idx = out_map[pc].get (*t, out, &valid);
- if (valid) {
- bufs.get (*t, out_idx).read_from (inplace_bufs.get (*t, m), nframes, offset, offset);
- used_outputs.set (*t, out_idx, 1); // mark as used
- }
- backmap.set (*t, m + 1);
- }
- }
}
- /* all instances have completed, now clear outputs that have not been written to.
- * (except midi bypass)
- */
+
+ /* all instances have completed, now copy data that was written
+ * and zero unconnected buffers */
+ ARDOUR::ChanMapping nonzero_out (used_outputs);
if (has_midi_bypass ()) {
- used_outputs.set (DataType::MIDI, 0, 1); // Midi bypass.
+ nonzero_out.set (DataType::MIDI, 0, 1); // Midi bypass.
}
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
for (uint32_t out = 0; out < bufs.count().get (*t); ++out) {
bool valid;
used_outputs.get (*t, out, &valid);
- if (valid) { continue; }
- bufs.get (*t, out).silence (nframes, offset);
+ if (!valid) {
+ nonzero_out.get (*t, out, &valid);
+ if (!valid) {
+ bufs.get (*t, out).silence (nframes, offset);
+ }
+ } else {
+ uint32_t m = out + natural_input_streams ().get (*t);
+ bufs.get (*t, out).read_from (inplace_bufs.get (*t, m), nframes, offset, offset);
+ }
}
}
-
} else {
+ /* in-place processing */
uint32_t pc = 0;
for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
if ((*i)->connect_and_run(bufs, in_map[pc], out_map[pc], nframes, offset)) {
ChanMapping in_map (natural_input_streams ());
ChanMapping out_map (natural_output_streams ());
+ // TODO run sidechain (delaylines)
for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
(*i)->connect_and_run (_session.get_scratch_buffers ((*i)->get_info()->n_inputs, true), in_map, out_map, nframes, 0);
}
}
void
-PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t /*end_frame*/, pframes_t nframes, bool)
+PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, pframes_t nframes, bool)
{
if (_pending_active) {
/* run as normal if we are active or moving from inactive to active */
+ if (_sidechain) {
+ // collect sidechain input for complete cycle (!)
+ // TODO we need delaylines here for latency compensation
+ _sidechain->run (bufs, start_frame, end_frame, nframes, true);
+ }
+
if (_session.transport_rolling() || _session.bounce_processing()) {
automation_run (bufs, start_frame, nframes);
} else {
// TODO use mapping in bypassed mode ?!
// -> do we bypass the processor or the plugin
+ // TODO include sidechain??
+
uint32_t in = input_streams ().n_audio ();
uint32_t out = output_streams().n_audio ();
if (num < _in_map.size()) {
bool changed = _in_map[num] != m;
_in_map[num] = m;
+ changed |= sanitize_maps ();
if (changed) {
PluginMapChanged (); /* EMIT SIGNAL */
+ _mapping_changed = true;
+ _session.set_dirty();
}
}
}
if (num < _out_map.size()) {
bool changed = _out_map[num] != m;
_out_map[num] = m;
+ changed |= sanitize_maps ();
if (changed) {
PluginMapChanged (); /* EMIT SIGNAL */
+ _mapping_changed = true;
+ _session.set_dirty();
}
}
}
+void
+PluginInsert::set_thru_map (ChanMapping m) {
+ bool changed = _thru_map != m;
+ _thru_map = m;
+ changed |= sanitize_maps ();
+ if (changed) {
+ PluginMapChanged (); /* EMIT SIGNAL */
+ _mapping_changed = true;
+ _session.set_dirty();
+ }
+}
+
ChanMapping
PluginInsert::input_map () const
{
bool
PluginInsert::has_midi_bypass () const
{
- if (_configured_in.n_midi () == 1 && _configured_out.n_midi () == 1 && natural_output_streams ().n_midi () == 0) {
+ if (_configured_in.n_midi () == 1 && _configured_out.n_midi () == 1
+ && natural_output_streams ().n_midi () == 0) {
+ return true;
+ }
+ return false;
+}
+
+bool
+PluginInsert::has_midi_thru () const
+{
+ if (_configured_in.n_midi () == 1 && _configured_out.n_midi () == 1
+ && natural_input_streams ().n_midi () == 0 && natural_output_streams ().n_midi () == 0) {
return true;
}
return false;
}
+#ifdef MIXBUS
+bool
+PluginInsert::is_channelstrip () const {
+ return _plugins.front()->is_channelstrip();
+}
+#endif
+
+bool
+PluginInsert::check_inplace ()
+{
+ bool inplace_ok = !_plugins.front()->inplace_broken ();
+
+ if (_thru_map.n_total () > 0) {
+ // TODO once midi-bypass is part of the mapping, ignore it
+ inplace_ok = false;
+ }
+
+ if (_match.method == Split && inplace_ok) {
+ assert (get_count() == 1);
+ assert (_in_map.size () == 1);
+ if (!_out_map[0].is_monotonic ()) {
+ inplace_ok = false;
+ }
+ if (_configured_internal != _configured_in) {
+ /* no sidechain -- TODO we could allow this with
+ * some more logic in PluginInsert::connect_and_run().
+ *
+ * PluginInsert::reset_map() already maps it.
+ */
+ inplace_ok = false;
+ }
+ /* check mapping */
+ for (DataType::iterator t = DataType::begin(); t != DataType::end() && inplace_ok; ++t) {
+ if (_configured_internal.get (*t) == 0) {
+ continue;
+ }
+ bool valid;
+ uint32_t first_idx = _in_map[0].get (*t, 0, &valid);
+ if (!valid || first_idx != 0) {
+ // so far only allow to copy the *first* stream's buffer to others
+ inplace_ok = false;
+ } else {
+ for (uint32_t i = 1; i < natural_input_streams ().get (*t); ++i) {
+ uint32_t idx = _in_map[0].get (*t, i, &valid);
+ if (valid && idx != first_idx) {
+ inplace_ok = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (inplace_ok) {
+ DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: In Place Split Map\n", name()));
+ return false;
+ }
+ }
+
+ for (uint32_t pc = 0; pc < get_count() && inplace_ok ; ++pc) {
+ if (!_in_map[pc].is_monotonic ()) {
+ inplace_ok = false;
+ }
+ if (!_out_map[pc].is_monotonic ()) {
+ inplace_ok = false;
+ }
+ }
+ DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: %2\n", name(), inplace_ok ? "In-Place" : "No Inplace Processing"));
+ return !inplace_ok; // no-inplace
+}
+
+bool
+PluginInsert::sanitize_maps ()
+{
+ bool changed = false;
+ /* strip dead wood */
+ PinMappings new_ins;
+ PinMappings new_outs;
+ ChanMapping new_thru;
+
+ for (uint32_t pc = 0; pc < get_count(); ++pc) {
+ ChanMapping new_in;
+ ChanMapping new_out;
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ for (uint32_t i = 0; i < natural_input_streams().get (*t); ++i) {
+ bool valid;
+ uint32_t idx = _in_map[pc].get (*t, i, &valid);
+ if (valid && idx < _configured_internal.get (*t)) {
+ new_in.set (*t, i, idx);
+ }
+ }
+ for (uint32_t o = 0; o < natural_output_streams().get (*t); ++o) {
+ bool valid;
+ uint32_t idx = _out_map[pc].get (*t, o, &valid);
+ if (valid && idx < _configured_out.get (*t)) {
+ new_out.set (*t, o, idx);
+ }
+ }
+ }
+ if (_in_map[pc] != new_in || _out_map[pc] != new_out) {
+ changed = true;
+ }
+ new_ins[pc] = new_in;
+ new_outs[pc] = new_out;
+ }
+
+ /* prevent dup output assignments */
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ for (uint32_t o = 0; o < _configured_out.get (*t); ++o) {
+ bool mapped = false;
+ for (uint32_t pc = 0; pc < get_count(); ++pc) {
+ bool valid;
+ uint32_t idx = new_outs[pc].get_src (*t, o, &valid);
+ if (valid && mapped) {
+ new_outs[pc].unset (*t, idx);
+ } else if (valid) {
+ mapped = true;
+ }
+ }
+ }
+ }
+
+ /* remove excess thru */
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ for (uint32_t o = 0; o < _configured_out.get (*t); ++o) {
+ bool valid;
+ uint32_t idx = _thru_map.get (*t, o, &valid);
+ if (valid && idx < _configured_internal.get (*t)) {
+ new_thru.set (*t, o, idx);
+ }
+ }
+ }
+
+ /* prevent out + thru, existing plugin outputs override thru */
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ for (uint32_t o = 0; o < _configured_out.get (*t); ++o) {
+ bool mapped = false;
+ bool valid;
+ for (uint32_t pc = 0; pc < get_count(); ++pc) {
+ new_outs[pc].get_src (*t, o, &mapped);
+ if (mapped) { break; }
+ }
+ if (!mapped) { continue; }
+ uint32_t idx = new_thru.get (*t, o, &valid);
+ if (mapped) {
+ new_thru.unset (*t, idx);
+ }
+ }
+ }
+
+ if (has_midi_bypass ()) {
+ // TODO: include midi-bypass in the thru set,
+ // remove dedicated handling.
+ new_thru.unset (DataType::MIDI, 0);
+ }
+
+ if (_in_map != new_ins || _out_map != new_outs || _thru_map != new_thru) {
+ changed = true;
+ }
+ _in_map = new_ins;
+ _out_map = new_outs;
+ _thru_map = new_thru;
+
+ return changed;
+}
+
bool
PluginInsert::reset_map (bool emit)
{
- uint32_t pc = 0;
const PinMappings old_in (_in_map);
const PinMappings old_out (_out_map);
_in_map.clear ();
_out_map.clear ();
- for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
- if (_match.method == Split) {
- _in_map[pc] = ChanMapping ();
- /* connect inputs in round-robin fashion */
- for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
- const uint32_t cend = _configured_in.get (*t);
- if (cend == 0) { continue; }
- uint32_t c = 0;
- for (uint32_t in = 0; in < natural_input_streams().get (*t); ++in) {
- _in_map[pc].set (*t, in, c);
- c = c + 1 % cend;
+ _thru_map = ChanMapping ();
+
+ /* build input map */
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ uint32_t sc = 0; // side-chain round-robin (all instances)
+ uint32_t pc = 0;
+ for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+ const uint32_t nis = natural_input_streams ().get(*t);
+ const uint32_t stride = nis - sidechain_input_pins().get (*t);
+
+ /* SC inputs are last in the plugin-insert.. */
+ const uint32_t sc_start = _configured_in.get (*t);
+ const uint32_t sc_len = _configured_internal.get (*t) - sc_start;
+ /* ...but may not be at the end of the plugin ports.
+ * in case the side-chain is not the last port, shift connections back.
+ * and connect to side-chain
+ */
+ uint32_t shift = 0;
+ uint32_t ic = 0; // split inputs
+ const uint32_t cend = _configured_in.get (*t);
+
+ for (uint32_t in = 0; in < nis; ++in) {
+ const Plugin::IOPortDescription& iod (_plugins[pc]->describe_io_port (*t, true, in));
+ if (iod.is_sidechain) {
+ /* connect sidechain sinks to sidechain inputs in round-robin fashion */
+ if (sc_len > 0) {// side-chain may be hidden
+ _in_map[pc].set (*t, in, sc_start + sc);
+ sc = (sc + 1) % sc_len;
+ }
+ ++shift;
+ } else {
+ if (_match.method == Split) {
+ if (cend == 0) { continue; }
+ if (_strict_io && ic + stride * pc >= cend) {
+ break;
+ }
+ /* connect *no* sidechain sinks in round-robin fashion */
+ _in_map[pc].set (*t, in, ic + stride * pc);
+ if (_strict_io && (ic + 1) == cend) {
+ break;
+ }
+ ic = (ic + 1) % cend;
+ } else {
+ uint32_t s = in - shift;
+ if (stride * pc + s < cend) {
+ _in_map[pc].set (*t, in, s + stride * pc);
+ }
+ }
}
}
- } else {
- _in_map[pc] = ChanMapping (ChanCount::min (natural_input_streams (), _configured_in));
}
- _out_map[pc] = ChanMapping (ChanCount::min (natural_output_streams(), _configured_out));
+ }
+ /* build output map */
+ uint32_t pc = 0;
+ for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+ _out_map[pc] = ChanMapping (ChanCount::min (natural_output_streams(), _configured_out));
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
- _in_map[pc].offset_to(*t, pc * natural_input_streams().get(*t));
_out_map[pc].offset_to(*t, pc * natural_output_streams().get(*t));
}
}
+
+ sanitize_maps ();
if (old_in == _in_map && old_out == _out_map) {
return false;
}
if (emit) {
PluginMapChanged (); /* EMIT SIGNAL */
+ _mapping_changed = true;
+ _session.set_dirty();
}
return true;
}
{
Match old_match = _match;
ChanCount old_in;
+ ChanCount old_internal;
ChanCount old_out;
if (_configured) {
old_in = _configured_in;
+ old_internal = _configured_internal;
old_out = _configured_out;
}
_configured_in = in;
+ _configured_internal = in;
_configured_out = out;
+ if (_sidechain) {
+ /* TODO hide midi-bypass, and custom outs. Best /fake/ "out" here.
+ * (currently _sidechain->configure_io always succeeds
+ * since Processor::configure_io() succeeds)
+ */
+ if (!_sidechain->configure_io (in, out)) {
+ DEBUG_TRACE (DEBUG::ChanMapping, "Sidechain configuration failed\n");
+ return false;
+ }
+ _configured_internal += _sidechain->input()->n_ports();
+ }
+
/* get plugin configuration */
_match = private_can_support_io_configuration (in, out);
-#ifndef NDEBUG // XXX
- cout << "Match '" << name() << "': " << _match;
+#ifndef NDEBUG
+ if (DEBUG_ENABLED(DEBUG::ChanMapping)) {
+ DEBUG_STR_DECL(a);
+ DEBUG_STR_APPEND(a, string_compose ("Match '%1': ", name()));
+ DEBUG_STR_APPEND(a, _match);
+ DEBUG_TRACE (DEBUG::ChanMapping, DEBUG_STR(a).str());
+ }
#endif
/* set the matching method and number of plugins that we will use to meet this configuration */
break;
case Delegate:
{
- ChanCount dout;
+ ChanCount dout (in); // hint
+ if (_custom_cfg) {
+ dout = _custom_out;
+ } else if (_preset_out.n_audio () > 0) {
+ dout.set (DataType::AUDIO, _preset_out.n_audio ());
+ } else if (dout.n_midi () > 0 && dout.n_audio () == 0) {
+ dout.set (DataType::AUDIO, 2);
+ }
+ if (out.n_audio () == 0) { out.set (DataType::AUDIO, 1); }
ChanCount useins;
bool const r = _plugins.front()->can_support_io_configuration (in, dout, &useins);
assert (r);
- assert (_match.strict_io || dout.n_audio() == out.n_audio()); // sans midi bypass
if (useins.n_audio() == 0) {
useins = in;
}
+ DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("Delegate configuration: %1 %2 %3\n", name(), useins, dout));
+
if (_plugins.front()->configure_io (useins, dout) == false) {
PluginIoReConfigure (); /* EMIT SIGNAL */
_configured = false;
}
bool mapping_changed = false;
- if (old_in == in && old_out == out && _configured
+ if (old_in == in && old_out == out
+ && _configured
&& old_match.method == _match.method
&& _in_map.size() == _out_map.size()
&& _in_map.size() == get_count ()
) {
- /* If the configuraton has not changed, keep the mapping */
- } else if (_match.custom_cfg && _configured) {
- /* strip dead wood */
- for (uint32_t pc = 0; pc < get_count(); ++pc) {
- ChanMapping new_in;
- ChanMapping new_out;
- for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
- for (uint32_t i = 0; i < natural_input_streams().get (*t); ++i) {
- bool valid;
- uint32_t idx = _in_map[pc].get (*t, i, &valid);
- if (valid && idx <= in.get (*t)) {
- new_in.set (*t, i, idx);
- }
- }
- for (uint32_t o = 0; o < natural_output_streams().get (*t); ++o) {
- bool valid;
- uint32_t idx = _out_map[pc].get (*t, o, &valid);
- if (valid && idx <= out.get (*t)) {
- new_out.set (*t, o, idx);
- }
- }
- }
- if (_in_map[pc] != new_in || _out_map[pc] != new_out) {
- mapping_changed = true;
- }
- _in_map[pc] = new_in;
- _out_map[pc] = new_out;
+ /* If the configuration has not changed, keep the mapping */
+ if (old_internal != _configured_internal) {
+ mapping_changed = sanitize_maps ();
}
+ } else if (_match.custom_cfg && _configured) {
+ mapping_changed = sanitize_maps ();
} else {
+#ifdef MIXBUS
+ if (is_channelstrip ()) { _maps_from_state = false; }
+#endif
if (_maps_from_state) {
_maps_from_state = false;
mapping_changed = true;
+ sanitize_maps ();
} else {
/* generate a new mapping */
mapping_changed = reset_map (false);
if (mapping_changed) {
PluginMapChanged (); /* EMIT SIGNAL */
-#ifndef NDEBUG // XXX
- uint32_t pc = 0;
- cout << "----<<----\n";
- for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
- cout << "Channel Map for " << name() << " plugin " << pc << "\n";
- cout << " * Inputs:\n" << _in_map[pc];
- cout << " * Outputs:\n" << _out_map[pc];
+
+#ifndef NDEBUG
+ if (DEBUG_ENABLED(DEBUG::ChanMapping)) {
+ uint32_t pc = 0;
+ DEBUG_STR_DECL(a);
+ DEBUG_STR_APPEND(a, "\n--------<<--------\n");
+ for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+ if (pc > 0) {
+ DEBUG_STR_APPEND(a, "----><----\n");
+ }
+ DEBUG_STR_APPEND(a, string_compose ("Channel Map for %1 plugin %2\n", name(), pc));
+ DEBUG_STR_APPEND(a, " * Inputs:\n");
+ DEBUG_STR_APPEND(a, _in_map[pc]);
+ DEBUG_STR_APPEND(a, " * Outputs:\n");
+ DEBUG_STR_APPEND(a, _out_map[pc]);
+ }
+ DEBUG_STR_APPEND(a, " * Thru:\n");
+ DEBUG_STR_APPEND(a, _thru_map);
+ DEBUG_STR_APPEND(a, "-------->>--------\n");
+ DEBUG_TRACE (DEBUG::ChanMapping, DEBUG_STR(a).str());
}
- cout << "---->>----\n";
#endif
}
- // auto-detect if inplace processing is possible
- bool inplace_ok = true;
- for (uint32_t pc = 0; pc < get_count() && inplace_ok ; ++pc) {
- if (!_in_map[pc].is_monotonic ()) {
- inplace_ok = false;
- }
- if (!_out_map[pc].is_monotonic ()) {
- inplace_ok = false;
- }
- }
- _no_inplace = !inplace_ok || _plugins.front()->inplace_broken ();
+ _no_inplace = check_inplace ();
+ _mapping_changed = false;
+
+ /* only the "noinplace_buffers" thread buffers need to be this large,
+ * this can be optimized. other buffers are fine with
+ * ChanCount::max (natural_input_streams (), natural_output_streams())
+ * and route.cc's max (configured_in, configured_out)
+ *
+ * no-inplace copies "thru" outputs (to emulate in-place) for
+ * all outputs (to prevent overwrite) into a temporary space
+ * which also holds input buffers (in case the plugin does process
+ * in-place and overwrites those).
+ *
+ * this buffers need to be at least as
+ * natural_input_streams () + possible outputs.
+ *
+ * sidechain inputs add a constraint on the input:
+ * configured input + sidechain (=_configured_internal)
+ */
+ _required_buffers =ChanCount::max (_configured_internal,
+ natural_input_streams () + ChanCount::max (_configured_out, natural_output_streams () * get_count ()));
- if (old_in != in || old_out != out
+ if (old_in != in || old_out != out || old_internal != _configured_internal
|| (old_match.method != _match.method && (old_match.method == Split || _match.method == Split))
) {
PluginIoReConfigure (); /* EMIT SIGNAL */
}
+ _delaybuffers.configure (_configured_out, _plugins.front()->max_latency ());
+ _latency_changed = true;
+
// we don't know the analysis window size, so we must work with the
// current buffer size here. each request for data fills in these
// buffers and the analyser makes sure it gets enough data for the
bool
PluginInsert::can_support_io_configuration (const ChanCount& in, ChanCount& out)
{
+ if (_sidechain) {
+ _sidechain->can_support_io_configuration (in, out); // never fails, sets "out"
+ }
return private_can_support_io_configuration (in, out).method != Impossible;
}
+PluginInsert::Match
+PluginInsert::private_can_support_io_configuration (ChanCount const& in, ChanCount& out) const
+{
+ if (!_custom_cfg && _preset_out.n_audio () > 0) {
+ // preseed hint (for variable i/o)
+ out.set (DataType::AUDIO, _preset_out.n_audio ());
+ }
+
+ Match rv = internal_can_support_io_configuration (in, out);
+
+ if (!_custom_cfg && _preset_out.n_audio () > 0) {
+ DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("using output preset: %1 %2\n", name(), _preset_out));
+ out.set (DataType::AUDIO, _preset_out.n_audio ());
+ }
+ return rv;
+}
+
/** A private version of can_support_io_configuration which returns the method
* by which the configuration can be matched, rather than just whether or not
* it can be.
*/
PluginInsert::Match
-PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanCount& out) const
+PluginInsert::internal_can_support_io_configuration (ChanCount const & inx, ChanCount& out) const
{
if (_plugins.empty()) {
return Match();
}
+#ifdef MIXBUS
+ if (is_channelstrip ()) {
+ out = inx;
+ return Match (ExactMatch, 1);
+ }
+#endif
+
/* if a user specified a custom cfg, so be it. */
if (_custom_cfg) {
+ PluginInfoPtr info = _plugins.front()->get_info();
out = _custom_out;
- return Match (ExactMatch, get_count(), false, true); // XXX
+ if (info->reconfigurable_io()) {
+ return Match (Delegate, get_count(), _strict_io, true);
+ } else {
+ return Match (ExactMatch, get_count(), _strict_io, true);
+ }
}
/* try automatic configuration */
PluginInfoPtr info = _plugins.front()->get_info();
ChanCount inputs = info->n_inputs;
ChanCount outputs = info->n_outputs;
- ChanCount midi_bypass;
- if (inx.get(DataType::MIDI) == 1 && outputs.get(DataType::MIDI) == 0) {
- midi_bypass.set (DataType::MIDI, 1);
- }
/* handle case strict-i/o */
if (_strict_io && m.method != Impossible) {
m.strict_io = true;
/* special case MIDI instruments */
- if (is_midi_instrument()) {
+ if (needs_midi_input ()) {
// output = midi-bypass + at most master-out channels.
ChanCount max_out (DataType::AUDIO, 2); // TODO use master-out
max_out.set (DataType::MIDI, out.get(DataType::MIDI));
out = ChanCount::min (out, max_out);
+ DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("special case strict-i/o instrument: %1\n", name()));
return m;
}
* at least enough to feed every output port. */
uint32_t f = 1; // at least one. e.g. control data filters, no in, no out.
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
- uint32_t nin = inputs.get (*t);
- if (nin == 0 || inx.get(*t) == 0) { continue; }
- f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nin));
+ uint32_t nout = outputs.get (*t);
+ if (nout == 0 || inx.get(*t) == 0) { continue; }
+ f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nout));
}
- out = inx + midi_bypass;
- return Match (Replicate, f);
+ out = inx;
+ DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("special case strict-i/o generator: %1\n", name()));
+ return Match (Replicate, f, _strict_io);
}
break;
- case Split:
- break;
default:
break;
}
- out = inx + midi_bypass;
- if (inx.get(DataType::MIDI) == 1
- && out.get (DataType::MIDI) == 0
- && outputs.get(DataType::MIDI) == 0) {
- out += ChanCount (DataType::MIDI, 1);
- }
+ out = inx;
return m;
}
return m;
}
+ ChanCount ns_inputs = inputs - sidechain_input_pins ();
+
+ DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("resolving 'Impossible' match for %1\n", name()));
+
if (info->reconfigurable_io()) {
ChanCount useins;
+ out = inx; // hint
+ if (out.n_midi () > 0 && out.n_audio () == 0) { out.set (DataType::AUDIO, 2); }
+ if (out.n_audio () == 0) { out.set (DataType::AUDIO, 1); }
bool const r = _plugins.front()->can_support_io_configuration (inx, out, &useins);
if (!r) {
// houston, we have a problem.
return Match (Impossible, 0);
}
- return Match (Delegate, 1);
+ return Match (Delegate, 1, _strict_io);
+ }
+
+ ChanCount midi_bypass;
+ if (inx.get(DataType::MIDI) == 1 && outputs.get(DataType::MIDI) == 0) {
+ midi_bypass.set (DataType::MIDI, 1);
}
- // add at least as many plugins so that output count matches input count
+ // add at least as many plugins so that output count matches input count (w/o sidechain pins)
uint32_t f = 0;
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
- uint32_t nin = inputs.get (*t);
+ uint32_t nin = ns_inputs.get (*t);
uint32_t nout = outputs.get (*t);
if (nin == 0 || inx.get(*t) == 0) { continue; }
// prefer floor() so the count won't overly increase IFF (nin < nout)
}
if (f > 0 && outputs * f >= _configured_out) {
out = outputs * f + midi_bypass;
- return Match (Replicate, f);
+ return Match (Replicate, f, _strict_io);
+ }
+
+ // add at least as many plugins needed to connect all inputs (w/o sidechain pins)
+ f = 0;
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ uint32_t nin = ns_inputs.get (*t);
+ if (nin == 0 || inx.get(*t) == 0) { continue; }
+ f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nin));
+ }
+ if (f > 0) {
+ out = outputs * f + midi_bypass;
+ return Match (Replicate, f, _strict_io);
}
// add at least as many plugins needed to connect all inputs
f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nin));
}
out = outputs * f + midi_bypass;
- return Match (Replicate, f);
+ return Match (Replicate, f, _strict_io);
}
/* this is the original Ardour 3/4 behavior, mainly for backwards compatibility */
ChanCount midi_bypass;
if (info->reconfigurable_io()) {
- /* Plugin has flexible I/O, so delegate to it */
+ /* Plugin has flexible I/O, so delegate to it
+ * pre-seed outputs, plugin tries closest match
+ */
+ out = in; // hint
+ if (out.n_midi () > 0 && out.n_audio () == 0) { out.set (DataType::AUDIO, 2); }
+ if (out.n_audio () == 0) { out.set (DataType::AUDIO, 1); }
bool const r = _plugins.front()->can_support_io_configuration (in, out);
if (!r) {
return Match (Impossible, 0);
ChanCount inputs = info->n_inputs;
ChanCount outputs = info->n_outputs;
+ ChanCount ns_inputs = inputs - sidechain_input_pins ();
if (in.get(DataType::MIDI) == 1 && outputs.get(DataType::MIDI) == 0) {
- DEBUG_TRACE ( DEBUG::Processors, string_compose ("bypassing midi-data around %1\n", name()));
+ DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("bypassing midi-data around %1\n", name()));
midi_bypass.set (DataType::MIDI, 1);
}
if (in.get(DataType::MIDI) == 1 && inputs.get(DataType::MIDI) == 0) {
- DEBUG_TRACE ( DEBUG::Processors, string_compose ("hiding midi-port from plugin %1\n", name()));
+ DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("hiding midi-port from plugin %1\n", name()));
in.set(DataType::MIDI, 0);
}
+ // add internally provided sidechain ports
+ ChanCount insc = in + sidechain_input_ports ();
+
bool no_inputs = true;
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
if (inputs.get (*t) != 0) {
return Match (NoInputs, 1);
}
- /* Plugin inputs match requested inputs exactly */
- if (inputs == in) {
+ /* Plugin inputs match requested inputs + side-chain-ports exactly */
+ if (inputs == insc) {
+ out = outputs + midi_bypass;
+ return Match (ExactMatch, 1);
+ }
+
+ /* Plugin inputs matches without side-chain-pins */
+ if (ns_inputs == in) {
out = outputs + midi_bypass;
return Match (ExactMatch, 1);
}
uint32_t f = 0;
bool can_replicate = true;
- for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ for (DataType::iterator t = DataType::begin(); t != DataType::end() && can_replicate; ++t) {
- uint32_t nin = inputs.get (*t);
+ // ignore side-chains
+ uint32_t nin = ns_inputs.get (*t);
// No inputs of this type
if (nin == 0 && in.get(*t) == 0) {
bool can_split = true;
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
- bool const can_split_type = (in.get (*t) == 1 && inputs.get (*t) > 1);
+ bool const can_split_type = (in.get (*t) == 1 && ns_inputs.get (*t) > 1);
bool const nothing_to_do_for_type = (in.get (*t) == 0 && inputs.get (*t) == 0);
if (!can_split_type && !nothing_to_do_for_type) {
* in case the plugin goes missing) */
node.add_child_nocopy (* _configured_in.state (X_("ConfiguredInput")));
node.add_child_nocopy (* _configured_out.state (X_("ConfiguredOutput")));
+ node.add_child_nocopy (* _preset_out.state (X_("PresetOutput")));
/* save custom i/o config */
node.add_property("custom", _custom_cfg ? "yes" : "no");
snprintf (tmp, sizeof(tmp), "OutputMap-%d", pc);
node.add_child_nocopy (* _out_map[pc].state (tmp));
}
+ node.add_child_nocopy (* _thru_map.state ("ThruMap"));
+
+ if (_sidechain) {
+ node.add_child_nocopy (_sidechain->state (full));
+ }
_plugins[0]->set_insert_id(this->id());
node.add_child_nocopy (_plugins[0]->get_state());
if ((*i)->name() == X_("ConfiguredOutput")) {
_custom_out = ChanCount(**i);
}
- if (strncmp ((*i)->name().c_str(), X_("InputMap-"), 9) == 0) {
+ if ((*i)->name() == X_("PresetOutput")) {
+ _preset_out = ChanCount(**i);
+ }
+ if (strncmp ((*i)->name ().c_str(), X_("InputMap-"), 9) == 0) {
long pc = atol (&((*i)->name().c_str()[9]));
if (pc >=0 && pc <= get_count()) {
_in_map[pc] = ChanMapping (**i);
++in_maps;
}
}
- if (strncmp ((*i)->name().c_str(), X_("OutputMap-"), 10) == 0) {
+ if (strncmp ((*i)->name ().c_str(), X_("OutputMap-"), 10) == 0) {
long pc = atol (&((*i)->name().c_str()[10]));
if (pc >=0 && pc <= get_count()) {
_out_map[pc] = ChanMapping (**i);
++out_maps;
}
}
+ if ((*i)->name () == "ThruMap") {
+ _thru_map = ChanMapping (**i);
+ }
+
+ // sidechain is a Processor (IO)
+ if ((*i)->name () == Processor::state_node_name) {
+ if (!_sidechain) {
+ add_sidechain (0);
+ }
+ _sidechain->set_state (**i, version);
+ }
}
+
if (in_maps == out_maps && out_maps >0 && out_maps == get_count()) {
_maps_from_state = true;
}
}
}
+ PluginConfigChanged (); /* EMIT SIGNAL */
return 0;
}
plugin->set_insert_id (this->id());
if (_plugins.empty()) {
- /* first (and probably only) plugin instance - connect to relevant signals
- */
+ /* first (and probably only) plugin instance - connect to relevant signals */
plugin->ParameterChangedExternally.connect_same_thread (*this, boost::bind (&PluginInsert::parameter_changed_externally, this, _1, _2));
- plugin->StartTouch.connect_same_thread (*this, boost::bind (&PluginInsert::start_touch, this, _1));
- plugin->EndTouch.connect_same_thread (*this, boost::bind (&PluginInsert::end_touch, this, _1));
+ plugin->StartTouch.connect_same_thread (*this, boost::bind (&PluginInsert::start_touch, this, _1));
+ plugin->EndTouch.connect_same_thread (*this, boost::bind (&PluginInsert::end_touch, this, _1));
+ plugin->LatencyChanged.connect_same_thread (*this, boost::bind (&PluginInsert::latency_changed, this, _1, _2));
+ // cache sidechain port count
+ _cached_sidechain_pins.reset ();
+ const ChanCount& nis (plugin->get_info()->n_inputs);
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ for (uint32_t in = 0; in < nis.get (*t); ++in) {
+ const Plugin::IOPortDescription& iod (plugin->describe_io_port (*t, true, in));
+ if (iod.is_sidechain) {
+ _cached_sidechain_pins.set (*t, 1 + _cached_sidechain_pins.n(*t));
+ }
+ }
+ }
}
-
+#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT)
+ boost::shared_ptr<VSTPlugin> vst = boost::dynamic_pointer_cast<VSTPlugin> (plugin);
+ if (vst) {
+ vst->set_insert (this, _plugins.size ());
+ }
+#endif
_plugins.push_back (plugin);
}
}
}
+void
+PluginInsert::latency_changed (framecnt_t, framecnt_t)
+{
+ // this is called in RT context, LatencyChanged is emitted after run()
+ _latency_changed = true;
+}
+
void
PluginInsert::start_touch (uint32_t param_id)
{