fix crash when copy'ing latent plugins
[ardour.git] / libs / ardour / plugin_insert.cc
index a9aca6b9ca9d30389c61f7fbebf197db8b02fb4d..9a0e6c55c40574e32cd001bf5e3df688593e625c 100644 (file)
@@ -57,7 +57,7 @@
 #include "ardour/session.h"
 #include "ardour/types.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace std;
 using namespace ARDOUR;
@@ -67,6 +67,9 @@ const string PluginInsert::port_automation_node_name = "PortAutomation";
 
 PluginInsert::PluginInsert (Session& s, boost::shared_ptr<Plugin> plug)
        : Processor (s, (plug ? plug->name() : string ("toBeRenamed")))
+       , _sc_playback_latency (0)
+       , _sc_capture_latency (0)
+       , _plugin_signal_latency (0)
        , _signal_analysis_collected_nframes(0)
        , _signal_analysis_collect_nframes_max(0)
        , _configured (false)
@@ -74,6 +77,7 @@ PluginInsert::PluginInsert (Session& s, boost::shared_ptr<Plugin> plug)
        , _strict_io (false)
        , _custom_cfg (false)
        , _maps_from_state (false)
+       , _bypass_port (UINT32_MAX)
 {
        /* the first is the master */
 
@@ -81,8 +85,8 @@ PluginInsert::PluginInsert (Session& s, boost::shared_ptr<Plugin> plug)
                add_plugin (plug);
                create_automatable_parameters ();
                const ChanCount& sc (sidechain_input_pins ());
-               if (sc.n_audio () > 0) {
-                       add_sidechain (sc.n_audio ());
+               if (sc.n_audio () > 0 || sc.n_midi () > 0) {
+                       add_sidechain (sc.n_audio (), sc.n_midi ());
                }
        }
 }
@@ -186,14 +190,14 @@ PluginInsert::set_preset_out (const ChanCount& c)
 }
 
 bool
-PluginInsert::add_sidechain (uint32_t n_audio)
+PluginInsert::add_sidechain (uint32_t n_audio, uint32_t n_midi)
 {
        // caller must hold process lock
        if (_sidechain) {
                return false;
        }
        std::ostringstream n;
-       if (n_audio > 0) {
+       if (n_audio > 0 || n_midi > 0) {
                n << "Sidechain " << Session::next_name_id ();
        } else {
                n << "TO BE RESET FROM XML";
@@ -202,7 +206,10 @@ PluginInsert::add_sidechain (uint32_t n_audio)
        _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.
+               _sidechain->input()->add_port ("", owner(), DataType::AUDIO); // add a port, don't connect.
+       }
+       for (uint32_t n = 0; n < n_midi; ++n) {
+               _sidechain->input()->add_port ("", owner(), DataType::MIDI); // add a port, don't connect.
        }
        PluginConfigChanged (); /* EMIT SIGNAL */
        return true;
@@ -215,10 +222,30 @@ PluginInsert::del_sidechain ()
                return false;
        }
        _sidechain.reset ();
+       _sc_playback_latency = 0;
+       _sc_capture_latency = 0;
        PluginConfigChanged (); /* EMIT SIGNAL */
        return true;
 }
 
+void
+PluginInsert::set_sidechain_latency (uint32_t capture, uint32_t playback)
+{
+       if (_sidechain &&
+                       (_sc_playback_latency != playback || _sc_capture_latency != capture)) {
+               _sc_capture_latency = capture;
+               _sc_playback_latency = playback;
+               LatencyRange pl; pl.min = pl.max = playback;
+               LatencyRange cl; cl.min = cl.max = capture;
+               DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: capture %2 playback; %3\n", _sidechain->name (), capture, playback));
+               PortSet& ps (_sidechain->input ()->ports ());
+               for (PortSet::iterator p = ps.begin(); p != ps.end(); ++p) {
+                       p->set_private_latency_range (pl, true);
+                       p->set_private_latency_range (cl, false);
+               }
+       }
+}
+
 void
 PluginInsert::control_list_automation_state_changed (Evoral::Parameter which, AutoState s)
 {
@@ -409,31 +436,53 @@ 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)));
+               }
+       }
+
+       _bypass_port = plugin->designated_bypass_port ();
+
+       if (_bypass_port != UINT32_MAX) {
+               boost::shared_ptr<AutomationControl> ac = automation_control (Evoral::Parameter (PluginAutomation, 0, _bypass_port));
+               if (0 == (ac->flags () & Controllable::NotAutomatable)) {
+                       ac->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&PluginInsert::bypassable_changed, this));
+                       ac->Changed.connect_same_thread (*this, boost::bind (&PluginInsert::enable_changed, this));
                }
        }
 }
