From ae6900d8af078072f89a47271b7ab62d7978fc44 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Thu, 2 Oct 2025 01:03:46 +0200 Subject: [PATCH] Catch up with plugin property changes (LV2/JUCE) When a user changes a value in the Plugin GUI, JUCE informs the DSP behind the host's back, and later informs the host from the DSP (realtime) thread, by sending patch:Set messages from the DSP back to the host/UI. This is really bad practice, particularly since the plugin does no even use LV2 instance access, or LV2 data access features. --- libs/ardour/ardour/io_plug.h | 1 + libs/ardour/ardour/plug_insert_base.h | 1 + libs/ardour/ardour/plugin_insert.h | 1 + libs/ardour/ardour/region_fx_plugin.h | 1 + libs/ardour/io_plug.cc | 12 ++++++++++ libs/ardour/plug_insert_base.cc | 8 +++++++ libs/ardour/plugin_insert.cc | 34 +++++++++++++++++++++++++++ libs/ardour/region_fx_plugin.cc | 29 +++++++++++++++++++++++ 8 files changed, 87 insertions(+) diff --git a/libs/ardour/ardour/io_plug.h b/libs/ardour/ardour/io_plug.h index 6421fee466..44f810b562 100644 --- a/libs/ardour/ardour/io_plug.h +++ b/libs/ardour/ardour/io_plug.h @@ -118,6 +118,7 @@ private: std::string ensure_io_name (std::string) const; void create_parameters (); void parameter_changed_externally (uint32_t, float); + void property_changed_externally (uint32_t, Variant); void setup (); diff --git a/libs/ardour/ardour/plug_insert_base.h b/libs/ardour/ardour/plug_insert_base.h index fcc4ff254e..a017f97cf7 100644 --- a/libs/ardour/ardour/plug_insert_base.h +++ b/libs/ardour/ardour/plug_insert_base.h @@ -100,6 +100,7 @@ public: std::shared_ptr list = std::shared_ptr ()); virtual double get_value (void) const; + void catch_up_with_external_value (double val); XMLNode& get_state () const; protected: diff --git a/libs/ardour/ardour/plugin_insert.h b/libs/ardour/ardour/plugin_insert.h index eeb12b4bc4..53d964613d 100644 --- a/libs/ardour/ardour/plugin_insert.h +++ b/libs/ardour/ardour/plugin_insert.h @@ -276,6 +276,7 @@ private: PluginInsert (const PluginInsert&); void parameter_changed_externally (uint32_t, float); + void property_changed_externally (uint32_t which, Variant); void set_parameter (Evoral::Parameter param, float val, sampleoffset_t); diff --git a/libs/ardour/ardour/region_fx_plugin.h b/libs/ardour/ardour/region_fx_plugin.h index 34f795d353..49f9197123 100644 --- a/libs/ardour/ardour/region_fx_plugin.h +++ b/libs/ardour/ardour/region_fx_plugin.h @@ -165,6 +165,7 @@ private: bool check_inplace (); void create_parameters (); void parameter_changed_externally (uint32_t, float); + void property_changed_externally (uint32_t, Variant); void automation_run (samplepos_t start, pframes_t nframes); bool find_next_event (timepos_t const& start, timepos_t const& end, Evoral::ControlEvent& next_event) const; void start_touch (uint32_t param_id); diff --git a/libs/ardour/io_plug.cc b/libs/ardour/io_plug.cc index 717581c975..ef02839204 100644 --- a/libs/ardour/io_plug.cc +++ b/libs/ardour/io_plug.cc @@ -256,6 +256,7 @@ IOPlug::setup () _plugin->reconfigure_io (_n_in, aux_in, _n_out); _plugin->ParameterChangedExternally.connect_same_thread (*this, std::bind (&IOPlug::parameter_changed_externally, this, _1, _2)); + _plugin->PropertyChanged.connect_same_thread (*this, std::bind (&IOPlug::property_changed_externally, this, _1, _2)); _plugin->activate (); _plugin->set_insert (this, 0); } @@ -372,6 +373,17 @@ IOPlug::parameter_changed_externally (uint32_t which, float val) } } +void +IOPlug::property_changed_externally (uint32_t which, Variant val) +{ + std::shared_ptr c = control (Evoral::Parameter (PluginPropertyAutomation, 0, which)); + std::shared_ptr pc = std::dynamic_pointer_cast (c); + + if (pc) { + pc->catch_up_with_external_value (val.to_double ()); + } +} + int IOPlug::set_block_size (pframes_t n_samples) { diff --git a/libs/ardour/plug_insert_base.cc b/libs/ardour/plug_insert_base.cc index 8163e89122..b813af9b8b 100644 --- a/libs/ardour/plug_insert_base.cc +++ b/libs/ardour/plug_insert_base.cc @@ -393,6 +393,14 @@ PlugInsertBase::PluginPropertyControl::actually_set_value (double user_val, Cont AutomationControl::actually_set_value (user_val, gcd); } +void +PlugInsertBase::PluginPropertyControl::catch_up_with_external_value (double user_val) +{ + if (user_val != Control::get_double ()) { + PluginPropertyControl::actually_set_value (user_val, Controllable::NoGroup); + } +} + XMLNode& PlugInsertBase::PluginPropertyControl::get_state () const { diff --git a/libs/ardour/plugin_insert.cc b/libs/ardour/plugin_insert.cc index b72ada25bd..77c1a7bdec 100644 --- a/libs/ardour/plugin_insert.cc +++ b/libs/ardour/plugin_insert.cc @@ -660,6 +660,39 @@ PluginInsert::parameter_changed_externally (uint32_t which, float val) } } +void +PluginInsert::property_changed_externally (uint32_t which, Variant val) +{ + std::shared_ptr c = control (Evoral::Parameter (PluginPropertyAutomation, 0, which)); + std::shared_ptr pc = std::dynamic_pointer_cast (c); + + if (pc) { + pc->catch_up_with_external_value (val.to_double ()); + } + + /* Second propagation: tell all plugins except the first to + * update the value of this parameter. For sane plugin APIs, + * there are no other plugins, so this is a no-op in those + * cases. + */ + + Plugins::iterator i = _plugins.begin (); + + /* don't set the first plugin, just all the slaves */ + + if (i != _plugins.end ()) { + ++i; + for (; i != _plugins.end (); ++i) { + (*i)->set_property (which, val); + } + } + + std::shared_ptr iasp = _impulseAnalysisPlugin.lock(); + if (iasp) { + iasp->set_property (which, val); + } +} + int PluginInsert::set_block_size (pframes_t nframes) { @@ -3021,6 +3054,7 @@ PluginInsert::add_plugin (std::shared_ptr plugin) /* first (and probably only) plugin instance - connect to relevant signals */ plugin->ParameterChangedExternally.connect_same_thread (*this, std::bind (&PluginInsert::parameter_changed_externally, this, _1, _2)); + plugin->PropertyChanged.connect_same_thread (*this, std::bind (&PluginInsert::property_changed_externally, this, _1, _2)); plugin->StartTouch.connect_same_thread (*this, std::bind (&PluginInsert::start_touch, this, _1)); plugin->EndTouch.connect_same_thread (*this, std::bind (&PluginInsert::end_touch, this, _1)); _custom_sinks = plugin->get_info()->n_inputs; diff --git a/libs/ardour/region_fx_plugin.cc b/libs/ardour/region_fx_plugin.cc index bbac230f23..ae080a76fd 100644 --- a/libs/ardour/region_fx_plugin.cc +++ b/libs/ardour/region_fx_plugin.cc @@ -430,6 +430,7 @@ RegionFxPlugin::add_plugin (std::shared_ptr plugin) if (_plugins.empty ()) { /* first (and probably only) plugin instance - connect to relevant signals */ plugin->ParameterChangedExternally.connect_same_thread (*this, std::bind (&RegionFxPlugin::parameter_changed_externally, this, _1, _2)); + plugin->PropertyChanged.connect_same_thread (*this, std::bind (&RegionFxPlugin::property_changed_externally, this, _1, _2)); plugin->StartTouch.connect_same_thread (*this, std::bind (&RegionFxPlugin::start_touch, this, _1)); plugin->EndTouch.connect_same_thread (*this, std::bind (&RegionFxPlugin::end_touch, this, _1)); } @@ -695,6 +696,34 @@ RegionFxPlugin::parameter_changed_externally (uint32_t which, float val) } } +void +RegionFxPlugin::property_changed_externally (uint32_t which, Variant val) +{ + std::shared_ptr c = control (Evoral::Parameter (PluginPropertyAutomation, 0, which)); + std::shared_ptr pc = std::dynamic_pointer_cast (c); + + if (pc) { + pc->catch_up_with_external_value (val.to_double ()); + } + + /* Second propagation: tell all plugins except the first to + * update the value of this parameter. For sane plugin APIs, + * there are no other plugins, so this is a no-op in those + * cases. + */ + + Plugins::iterator i = _plugins.begin (); + + /* don't set the first plugin, just all the slaves */ + + if (i != _plugins.end ()) { + ++i; + for (; i != _plugins.end (); ++i) { + (*i)->set_property (which, val); + } + } +} + std::string RegionFxPlugin::describe_parameter (Evoral::Parameter param) {