plugin support for Controllable::NotAutomatable
[ardour.git] / libs / ardour / plugin_insert.cc
index 7ae6eb6a0ce5579210441ae64f8c02bb1ea238d1..291ccbfc54d0c3302614a92b781846292154f559 100644 (file)
@@ -375,36 +375,76 @@ PluginInsert::needs_midi_input() const
        return pip->n_inputs.n_midi() != 0 && pip->n_outputs.n_audio() != 0;
 }
 
+bool
+PluginInsert::has_output_presets (ChanCount in, ChanCount out)
+{
+       if (!_configured && _plugins[0]->get_info ()->reconfigurable_io ()) {
+               // collect possible configurations, prefer given in/out
+               _plugins[0]->can_support_io_configuration (in, out);
+       }
+
+       PluginOutputConfiguration ppc (_plugins[0]->possible_output ());
+
+       if (ppc.size () == 0) {
+               return false;
+       }
+       if (!strict_io () && ppc.size () == 1) {
+               return false;
+       }
+
+       if (strict_io () && ppc.size () == 1) {
+               // "stereo" is currently preferred default for instruments
+               if (ppc.find (2) != ppc.end ()) {
+                       return false;
+               }
+       }
+       if (!needs_midi_input ()) {
+                       return false;
+       }
+       return true;
+}
+
 void
 PluginInsert::create_automatable_parameters ()
 {
        assert (!_plugins.empty());
 
+       boost::shared_ptr<Plugin> plugin = _plugins.front();
        set<Evoral::Parameter> a = _plugins.front()->automatable ();
 
-       for (set<Evoral::Parameter>::iterator i = a.begin(); i != a.end(); ++i) {
-               if (i->type() == PluginAutomation) {
+       for (uint32_t i = 0; i < plugin->parameter_count(); ++i) {
+               if (!plugin->parameter_is_control (i) || !plugin->parameter_is_input (i)) {
+                       continue;
+               }
+               Evoral::Parameter param (PluginAutomation, 0, i);
 
-                       Evoral::Parameter param(*i);
+               ParameterDescriptor desc;
+               plugin->get_parameter_descriptor(i, desc);
 
-                       ParameterDescriptor desc;
-                       _plugins.front()->get_parameter_descriptor(i->id(), desc);
+               const bool automatable = a.find(param) != a.end();
 
+               if (automatable) {
                        can_automate (param);
-                       boost::shared_ptr<AutomationList> list(new AutomationList(param, desc));
-                       boost::shared_ptr<AutomationControl> c (new PluginControl(this, param, desc, list));
-                       add_control (c);
-                       _plugins.front()->set_automation_control (i->id(), c);
-               } else if (i->type() == PluginPropertyAutomation) {
-                       Evoral::Parameter param(*i);
-                       const ParameterDescriptor& desc = _plugins.front()->get_property_descriptor(param.id());
-                       if (desc.datatype != Variant::NOTHING) {
-                               boost::shared_ptr<AutomationList> list;
-                               if (Variant::type_is_numeric(desc.datatype)) {
-                                       list = boost::shared_ptr<AutomationList>(new AutomationList(param, desc));
-                               }
-                               add_control (boost::shared_ptr<AutomationControl> (new PluginPropertyControl(this, param, desc, list)));
+               }
+               boost::shared_ptr<AutomationList> list(new AutomationList(param, desc));
+               boost::shared_ptr<AutomationControl> c (new PluginControl(this, param, desc, list));
+               if (!automatable) {
+                       c->set_flags (Controllable::Flag ((int)c->flags() | Controllable::NotAutomatable));
+               }
+               add_control (c);
+               plugin->set_automation_control (i, c);
+       }
+
+       const Plugin::PropertyDescriptors& pdl (plugin->get_supported_properties ());
+       for (Plugin::PropertyDescriptors::const_iterator p = pdl.begin(); p != pdl.end(); ++p) {
+               Evoral::Parameter param (PluginPropertyAutomation, 0, p->first);
+               const ParameterDescriptor& desc = plugin->get_property_descriptor(param.id());
+               if (desc.datatype != Variant::NOTHING) {
+                       boost::shared_ptr<AutomationList> list;
+                       if (Variant::type_is_numeric(desc.datatype)) {
+                               list = boost::shared_ptr<AutomationList>(new AutomationList(param, desc));
                        }
+                       add_control (boost::shared_ptr<AutomationControl> (new PluginPropertyControl(this, param, desc, list)));
                }
        }
 }
@@ -497,6 +537,39 @@ PluginInsert::flush ()
        }
 }
 
+void
+PluginInsert::inplace_silence_unconnected (BufferSet& bufs, const PinMappings& out_map, framecnt_t nframes, framecnt_t offset) const
+{
+       // TODO optimize: store "unconnected" in a fixed set.
+       // it only changes on reconfiguration.
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               for (uint32_t out = 0; out < bufs.count().get (*t); ++out) {
+                       bool mapped = false;
+                       if (*t == DataType::MIDI && out == 0 && has_midi_bypass ()) {
+                               mapped = true; // in-place Midi bypass
+                       }
+                       for (uint32_t pc = 0; pc < get_count() && !mapped; ++pc) {
+                               PinMappings::const_iterator i = out_map.find (pc);
+                               if (i == out_map.end ()) {
+                                       continue;
+                               }
+                               const ChanMapping& outmap (i->second);
+                               for (uint32_t o = 0; o < natural_output_streams().get (*t); ++o) {
+                                       bool valid;
+                                       uint32_t idx = outmap.get (*t, o, &valid);
+                                       if (valid && idx == out) {
+                                               mapped = true;
+                                               break;
+                                       }
+                               }
+                       }
+                       if (!mapped) {
+                               bufs.get (*t, out).silence (nframes, offset);
+                       }
+               }
+       }
+}
+
 void
 PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t offset, bool with_auto, framepos_t now)
 {
@@ -598,10 +671,10 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                //std::cerr << "               streams " << internal_input_streams().n_audio() << std::endl;
                //std::cerr << "filling buffer with " << collect_signal_nframes << " frames at " << _signal_analysis_collected_nframes << std::endl;
 
-               _signal_analysis_inputs.set_count(internal_input_streams());
+               _signal_analysis_inputs.set_count(input_streams());
 
-               for (uint32_t i = 0; i < internal_input_streams().n_audio(); ++i) {
-                       _signal_analysis_inputs.get_audio(i).read_from(
+               for (uint32_t i = 0; i < input_streams().n_audio(); ++i) {
+                       _signal_analysis_inputs.get_audio(i).read_from (
                                bufs.get_audio(i),
                                collect_signal_nframes,
                                _signal_analysis_collected_nframes); // offset is for target buffer
@@ -724,30 +797,8 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                                deactivate ();
                        }
                }
-
-               // TODO optimize: store "unconnected" in a fixed set.
-               // it only changes on reconfiguration.
-               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
-                       for (uint32_t out = 0; out < bufs.count().get (*t); ++out) {
-                               bool mapped = false;
-                               if (*t == DataType::MIDI && out == 0 && has_midi_bypass ()) {
-                                       mapped = true; // in-place Midi bypass
-                               }
-                               for (uint32_t pc = 0; pc < get_count() && !mapped; ++pc) {
-                                       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) {
-                                                       mapped = true;
-                                                       break;
-                                               }
-                                       }
-                               }
-                               if (!mapped) {
-                                       bufs.get (*t, out).silence (nframes, offset);
-                               }
-                       }
-               }
+               // now silence unconnected outputs
+               inplace_silence_unconnected (bufs, _out_map, nframes, offset);
        }
 
        if (collect_signal_nframes > 0) {
@@ -755,9 +806,9 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                //std::cerr << "       output, bufs " << bufs.count().n_audio() << " count,  " << bufs.available().n_audio() << " available" << std::endl;
                //std::cerr << "               streams " << internal_output_streams().n_audio() << std::endl;
 
-               _signal_analysis_outputs.set_count(internal_output_streams());
+               _signal_analysis_outputs.set_count(output_streams());
 
-               for (uint32_t i = 0; i < internal_output_streams().n_audio(); ++i) {
+               for (uint32_t i = 0; i < output_streams().n_audio(); ++i) {
                        _signal_analysis_outputs.get_audio(i).read_from(
                                bufs.get_audio(i),
                                collect_signal_nframes,
@@ -777,6 +828,117 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
        }
 }
 
+void
+PluginInsert::bypass (BufferSet& bufs, pframes_t nframes)
+{
+       /* bypass the plugin(s) not the whole processor.
+        * -> use mappings just like connect_and_run
+        */
+
+       // TODO: atomically copy maps & _no_inplace
+       ChanMapping in_map (input_map ());
+       ChanMapping out_map (output_map ());
+       if (_mapping_changed) {
+               _no_inplace = check_inplace ();
+               _mapping_changed = false;
+       }
+
+       bufs.set_count(ChanCount::max(bufs.count(), _configured_internal));
+       bufs.set_count(ChanCount::max(bufs.count(), _configured_out));
+
+       if (_no_inplace) {
+               ChanMapping thru_map (_thru_map);
+
+               BufferSet& inplace_bufs  = _session.get_noinplace_buffers();
+               // copy all inputs
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t in = 0; in < _configured_internal.get (*t); ++in) {
+                               inplace_bufs.get (*t, in).read_from (bufs.get (*t, in), nframes, 0, 0);
+                       }
+               }
+               ARDOUR::ChanMapping used_outputs;
+               // copy thru
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t out = 0; out < _configured_out.get (*t); ++out) {
+                               bool valid;
+                               uint32_t in_idx = thru_map.get (*t, out, &valid);
+                               if (valid) {
+                                       bufs.get (*t, out).read_from (inplace_bufs.get (*t, in_idx), nframes, 0, 0);
+                                       used_outputs.set (*t, out, 1); // mark as used
+                               }
+                       }
+               }
+               // plugin no-op: assume every plugin has an internal identity map
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t out = 0; out < _configured_out.get (*t); ++out) {
+                               bool valid;
+                               uint32_t src_idx = out_map.get_src (*t, out, &valid);
+                               if (!valid) {
+                                       continue;
+                               }
+                               uint32_t in_idx = in_map.get (*t, src_idx, &valid);
+                               if (!valid) {
+                                       continue;
+                               }
+                               bufs.get (*t, out).read_from (inplace_bufs.get (*t, in_idx), nframes, 0, 0);
+                               used_outputs.set (*t, out, 1); // mark as used
+                       }
+               }
+               // now silence all unused outputs
+               if (has_midi_bypass ()) {
+                       used_outputs.set (DataType::MIDI, 0, 1); // Midi bypass.
+               }
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t out = 0; out < _configured_out.get (*t); ++out) {
+                               bool valid;
+                               used_outputs.get (*t, out, &valid);
+                               if (!valid) {
+                                               bufs.get (*t, out).silence (nframes, 0);
+                               }
+                       }
+               }
+       } else {
+               if (_match.method == Split) {
+                       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                               if (_configured_internal.get (*t) == 0) {
+                                       continue;
+                               }
+                               // copy/feeds _all_ *connected* inputs, copy the first buffer
+                               bool valid;
+                               uint32_t first_idx = in_map.get (*t, 0, &valid);
+                               assert (valid && first_idx == 0); // check_inplace ensures this
+                               for (uint32_t i = 1; i < natural_input_streams ().get (*t); ++i) {
+                                       uint32_t idx = in_map.get (*t, i, &valid);
+                                       if (valid) {
+                                               assert (idx == 0);
+                                               bufs.get (*t, i).read_from (bufs.get (*t, first_idx), nframes, 0, 0);
+                                       }
+                               }
+                       }
+               }
+
+               // apply output map and/or monotonic but not identity i/o mappings
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t out = 0; out < _configured_out.get (*t); ++out) {
+                               bool valid;
+                               uint32_t src_idx = out_map.get_src (*t, out, &valid);
+                               if (!valid) {
+                                       bufs.get (*t, out).silence (nframes, 0);
+                                       continue;
+                               }
+                               uint32_t in_idx = in_map.get (*t, src_idx, &valid);
+                               if (!valid) {
+                                       bufs.get (*t, out).silence (nframes, 0);
+                                       continue;
+                               }
+                               if (in_idx != src_idx) {
+                                       bufs.get (*t, out).read_from (bufs.get (*t, in_idx), nframes, 0, 0);
+                               }
+                       }
+               }
+       }
+}
+
 void
 PluginInsert::silence (framecnt_t nframes)
 {
@@ -820,35 +982,8 @@ PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame
                }
 
        } 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 (has_no_audio_inputs() || in == 0) {
-
-                       /* silence all (audio) outputs. Should really declick
-                        * at the transitions of "active"
-                        */
-
-                       for (uint32_t n = 0; n < out; ++n) {
-                               bufs.get_audio (n).silence (nframes);
-                       }
-
-               } else if (out > in) {
-
-                       /* not active, but something has make up for any channel count increase
-                        * for now , simply replicate last buffer
-                        */
-                       for (uint32_t n = in; n < out; ++n) {
-                               bufs.get_audio(n).read_from(bufs.get_audio(in - 1), nframes);
-                       }
-               }
-
-               bufs.count().set_audio (out);
+               bypass (bufs, nframes);
+               _delaybuffers.flush ();
        }
 
        _active = _pending_active;