@@ -506,6 +555,17 @@ PluginInsert::activate ()
        }
 
        Processor::activate ();
+       /* when setting state e.g ProcessorBox::paste_processor_state ()
+        * the plugin is not yet owned by a route.
+        * but no matter.  Route::add_processors() will call activate () again
+        */
+       if (!owner ()) {
+               return;
+       }
+       if (_plugin_signal_latency != signal_latency ()) {
+               _plugin_signal_latency = signal_latency ();
+               latency_changed ();
+       }
 }
 
 void
@@ -516,6 +576,10 @@ PluginInsert::deactivate ()
        for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
                (*i)->deactivate ();
        }
+       if (_plugin_signal_latency != signal_latency ()) {
+               _plugin_signal_latency = signal_latency ();
+               latency_changed ();
+       }
 }
 
 void
@@ -526,6 +590,60 @@ PluginInsert::flush ()
        }
 }
 
+void
+PluginInsert::enable (bool yn)
+{
+       if (_bypass_port == UINT32_MAX) {
+               if (yn) {
+                       activate ();
+               } else {
+                       deactivate ();
+               }
+       } else {
+               if (!_pending_active) {
+                       activate ();
+               }
+               boost::shared_ptr<AutomationControl> ac = automation_control (Evoral::Parameter (PluginAutomation, 0, _bypass_port));
+               ac->set_value (yn ? 1.0 : 0.0, Controllable::NoGroup);
+               ActiveChanged ();
+       }
+}
+
+bool
+PluginInsert::enabled () const
+{
+       if (_bypass_port == UINT32_MAX) {
+               return Processor::enabled ();
+       } else {
+               boost::shared_ptr<const AutomationControl> ac = boost::const_pointer_cast<AutomationControl> (automation_control (Evoral::Parameter (PluginAutomation, 0, _bypass_port)));
+               return (ac->get_value () > 0 && _pending_active);
+       }
+}
+
+bool
+PluginInsert::bypassable () const
+{
+       if (_bypass_port == UINT32_MAX) {
+               return true;
+       } else {
+               boost::shared_ptr<const AutomationControl> ac = boost::const_pointer_cast<AutomationControl> (automation_control (Evoral::Parameter (PluginAutomation, 0, _bypass_port)));
+
+               return !ac->automation_playback ();
+       }
+}
+
+void
+PluginInsert::enable_changed ()
+{
+       ActiveChanged ();
+}
+
+void
+PluginInsert::bypassable_changed ()
+{
+       BypassableChanged ();
+}
+
 void
 PluginInsert::inplace_silence_unconnected (BufferSet& bufs, const PinMappings& out_map, framecnt_t nframes, framecnt_t offset) const
 {
@@ -560,7 +678,7 @@ PluginInsert::inplace_silence_unconnected (BufferSet& bufs, const PinMappings& o
 }
 
 void
-PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t offset, bool with_auto, framepos_t now)
+PluginInsert::connect_and_run (BufferSet& bufs, framepos_t start, framepos_t end, double speed, pframes_t nframes, framecnt_t offset, bool with_auto)
 {
        // TODO: atomically copy maps & _no_inplace
        PinMappings in_map (_in_map);
@@ -628,7 +746,7 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                        if (c->list() && c->automation_playback()) {
                                bool valid;
 
-                               const float val = c->list()->rt_safe_eval (now, valid);
+                               const float val = c->list()->rt_safe_eval (start, valid);
 
                                if (valid) {
                                        /* This is the ONLY place where we are
@@ -676,7 +794,7 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                        ChanMapping mb_in_map (ChanCount::min (_configured_in, ChanCount (DataType::AUDIO, 2)));
                        ChanMapping mb_out_map (ChanCount::min (_configured_out, ChanCount (DataType::AUDIO, 2)));
 
-                       _plugins.front()->connect_and_run (bufs, mb_in_map, mb_out_map, nframes, offset);
+                       _plugins.front()->connect_and_run (bufs, start, end, speed, mb_in_map, mb_out_map, nframes, offset);
 
                        for (uint32_t out = _configured_in.n_audio (); out < bufs.count().get (DataType::AUDIO); ++out) {
                                bufs.get (DataType::AUDIO, out).silence (nframes, offset);
@@ -752,7 +870,7 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                                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, start, end, speed, i_in_map, i_out_map, nframes, offset)) {
                                deactivate ();
                        }
                }
@@ -782,7 +900,7 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                /* 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)) {
+                       if ((*i)->connect_and_run(bufs, start, end, speed, in_map[pc], out_map[pc], nframes, offset)) {
                                deactivate ();
                        }
                }
@@ -815,6 +933,11 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                                             &_signal_analysis_outputs);
                }
        }
+
+       if (_plugin_signal_latency != signal_latency ()) {
+               _plugin_signal_latency = signal_latency ();
+               latency_changed ();
+       }
 }
 
 void
@@ -825,8 +948,8 @@ PluginInsert::bypass (BufferSet& bufs, pframes_t nframes)
         */
 
        // TODO: atomically copy maps & _no_inplace
-       ChanMapping in_map (input_map ());
-       ChanMapping out_map (output_map ());
+       const ChanMapping in_map (no_sc_input_map ());
+       const ChanMapping out_map (output_map ());
        if (_mapping_changed) {
                _no_inplace = check_inplace ();
                _mapping_changed = false;
@@ -929,7 +1052,7 @@ PluginInsert::bypass (BufferSet& bufs, pframes_t nframes)
 }
 
 void
-PluginInsert::silence (framecnt_t nframes)
+PluginInsert::silence (framecnt_t nframes, framepos_t start_frame)
 {
        if (!active ()) {
                return;
@@ -943,37 +1066,31 @@ PluginInsert::silence (framecnt_t nframes)
 #ifdef MIXBUS
        if (is_channelstrip ()) {
                if (_configured_in.n_audio() > 0) {
-                       _plugins.front()->connect_and_run (_session.get_scratch_buffers (maxbuf, true), in_map, out_map, nframes, 0);
+                       _plugins.front()->connect_and_run (_session.get_scratch_buffers (maxbuf, true), start_frame, start_frame + nframes, 1.0, in_map, out_map, nframes, 0);
                }
        } else
 #endif
        for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
-               (*i)->connect_and_run (_session.get_scratch_buffers (maxbuf, true), in_map, out_map, nframes, 0);
+               (*i)->connect_and_run (_session.get_scratch_buffers (maxbuf, true), start_frame, start_frame + nframes, 1.0, 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, double speed, pframes_t nframes, bool)
 {
-#ifndef NDEBUG
-       if (!_configured) {
-               error << string_compose (_("Force bypassed unconfigured plugin: %1"), name ()) << endmsg;
-               deactivate ();
+       if (_sidechain) {
+               // collect sidechain input for complete cycle (!)
+               // TODO we need delaylines here for latency compensation
+               _sidechain->run (bufs, start_frame, end_frame, speed, nframes, true);
        }
-#endif
+
        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);
+                       automation_run (bufs, start_frame, end_frame, speed, nframes);
                } else {
-                       connect_and_run (bufs, nframes, 0, false);
+                       connect_and_run (bufs, start_frame, end_frame, speed, nframes, 0, false);
                }
 
        } else {
@@ -989,39 +1106,37 @@ PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame
 }
 
 void
-PluginInsert::automation_run (BufferSet& bufs, framepos_t start, pframes_t nframes)
+PluginInsert::automation_run (BufferSet& bufs, framepos_t start, framepos_t end, double speed, pframes_t nframes)
 {
        Evoral::ControlEvent next_event (0, 0.0f);
-       framepos_t now = start;
-       framepos_t end = now + nframes;
        framecnt_t offset = 0;
 
        Glib::Threads::Mutex::Lock lm (control_lock(), Glib::Threads::TRY_LOCK);
 
        if (!lm.locked()) {
-               connect_and_run (bufs, nframes, offset, false);
+               connect_and_run (bufs, start, end, speed, nframes, offset, false);
                return;
        }
 
-       if (!find_next_event (now, end, next_event) || _plugins.front()->requires_fixed_sized_buffers()) {
+       if (!find_next_event (start, end, next_event) || _plugins.front()->requires_fixed_sized_buffers()) {
 
                /* no events have a time within the relevant range */
 
-               connect_and_run (bufs, nframes, offset, true, now);
+               connect_and_run (bufs, start, end, speed, nframes, offset, true);
                return;
        }
 
        while (nframes) {
 
-               framecnt_t cnt = min (((framecnt_t) ceil (next_event.when) - now), (framecnt_t) nframes);
+               framecnt_t cnt = min (((framecnt_t) ceil (next_event.when) - start), (framecnt_t) nframes);
 
-               connect_and_run (bufs, cnt, offset, true, now);
+               connect_and_run (bufs, start, start + cnt, speed, cnt, offset, true); // XXX (start + cnt) * speed
 
                nframes -= cnt;
                offset += cnt;
-               now += cnt;
+               start += cnt;
 
-               if (!find_next_event (now, end, next_event)) {
+               if (!find_next_event (start, end, next_event)) {
                        break;
                }
        }
@@ -1029,7 +1144,7 @@ PluginInsert::automation_run (BufferSet& bufs, framepos_t start, pframes_t nfram
        /* cleanup anything that is left to do */
 
        if (nframes) {
-               connect_and_run (bufs, nframes, offset, true, now);
+               connect_and_run (bufs, start, start + nframes, speed, nframes, offset, true);
        }
 }
 
@@ -1229,6 +1344,27 @@ PluginInsert::input_map () const
        return rv;
 }
 
+
+ChanMapping
+PluginInsert::no_sc_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) {
+                       uint32_t ins = natural_input_streams().get(tm->first) - _cached_sidechain_pins.get(tm->first);
+                       for (ChanMapping::TypeMapping::const_iterator i = tm->second.begin(); i != tm->second.end(); ++i) {
+                               if (i->first < ins) {
+                                       rv.set (tm->first, i->first + pc * ins, i->second);
+                               }
+                       }
+               }
+       }
+       return rv;
+}
+
 ChanMapping
 PluginInsert::output_map () const
 {
@@ -1336,6 +1472,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
 }
@@ -2008,7 +2168,7 @@ PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, Cha
 
        uint32_t f             = 0;
        bool     can_replicate = true;
-       for (DataType::iterator t = DataType::begin(); t != DataType::end() && can_replicate; ++t) {
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
 
                // ignore side-chains
                uint32_t nin = ns_inputs.get (*t);
@@ -2563,6 +2723,9 @@ PluginInsert::describe_parameter (Evoral::Parameter param)
 ARDOUR::framecnt_t
 PluginInsert::signal_latency() const
 {
+       if (!_pending_active) {
+               return 0;
+       }
        if (_user_latency) {
                return _user_latency;
        }
@@ -2589,29 +2752,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.. */
 
@@ -2624,13 +2770,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&
@@ -2675,22 +2821,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
@@ -2706,7 +2840,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&
@@ -2737,7 +2872,14 @@ PluginInsert::get_impulse_analysis_plugin()
                // during init() -- most notably block_size..
                // not great.
                ret = plugin_factory(_plugins[0]);
-               ret->configure_io (internal_input_streams (), internal_output_streams ());
+               ChanCount out (internal_output_streams ());
+               if (ret->get_info ()->reconfigurable_io ()) {
+                       // populate get_info ()->n_inputs and ->n_outputs
+                       ChanCount useins;
+                       ret->can_support_io_configuration (internal_input_streams (), out, &useins);
+                       assert (out == internal_output_streams ());
+               }
+               ret->configure_io (internal_input_streams (), out);
                _impulseAnalysisPlugin = ret;
        } else {
                ret = _impulseAnalysisPlugin.lock();
@@ -2770,7 +2912,6 @@ PluginInsert::add_plugin (boost::shared_ptr<Plugin> plugin)
                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->LatencyChanged.connect_same_thread (*this, boost::bind (&PluginInsert::latency_changed, this, _1, _2));
                _custom_sinks = plugin->get_info()->n_inputs;
                // cache sidechain port count
                _cached_sidechain_pins.reset ();
@@ -2790,6 +2931,7 @@ PluginInsert::add_plugin (boost::shared_ptr<Plugin> plugin)
                vst->set_insert (this, _plugins.size ());
        }
 #endif
+
        _plugins.push_back (plugin);
 }
 
@@ -2830,10 +2972,13 @@ PluginInsert::monitoring_changed ()
 }
 
 void
-PluginInsert::latency_changed (framecnt_t, framecnt_t)
+PluginInsert::latency_changed ()
 {
        // this is called in RT context, LatencyChanged is emitted after run()
        _latency_changed = true;
+       // XXX This also needs a proper API not an owner() hack.
+       assert (owner ());
+       static_cast<Route*>(owner ())->processor_latency_changed (); /* EMIT SIGNAL */
 }
 
 void