From 79ff99ba1508ca88c66b4a2bd525430feb076164 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 6 Aug 2024 01:39:35 +0200 Subject: [PATCH] RegionFX: replay control automation (1/2) This riffs off the previous commit, a simple way to replay ctrl events without re-evaluating plugin automation line. There may be a better way to handle this (e.g. replicate the plugin for display only, evaluate on the fly in the UI thread) Short of redesigning our disk-reader/playlist/region infrastructure this is likely a sucks-least compromise for the time being. --- libs/ardour/ardour/plug_insert_base.h | 15 +-- libs/ardour/ardour/region_fx_plugin.h | 6 ++ libs/ardour/region_fx_plugin.cc | 146 +++++++++++++++++++++++++- 3 files changed, 159 insertions(+), 8 deletions(-) diff --git a/libs/ardour/ardour/plug_insert_base.h b/libs/ardour/ardour/plug_insert_base.h index 2f5d32748d..fcc4ff254e 100644 --- a/libs/ardour/ardour/plug_insert_base.h +++ b/libs/ardour/ardour/plug_insert_base.h @@ -73,17 +73,18 @@ public: virtual ChanMapping output_map (uint32_t num) const = 0; /** A control that manipulates a plugin parameter (control port). */ - struct PluginControl : public AutomationControl { + class PluginControl : public AutomationControl { + public: PluginControl (Session& s, PlugInsertBase* p, const Evoral::Parameter& param, const ParameterDescriptor& desc, std::shared_ptr list = std::shared_ptr ()); - double get_value (void) const; - void catch_up_with_external_value (double val); - XMLNode& get_state () const; - std::string get_user_string () const; + virtual double get_value (void) const; + void catch_up_with_external_value (double val); + XMLNode& get_state () const; + std::string get_user_string () const; protected: virtual void actually_set_value (double val, PBD::Controllable::GroupControlDisposition group_override); @@ -98,8 +99,8 @@ public: const ParameterDescriptor& desc, std::shared_ptr list = std::shared_ptr ()); - double get_value (void) const; - XMLNode& get_state () const; + virtual double get_value (void) const; + XMLNode& get_state () const; protected: virtual void actually_set_value (double value, PBD::Controllable::GroupControlDisposition); diff --git a/libs/ardour/ardour/region_fx_plugin.h b/libs/ardour/ardour/region_fx_plugin.h index e39bd276e8..10809b5547 100644 --- a/libs/ardour/ardour/region_fx_plugin.h +++ b/libs/ardour/ardour/region_fx_plugin.h @@ -91,6 +91,8 @@ public: bool reset_parameters_to_default (); bool can_reset_all_parameters (); + void maybe_emit_changed_signals () const; + std::string describe_parameter (Evoral::Parameter param); bool provides_stats () const @@ -189,11 +191,15 @@ private: bool _configured; bool _no_inplace; + mutable samplepos_t _last_emit; + typedef std::map> CtrlOutMap; CtrlOutMap _control_outputs; Gtkmm2ext::WindowProxy* _window_proxy; std::atomic _flush; + + mutable Glib::Threads::Mutex _process_lock; }; } // namespace ARDOUR diff --git a/libs/ardour/region_fx_plugin.cc b/libs/ardour/region_fx_plugin.cc index 7107c9d105..a61aee9290 100644 --- a/libs/ardour/region_fx_plugin.cc +++ b/libs/ardour/region_fx_plugin.cc @@ -107,12 +107,111 @@ private: bool _flush; }; +class TimedPluginControl : public PlugInsertBase::PluginControl +{ +public: + TimedPluginControl (Session& s, + PlugInsertBase* p, + const Evoral::Parameter& param, + const ParameterDescriptor& desc, + std::shared_ptr list, + bool replay_param) + : PlugInsertBase::PluginControl (s, p, param, desc, list) + , _last_value (desc.lower - 1) + , _replay_param (replay_param) + , _flush (false) + { + } + + double get_value (void) const + { + samplepos_t when = _session.audible_sample (); + Glib::Threads::Mutex::Lock lm (_history_mutex); + auto it = _history.lower_bound (when); + if (it != _history.begin ()) { + --it; + } + if (it == _history.end ()) { + return PlugInsertBase::PluginControl::get_value (); + } else { + return it->second; + } + } + + bool maybe_emit_changed () + { + double current = get_value (); + if (current == _last_value) { + return false; + } + _last_value = current; + if (_replay_param) { // AU, VST2 + /* this is only called for automated parameters. + * Next call to ::run() will set the actual value before + * running the plugin (via automation_run). + */ + actually_set_value (current, PBD::Controllable::NoGroup); + } else { // generic UI, LV2 + Changed (true, Controllable::NoGroup); + } + return true; + } + + void flush () { + if (automation_playback ()) { + _flush = true; + } else { + Glib::Threads::Mutex::Lock lm (_history_mutex); + _history.clear (); + } + } + + void store_value (samplepos_t start, samplepos_t end) + { + double value = PlugInsertBase::PluginControl::get_value (); + Glib::Threads::Mutex::Lock lm (_history_mutex); + if (_flush) { + _flush = false; + _history.clear (); + } + auto it = _history.lower_bound (start); + if (it != _history.begin ()) { + --it; + if (it->second == value) { + return; + } + assert (start > it->first); + if (start - it->first < 512) { + return; + } + } + _history[start] = value; + + /* do not accumulate */ + if (_history.size () > 2000 && std::distance (_history.begin(), it) > 1500) { + samplepos_t when = min (start, _session.audible_sample ()); + auto io = _history.lower_bound (when - _session.sample_rate ()); + if (std::distance (io, it) > 1) { + _history.erase (_history.begin(), io); + } + } + } + +private: + std::map _history; + mutable Glib::Threads::Mutex _history_mutex; + double _last_value; + bool _replay_param; + bool _flush; +}; + RegionFxPlugin::RegionFxPlugin (Session& s, Temporal::TimeDomain const td, std::shared_ptr plug) : SessionObject (s, (plug ? plug->name () : string ("toBeRenamed"))) , TimeDomainProvider (td) , _plugin_signal_latency (0) , _configured (false) , _no_inplace (false) + , _last_emit (0) , _window_proxy (0) { _flush.store (0); @@ -381,6 +480,18 @@ RegionFxPlugin::create_parameters () std::shared_ptr plugin = _plugins.front (); set a = _plugins.front ()->automatable (); + bool replay_param = false; + switch (_plugins.front ()->get_info ()->type) { + case AudioUnit: + case LXVST: + case MacVST: + case Windows_VST: + replay_param = true; + break; + default: + break; + } + for (uint32_t i = 0; i < plugin->parameter_count (); ++i) { if (!plugin->parameter_is_control (i)) { continue; @@ -398,7 +509,7 @@ RegionFxPlugin::create_parameters () const bool automatable = a.find(param) != a.end(); std::shared_ptr list (new AutomationList (param, desc, *this)); - std::shared_ptr c (new PluginControl (_session, this, param, desc, list)); + std::shared_ptr c (new TimedPluginControl (_session, this, param, desc, list, replay_param)); if (!automatable) { c->set_flag (Controllable::NotAutomatable); } @@ -624,6 +735,10 @@ RegionFxPlugin::flush () shared_ptr toc = std::dynamic_pointer_cast(i.second); toc->flush (); } + for (auto const& i : _controls) { + shared_ptr tpc = std::dynamic_pointer_cast(i.second); + tpc->flush (); + } } bool @@ -1069,6 +1184,8 @@ RegionFxPlugin::find_next_event (timepos_t const& start, timepos_t const& end, E bool RegionFxPlugin::run (BufferSet& bufs, samplepos_t start, samplepos_t end, samplepos_t pos, pframes_t nframes, sampleoffset_t off) { + Glib::Threads::Mutex::Lock lp (_process_lock); + int canderef (1); if (_flush.compare_exchange_strong (canderef, 0)) { for (auto const& i : _plugins) { @@ -1247,6 +1364,13 @@ RegionFxPlugin::connect_and_run (BufferSet& bufs, samplepos_t start, samplepos_t toc->store_value (start + pos, end + pos); } + for (auto const& i : _controls) { + shared_ptr tpc = std::dynamic_pointer_cast(i.second); + if (tpc->automation_playback ()) { + tpc->store_value (start + pos, end + pos); + } + } + const samplecnt_t l = effective_latency (); if (_plugin_signal_latency != l) { _plugin_signal_latency= l; @@ -1254,3 +1378,23 @@ RegionFxPlugin::connect_and_run (BufferSet& bufs, samplepos_t start, samplepos_t } return true; } + +void +RegionFxPlugin::maybe_emit_changed_signals () const +{ + if (!_session.transport_rolling ()) { + samplepos_t when = _session.audible_sample (); + if (_last_emit == when) { + return; + } + _last_emit = when; + } + + Glib::Threads::Mutex::Lock lp (_process_lock); + for (auto const& i : _controls) { + shared_ptr tpc = std::dynamic_pointer_cast(i.second); + if (tpc->automation_playback ()) { + tpc->maybe_emit_changed (); + } + } +}