+}
+
+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
+{
+ ChanMapping rv;
+ uint32_t pc = 0;
+ for (PinMappings::const_iterator i = _in_map.begin (); i != _in_map.end (); ++i, ++pc) {
+ ChanMapping m (i->second);
+ const ChanMapping::Mappings& mp ((*i).second.mappings());
+ for (ChanMapping::Mappings::const_iterator tm = mp.begin(); tm != mp.end(); ++tm) {
+ for (ChanMapping::TypeMapping::const_iterator i = tm->second.begin(); i != tm->second.end(); ++i) {
+ rv.set (tm->first, i->first + pc * natural_input_streams().get(tm->first), i->second);
+ }
+ }
+ }
+ return rv;
+}
+
+ChanMapping
+PluginInsert::output_map () const
+{
+ ChanMapping rv;
+ uint32_t pc = 0;
+ for (PinMappings::const_iterator i = _out_map.begin (); i != _out_map.end (); ++i, ++pc) {
+ ChanMapping m (i->second);
+ const ChanMapping::Mappings& mp ((*i).second.mappings());
+ for (ChanMapping::Mappings::const_iterator tm = mp.begin(); tm != mp.end(); ++tm) {
+ for (ChanMapping::TypeMapping::const_iterator i = tm->second.begin(); i != tm->second.end(); ++i) {
+ rv.set (tm->first, i->first + pc * natural_output_streams().get(tm->first), i->second);
+ }
+ }
+ }
+ if (has_midi_bypass ()) {
+ rv.set (DataType::MIDI, 0, 0);
+ }
+
+ return rv;
+}
+
+bool
+PluginInsert::has_midi_bypass () const
+{
+ 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)
+{
+ const PinMappings old_in (_in_map);
+ const PinMappings old_out (_out_map);
+
+ _in_map.clear ();
+ _out_map.clear ();
+ _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);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* 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) {
+ _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;
+}
+
+bool
+PluginInsert::configure_io (ChanCount in, ChanCount out)
+{
+ 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();
+
+ // include (static_cast<Route*>owner())->name() ??
+ _sidechain->input ()-> set_pretty_name (string_compose (_("SC %1"), name ()));
+ }
+
+ /* get plugin configuration */
+ _match = private_can_support_io_configuration (in, out);
+#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 */
+ if (set_count (_match.plugins) == false) {
+ PluginIoReConfigure (); /* EMIT SIGNAL */
+ _configured = false;
+ return false;
+ }
+
+ /* configure plugins */
+ switch (_match.method) {
+ case Split:
+ case Hide:
+ if (_plugins.front()->configure_io (natural_input_streams(), out) == false) {
+ PluginIoReConfigure (); /* EMIT SIGNAL */
+ _configured = false;
+ return false;
+ }
+ break;
+ case Delegate:
+ {
+ 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);
+ 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;
+ return false;
+ }
+ }
+ break;
+ default:
+ if (_plugins.front()->configure_io (in, out) == false) {
+ PluginIoReConfigure (); /* EMIT SIGNAL */
+ _configured = false;
+ return false;
+ }
+ break;
+ }
+
+ bool mapping_changed = false;
+ 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 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
+ 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());
+ }
+#endif
+ }
+
+ _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 || 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;