Robin Gareus
520bbfe515
This is in preparation to display current tail time, and allow a user to override it. This is similar to existing processor latency API.
1474 lines
40 KiB
C++
1474 lines
40 KiB
C++
/*
|
|
* Copyright (C) 2024 Robin Gareus <robin@gareus.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#ifdef WAF_BUILD
|
|
#include "libardour-config.h"
|
|
#endif
|
|
|
|
#include <string>
|
|
|
|
#include "pbd/assert.h"
|
|
#include "pbd/types_convert.h"
|
|
#include "pbd/xml++.h"
|
|
|
|
#include "ardour/automatable.h"
|
|
#include "ardour/debug.h"
|
|
#include "ardour/plugin.h"
|
|
#include "ardour/readonly_control.h"
|
|
#include "ardour/region_fx_plugin.h"
|
|
#include "ardour/session.h"
|
|
|
|
using namespace std;
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
|
|
class TimedReadOnlyControl : public ReadOnlyControl {
|
|
public:
|
|
TimedReadOnlyControl (std::shared_ptr<Plugin> p, const ParameterDescriptor& desc, uint32_t pnum)
|
|
: ReadOnlyControl (p, desc, pnum)
|
|
, _flush (false)
|
|
{}
|
|
|
|
double get_parameter () const {
|
|
std::shared_ptr<Plugin> p = _plugin.lock();
|
|
|
|
if (!p) {
|
|
return 0;
|
|
}
|
|
samplepos_t when = p->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 p->get_parameter (_parameter_num);
|
|
} else {
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
void flush () {
|
|
_flush = true;
|
|
}
|
|
|
|
void store_value (samplepos_t start, samplepos_t end) {
|
|
std::shared_ptr<Plugin> p = _plugin.lock();
|
|
if (!p) {
|
|
return;
|
|
}
|
|
double value = p->get_parameter (_parameter_num);
|
|
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;
|
|
|
|
if (_history.size () > 2000 && std::distance (_history.begin(), it) > 1500) {
|
|
samplepos_t when = min (start, p->session().audible_sample ());
|
|
auto io = _history.lower_bound (when - p->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;
|
|
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)
|
|
, _state (0)
|
|
{
|
|
_flush.store (0);
|
|
|
|
if (plug) {
|
|
add_plugin (plug);
|
|
plug->activate ();
|
|
create_parameters ();
|
|
}
|
|
}
|
|
|
|
RegionFxPlugin::~RegionFxPlugin ()
|
|
{
|
|
for (auto const& i : _control_outputs) {
|
|
std::dynamic_pointer_cast<ReadOnlyControl>(i.second)->drop_references ();
|
|
}
|
|
|
|
Glib::Threads::Mutex::Lock lm (_control_lock);
|
|
for (auto const& i : _controls) {
|
|
std::dynamic_pointer_cast<AutomationControl>(i.second)->drop_references ();
|
|
}
|
|
_controls.clear ();
|
|
|
|
delete _state;
|
|
}
|
|
|
|
XMLNode&
|
|
RegionFxPlugin::get_state () const
|
|
{
|
|
if (_plugins.empty ()) {
|
|
assert (_state);
|
|
return *(new XMLNode (*_state));
|
|
}
|
|
|
|
XMLNode* node = new XMLNode (/*state_node_name*/ "RegionFXPlugin");
|
|
|
|
Latent::add_state (node);
|
|
TailTime::add_state (node);
|
|
|
|
node->set_property ("type", _plugins[0]->state_node_name ());
|
|
node->set_property ("unique-id", _plugins[0]->unique_id ());
|
|
node->set_property ("count", (uint32_t)_plugins.size ());
|
|
|
|
node->set_property ("id", id ());
|
|
node->set_property ("name", name ());
|
|
|
|
_plugins[0]->set_insert_id (this->id ());
|
|
node->add_child_nocopy (_plugins[0]->get_state ());
|
|
|
|
for (auto const& c : controls ()) {
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (c.second);
|
|
if (!ac) {
|
|
continue;
|
|
}
|
|
node->add_child_nocopy (ac->get_state ());
|
|
std::shared_ptr<AutomationList> l = ac->alist ();
|
|
if (l && 0 == (ac->flags() & Controllable::NotAutomatable)) {
|
|
node->add_child_nocopy (l->get_state ());
|
|
}
|
|
}
|
|
|
|
return *node;
|
|
}
|
|
|
|
int
|
|
RegionFxPlugin::set_state (const XMLNode& node, int version)
|
|
{
|
|
set_id (node);
|
|
|
|
/* with ::regenerate_xml_or_string_ids(), set_id() creates a new ID */
|
|
PBD::ID new_id = this->id();
|
|
PBD::ID old_id = this->id();
|
|
|
|
node.get_property ("id", old_id);
|
|
|
|
ARDOUR::PluginType type;
|
|
std::string unique_id;
|
|
if (!parse_plugin_type (node, type, unique_id)) {
|
|
return -1;
|
|
}
|
|
|
|
bool any_vst;
|
|
|
|
uint32_t count = 1;
|
|
node.get_property ("count", count);
|
|
|
|
if (_plugins.empty()) {
|
|
std::shared_ptr<Plugin> plugin = find_and_load_plugin (_session, node, type, unique_id, any_vst);
|
|
|
|
if (!plugin) {
|
|
delete _state;
|
|
_state = new XMLNode (node);
|
|
string name;
|
|
if (node.get_property ("name", name)) {
|
|
set_name (name);
|
|
} else {
|
|
set_name ("Unknown Plugin");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
add_plugin (plugin);
|
|
create_parameters ();
|
|
set_control_ids (node, version);
|
|
|
|
if (_plugins.size () != count) {
|
|
for (uint32_t n = 1; n < count; ++n) {
|
|
add_plugin (plugin_factory (plugin));
|
|
}
|
|
}
|
|
|
|
} else {
|
|
assert (_plugins[0]->unique_id() == unique_id);
|
|
set_control_ids (node, version, true);
|
|
}
|
|
|
|
string name;
|
|
if (node.get_property ("name", name)) {
|
|
set_name (name);
|
|
} else {
|
|
set_name (_plugins[0]->get_info ()->name);
|
|
}
|
|
|
|
XMLNodeList nlist = node.children ();
|
|
XMLNodeIterator niter;
|
|
|
|
for (niter = nlist.begin (); niter != nlist.end (); ++niter) {
|
|
if ((*niter)->name () != "AutomationList") {
|
|
continue;
|
|
}
|
|
XMLProperty const* id_prop = (*niter)->property("automation-id");
|
|
if (!id_prop) {
|
|
assert (0);
|
|
continue;
|
|
}
|
|
Evoral::Parameter param = EventTypeMap::instance().from_symbol(id_prop->value());
|
|
std::shared_ptr<Evoral::Control> c = control (param);
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (c);
|
|
if (ac && ac->alist () && 0 == (ac->flags() & Controllable::NotAutomatable)) {
|
|
ac->alist()->set_state (**niter, version);
|
|
}
|
|
}
|
|
|
|
for (niter = nlist.begin (); niter != nlist.end (); ++niter) {
|
|
if ((*niter)->name () == _plugins[0]->state_node_name () || (any_vst && ((*niter)->name () == "lxvst" || (*niter)->name () == "windows-vst" || (*niter)->name () == "mac-vst"))) {
|
|
for (auto const& i : _plugins) {
|
|
if (!regenerate_xml_or_string_ids ()) {
|
|
i->set_insert_id (new_id);
|
|
} else {
|
|
i->set_insert_id (old_id);
|
|
}
|
|
|
|
i->set_state (**niter, version);
|
|
|
|
if (regenerate_xml_or_string_ids ()) {
|
|
i->set_insert_id (new_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (auto const& i : _plugins) {
|
|
i->activate ();
|
|
}
|
|
|
|
/* when copying plugin state, notify UI */
|
|
for (auto const& i : _controls) {
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (i.second);
|
|
if (ac) {
|
|
ac->Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
|
|
}
|
|
}
|
|
|
|
Latent::set_state (node, version);
|
|
TailTime::set_state (node, version);
|
|
|
|
return 0;
|
|
}
|
|
|
|
PluginType
|
|
RegionFxPlugin::type () const
|
|
{
|
|
if (!_plugins.empty ()) {
|
|
return plugin ()->get_info ()->type;
|
|
}
|
|
if (_state) {
|
|
ARDOUR::PluginType type;
|
|
std::string unique_id;
|
|
if (parse_plugin_type (*_state, type, unique_id)) {
|
|
return type;
|
|
}
|
|
}
|
|
return LXVST; /* whatever */
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::update_id (PBD::ID id)
|
|
{
|
|
set_id (id.to_s());
|
|
for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
|
|
(*i)->set_insert_id (id);
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::add_plugin (std::shared_ptr<Plugin> plugin)
|
|
{
|
|
plugin->set_insert_id (this->id ());
|
|
plugin->set_non_realtime (true);
|
|
|
|
if (_plugins.empty ()) {
|
|
/* first (and probably only) plugin instance - connect to relevant signals */
|
|
plugin->ParameterChangedExternally.connect_same_thread (*this, boost::bind (&RegionFxPlugin::parameter_changed_externally, this, _1, _2));
|
|
plugin->StartTouch.connect_same_thread (*this, boost::bind (&RegionFxPlugin::start_touch, this, _1));
|
|
plugin->EndTouch.connect_same_thread (*this, boost::bind (&RegionFxPlugin::end_touch, this, _1));
|
|
}
|
|
|
|
plugin->set_insert (this, _plugins.size ());
|
|
|
|
_plugins.push_back (plugin);
|
|
|
|
if (_plugins.size () > 1) {
|
|
_plugins[0]->add_slave (plugin, true);
|
|
plugin->DropReferences.connect_same_thread (*this, boost::bind (&RegionFxPlugin::plugin_removed, this, std::weak_ptr<Plugin> (plugin)));
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::plugin_removed (std::weak_ptr<Plugin> wp)
|
|
{
|
|
std::shared_ptr<Plugin> plugin = wp.lock ();
|
|
if (_plugins.size () == 0 || !plugin) {
|
|
return;
|
|
}
|
|
_plugins[0]->remove_slave (plugin);
|
|
}
|
|
|
|
bool
|
|
RegionFxPlugin::set_count (uint32_t num)
|
|
{
|
|
bool require_state = !_plugins.empty ();
|
|
|
|
if (require_state && num > 1 && plugin (0)->get_info ()->type == ARDOUR::AudioUnit) {
|
|
// we don't allow to replicate AUs
|
|
return false;
|
|
}
|
|
|
|
if (num == 0) {
|
|
return false;
|
|
} else if (num > _plugins.size ()) {
|
|
uint32_t diff = num - _plugins.size ();
|
|
|
|
for (uint32_t n = 0; n < diff; ++n) {
|
|
std::shared_ptr<Plugin> p = plugin_factory (_plugins[0]);
|
|
add_plugin (p);
|
|
|
|
if (require_state) {
|
|
_plugins[0]->set_insert_id (this->id ());
|
|
XMLNode& state = _plugins[0]->get_state ();
|
|
p->set_state (state, Stateful::current_state_version);
|
|
delete &state;
|
|
}
|
|
p->activate ();
|
|
}
|
|
|
|
} else if (num < _plugins.size ()) {
|
|
uint32_t diff = _plugins.size () - num;
|
|
for (uint32_t n = 0; n < diff; ++n) {
|
|
_plugins.back ()->drop_references ();
|
|
_plugins.pop_back ();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::drop_references ()
|
|
{
|
|
for (Plugins::iterator i = _plugins.begin (); i != _plugins.end (); ++i) {
|
|
(*i)->drop_references ();
|
|
}
|
|
|
|
SessionObject::drop_references ();
|
|
}
|
|
|
|
ARDOUR::samplecnt_t
|
|
RegionFxPlugin::signal_latency () const
|
|
{
|
|
if (_plugins.empty ()) {
|
|
return 0;
|
|
}
|
|
return _plugins.front ()->signal_latency ();
|
|
}
|
|
|
|
ARDOUR::samplecnt_t
|
|
RegionFxPlugin::signal_tailtime () const
|
|
{
|
|
if (_plugins.empty ()) {
|
|
return 0;
|
|
}
|
|
return _plugins.front ()->signal_tailtime ();
|
|
}
|
|
|
|
PlugInsertBase::UIElements
|
|
RegionFxPlugin::ui_elements () const
|
|
{
|
|
return PluginPreset;
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::create_parameters ()
|
|
{
|
|
assert (!_plugins.empty ());
|
|
|
|
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;
|
|
}
|
|
|
|
ParameterDescriptor desc;
|
|
plugin->get_parameter_descriptor (i, desc);
|
|
|
|
if (!plugin->parameter_is_input (i)) {
|
|
_control_outputs[i] = std::shared_ptr<TimedReadOnlyControl> (new TimedReadOnlyControl (plugin, desc, i));
|
|
continue;
|
|
}
|
|
|
|
Evoral::Parameter param (PluginAutomation, 0, i);
|
|
const bool automatable = a.find(param) != a.end();
|
|
|
|
std::shared_ptr<AutomationList> list (new AutomationList (param, desc, *this));
|
|
std::shared_ptr<AutomationControl> c (new TimedPluginControl (_session, this, param, desc, list, replay_param));
|
|
if (!automatable) {
|
|
c->set_flag (Controllable::NotAutomatable);
|
|
}
|
|
add_control (c);
|
|
|
|
plugin->set_automation_control (i, c);
|
|
}
|
|
|
|
Plugin::PropertyDescriptors const& pdl (plugin->get_supported_properties ());
|
|
|
|
for (Plugin::PropertyDescriptors::const_iterator p = pdl.begin (); p != pdl.end (); ++p) {
|
|
Evoral::Parameter param (PluginPropertyAutomation, 0, p->first);
|
|
ParameterDescriptor const& desc = plugin->get_property_descriptor (param.id ());
|
|
if (desc.datatype == Variant::NOTHING) {
|
|
continue;
|
|
}
|
|
std::shared_ptr<AutomationControl> c (new PluginPropertyControl (_session, this, param, desc));
|
|
c->set_flag (Controllable::NotAutomatable);
|
|
add_control (c);
|
|
}
|
|
|
|
plugin->PresetPortSetValue.connect_same_thread (*this, boost::bind (&RegionFxPlugin::preset_load_set_value, this, _1, _2));
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::set_default_automation (timepos_t end)
|
|
{
|
|
for (auto const& i : _controls) {
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (i.second);
|
|
if (ac->alist ()->empty ()) {
|
|
ac->alist ()->fast_simple_add (timepos_t (time_domain ()), ac->normal ());
|
|
ac->alist ()->fast_simple_add (end, ac->normal ());
|
|
} else {
|
|
ac->alist ()->truncate_end (end);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::truncate_automation_start (timecnt_t start)
|
|
{
|
|
for (auto const& i : _controls) {
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (i.second);
|
|
ac->alist ()->truncate_start (start);
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::truncate_automation_end (timepos_t end)
|
|
{
|
|
for (auto const& i : _controls) {
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (i.second);
|
|
ac->alist ()->truncate_end (end);
|
|
}
|
|
}
|
|
|
|
bool
|
|
RegionFxPlugin::write_immediate_event (Evoral::EventType event_type, size_t size, const uint8_t* buf)
|
|
{
|
|
bool rv = true;
|
|
for (auto const& i : _plugins) {
|
|
if (!i->write_immediate_event (event_type, size, buf)) {
|
|
rv = false;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
bool
|
|
RegionFxPlugin::load_preset (ARDOUR::Plugin::PresetRecord pr)
|
|
{
|
|
bool rv = true;
|
|
for (auto const& i : _plugins) {
|
|
if (!i->load_preset (pr)) {
|
|
rv = false;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
std::shared_ptr<ReadOnlyControl>
|
|
RegionFxPlugin::control_output (uint32_t num) const
|
|
{
|
|
CtrlOutMap::const_iterator i = _control_outputs.find (num);
|
|
if (i == _control_outputs.end ()) {
|
|
return std::shared_ptr<ReadOnlyControl> ();
|
|
} else {
|
|
return (*i).second;
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::parameter_changed_externally (uint32_t which, float val)
|
|
{
|
|
std::shared_ptr<Evoral::Control> c = control (Evoral::Parameter (PluginAutomation, 0, which));
|
|
std::shared_ptr<PluginControl> pc = std::dynamic_pointer_cast<PluginControl> (c);
|
|
|
|
if (pc) {
|
|
pc->catch_up_with_external_value (val);
|
|
}
|
|
|
|
/* 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_parameter (which, val, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string
|
|
RegionFxPlugin::describe_parameter (Evoral::Parameter param)
|
|
{
|
|
assert (!_plugins.empty ());
|
|
if (param.type () == PluginAutomation) {
|
|
return _plugins[0]->describe_parameter (param);
|
|
} else if (param.type () == PluginPropertyAutomation) {
|
|
std::shared_ptr<AutomationControl> c = std::dynamic_pointer_cast<AutomationControl> (control (param));
|
|
if (c && !c->desc ().label.empty ()) {
|
|
return c->desc ().label;
|
|
}
|
|
}
|
|
return EventTypeMap::instance ().to_symbol (param);
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::start_touch (uint32_t param_id)
|
|
{
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (control (Evoral::Parameter (PluginAutomation, 0, param_id)));
|
|
if (ac) {
|
|
ac->start_touch (timepos_t (_session.audible_sample ())); // XXX subtract region position
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::end_touch (uint32_t param_id)
|
|
{
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (control (Evoral::Parameter (PluginAutomation, 0, param_id)));
|
|
if (ac) {
|
|
ac->stop_touch (timepos_t (_session.audible_sample ())); // XXX subtract region position
|
|
}
|
|
}
|
|
|
|
bool
|
|
RegionFxPlugin::can_reset_all_parameters ()
|
|
{
|
|
if (_plugins.empty ()) {
|
|
return false;
|
|
}
|
|
|
|
bool all = true;
|
|
uint32_t params = 0;
|
|
std::shared_ptr<Plugin> plugin = _plugins.front ();
|
|
for (uint32_t par = 0; par < plugin->parameter_count (); ++par) {
|
|
bool ok = false;
|
|
const uint32_t cid = plugin->nth_parameter (par, ok);
|
|
|
|
if (!ok || !plugin->parameter_is_input (cid)) {
|
|
continue;
|
|
}
|
|
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (control (Evoral::Parameter (PluginAutomation, 0, cid)));
|
|
if (!ac) {
|
|
continue;
|
|
}
|
|
|
|
++params;
|
|
|
|
if (ac->automation_state () & Play) {
|
|
all = false;
|
|
break;
|
|
}
|
|
}
|
|
return all && (params > 0);
|
|
}
|
|
|
|
bool
|
|
RegionFxPlugin::reset_parameters_to_default ()
|
|
{
|
|
assert (!_plugins.empty ());
|
|
|
|
bool all = true;
|
|
std::shared_ptr<Plugin> plugin = _plugins.front ();
|
|
|
|
for (uint32_t par = 0; par < plugin->parameter_count (); ++par) {
|
|
bool ok = false;
|
|
const uint32_t cid = plugin->nth_parameter (par, ok);
|
|
|
|
if (!ok || !plugin->parameter_is_input (cid)) {
|
|
continue;
|
|
}
|
|
|
|
const float dflt = plugin->default_value (cid);
|
|
const float curr = plugin->get_parameter (cid);
|
|
|
|
if (dflt == curr) {
|
|
continue;
|
|
}
|
|
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (control (Evoral::Parameter (PluginAutomation, 0, cid)));
|
|
if (!ac) {
|
|
continue;
|
|
}
|
|
|
|
if (ac->automation_state () & Play) {
|
|
all = false;
|
|
continue;
|
|
}
|
|
|
|
ac->set_value (dflt, Controllable::NoGroup);
|
|
}
|
|
return all;
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::flush ()
|
|
{
|
|
_flush.store (1);
|
|
|
|
for (auto const& i : _control_outputs) {
|
|
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
|
|
RegionFxPlugin::can_support_io_configuration (const ChanCount& in, ChanCount& out)
|
|
{
|
|
if (_plugins.empty ()) {
|
|
out = ChanCount::min (in, out);
|
|
return true;
|
|
}
|
|
return private_can_support_io_configuration (in, out).method != Impossible;
|
|
}
|
|
|
|
PlugInsertBase::Match
|
|
RegionFxPlugin::private_can_support_io_configuration (ChanCount const& in, ChanCount& out) const
|
|
{
|
|
assert (!_plugins.empty ());
|
|
PluginInfoPtr info = _plugins.front ()->get_info ();
|
|
ChanCount aux_in;
|
|
|
|
/* count sidechain inputs */
|
|
const ChanCount& nis (info->n_inputs);
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
for (uint32_t in = 0; in < nis.get (*t); ++in) {
|
|
const Plugin::IOPortDescription& iod (plugin (0)->describe_io_port (*t, true, in));
|
|
if (iod.is_sidechain) {
|
|
aux_in.set (*t, 1 + aux_in.n (*t));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (info->reconfigurable_io ()) {
|
|
ChanCount inx (in);
|
|
bool const r = _plugins.front ()->match_variable_io (inx, aux_in, out);
|
|
if (!r) {
|
|
return Match (Impossible, 0);
|
|
}
|
|
out = ChanCount::min (in, out);
|
|
return Match (Delegate, 1, true);
|
|
}
|
|
|
|
ChanCount inputs = info->n_inputs - aux_in;
|
|
ChanCount outputs = info->n_outputs;
|
|
|
|
bool no_inputs = true;
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
if (inputs.get (*t) != 0) {
|
|
no_inputs = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (no_inputs) {
|
|
/* RegionFX cannot be generators */
|
|
return Match (Impossible, 0);
|
|
}
|
|
|
|
if (inputs == in && outputs == in) {
|
|
out = outputs;
|
|
return Match (ExactMatch, 1);
|
|
}
|
|
|
|
/* if the plugin has more outputs than we need, we ignore them */
|
|
if (inputs == in && outputs > in) {
|
|
out = inputs;
|
|
return Match (Split, 1);
|
|
}
|
|
|
|
/* test replication of mono plugins */
|
|
uint32_t f = 0;
|
|
bool can_replicate = true;
|
|
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
int32_t nin = inputs.get (*t);
|
|
/* No inputs of this type */
|
|
if (nin == 0 && in.get (*t) == 0) {
|
|
continue;
|
|
}
|
|
if (nin != 1 || outputs.get (*t) != 1) {
|
|
can_replicate = false;
|
|
break;
|
|
}
|
|
|
|
if (f == 0) {
|
|
f = in.get (*t) / nin;
|
|
}
|
|
if (f != (in.get (*t) / nin)) {
|
|
can_replicate = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (can_replicate && f > 0) {
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
out.set (*t, outputs.get (*t) * f);
|
|
}
|
|
return Match (Replicate, f);
|
|
}
|
|
|
|
/* If the plugin has more inputs than we want, we can `hide' some of them by feeding them silence. */
|
|
bool could_hide = false;
|
|
bool cannot_hide = false;
|
|
ChanCount hide_channels;
|
|
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
if (inputs.get (*t) > in.get (*t)) {
|
|
/* there is potential to hide, since the plugin has more inputs of type t than the insert */
|
|
hide_channels.set (*t, inputs.get (*t) - in.get (*t));
|
|
could_hide = true;
|
|
} else if (inputs.get (*t) < in.get (*t)) {
|
|
/* we definitely cannot hide, since the plugin has fewer inputs of type t than the insert */
|
|
cannot_hide = true;
|
|
}
|
|
}
|
|
|
|
if (could_hide && !cannot_hide && outputs >= in) {
|
|
out = ChanCount::min (in, outputs);
|
|
return Match (Hide, 1, false, false, hide_channels);
|
|
}
|
|
|
|
/* Test replication of multi-channel plugins:
|
|
* (at least as many plugins so that output count matches input count)
|
|
*/
|
|
f = 0;
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
uint32_t nin = inputs.get (*t);
|
|
uint32_t nout = outputs.get (*t);
|
|
if (nin == 0 || in.get (*t) == 0 || nout == 0) {
|
|
continue;
|
|
}
|
|
// prefer floor() so the count won't overly increase IFF (nin < nout)
|
|
f = max (f, (uint32_t)floor (in.get (*t) / (float)nout));
|
|
}
|
|
if (f > 0 && outputs * f >= out) {
|
|
out = ChanCount::min (in, outputs * f);
|
|
return Match (Replicate, f, true);
|
|
}
|
|
|
|
/* Test replication of multi-channel plugins:
|
|
* (at least as many plugins to connect all inputs)
|
|
*/
|
|
f = 0;
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
uint32_t nin = inputs.get (*t);
|
|
if (nin == 0 || in.get (*t) == 0) {
|
|
continue;
|
|
}
|
|
f = max (f, (uint32_t)ceil (in.get (*t) / (float)nin));
|
|
}
|
|
if (f > 0) {
|
|
out = ChanCount::min (in, outputs * f);
|
|
return Match (Replicate, f, true);
|
|
}
|
|
|
|
return Match (Impossible, 0);
|
|
}
|
|
|
|
bool
|
|
RegionFxPlugin::configure_io (ChanCount in, ChanCount out)
|
|
{
|
|
_configured_in = in;
|
|
_configured_out = out;
|
|
|
|
if (_plugins.empty ()) {
|
|
return true;
|
|
}
|
|
|
|
ChanCount natural_input_streams = _plugins[0]->get_info ()->n_inputs;
|
|
ChanCount natural_output_streams = _plugins[0]->get_info ()->n_outputs;
|
|
|
|
_match = private_can_support_io_configuration (in, out);
|
|
|
|
if (set_count (_match.plugins) == false) {
|
|
return false;
|
|
}
|
|
|
|
/* configure plugins */
|
|
switch (_match.method) {
|
|
case Split:
|
|
/* fallthrough */
|
|
case Hide:
|
|
if (_plugins.front ()->reconfigure_io (natural_input_streams, ChanCount (), out) == false) {
|
|
return false;
|
|
}
|
|
break;
|
|
case Delegate: {
|
|
ChanCount din (in);
|
|
ChanCount daux; // no sidechain ports
|
|
ChanCount dout (_configured_out);
|
|
bool const r = _plugins.front ()->match_variable_io (din, daux, dout);
|
|
assert (r);
|
|
if (_plugins.front ()->reconfigure_io (din, daux, dout) == false) {
|
|
return false;
|
|
}
|
|
DEBUG_TRACE (DEBUG::RegionFx, string_compose ("Delegate configured in: %1, out: %2 for in: %3 out: %4\n", din, dout, in, _configured_out));
|
|
if (din < in || dout < _configured_out) {
|
|
return false;
|
|
}
|
|
/* update after match_variable_io sets info */
|
|
natural_input_streams = _plugins[0]->get_info ()->n_inputs;
|
|
natural_output_streams = _plugins[0]->get_info ()->n_outputs;
|
|
}
|
|
break;
|
|
case Replicate:
|
|
assert (!_plugins.front ()->get_info ()->reconfigurable_io ());
|
|
break;
|
|
default:
|
|
if (_plugins.front ()->reconfigure_io (in, ChanCount (), out) == false) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* compare to PluginInsert::reset_map */
|
|
_in_map.clear ();
|
|
_out_map.clear ();
|
|
|
|
/* builld input map, skip plugin sidechain pin. */
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
uint32_t pc = 0;
|
|
uint32_t cin = 0;
|
|
const uint32_t cend = _configured_in.get (*t);
|
|
for (Plugins::iterator i = _plugins.begin (); i != _plugins.end (); ++i, ++pc) {
|
|
const uint32_t nis = natural_input_streams.get (*t);
|
|
for (uint32_t in = 0; in < nis; ++in) {
|
|
const Plugin::IOPortDescription& iod (_plugins[pc]->describe_io_port (*t, true, in));
|
|
if (iod.is_sidechain) {
|
|
/* leave N/C */
|
|
continue;
|
|
}
|
|
if (cin < cend) {
|
|
_in_map[pc].set (*t, in, cin);
|
|
++cin;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* build output map */
|
|
uint32_t pc = 0;
|
|
for (Plugins::iterator i = _plugins.begin (); i != _plugins.end (); ++i, ++pc) {
|
|
_out_map[pc] = ChanMapping (ChanCount::min (natural_output_streams, _configured_out));
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
_out_map[pc].offset_to (*t, pc * natural_output_streams.get (*t));
|
|
}
|
|
}
|
|
|
|
/* now sanitize maps */
|
|
for (uint32_t pc = 0; pc < get_count (); ++pc) {
|
|
ChanMapping new_in;
|
|
ChanMapping new_out;
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
for (uint32_t i = 0; i < natural_input_streams.get (*t); ++i) {
|
|
bool valid;
|
|
uint32_t idx = _in_map[pc].get (*t, i, &valid);
|
|
if (valid && idx < _configured_in.get (*t)) {
|
|
new_in.set (*t, i, idx);
|
|
}
|
|
}
|
|
for (uint32_t o = 0; o < natural_output_streams.get (*t); ++o) {
|
|
bool valid;
|
|
uint32_t idx = _out_map[pc].get (*t, o, &valid);
|
|
if (valid && idx < _configured_out.get (*t)) {
|
|
new_out.set (*t, o, idx);
|
|
}
|
|
}
|
|
}
|
|
_in_map[pc] = new_in;
|
|
_out_map[pc] = new_out;
|
|
}
|
|
|
|
_no_inplace = check_inplace ();
|
|
_required_buffers = ChanCount::max (_configured_in, natural_input_streams + ChanCount::max (_configured_out, natural_output_streams * get_count ()));
|
|
|
|
#ifndef NDEBUG
|
|
if (DEBUG_ENABLED(DEBUG::RegionFx)) {
|
|
pc = 0;
|
|
DEBUG_STR_DECL(a);
|
|
DEBUG_STR_APPEND(a, "\n--------<<--------\n");
|
|
DEBUG_STR_APPEND(a, string_compose ("RFX IO Config for %1 in: %2 out: %3 req: %4\n", name(), _configured_in, _configured_out, _required_buffers));
|
|
DEBUG_STR_APPEND(a, "Match: " << _match << " no inplace: " << _no_inplace << "\n");
|
|
for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
|
|
if (pc > 0) {
|
|
DEBUG_STR_APPEND(a, "----><----\n");
|
|
}
|
|
DEBUG_STR_APPEND(a, string_compose ("Channel Map for %1 plugin %2\n", name(), pc));
|
|
DEBUG_STR_APPEND(a, " * Inputs:\n");
|
|
DEBUG_STR_APPEND(a, _in_map[pc]);
|
|
DEBUG_STR_APPEND(a, " * Outputs:\n");
|
|
DEBUG_STR_APPEND(a, _out_map[pc]);
|
|
}
|
|
DEBUG_STR_APPEND(a, "-------->>--------\n");
|
|
DEBUG_TRACE (DEBUG::RegionFx, DEBUG_STR(a).str());
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
RegionFxPlugin::check_inplace ()
|
|
{
|
|
bool inplace_ok = !_plugins.front ()->inplace_broken ();
|
|
|
|
if (_match.method == Hide || _match.method == Split) {
|
|
inplace_ok = false;
|
|
}
|
|
|
|
if (_match.method == Replicate) {
|
|
for (uint32_t pc = 0; pc < get_count () && inplace_ok; ++pc) {
|
|
if (_in_map[pc] != _out_map[pc]) {
|
|
inplace_ok = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ChanCount natural_input_streams = _plugins[0]->get_info ()->n_inputs;
|
|
ChanCount natural_output_streams = _plugins[0]->get_info ()->n_outputs;
|
|
|
|
if (natural_input_streams * get_count () != _configured_in) {
|
|
inplace_ok = false;
|
|
}
|
|
if (natural_output_streams * get_count () != _configured_out) {
|
|
inplace_ok = false;
|
|
}
|
|
|
|
ARDOUR::ChanMapping in_map;
|
|
ARDOUR::ChanMapping out_map;
|
|
|
|
uint32_t pc = 0;
|
|
for (auto const& mi : _in_map) {
|
|
ChanMapping m (mi.second);
|
|
const ChanMapping::Mappings& mp (mi.second.mappings());
|
|
for (auto const& tm: mp) {
|
|
for (auto const& i: tm.second) {
|
|
in_map.set (tm.first, i.first + pc * natural_input_streams.get (tm.first), i.second);
|
|
}
|
|
}
|
|
++pc;
|
|
}
|
|
|
|
pc = 0;
|
|
for (auto const& mi : _out_map) {
|
|
ChanMapping m (mi.second);
|
|
const ChanMapping::Mappings& mp (mi.second.mappings());
|
|
for (auto const& tm: mp) {
|
|
for (auto const& i: tm.second) {
|
|
out_map.set (tm.first, i.first + pc * natural_output_streams.get (tm.first), i.second);
|
|
}
|
|
}
|
|
++pc;
|
|
}
|
|
|
|
if (!in_map.is_monotonic ()) {
|
|
inplace_ok = false;
|
|
}
|
|
if (!out_map.is_monotonic ()) {
|
|
inplace_ok = false;
|
|
}
|
|
return !inplace_ok;
|
|
}
|
|
|
|
for (uint32_t pc = 0; pc < get_count () && inplace_ok; ++pc) {
|
|
if (!_in_map[pc].is_monotonic ()) {
|
|
inplace_ok = false;
|
|
}
|
|
if (!_out_map[pc].is_monotonic ()) {
|
|
inplace_ok = false;
|
|
}
|
|
}
|
|
return !inplace_ok;
|
|
}
|
|
|
|
int
|
|
RegionFxPlugin::set_block_size (pframes_t nframes)
|
|
{
|
|
int ret = 0;
|
|
for (auto const& i : _plugins) {
|
|
if (i->set_block_size (nframes) != 0) {
|
|
ret = -1;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::shared_ptr<Evoral::Control>
|
|
RegionFxPlugin::control_factory(const Evoral::Parameter& param)
|
|
{
|
|
Evoral::Control* control = NULL;
|
|
ParameterDescriptor desc(param);
|
|
std::shared_ptr<AutomationList> list;
|
|
|
|
#if 0
|
|
if (param.type() == PluginAutomation) {
|
|
_plugin->get_parameter_descriptor(param.id(), desc);
|
|
control = new IOPlug::PluginControl (pi, param, desc);
|
|
} else if (param.type() == PluginPropertyAutomation) {
|
|
desc = _plugin->get_property_descriptor (param.id());
|
|
if (desc.datatype != Variant::NOTHING) {
|
|
control = new IOPlug::PluginPropertyControl(pi, param, desc, list);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!control) {
|
|
std::shared_ptr<AutomationList> list;
|
|
control = new AutomationControl (_session, param, desc, list);
|
|
}
|
|
|
|
return std::shared_ptr<Evoral::Control>(control);
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::automatables (ControllableSet& s) const
|
|
{
|
|
for (auto const& i : _controls) {
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (i.second);
|
|
if (ac) {
|
|
s.insert (ac);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFxPlugin::automation_run (samplepos_t start, pframes_t nframes)
|
|
{
|
|
for (auto const& i : controls ()) {
|
|
std::shared_ptr<AutomationControl> c = std::dynamic_pointer_cast<AutomationControl> (i.second);
|
|
if (!c) {
|
|
continue;
|
|
}
|
|
c->automation_run (start, nframes);
|
|
}
|
|
}
|
|
|
|
bool
|
|
RegionFxPlugin::find_next_event (timepos_t const& start, timepos_t const& end, Evoral::ControlEvent& next_event) const
|
|
{
|
|
next_event.when = start <= end ? timepos_t::max (start.time_domain()) : timepos_t (start.time_domain());
|
|
|
|
for (auto const& i : controls ()) {
|
|
std::shared_ptr<AutomationControl> c = std::dynamic_pointer_cast<AutomationControl> (i.second);
|
|
if (c) {
|
|
Automatable::find_next_ac_event (c, start, end, next_event);
|
|
}
|
|
}
|
|
return next_event.when != (start <= end ? timepos_t::max (next_event.when.time_domain ()) : timepos_t (next_event.when.time_domain ()));
|
|
}
|
|
|
|
bool
|
|
RegionFxPlugin::run (BufferSet& bufs, samplepos_t start, samplepos_t end, samplepos_t pos, pframes_t nframes, sampleoffset_t off)
|
|
{
|
|
if (_plugins.empty ()) {
|
|
return true;
|
|
}
|
|
|
|
Glib::Threads::Mutex::Lock lp (_process_lock);
|
|
|
|
int canderef (1);
|
|
if (_flush.compare_exchange_strong (canderef, 0)) {
|
|
for (auto const& i : _plugins) {
|
|
i->flush ();
|
|
}
|
|
}
|
|
|
|
const bool no_split_cycle = _plugins.front ()->requires_fixed_sized_buffers () /* || _plugins.front ()->get_info ()->type == ARDOUR::VST3 */;
|
|
|
|
Evoral::ControlEvent next_event (timepos_t (Temporal::AudioTime), 0.0f);
|
|
samplecnt_t offset = 0;
|
|
|
|
Glib::Threads::Mutex::Lock lm (control_lock ());
|
|
|
|
if (no_split_cycle || !find_next_event (timepos_t (start), timepos_t (end), next_event)) {
|
|
/* no events have a time within the relevant range */
|
|
return connect_and_run (bufs, start, end, pos, nframes, off, offset);
|
|
}
|
|
|
|
while (nframes) {
|
|
samplecnt_t cnt = min (timepos_t (start).distance (next_event.when).samples (), (samplecnt_t)nframes);
|
|
|
|
/* An event returned by find_next_event is always be *after* `start`. */
|
|
assert (timepos_t (start) < next_event.when);
|
|
/* However it may still be at the sample sample (when event is using BeatTime),
|
|
* in which case we need to look for the next event, after that.
|
|
*/
|
|
int timeout = 8; // just in case there is more than one music-time event for the given sample.
|
|
while (cnt == 0 && --timeout > 0 && Temporal::AudioTime != next_event.when.time_domain ()) {
|
|
timepos_t _start = next_event.when; // copy, since find_next_event uses a reference, and modifies next_event
|
|
if (!find_next_event (_start, timepos_t (end), next_event)) {
|
|
cnt = nframes;
|
|
break;
|
|
} else {
|
|
cnt = min (timepos_t (start).distance (next_event.when).samples (), (samplecnt_t)nframes);
|
|
}
|
|
}
|
|
|
|
if (cnt <= 0) {
|
|
/* prevent endless loops, just skip over events until next cycle.
|
|
* (alternatively we could single step and set cnt = 1;)
|
|
*/
|
|
break;
|
|
}
|
|
|
|
if (!connect_and_run (bufs, start, start + cnt, pos, cnt, off, offset)) {
|
|
return false;
|
|
}
|
|
|
|
nframes -= cnt;
|
|
offset += cnt;
|
|
start += cnt;
|
|
|
|
timepos_t _start = next_event.when;
|
|
if (!find_next_event (_start, timepos_t (end), next_event)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nframes) {
|
|
return connect_and_run (bufs, start, start + nframes, pos, nframes, off, offset);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
RegionFxPlugin::connect_and_run (BufferSet& bufs, samplepos_t start, samplepos_t end, samplepos_t pos, pframes_t nframes, samplecnt_t buf_off, samplecnt_t cycle_off)
|
|
{
|
|
const bool no_inplace = _no_inplace;
|
|
Temporal::TempoMap::update_thread_tempo_map ();
|
|
|
|
//bufs.set_count(ChanCount::max(bufs.count(), _configured_internal)); // ADD SC
|
|
bufs.set_count (ChanCount::max (bufs.count (), _configured_out));
|
|
|
|
automation_run (start, nframes);
|
|
// TODO set VST3 event-list, then unset no_split_cycle
|
|
|
|
ChanCount natural_input_streams = _plugins[0]->get_info ()->n_inputs;
|
|
ChanCount natural_output_streams = _plugins[0]->get_info ()->n_outputs;
|
|
|
|
std::map<uint32_t, ARDOUR::ChanMapping> in_map (_in_map);
|
|
std::map<uint32_t, ARDOUR::ChanMapping> const& out_map (_out_map);
|
|
|
|
if (no_inplace) {
|
|
uint32_t pc = 0;
|
|
BufferSet& inplace_bufs = _session.get_noinplace_buffers ();
|
|
ARDOUR::ChanMapping used_outputs;
|
|
|
|
assert (inplace_bufs.count () >= natural_input_streams + _configured_out);
|
|
|
|
/* build used-output map */
|
|
for (Plugins::iterator i = _plugins.begin (); i != _plugins.end (); ++i, ++pc) {
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
for (uint32_t out = 0; out < natural_output_streams.get (*t); ++out) {
|
|
bool valid;
|
|
uint32_t out_idx = out_map.at (pc).get (*t, out, &valid);
|
|
if (valid) {
|
|
used_outputs.set (*t, out_idx, 1); // mark as used
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* silence outputs */
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
for (uint32_t out = 0; out < bufs.count ().get (*t); ++out) {
|
|
bool valid;
|
|
uint32_t m = out + natural_input_streams.get (*t);
|
|
used_outputs.get (*t, out, &valid);
|
|
if (valid) {
|
|
/* the plugin is expected to write here, but may not :(
|
|
* (e.g. drumgizmo w/o kit loaded)
|
|
*/
|
|
inplace_bufs.get_available (*t, m).silence (nframes);
|
|
}
|
|
}
|
|
}
|
|
|
|
pc = 0;
|
|
for (Plugins::iterator i = _plugins.begin (); i != _plugins.end (); ++i, ++pc) {
|
|
ARDOUR::ChanMapping i_in_map (natural_input_streams);
|
|
ARDOUR::ChanMapping i_out_map (out_map.at (pc));
|
|
ARDOUR::ChanCount mapped;
|
|
|
|
/* map inputs sequentially */
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
for (uint32_t in = 0; in < natural_input_streams.get (*t); ++in) {
|
|
bool valid;
|
|
uint32_t in_idx = in_map.at (pc).get (*t, in, &valid);
|
|
uint32_t m = mapped.get (*t);
|
|
if (valid) {
|
|
inplace_bufs.get_available (*t, m).read_from (bufs.get_available (*t, in_idx), nframes, cycle_off, cycle_off + buf_off);
|
|
} else {
|
|
inplace_bufs.get_available (*t, m).silence (nframes, cycle_off);
|
|
i_in_map.unset (*t, in);
|
|
}
|
|
mapped.set (*t, m + 1);
|
|
}
|
|
}
|
|
|
|
/* outputs are mapped to inplace_bufs after the inputs */
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
i_out_map.offset_to (*t, natural_input_streams.get (*t));
|
|
}
|
|
|
|
if ((*i)->connect_and_run (inplace_bufs, pos + start, pos + end, 1.0, i_in_map, i_out_map, nframes, cycle_off)) {
|
|
return false;
|
|
}
|
|
}
|
|
/* all instances have completed, now copy data that was written
|
|
* and zero unconnected buffers */
|
|
ARDOUR::ChanMapping nonzero_out (used_outputs);
|
|
for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
|
|
for (uint32_t out = 0; out < bufs.count ().get (*t); ++out) {
|
|
bool valid;
|
|
used_outputs.get (*t, out, &valid);
|
|
if (!valid) {
|
|
bufs.get_available (*t, out).silence (nframes, cycle_off + buf_off);
|
|
} else {
|
|
uint32_t m = out + natural_input_streams.get (*t);
|
|
bufs.get_available (*t, out).read_from (inplace_bufs.get_available (*t, m), nframes, cycle_off + buf_off, cycle_off);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* in-place processing */
|
|
uint32_t pc = 0;
|
|
for (Plugins::iterator i = _plugins.begin (); i != _plugins.end (); ++i, ++pc) {
|
|
if ((*i)->connect_and_run (bufs, pos + start, pos + end, 1.0, in_map.at (pc), out_map.at (pc), nframes, cycle_off + buf_off)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto const& i : _control_outputs) {
|
|
shared_ptr<TimedReadOnlyControl> toc = std::dynamic_pointer_cast<TimedReadOnlyControl>(i.second);
|
|
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;
|
|
LatencyChanged (); /* EMIT SIGNAL */
|
|
}
|
|
const samplecnt_t t = effective_latency ();
|
|
if (_plugin_signal_tailtime != l) {
|
|
_plugin_signal_tailtime = t;
|
|
TailTimeChanged (); /* EMIT SIGNAL */
|
|
}
|
|
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 ();
|
|
}
|
|
}
|
|
}
|