@@ -1068,6 +1203,20 @@ PluginInsert::set_thru_map (ChanMapping m) {
        }
 }
 
+bool
+PluginInsert::pre_seed (const ChanCount& in, const ChanCount& out,
+               const ChanMapping& im, const ChanMapping& om, const ChanMapping& tm)
+{
+       if (_configured) { return false; }
+       _configured_in = in;
+       _configured_out = out;
+       _in_map[0] = im;
+       _out_map[0] = om;
+       _thru_map = tm;
+       _maps_from_state = in.n_total () > 0 && out.n_total () > 0;
+       return true;
+}
+
 ChanMapping
 PluginInsert::input_map () const
 {
@@ -1192,6 +1341,30 @@ PluginInsert::check_inplace ()
                        inplace_ok = false;
                }
        }
+
+       if (inplace_ok) {
+               /* check if every output is fed by the corresponding input
+                *
+                * this prevents  in-port 1 -> sink-pin 2  ||  source-pin 1 -> out port 1, source-pin 2 -> out port 2
+                * (with in-place,  source-pin 1 -> out port 1 overwrites in-port 1)
+                *
+                * but allows     in-port 1 -> sink-pin 2  ||  source-pin 2 -> out port 1
+                */
+               ChanMapping in_map (input_map ());
+               const ChanMapping::Mappings out_m (output_map ().mappings ());
+               for (ChanMapping::Mappings::const_iterator t = out_m.begin (); t != out_m.end () && inplace_ok; ++t) {
+                       for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
+                               /* src-pin: c->first, out-port: c->second */
+                               bool valid;
+                               uint32_t in_port = in_map.get (t->first, c->first, &valid);
+                               if (valid && in_port != c->second) {
+                                       inplace_ok = false;
+                                       break;
+                               }
+                       }
+               }
+       }
+
        DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: %2\n", name(), inplace_ok ? "In-Place" : "No Inplace Processing"));
        return !inplace_ok; // no-inplace
 }
