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.
This commit is contained in:
Robin Gareus 2025-10-02 01:03:46 +02:00
parent e974a861ea
commit ae6900d8af
8 changed files with 87 additions and 0 deletions

View file

@ -118,6 +118,7 @@ private:
std::string ensure_io_name (std::string) const; std::string ensure_io_name (std::string) const;
void create_parameters (); void create_parameters ();
void parameter_changed_externally (uint32_t, float); void parameter_changed_externally (uint32_t, float);
void property_changed_externally (uint32_t, Variant);
void setup (); void setup ();

View file

@ -100,6 +100,7 @@ public:
std::shared_ptr<AutomationList> list = std::shared_ptr<AutomationList> ()); std::shared_ptr<AutomationList> list = std::shared_ptr<AutomationList> ());
virtual double get_value (void) const; virtual double get_value (void) const;
void catch_up_with_external_value (double val);
XMLNode& get_state () const; XMLNode& get_state () const;
protected: protected:

View file

@ -276,6 +276,7 @@ private:
PluginInsert (const PluginInsert&); PluginInsert (const PluginInsert&);
void parameter_changed_externally (uint32_t, float); 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); void set_parameter (Evoral::Parameter param, float val, sampleoffset_t);

View file

@ -165,6 +165,7 @@ private:
bool check_inplace (); bool check_inplace ();
void create_parameters (); void create_parameters ();
void parameter_changed_externally (uint32_t, float); void parameter_changed_externally (uint32_t, float);
void property_changed_externally (uint32_t, Variant);
void automation_run (samplepos_t start, pframes_t nframes); 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; bool find_next_event (timepos_t const& start, timepos_t const& end, Evoral::ControlEvent& next_event) const;
void start_touch (uint32_t param_id); void start_touch (uint32_t param_id);

View file

@ -256,6 +256,7 @@ IOPlug::setup ()
_plugin->reconfigure_io (_n_in, aux_in, _n_out); _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->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->activate ();
_plugin->set_insert (this, 0); _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<Evoral::Control> c = control (Evoral::Parameter (PluginPropertyAutomation, 0, which));
std::shared_ptr<PluginPropertyControl> pc = std::dynamic_pointer_cast<PluginPropertyControl> (c);
if (pc) {
pc->catch_up_with_external_value (val.to_double ());
}
}
int int
IOPlug::set_block_size (pframes_t n_samples) IOPlug::set_block_size (pframes_t n_samples)
{ {

View file

@ -393,6 +393,14 @@ PlugInsertBase::PluginPropertyControl::actually_set_value (double user_val, Cont
AutomationControl::actually_set_value (user_val, gcd); 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& XMLNode&
PlugInsertBase::PluginPropertyControl::get_state () const PlugInsertBase::PluginPropertyControl::get_state () const
{ {

View file

@ -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<Evoral::Control> c = control (Evoral::Parameter (PluginPropertyAutomation, 0, which));
std::shared_ptr<PluginPropertyControl> pc = std::dynamic_pointer_cast<PluginPropertyControl> (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<Plugin> iasp = _impulseAnalysisPlugin.lock();
if (iasp) {
iasp->set_property (which, val);
}
}
int int
PluginInsert::set_block_size (pframes_t nframes) PluginInsert::set_block_size (pframes_t nframes)
{ {
@ -3021,6 +3054,7 @@ PluginInsert::add_plugin (std::shared_ptr<Plugin> plugin)
/* 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, std::bind (&PluginInsert::parameter_changed_externally, this, _1, _2)); 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->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)); plugin->EndTouch.connect_same_thread (*this, std::bind (&PluginInsert::end_touch, this, _1));
_custom_sinks = plugin->get_info()->n_inputs; _custom_sinks = plugin->get_info()->n_inputs;

View file

@ -430,6 +430,7 @@ RegionFxPlugin::add_plugin (std::shared_ptr<Plugin> plugin)
if (_plugins.empty ()) { 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, std::bind (&RegionFxPlugin::parameter_changed_externally, this, _1, _2)); 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->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)); 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<Evoral::Control> c = control (Evoral::Parameter (PluginPropertyAutomation, 0, which));
std::shared_ptr<PluginPropertyControl> pc = std::dynamic_pointer_cast<PluginPropertyControl> (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 std::string
RegionFxPlugin::describe_parameter (Evoral::Parameter param) RegionFxPlugin::describe_parameter (Evoral::Parameter param)
{ {