/* * Copyright (C) 2022 Robin Gareus * * 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 #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 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(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(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 ac = std::dynamic_pointer_cast (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 () || (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, 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 (new ReadOnlyControl (_plugin, desc, i)); continue; } Evoral::Parameter param (PluginAutomation, 0, i); std::shared_ptr 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 c (new PluginPropertyControl (_session, 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 c = control (Evoral::Parameter (PluginAutomation, 0, which)); std::shared_ptr pc = std::dynamic_pointer_cast (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 (static_cast (rv) | static_cast (PlugInsertBase::MIDIKeyboard)); } return rv; } bool IOPlug::ensure_io () { PBD::Unwinder 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); } } 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 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 IOPlug::control_output (uint32_t num) const { CtrlOutMap::const_iterator i = _control_outputs.find (num); if (i == _control_outputs.end ()) { return std::shared_ptr (); } 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 IOPlug::control_factory(const Evoral::Parameter& param) { Evoral::Control* control = NULL; ParameterDescriptor desc(param); std::shared_ptr 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 list; control = new AutomationControl (_session, param, desc, list); } return std::shared_ptr(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 node, bool* via_send_only) { std::shared_ptr other (std::dynamic_pointer_cast (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 ac = std::dynamic_pointer_cast(control (Evoral::Parameter(PluginAutomation, 0, cid))); if (!ac) { continue; } ac->set_value (dflt, Controllable::NoGroup); } return all; }