@@ -1602,10 +1775,10 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
        // buffers and the analyser makes sure it gets enough data for the
        // analysis window
        session().ensure_buffer_set (_signal_analysis_inputs, in);
-       //_signal_analysis_inputs.set_count (in);
+       _signal_analysis_inputs.set_count (in);
 
        session().ensure_buffer_set (_signal_analysis_outputs, out);
-       //_signal_analysis_outputs.set_count (out);
+       _signal_analysis_outputs.set_count (out);
 
        // std::cerr << "set counts to i" << in.n_audio() << "/o" << out.n_audio() << std::endl;
 
@@ -1739,6 +1912,8 @@ PluginInsert::internal_can_support_io_configuration (ChanCount const & inx, Chan
                        // houston, we have a problem.
                        return Match (Impossible, 0);
                }
+               // midi bypass
+               if (inx.n_midi () > 0 && out.n_midi () == 0) { out.set (DataType::MIDI, 1); }
                return Match (Delegate, 1, _strict_io);
        }
 
@@ -1807,6 +1982,8 @@ PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, Cha
                if (!r) {
                        return Match (Impossible, 0);
                }
+               // midi bypass
+               if (in.n_midi () > 0 && out.n_midi () == 0) { out.set (DataType::MIDI, 1); }
                return Match (Delegate, 1);
        }
 
