diff --git a/libs/ardour/ardour/vst3_plugin.h b/libs/ardour/ardour/vst3_plugin.h index 815d75f008..b5f7cc4428 100644 --- a/libs/ardour/ardour/vst3_plugin.h +++ b/libs/ardour/ardour/vst3_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Robin Gareus + * Copyright (C) 2019-2020 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 @@ -19,17 +19,211 @@ #ifndef _ardour_vst3_plugin_h_ #define _ardour_vst3_plugin_h_ +#include + #include "pbd/signals.h" -#include "vst3/vst3.h" +#include "pbd/search_path.h" #include "ardour/plugin.h" +#include "ardour/vst3_host.h" + +namespace ARDOUR { +class VST3PluginModule; +} + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + +namespace Steinberg { + +/* VST3 hosted Plugin abstraction Implementation + * + * For convenience this is placed in the Steinberg namespace. + * Ardour::VST3Plugin has-a VST3PI (not is-a). + */ +class LIBARDOUR_API VST3PI + : public Vst::IComponentHandler + , public Vst::IConnectionPoint + , public IPlugFrame +{ +public: + VST3PI (boost::shared_ptr m, int index, std::string unique_id); + virtual ~VST3PI (); + + /* IComponentHandler */ + tresult beginEdit (Vst::ParamID id) SMTG_OVERRIDE; + tresult performEdit (Vst::ParamID id, Vst::ParamValue value) SMTG_OVERRIDE; + tresult endEdit (Vst::ParamID id) SMTG_OVERRIDE; + tresult restartComponent (int32 flags) SMTG_OVERRIDE; + + /* IConnectionPoint API */ + tresult connect (Vst::IConnectionPoint* other) SMTG_OVERRIDE; + tresult disconnect (Vst::IConnectionPoint* other) SMTG_OVERRIDE; + tresult notify (Vst::IMessage* message) SMTG_OVERRIDE; + + /* GUI */ + tresult resizeView (IPlugView* view, ViewRect* newSize) SMTG_OVERRIDE; + IPlugView* view (); + void close_view (); + PBD::Signal2 OnResizeView; +#if SMTG_OS_LINUX + void set_runloop (Linux::IRunLoop*); +#endif + void update_contoller_param (); + + tresult queryInterface (const TUID _iid, void** obj); + uint32 addRef () SMTG_OVERRIDE { return 1; } + uint32 release () SMTG_OVERRIDE { return 1; } + + /* Ardour Preset Helpers */ + Vst::IUnitInfo* unit_info (); + FUID const& fuid() const { return _fuid; } + Vst::ParameterInfo const& program_change_port() const { return _program_change_port; } + + /* API for Ardour -- Ports */ + uint32_t designated_bypass_port () const { return _port_id_bypass; } + uint32_t parameter_count () const { return _ctrl_params.size (); } + bool parameter_is_automatable (uint32_t p) const { return _ctrl_params[p].automatable; } + bool parameter_is_readonly (uint32_t p) const { return _ctrl_params[p].read_only; } + std::string parameter_label (uint32_t p) const { return _ctrl_params[p].label; } + float default_value (uint32_t p) const; + void get_parameter_descriptor (uint32_t, ARDOUR::ParameterDescriptor&) const; + std::string print_parameter (uint32_t p) const; + std::string print_parameter (Vst::ParamID, Vst::ParamValue) const; + bool set_program (float p, int32 sample_off, bool normalized); + + ARDOUR::Plugin::IOPortDescription describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const; + + uint32_t n_audio_inputs () const; + uint32_t n_audio_outputs () const; + + /* MIDI/Event interface */ + void cycle_start (); + void add_event (Evoral::Event const&, int32_t bus); + void vst3_to_midi_buffers (ARDOUR::BufferSet&, ARDOUR::ChanMapping const&); + + uint32_t n_midi_inputs () const; + uint32_t n_midi_outputs () const; + + /* API for Ardour -- Parameters */ + bool try_set_parameter_by_id (Vst::ParamID id, float value); + void set_parameter (uint32_t p, float value, int32 sample_off); + float get_parameter (uint32_t p) const; + std::string format_parameter (uint32_t p) const; + Vst::ParamID index_to_id (uint32_t) const; + + enum ParameterChange { BeginGesture, EndGesture , ValueChange }; + PBD::Signal3 OnParameterChange; + + /* API for Ardour -- Setup/Processing */ + uint32_t plugin_latency (); + bool set_block_size (int32_t); + bool activate (); + bool deactivate (); + + /* State */ + bool save_state (RAMStream& stream); + bool load_state (RAMStream& stream); + + Vst::ProcessContext& context () { return _context; } + + void enable_io (std::vector const&, std::vector const&); + + void process (float** ins, float** outs, uint32_t n_samples); + +private: + void init (); + void terminate (); + bool connect_components (); + bool disconnect_components (); + + bool update_processor (); + int32 count_channels (Vst::MediaType, Vst::BusDirection, Vst::BusType); + + bool evoral_to_vst3 (Vst::Event&, Evoral::Event const&, int32_t); + + void update_shadow_data (); + bool synchronize_states (); + + void set_parameter_by_id (Vst::ParamID id, float value, int32 sample_off); + void set_parameter_internal (Vst::ParamID id, float& value, int32 sample_off, bool normalized); + + bool midi_controller (int32_t, int16_t, Vst::CtrlNumber, Vst::ParamID &id); + + boost::shared_ptr _module; + + FUID _fuid; + IPluginFactory* _factory; + Vst::IComponent* _component; + Vst::IEditController* _controller; + IPlugView* _view; + +#if SMTG_OS_LINUX + Linux::IRunLoop* _run_loop; +#endif + + FUnknownPtr _processor; + Vst::ProcessContext _context; + + /* Parameters */ + Vst3ParameterChanges _input_param_changes; + Vst3ParameterChanges _output_param_changes; + + Vst3EventList _input_events; + Vst3EventList _output_events; + + /* state */ + bool _is_processing; + int32_t _block_size; + + /* ports */ + struct Param { + uint32_t id; + std::string label; + std::string unit; + int32_t steps; // 1: toggle + double normal; + bool is_enum; + bool read_only; + bool automatable; + }; + + uint32_t _port_id_bypass; + Vst::ParameterInfo _program_change_port; + std::vector _ctrl_params; + std::map _ctrl_id_index; + std::map _ctrl_index_id; + std::vector _shadow_data; + mutable std::vector _update_ctrl; + + std::vector _io_name[Vst::kNumMediaTypes][2]; + + std::vector _enabled_audio_in; + std::vector _enabled_audio_out; + + boost::optional _plugin_latency; + + int _n_inputs; + int _n_outputs; + int _n_aux_inputs; + int _n_aux_outputs; + int _n_midi_inputs; + int _n_midi_outputs; +}; + +} // namespace Steinberg namespace ARDOUR { class LIBARDOUR_API VST3Plugin : public ARDOUR::Plugin { public: - VST3Plugin (AudioEngine&, Session&, std::string const&); + VST3Plugin (AudioEngine&, Session&, Steinberg::VST3PI*); VST3Plugin (const VST3Plugin&); ~VST3Plugin (); @@ -40,18 +234,23 @@ public: uint32_t parameter_count () const; float default_value (uint32_t port); - void set_parameter (uint32_t port, float val); + void set_parameter (uint32_t port, float val, sampleoffset_t when); float get_parameter (uint32_t port) const; int get_parameter_descriptor (uint32_t which, ParameterDescriptor&) const; uint32_t nth_parameter (uint32_t port, bool& ok) const; + bool print_parameter (uint32_t, std::string&) const; - bool parameter_is_audio (uint32_t) const; - bool parameter_is_control (uint32_t) const; + bool parameter_is_audio (uint32_t) const { return false; } + bool parameter_is_control (uint32_t) const { return true; } bool parameter_is_input (uint32_t) const; bool parameter_is_output (uint32_t) const; + uint32_t designated_bypass_port (); + std::set automatable () const; std::string describe_parameter (Evoral::Parameter); + IOPortDescription describe_io_port (DataType dt, bool input, uint32_t id) const; + PluginOutputConfiguration possible_output () const; std::string state_node_name () const { return "vst3"; } @@ -62,9 +261,8 @@ public: std::string do_save_preset (std::string); void do_remove_preset (std::string); - void activate (); - void deactivate (); - void cleanup (); + void activate () { _plug->activate (); } + void deactivate () { _plug->deactivate (); } int set_block_size (pframes_t); @@ -74,14 +272,36 @@ public: pframes_t nframes, samplecnt_t offset); bool has_editor () const; + Steinberg::IPlugView* view (); + void close_view (); +#if SMTG_OS_LINUX + void set_runloop (Steinberg::Linux::IRunLoop*); +#endif + void update_contoller_param (); bool configure_io (ChanCount in, ChanCount out); + PBD::Signal2 OnResizeView; + private: samplecnt_t plugin_latency () const; + void init (); void find_presets (); + void forward_resize_view (int w, int h); + void parameter_change_handler (Steinberg::VST3PI::ParameterChange, uint32_t, float); + + PBD::Searchpath preset_search_path () const; + + Steinberg::VST3PI* _plug; + PBD::ScopedConnectionList _connections; + std::map _preset_uri_map; + + std::vector _connected_inputs; + std::vector _connected_outputs; }; +/* ****************************************************************************/ + class LIBARDOUR_API VST3PluginInfo : public PluginInfo { public: @@ -90,6 +310,9 @@ public: PluginPtr load (Session& session); std::vector get_presets (bool user_only) const; + bool is_instrument () const; + + boost::shared_ptr m; }; } // namespace ARDOUR diff --git a/libs/ardour/vst3_plugin.cc b/libs/ardour/vst3_plugin.cc index c6b9c59698..f0eed845cd 100644 --- a/libs/ardour/vst3_plugin.cc +++ b/libs/ardour/vst3_plugin.cc @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Robin Gareus + * Copyright (C) 2019-2020 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 @@ -16,147 +16,518 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "pbd/gstdio_compat.h" +#include + +#include "pbd/basename.h" +#include "pbd/compose.h" +#include "pbd/convert.h" #include "pbd/error.h" +#include "pbd/failed_constructor.h" +#include "pbd/file_utils.h" +#include "pbd/tokenizer.h" + +#ifdef PLATFORM_WINDOWS +#include // CSIDL_* +#include "pbd/windows_special_dirs.h" +#endif + +#include "ardour/audio_buffer.h" +#include "ardour/audioengine.h" +#include "ardour/session.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 Steinberg; -VST3Plugin::VST3Plugin (AudioEngine& engine, Session& session, std::string const&) +VST3Plugin::VST3Plugin (AudioEngine& engine, Session& session, VST3PI* plug) : Plugin (engine, session) + , _plug (plug) { + init (); } VST3Plugin::VST3Plugin (const VST3Plugin& other) : Plugin (other) { + boost::shared_ptr nfo = boost::dynamic_pointer_cast (other.get_info ()); + _plug = new VST3PI (nfo->m, nfo->index, nfo->unique_id); + init (); } VST3Plugin::~VST3Plugin () { + delete _plug; } +void +VST3Plugin::init () +{ + 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 all I/O is connected by default */ + for (int32_t i = 0; i < (int32_t)_plug->n_audio_inputs (); ++i) { + _connected_inputs.push_back (true); + } + for (int32_t i = 0; i < (int32_t)_plug->n_audio_outputs (); ++i) { + _connected_outputs.push_back (true); + } + /* 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: + /* emit ParameterChangedExternally, mark preset dirty */ + Plugin::parameter_changed_externally (param, value); + break; + } +} + +/* **************************************************************************** + * Parameter API + */ + uint32_t VST3Plugin::parameter_count () const { - return 0; + return _plug->parameter_count (); } float VST3Plugin::default_value (uint32_t port) { - return 0; + assert (port < parameter_count ()); + return _plug->default_value (port); } void -VST3Plugin::set_parameter (uint32_t port, float val) +VST3Plugin::set_parameter (uint32_t port, float val, sampleoffset_t when) { + _plug->set_parameter (port, val, when); + Plugin::set_parameter (port, val, when); } float VST3Plugin::get_parameter (uint32_t port) const { - return 0.f; + 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_audio (uint32_t port) const -{ - return false; -} - -bool -VST3Plugin::parameter_is_control (uint32_t port) const -{ - return false; -} - bool VST3Plugin::parameter_is_input (uint32_t port) const { - return false; + return !_plug->parameter_is_readonly (port); } bool VST3Plugin::parameter_is_output (uint32_t port) const { - return false; + return _plug->parameter_is_readonly (port); +} + +uint32_t +VST3Plugin::designated_bypass_port () +{ + return _plug->designated_bypass_port (); } 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 +{ + return _plug->describe_io_port (dt, input, id); +} + +PluginOutputConfiguration +VST3Plugin::possible_output () const +{ + return Plugin::possible_output (); // TODO +} + +/* **************************************************************************** + * Plugin UI + */ + bool VST3Plugin::has_editor () const { + IPlugView* view = const_cast(_plug)->view (); + if (view){ +#ifdef PLATFORM_WINDOWS + return kResultOk == view->isPlatformTypeSupported ("HWND"); +#elif defined (__APPLE__) + return kResultOk == view->isPlatformTypeSupported ("NSView"); +#else + return kResultOk == view->isPlatformTypeSupported ("X11EmbedWindowID"); +#endif + } return false; } +Steinberg::IPlugView* +VST3Plugin::view () +{ + return _plug->view (); +} + +void +VST3Plugin::close_view () +{ + _plug->close_view (); +} + +#if SMTG_OS_LINUX +void +VST3Plugin::set_runloop (Steinberg::Linux::IRunLoop* run_loop) +{ + return _plug->set_runloop (run_loop); +} +#endif + + +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 (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 / 127.f, ev.time (), true); // TODO map to available programs ?! + 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 / 127.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) { +#ifndef NDEBUG + printf ("VST3PI::vst3_to_midi_buffers - Invalid MIDI Bus %d\n", e.busIndex); +#endif + continue; + } + + MidiBuffer& mb = bufs.get_midi (index); + uint8_t data[3]; + + switch (e.type) { + case Vst::Event::kDataEvent: + /* sysex */ + mb.push_back (e.sampleOffset, e.data.size, (uint8_t const*)e.data.bytes); + break; + case Vst::Event::kNoteOffEvent: + data[0] = 0x80 | e.noteOff.channel; + data[1] = e.noteOff.pitch; + data[2] = vst_to_midi (e.noteOff.velocity); + mb.push_back (e.sampleOffset, 3, data); + break; + case Vst::Event::kNoteOnEvent: + data[0] = 0x90 | e.noteOn.channel; + data[1] = e.noteOn.pitch; + data[2] = vst_to_midi (e.noteOn.velocity); + mb.push_back (e.sampleOffset, 3, data); + break; + case Vst::Event::kPolyPressureEvent: + data[0] = 0xa0 | e.noteOff.channel; + data[1] = e.polyPressure.pitch; + data[2] = vst_to_midi (e.polyPressure.pressure); + mb.push_back (e.sampleOffset, 3, data); + break; + case Vst::Event::kLegacyMIDICCOutEvent: + switch (e.midiCCOut.controlNumber) { + case Vst::kCtrlPolyPressure: + data[0] = 0x0a | e.midiCCOut.channel; + data[1] = e.midiCCOut.value; + data[2] = e.midiCCOut.value2; + break; + default: /* Control Change */ + data[0] = 0xb0 | e.midiCCOut.channel; + data[1] = e.midiCCOut.controlNumber; + data[2] = e.midiCCOut.value; + break; + case Vst::kCtrlProgramChange: + data[0] = 0x0c | e.midiCCOut.channel; + data[1] = e.midiCCOut.value; + data[2] = e.midiCCOut.value2; + break; + case Vst::kAfterTouch: + data[0] = 0x0d | e.midiCCOut.channel; + data[1] = e.midiCCOut.value; + data[2] = e.midiCCOut.value2; + break; + case Vst::kPitchBend: + data[0] = 0x0e | e.midiCCOut.channel; + data[1] = e.midiCCOut.value; + data[2] = e.midiCCOut.value2; + break; + } + mb.push_back (e.sampleOffset, 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 (!_plug->parameter_is_automatable (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; + } + + 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; + } + } + } + } + + 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; + } + + if (!_plug->try_set_parameter_by_id ( param_id, value)) { + warning << string_compose (_("VST3<%1>: Invalid Vst::ParamID in VST3Plugin::set_state"), name ()) << endmsg; + } + } + return Plugin::set_state (node, version); } /* ****************************************************************************/ int -VST3Plugin::set_block_size (pframes_t) +VST3Plugin::set_block_size (pframes_t n_samples) { + _plug->set_block_size (n_samples); return 0; } samplecnt_t VST3Plugin::plugin_latency () const { - return 0; -} - -void -VST3Plugin::activate () -{ -} - -void -VST3Plugin::deactivate () -{ -} - -void -VST3Plugin::cleanup () -{ + return _plug->plugin_latency (); } bool @@ -169,8 +540,120 @@ 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 nframes, samplecnt_t offset) + pframes_t n_samples, samplecnt_t offset) { + //DEBUG_TRACE(DEBUG::VST3, string_compose("%1 run %2 offset %3\n", name(), nframes, 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 = _session.engine ().processed_samples (); + context.systemTime = g_get_monotonic_time (); + + { + TempoMap const& tmap (_session.tempo_map ()); + const Tempo& t (tmap.tempo_at_sample (start)); + const MeterSection& ms (tmap.meter_section_at_sample (start)); + context.tempo = t.quarter_notes_per_minute (); + context.timeSigNumerator = ms.divisions_per_bar (); + context.timeSigDenominator = ms.note_divisor (); + } + + 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 const& tmap (_session.tempo_map ()); + context.cycleStartMusic = tmap.quarter_note_at_sample_rt (looploc->start ()); + context.cycleEndMusic = tmap.quarter_note_at_sample_rt (looploc->end ()); + 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 (offset); + _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 (offset); + _connected_outputs[i] = valid; + } + + 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) { + _plug->vst3_to_midi_buffers (bufs, out_map); + } + return 0; } @@ -179,23 +662,229 @@ VST3Plugin::connect_and_run (BufferSet& bufs, bool VST3Plugin::load_preset (PresetRecord r) { - return false; + bool ok = false; + + /* Extract the index 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() != 4) { + return false; + } + + std::string const& unique_id = tmp[1]; + uint32_t index = PBD::atoi (tmp[2]); + + FUID fuid; + if (!fuid.fromString (unique_id.c_str()) || fuid != _plug->fuid ()) { + assert (0); + return false; + } + + if (index != _info->index) { + assert (0); + return false; + } + + if (tmp[0] == "VST3-P") { + int program = PBD::atoi (tmp[3]); + assert (!r.user); + if (!_plug->set_program (program, 0, false)) { +#ifndef NDEBUG + std::cerr << "set_program failed\n"; +#endif + 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); + } + } + + if (ok) { + Plugin::load_preset (r); + } + return ok; } std::string VST3Plugin::do_save_preset (std::string name) { + assert (!preset_search_path().empty ()); + std::string dir = preset_search_path ().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:%3"), unique_id (), _info->index, PBD::basename_nosuffix (fn)); + _preset_uri_map[uri] = fn; + return uri; + } return ""; } void VST3Plugin::do_remove_preset (std::string name) { + assert (!preset_search_path().empty ()); + std::string dir = preset_search_path ().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:%3"), unique_id (), _info->index, 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 */ + Vst::IUnitInfo* 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:%3"), unique_id (), _info->index, 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:%3"), unique_id (), _info->index, std::setw(4), std::setfill('0'), i); + PresetRecord r (uri , preset_name, false); + _presets.insert (make_pair (uri, r)); + } + } + } + + // TODO check _plug->unit_data() + // IUnitData: programDataSupported -> setUnitProgramData (IBStream) + + 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:%3"), unique_id (), _info->index, 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; + } +} + +PBD::Searchpath +VST3Plugin::preset_search_path () const +{ + boost::shared_ptr nfo = boost::dynamic_pointer_cast (get_info ()); + PBD::Searchpath preset_path; + + std::string vendor = legalize_for_universal_path (nfo->creator); + std::string name = legalize_for_universal_path (nfo->name); + + /* first listed is used to save custom user-presets */ +#ifdef __APPLE__ + preset_path += Glib::build_filename (Glib::get_home_dir(), "Library/Audio/Presets", vendor, name); + preset_path += Glib::build_filename ("/Library/Audio/Presets", vendor, name); +#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, name); + preset_path += Glib::build_filename (documents, "vst3 presets", vendor, name); + } + + preset_path += Glib::build_filename (Glib::get_user_data_dir(), "VST3 Presets", vendor, name); + + std::string appdata = PBD::get_win_special_folder_path (CSIDL_APPDATA); + if (!appdata.empty ()) { + preset_path += Glib::build_filename (appdata, "VST3 Presets", vendor, name); + preset_path += Glib::build_filename (appdata, "vst3 presets", vendor, name); + } +#else + preset_path += Glib::build_filename (Glib::get_home_dir(), ".vst3", "presets", vendor, name); + preset_path += Glib::build_filename ("/usr/share/vst3/presets", vendor, name); + preset_path += Glib::build_filename ("/usr/local/share/vst3/presets", vendor, name); +#endif + + return preset_path; } /* ****************************************************************************/ @@ -208,6 +897,25 @@ VST3PluginInfo::VST3PluginInfo () PluginPtr VST3PluginInfo::load (Session& session) { + try { + if (!m) { +#ifndef NDEBUG + printf ("Loading %s\n", path.c_str ()); +#endif + m = VST3PluginModule::load (path); +#ifndef NDEBUG + printf ("Loaded module\n"); +#endif + } + PluginPtr plugin; + Steinberg::VST3PI* plug = new VST3PI (m, index, unique_id); + plugin.reset (new VST3Plugin (session.engine (), session, plug)); + plugin->set_info (PluginInfoPtr (new VST3PluginInfo (*this))); + return plugin; + } catch (failed_constructor& err) { + ; + } + return PluginPtr (); } @@ -217,3 +925,1205 @@ VST3PluginInfo::get_presets (bool /*user_only*/) const std::vector p; return p; } + +bool +VST3PluginInfo::is_instrument () const +{ + if (category.find (Vst::PlugType::kInstrument) != std::string::npos) { + return true; + } + + return PluginInfo::is_instrument (); +} + +/* ****************************************************************************/ + +VST3PI::VST3PI (boost::shared_ptr m, int index, std::string unique_id) + : _module (m) + , _factory (0) + , _component (0) + , _controller (0) + , _view (0) +#if SMTG_OS_LINUX + , _run_loop (0) +#endif + , _is_processing (false) + , _block_size (0) + , _port_id_bypass (UINT32_MAX) +{ + using namespace std; + + GetFactoryProc fp = (GetFactoryProc)m->fn_ptr ("GetPluginFactory"); + if (!fp) { + throw failed_constructor (); + } + + if (!(_factory = fp ())) { + throw failed_constructor (); + } + + if (!_fuid.fromString (unique_id.c_str ())) { + throw failed_constructor (); + } + + PClassInfo ci; + if (_factory->getClassInfo (index, &ci) != kResultTrue) { + throw failed_constructor (); + } + if (strcmp (ci.category, kVstAudioEffectClass)) { + throw failed_constructor (); + } + if (FUID::fromTUID (ci.cid) != _fuid) { + throw failed_constructor (); + } + + if (_factory->createInstance (ci.cid, Vst::IComponent::iid, (void**)&_component) != kResultTrue) { + throw failed_constructor (); + } + + if (_component->initialize (HostApplication::getHostContext ()) != kResultOk) { + throw failed_constructor (); + } + + _controller = FUnknownPtr (_component); + if (!_controller) { + TUID controllerCID; + if (_component->getControllerClassId (controllerCID) == kResultTrue) { + if (_factory->createInstance (controllerCID, Vst::IEditController::iid, (void**)&_controller) != kResultTrue) { + throw failed_constructor (); + } + if (_controller && (_controller->initialize (HostApplication::getHostContext ()) != kResultOk)) { + throw failed_constructor (); + } + } + } + + if (!_controller) { + _component->terminate (); + throw failed_constructor (); + } + + if (_controller->setComponentHandler (this) != kResultOk) { + _component->terminate (); + throw failed_constructor (); + } + + if (!(_processor = FUnknownPtr (_component))) { + //_controller->terminate(); + _component->terminate (); + throw failed_constructor (); + } + + /* prepare process context */ + memset (&_context, 0, sizeof (Vst::ProcessContext)); + + /* 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 (); + throw failed_constructor (); + } + + memset (&_program_change_port, 0, sizeof (_program_change_port)); + _program_change_port.id = Vst::kNoParamId; + + int32 n_params = _controller->getParameterCount (); + 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 (0 == (pi.flags & Vst::ParameterInfo::kCanAutomate)) { + /* but allow read-only, not automatable params (ctrl outputs) */ + if (0 == (pi.flags & Vst::ParameterInfo::kIsReadOnly)) { + 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); + + 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); + } + + _input_param_changes.set_n_params (n_params); + _output_param_changes.set_n_params (n_params); + + synchronize_states (); + + /* enable all MIDI busses */ + int32 n_bus_in = _component->getBusCount (Vst::kEvent, Vst::kInput); + int32 n_bus_out = _component->getBusCount (Vst::kEvent, Vst::kOutput); + for (int32 i = 0; i < n_bus_in; ++i) { + _component->activateBus (Vst::kEvent, Vst::kInput, i, true); + } + for (int32 i = 0; i < n_bus_out; ++i) { + _component->activateBus (Vst::kEvent, Vst::kOutput, i, true); + } +} + +VST3PI::~VST3PI () +{ + _processor = 0; + terminate (); +} + +Vst::IUnitInfo* +VST3PI::unit_info () +{ + Vst::IUnitInfo* nfo = FUnknownPtr(_component); + if (nfo) { + return nfo; + } + return FUnknownPtr(_controller); +} + +#if 0 +Vst::IUnitData* +VST3PI::unit_data () +{ + Vst::IUnitData* iud = FUnknownPtr (_component); + if (iud) { + return iud; + } + return FUnknownPtr (_controller); +} +#endif + +void +VST3PI::terminate () +{ + disconnect_components (); + + bool controller_is_component = false; + if (_component) { + controller_is_component = FUnknownPtr (_component) != 0; + _component->terminate (); + } + + if (_controller && controller_is_component == false) { + _controller->terminate (); + } + _component = 0; + _controller = 0; +} + +bool +VST3PI::connect_components () +{ + if (!_component || !_controller) { + return false; + } + + FUnknownPtr componentCP (_component); + FUnknownPtr controllerCP (_controller); + + if (!componentCP || !controllerCP) { + return true; + } + + tresult res = componentCP->connect (this); + if (!(res == kResultOk || res == kNotImplemented)) { + return false; + } + + res = controllerCP->connect (this); + if (!(res == kResultOk || res == kNotImplemented)) { +#ifndef NDEBUG + std::cerr << "VST3: Cannot connect controller, ignored.\n"; +#endif + } + + return true; +} + +bool +VST3PI::disconnect_components () +{ + FUnknownPtr componentCP (_component); + FUnknownPtr controllerCP (_controller); + if (!componentCP || !controllerCP) { + return false; + } + + bool res = componentCP->disconnect (this); + res &= controllerCP->disconnect (this); + return res; +} + +tresult +VST3PI::connect (Vst::IConnectionPoint* other) +{ + return other ? kResultTrue : kInvalidArgument; +} + +tresult +VST3PI::disconnect (Vst::IConnectionPoint* other) +{ + return kResultTrue; +} + +tresult +VST3PI::notify (Vst::IMessage* msg) +{ +#ifndef NDEBUG + std::cerr << "VST3PI::notify\n"; +#endif + FUnknownPtr componentCP (_component); + FUnknownPtr controllerCP (_controller); + // XXX this bounces the message back.. + // we likely need a proxy here + if (componentCP) { + componentCP->notify (msg); + } + if (controllerCP) { + controllerCP->notify (msg); + } + return kResultTrue; +} + +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) + +#if SMTG_OS_LINUX + if (_run_loop && FUnknownPrivate::iidEqual (_iid, Linux::IRunLoop::iid)) { + *obj = _run_loop; + return kResultOk; + } +#endif + + *obj = nullptr; + return kNoInterface; +} + +tresult +VST3PI::restartComponent (int32 flags) +{ +#ifndef NDEBUG + printf ("VST3PI::restartComponent %x\n", flags); +#endif + if (flags & Vst::kReloadComponent) { + return kNotImplemented; + } + if (flags & Vst::kIoChanged) { + return kNotImplemented; + } + if (flags & Vst::kParamValuesChanged) { + update_shadow_data (); + } + if (flags & Vst::kLatencyChanged) { + _plugin_latency.reset (); + } + 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 (id, value, 0, true); + value = _controller->normalizedParamToPlain (id, value); + OnParameterChange (ValueChange, idx->second, v); /* 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; +} + +bool +VST3PI::deactivate () +{ + if (!_is_processing) { + return true; + } + if (_processor->setProcessing (false) != kResultOk) { + return false; + } + if (_component->setActive (false) != kResultOk) { + return false; + } + _is_processing = false; + return true; +} + +bool +VST3PI::activate () +{ + if (_is_processing) { + return true; + } + + if (_component->setActive (true) != kResultOk) { + return false; + } + + if (_processor->setProcessing (true) != kResultOk) { + return false; + } + _is_processing = true; + return true; +} + +bool +VST3PI::update_processor () +{ + bool was_active = _is_processing; + + if (!deactivate ()) { + return false; + } + + Vst::ProcessSetup setup; + setup.processMode = 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 (); +} + +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) { +#if 1 + if ((type == Vst::kMain && i != 0) || (type == Vst::kAux && i != 1)) { + /* For now allow we only support one main bus, and one aux-bus. + * Also an aux-bus by itself is currently N/A. + */ + continue; + } +#endif + + std::string bus_name = tchar_to_utf8 (bus.name); + bool is_sidechain = (type == Vst::kAux) && (dir == Vst::kInput); + + if (media == Vst::kEvent) { + /* MIDI Channel count -> 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 { + 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)); + } + n_channels += bus.channelCount; + } + } + } + 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 ? p.steps : 0; + 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; + } +} + +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 () const +{ + return _n_inputs + _n_aux_inputs; +} + +uint32_t +VST3PI::n_audio_outputs () const +{ + return _n_outputs + _n_aux_outputs; +} + +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); + return true; +} + +void +VST3PI::set_parameter (uint32_t p, float value, int32 sample_off) +{ + set_parameter_internal (index_to_id (p), value, sample_off, false); + _shadow_data[p] = value; + _update_ctrl[p] = true; +} + +bool +VST3PI::set_program (float value, int32 sample_off, bool normalized) +{ + if (_program_change_port.id == Vst::kNoParamId) { + return false; + } + Vst::ParamID id = _program_change_port.id; + + if (!normalized) { + value = _controller->plainParamToNormalized (id, value); + } + 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 false; + } + } + return true; +} + +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) { +#ifndef NDEBUG + 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; + } + } +} + +void +VST3PI::update_contoller_param () +{ + /* GUI thread */ + 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; + _controller->setParamNormalized (i->second, _shadow_data[i->first]); + } +} + +void +VST3PI::set_parameter_by_id (Vst::ParamID id, float value, int32 sample_off) +{ + set_parameter_internal (id, value, sample_off, true); + 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, bool normalized) +{ + int32 index; + if (!normalized) { + value = _controller->plainParamToNormalized (id, value); + } + _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; + _controller->setParamNormalized (id, _shadow_data[p]); // GUI thread only + } + return _controller->normalizedParamToPlain (id, _shadow_data[p]); +} + +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 = Vst::Event::kIsLive; + e.sampleOffset = ev.time(); + e.ppqPosition = _context.projectTimeMusic; + if (evoral_to_vst3 (e, ev, bus)) { + _input_events.addEvent (e); + } +} + +void +VST3PI::enable_io (std::vector const& ins, std::vector const& outs) +{ + if (_enabled_audio_in == ins && _enabled_audio_out == outs) { + return; + } + _enabled_audio_in = ins; + _enabled_audio_out = outs; + + assert (_enabled_audio_in.size () == n_audio_inputs ()); + assert (_enabled_audio_out.size () == n_audio_outputs ()); + + int32 n_bus_in = _component->getBusCount (Vst::kAudio, Vst::kInput); + int32 n_bus_out = _component->getBusCount (Vst::kAudio, Vst::kOutput); + + std::vector sa_in; + std::vector sa_out; + + bool enable = false; + Vst::SpeakerArrangement sa = 0; + + for (int i = 0; i < _n_inputs; ++i) { + if (ins[i]) { + enable = true; + } + sa |= (uint64_t)1 << i; + } + if (_n_inputs > 0) { + _component->activateBus (Vst::kAudio, Vst::kInput, 0, enable); + sa_in.push_back (sa); + } + + enable = false; + sa = 0; + for (int i = 0; i < _n_aux_inputs; ++i) { + if (ins[i + _n_inputs]) { + enable = true; + } + sa |= (uint64_t)1 << i; + } + if (_n_aux_inputs > 0) { + _component->activateBus (Vst::kAudio, Vst::kInput, 1, enable); + sa_in.push_back (sa); + } + + /* disable remaining input busses and set their speaker-count to zero */ + while (sa_in.size() < n_bus_in) { + _component->activateBus (Vst::kAudio, Vst::kInput, sa_in.size(), false); + sa_in.push_back (0); + } + + enable = false; + sa = 0; + for (int i = 0; i < _n_outputs; ++i) { + if (outs[i]) { + enable = true; + } + sa |= (uint64_t)1 << i; + } + + if (_n_outputs > 0) { + _component->activateBus (Vst::kAudio, Vst::kOutput, 0, enable); + sa_out.push_back (sa); + } + + enable = false; + sa = 0; + for (int i = 0; i < _n_aux_outputs; ++i) { + if (outs[i + _n_outputs]) { + enable = true; + } + sa |= (uint64_t)1 << i; + } + if (_n_aux_inputs > 0) { + _component->activateBus (Vst::kAudio, Vst::kOutput, 1, enable); + sa_out.push_back (sa); + } + + while (sa_out.size() < n_bus_out) { + _component->activateBus (Vst::kAudio, Vst::kOutput, sa_out.size(), false); + sa_out.push_back (0); + } + + _processor->setBusArrangements (&sa_in[0], sa_in.size(), &sa_out[0], sa_out.size()); + +#if 0 + 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); + std::cerr << "VST3: Input BusArrangements: " << i << " chan: " << cc << " bits: " << arr << "\n"; + } + } + 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); + std::cerr << "VST3: Output BusArrangements: " << i << " chan: " << cc << " bits: " << arr << "\n"; + } + } +#endif +} + +static int32 used_bus_count (int auxes, int inputs) +{ + if (auxes > 0 && inputs > 0) { + return 2; + } + if (auxes == 0 && inputs == 0) { + return 0; + } + return 1; +} + +void +VST3PI::process (float** ins, float** outs, uint32_t n_samples) +{ + Vst::AudioBusBuffers input[2]; // in-bus & aux-bus + Vst::AudioBusBuffers output[2]; + + Vst::ProcessData data; + data.numSamples = n_samples; + data.processMode = Vst::kRealtime; // or kOffline + data.symbolicSampleSize = Vst::kSample32; + data.numInputs = used_bus_count (_n_aux_inputs, _n_inputs); + data.numOutputs = used_bus_count (_n_aux_outputs, _n_outputs); + data.inputs = input; + data.outputs = output; + + data.processContext = &_context; + data.inputEvents = &_input_events; + data.outputEvents = &_output_events; + + data.inputParameterChanges = &_input_param_changes; + data.outputParameterChanges = &_output_param_changes; + + input[0].silenceFlags = 0; + input[0].numChannels = _n_inputs; + input[0].channelBuffers32 = ins; + + if (_n_aux_inputs > 0) { + input[1].silenceFlags = 0; + input[1].numChannels = _n_aux_inputs; + input[1].channelBuffers32 = &ins[_n_inputs]; + } + + output[0].silenceFlags = 0; + output[0].numChannels = _n_outputs; + output[0].channelBuffers32 = outs; + + if (_n_aux_outputs > 0) { + output[1].silenceFlags = 0; + output[1].numChannels = _n_outputs; + output[1].channelBuffers32 = &outs[_n_outputs]; + } + + /* and go */ + if (_processor->process (data) != kResultOk) { +#ifndef NDEBUG + std::cerr << "VST3: Process error\n"; // XXX +#endif + } + + /* 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; +}; + +} // Vst + +typedef std::vector ChunkEntryVector; + +} // 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 + ) + ) { +#ifndef NDEBUG + printf ("VST3PI::load_state: invalid header v%d s:%lld\n", version, list_offset); +#endif + return false; + } + + if (_fuid != FUID::fromTUID (class_id)) { +#ifndef NDEBUG + std::cerr << "VST3PI::load_state: class ID mismatch\n"; +#endif + 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; + } + 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); + } + + bool rv = true; + + /* 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))) { + tresult res = _component->setState(&stream); + + stream.seek (i->_offset, IBStream::kIBSeekSet, &seek_result); + tresult re2 = _controller->setComponentState(&stream); + + if (!(re2 == kResultOk || re2 == kNotImplemented || res == kResultOk || res == kNotImplemented)) { +#ifndef NDEBUG + std::cerr << "VST3: failed to restore component state\n"; +#endif + rv = false; + } + } + else if (is_equal_ID (i->_id, Vst::getChunkID (Vst::kControllerState))) { + tresult res = _controller->setState (&stream); + if (!(res == kResultOk || res == kNotImplemented)) { +#ifndef NDEBUG + std::cerr << "VST3: failed to restore controller state\n"; +#endif + rv = false; + } + } +#if 0 + else if (is_equal_ID (i->_id, Vst::getChunkID (Vst::kProgramData))) { + Vst::IUnitInfo* unitInfo = unit_info (); + printf ("VST3: ignored unsupported kProgramData.\n"); + // PresetFile::restoreProgramData + // RAMStream pgmstream (...) create substream + // unit_info->setUnitProgramData (unitProgramListID, programIndex, pgmstream) + } +#endif + else { +#ifndef NDEBUG + std::cerr << "VST3: ignored unsupported state chunk.\n"; +#endif + } + } + + if (rv) { + 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; +} + +/* **************************************************************************** + * GUI + */ + +IPlugView* +VST3PI::view () +{ + if (!_view) { + _view = _controller->createView (Vst::ViewType::kEditor); + if (!_view) { + _view = _controller->createView (0); + } + if (!_view) { + _view = FUnknownPtr (_controller); + } + if (_view) { + _view->setFrame (this); + } + } + return _view; +} + +void +VST3PI::close_view () +{ + if (!_view) { + return; + } + _view->removed (); + _view->setFrame (0); + _view = 0; +} + +#if SMTG_OS_LINUX +void +VST3PI::set_runloop (Linux::IRunLoop* run_loop) +{ + _run_loop = run_loop; +} +#endif + +tresult +VST3PI::resizeView (IPlugView* view, ViewRect* new_size) +{ + OnResizeView (new_size->getWidth (), new_size->getHeight ()); /* EMIT SIGNAL */ + return view->onSize (new_size); +}