mirror of
https://github.com/Ardour/ardour.git
synced 2026-01-04 20:55:48 +01:00
There are some issues with PBD::SignalWithCombiner on aarch64 when the handlers return bool. These may arise from specializations of std::vector<bool> and std::list<bool> in stdlib, but this is not clear. For now, to avoid an ASAN warning about calling operator delete() on stack memory, change the only signal that does this to use int rather than bool
679 lines
18 KiB
C++
679 lines
18 KiB
C++
/*
|
|
* Copyright (C) 2022 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.
|
|
*/
|
|
#include <cassert>
|
|
|
|
#include "pbd/types_convert.h"
|
|
#include "pbd/unwind.h"
|
|
#include "pbd/xml++.h"
|
|
|
|
#include "temporal/tempo.h"
|
|
|
|
#include "ardour/audioengine.h"
|
|
#include "ardour/audio_buffer.h"
|
|
#include "ardour/audio_port.h"
|
|
#include "ardour/event_type_map.h"
|
|
#include "ardour/graph.h"
|
|
#include "ardour/io.h"
|
|
#include "ardour/io_plug.h"
|
|
#include "ardour/lv2_plugin.h"
|
|
#include "ardour/readonly_control.h"
|
|
#include "ardour/session.h"
|
|
#include "ardour/utils.h"
|
|
|
|
#include "pbd/i18n.h"
|
|
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
using namespace std;
|
|
|
|
IOPlug::IOPlug (Session& s, std::shared_ptr<Plugin> p, bool pre)
|
|
: SessionObject (s, "")
|
|
, GraphNode (s._process_graph)
|
|
, _plugin (p)
|
|
, _pre (pre)
|
|
, _plugin_signal_latency (0)
|
|
, _configuring_io (false)
|
|
, _window_proxy (0)
|
|
{
|
|
_stat_reset.store (0);
|
|
_reset_meters.store (0);
|
|
|
|
if (_plugin) {
|
|
setup ();
|
|
set_name (p->get_info()->name);
|
|
}
|
|
_input.reset (new IO (_session, io_name (), IO::Input));
|
|
_output.reset (new IO (_session, io_name (), IO::Output));
|
|
|
|
/* do not allow to add/remove ports (for now).
|
|
* when adding ports _buf will needs to be resized.
|
|
*/
|
|
_input->PortCountChanging.connect_same_thread (*this, [this](ChanCount) { return _configuring_io ? 0 : 1; });
|
|
_output->PortCountChanging.connect_same_thread (*this, [this](ChanCount) { return _configuring_io ? 0 : 1; });
|
|
}
|
|
|
|
IOPlug::~IOPlug ()
|
|
{
|
|
for (CtrlOutMap::const_iterator i = _control_outputs.begin(); i != _control_outputs.end(); ++i) {
|
|
std::dynamic_pointer_cast<ReadOnlyControl>(i->second)->drop_references ();
|
|
}
|
|
|
|
Glib::Threads::Mutex::Lock lm (_control_lock);
|
|
for (Controls::const_iterator li = _controls.begin(); li != _controls.end(); ++li) {
|
|
std::dynamic_pointer_cast<AutomationControl>(li->second)->drop_references ();
|
|
}
|
|
_controls.clear ();
|
|
}
|
|
|
|
std::string
|
|
IOPlug::io_name (std::string const& n) const
|
|
{
|
|
return (string_compose ("%1/%2/%3", _("IO"), _pre ? S_("IO|Pre"): S_("IO|Post"), n.empty () ? name () : n));
|
|
}
|
|
|
|
std::string
|
|
IOPlug::ensure_io_name (std::string newname) const
|
|
{
|
|
while (!_session.io_name_is_legal (io_name (newname))) {
|
|
newname = bump_name_once (newname, ' ');
|
|
if (newname == name()) {
|
|
break;
|
|
}
|
|
}
|
|
return newname;
|
|
}
|
|
|
|
XMLNode&
|
|
IOPlug::get_state() const
|
|
{
|
|
XMLNode* node = new XMLNode (/*state_node_name*/ "IOPlug");
|
|
|
|
Latent::add_state (node);
|
|
|
|
node->set_property("type", _plugin->state_node_name ());
|
|
node->set_property("unique-id", _plugin->unique_id ());
|
|
|
|
node->set_property("id", id());
|
|
node->set_property("name", name());
|
|
node->set_property("pre", _pre);
|
|
|
|
_plugin->set_insert_id(this->id());
|
|
node->add_child_nocopy (_plugin->get_state());
|
|
|
|
for (auto const& c : controls()) {
|
|
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl> (c.second);
|
|
if (ac) {
|
|
node->add_child_nocopy (ac->get_state());
|
|
}
|
|
}
|
|
|
|
if (_input) {
|
|
XMLNode& i (_input->get_state ());
|
|
node->add_child_nocopy (i);
|
|
}
|
|
if (_output) {
|
|
XMLNode& o (_output->get_state ());
|
|
node->add_child_nocopy (o);
|
|
}
|
|
return *node;
|
|
}
|
|
|
|
int
|
|
IOPlug::set_state (const XMLNode& node, int version)
|
|
{
|
|
set_id (node);
|
|
assert (!regenerate_xml_or_string_ids ());
|
|
|
|
ARDOUR::PluginType type;
|
|
std::string unique_id;
|
|
if (! parse_plugin_type (node, type, unique_id)) {
|
|
return -1;
|
|
}
|
|
|
|
bool any_vst = false;
|
|
_plugin = find_and_load_plugin (_session, node, type, unique_id, any_vst);
|
|
|
|
if (!_plugin) {
|
|
return -1;
|
|
}
|
|
|
|
node.get_property ("pre", _pre);
|
|
|
|
string name;
|
|
if (node.get_property ("name", name)) {
|
|
set_name (name);
|
|
} else {
|
|
set_name (_plugin->get_info()->name);
|
|
}
|
|
|
|
setup ();
|
|
set_control_ids (node, version);
|
|
|
|
_plugin->set_insert_id (this->id());
|
|
|
|
XMLNodeList nlist = node.children();
|
|
XMLNodeIterator niter;
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
if ((*niter)->name() == _plugin->state_node_name ()
|
|
|| (any_vst && ((*niter)->name() == "lxvst" || (*niter)->name() == "windows-vst" || (*niter)->name() == "mac-vst"))
|
|
) {
|
|
_plugin->set_state (**niter, version);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_input) {
|
|
std::string str;
|
|
const string instr = enum_2_string (IO::Input);
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
if ((*niter)->get_property ("direction", str) && str == instr) {
|
|
_input->set_state(**niter, version);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (_output) {
|
|
std::string str;
|
|
const string outstr = enum_2_string (IO::Output);
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
if ((*niter)->get_property ("direction", str) && str == outstr) {
|
|
_output->set_state(**niter, version);
|
|
}
|
|
}
|
|
}
|
|
|
|
Latent::set_state (node, version);
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
IOPlug::set_name (std::string const& str)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (name () == str) {
|
|
return ret;
|
|
}
|
|
|
|
std::string new_name = ensure_io_name (str);
|
|
|
|
if (ret && _input) {
|
|
ret = _input->set_name (io_name (new_name));
|
|
}
|
|
|
|
if (ret && _output) {
|
|
ret = _output->set_name (io_name (new_name));
|
|
}
|
|
|
|
if (ret) {
|
|
ret = SessionObject::set_name (new_name); /* never fails */
|
|
assert (ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
IOPlug::setup ()
|
|
{
|
|
create_parameters ();
|
|
|
|
PluginInfoPtr pip = _plugin->get_info ();
|
|
ChanCount aux_in;
|
|
if (pip->reconfigurable_io()) {
|
|
_n_in = _plugin->input_streams ();
|
|
_n_out = _plugin->output_streams ();
|
|
if (_n_in.n_total () == 0 && _n_out.n_total () == 0) {
|
|
if (pip->is_instrument ()) {
|
|
_n_in.set_midi (1);
|
|
} else {
|
|
_n_in.set_audio (2);
|
|
}
|
|
_n_out.set_audio (2);
|
|
}
|
|
_plugin->match_variable_io (_n_in, aux_in, _n_out);
|
|
} else {
|
|
_n_in = pip->n_inputs;
|
|
_n_out = pip->n_outputs;
|
|
}
|
|
|
|
_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->activate ();
|
|
_plugin->set_insert (this, 0);
|
|
}
|
|
|
|
samplecnt_t
|
|
IOPlug::signal_latency () const
|
|
{
|
|
return _plugin->signal_latency ();
|
|
}
|
|
|
|
void
|
|
IOPlug::set_public_latency (bool playback)
|
|
{
|
|
/* Step1: set private port latency
|
|
* compare to Route::set_private_port_latencies, Route::update_port_latencies
|
|
*/
|
|
std::shared_ptr<PortSet> from = playback ? _output->ports () : _input->ports ();
|
|
std::shared_ptr<PortSet> to = playback ? _input->ports () : _output->ports ();
|
|
|
|
LatencyRange all_connections;
|
|
all_connections.min = ~((pframes_t) 0);
|
|
all_connections.max = 0;
|
|
|
|
for (auto const& p : *from) {
|
|
if (!p->connected ()) {
|
|
continue;
|
|
}
|
|
|
|
LatencyRange range;
|
|
p->get_connected_latency_range (range, playback);
|
|
|
|
all_connections.min = min (all_connections.min, range.min);
|
|
all_connections.max = max (all_connections.max, range.max);
|
|
}
|
|
if (all_connections.min == ~((pframes_t) 0)) {
|
|
all_connections.min = 0;
|
|
}
|
|
|
|
/* set the "from" port latencies to the max/min range of all their connections */
|
|
for (auto const& p : *from) {
|
|
p->set_private_latency_range (all_connections, playback);
|
|
}
|
|
|
|
all_connections.min += _plugin_signal_latency;
|
|
all_connections.max += _plugin_signal_latency;
|
|
|
|
for (auto const& p : *to) {
|
|
p->set_private_latency_range (all_connections, playback);
|
|
}
|
|
|
|
/* Step2: publish private latencies.
|
|
* compare to Route::set_public_port_latencies
|
|
*/
|
|
|
|
if (playback) {
|
|
_output->set_public_port_latency_from_connections ();
|
|
_input->set_public_port_latencies (all_connections.max, true);
|
|
} else {
|
|
_input->set_public_port_latency_from_connections ();
|
|
_output->set_public_port_latencies (all_connections.max, false);
|
|
}
|
|
}
|
|
|
|
void
|
|
IOPlug::create_parameters ()
|
|
{
|
|
assert (_plugin);
|
|
|
|
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<ReadOnlyControl> (new ReadOnlyControl (_plugin, desc, i));
|
|
continue;
|
|
}
|
|
|
|
Evoral::Parameter param (PluginAutomation, 0, i);
|
|
|
|
std::shared_ptr<AutomationControl> c (new PluginControl (_session, this, param, desc));
|
|
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, std::bind (&IOPlug::preset_load_set_value, this, _1, _2));
|
|
}
|
|
|
|
void
|
|
IOPlug::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);
|
|
}
|
|
}
|
|
|
|
int
|
|
IOPlug::set_block_size (pframes_t n_samples)
|
|
{
|
|
return _plugin->set_block_size (n_samples);
|
|
}
|
|
|
|
PlugInsertBase::UIElements
|
|
IOPlug::ui_elements () const
|
|
{
|
|
UIElements rv = PluginPreset;
|
|
if (_plugin->get_info ()->is_instrument ()) {
|
|
rv = static_cast<PlugInsertBase::UIElements> (static_cast <std::uint8_t>(rv) | static_cast<std::uint8_t> (PlugInsertBase::MIDIKeyboard));
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
bool
|
|
IOPlug::ensure_io ()
|
|
{
|
|
PBD::Unwinder<bool> uw (_configuring_io, true);
|
|
|
|
/* must be called with process-lock held */
|
|
if (_input->ensure_io (_n_in, false, this) != 0) {
|
|
return false;
|
|
}
|
|
if (_output->ensure_io (_n_out, false, this) != 0) {
|
|
return false;
|
|
}
|
|
|
|
_bufs.ensure_buffers (ChanCount::max (_n_in, _n_out), _session.get_block_size ());
|
|
|
|
for (uint32_t i = 0; i < _n_in.n_audio (); ++i) {
|
|
const auto& pd (_plugin->describe_io_port (DataType::AUDIO, true, i));
|
|
std::string const pn = string_compose ("%1 %2 - %3", _("IO"), name (), pd.name);
|
|
_input->audio (i)->set_pretty_name (pn);
|
|
}
|
|
for (uint32_t i = 0; i < _n_in.n_midi (); ++i) {
|
|
const auto& pd (_plugin->describe_io_port (DataType::MIDI, true, i));
|
|
std::string const pn = string_compose ("%1 %2 - %3", _("IO"), name (), pd.name);
|
|
_input->midi (i)->set_pretty_name (pn);
|
|
}
|
|
for (uint32_t i = 0; i < _n_out.n_audio (); ++i) {
|
|
const auto& pd (_plugin->describe_io_port (DataType::AUDIO, false, i));
|
|
std::string const pn = string_compose ("%1 %2 - %3", _("IO"), name (), pd.name);
|
|
_output->audio (i)->set_pretty_name (pn);
|
|
}
|
|
for (uint32_t i = 0; i < _n_out.n_midi (); ++i) {
|
|
const auto& pd (_plugin->describe_io_port (DataType::MIDI, false, i));
|
|
std::string const pn = string_compose ("%1 %2 - %3", _("IO"), name (), pd.name);
|
|
_output->midi (i)->set_pretty_name (pn);
|
|
}
|
|
|
|
if (_pre) {
|
|
for (uint32_t i = 0; i < _n_out.n_audio (); ++i) {
|
|
std::string const& n = AudioEngine::instance ()->make_port_name_non_relative (_output->audio (i)->name ());
|
|
_audio_input_ports.insert (make_pair (n, PortManager::AudioInputPort (24288))); // 2^19 ~ 1MB / port
|
|
}
|
|
for (uint32_t i = 0; i < _n_out.n_midi (); ++i) {
|
|
std::string const& n = AudioEngine::instance ()->make_port_name_non_relative (_output->midi (i)->name ());
|
|
_midi_input_ports.insert (make_pair (n, PortManager::MIDIInputPort (32)));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ChanMapping
|
|
IOPlug::input_map (uint32_t num) const
|
|
{
|
|
if (num == 1) {
|
|
return ChanMapping (_n_in);
|
|
} else {
|
|
return ChanMapping ();
|
|
}
|
|
}
|
|
|
|
ChanMapping
|
|
IOPlug::output_map (uint32_t num) const
|
|
{
|
|
if (num == 1) {
|
|
return ChanMapping (_n_out);
|
|
} else {
|
|
return ChanMapping ();
|
|
}
|
|
}
|
|
|
|
void
|
|
IOPlug::process ()
|
|
{
|
|
_graph->process_one_ioplug (this);
|
|
}
|
|
|
|
void
|
|
IOPlug::connect_and_run (samplepos_t start, pframes_t n_samples)
|
|
{
|
|
Temporal::TempoMap::update_thread_tempo_map ();
|
|
assert (n_samples > 0);
|
|
|
|
int canderef (1);
|
|
if (_stat_reset.compare_exchange_strong (canderef, 0)) {
|
|
_timing_stats.reset ();
|
|
}
|
|
|
|
if (!_plugin) {
|
|
_output->silence (n_samples);
|
|
return;
|
|
}
|
|
|
|
_timing_stats.start ();
|
|
|
|
ARDOUR::ChanMapping in_map (_n_in);
|
|
ARDOUR::ChanMapping out_map (_n_out);
|
|
|
|
double speed = 0;
|
|
samplepos_t end = start + n_samples * speed;
|
|
|
|
_input->collect_input (_bufs, n_samples, ChanCount::ZERO); /* calls _bufs.set_count() */
|
|
|
|
if (_plugin->connect_and_run (_bufs, start, end, speed, in_map, out_map, n_samples, 0)) {
|
|
// TODO report error, deactivate plugin
|
|
_output->silence (n_samples);
|
|
_timing_stats.update ();
|
|
return;
|
|
}
|
|
|
|
_bufs.set_count (_n_out);
|
|
|
|
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
|
|
if (_bufs.count().get(*t) > 0) {
|
|
_output->copy_to_outputs (_bufs, *t, n_samples, 0);
|
|
}
|
|
}
|
|
|
|
if (_pre) {
|
|
canderef = 1;
|
|
const bool reset = _reset_meters.compare_exchange_strong (canderef, 0);
|
|
samplecnt_t const rate = _session.nominal_sample_rate ();
|
|
|
|
auto a = _audio_input_ports.begin ();
|
|
auto m = _midi_input_ports.begin ();
|
|
for (auto p = _bufs.audio_begin (); p != _bufs.audio_end (); ++p, ++a) {
|
|
AudioBuffer const& ab (*p);
|
|
PortManager::AudioInputPort& ai (a->second);
|
|
ai.apply_falloff (n_samples, rate, reset);
|
|
ai.process (ab.data (), n_samples, reset);
|
|
}
|
|
for (auto p = _bufs.midi_begin (); p != _bufs.midi_end (); ++p, ++m) {
|
|
PortManager::MIDIInputPort& mi (m->second);
|
|
MidiBuffer const& mb (*p);
|
|
mi.apply_falloff (n_samples, rate, reset);
|
|
for (MidiBuffer::const_iterator i = mb.begin (); i != mb.end (); ++i) {
|
|
Evoral::Event<samplepos_t> ev (*i, false);
|
|
mi.process_event (ev.buffer (), ev.size ());
|
|
}
|
|
}
|
|
}
|
|
_output->flush_buffers (n_samples);;
|
|
|
|
const samplecnt_t l = effective_latency ();
|
|
if (_plugin_signal_latency != l) {
|
|
_plugin_signal_latency = l;
|
|
LatencyChanged (); /* EMIT SIGNAL */
|
|
}
|
|
|
|
_timing_stats.update ();
|
|
}
|
|
|
|
void
|
|
IOPlug::reset_input_meters ()
|
|
{
|
|
_reset_meters.store (1);
|
|
}
|
|
|
|
bool
|
|
IOPlug::get_stats (PBD::microseconds_t& min, PBD::microseconds_t& max, double& avg, double& dev) const
|
|
{
|
|
return _timing_stats.get_stats (min, max, avg, dev);
|
|
}
|
|
|
|
void
|
|
IOPlug::clear_stats ()
|
|
{
|
|
_stat_reset.store (1);
|
|
}
|
|
|
|
std::shared_ptr<ReadOnlyControl>
|
|
IOPlug::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;
|
|
}
|
|
}
|
|
|
|
bool
|
|
IOPlug::load_preset (Plugin::PresetRecord pr)
|
|
{
|
|
return _plugin->load_preset (pr);
|
|
}
|
|
|
|
bool
|
|
IOPlug::write_immediate_event (Evoral::EventType event_type, size_t size, const uint8_t* buf)
|
|
{
|
|
return _plugin->write_immediate_event (event_type, size, buf);
|
|
}
|
|
|
|
std::shared_ptr<Evoral::Control>
|
|
IOPlug::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);
|
|
}
|
|
|
|
std::string
|
|
IOPlug::describe_parameter (Evoral::Parameter param)
|
|
{
|
|
if (param.type() == PluginAutomation) {
|
|
return _plugin->describe_parameter (param);
|
|
} else if (param.type() == PluginPropertyAutomation) {
|
|
return string_compose ("Property %1", URIMap::instance ().id_to_uri (param.id()));
|
|
}
|
|
return EventTypeMap::instance ().to_symbol (param);
|
|
}
|
|
|
|
bool
|
|
IOPlug::direct_feeds_according_to_reality (std::shared_ptr<GraphNode> node, bool* via_send_only)
|
|
{
|
|
std::shared_ptr<IOPlug> other (std::dynamic_pointer_cast<IOPlug> (node));
|
|
assert (other && other->_pre == _pre);
|
|
if (via_send_only) {
|
|
*via_send_only = false;
|
|
}
|
|
return other->input()->connected_to (_output);
|
|
}
|
|
|
|
/* ****************************************************************************/
|
|
|
|
bool
|
|
IOPlug::can_reset_all_parameters ()
|
|
{
|
|
bool all = true;
|
|
uint32_t params = 0;
|
|
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;
|
|
}
|
|
|
|
++params;
|
|
}
|
|
return all && (params > 0);
|
|
}
|
|
|
|
bool
|
|
IOPlug::reset_parameters_to_default ()
|
|
{
|
|
bool all = true;
|
|
|
|
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;
|
|
}
|
|
|
|
ac->set_value (dflt, Controllable::NoGroup);
|
|
}
|
|
return all;
|
|
}
|