@@ -2006,7 +2183,7 @@ PluginInsert::set_control_ids (const XMLNode& node, int version)
 
        for (iter = nlist.begin(); iter != nlist.end(); ++iter) {
                if ((*iter)->name() == Controllable::xml_node_name) {
-                       const XMLProperty* prop;
+                       XMLProperty const * prop;
 
                        uint32_t p = (uint32_t)-1;
 #ifdef LV2_SUPPORT
@@ -2047,7 +2224,7 @@ PluginInsert::set_state(const XMLNode& node, int version)
        XMLNodeList nlist = node.children();
        XMLNodeIterator niter;
        XMLPropertyList plist;
-       const XMLProperty *prop;
+       XMLProperty const * prop;
        ARDOUR::PluginType type;
 
        if ((prop = node.property ("type")) == 0) {
@@ -2329,7 +2506,7 @@ PluginInsert::set_parameter_state_2X (const XMLNode& node, int version)
                }
 
                XMLNodeList cnodes;
-               XMLProperty *cprop;
+               XMLProperty const * cprop;
                XMLNodeConstIterator iter;
                XMLNode *child;
                const char *port;
@@ -2441,29 +2618,12 @@ PluginInsert::PluginControl::PluginControl (PluginInsert*                     p,
                        list->set_interpolation(Evoral::ControlList::Discrete);
                }
        }
-
-       if (desc.toggled) {
-               set_flags(Controllable::Toggle);
-       }
 }
 
 /** @param val `user' value */
