ardour/libs/ardour/io_plug.cc

759 lines
20 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; });
_output->PortCountChanging.connect_same_thread (*this, [this](ChanCount) { return !_configuring_io; });
}
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;
_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 ()) {
_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, boost::bind (&IOPlug::parameter_changed_externally, this, _1, _2));
_plugin->activate ();
}
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
*/
PortSet& from = playback ? _output->ports () : _input->ports ();
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(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 (this, param, desc));
c->set_flag (Controllable::NotAutomatable);
add_control (c);
}
_plugin->PresetPortSetValue.connect_same_thread (*this, boost::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;
}
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);
}
}
PortSet& ports (_output->ports());
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) {
_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;
}
/* ****************************************************************************/
IOPlug::PluginControl::PluginControl (IOPlug* p,
Evoral::Parameter const& param,
ParameterDescriptor const& desc)
: AutomationControl (p->session (), param, desc, std::shared_ptr<AutomationList> (), p->describe_parameter (param))
, _iop (p)
{
}
void
IOPlug::PluginControl::actually_set_value (double user_val, PBD::Controllable::GroupControlDisposition group_override)
{
_iop->plugin ()->set_parameter (parameter().id(), user_val, 0);
AutomationControl::actually_set_value (user_val, group_override);
}
void
IOPlug::PluginControl::catch_up_with_external_value (double user_val)
{
AutomationControl::actually_set_value (user_val, Controllable::NoGroup);
}
XMLNode&
IOPlug::PluginControl::get_state () const
{
XMLNode& node (AutomationControl::get_state());
node.set_property ("parameter", parameter().id());
std::shared_ptr<LV2Plugin> lv2plugin = std::dynamic_pointer_cast<LV2Plugin> (_iop->plugin ());
if (lv2plugin) {
node.set_property ("symbol", lv2plugin->port_symbol (parameter().id()));
}
return node;
}
double
IOPlug::PluginControl::get_value () const
{
std::shared_ptr<Plugin> plugin = _iop->plugin ();
if (!plugin) {
return 0.0;
}
return plugin->get_parameter (parameter().id());
}
std::string
IOPlug::PluginControl::get_user_string () const
{
std::shared_ptr<Plugin> plugin = _iop->plugin (0);
if (plugin) {
std::string pp;
if (plugin->print_parameter (parameter().id(), pp) && pp.size () > 0) {
return pp;
}
}
return AutomationControl::get_user_string ();
}
IOPlug::PluginPropertyControl::PluginPropertyControl (IOPlug* p,
Evoral::Parameter const& param,
ParameterDescriptor const& desc)
: AutomationControl (p->session(), param, desc )
, _iop (p)
{
}
void
IOPlug::PluginPropertyControl::actually_set_value (double user_val, Controllable::GroupControlDisposition gcd)
{
const Variant value(_desc.datatype, user_val);
if (value.type() == Variant::NOTHING) {
return;
}
_iop->plugin ()->set_property (parameter().id(), value);
_value = value;
AutomationControl::actually_set_value (user_val, gcd);
}
XMLNode&
IOPlug::PluginPropertyControl::get_state () const
{
XMLNode& node (AutomationControl::get_state());
node.set_property ("property", parameter ().id ());
node.remove_property ("value");
return node;
}
double
IOPlug::PluginPropertyControl::get_value () const
{
return _value.to_double();
}