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.
This commit is contained in:
Robin Gareus 2024-08-06 01:39:35 +02:00
parent 4ff1de4c75
commit 79ff99ba15
No known key found for this signature in database
GPG key ID: A090BCE02CF57F04
3 changed files with 159 additions and 8 deletions

View file

@ -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<AutomationList> list = std::shared_ptr<AutomationList> ());
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<AutomationList> list = std::shared_ptr<AutomationList> ());
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);

View file

@ -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<uint32_t, std::shared_ptr<ReadOnlyControl>> CtrlOutMap;
CtrlOutMap _control_outputs;
Gtkmm2ext::WindowProxy* _window_proxy;
std::atomic<int> _flush;
mutable Glib::Threads::Mutex _process_lock;
};
} // namespace ARDOUR

View file

@ -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<AutomationList> 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<samplepos_t, double> _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<Plugin> 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> plugin = _plugins.front ();
set<Evoral::Parameter> 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<AutomationList> list (new AutomationList (param, desc, *this));
std::shared_ptr<AutomationControl> c (new PluginControl (_session, this, param, desc, list));
std::shared_ptr<AutomationControl> 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<TimedReadOnlyControl> toc = std::dynamic_pointer_cast<TimedReadOnlyControl>(i.second);
toc->flush ();
}
for (auto const& i : _controls) {
shared_ptr<TimedPluginControl> tpc = std::dynamic_pointer_cast<TimedPluginControl>(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<TimedPluginControl> tpc = std::dynamic_pointer_cast<TimedPluginControl>(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<TimedPluginControl> tpc = std::dynamic_pointer_cast<TimedPluginControl>(i.second);
if (tpc->automation_playback ()) {
tpc->maybe_emit_changed ();
}
}
}