-void
-PluginInsert::PluginControl::set_value (double user_val, PBD::Controllable::GroupControlDisposition group_override)
-{
-       if (writable()) {
-               _set_value (user_val, group_override);
-       }
-}
-void
-PluginInsert::PluginControl::set_value_unchecked (double user_val)
-{
-       /* used only by automation playback */
-       _set_value (user_val, Controllable::NoGroup);
-}
 
 void
-PluginInsert::PluginControl::_set_value (double user_val, PBD::Controllable::GroupControlDisposition group_override)
+PluginInsert::PluginControl::actually_set_value (double user_val, PBD::Controllable::GroupControlDisposition group_override)
 {
        /* FIXME: probably should be taking out some lock here.. */
 
@@ -2476,13 +2636,13 @@ PluginInsert::PluginControl::_set_value (double user_val, PBD::Controllable::Gro
                iasp->set_parameter (_list->parameter().id(), user_val);
        }
 
-       AutomationControl::set_value (user_val, group_override);
+       AutomationControl::actually_set_value (user_val, group_override);
 }
 
 void
 PluginInsert::PluginControl::catch_up_with_external_value (double user_val)
 {
-       AutomationControl::set_value (user_val, Controllable::NoGroup);
+       AutomationControl::actually_set_value (user_val, Controllable::NoGroup);
 }
 
 XMLNode&
@@ -2527,22 +2687,10 @@ PluginInsert::PluginPropertyControl::PluginPropertyControl (PluginInsert*
                alist()->set_yrange (desc.lower, desc.upper);
                alist()->reset_default (desc.normal);
        }
-
-       if (desc.toggled) {
-               set_flags(Controllable::Toggle);
-       }
-}
-
-void
-PluginInsert::PluginPropertyControl::set_value (double user_val, PBD::Controllable::GroupControlDisposition /* group_override*/)
-{
-       if (writable()) {
-               set_value_unchecked (user_val);
-       }
 }
 
 void
-PluginInsert::PluginPropertyControl::set_value_unchecked (double user_val)
+PluginInsert::PluginPropertyControl::actually_set_value (double user_val, Controllable::GroupControlDisposition gcd)
 {
        /* Old numeric set_value(), coerce to appropriate datatype if possible.
           This is lossy, but better than nothing until Ardour's automation system
@@ -2558,7 +2706,8 @@ PluginInsert::PluginPropertyControl::set_value_unchecked (double user_val)
        }
 
        _value = value;
-       AutomationControl::set_value (user_val, Controllable::NoGroup);
+
+       AutomationControl::actually_set_value (user_val, gcd);
 }
 
 XMLNode&
@@ -2603,8 +2752,8 @@ PluginInsert::collect_signal_for_analysis (framecnt_t nframes)
 {
        // called from outside the audio thread, so this should be safe
        // only do audio as analysis is (currently) only for audio plugins
-       _signal_analysis_inputs.ensure_buffers(  DataType::AUDIO, internal_input_streams().n_audio(),  nframes);
-       _signal_analysis_outputs.ensure_buffers( DataType::AUDIO, internal_output_streams().n_audio(), nframes);
+       _signal_analysis_inputs.ensure_buffers (DataType::AUDIO, input_streams().n_audio(),  nframes);
+       _signal_analysis_outputs.ensure_buffers (DataType::AUDIO, output_streams().n_audio(), nframes);
 
        _signal_analysis_collected_nframes   = 0;
        _signal_analysis_collect_nframes_max = nframes;