/* * Copyright (C) 2019-2023 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 "pbd/gstdio_compat.h" #include #include #include "pbd/basename.h" #include "pbd/compose.h" #include "pbd/convert.h" #include "pbd/debug.h" #include "pbd/error.h" #include "pbd/failed_constructor.h" #include "pbd/file_utils.h" #include "pbd/tokenizer.h" #include "pbd/unwind.h" #ifdef PLATFORM_WINDOWS #include "pbd/windows_special_dirs.h" #include // CSIDL_* #endif #include "ardour/audio_buffer.h" #include "ardour/audioengine.h" #include "ardour/debug.h" #include "ardour/rc_configuration.h" #include "ardour/selection.h" #include "ardour/session.h" #include "ardour/stripable.h" #include "ardour/tempo.h" #include "ardour/utils.h" #include "ardour/vst3_module.h" #include "ardour/vst3_plugin.h" #include "pbd/i18n.h" using namespace PBD; using namespace ARDOUR; using namespace Temporal; using namespace Steinberg; using namespace Presonus; #if SMTG_OS_LINUX class AVST3Runloop : public Linux::IRunLoop { private: struct EventHandler { EventHandler (Linux::IEventHandler* handler = 0, GIOChannel* gio_channel = 0, guint source_id = 0) : _handler (handler) , _gio_channel (gio_channel) , _source_id (source_id) {} bool operator== (EventHandler const& other) { return other._handler == _handler && other._gio_channel == _gio_channel && other._source_id == _source_id; } Linux::IEventHandler* _handler; GIOChannel* _gio_channel; guint _source_id; }; boost::unordered_map _event_handlers; boost::unordered_map _timer_handlers; static gboolean event (GIOChannel* source, GIOCondition condition, gpointer data) { Linux::IEventHandler* handler = reinterpret_cast (data); handler->onFDIsSet (g_io_channel_unix_get_fd (source)); if (condition & ~G_IO_IN) { /* remove on error */ return false; } else { return true; } } static gboolean timeout (gpointer data) { Linux::ITimerHandler* handler = reinterpret_cast (data); handler->onTimer (); return true; } public: ~AVST3Runloop () { clear (); } void clear () { Glib::Threads::Mutex::Lock lm (_lock); for (boost::unordered_map::const_iterator it = _event_handlers.begin (); it != _event_handlers.end (); ++it) { g_source_remove (it->second._source_id); g_io_channel_unref (it->second._gio_channel); } for (boost::unordered_map::const_iterator it = _timer_handlers.begin (); it != _timer_handlers.end (); ++it) { g_source_remove (it->first); } _event_handlers.clear (); _timer_handlers.clear (); } /* VST3 IRunLoop interface */ tresult registerEventHandler (Linux::IEventHandler* handler, FileDescriptor fd) SMTG_OVERRIDE { if (!handler || _event_handlers.find(fd) != _event_handlers.end()) { return kInvalidArgument; } Glib::Threads::Mutex::Lock lm (_lock); GIOChannel* gio_channel = g_io_channel_unix_new (fd); guint id = g_io_add_watch (gio_channel, (GIOCondition) (G_IO_IN /*| G_IO_OUT*/ | G_IO_ERR | G_IO_HUP), event, handler); _event_handlers[fd] = EventHandler (handler, gio_channel, id); return kResultTrue; } tresult unregisterEventHandler (Linux::IEventHandler* handler) SMTG_OVERRIDE { if (!handler) { return kInvalidArgument; } tresult rv = false; Glib::Threads::Mutex::Lock lm (_lock); for (boost::unordered_map::const_iterator it = _event_handlers.begin (); it != _event_handlers.end ();) { if (it->second._handler == handler) { g_source_remove (it->second._source_id); g_io_channel_unref (it->second._gio_channel); it = _event_handlers.erase (it); rv = kResultTrue; } else { ++it; } } return rv; } tresult registerTimer (Linux::ITimerHandler* handler, TimerInterval milliseconds) SMTG_OVERRIDE { if (!handler || milliseconds == 0) { return kInvalidArgument; } Glib::Threads::Mutex::Lock lm (_lock); guint id = g_timeout_add_full (G_PRIORITY_HIGH_IDLE, milliseconds, timeout, handler, NULL); _timer_handlers[id] = handler; return kResultTrue; } tresult unregisterTimer (Linux::ITimerHandler* handler) SMTG_OVERRIDE { if (!handler) { return kInvalidArgument; } tresult rv = false; Glib::Threads::Mutex::Lock lm (_lock); for (boost::unordered_map::const_iterator it = _timer_handlers.begin (); it != _timer_handlers.end ();) { if (it->second == handler) { g_source_remove (it->first); it = _timer_handlers.erase (it); rv = kResultTrue; } else { ++it; } } return rv; } uint32 PLUGIN_API addRef () SMTG_OVERRIDE { return 1; } uint32 PLUGIN_API release () SMTG_OVERRIDE { return 1; } tresult queryInterface (const TUID, void**) SMTG_OVERRIDE { return kNoInterface; } private: Glib::Threads::Mutex _lock; }; AVST3Runloop static_runloop; #endif VST3Plugin::VST3Plugin (AudioEngine& engine, Session& session, VST3PI* plug) : Plugin (engine, session) , _plug (plug) , _parameter_queue (128 + plug->parameter_count ()) { init (); } VST3Plugin::VST3Plugin (const VST3Plugin& other) : Plugin (other) , _parameter_queue (128 + other.parameter_count ()) { std::shared_ptr nfo = std::dynamic_pointer_cast (other.get_info ()); _plug = new VST3PI (nfo->m, nfo->unique_id); init (); XMLNode root (other.state_node_name ()); other.add_state (&root); set_state (root, Stateful::current_state_version); } VST3Plugin::~VST3Plugin () { delete _plug; } void VST3Plugin::init () { #ifndef NDEBUG if (DEBUG_ENABLED (DEBUG::VST3Config)) { char fuid[33]; _plug->fuid ().toString (fuid); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3 instantiating FUID: %1\n", fuid)); } #endif Vst::ProcessContext& context (_plug->context ()); context.sampleRate = _session.nominal_sample_rate (); _plug->set_block_size (_session.get_block_size ()); _plug->OnResizeView.connect_same_thread (_connections, boost::bind (&VST3Plugin::forward_resize_view, this, _1, _2)); _plug->OnParameterChange.connect_same_thread (_connections, boost::bind (&VST3Plugin::parameter_change_handler, this, _1, _2, _3)); /* assume only default active busses are connected */ for (auto const& abi : _plug->bus_info_in ()) { for (int32_t i = 0; i < abi.second.n_chn; ++i) { _connected_inputs.push_back (abi.second.dflt); } } for (auto const& abi : _plug->bus_info_out ()) { for (int32_t i = 0; i < abi.second.n_chn; ++i) { _connected_outputs.push_back (abi.second.dflt); } } /* pre-configure from GUI thread */ _plug->enable_io (_connected_inputs, _connected_outputs); } void VST3Plugin::forward_resize_view (int w, int h) { OnResizeView (w, h); /* EMIT SINAL */ } void VST3Plugin::parameter_change_handler (VST3PI::ParameterChange t, uint32_t param, float value) { switch (t) { case VST3PI::BeginGesture: Plugin::StartTouch (param); break; case VST3PI::EndGesture: Plugin::EndTouch (param); break; case VST3PI::ValueChange: _parameter_queue.write_one (PV (param, value)); /* fallthrough */ case VST3PI::ParamValueChanged: /* emit ParameterChangedExternally, mark preset dirty */ Plugin::parameter_changed_externally (param, value); break; case VST3PI::InternalChange: Plugin::state_changed (); break; case VST3PI::PresetChange: PresetsChanged (unique_id (), this, false); /* EMIT SIGNAL */ size_t n_presets = _plug->n_factory_presets (); // ths may be old, invalidated count if (_plug->program_change_port ().id != Vst::kNoParamId) { int pgm = value * (n_presets > 1 ? (n_presets - 1) : 1); std::string uri = string_compose (X_("VST3-P:%1:%2"), unique_id (), std::setw (4), std::setfill ('0'), pgm); const Plugin::PresetRecord* pset = Plugin::preset_by_uri (uri); if (pset && n_presets == _plug->n_factory_presets ()) { Plugin::load_preset (*pset); // XXX TODO notify replicated instances, unless plugin implements ISlaveControllerHandler } } break; } } /* **************************************************************************** * Parameter API */ uint32_t VST3Plugin::parameter_count () const { return _plug->parameter_count (); } float VST3Plugin::default_value (uint32_t port) { assert (port < parameter_count ()); return _plug->default_value (port); } void VST3Plugin::set_parameter (uint32_t port, float val, sampleoffset_t when) { if (!_plug->active () || _plug->is_loading_state () || AudioEngine::instance ()->in_process_thread ()) { /* directly use VST3PI::_input_param_changes */ _plug->set_parameter (port, val, when, true); } else { assert (when == 0); _plug->set_parameter (port, val, when, false); _parameter_queue.write_one (PV (port, val)); } Plugin::set_parameter (port, val, when); } float VST3Plugin::get_parameter (uint32_t port) const { return _plug->get_parameter (port); } int VST3Plugin::get_parameter_descriptor (uint32_t port, ParameterDescriptor& desc) const { assert (port < parameter_count ()); _plug->get_parameter_descriptor (port, desc); desc.update_steps (); return 0; } uint32_t VST3Plugin::nth_parameter (uint32_t port, bool& ok) const { if (port < parameter_count ()) { ok = true; return port; } ok = false; return 0; } bool VST3Plugin::parameter_is_input (uint32_t port) const { return !_plug->parameter_is_readonly (port); } bool VST3Plugin::parameter_is_output (uint32_t port) const { return _plug->parameter_is_readonly (port); } uint32_t VST3Plugin::designated_bypass_port () { return _plug->designated_bypass_port (); } void VST3Plugin::set_automation_control (uint32_t port, std::shared_ptr ac) { if (!ac->alist () || !_plug->subscribe_to_automation_changes ()) { return; } ac->alist ()->automation_state_changed.connect_same_thread (_connections, boost::bind (&VST3PI::automation_state_changed, _plug, port, _1, std::weak_ptr (ac->alist ()))); } std::set VST3Plugin::automatable () const { std::set automatables; for (uint32_t i = 0; i < parameter_count (); ++i) { if (parameter_is_input (i) && _plug->parameter_is_automatable (i)) { automatables.insert (automatables.end (), Evoral::Parameter (PluginAutomation, 0, i)); } } return automatables; } std::string VST3Plugin::describe_parameter (Evoral::Parameter param) { if (param.type () == PluginAutomation && param.id () < parameter_count ()) { return _plug->parameter_label (param.id ()); } return "??"; } bool VST3Plugin::print_parameter (uint32_t port, std::string& rv) const { rv = _plug->print_parameter (port); return rv.size () > 0; } Plugin::IOPortDescription VST3Plugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const { if ((dt == DataType::AUDIO && ((input && id >= _plug->n_audio_inputs()) || (!input && id >= _plug->n_audio_outputs()))) || (dt == DataType::MIDI && ((input && id >= _plug->n_midi_inputs()) || (!input && id >= _plug->n_midi_outputs())))) { return Plugin::describe_io_port(dt, input, id); } return _plug->describe_io_port (dt, input, id); } PluginOutputConfiguration VST3Plugin::possible_output () const { auto const& bi (_plug->bus_info_out ()); if (bi.size () < 2) { return Plugin::possible_output (); } #if 0 PluginOutputConfiguration oc; int32_t sum = 0; for (auto const& n : bi) { sum += n.second.n_chn; oc.insert (sum); } return oc; #else /* first main out, + individual stereo main outs, +all main outs, + individual aux outs */ auto i = bi.begin (); int32_t sum = i->second.n_chn; bool seen_aux = i->second.type == Vst::kAux; bool seen_mono = sum == 1; PluginOutputConfiguration oc; oc.insert (sum); for (++i; i != bi.end (); ++i) { if (seen_aux || seen_mono) { sum += i->second.n_chn; oc.insert (sum); } else if (i->second.type == Vst::kAux) { oc.insert (sum); seen_aux = true; sum += i->second.n_chn; oc.insert (sum); } else { sum += i->second.n_chn; if (i->second.n_chn == 1) { seen_mono = true; oc.insert (sum); } else if (!seen_mono) { oc.insert (sum); } } } if (!seen_aux && seen_mono) { oc.insert (sum); } return oc; #endif } /* **************************************************************************** * Plugin UI */ bool VST3Plugin::has_editor () const { VST3PI::RouteProcessorChangeBlock rpcb (_plug); std::shared_ptr nfo = std::dynamic_pointer_cast (get_info ()); if (nfo->has_editor.has_value ()) { return nfo->has_editor.value (); } bool rv = _plug->has_editor (); nfo->has_editor = rv; return rv; } Steinberg::IPlugView* VST3Plugin::view () { return _plug->view (); } void VST3Plugin::close_view () { _plug->close_view (); } void VST3Plugin::update_contoller_param () { /* GUI Thread */ _plug->update_contoller_param (); } /* **************************************************************************** * MIDI converters */ bool VST3PI::evoral_to_vst3 (Vst::Event& e, Evoral::Event const& ev, int32_t bus) { const uint32_t size = ev.size (); if (size == 0) { return false; } const uint8_t* data = ev.buffer (); uint8_t status = data[0]; if (status >= 0x80 && status < 0xF0) { status &= 0xf0; } if (size == 2 || size == 3) { Vst::ParamID id = Vst::kNoParamId; const uint8_t channel = data[0] & 0x0f; const uint8_t data1 = data[1] & 0x7f; const uint8_t data2 = size == 3 ? (data[2] & 0x7f) : 0; switch (status) { case MIDI_CMD_NOTE_OFF: e.type = Vst::Event::kNoteOffEvent; e.noteOff.channel = channel; e.noteOff.noteId = -1; e.noteOff.pitch = data1; e.noteOff.velocity = data2 / 127.f; e.noteOff.tuning = 0.f; return true; case MIDI_CMD_NOTE_ON: e.type = Vst::Event::kNoteOnEvent; e.noteOn.channel = channel; e.noteOn.noteId = -1; e.noteOn.pitch = data1; e.noteOn.velocity = data2 / 127.f; e.noteOn.length = 0; e.noteOn.tuning = 0.f; return true; case MIDI_CMD_NOTE_PRESSURE: e.type = Vst::Event::kPolyPressureEvent; e.polyPressure.channel = channel; e.polyPressure.pitch = data1; e.polyPressure.pressure = data2 / 127.f; e.polyPressure.noteId = -1; return true; case MIDI_CMD_CONTROL: if (ev.is_live_midi () /* live input -- no playback */) { live_midi_cc (bus, channel, data1); } if (midi_controller (bus, channel, data1, id)) { set_parameter_by_id (id, data2 / 127.f, ev.time ()); } return false; case MIDI_CMD_PGM_CHANGE: assert (size == 2); set_program (data2, ev.time ()); return false; case MIDI_CMD_CHANNEL_PRESSURE: assert (size == 2); if (midi_controller (bus, channel, Vst::kAfterTouch, id)) { set_parameter_by_id (id, data1 / 127.f, ev.time ()); } return false; case MIDI_CMD_BENDER: if (midi_controller (bus, channel, Vst::kPitchBend, id)) { uint32_t m14 = (data2 << 7) | data1; set_parameter_by_id (id, m14 / 16383.f, ev.time ()); } return false; } } else if (status == MIDI_CMD_COMMON_SYSEX) { memset (&e, 0, sizeof (Vst::Event)); e.type = Vst::Event::kDataEvent; e.data.type = Vst::DataEvent::kMidiSysEx; e.data.bytes = ev.buffer (); // TODO copy ?! e.data.size = ev.size (); return true; } return false; } #define vst_to_midi(x) (static_cast ((x)*127.f) & 0x7f) void VST3PI::vst3_to_midi_buffers (BufferSet& bufs, ChanMapping const& out_map) { for (int32 i = 0; i < _output_events.getEventCount (); ++i) { Vst::Event e; if (_output_events.getEvent (i, e) == kResultFalse) { continue; } bool valid = false; uint32_t index = out_map.get (DataType::MIDI, e.busIndex, &valid); if (!valid || bufs.count ().n_midi () <= index) { DEBUG_TRACE (DEBUG::VST3Process, string_compose ("VST3PI::vst3_to_midi_buffers - Invalid MIDI Bus %1\n", e.busIndex)); continue; } MidiBuffer& mb = bufs.get_midi (index); uint8_t data[3]; switch (e.type) { case Vst::Event::kDataEvent: /* sysex */ mb.push_back (e.sampleOffset, Evoral::MIDI_EVENT, e.data.size, (uint8_t const*)e.data.bytes); break; case Vst::Event::kNoteOffEvent: data[0] = MIDI_CMD_NOTE_OFF | e.noteOff.channel; data[1] = e.noteOff.pitch; data[2] = vst_to_midi (e.noteOff.velocity); mb.push_back (e.sampleOffset, Evoral::MIDI_EVENT, 3, data); break; case Vst::Event::kNoteOnEvent: data[0] = MIDI_CMD_NOTE_ON | e.noteOn.channel; data[1] = e.noteOn.pitch; data[2] = vst_to_midi (e.noteOn.velocity); mb.push_back (e.sampleOffset, Evoral::MIDI_EVENT, 3, data); break; case Vst::Event::kPolyPressureEvent: data[0] = MIDI_CMD_NOTE_PRESSURE | e.noteOff.channel; data[1] = e.polyPressure.pitch; data[2] = vst_to_midi (e.polyPressure.pressure); mb.push_back (e.sampleOffset, Evoral::MIDI_EVENT, 3, data); break; case Vst::Event::kLegacyMIDICCOutEvent: switch (e.midiCCOut.controlNumber) { case Vst::kCtrlPolyPressure: data[0] = MIDI_CMD_NOTE_PRESSURE | e.midiCCOut.channel; data[1] = e.midiCCOut.value; data[2] = e.midiCCOut.value2; break; default: /* Control Change */ data[0] = MIDI_CMD_CONTROL | e.midiCCOut.channel; data[1] = e.midiCCOut.controlNumber; data[2] = e.midiCCOut.value; break; case Vst::kCtrlProgramChange: data[0] = MIDI_CMD_PGM_CHANGE | e.midiCCOut.channel; data[1] = e.midiCCOut.value; data[2] = e.midiCCOut.value2; break; case Vst::kAfterTouch: data[0] = MIDI_CMD_CHANNEL_PRESSURE | e.midiCCOut.channel; data[1] = e.midiCCOut.value; data[2] = e.midiCCOut.value2; break; case Vst::kPitchBend: data[0] = MIDI_CMD_BENDER | e.midiCCOut.channel; data[1] = e.midiCCOut.value; data[2] = e.midiCCOut.value2; break; } mb.push_back (e.sampleOffset, Evoral::MIDI_EVENT, e.midiCCOut.controlNumber == Vst::kCtrlProgramChange ? 2 : 3, data); break; case Vst::Event::kNoteExpressionValueEvent: case Vst::Event::kNoteExpressionTextEvent: case Vst::Event::kChordEvent: case Vst::Event::kScaleEvent: default: /* unsupported, unhandled event */ break; } } } /* ****************************************************************************/ void VST3Plugin::add_state (XMLNode* root) const { XMLNode* child; for (uint32_t i = 0; i < parameter_count (); ++i) { if (!parameter_is_input (i)) { continue; } child = new XMLNode ("Port"); child->set_property ("id", (uint32_t)_plug->index_to_id (i)); child->set_property ("value", _plug->get_parameter (i)); root->add_child_nocopy (*child); } RAMStream stream; if (_plug->save_state (stream)) { gchar* data = g_base64_encode (stream.data (), stream.size ()); if (data == 0) { return; } XMLNode* chunk_node = new XMLNode (X_("chunk")); chunk_node->add_content (data); g_free (data); root->add_child_nocopy (*chunk_node); } } int VST3Plugin::set_state (const XMLNode& node, int version) { XMLNodeConstIterator iter; if (node.name () != state_node_name ()) { error << string_compose (_("VST3<%1>: Bad node sent to VST3Plugin::set_state"), name ()) << endmsg; return -1; } const Plugin::PresetRecord* r = 0; std::string preset_uri; if (node.get_property (X_("last-preset-uri"), preset_uri)) { r = preset_by_uri (preset_uri); } if (r && _plug->program_change_port ().id != Vst::kNoParamId) { std::vector tmp; if (PBD::tokenize (r->uri, std::string (":"), std::back_inserter (tmp)) && tmp.size () == 3 && tmp[0] == "VST3-P") { float value = PBD::atoi (tmp[2]); size_t n_presets = _plug->n_factory_presets (); if (n_presets > 1) { value /= (n_presets - 1.f); } DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3Plugin::set_state: set_program (pgm: %1 plug: %2)\n", value, name ())); _plug->controller ()->setParamNormalized (_plug->program_change_port ().id, value); } } XMLNodeList nodes = node.children ("Port"); for (iter = nodes.begin (); iter != nodes.end (); ++iter) { XMLNode* child = *iter; uint32_t param_id; float value; if (!child->get_property ("id", param_id)) { warning << string_compose (_("VST3<%1>: Missing parameter-id in VST3Plugin::set_state"), name ()) << endmsg; continue; } if (!child->get_property ("value", value)) { warning << string_compose (_("VST3<%1>: Missing parameter value in VST3Plugin::set_state"), name ()) << endmsg; continue; } /* This is not required, PluginInsert::set_state calls * set_control_ids() which already calls VST3Plugin::set_parameter. * * However there /may/ not be controllables for all parameters. Doing * this here also prevents an additional pass to synchronize the controller * via VST3PI::load_state -> VST3PI::update_shadow_data -> addParameterData */ #if 1 if (!_plug->try_set_parameter_by_id (param_id, value)) { warning << string_compose (_("VST3<%1>: Invalid Vst::ParamID in VST3Plugin::set_state"), name ()) << endmsg; } #endif } XMLNode* chunk; if ((chunk = find_named_node (node, X_("chunk"))) != 0) { for (iter = chunk->children ().begin (); iter != chunk->children ().end (); ++iter) { if ((*iter)->is_content ()) { gsize size = 0; guchar* _data = g_base64_decode ((*iter)->content ().c_str (), &size); RAMStream stream (_data, size); if (!_plug->load_state (stream)) { error << string_compose (_("VST3<%1>: failed to load chunk-data"), name ()) << endmsg; } } } } return Plugin::set_state (node, version); } /* ****************************************************************************/ void VST3Plugin::set_owner (ARDOUR::SessionObject* o) { Plugin::set_owner (o); _plug->set_owner (o); } int VST3Plugin::set_block_size (pframes_t n_samples) { _plug->set_block_size (n_samples); return 0; } samplecnt_t VST3Plugin::plugin_latency () const { return _plug->plugin_latency (); } void VST3Plugin::add_slave (std::shared_ptr p, bool rt) { std::shared_ptr vst = std::dynamic_pointer_cast (p); if (vst) { _plug->add_slave (vst->_plug->controller (), rt); } } void VST3Plugin::remove_slave (std::shared_ptr p) { std::shared_ptr vst = std::dynamic_pointer_cast (p); if (vst) { _plug->remove_slave (vst->_plug->controller ()); } } void VST3Plugin::set_non_realtime (bool yn) { _plug->set_non_realtime (yn); } int VST3Plugin::connect_and_run (BufferSet& bufs, samplepos_t start, samplepos_t end, double speed, ChanMapping const& in_map, ChanMapping const& out_map, pframes_t n_samples, samplecnt_t offset) { Glib::Threads::Mutex::Lock tm (_plug->process_lock (), Glib::Threads::TRY_LOCK); if (!tm.locked ()) { return 0; } DEBUG_TRACE (DEBUG::VST3Process, string_compose ("%1 run %2 offset %3\n", name (), n_samples, offset)); Plugin::connect_and_run (bufs, start, end, speed, in_map, out_map, n_samples, offset); Vst::ProcessContext& context (_plug->context ()); /* clear event ports */ _plug->cycle_start (); context.state = Vst::ProcessContext::kContTimeValid | Vst::ProcessContext::kSystemTimeValid | Vst::ProcessContext::kSmpteValid | Vst::ProcessContext::kProjectTimeMusicValid | Vst::ProcessContext::kBarPositionValid | Vst::ProcessContext::kTempoValid | Vst::ProcessContext::kTimeSigValid | Vst::ProcessContext::kClockValid; context.projectTimeSamples = start; context.continousTimeSamples = _engine.processed_samples (); context.systemTime = g_get_monotonic_time (); { TempoMap::SharedPtr tmap (TempoMap::use ()); const TempoMetric& metric (tmap->metric_at (timepos_t (start))); const BBT_Time& bbt (metric.bbt_at (timepos_t (start))); context.tempo = metric.tempo ().quarter_notes_per_minute (); context.timeSigNumerator = metric.meter ().divisions_per_bar (); context.timeSigDenominator = metric.meter ().note_value (); context.projectTimeMusic = DoubleableBeats (metric.tempo ().quarters_at_sample (start)).to_double (); context.barPositionMusic = (bbt.bars - 1) * 4; // PPQN, NOT tmap.metric_at(bbt).meter().divisions_per_bar() } const double tcfps = _session.timecode_frames_per_second (); context.frameRate.framesPerSecond = ceil (tcfps); context.frameRate.flags = 0; if (_session.timecode_drop_frames ()) { context.frameRate.flags = Vst::FrameRate::kDropRate; /* 29.97 */ } else if (tcfps > context.frameRate.framesPerSecond) { context.frameRate.flags = Vst::FrameRate::kPullDownRate; /* 23.976 etc */ } if (_session.get_play_loop ()) { Location* looploc = _session.locations ()->auto_loop_location (); try { /* loop start/end in quarter notes */ TempoMap::SharedPtr tmap (TempoMap::use ()); context.cycleStartMusic = DoubleableBeats (tmap->quarters_at (looploc->start ())).to_double (); context.cycleEndMusic = DoubleableBeats (tmap->quarters_at (looploc->end ())).to_double (); context.state |= Vst::ProcessContext::kCycleValid; context.state |= Vst::ProcessContext::kCycleActive; } catch (...) { } } if (speed != 0) { context.state |= Vst::ProcessContext::kPlaying; } if (_session.actively_recording ()) { context.state |= Vst::ProcessContext::kRecording; } #if 0 // TODO context.state |= Vst::ProcessContext::kClockValid; context.samplesToNextClock = 0 // MIDI Clock Resolution (24 Per Quarter Note), can be negative (nearest); #endif ChanCount bufs_count; bufs_count.set (DataType::AUDIO, 1); bufs_count.set (DataType::MIDI, 1); BufferSet& silent_bufs = _session.get_silent_buffers (bufs_count); BufferSet& scratch_bufs = _session.get_scratch_buffers (bufs_count); uint32_t n_bin = std::max (1, _plug->n_audio_inputs ()); uint32_t n_bout = std::max (1, _plug->n_audio_outputs ()); float** ins = (float**)alloca (n_bin * sizeof (float*)); float** outs = (float**)alloca (n_bout * sizeof (float*)); uint32_t in_index = 0; for (int32_t i = 0; i < (int32_t)_plug->n_audio_inputs (); ++i) { uint32_t index; bool valid = false; index = in_map.get (DataType::AUDIO, in_index++, &valid); ins[i] = (valid) ? bufs.get_audio (index).data (offset) : silent_bufs.get_audio (0).data (0); _connected_inputs[i] = valid; } uint32_t out_index = 0; for (int32_t i = 0; i < (int32_t)_plug->n_audio_outputs (); ++i) { uint32_t index; bool valid = false; index = out_map.get (DataType::AUDIO, out_index++, &valid); outs[i] = (valid) ? bufs.get_audio (index).data (offset) : scratch_bufs.get_audio (0).data (0); _connected_outputs[i] = valid; } /* apply parameter changes */ PV pv; while (_parameter_queue.read (&pv, 1)) { _plug->set_parameter (pv.port, pv.val, 0, true, true); } in_index = 0; for (int32_t i = 0; i < (int32_t)_plug->n_midi_inputs (); ++i) { bool valid = false; uint32_t index = in_map.get (DataType::MIDI, in_index++, &valid); if (valid && bufs.count ().n_midi () > index) { for (MidiBuffer::iterator m = bufs.get_midi (index).begin (); m != bufs.get_midi (index).end (); ++m) { const Evoral::Event ev (*m, false); _plug->add_event (ev, i); } } } _plug->enable_io (_connected_inputs, _connected_outputs); _plug->process (ins, outs, n_samples); /* handle outgoing MIDI events */ if (_plug->n_midi_outputs () > 0 && bufs.count ().n_midi () > 0) { /* clear valid in-place MIDI buffers (forward MIDI otherwise) */ in_index = 0; for (int32_t i = 0; i < (int32_t)_plug->n_midi_inputs (); ++i) { bool valid = false; uint32_t index = in_map.get (DataType::MIDI, in_index++, &valid); if (valid && bufs.count ().n_midi () > index) { bufs.get_midi (index).clear (); } } _plug->vst3_to_midi_buffers (bufs, out_map); } return 0; } /* ****************************************************************************/ bool VST3Plugin::load_preset (PresetRecord r) { bool ok = false; /* Extract the UUID of this preset from the URI */ std::vector tmp; if (!PBD::tokenize (r.uri, std::string (":"), std::back_inserter (tmp))) { return false; } if (tmp.size () != 3) { return false; } std::string const& unique_id = tmp[1]; FUID fuid; if (!fuid.fromString (unique_id.c_str ()) || fuid != _plug->fuid ()) { assert (0); return false; } Glib::Threads::Mutex::Lock lx (_plug->process_lock ()); if (tmp[0] == "VST3-P") { int program = PBD::atoi (tmp[2]); assert (!r.user); if (!_plug->set_program (program, 0)) { DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3Plugin::load_preset: set_program failed (pgm: %1 plug: %2)\n", program, name ())); return false; } ok = true; } else if (tmp[0] == "VST3-S") { if (_preset_uri_map.find (r.uri) == _preset_uri_map.end ()) { /* build _preset_uri_map for replicated instances */ find_presets (); } assert (_preset_uri_map.find (r.uri) != _preset_uri_map.end ()); std::string const& fn = _preset_uri_map[r.uri]; if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) { RAMStream stream (fn); ok = _plug->load_state (stream); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3Plugin::load_preset: file %1 status %2\n", fn, ok ? "OK" : "error")); } } lx.release (); if (ok) { Plugin::load_preset (r); } return ok; } std::string VST3Plugin::do_save_preset (std::string name) { std::shared_ptr nfo = std::dynamic_pointer_cast (get_info ()); PBD::Searchpath psp = nfo->preset_search_path (); assert (!psp.empty ()); std::string dir = psp.front (); std::string fn = Glib::build_filename (dir, legalize_for_universal_path (name) + ".vstpreset"); if (g_mkdir_with_parents (dir.c_str (), 0775)) { error << string_compose (_("Could not create VST3 Preset Folder '%1'"), dir) << endmsg; } RAMStream stream; if (_plug->save_state (stream)) { GError* err = NULL; if (!g_file_set_contents (fn.c_str (), (const gchar*)stream.data (), stream.size (), &err)) { ::g_unlink (fn.c_str ()); if (err) { error << string_compose (_("Could not save VST3 Preset (%1)"), err->message) << endmsg; g_error_free (err); } return ""; } std::string uri = string_compose (X_("VST3-S:%1:%2"), unique_id (), PBD::basename_nosuffix (fn)); _preset_uri_map[uri] = fn; return uri; } return ""; } void VST3Plugin::do_remove_preset (std::string name) { std::shared_ptr nfo = std::dynamic_pointer_cast (get_info ()); PBD::Searchpath psp = nfo->preset_search_path (); assert (!psp.empty ()); std::string dir = psp.front (); std::string fn = Glib::build_filename (dir, legalize_for_universal_path (name) + ".vstpreset"); ::g_unlink (fn.c_str ()); std::string uri = string_compose (X_("VST3-S:%1:%2"), unique_id (), PBD::basename_nosuffix (fn)); if (_preset_uri_map.find (uri) != _preset_uri_map.end ()) { _preset_uri_map.erase (_preset_uri_map.find (uri)); } } static bool vst3_preset_filter (const std::string& str, void*) { return str[0] != '.' && (str.length () > 9 && str.find (".vstpreset") == (str.length () - 10)); } void VST3Plugin::find_presets () { _presets.clear (); _preset_uri_map.clear (); /* read vst3UnitPrograms */ IPtr nfo = _plug->unit_info (); if (nfo && _plug->program_change_port ().id != Vst::kNoParamId) { Vst::UnitID program_unit_id = _plug->program_change_port ().unitId; int32 unit_count = nfo->getUnitCount (); for (int32 idx = 0; idx < unit_count; ++idx) { Vst::UnitInfo unit_info; if (!(nfo->getUnitInfo (idx, unit_info) == kResultOk && unit_info.id == program_unit_id)) { continue; } int32 count = nfo->getProgramListCount (); for (int32 i = 0; i < count; ++i) { Vst::ProgramListInfo pli; if (nfo->getProgramListInfo (i, pli) != kResultTrue) { continue; } if (pli.id != unit_info.programListId) { continue; } for (int32 j = 0; j < pli.programCount; ++j) { Vst::String128 pname; if (nfo->getProgramName (pli.id, j, pname) == kResultTrue) { std::string preset_name = tchar_to_utf8 (pname); if (preset_name.empty ()) { warning << string_compose (_("VST3<%1>: ignored unnamed factory preset/program"), name ()) << endmsg; continue; } std::string uri = string_compose (X_("VST3-P:%1:%2"), unique_id (), std::setw (4), std::setfill ('0'), j); PresetRecord r (uri, preset_name, false); _presets.insert (make_pair (uri, r)); } if (nfo->hasProgramPitchNames (pli.id, j)) { // TODO -> midnam } } break; // only one program list } break; // only one unit } } if (_presets.empty () && _plug->program_change_port ().id != Vst::kNoParamId) { /* fill in presets by number */ Vst::ParameterInfo const& pi (_plug->program_change_port ()); int32 n_programs = pi.stepCount + 1; for (int32 i = 0; i < n_programs; ++i) { float value = static_cast (i) / static_cast (pi.stepCount); std::string preset_name = _plug->print_parameter (pi.id, value); if (!preset_name.empty ()) { std::string uri = string_compose (X_("VST3-P:%1:%2"), unique_id (), std::setw (4), std::setfill ('0'), i); PresetRecord r (uri, preset_name, false); _presets.insert (make_pair (uri, r)); } } } _plug->set_n_factory_presets (_presets.size ()); // TODO check _plug->unit_data() // IUnitData: programDataSupported -> setUnitProgramData (IBStream) std::shared_ptr info = std::dynamic_pointer_cast (get_info ()); PBD::Searchpath psp = info->preset_search_path (); std::vector preset_files; find_paths_matching_filter (preset_files, psp, vst3_preset_filter, 0, false, true, false); for (std::vector::iterator i = preset_files.begin (); i != preset_files.end (); ++i) { bool is_user = PBD::path_is_within (psp.front (), *i); std::string preset_name = PBD::basename_nosuffix (*i); std::string uri = string_compose (X_("VST3-S:%1:%2"), unique_id (), preset_name); if (_presets.find (uri) != _presets.end ()) { continue; } PresetRecord r (uri, preset_name, is_user); _presets.insert (make_pair (uri, r)); _preset_uri_map[uri] = *i; } } /* ****************************************************************************/ VST3PluginInfo::VST3PluginInfo () { type = ARDOUR::VST3; } PluginPtr VST3PluginInfo::load (Session& session) { try { if (!m) { DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3 Loading: %1\n", path)); m = VST3PluginModule::load (path); } PluginPtr plugin; Steinberg::VST3PI* plug = new VST3PI (m, unique_id); plugin.reset (new VST3Plugin (session.engine (), session, plug)); plugin->set_info (PluginInfoPtr (shared_from_this ())); return plugin; } catch (failed_constructor& err) { ; } return PluginPtr (); } std::vector VST3PluginInfo::get_presets (bool user_only) const { std::vector p; /* This only returns user-presets, which is sufficient for the time * being. So far only Mixer_UI::sync_treeview_from_favorite_order() * uses PluginInfo to query presets. * * see also VST3Plugin::find_presets */ assert (user_only); PBD::Searchpath psp = preset_search_path (); std::vector preset_files; find_paths_matching_filter (preset_files, psp, vst3_preset_filter, 0, false, true, false); for (std::vector::iterator i = preset_files.begin (); i != preset_files.end (); ++i) { bool is_user = PBD::path_is_within (psp.front (), *i); std::string preset_name = PBD::basename_nosuffix (*i); std::string uri = string_compose (X_("VST3-S:%1:%2"), unique_id, preset_name); if (!is_user) { continue; } p.push_back (Plugin::PresetRecord (uri, preset_name, is_user)); } std::sort (p.begin (), p.end ()); return p; } bool VST3PluginInfo::is_instrument () const { if (category.find (Vst::PlugType::kInstrument) != std::string::npos) { return true; } return PluginInfo::is_instrument (); } PBD::Searchpath VST3PluginInfo::preset_search_path () const { std::string vendor = legalize_for_universal_path (creator); std::string pname = legalize_for_universal_path (name); /* first listed is used to save custom user-presets */ PBD::Searchpath preset_path; #ifdef __APPLE__ preset_path += Glib::build_filename (Glib::get_home_dir (), "Library/Audio/Presets", vendor, pname); preset_path += Glib::build_filename ("/Library/Audio/Presets", vendor, pname); #elif defined PLATFORM_WINDOWS std::string documents = PBD::get_win_special_folder_path (CSIDL_PERSONAL); if (!documents.empty ()) { preset_path += Glib::build_filename (documents, "VST3 Presets", vendor, pname); preset_path += Glib::build_filename (documents, "vst3 presets", vendor, pname); } preset_path += Glib::build_filename (Glib::get_user_data_dir (), "VST3 Presets", vendor, pname); std::string appdata = PBD::get_win_special_folder_path (CSIDL_APPDATA); if (!appdata.empty ()) { preset_path += Glib::build_filename (appdata, "VST3 Presets", vendor, pname); preset_path += Glib::build_filename (appdata, "vst3 presets", vendor, pname); } #else preset_path += Glib::build_filename (Glib::get_home_dir (), ".vst3", "presets", vendor, pname); preset_path += Glib::build_filename ("/usr/share/vst3/presets", vendor, pname); preset_path += Glib::build_filename ("/usr/local/share/vst3/presets", vendor, pname); #endif return preset_path; } /* ****************************************************************************/ VST3PI::VST3PI (std::shared_ptr m, std::string unique_id) : _module (m) , _component (0) , _controller (0) , _view (0) , _is_loading_state (false) , _is_processing (false) , _block_size (0) , _process_offline (false) , _port_id_bypass (UINT32_MAX) , _owner (0) , _add_to_selection (false) , _n_factory_presets (0) , _block_rpc (0) , _rpc_queue (RouteProcessorChange::NoProcessorChange, false) , _no_kMono (false) , _restart_component_is_synced (false) { using namespace std; IPluginFactory* factory = m->factory (); if (!factory) { throw failed_constructor (); } if (!_fuid.fromString (unique_id.c_str ())) { throw failed_constructor (); } PFactoryInfo fi; if (factory->getFactoryInfo (&fi) == kResultTrue) { /* work around issue with UADx VST3s not recognizing * Vst::SpeakerArr::kMono. (see commit message for details) */ if (0 == strcmp (fi.vendor, "Universal Audio (UADx)")) { _no_kMono = true; } } #if !(defined PLATFORM_WINDOWS || defined __APPLE__) /* Linux only */ _restart_component_is_synced = m->has_symbol ("yabridge_version"); if (_restart_component_is_synced) { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI detected yabridge\n"); } #endif #ifndef NDEBUG if (DEBUG_ENABLED (DEBUG::VST3Config)) { char fuid[33]; _fuid.toString (fuid); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI create instance %1\n", fuid)); } #endif if (factory->createInstance (_fuid.toTUID (), Vst::IComponent::iid, (void**)&_component) != kResultTrue) { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI create instance failed\n"); throw failed_constructor (); } if (!_component || _component->initialize (HostApplication::getHostContext ()) != kResultOk) { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI component initialize failed\n"); throw failed_constructor (); } _controller = FUnknownPtr (_component).take (); if (!_controller) { TUID controllerCID; if (_component->getControllerClassId (controllerCID) == kResultTrue) { if (factory->createInstance (controllerCID, Vst::IEditController::iid, (void**)&_controller) != kResultTrue) { _component->terminate (); _component->release (); throw failed_constructor (); } } } if (!_controller) { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI no controller was found\n"); _component->terminate (); _component->release (); throw failed_constructor (); } /* The official Steinberg SDK's source/vst/hosting/plugprovider.cpp * only initializes the controller if it is separate of the component. * * However some plugins expect and unconditional call and other * hosts incl. JUCE based one initialize a controller separately because * FUnknownPtr<> cast may return a new obeject. * * So do not check for errors. * if Vst::IEditController is-a Vst::IComponent the Controller * may or may not already be initialized. */ _controller->initialize (HostApplication::getHostContext ()); if (_controller->setComponentHandler (this) != kResultOk) { _controller->terminate (); _controller->release (); _component->terminate (); _component->release (); throw failed_constructor (); } if (!(_processor = FUnknownPtr (_component))) { _controller->terminate (); _controller->release (); _component->terminate (); _component->release (); throw failed_constructor (); } #if SMTG_OS_LINUX IPtr factory3 = FUnknownPtr (factory); if (factory3) { Vst::IComponentHandler* ctx = this; factory3->setHostContext ((FUnknown*) ctx); } #endif /* prepare process context */ memset (&_context, 0, sizeof (Vst::ProcessContext)); /* bus-count for process-context */ _n_bus_in = _component->getBusCount (Vst::kAudio, Vst::kInput); _n_bus_out = _component->getBusCount (Vst::kAudio, Vst::kOutput); _busbuf_in.resize (_n_bus_in); _busbuf_out.resize (_n_bus_out); /* do not re-order, _io_name is build in sequence */ _n_inputs = count_channels (Vst::kAudio, Vst::kInput, Vst::kMain); _n_aux_inputs = count_channels (Vst::kAudio, Vst::kInput, Vst::kAux); _n_outputs = count_channels (Vst::kAudio, Vst::kOutput, Vst::kMain); _n_aux_outputs = count_channels (Vst::kAudio, Vst::kOutput, Vst::kAux); _n_midi_inputs = count_channels (Vst::kEvent, Vst::kInput, Vst::kMain); _n_midi_outputs = count_channels (Vst::kEvent, Vst::kOutput, Vst::kMain); if (!connect_components ()) { //_controller->terminate(); // XXX ? _component->terminate (); _component->release (); throw failed_constructor (); } memset (&_program_change_port, 0, sizeof (_program_change_port)); _program_change_port.id = Vst::kNoParamId; FUnknownPtr controller2 (_controller); if (controller2) { controller2->setKnobMode (Vst::kLinearMode); } int32 n_params = _controller->getParameterCount (); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3 parameter count: %1\n", n_params)); for (int32 i = 0; i < n_params; ++i) { Vst::ParameterInfo pi; if (_controller->getParameterInfo (i, pi) != kResultTrue) { continue; } if (pi.flags & Vst::ParameterInfo::kIsProgramChange) { _program_change_port = pi; continue; } if (tchar_to_utf8 (pi.title).find ("MIDI CC ") != std::string::npos) { /* Some JUCE plugins add 16 * 128 automatable MIDI CC parameters */ continue; } Param p; p.id = pi.id; p.label = tchar_to_utf8 (pi.title).c_str (); p.unit = tchar_to_utf8 (pi.units).c_str (); p.steps = pi.stepCount; p.normal = pi.defaultNormalizedValue; p.is_enum = 0 != (pi.flags & Vst::ParameterInfo::kIsList); p.read_only = 0 != (pi.flags & Vst::ParameterInfo::kIsReadOnly); p.automatable = 0 != (pi.flags & Vst::ParameterInfo::kCanAutomate); if (pi.flags & /*Vst::ParameterInfo::kIsHidden*/ (1 << 4)) { p.label = X_("hidden"); } #if 1 // if (host_editing == 0) // FUnknownPtr host_editing (_controller); if (0 == (pi.flags & (Vst::ParameterInfo::kCanAutomate | Vst::ParameterInfo::kIsReadOnly))) { /* Hide writable parameters that cannot be automated in the generic UI. * Those are usually internal params or MIDI ctrl hacks. * index_to_id() needs to work for those. */ p.label = X_("hidden"); } #endif uint32_t idx = _ctrl_params.size (); _ctrl_params.push_back (p); if (pi.flags & Vst::ParameterInfo::kIsBypass) { _port_id_bypass = idx; } _ctrl_id_index[pi.id] = idx; _ctrl_index_id[idx] = pi.id; _shadow_data.push_back (p.normal); _update_ctrl.push_back (false); } if (_n_midi_inputs > 0 || _n_midi_outputs > 0) { n_params += 128; } else if (n_params == 0) { n_params = 16; /* arbitrary baseline, grows as needed */ } _input_param_changes.set_n_params (n_params); _output_param_changes.set_n_params (n_params); synchronize_states (); /* enable all MIDI busses */ set_event_bus_state (true); } VST3PI::~VST3PI () { terminate (); } IPtr VST3PI::unit_info () { IPtr nfo = FUnknownPtr (_component); if (nfo) { return nfo; } return FUnknownPtr (_controller); } #if 0 IPtr VST3PI::unit_data () { Vst::IUnitData* iud = FUnknownPtr (_component); if (iud) { return iud; } return FUnknownPtr (_controller); } #endif void VST3PI::terminate () { assert (!_view); /* disable all MIDI busses */ set_event_bus_state (false); deactivate (); _processor = 0; disconnect_components (); if (_controller) { _controller->setComponentHandler (0); _controller->terminate (); _controller->release (); } if (_component) { _component->terminate (); _component->release (); } _controller = 0; _component = 0; } bool VST3PI::connect_components () { if (!_component || !_controller) { return false; } FUnknownPtr componentCP (_component); FUnknownPtr controllerCP (_controller); if (!componentCP || !controllerCP) { return true; } _component_cproxy = std::shared_ptr (new ConnectionProxy (componentCP)); _controller_cproxy = std::shared_ptr (new ConnectionProxy (controllerCP)); tresult res = _component_cproxy->connect (controllerCP); if (!(res == kResultOk || res == kNotImplemented)) { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::connect_components Cannot connect controller to component\n"); //return false; } res = _controller_cproxy->connect (componentCP); if (!(res == kResultOk || res == kNotImplemented)) { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::connect_components Cannot connect component to controller\n"); } return true; } bool VST3PI::disconnect_components () { if (!_component_cproxy || !_controller_cproxy) { return false; } bool rv = _component_cproxy->disconnect (); rv &= _controller_cproxy->disconnect (); _component_cproxy.reset (); _controller_cproxy.reset (); return rv; } tresult VST3PI::queryInterface (const TUID _iid, void** obj) { QUERY_INTERFACE (_iid, obj, FUnknown::iid, Vst::IComponentHandler) QUERY_INTERFACE (_iid, obj, Vst::IComponentHandler::iid, Vst::IComponentHandler) QUERY_INTERFACE (_iid, obj, Vst::IComponentHandler2::iid, Vst::IComponentHandler2) QUERY_INTERFACE (_iid, obj, Vst::IUnitHandler::iid, Vst::IUnitHandler) QUERY_INTERFACE (_iid, obj, Presonus::IContextInfoProvider::iid, Presonus::IContextInfoProvider) QUERY_INTERFACE (_iid, obj, Presonus::IContextInfoProvider2::iid, Presonus::IContextInfoProvider2) QUERY_INTERFACE (_iid, obj, Presonus::IContextInfoProvider3::iid, Presonus::IContextInfoProvider3) QUERY_INTERFACE (_iid, obj, IPlugFrame::iid, IPlugFrame) #if SMTG_OS_LINUX if (FUnknownPrivate::iidEqual (_iid, Linux::IRunLoop::iid)) { *obj = &static_runloop; return kResultOk; } #endif if (DEBUG_ENABLED (DEBUG::VST3Config)) { char fuid[33]; FUID::fromTUID (_iid).toString (fuid); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::queryInterface not supported: %1\n", fuid)); } *obj = nullptr; return kNoInterface; } tresult VST3PI::restartComponent (int32 flags) { DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::restartComponent %1%2\n", std::hex, flags)); if (flags & Vst::kReloadComponent) { Glib::Threads::Mutex::Lock pl (_process_lock, Glib::Threads::NOT_LOCK); if (!AudioEngine::instance ()->in_process_thread () && !_is_loading_state && !_restart_component_is_synced) { pl.acquire (); } else { assert (0); // a plugin should not call this while processing } /* according to the spec, "The host has to unload completely * the plug-in (controller/processor) and reload it." * * However other implementations, in particular JUCE, only * re-activates the plugin. So let's follow their lead for * the time being. */ warning << "VST3: Vst::kReloadComponent (ignored)" << endmsg; deactivate (); activate (); } if (flags & Vst::kParamValuesChanged) { Glib::Threads::Mutex::Lock pl (_process_lock, Glib::Threads::NOT_LOCK); if (!AudioEngine::instance ()->in_process_thread () && !_is_loading_state && !_restart_component_is_synced) { pl.acquire (); } update_shadow_data (); } if (flags & Vst::kLatencyChanged) { /* https://forums.steinberg.net/t/reporting-latency-change/201601 * mentions that the host plugin should be deactivated before querying * latency. However the official spec does not require this. * * However other implementations do not call setActive(false/true) when * the latency changes, and Ardour does not require it either, latency * changes are automatically picked up. */ Glib::Threads::Mutex::Lock pl (_process_lock, Glib::Threads::NOT_LOCK); if (!AudioEngine::instance ()->in_process_thread () && !_is_loading_state && !_restart_component_is_synced) { /* Some plugins (e.g BlendEQ) call this from the process, * IPlugProcessor::ProcessBuffers. In that case taking the * _process_lock would deadlock. */ pl.acquire (); } _plugin_latency.reset (); } if (flags & Vst::kIoTitlesChanged) { /* Input and/or Output bus titles have changed * compare to VST3PI::count_channels */ const Vst::MediaTypes medien [] = { Vst::kAudio, Vst::kEvent }; const Vst::BusDirections dirs[] = { Vst::kInput, Vst::kOutput }; for (auto const& media : medien) { for (auto const& dir : dirs) { int32 n_busses = _component->getBusCount (media, dir); for (int32 i = 0; i < n_busses; ++i) { Vst::BusInfo bus; if (_component->getBusInfo (media, dir, i, bus) != kResultTrue) { continue; } std::string bus_name = tchar_to_utf8 (bus.name); if (media == Vst::kEvent && _io_name[media][dir].size () == 1) { _io_name[media][dir][0].name = bus_name; } else if (media == Vst::kAudio && _io_name[media][dir].size () == size_t(bus.channelCount)) { for (int32_t j = 0; j < bus.channelCount; ++j) { std::string channel_name; if (bus.channelCount > 1) { channel_name = string_compose ("%1 %2", bus_name, j + 1); } else { channel_name = bus_name; } _io_name[media][dir][j].name = channel_name; } } } } } } if (flags & Vst::kParamTitlesChanged) { /* titles or default values or flags have changed */ int32 n_params = _controller->getParameterCount (); for (int32 i = 0; i < n_params; ++i) { Vst::ParameterInfo pi; if (_controller->getParameterInfo (i, pi) != kResultTrue) { continue; } std::map::const_iterator idx = _ctrl_id_index.find (pi.id); if (idx != _ctrl_id_index.end ()) { Param& p (_ctrl_params[idx->second]); #ifndef NDEBUG if (DEBUG_ENABLED (DEBUG::VST3Config) && p.label != tchar_to_utf8 (pi.title)) { DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3 ParamTitlesChanged: '%1' -> '%2'\n", p.label, tchar_to_utf8 (pi.title))); } #endif p.label = tchar_to_utf8 (pi.title).c_str (); p.normal = pi.defaultNormalizedValue; } } send_processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ } if (flags & Vst::kIoChanged) { warning << "VST3: Vst::kIoChanged (not implemented)" << endmsg; #if 0 update_processor (); // TODO getBusArrangement(); enable_io() #endif return kNotImplemented; } return kResultOk; } tresult VST3PI::notifyUnitSelection (Vst::UnitID unitId) { return kResultFalse; } tresult VST3PI::notifyProgramListChange (Vst::ProgramListID, int32) { float v = 0; Vst::ParamID id = _program_change_port.id; if (id != Vst::kNoParamId) { v = _controller->getParamNormalized (id); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::notifyProgramListChange: val: %1 (norm: %2)\n", v, _controller->normalizedParamToPlain (id, v))); } OnParameterChange (PresetChange, 0, v); /* EMIT SIGNAL */ return kResultOk; } tresult VST3PI::performEdit (Vst::ParamID id, Vst::ParamValue v) { std::map::const_iterator idx = _ctrl_id_index.find (id); if (idx != _ctrl_id_index.end ()) { float value = v; _shadow_data[idx->second] = value; _update_ctrl[idx->second] = true; /* set_parameter_internal() is called via OnParameterChange */ value = _controller->normalizedParamToPlain (id, value); OnParameterChange (ValueChange, idx->second, value); /* EMIT SIGNAL */ } return kResultOk; } tresult VST3PI::beginEdit (Vst::ParamID id) { std::map::const_iterator idx = _ctrl_id_index.find (id); if (idx != _ctrl_id_index.end ()) { OnParameterChange (BeginGesture, idx->second, 0); /* EMIT SIGNAL */ } return kResultOk; } tresult VST3PI::endEdit (Vst::ParamID id) { std::map::const_iterator idx = _ctrl_id_index.find (id); if (idx != _ctrl_id_index.end ()) { OnParameterChange (EndGesture, idx->second, 0); /* EMIT SIGNAL */ } return kResultOk; } tresult VST3PI::setDirty (TBool state) { if (state) { OnParameterChange (InternalChange, 0, 0); /* EMIT SIGNAL */ } return kResultOk; } tresult VST3PI::requestOpenEditor (FIDString name) { if (name == Vst::ViewType::kEditor) { /* TODO get plugin-insert (first plugin only, not replicated ones) * call pi->ShowUI (); */ } return kNotImplemented; } tresult VST3PI::startGroupEdit () { /* TODO: * remember current time, update StartTouch API * to allow passing a timestamp to PluginInsert::start_touch * replacing .audible_sample() */ return kNotImplemented; } tresult VST3PI::finishGroupEdit () { return kNotImplemented; } bool VST3PI::deactivate () { if (!_is_processing) { return true; } tresult res = _processor->setProcessing (false); if (!(res == kResultOk || res == kNotImplemented)) { return false; } res = _component->setActive (false); if (!(res == kResultOk || res == kNotImplemented)) { return false; } _is_processing = false; return true; } bool VST3PI::activate () { if (_is_processing) { return true; } tresult res = _component->setActive (true); if (!(res == kResultOk || res == kNotImplemented)) { return false; } res = _processor->setProcessing (true); if (!(res == kResultOk || res == kNotImplemented)) { return false; } _plugin_latency.reset (); _is_processing = true; return true; } bool VST3PI::update_processor () { bool was_active = _is_processing; if (!deactivate ()) { return false; } Vst::ProcessSetup setup; setup.processMode = _process_offline || AudioEngine::instance ()->freewheeling () ? Vst::kOffline : Vst::kRealtime; setup.symbolicSampleSize = Vst::kSample32; setup.maxSamplesPerBlock = _block_size; setup.sampleRate = _context.sampleRate; if (_processor->setupProcessing (setup) != kResultOk) { return false; } if (was_active) { return activate (); } return true; } uint32_t VST3PI::plugin_latency () { if (!_plugin_latency) { _plugin_latency = _processor->getLatencySamples (); } return _plugin_latency.value (); } void VST3PI::set_owner (SessionObject* o) { _owner = o; if (!o) { _strip_connections.drop_connections (); _ac_connection_list.drop_connections (); _ac_subscriptions.clear (); return; } if (!setup_psl_info_handler ()) { setup_info_listener (); } } void VST3PI::set_non_realtime (bool yn) { _process_offline = yn; } int32 VST3PI::count_channels (Vst::MediaType media, Vst::BusDirection dir, Vst::BusType type) { /* see also libs/ardour/vst3_scan.cc count_channels */ int32 n_busses = _component->getBusCount (media, dir); int32 n_channels = 0; for (int32 i = 0; i < n_busses; ++i) { Vst::BusInfo bus; if (_component->getBusInfo (media, dir, i, bus) == kResultTrue && bus.busType == type) { std::string bus_name = tchar_to_utf8 (bus.name); bool is_sidechain = (type == Vst::kAux) && (dir == Vst::kInput); if (media == Vst::kEvent) { #if 0 /* Supported MIDI Channel count (for a single MIDI input) */ if (bus.channelCount > 0) { _io_name[media][dir].push_back (Plugin::IOPortDescription (bus_name, is_sidechain)); } return std::min (1, bus.channelCount); #else /* Some plugin leave it at zero, even though they accept events */ _io_name[media][dir].push_back (Plugin::IOPortDescription (bus_name, is_sidechain, "", 0, i)); return 1; #endif } else { for (int32_t j = 0; j < bus.channelCount; ++j) { std::string channel_name; if (bus.channelCount > 1) { channel_name = string_compose ("%1 %2", bus_name, j + 1); } else { channel_name = bus_name; } _io_name[media][dir].push_back (Plugin::IOPortDescription (channel_name, is_sidechain, bus_name, j, i)); } n_channels += bus.channelCount; if (dir == Vst::kInput) { _bus_info_in.insert (std::make_pair (i, AudioBusInfo (type, bus.channelCount, bus.flags & Vst::BusInfo::kDefaultActive))); } else { _bus_info_out.insert (std::make_pair (i, AudioBusInfo (type, bus.channelCount, bus.flags & Vst::BusInfo::kDefaultActive))); } } } } return n_channels; } Vst::ParamID VST3PI::index_to_id (uint32_t p) const { assert (_ctrl_index_id.find (p) != _ctrl_index_id.end ()); return (_ctrl_index_id.find (p))->second; } bool VST3PI::set_block_size (int32_t n_samples) { if (_block_size == n_samples) { return true; } _block_size = n_samples; return update_processor (); } float VST3PI::default_value (uint32_t port) const { Vst::ParamID id (index_to_id (port)); return _controller->normalizedParamToPlain (id, _ctrl_params[port].normal); } void VST3PI::get_parameter_descriptor (uint32_t port, ParameterDescriptor& desc) const { Param const& p (_ctrl_params[port]); Vst::ParamID id (index_to_id (port)); desc.lower = _controller->normalizedParamToPlain (id, 0.f); desc.upper = _controller->normalizedParamToPlain (id, 1.f); desc.normal = _controller->normalizedParamToPlain (id, p.normal); desc.toggled = 1 == p.steps; desc.logarithmic = false; desc.integer_step = p.steps > 1 && (desc.upper - desc.lower) == p.steps; desc.sr_dependent = false; desc.enumeration = p.is_enum; desc.label = p.label; if (p.unit == "dB") { desc.unit = ARDOUR::ParameterDescriptor::DB; } else if (p.unit == "Hz") { desc.unit = ARDOUR::ParameterDescriptor::HZ; } if (p.steps > 1) { desc.rangesteps = 1 + p.steps; } FUnknownPtr extra_ctrl (_controller); if (extra_ctrl && port != designated_bypass_port ()) { int32 flags = extra_ctrl->getParamExtraFlags (id); if (Config->get_show_vst3_micro_edit_inline ()) { desc.inline_ctrl = (flags & kParamFlagMicroEdit) ? true : false; } } } std::string VST3PI::print_parameter (uint32_t port) const { Vst::ParamID id (index_to_id (port)); return print_parameter (id, _shadow_data[port]); } std::string VST3PI::print_parameter (Vst::ParamID id, Vst::ParamValue value) const { Vst::String128 rv; if (_controller->getParamStringByValue (id, value, rv) == kResultOk) { return tchar_to_utf8 (rv); } return ""; } uint32_t VST3PI::n_audio_inputs (bool with_aux) const { return _n_inputs + (with_aux ? _n_aux_inputs : 0); } uint32_t VST3PI::n_audio_outputs (bool with_aux) const { return _n_outputs + (with_aux ? _n_aux_outputs : 0); } uint32_t VST3PI::n_midi_inputs () const { return _n_midi_inputs; } uint32_t VST3PI::n_midi_outputs () const { return _n_midi_outputs; } Plugin::IOPortDescription VST3PI::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const { switch (dt) { case DataType::AUDIO: return _io_name[Vst::kAudio][input ? 0 : 1][id]; break; case DataType::MIDI: return _io_name[Vst::kEvent][input ? 0 : 1][id]; break; default: return Plugin::IOPortDescription ("?"); break; } } bool VST3PI::try_set_parameter_by_id (Vst::ParamID id, float value) { std::map::const_iterator idx = _ctrl_id_index.find (id); if (idx == _ctrl_id_index.end ()) { return false; } set_parameter (idx->second, value, 0, true /* OK, called from set_state */); return true; } void VST3PI::set_parameter (uint32_t p, float value, int32 sample_off, bool to_list, bool force) { Vst::ParamID id = index_to_id (p); value = _controller->plainParamToNormalized (id, value); if (_shadow_data[p] == value && sample_off == 0 && to_list && !force) { return; } if (to_list && (sample_off == 0 || parameter_is_automatable (p))) { set_parameter_internal (id, value, sample_off); } _shadow_data[p] = value; _update_ctrl[p] = true; } bool VST3PI::set_program (int pgm, int32 sample_off) { if (_program_change_port.id == Vst::kNoParamId) { return false; } if (_n_factory_presets < 1) { return false; } if (pgm < 0 || pgm >= _n_factory_presets) { return false; } Vst::ParamID id = _program_change_port.id; #if 0 /* This fails with some plugins (e.g. waves.vst3), * that do not use integer indexed presets. */ float value = _controller->plainParamToNormalized (id, pgm); #else float value = pgm; if (_n_factory_presets > 1) { value /= (_n_factory_presets - 1.f); } #endif DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::set_program pgm: %1 val: %2 (norm: %3)\n", pgm, value, _controller->plainParamToNormalized (id, pgm))); /* must not be called concurrently with processing */ int32 index; _input_param_changes.addParameterData (id, index)->addPoint (sample_off, value, index); _controller->setParamNormalized (id, value); #if 0 update_shadow_data (); synchronize_states (); #endif return true; } bool VST3PI::synchronize_states () { RAMStream stream; if (_component->getState (&stream) == kResultTrue) { stream.rewind (); tresult res = _controller->setComponentState (&stream); if (!(res == kResultOk || res == kNotImplemented)) { #ifndef NDEBUG std::cerr << "Failed to synchronize VST3 component <> controller state\n"; stream.hexdump (0); #endif } return res == kResultOk; } return false; } void VST3PI::update_shadow_data () { std::map::const_iterator i; for (i = _ctrl_index_id.begin (); i != _ctrl_index_id.end (); ++i) { Vst::ParamValue v = _controller->getParamNormalized (i->second); if (_shadow_data[i->first] != v) { #if 0 // DEBUG printf ("VST3PI::update_shadow_data %d: %f -> %f\n", i->first, _shadow_data[i->first], _controller->getParamNormalized (i->second)); #endif #if 1 // needed for set_program() changes to take effect, after kParamValuesChanged int32 index; _input_param_changes.addParameterData (i->second, index)->addPoint (0, v, index); #endif _shadow_data[i->first] = v; _update_ctrl[i->first] = true; OnParameterChange (ParamValueChanged, i->first, v); /* EMIT SIGNAL */ } } } void VST3PI::update_contoller_param () { /* GUI thread */ FUnknownPtr host_editing (_controller); std::map::const_iterator i; for (i = _ctrl_index_id.begin (); i != _ctrl_index_id.end (); ++i) { if (!_update_ctrl[i->first]) { continue; } _update_ctrl[i->first] = false; if (host_editing && !parameter_is_automatable (i->first) && !parameter_is_readonly (i->first)) { host_editing->beginEditFromHost (i->second); } _controller->setParamNormalized (i->second, _shadow_data[i->first]); if (host_editing && !parameter_is_automatable (i->first) && !parameter_is_readonly (i->first)) { host_editing->endEditFromHost (i->second); } } } void VST3PI::set_parameter_by_id (Vst::ParamID id, float value, int32 sample_off) { /* called in rt-thread from evoral_to_vst3 */ set_parameter_internal (id, value, sample_off); std::map::const_iterator idx = _ctrl_id_index.find (id); if (idx != _ctrl_id_index.end ()) { _shadow_data[idx->second] = value; _update_ctrl[idx->second] = true; } } void VST3PI::set_parameter_internal (Vst::ParamID id, float value, int32 sample_off) { int32 index; /* must not be called concurrently with processing */ _input_param_changes.addParameterData (id, index)->addPoint (sample_off, value, index); } float VST3PI::get_parameter (uint32_t p) const { Vst::ParamID id = index_to_id (p); if (_update_ctrl[p]) { _update_ctrl[p] = false; FUnknownPtr host_editing (_controller); if (host_editing && !parameter_is_automatable (p) && !parameter_is_readonly (p)) { host_editing->beginEditFromHost (id); } _controller->setParamNormalized (id, _shadow_data[p]); // GUI thread only if (host_editing && !parameter_is_automatable (p) && !parameter_is_readonly (p)) { host_editing->endEditFromHost (id); } } return _controller->normalizedParamToPlain (id, _shadow_data[p]); } bool VST3PI::live_midi_cc (int32_t bus, int16_t channel, Vst::CtrlNumber ctrl) { FUnknownPtr midiLearn (_controller); if (!midiLearn) { return false; } return kResultOk == midiLearn->onLiveMIDIControllerInput (bus, channel, ctrl); } bool VST3PI::midi_controller (int32_t bus, int16_t channel, Vst::CtrlNumber ctrl, Vst::ParamID& id) { FUnknownPtr midiMapping (_controller); if (!midiMapping) { return false; } return kResultOk == midiMapping->getMidiControllerAssignment (bus, channel, ctrl, id); } void VST3PI::cycle_start () { _input_events.clear (); _output_events.clear (); } void VST3PI::add_event (Evoral::Event const& ev, int32_t bus) { Vst::Event e; e.busIndex = bus; e.flags = ev.is_live_midi () ? Vst::Event::kIsLive : 0; e.sampleOffset = ev.time (); e.ppqPosition = _context.projectTimeMusic; if (evoral_to_vst3 (e, ev, bus)) { _input_events.addEvent (e); } } void VST3PI::set_event_bus_state (bool enable) { int32 n_bus_in = _component->getBusCount (Vst::kEvent, Vst::kInput); int32 n_bus_out = _component->getBusCount (Vst::kEvent, Vst::kOutput); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::set_event_bus_state: n_bus_in = %1 n_bus_in = %2 enable = %3\n", n_bus_in, n_bus_out, enable)); for (int32 i = 0; i < n_bus_in; ++i) { _component->activateBus (Vst::kEvent, Vst::kInput, i, enable); } for (int32 i = 0; i < n_bus_out; ++i) { _component->activateBus (Vst::kEvent, Vst::kOutput, i, enable); } } void VST3PI::enable_io (std::vector const& ins, std::vector const& outs) { if (_enabled_audio_in == ins && _enabled_audio_out == outs) { return; } /* some plugins, notably JUCE based ones, require the plugin to be * inactive to change bus arrangements. */ bool was_active = _is_processing; if (!deactivate ()) { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::enable_io failed to deactivate plugin\n"); } DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: ins = %1 == %3 outs = %2 == %4\n", ins.size (), outs.size (), n_audio_inputs (), n_audio_outputs ())); _enabled_audio_in = ins; _enabled_audio_out = outs; assert (_enabled_audio_in.size () == n_audio_inputs ()); assert (_enabled_audio_out.size () == n_audio_outputs ()); /* check that settings have not changed */ assert (_n_bus_in == _component->getBusCount (Vst::kAudio, Vst::kInput)); assert (_n_bus_out == _component->getBusCount (Vst::kAudio, Vst::kOutput)); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: n_bus_in = %1 n_bus_out = %2\n", _n_bus_in, _n_bus_out)); typedef std::vector VSTSpeakerArrangements; VSTSpeakerArrangements sa_in; VSTSpeakerArrangements sa_out; size_t cnt = 0; while (sa_in.size () < (VSTSpeakerArrangements::size_type) _n_bus_in) { bool enable = false; int32_t const n_chn = _bus_info_in[sa_in.size ()].n_chn; Vst::SpeakerArrangement sa = 0; _bus_info_in[sa_in.size ()].n_used_chn = 0; /* use all channels before the last connected one */ for (int32_t i = n_chn - 1; i >= 0; --i) { if (ins[cnt + i] || enable) { sa |= (uint64_t)1 << i; ++_bus_info_in[sa_in.size ()].n_used_chn; enable = true; } } cnt += n_chn; /* special case for Left only == Mono */ if (sa == 1 /*Vst::SpeakerArr::kSpeakerL */ && !_no_kMono) { sa = Vst::SpeakerArr::kMono; /* 1 << 19 */ } DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: activateBus (kAudio, kInput, %1, %2) used-chn: %3 spk-arr: %4\n", sa_in.size (), enable, _bus_info_in[sa_in.size ()].n_used_chn, std::hex, sa)); _component->activateBus (Vst::kAudio, Vst::kInput, sa_in.size (), enable); sa_in.push_back (sa); } cnt = 0; while (sa_out.size () < (VSTSpeakerArrangements::size_type) _n_bus_out) { bool enable = false; int32_t const n_chn = _bus_info_out[sa_out.size ()].n_chn; Vst::SpeakerArrangement sa = 0; _bus_info_out[sa_out.size ()].n_used_chn = 0; for (int32_t i = n_chn - 1; i >= 0; --i) { if (outs[cnt + i] || enable) { sa |= (uint64_t)1 << i; ++_bus_info_out[sa_out.size ()].n_used_chn; enable = true; } } cnt += n_chn; /* special case for Left only == Mono */ if (sa == 1 /*Vst::SpeakerArr::kSpeakerL */ && !_no_kMono) { sa = Vst::SpeakerArr::kMono; /* 1 << 19 */ } DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: activateBus (kAudio, kOutput, %1, %2) used-chn: %3 spk-arr: %4\n", sa_out.size (), enable, _bus_info_out[sa_out.size ()].n_used_chn, std::hex, sa)); _component->activateBus (Vst::kAudio, Vst::kOutput, sa_out.size (), enable); sa_out.push_back (sa); } Vst::SpeakerArrangement null_arrangement = {}; #ifndef NDEBUG tresult rv = #endif _processor->setBusArrangements (sa_in.size () > 0 ? &sa_in[0] : &null_arrangement, sa_in.size (), sa_out.size () > 0 ? &sa_out[0] : &null_arrangement, sa_out.size ()); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: setBusArrangements ins = %1 outs = %2 | rv = %3\n", sa_in.size (), sa_out.size (), rv)); /* https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html#ad3bc7bac3fd3b194122669be2a1ecc42 */ for (int32 i = 0; i < _n_bus_in; ++i) { Vst::SpeakerArrangement arr; if (_processor->getBusArrangement (Vst::kInput, i, arr) == kResultOk) { int cc = Vst::SpeakerArr::getChannelCount (arr); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI: Input BusArrangements: %1 chan: %2 bits: %3%4\n", i, cc, std::hex, arr)); assert (cc <= _bus_info_in[i].n_chn); if (cc <= _bus_info_in[i].n_chn) { _bus_info_in[i].n_used_chn = cc; } } } for (int32 i = 0; i < _n_bus_out; ++i) { Vst::SpeakerArrangement arr; if (_processor->getBusArrangement (Vst::kOutput, i, arr) == kResultOk) { int cc = Vst::SpeakerArr::getChannelCount (arr); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI: Output BusArrangements: %1 chan: %2 bits: %3%4\n", i, cc, std::hex, arr)); assert (cc <= _bus_info_out[i].n_chn); if (cc <= _bus_info_out[i].n_chn) { _bus_info_out[i].n_used_chn = cc; } } } if (was_active) { activate (); } } void VST3PI::process (float** ins, float** outs, uint32_t n_samples) { Vst::AudioBusBuffers* inputs = _n_bus_in > 0 ? &_busbuf_in[0] : NULL; Vst::AudioBusBuffers* outputs = _n_bus_out > 0 ? &_busbuf_out[0] : NULL; Vst::ProcessData data; data.numSamples = n_samples; data.processMode = _process_offline || AudioEngine::instance ()->freewheeling () ? Vst::kOffline : Vst::kRealtime; data.symbolicSampleSize = Vst::kSample32; data.numInputs = _n_bus_in; data.numOutputs = _n_bus_out; data.inputs = inputs; data.outputs = outputs; data.processContext = &_context; data.inputEvents = &_input_events; data.outputEvents = &_output_events; data.inputParameterChanges = &_input_param_changes; data.outputParameterChanges = &_output_param_changes; uint32_t used_ins = 0; uint32_t used_outs = 0; for (int i = 0; i < _n_bus_in; ++i) { inputs[i].silenceFlags = 0; inputs[i].numChannels = _bus_info_in[i].n_used_chn; inputs[i].channelBuffers32 = &ins[used_ins]; used_ins += _bus_info_in[i].n_chn; } for (int i = 0; i < _n_bus_out; ++i) { outputs[i].silenceFlags = 0; outputs[i].numChannels = _bus_info_out[i].n_used_chn; outputs[i].channelBuffers32 = &outs[used_outs]; used_outs += _bus_info_out[i].n_chn; } assert (used_ins == n_audio_inputs ()); assert (used_outs == n_audio_outputs ()); /* and go */ if (_processor->process (data) != kResultOk) { DEBUG_TRACE (DEBUG::VST3Process, "VST3 process error\n"); } /* handle output parameter changes */ int n_changes = _output_param_changes.getParameterCount (); for (int i = 0; i < n_changes; ++i) { Vst::IParamValueQueue* data = _output_param_changes.getParameterData (i); if (!data) { continue; } Vst::ParamID id = data->getParameterId (); int n_points = data->getPointCount (); if (n_points == 0) { continue; } std::map::const_iterator idx = _ctrl_id_index.find (id); if (idx != _ctrl_id_index.end ()) { /* automatable parameter, or read-only output */ int32 offset = 0; Vst::ParamValue value = 0; /* only get most recent point */ if (data->getPoint (n_points - 1, offset, value) == kResultOk) { if (_shadow_data[idx->second] != value) { _update_ctrl[idx->second] = true; _shadow_data[idx->second] = (float)value; } } } else { #ifndef NDEBUG /* non-automatable parameter */ std::cerr << "VST3: TODO non-automatable output param..\n"; // TODO inform UI #endif } } _input_param_changes.clear (); _output_param_changes.clear (); } /* **************************************************************************** * State * compare to public.sdk/source/vst/vstpresetfile.cpp */ namespace Steinberg { namespace Vst { enum ChunkType { kHeader, kComponentState, kControllerState, kProgramData, kMetaInfo, kChunkList, kNumPresetChunks }; static const ChunkID commonChunks[kNumPresetChunks] = { { 'V', 'S', 'T', '3' }, // kHeader { 'C', 'o', 'm', 'p' }, // kComponentState { 'C', 'o', 'n', 't' }, // kControllerState { 'P', 'r', 'o', 'g' }, // kProgramData { 'I', 'n', 'f', 'o' }, // kMetaInfo { 'L', 'i', 's', 't' } // kChunkList }; static const int32 kFormatVersion = 1; static const ChunkID& getChunkID (ChunkType type) { return commonChunks[type]; } struct ChunkEntry { void start_chunk (const ChunkID& id, RAMStream& stream) { memcpy (_id, &id, sizeof (ChunkID)); stream.tell (&_offset); _size = 0; } void end_chunk (RAMStream& stream) { int64 pos = 0; stream.tell (&pos); _size = pos - _offset; } ChunkID _id; int64 _offset; int64 _size; }; } // namespace Vst typedef std::vector ChunkEntryVector; } // namespace Steinberg static bool is_equal_ID (const Vst::ChunkID id1, const Vst::ChunkID id2) { return 0 == memcmp (id1, id2, sizeof (Vst::ChunkID)); } static bool read_equal_ID (RAMStream& stream, const Vst::ChunkID id) { Vst::ChunkID tmp; return stream.read_ChunkID (tmp) && is_equal_ID (tmp, id); } bool VST3PI::load_state (RAMStream& stream) { assert (stream.readonly ()); if (stream.size () < Vst::kHeaderSize) { return false; } int32 version = 0; int64 list_offset = 0; TUID class_id; if (!(read_equal_ID (stream, Vst::getChunkID (Vst::kHeader)) && stream.read_int32 (version) && stream.read_TUID (class_id) && stream.read_int64 (list_offset) && list_offset > 0 ) ) { DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::load_state: invalid header vers: %1 off: %2\n", version, list_offset)); return false; } if (_fuid != FUID::fromTUID (class_id)) { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::load_state: class ID mismatch\n"); return false; } /* read chunklist */ ChunkEntryVector entries; int64 seek_result = 0; stream.seek (list_offset, IBStream::kIBSeekSet, &seek_result); if (seek_result != list_offset) { return false; } if (!read_equal_ID (stream, Vst::getChunkID (Vst::kChunkList))) { return false; } PBD::Unwinder uw (_is_loading_state, true); int32 count; stream.read_int32 (count); for (int32 i = 0; i < count; ++i) { Vst::ChunkEntry c; stream.read_ChunkID (c._id); stream.read_int64 (c._offset); stream.read_int64 (c._size); entries.push_back (c); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::load_state: chunk: %1 off: %2 size: %3 type: %4\n", i, c._offset, c._size, c._id)); } bool rv = true; bool synced = false; /* parse chunks */ for (ChunkEntryVector::const_iterator i = entries.begin (); i != entries.end (); ++i) { stream.seek (i->_offset, IBStream::kIBSeekSet, &seek_result); if (seek_result != i->_offset) { rv = false; continue; } if (is_equal_ID (i->_id, Vst::getChunkID (Vst::kComponentState))) { ROMStream s (stream, i->_offset, i->_size); tresult res = _component->setState (&s); s.rewind (); tresult re2 = _controller->setComponentState (&s); if (re2 == kResultOk) { synced = true; } if (!(re2 == kResultOk || re2 == kNotImplemented || res == kResultOk || res == kNotImplemented)) { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::load_state: failed to restore component state\n"); rv = false; } } else if (is_equal_ID (i->_id, Vst::getChunkID (Vst::kControllerState))) { ROMStream s (stream, i->_offset, i->_size); tresult res = _controller->setState (&s); if (res == kResultOk) { synced = true; } if (!(res == kResultOk || res == kNotImplemented)) { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::load_state: failed to restore controller state\n"); rv = false; } } #if 0 else if (is_equal_ID (i->_id, Vst::getChunkID (Vst::kProgramData))) { Vst::IUnitInfo* unitInfo = unit_info (); stream.seek (i->_offset, IBStream::kIBSeekSet, &seek_result); int32 id = -1; if (stream.read_int32 (id)) { ROMStream s (stream, i->_offset + sizeof (int32), i->_size - sizeof (int32)); unit_info->setUnitProgramData (id, programIndex, s); //unit_data->setUnitData (id, programIndex, s) } } #endif else { DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::load_state: ignored unsupported state chunk.\n"); } } if (rv && !synced) { synced = synchronize_states (); } if (rv && synced) { update_shadow_data (); } return rv; } bool VST3PI::save_state (RAMStream& stream) { assert (!stream.readonly ()); Vst::ChunkEntry c; ChunkEntryVector entries; /* header */ stream.write_ChunkID (Vst::getChunkID (Vst::kHeader)); stream.write_int32 (Vst::kFormatVersion); stream.write_TUID (_fuid.toTUID ()); // class ID stream.write_int64 (0); // skip offset /* state chunks */ c.start_chunk (getChunkID (Vst::kComponentState), stream); if (_component->getState (&stream) == kResultTrue) { c.end_chunk (stream); entries.push_back (c); } c.start_chunk (getChunkID (Vst::kControllerState), stream); if (_controller->getState (&stream) == kResultTrue) { c.end_chunk (stream); entries.push_back (c); } /* update header */ int64 pos; stream.tell (&pos); stream.seek (Vst::kListOffsetPos, IBStream::kIBSeekSet, NULL); stream.write_int64 (pos); stream.seek (pos, IBStream::kIBSeekSet, NULL); /* write list */ stream.write_ChunkID (Vst::getChunkID (Vst::kChunkList)); stream.write_int32 (entries.size ()); for (ChunkEntryVector::const_iterator i = entries.begin (); i != entries.end (); ++i) { stream.write_ChunkID (i->_id); stream.write_int64 (i->_offset); stream.write_int64 (i->_size); } return entries.size () > 0; } /* ****************************************************************************/ void VST3PI::stripable_property_changed (PBD::PropertyChange const&) { FUnknownPtr il (_controller); Stripable* s = dynamic_cast (_owner); assert (il && s); DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::stripable_property_changed\n"); IPtr al (new HostAttributeList ()); Vst::String128 tmp; utf8_to_tchar (tmp, _owner->name (), 128); al->setInt (Vst::ChannelContext::kChannelNameLengthKey, _owner->name ().size ()); al->setString (Vst::ChannelContext::kChannelNameKey, tmp); utf8_to_tchar (tmp, _owner->id ().to_s (), 128); al->setInt (Vst::ChannelContext::kChannelNameLengthKey, _owner->id ().to_s ().size ()); al->setString (Vst::ChannelContext::kChannelUIDKey, tmp); std::string ns; int order_key; if (s->is_master () || s->is_surround_master ()) { ns = _("Master"); order_key = 2; } else if (s->is_monitor ()) { ns = _("Monitor"); order_key = 3; } else { ns = _("Track"); order_key = 1; } al->setInt (Vst::ChannelContext::kChannelIndexNamespaceOrderKey, order_key); al->setInt (Vst::ChannelContext::kChannelIndexKey, 1 + s->presentation_info ().order ()); utf8_to_tchar (tmp, ns, 128); al->setInt (Vst::ChannelContext::kChannelIndexNamespaceLengthKey, ns.size ()); al->setString (Vst::ChannelContext::kChannelIndexNamespaceKey, tmp); uint32_t rgba = s->presentation_info ().color (); Vst::ChannelContext::ColorSpec argb = ((rgba >> 8) & 0xffffff) | ((rgba & 0xff) << 24); al->setInt (Vst::ChannelContext::kChannelColorKey, argb); al->setInt (Vst::ChannelContext::kChannelPluginLocationKey, Vst::ChannelContext::kPreVolumeFader); // XXX il->setChannelContextInfos (al); } bool VST3PI::setup_info_listener () { FUnknownPtr il (_controller); if (!il) { return false; } DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::setup_info_listener\n"); Stripable* s = dynamic_cast (_owner); s->PropertyChanged.connect_same_thread (_strip_connections, boost::bind (&VST3PI::stripable_property_changed, this, _1)); s->presentation_info ().PropertyChanged.connect_same_thread (_strip_connections, boost::bind (&VST3PI::stripable_property_changed, this, _1)); /* send initial change */ stripable_property_changed (PropertyChange ()); return true; } /* **************************************************************************** * PSL Extensions */ bool VST3PI::add_slave (Vst::IEditController* c, bool rt) { FUnknownPtr slave_ctrl (_controller); if (slave_ctrl) { return slave_ctrl->addSlave (c, rt ? kSlaveModeLowLatencyClone : kSlaveModeNormal) == kResultOk; } return false; } bool VST3PI::remove_slave (Vst::IEditController* c) { FUnknownPtr slave_ctrl (_controller); if (slave_ctrl) { return slave_ctrl->removeSlave (c) == kResultOk; } return false; } bool VST3PI::subscribe_to_automation_changes () const { FUnknownPtr extra_ctrl (_controller); return 0 != extra_ctrl ? true : false; } void VST3PI::automation_state_changed (uint32_t port, AutoState s, std::weak_ptr wal) { Vst::ParamID id (index_to_id (port)); std::shared_ptr al = wal.lock (); FUnknownPtr extra_ctrl (_controller); assert (extra_ctrl); AutomationMode am; switch (s) { case ARDOUR::Off: if (!al || al->empty ()) { am = kAutomationNone; } else { am = kAutomationOff; } break; case Write: am = kAutomationWrite; break; case Touch: am = kAutomationTouch; break; case Play: am = kAutomationRead; break; case Latch: am = kAutomationLatch; break; default: return; } extra_ctrl->setParamAutomationMode (id, am); } /* ****************************************************************************/ static std::shared_ptr lookup_ac (SessionObject* o, FIDString id) { Stripable* s = dynamic_cast (o); if (!s) { return std::shared_ptr (); } if (0 == strcmp (id, ContextInfo::kMute)) { return s->mute_control (); } else if (0 == strcmp (id, ContextInfo::kSolo)) { return s->solo_control (); } else if (0 == strcmp (id, ContextInfo::kPan)) { return s->pan_azimuth_control (); } else if (0 == strcmp (id, ContextInfo::kVolume)) { return s->gain_control (); } else if (0 == strncmp (id, ContextInfo::kSendLevel, strlen (ContextInfo::kSendLevel))) { #ifdef MIXBUS /* Only use mixbus sends, which are identified by providing a * send_enable_controllable(). * * The main reason is that the number of Mixbus sends * per route is fixed, but this also works around a crash: * * For Ardour sends, send_level_controllable() calls * Route::nth_send() which takes the _processor_lock. * * However this callback can be triggered initially * Route::add_processors () -> set_owner() -> * setup_psl_info_handler() -> ..notify.. * with process and processor locks held, leading to * recurive locks (deadlock, or double unlock crash). */ int send_id = atoi (id + strlen (ContextInfo::kSendLevel)); if (s->send_enable_controllable (send_id)) { return s->send_level_controllable (send_id); } #endif } return std::shared_ptr (); } tresult VST3PI::getContextInfoValue (int32& value, FIDString id) { Stripable* s = dynamic_cast (_owner); if (!s) { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::getContextInfoValue: not initialized"); return kNotInitialized; } if (0 == strcmp (id, ContextInfo::kIndexMode)) { value = ContextInfo::kFlatIndex; } else if (0 == strcmp (id, ContextInfo::kType)) { if (s->is_singleton ()) { value = ContextInfo::kOut; } else if (s->presentation_info ().flags () & PresentationInfo::AudioTrack) { value = ContextInfo::kTrack; } else if (s->presentation_info ().flags () & PresentationInfo::MidiTrack) { value = ContextInfo::kSynth; } else { value = ContextInfo::kBus; } } else if (0 == strcmp (id, ContextInfo::kMain)) { value = s->is_singleton () ? 1 : 0; } else if (0 == strcmp (id, ContextInfo::kIndex)) { value = s->presentation_info ().order (); } else if (0 == strcmp (id, ContextInfo::kColor)) { value = s->presentation_info ().color (); #if BYTEORDER == kBigEndian SWAP_32 (value) // RGBA32 -> ABGR32 #endif } else if (0 == strcmp (id, ContextInfo::kVisibility)) { value = s->is_hidden () ? 0 : 1; } else if (0 == strcmp (id, ContextInfo::kSelected)) { value = s->is_selected () ? 1 : 0; } else if (0 == strcmp (id, ContextInfo::kFocused)) { std::shared_ptr stripable = s->session ().selection ().first_selected_stripable (); value = stripable && stripable.get () == s ? 1 : 0; } else if (0 == strcmp (id, ContextInfo::kSendCount)) { value = 0; while (s->send_enable_controllable (value)) { ++value; } } else if (0 == strcmp (id, ContextInfo::kMute)) { std::shared_ptr ac = s->mute_control (); if (ac) { psl_subscribe_to (ac, id); value = ac->muted_by_self (); } else { value = 0; } } else if (0 == strcmp (id, ContextInfo::kSolo)) { std::shared_ptr ac = s->solo_control (); if (ac) { psl_subscribe_to (ac, id); value = ac->self_soloed (); } else { value = 0; } } else { DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue unsupported ID %1\n", id)); return kNotImplemented; } DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue %1 = %2\n", id, value)); return kResultOk; } tresult VST3PI::getContextInfoString (Vst::TChar* string, int32 max_len, FIDString id) { if (!_owner) { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::getContextInfoString: not initialized"); return kNotInitialized; } if (0 == strcmp (id, ContextInfo::kID)) { utf8_to_tchar (string, _owner->id ().to_s (), max_len); } else if (0 == strcmp (id, ContextInfo::kName)) { utf8_to_tchar (string, _owner->name (), max_len); } else if (0 == strcmp (id, ContextInfo::kActiveDocumentID)) { DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString: NOT IMPLEMENTED (%1)\n", id)); return kNotImplemented; // XXX TODO } else if (0 == strcmp (id, ContextInfo::kDocumentID)) { DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString: NOT IMPLEMENTED (%1)\n", id)); return kNotImplemented; // XXX TODO } else if (0 == strcmp (id, ContextInfo::kDocumentName)) { DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString: NOT IMPLEMENTED (%1)\n", id)); return kNotImplemented; // XXX TODO } else if (0 == strcmp (id, ContextInfo::kDocumentFolder)) { DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString: NOT IMPLEMENTED (%1)\n", id)); return kNotImplemented; // XXX TODO } else if (0 == strcmp (id, ContextInfo::kAudioFolder)) { DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString: NOT IMPLEMENTED (%1)\n", id)); return kNotImplemented; // XXX TODO } else { std::shared_ptr ac = lookup_ac (_owner, id); if (!ac) { DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoString unsupported ID %1\n", id)); return kInvalidArgument; } utf8_to_tchar (string, ac->get_user_string (), max_len); } DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue %1 = %2\n", id, tchar_to_utf8 (string))); return kResultOk; } tresult VST3PI::getContextInfoValue (double& value, FIDString id) { Stripable* s = dynamic_cast (_owner); if (!s) { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::getContextInfoValue: not initialized"); return kNotInitialized; } if (0 == strcmp (id, ContextInfo::kMaxVolume)) { value = s->gain_control ()->upper (); } else if (0 == strcmp (id, ContextInfo::kMaxSendLevel)) { #ifdef MIXBUS if (s->send_level_controllable (0)) { value = s->send_level_controllable (0)->upper (); // pow (10.0, .05 * 15.0); } #endif value = 2.0; // Config->get_max_gain(); } else if (0 == strcmp (id, ContextInfo::kVolume)) { std::shared_ptr ac = s->gain_control (); value = ac->get_value (); // gain coefficient 0..2 (1.0 = 0dB) psl_subscribe_to (ac, id); } else if (0 == strcmp (id, ContextInfo::kPan)) { std::shared_ptr ac = s->pan_azimuth_control (); if (ac) { value = ac->internal_to_interface (ac->get_value (), true); psl_subscribe_to (ac, id); } else { value = 0.5; // center } } else if (0 == strncmp (id, ContextInfo::kSendLevel, strlen (ContextInfo::kSendLevel))) { std::shared_ptr ac = lookup_ac (_owner, id); if (ac) { value = ac->get_value (); // gain cofficient psl_subscribe_to (ac, id); } else { DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue invalid AC %1\n", id)); return kInvalidArgument; // send index out of bounds } } else { DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue unsupported ID %1\n", id)); return kInvalidArgument; } DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue %1 = %2\n", id, value)); return kResultOk; } tresult VST3PI::setContextInfoValue (FIDString id, double value) { Stripable* s = dynamic_cast (_owner); if (!s) { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoValue: not initialized"); return kNotInitialized; } DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoValue %1 to %2\n", id, value)); if (s->session ().loading () || s->session ().deletion_in_progress ()) { return kResultOk; } if (0 == strcmp (id, ContextInfo::kVolume)) { std::shared_ptr ac = lookup_ac (_owner, id); assert (ac); if (ac) { ac->set_value (value, Controllable::NoGroup); } } else if (0 == strcmp (id, ContextInfo::kPan)) { std::shared_ptr ac = lookup_ac (_owner, id); if (ac) { ac->set_value (ac->interface_to_internal (value, true), PBD::Controllable::NoGroup); } } else if (0 == strncmp (id, ContextInfo::kSendLevel, strlen (ContextInfo::kSendLevel))) { std::shared_ptr ac = lookup_ac (_owner, id); if (ac) { ac->set_value (value, Controllable::NoGroup); } else { return kInvalidArgument; // send index out of bounds } } else { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoValue: unsupported ID\n"); return kInvalidArgument; } return kResultOk; } tresult VST3PI::setContextInfoValue (FIDString id, int32 value) { Stripable* s = dynamic_cast (_owner); if (!s) { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoValue: not initialized"); return kNotInitialized; } DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoValue %1 to %2\n", id, value)); if (s->session ().loading () || s->session ().deletion_in_progress ()) { return kResultOk; } if (0 == strcmp (id, ContextInfo::kColor)) { #if BYTEORDER == kBigEndian SWAP_32 (value) // ABGR32 -> RGBA32 #endif s->presentation_info ().set_color (value); } else if (0 == strcmp (id, ContextInfo::kSelected)) { std::shared_ptr stripable = s->session ().stripable_by_id (s->id ()); assert (stripable); if (value == 0) { s->session ().selection ().remove (stripable, std::shared_ptr ()); } else if (_add_to_selection) { s->session ().selection ().add (stripable, std::shared_ptr ()); } else { s->session ().selection ().set (stripable, std::shared_ptr ()); } } else if (0 == strcmp (id, ContextInfo::kMultiSelect)) { _add_to_selection = value != 0; } else if (0 == strcmp (id, ContextInfo::kMute) || 0 == strcmp (id, ContextInfo::kSolo)) { std::shared_ptr ac = lookup_ac (_owner, id); if (ac) { s->session ().set_control (ac, value != 0 ? 1 : 0, Controllable::NoGroup); } } else { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoValue: unsupported ID\n"); return kNotImplemented; } return kResultOk; } tresult VST3PI::setContextInfoString (FIDString id, Vst::TChar* string) { if (!_owner) { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoString: not initialized"); return kNotInitialized; } DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString %1 to %2\n", id, tchar_to_utf8 (string))); if (0 == strcmp (id, ContextInfo::kName)) { return _owner->set_name (tchar_to_utf8 (string)) ? kResultOk : kResultFalse; } DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoString: unsupported ID\n"); return kInvalidArgument; } tresult VST3PI::beginEditContextInfoValue (FIDString id) { if (!_owner) { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::beginEditContextInfoValue: not initialized"); return kNotInitialized; } std::shared_ptr ac = lookup_ac (_owner, id); if (!ac) { return kInvalidArgument; } DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::beginEditContextInfoValue %1\n", id)); ac->start_touch (timepos_t (ac->session ().transport_sample ())); return kResultOk; } tresult VST3PI::endEditContextInfoValue (FIDString id) { if (!_owner) { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::endEditContextInfoValue: not initialized"); return kNotInitialized; } std::shared_ptr ac = lookup_ac (_owner, id); if (!ac) { return kInvalidArgument; } DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::endEditContextInfoValue %1\n", id)); ac->stop_touch (timepos_t (ac->session ().transport_sample ())); return kResultOk; } void VST3PI::psl_subscribe_to (std::shared_ptr ac, FIDString id) { FUnknownPtr nfo2 (_controller); if (!nfo2) { return; } std::pair::iterator, bool> r = _ac_subscriptions.insert (ac->parameter ()); if (!r.second) { return; } DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::psl_subscribe_to: %1\n", ac->name ())); ac->Changed.connect_same_thread (_ac_connection_list, boost::bind (&VST3PI::forward_signal, this, nfo2.get (), id)); } void VST3PI::forward_signal (IContextInfoHandler2* handler, FIDString id) const { assert (handler); DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::psl_subscribtion AC changed %1\n", id)); handler->notifyContextInfoChange (id); } void VST3PI::psl_stripable_property_changed (PBD::PropertyChange const& what_changed) { FUnknownPtr nfo (_controller); FUnknownPtr nfo2 (_controller); if (nfo && !nfo2) { DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::psl_stripable_property_changed v1\n"); nfo->notifyContextInfoChange (); } if (!nfo2) { return; } DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::psl_stripable_property_changed v2\n"); if (what_changed.contains (Properties::selected)) { nfo2->notifyContextInfoChange (ContextInfo::kSelected); nfo2->notifyContextInfoChange (ContextInfo::kFocused); // XXX } if (what_changed.contains (Properties::hidden)) { nfo2->notifyContextInfoChange (ContextInfo::kVisibility); } if (what_changed.contains (Properties::name)) { nfo2->notifyContextInfoChange (ContextInfo::kName); } if (what_changed.contains (Properties::color)) { nfo2->notifyContextInfoChange (ContextInfo::kColor); } } bool VST3PI::setup_psl_info_handler () { /* initial update */ FUnknownPtr nfo (_controller); FUnknownPtr nfo2 (_controller); DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::setup_psl_info_handler: (%1) (%2)\n", nfo != 0, nfo2 != 0)); if (nfo2) { nfo2->notifyContextInfoChange (""); } else if (nfo) { nfo->notifyContextInfoChange (); } if (!nfo && !nfo2) { return false; } Stripable* s = dynamic_cast (_owner); s->PropertyChanged.connect_same_thread (_strip_connections, boost::bind (&VST3PI::psl_stripable_property_changed, this, _1)); s->presentation_info ().PropertyChanged.connect_same_thread (_strip_connections, boost::bind (&VST3PI::psl_stripable_property_changed, this, _1)); return true; } /* **************************************************************************** * GUI */ IPlugView* VST3PI::try_create_view () const { IPlugView* view = _controller->createView (Vst::ViewType::kEditor); if (!view) { view = _controller->createView (0); } if (!view) { view = FUnknownPtr (_controller).take (); if (view) { view->addRef (); } } return view; } IPlugView* VST3PI::view () { if (!_view) { _view = try_create_view (); if (_view) { _view->setFrame (this); } } return _view; } void VST3PI::close_view () { if (!_view) { return; } _view->removed (); _view->setFrame (0); _view->release (); _view = 0; } bool VST3PI::has_editor () const { IPlugView* view = _view; if (!view) { view = try_create_view (); } bool rv = false; if (view) { #ifdef PLATFORM_WINDOWS rv = kResultOk == view->isPlatformTypeSupported (kPlatformTypeHWND); #elif defined(__APPLE__) rv = kResultOk == view->isPlatformTypeSupported (kPlatformTypeNSView); #else rv = kResultOk == view->isPlatformTypeSupported (kPlatformTypeX11EmbedWindowID); #endif if (!_view) { view->release (); } } return rv; } tresult VST3PI::resizeView (IPlugView* view, ViewRect* new_size) { OnResizeView (new_size->getWidth (), new_size->getHeight ()); /* EMIT SIGNAL */ return view->onSize (new_size); } void VST3PI::block_notifications () { _block_rpc.fetch_add (1); } void VST3PI::resume_notifications () { if (!PBD::atomic_dec_and_test (_block_rpc)) { return; } ARDOUR::RouteProcessorChange rpc (RouteProcessorChange::NoProcessorChange, false); std::swap (rpc, _rpc_queue); Route* r = dynamic_cast (_owner); if (r && _rpc_queue.type != RouteProcessorChange::NoProcessorChange) { r->processors_changed (rpc); /* EMIT SIGNAL */ } } void VST3PI::send_processors_changed (RouteProcessorChange const& rpc) { if (_block_rpc.load () != 0) { _rpc_queue.type = ARDOUR::RouteProcessorChange::Type (_rpc_queue.type | rpc.type); _rpc_queue.meter_visibly_changed |= rpc.meter_visibly_changed; return; } Route* r = dynamic_cast (_owner); if (r) { r->processors_changed (rpc); /* EMIT SIGNAL */ } }