Mercurial > hg > pub > prymula > com
diff DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoPluginVST3.cpp @ 3:84e66ea83026
DPF-Prymula-audioplugins-0.231015-2
author | prymula <prymula76@outlook.com> |
---|---|
date | Mon, 16 Oct 2023 21:53:34 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoPluginVST3.cpp Mon Oct 16 21:53:34 2023 +0200 @@ -0,0 +1,4979 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2023 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* TODO items: + * == parameters + * - test parameter triggers + * - have parameter outputs host-provided UI working in at least 1 host + * - parameter groups via unit ids + * - test parameter changes from DSP (aka requestParameterValueChange) + * - implement getParameterNormalized/setParameterNormalized for MIDI CC params ? + * - float to int safe casting + * - verify that latency changes works (with and without DPF_VST3_USES_SEPARATE_CONTROLLER) + * == MIDI + * - MIDI CC changes (need to store value to give to the host?) + * - MIDI program changes + * - MIDI sysex + * == BUSES + * - routing info, do we care? + * == CV + * - cv scaling to -1/+1 + * - test in at least 1 host + * == INFO + * - set factory email (needs new DPF API, useful for LV2 as well) + * - do something with set_io_mode? + */ + +#include "DistrhoPluginInternal.hpp" +#include "../DistrhoPluginUtils.hpp" +#include "../extra/ScopedPointer.hpp" + +#define DPF_VST3_MAX_BUFFER_SIZE 32768 +#define DPF_VST3_MAX_SAMPLE_RATE 384000 +#define DPF_VST3_MAX_LATENCY DPF_VST3_MAX_SAMPLE_RATE * 10 + +#if DISTRHO_PLUGIN_HAS_UI +# include "../extra/RingBuffer.hpp" +#endif + +#include "travesty/audio_processor.h" +#include "travesty/component.h" +#include "travesty/edit_controller.h" +#include "travesty/factory.h" +#include "travesty/host.h" + +#include <map> +#include <string> +#include <vector> + +START_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- + +#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT +static constexpr const writeMidiFunc writeMidiCallback = nullptr; +#endif +#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST +static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; +#endif + +typedef std::map<const String, String> StringMap; + +// -------------------------------------------------------------------------------------------------------------------- +// custom v3_tuid compatible type + +typedef uint32_t dpf_tuid[4]; +#ifdef DISTRHO_PROPER_CPP11_SUPPORT +static_assert(sizeof(v3_tuid) == sizeof(dpf_tuid), "uid size mismatch"); +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// custom, constant uids related to DPF + +static constexpr const uint32_t dpf_id_entry = d_cconst('D', 'P', 'F', ' '); +static constexpr const uint32_t dpf_id_clas = d_cconst('c', 'l', 'a', 's'); +static constexpr const uint32_t dpf_id_comp = d_cconst('c', 'o', 'm', 'p'); +static constexpr const uint32_t dpf_id_ctrl = d_cconst('c', 't', 'r', 'l'); +static constexpr const uint32_t dpf_id_proc = d_cconst('p', 'r', 'o', 'c'); +static constexpr const uint32_t dpf_id_view = d_cconst('v', 'i', 'e', 'w'); + +// -------------------------------------------------------------------------------------------------------------------- +// plugin specific uids (values are filled in during plugin init) + +static dpf_tuid dpf_tuid_class = { dpf_id_entry, dpf_id_clas, 0, 0 }; +static dpf_tuid dpf_tuid_component = { dpf_id_entry, dpf_id_comp, 0, 0 }; +static dpf_tuid dpf_tuid_controller = { dpf_id_entry, dpf_id_ctrl, 0, 0 }; +static dpf_tuid dpf_tuid_processor = { dpf_id_entry, dpf_id_proc, 0, 0 }; +static dpf_tuid dpf_tuid_view = { dpf_id_entry, dpf_id_view, 0, 0 }; + +// -------------------------------------------------------------------------------------------------------------------- +// Utility functions + +const char* tuid2str(const v3_tuid iid) +{ + static constexpr const struct { + v3_tuid iid; + const char* name; + } extra_known_iids[] = { + { V3_ID(0x00000000,0x00000000,0x00000000,0x00000000), "(nil)" }, + // edit-controller + { V3_ID(0xF040B4B3,0xA36045EC,0xABCDC045,0xB4D5A2CC), "{v3_component_handler2|NOT}" }, + { V3_ID(0x7F4EFE59,0xF3204967,0xAC27A3AE,0xAFB63038), "{v3_edit_controller2|NOT}" }, + { V3_ID(0x067D02C1,0x5B4E274D,0xA92D90FD,0x6EAF7240), "{v3_component_handler_bus_activation|NOT}" }, + { V3_ID(0xC1271208,0x70594098,0xB9DD34B3,0x6BB0195E), "{v3_edit_controller_host_editing|NOT}" }, + { V3_ID(0xB7F8F859,0x41234872,0x91169581,0x4F3721A3), "{v3_edit_controller_note_expression_controller|NOT}" }, + // units + { V3_ID(0x8683B01F,0x7B354F70,0xA2651DEC,0x353AF4FF), "{v3_program_list_data|NOT}" }, + { V3_ID(0x6C389611,0xD391455D,0xB870B833,0x94A0EFDD), "{v3_unit_data|NOT}" }, + { V3_ID(0x4B5147F8,0x4654486B,0x8DAB30BA,0x163A3C56), "{v3_unit_handler|NOT}" }, + { V3_ID(0xF89F8CDF,0x699E4BA5,0x96AAC9A4,0x81452B01), "{v3_unit_handler2|NOT}" }, + { V3_ID(0x3D4BD6B5,0x913A4FD2,0xA886E768,0xA5EB92C1), "{v3_unit_info|NOT}" }, + // misc + { V3_ID(0x309ECE78,0xEB7D4FAE,0x8B2225D9,0x09FD08B6), "{v3_audio_presentation_latency|NOT}" }, + { V3_ID(0xB4E8287F,0x1BB346AA,0x83A46667,0x68937BAB), "{v3_automation_state|NOT}" }, + { V3_ID(0x0F194781,0x8D984ADA,0xBBA0C1EF,0xC011D8D0), "{v3_info_listener|NOT}" }, + { V3_ID(0x6D21E1DC,0x91199D4B,0xA2A02FEF,0x6C1AE55C), "{v3_parameter_function_name|NOT}" }, + { V3_ID(0x8AE54FDA,0xE93046B9,0xA28555BC,0xDC98E21E), "{v3_prefetchable_support|NOT}" }, + { V3_ID(0xA81A0471,0x48C34DC4,0xAC30C9E1,0x3C8393D5), "{v3_xml_representation_stream|NOT}" }, + /* + // seen in the wild but unknown, related to component + { V3_ID(0x6548D671,0x997A4EA5,0x9B336A6F,0xB3E93B50), "{v3_|NOT}" }, + { V3_ID(0xC2B7896B,0x069844D5,0x8F06E937,0x33A35FF7), "{v3_|NOT}" }, + { V3_ID(0xE123DE93,0xE0F642A4,0xAE53867E,0x53F059EE), "{v3_|NOT}" }, + { V3_ID(0x83850D7B,0xC12011D8,0xA143000A,0x959B31C6), "{v3_|NOT}" }, + { V3_ID(0x9598D418,0xA00448AC,0x9C6D8248,0x065B2E5C), "{v3_|NOT}" }, + { V3_ID(0xBD386132,0x45174BAD,0xA324390B,0xFD297506), "{v3_|NOT}" }, + { V3_ID(0xD7296A84,0x23B1419C,0xAAD0FAA3,0x53BB16B7), "{v3_|NOT}" }, + { V3_ID(0x181A0AF6,0xA10947BA,0x8A6F7C7C,0x3FF37129), "{v3_|NOT}" }, + { V3_ID(0xC2B7896B,0x69A844D5,0x8F06E937,0x33A35FF7), "{v3_|NOT}" }, + // seen in the wild but unknown, related to edit controller + { V3_ID(0x1F2F76D3,0xBFFB4B96,0xB99527A5,0x5EBCCEF4), "{v3_|NOT}" }, + { V3_ID(0x6B2449CC,0x419740B5,0xAB3C79DA,0xC5FE5C86), "{v3_|NOT}" }, + { V3_ID(0x67800560,0x5E784D90,0xB97BAB4C,0x8DC5BAA3), "{v3_|NOT}" }, + { V3_ID(0xDB51DA00,0x8FD5416D,0xB84894D8,0x7FDE73E4), "{v3_|NOT}" }, + { V3_ID(0xE90FC54F,0x76F24235,0x8AF8BD15,0x68C663D6), "{v3_|NOT}" }, + { V3_ID(0x07938E89,0xBA0D4CA8,0x8C7286AB,0xA9DDA95B), "{v3_|NOT}" }, + { V3_ID(0x42879094,0xA2F145ED,0xAC90E82A,0x99458870), "{v3_|NOT}" }, + { V3_ID(0xC3B17BC0,0x2C174494,0x80293402,0xFBC4BBF8), "{v3_|NOT}" }, + { V3_ID(0x31E29A7A,0xE55043AD,0x8B95B9B8,0xDA1FBE1E), "{v3_|NOT}" }, + { V3_ID(0x8E3C292C,0x95924F9D,0xB2590B1E,0x100E4198), "{v3_|NOT}" }, + { V3_ID(0x50553FD9,0x1D2C4C24,0xB410F484,0xC5FB9F3F), "{v3_|NOT}" }, + { V3_ID(0xF185556C,0x5EE24FC7,0x92F28754,0xB7759EA8), "{v3_|NOT}" }, + { V3_ID(0xD2CE9317,0xF24942C9,0x9742E82D,0xB10CCC52), "{v3_|NOT}" }, + { V3_ID(0xDA57E6D1,0x1F3242D1,0xAD9C1A82,0xFDB95695), "{v3_|NOT}" }, + { V3_ID(0x3ABDFC3E,0x4B964A66,0xFCD86F10,0x0D554023), "{v3_|NOT}" }, + // seen in the wild but unknown, related to view + { V3_ID(0xAA3E50FF,0xB78840EE,0xADCD48E8,0x094CEDB7), "{v3_|NOT}" }, + { V3_ID(0x2CAE14DB,0x4DE04C6E,0x8BD2E611,0x1B31A9C2), "{v3_|NOT}" }, + { V3_ID(0xD868D61D,0x20F445F4,0x947D069E,0xC811D1E4), "{v3_|NOT}" }, + { V3_ID(0xEE49E3CA,0x6FCB44FB,0xAEBEE6C3,0x48625122), "{v3_|NOT}" }, + */ + }; + + if (v3_tuid_match(iid, v3_audio_processor_iid)) + return "{v3_audio_processor}"; + if (v3_tuid_match(iid, v3_attribute_list_iid)) + return "{v3_attribute_list_iid}"; + if (v3_tuid_match(iid, v3_bstream_iid)) + return "{v3_bstream}"; + if (v3_tuid_match(iid, v3_component_iid)) + return "{v3_component}"; + if (v3_tuid_match(iid, v3_component_handler_iid)) + return "{v3_component_handler}"; + if (v3_tuid_match(iid, v3_connection_point_iid)) + return "{v3_connection_point_iid}"; + if (v3_tuid_match(iid, v3_edit_controller_iid)) + return "{v3_edit_controller}"; + if (v3_tuid_match(iid, v3_event_handler_iid)) + return "{v3_event_handler_iid}"; + if (v3_tuid_match(iid, v3_event_list_iid)) + return "{v3_event_list}"; + if (v3_tuid_match(iid, v3_funknown_iid)) + return "{v3_funknown}"; + if (v3_tuid_match(iid, v3_host_application_iid)) + return "{v3_host_application_iid}"; + if (v3_tuid_match(iid, v3_message_iid)) + return "{v3_message_iid}"; + if (v3_tuid_match(iid, v3_midi_mapping_iid)) + return "{v3_midi_mapping_iid}"; + if (v3_tuid_match(iid, v3_param_value_queue_iid)) + return "{v3_param_value_queue}"; + if (v3_tuid_match(iid, v3_param_changes_iid)) + return "{v3_param_changes}"; + if (v3_tuid_match(iid, v3_plugin_base_iid)) + return "{v3_plugin_base}"; + if (v3_tuid_match(iid, v3_plugin_factory_iid)) + return "{v3_plugin_factory}"; + if (v3_tuid_match(iid, v3_plugin_factory_2_iid)) + return "{v3_plugin_factory_2}"; + if (v3_tuid_match(iid, v3_plugin_factory_3_iid)) + return "{v3_plugin_factory_3}"; + if (v3_tuid_match(iid, v3_plugin_frame_iid)) + return "{v3_plugin_frame}"; + if (v3_tuid_match(iid, v3_plugin_view_iid)) + return "{v3_plugin_view}"; + if (v3_tuid_match(iid, v3_plugin_view_content_scale_iid)) + return "{v3_plugin_view_content_scale_iid}"; + if (v3_tuid_match(iid, v3_plugin_view_parameter_finder_iid)) + return "{v3_plugin_view_parameter_finder}"; + if (v3_tuid_match(iid, v3_process_context_requirements_iid)) + return "{v3_process_context_requirements}"; + if (v3_tuid_match(iid, v3_run_loop_iid)) + return "{v3_run_loop_iid}"; + if (v3_tuid_match(iid, v3_timer_handler_iid)) + return "{v3_timer_handler_iid}"; + + if (std::memcmp(iid, dpf_tuid_class, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_class}"; + if (std::memcmp(iid, dpf_tuid_component, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_component}"; + if (std::memcmp(iid, dpf_tuid_controller, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_controller}"; + if (std::memcmp(iid, dpf_tuid_processor, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_processor}"; + if (std::memcmp(iid, dpf_tuid_view, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_view}"; + + for (size_t i=0; i<ARRAY_SIZE(extra_known_iids); ++i) + { + if (v3_tuid_match(iid, extra_known_iids[i].iid)) + return extra_known_iids[i].name; + } + + static char buf[46]; + std::snprintf(buf, sizeof(buf), "{0x%08X,0x%08X,0x%08X,0x%08X}", + (uint32_t)d_cconst(iid[ 0], iid[ 1], iid[ 2], iid[ 3]), + (uint32_t)d_cconst(iid[ 4], iid[ 5], iid[ 6], iid[ 7]), + (uint32_t)d_cconst(iid[ 8], iid[ 9], iid[10], iid[11]), + (uint32_t)d_cconst(iid[12], iid[13], iid[14], iid[15])); + return buf; +} + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_plugin_view_create (implemented on UI side) + +v3_plugin_view** dpf_plugin_view_create(v3_host_application** host, void* instancePointer, double sampleRate); + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * VST3 DSP class. + * + * All the dynamic things from VST3 get implemented here, free of complex low-level VST3 pointer things. + * This class is created during the "initialize" component event, and destroyed during "terminate". + * + * The low-level VST3 stuff comes after. + */ +class PluginVst3 +{ + /* Buses: count possible buses we can provide to the host, in case they are not yet defined by the developer. + * These values are only used if port groups aren't set. + * + * When port groups are not in use: + * - 1 bus is provided for the main audio (if there is any) + * - 1 for sidechain + * - 1 for each cv port + * So basically: + * Main audio is used as first bus, if available. + * Then sidechain, also if available. + * And finally each CV port individually. + * + * MIDI will have a single bus, nothing special there. + */ + struct BusInfo { + uint8_t audio; // either 0 or 1 + uint8_t sidechain; // either 0 or 1 + uint32_t groups; + uint32_t audioPorts; + uint32_t sidechainPorts; + uint32_t groupPorts; + uint32_t cvPorts; + + BusInfo() + : audio(0), + sidechain(0), + groups(0), + audioPorts(0), + sidechainPorts(0), + groupPorts(0), + cvPorts(0) {} + } inputBuses, outputBuses; + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + /* Handy class for storing and sorting VST3 events and MIDI CC parameters. + * It will only store events for which a MIDI conversion is possible. + */ + struct InputEventList { + enum Type { + NoteOn, + NoteOff, + SysexData, + PolyPressure, + CC_Normal, + CC_ChannelPressure, + CC_Pitchbend, + UI_MIDI // event from UI + }; + struct InputEventStorage { + Type type; + union { + v3_event_note_on noteOn; + v3_event_note_off noteOff; + v3_event_data sysexData; + v3_event_poly_pressure polyPressure; + uint8_t midi[3]; + }; + } eventListStorage[kMaxMidiEvents]; + + struct InputEvent { + int32_t sampleOffset; + const InputEventStorage* storage; + InputEvent* next; + } eventList[kMaxMidiEvents]; + + uint16_t numUsed; + int32_t firstSampleOffset; + int32_t lastSampleOffset; + InputEvent* firstEvent; + InputEvent* lastEvent; + + void init() + { + numUsed = 0; + firstSampleOffset = lastSampleOffset = 0; + firstEvent = nullptr; + } + + uint32_t convert(MidiEvent midiEvents[kMaxMidiEvents]) const noexcept + { + uint32_t count = 0; + + for (const InputEvent* event = firstEvent; event != nullptr; event = event->next) + { + MidiEvent& midiEvent(midiEvents[count++]); + midiEvent.frame = event->sampleOffset; + + const InputEventStorage& eventStorage(*event->storage); + + switch (eventStorage.type) + { + case NoteOn: + midiEvent.size = 3; + midiEvent.data[0] = 0x90 | (eventStorage.noteOn.channel & 0xf); + midiEvent.data[1] = eventStorage.noteOn.pitch; + midiEvent.data[2] = std::max(0, std::min(127, (int)(eventStorage.noteOn.velocity * 127))); + midiEvent.data[3] = 0; + break; + case NoteOff: + midiEvent.size = 3; + midiEvent.data[0] = 0x80 | (eventStorage.noteOff.channel & 0xf); + midiEvent.data[1] = eventStorage.noteOff.pitch; + midiEvent.data[2] = std::max(0, std::min(127, (int)(eventStorage.noteOff.velocity * 127))); + midiEvent.data[3] = 0; + break; + /* TODO + case SysexData: + break; + */ + case PolyPressure: + midiEvent.size = 3; + midiEvent.data[0] = 0xA0 | (eventStorage.polyPressure.channel & 0xf); + midiEvent.data[1] = eventStorage.polyPressure.pitch; + midiEvent.data[2] = std::max(0, std::min(127, (int)(eventStorage.polyPressure.pressure * 127))); + midiEvent.data[3] = 0; + break; + case CC_Normal: + midiEvent.size = 3; + midiEvent.data[0] = 0xB0 | (eventStorage.midi[0] & 0xf); + midiEvent.data[1] = eventStorage.midi[1]; + midiEvent.data[2] = eventStorage.midi[2]; + break; + case CC_ChannelPressure: + midiEvent.size = 2; + midiEvent.data[0] = 0xD0 | (eventStorage.midi[0] & 0xf); + midiEvent.data[1] = eventStorage.midi[1]; + midiEvent.data[2] = 0; + break; + case CC_Pitchbend: + midiEvent.size = 3; + midiEvent.data[0] = 0xE0 | (eventStorage.midi[0] & 0xf); + midiEvent.data[1] = eventStorage.midi[1]; + midiEvent.data[2] = eventStorage.midi[2]; + break; + case UI_MIDI: + midiEvent.size = 3; + midiEvent.data[0] = eventStorage.midi[0]; + midiEvent.data[1] = eventStorage.midi[1]; + midiEvent.data[2] = eventStorage.midi[2]; + break; + default: + midiEvent.size = 0; + break; + } + } + + return count; + } + + bool appendEvent(const v3_event& event) noexcept + { + // only save events that can be converted directly into MIDI + switch (event.type) + { + case V3_EVENT_NOTE_ON: + case V3_EVENT_NOTE_OFF: + // case V3_EVENT_DATA: + case V3_EVENT_POLY_PRESSURE: + break; + default: + return false; + } + + InputEventStorage& eventStorage(eventListStorage[numUsed]); + + switch (event.type) + { + case V3_EVENT_NOTE_ON: + eventStorage.type = NoteOn; + eventStorage.noteOn = event.note_on; + break; + case V3_EVENT_NOTE_OFF: + eventStorage.type = NoteOff; + eventStorage.noteOff = event.note_off; + break; + case V3_EVENT_DATA: + eventStorage.type = SysexData; + eventStorage.sysexData = event.data; + break; + case V3_EVENT_POLY_PRESSURE: + eventStorage.type = PolyPressure; + eventStorage.polyPressure = event.poly_pressure; + break; + default: + return false; + } + + eventList[numUsed].sampleOffset = event.sample_offset; + eventList[numUsed].storage = &eventStorage; + + return placeSorted(event.sample_offset); + } + + bool appendCC(const int32_t sampleOffset, v3_param_id paramId, const double normalized) noexcept + { + InputEventStorage& eventStorage(eventListStorage[numUsed]); + + paramId -= kVst3InternalParameterMidiCC_start; + + const uint8_t cc = paramId % 130; + + switch (cc) + { + case 128: + eventStorage.type = CC_ChannelPressure; + eventStorage.midi[1] = std::max(0, std::min(127, (int)(normalized * 127))); + eventStorage.midi[2] = 0; + break; + case 129: + eventStorage.type = CC_Pitchbend; + eventStorage.midi[1] = std::max(0, std::min(16384, (int)(normalized * 16384))) & 0x7f; + eventStorage.midi[2] = std::max(0, std::min(16384, (int)(normalized * 16384))) >> 7; + break; + default: + eventStorage.type = CC_Normal; + eventStorage.midi[1] = cc; + eventStorage.midi[2] = std::max(0, std::min(127, (int)(normalized * 127))); + break; + } + + eventStorage.midi[0] = paramId / 130; + + eventList[numUsed].sampleOffset = sampleOffset; + eventList[numUsed].storage = &eventStorage; + + return placeSorted(sampleOffset); + } + + #if DISTRHO_PLUGIN_HAS_UI + // NOTE always runs first + bool appendFromUI(const uint8_t midiData[3]) + { + InputEventStorage& eventStorage(eventListStorage[numUsed]); + + eventStorage.type = UI_MIDI; + std::memcpy(eventStorage.midi, midiData, sizeof(uint8_t)*3); + + InputEvent* const event = &eventList[numUsed]; + + event->sampleOffset = 0; + event->storage = &eventStorage; + event->next = nullptr; + + if (numUsed == 0) + { + firstEvent = lastEvent = event; + } + else + { + lastEvent->next = event; + lastEvent = event; + } + + return ++numUsed == kMaxMidiEvents; + } + #endif + + private: + bool placeSorted(const int32_t sampleOffset) noexcept + { + InputEvent* const event = &eventList[numUsed]; + + // initialize + if (numUsed == 0) + { + firstSampleOffset = lastSampleOffset = sampleOffset; + firstEvent = lastEvent = event; + event->next = nullptr; + } + // push to the back + else if (sampleOffset >= lastSampleOffset) + { + lastSampleOffset = sampleOffset; + lastEvent->next = event; + lastEvent = event; + event->next = nullptr; + } + // push to the front + else if (sampleOffset < firstSampleOffset) + { + firstSampleOffset = sampleOffset; + event->next = firstEvent; + firstEvent = event; + } + // find place in between events + else + { + // keep reference out of the loop so we can check validity afterwards + InputEvent* event2 = firstEvent; + + // iterate all events + for (; event2 != nullptr; event2 = event2->next) + { + // if offset is higher than iterated event, stop and insert in-between + if (sampleOffset > event2->sampleOffset) + break; + + // if offset matches, find the last event with the same offset so we can push after it + if (sampleOffset == event2->sampleOffset) + { + event2 = event2->next; + for (; event2 != nullptr && sampleOffset == event2->sampleOffset; event2 = event2->next) {} + break; + } + } + + DISTRHO_SAFE_ASSERT_RETURN(event2 != nullptr, true); + + event->next = event2->next; + event2->next = event; + } + + return ++numUsed == kMaxMidiEvents; + } + } inputEventList; + #endif // DISTRHO_PLUGIN_WANT_MIDI_INPUT + +public: + PluginVst3(v3_host_application** const host, const bool isComponent) + : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr), + fComponentHandler(nullptr), + #if DISTRHO_PLUGIN_HAS_UI + #if DPF_VST3_USES_SEPARATE_CONTROLLER + fConnectionFromCompToCtrl(nullptr), + #endif + fConnectionFromCtrlToView(nullptr), + fHostApplication(host), + #endif + fParameterCount(fPlugin.getParameterCount()), + fVst3ParameterCount(fParameterCount + kVst3InternalParameterCount), + fCachedParameterValues(nullptr), + fDummyAudioBuffer(nullptr), + fParameterValuesChangedDuringProcessing(nullptr) + #if DPF_VST3_USES_SEPARATE_CONTROLLER + , fIsComponent(isComponent) + #endif + #if DISTRHO_PLUGIN_HAS_UI + , fParameterValueChangesForUI(nullptr) + , fConnectedToUI(false) + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + , fLastKnownLatency(fPlugin.getLatency()) + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + , fHostEventOutputHandle(nullptr) + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + , fCurrentProgram(0) + , fProgramCountMinusOne(fPlugin.getProgramCount()-1) + #endif + { + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + DISTRHO_SAFE_ASSERT(isComponent); + #endif + + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + std::memset(fEnabledInputs, 0, sizeof(fEnabledInputs)); + fillInBusInfoDetails<true>(); + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + std::memset(fEnabledOutputs, 0, sizeof(fEnabledOutputs)); + fillInBusInfoDetails<false>(); + #endif + + if (const uint32_t extraParameterCount = fParameterCount + kVst3InternalParameterBaseCount) + { + fCachedParameterValues = new float[extraParameterCount]; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + fCachedParameterValues[kVst3InternalParameterBufferSize] = fPlugin.getBufferSize(); + fCachedParameterValues[kVst3InternalParameterSampleRate] = fPlugin.getSampleRate(); + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + fCachedParameterValues[kVst3InternalParameterLatency] = fLastKnownLatency; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + fCachedParameterValues[kVst3InternalParameterProgram] = 0.0f; + #endif + + for (uint32_t i=0; i < fParameterCount; ++i) + fCachedParameterValues[kVst3InternalParameterBaseCount + i] = fPlugin.getParameterDefault(i); + + fParameterValuesChangedDuringProcessing = new bool[extraParameterCount]; + std::memset(fParameterValuesChangedDuringProcessing, 0, sizeof(bool)*extraParameterCount); + + #if DISTRHO_PLUGIN_HAS_UI + fParameterValueChangesForUI = new bool[extraParameterCount]; + std::memset(fParameterValueChangesForUI, 0, sizeof(bool)*extraParameterCount); + #endif + } + + #if DISTRHO_PLUGIN_WANT_STATE + for (uint32_t i=0, count=fPlugin.getStateCount(); i<count; ++i) + { + const String& dkey(fPlugin.getStateKey(i)); + fStateMap[dkey] = fPlugin.getStateDefaultValue(i); + } + #endif + + #if !DISTRHO_PLUGIN_HAS_UI + // unused + return; (void)host; + #endif + } + + ~PluginVst3() + { + if (fCachedParameterValues != nullptr) + { + delete[] fCachedParameterValues; + fCachedParameterValues = nullptr; + } + + if (fDummyAudioBuffer != nullptr) + { + delete[] fDummyAudioBuffer; + fDummyAudioBuffer = nullptr; + } + + if (fParameterValuesChangedDuringProcessing != nullptr) + { + delete[] fParameterValuesChangedDuringProcessing; + fParameterValuesChangedDuringProcessing = nullptr; + } + + #if DISTRHO_PLUGIN_HAS_UI + if (fParameterValueChangesForUI != nullptr) + { + delete[] fParameterValueChangesForUI; + fParameterValueChangesForUI = nullptr; + } + #endif + } + + // ---------------------------------------------------------------------------------------------------------------- + // utilities and common code + + double _getNormalizedParameterValue(const uint32_t index, const double plain) + { + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + return ranges.getFixedAndNormalizedValue(plain); + } + + void _setNormalizedPluginParameterValue(const uint32_t index, const double normalized) + { + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + const uint32_t hints = fPlugin.getParameterHints(index); + float value = ranges.getUnnormalizedValue(normalized); + + // convert as needed as check for changes + if (hints & kParameterIsBoolean) + { + const float midRange = ranges.min + (ranges.max - ranges.min) / 2.f; + const bool isHigh = value > midRange; + + if (isHigh == (fCachedParameterValues[kVst3InternalParameterBaseCount + index] > midRange)) + return; + + value = isHigh ? ranges.max : ranges.min; + } + else if (hints & kParameterIsInteger) + { + const int ivalue = static_cast<int>(std::round(value)); + + if (static_cast<int>(fCachedParameterValues[kVst3InternalParameterBaseCount + index]) == ivalue) + return; + + value = ivalue; + } + else + { + // deal with low resolution of some hosts, which convert double to float internally and lose precision + if (std::abs(ranges.getNormalizedValue(static_cast<double>(fCachedParameterValues[kVst3InternalParameterBaseCount + index])) - normalized) < 0.0000001) + return; + } + + fCachedParameterValues[kVst3InternalParameterBaseCount + index] = value; + + #if DISTRHO_PLUGIN_HAS_UI + #if DPF_VST3_USES_SEPARATE_CONTROLLER + if (!fIsComponent) + #endif + { + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + index] = true; + } + #endif + + if (!fPlugin.isParameterOutputOrTrigger(index)) + fPlugin.setParameterValue(index, value); + } + + // ---------------------------------------------------------------------------------------------------------------- + // stuff called for UI creation + + void* getInstancePointer() const noexcept + { + return fPlugin.getInstancePointer(); + } + + double getSampleRate() const noexcept + { + return fPlugin.getSampleRate(); + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_component interface calls + + int32_t getBusCount(const int32_t mediaType, const int32_t busDirection) const noexcept + { + switch (mediaType) + { + case V3_AUDIO: + if (busDirection == V3_INPUT) + return inputBuses.audio + inputBuses.sidechain + inputBuses.groups + inputBuses.cvPorts; + if (busDirection == V3_OUTPUT) + return outputBuses.audio + outputBuses.sidechain + outputBuses.groups + outputBuses.cvPorts; + break; + case V3_EVENT: + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (busDirection == V3_INPUT) + return 1; + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + if (busDirection == V3_OUTPUT) + return 1; + #endif + break; + } + + return 0; + } + + v3_result getBusInfo(const int32_t mediaType, + const int32_t busDirection, + const int32_t busIndex, + v3_bus_info* const info) const + { + DISTRHO_SAFE_ASSERT_INT_RETURN(mediaType == V3_AUDIO || mediaType == V3_EVENT, mediaType, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_INT_RETURN(busDirection == V3_INPUT || busDirection == V3_OUTPUT, busDirection, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_INT_RETURN(busIndex >= 0, busIndex, V3_INVALID_ARG); + + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 || DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + const uint32_t busId = static_cast<uint32_t>(busIndex); + #endif + + if (mediaType == V3_AUDIO) + { + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + if (busDirection == V3_INPUT) + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + return getAudioBusInfo<true>(busId, info); + #else + d_stderr("invalid input bus %d", busId); + return V3_INVALID_ARG; + #endif // DISTRHO_PLUGIN_NUM_INPUTS + } + else + { + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + return getAudioBusInfo<false>(busId, info); + #else + d_stderr("invalid output bus %d", busId); + return V3_INVALID_ARG; + #endif // DISTRHO_PLUGIN_NUM_OUTPUTS + } + #else + d_stderr("invalid bus, line %d", __LINE__); + return V3_INVALID_ARG; + #endif // DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS + } + else + { + if (busDirection == V3_INPUT) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + DISTRHO_SAFE_ASSERT_RETURN(busId == 0, V3_INVALID_ARG); + #else + d_stderr("invalid bus, line %d", __LINE__); + return V3_INVALID_ARG; + #endif + } + else + { + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + DISTRHO_SAFE_ASSERT_RETURN(busId == 0, V3_INVALID_ARG); + #else + d_stderr("invalid bus, line %d", __LINE__); + return V3_INVALID_ARG; + #endif + } + info->media_type = V3_EVENT; + info->direction = busDirection; + info->channel_count = 1; + strncpy_utf16(info->bus_name, busDirection == V3_INPUT ? "Event/MIDI Input" + : "Event/MIDI Output", 128); + info->bus_type = V3_MAIN; + info->flags = V3_DEFAULT_ACTIVE; + return V3_OK; + } + } + + v3_result getRoutingInfo(v3_routing_info*, v3_routing_info*) + { + /* + output->media_type = V3_AUDIO; + output->bus_idx = 0; + output->channel = -1; + d_stdout("getRoutingInfo %s %d %d", + v3_media_type_str(input->media_type), input->bus_idx, input->channel); + */ + return V3_NOT_IMPLEMENTED; + } + + v3_result activateBus(const int32_t mediaType, + const int32_t busDirection, + const int32_t busIndex, + const bool state) noexcept + { + DISTRHO_SAFE_ASSERT_INT_RETURN(busDirection == V3_INPUT || busDirection == V3_OUTPUT, busDirection, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_INT_RETURN(busIndex >= 0, busIndex, V3_INVALID_ARG); + + if (mediaType == V3_AUDIO) + { + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + const uint32_t busId = static_cast<uint32_t>(busIndex); + + if (busDirection == V3_INPUT) + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(true, i)); + + if (port.busId == busId) + fEnabledInputs[i] = state; + } + #endif + } + else + { + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(false, i)); + + if (port.busId == busId) + fEnabledOutputs[i] = state; + } + #endif + } + #endif + } + + return V3_OK; + + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS == 0 + // unused + (void)state; + #endif + } + + v3_result setActive(const bool active) + { + if (active) + fPlugin.activate(); + else + fPlugin.deactivateIfNeeded(); + + return V3_OK; + } + + /* state: we pack pairs of key-value strings each separated by a null/zero byte. + * current-program comes first, then dpf key/value states and then parameters. + * parameters are simply converted to/from strings and floats. + * the parameter symbol is used as the "key", so it is possible to reorder them or even remove and add safely. + * there are markers for begin and end of state and parameters, so they never conflict. + */ + v3_result setState(v3_bstream** const stream) + { + #if DISTRHO_PLUGIN_HAS_UI + const bool connectedToUI = fConnectionFromCtrlToView != nullptr && fConnectedToUI; + #endif + String key, value; + bool hasValue = false; + bool fillingKey = true; // if filling key or value + char queryingType = 'i'; // can be 'n', 's' or 'p' (none, states, parameters) + + char buffer[512], orig; + buffer[sizeof(buffer)-1] = '\xff'; + v3_result res; + + for (int32_t terminated = 0, read; terminated == 0;) + { + read = -1; + res = v3_cpp_obj(stream)->read(stream, buffer, sizeof(buffer)-1, &read); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(read > 0, read, V3_INTERNAL_ERR); + + if (read == 0) + return V3_OK; + + for (int32_t i = 0; i < read; ++i) + { + // found terminator, stop here + if (buffer[i] == '\xfe') + { + terminated = 1; + break; + } + + // store character at read position + orig = buffer[read]; + + // place null character to create valid string + buffer[read] = '\0'; + + // append to temporary vars + if (fillingKey) + { + key += buffer + i; + } + else + { + value += buffer + i; + hasValue = true; + } + + // increase buffer offset by length of string + i += std::strlen(buffer + i); + + // restore read character + buffer[read] = orig; + + // if buffer offset points to null, we found the end of a string, lets check + if (buffer[i] == '\0') + { + // special keys + if (key == "__dpf_state_begin__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n', + queryingType, V3_INTERNAL_ERR); + queryingType = 's'; + key.clear(); + value.clear(); + hasValue = false; + continue; + } + if (key == "__dpf_state_end__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 's', queryingType, V3_INTERNAL_ERR); + queryingType = 'n'; + key.clear(); + value.clear(); + hasValue = false; + continue; + } + if (key == "__dpf_parameters_begin__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n', + queryingType, V3_INTERNAL_ERR); + queryingType = 'p'; + key.clear(); + value.clear(); + hasValue = false; + continue; + } + if (key == "__dpf_parameters_end__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'p', queryingType, V3_INTERNAL_ERR); + queryingType = 'x'; + key.clear(); + value.clear(); + hasValue = false; + continue; + } + + // no special key, swap between reading real key and value + fillingKey = !fillingKey; + + // if there is no value yet keep reading until we have one + if (! hasValue) + continue; + + if (key == "__dpf_program__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i', queryingType, V3_INTERNAL_ERR); + queryingType = 'n'; + + d_debug("found program '%s'", value.buffer()); + + #if DISTRHO_PLUGIN_WANT_PROGRAMS + const int program = std::atoi(value.buffer()); + DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0); + + fCurrentProgram = static_cast<uint32_t>(program); + fPlugin.loadProgram(fCurrentProgram); + + #if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + { + fParameterValueChangesForUI[kVst3InternalParameterProgram] = false; + sendParameterSetToUI(kVst3InternalParameterProgram, program); + } + #endif + #endif + } + else if (queryingType == 's') + { + d_debug("found state '%s' '%s'", key.buffer(), value.buffer()); + + #if DISTRHO_PLUGIN_WANT_STATE + if (fPlugin.wantStateKey(key)) + { + fStateMap[key] = value; + fPlugin.setState(key, value); + + #if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + sendStateSetToUI(key, value); + #endif + } + #endif + } + else if (queryingType == 'p') + { + d_debug("found parameter '%s' '%s'", key.buffer(), value.buffer()); + float fvalue; + + // find parameter with this symbol, and set its value + for (uint32_t j=0; j < fParameterCount; ++j) + { + if (fPlugin.isParameterOutputOrTrigger(j)) + continue; + if (fPlugin.getParameterSymbol(j) != key) + continue; + + if (fPlugin.getParameterHints(j) & kParameterIsInteger) + fvalue = std::atoi(value.buffer()); + else + fvalue = std::atof(value.buffer()); + + fCachedParameterValues[kVst3InternalParameterBaseCount + j] = fvalue; + #if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + { + // UI parameter updates are handled outside the read loop (after host param restart) + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + j] = true; + } + #endif + fPlugin.setParameterValue(j, fvalue); + break; + } + } + + key.clear(); + value.clear(); + hasValue = false; + } + } + } + + if (fComponentHandler != nullptr) + v3_cpp_obj(fComponentHandler)->restart_component(fComponentHandler, V3_RESTART_PARAM_VALUES_CHANGED); + + #if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + { + for (uint32_t i=0; i<fParameterCount; ++i) + { + if (fPlugin.isParameterOutputOrTrigger(i)) + continue; + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + i] = false; + sendParameterSetToUI(kVst3InternalParameterCount + i, + fCachedParameterValues[kVst3InternalParameterBaseCount + i]); + } + } + #endif + + return V3_OK; + } + + v3_result getState(v3_bstream** const stream) + { + const uint32_t paramCount = fPlugin.getParameterCount(); + #if DISTRHO_PLUGIN_WANT_STATE + const uint32_t stateCount = fPlugin.getStateCount(); + #else + const uint32_t stateCount = 0; + #endif + + if (stateCount == 0 && paramCount == 0) + { + char buffer = '\0'; + int32_t ignored; + return v3_cpp_obj(stream)->write(stream, &buffer, 1, &ignored); + } + + #if DISTRHO_PLUGIN_WANT_FULL_STATE + // Update current state + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + fStateMap[key] = fPlugin.getStateValue(key); + } + #endif + + String state; + + #if DISTRHO_PLUGIN_WANT_PROGRAMS + { + String tmpStr("__dpf_program__\xff"); + tmpStr += String(fCurrentProgram); + tmpStr += "\xff"; + + state += tmpStr; + } + #endif + + #if DISTRHO_PLUGIN_WANT_STATE + if (stateCount != 0) + { + state += "__dpf_state_begin__\xff"; + + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + const String& value = cit->second; + + // join key and value + String tmpStr; + tmpStr = key; + tmpStr += "\xff"; + tmpStr += value; + tmpStr += "\xff"; + + state += tmpStr; + } + + state += "__dpf_state_end__\xff"; + } + #endif + + if (paramCount != 0) + { + state += "__dpf_parameters_begin__\xff"; + + for (uint32_t i=0; i<paramCount; ++i) + { + if (fPlugin.isParameterOutputOrTrigger(i)) + continue; + + // join key and value + String tmpStr; + tmpStr = fPlugin.getParameterSymbol(i); + tmpStr += "\xff"; + if (fPlugin.getParameterHints(i) & kParameterIsInteger) + tmpStr += String(static_cast<int>(std::round(fPlugin.getParameterValue(i)))); + else + tmpStr += String(fPlugin.getParameterValue(i)); + tmpStr += "\xff"; + + state += tmpStr; + } + + state += "__dpf_parameters_end__\xff"; + } + + // terminator + state += "\xfe"; + + state.replace('\xff', '\0'); + + // now saving state, carefully until host written bytes matches full state size + const char* buffer = state.buffer(); + const int32_t size = static_cast<int32_t>(state.length())+1; + v3_result res; + + for (int32_t wrtntotal = 0, wrtn; wrtntotal < size; wrtntotal += wrtn) + { + wrtn = 0; + res = v3_cpp_obj(stream)->write(stream, const_cast<char*>(buffer), size - wrtntotal, &wrtn); + + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(wrtn > 0, wrtn, V3_INTERNAL_ERR); + } + + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_audio_processor interface calls + + v3_result setBusArrangements(v3_speaker_arrangement* const inputs, const int32_t numInputs, + v3_speaker_arrangement* const outputs, const int32_t numOutputs) + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + DISTRHO_SAFE_ASSERT_RETURN(numInputs >= 0, V3_INVALID_ARG); + if (!setAudioBusArrangement<true>(inputs, static_cast<uint32_t>(numInputs))) + return V3_INTERNAL_ERR; + #else + DISTRHO_SAFE_ASSERT_RETURN(numInputs == 0, V3_INVALID_ARG); + // unused + (void)inputs; + #endif + + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + DISTRHO_SAFE_ASSERT_RETURN(numOutputs >= 0, V3_INVALID_ARG); + if (!setAudioBusArrangement<false>(outputs, static_cast<uint32_t>(numOutputs))) + return V3_INTERNAL_ERR; + #else + DISTRHO_SAFE_ASSERT_RETURN(numOutputs == 0, V3_INVALID_ARG); + // unused + (void)outputs; + #endif + + return V3_OK; + } + + v3_result getBusArrangement(const int32_t busDirection, const int32_t busIndex, v3_speaker_arrangement* const speaker) const noexcept + { + DISTRHO_SAFE_ASSERT_INT_RETURN(busDirection == V3_INPUT || busDirection == V3_OUTPUT, busDirection, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_INT_RETURN(busIndex >= 0, busIndex, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(speaker != nullptr, V3_INVALID_ARG); + + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 || DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + const uint32_t busId = static_cast<uint32_t>(busIndex); + #endif + + if (busDirection == V3_INPUT) + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + if (getAudioBusArrangement<true>(busId, speaker)) + return V3_OK; + #endif + d_stderr("invalid input bus arrangement %d, line %d", busIndex, __LINE__); + return V3_INVALID_ARG; + } + else + { + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + if (getAudioBusArrangement<false>(busId, speaker)) + return V3_OK; + #endif + d_stderr("invalid output bus arrangement %d, line %d", busIndex, __LINE__); + return V3_INVALID_ARG; + } + } + + uint32_t getLatencySamples() const noexcept + { + #if DISTRHO_PLUGIN_WANT_LATENCY + return fPlugin.getLatency(); + #else + return 0; + #endif + } + + v3_result setupProcessing(v3_process_setup* const setup) + { + DISTRHO_SAFE_ASSERT_RETURN(setup->symbolic_sample_size == V3_SAMPLE_32, V3_INVALID_ARG); + + const bool active = fPlugin.isActive(); + fPlugin.deactivateIfNeeded(); + + // TODO process_mode can be V3_REALTIME, V3_PREFETCH, V3_OFFLINE + + fPlugin.setSampleRate(setup->sample_rate, true); + fPlugin.setBufferSize(setup->max_block_size, true); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + fCachedParameterValues[kVst3InternalParameterBufferSize] = setup->max_block_size; + fParameterValuesChangedDuringProcessing[kVst3InternalParameterBufferSize] = true; + + fCachedParameterValues[kVst3InternalParameterSampleRate] = setup->sample_rate; + fParameterValuesChangedDuringProcessing[kVst3InternalParameterSampleRate] = true; + #if DISTRHO_PLUGIN_HAS_UI + fParameterValueChangesForUI[kVst3InternalParameterSampleRate] = true; + #endif + #endif + + if (active) + fPlugin.activate(); + + delete[] fDummyAudioBuffer; + fDummyAudioBuffer = new float[setup->max_block_size]; + + return V3_OK; + } + + v3_result setProcessing(const bool processing) + { + if (processing) + { + if (! fPlugin.isActive()) + fPlugin.activate(); + } + else + { + fPlugin.deactivateIfNeeded(); + } + + return V3_OK; + } + + v3_result process(v3_process_data* const data) + { + DISTRHO_SAFE_ASSERT_RETURN(data->symbolic_sample_size == V3_SAMPLE_32, V3_INVALID_ARG); + // d_debug("process %i", data->symbolic_sample_size); + + // activate plugin if not done yet + if (! fPlugin.isActive()) + fPlugin.activate(); + + #if DISTRHO_PLUGIN_WANT_TIMEPOS + if (v3_process_context* const ctx = data->ctx) + { + fTimePosition.playing = ctx->state & V3_PROCESS_CTX_PLAYING; + + // ticksPerBeat is not possible with VST3 + fTimePosition.bbt.ticksPerBeat = 1920.0; + + if (ctx->state & V3_PROCESS_CTX_PROJECT_TIME_VALID) + fTimePosition.frame = ctx->project_time_in_samples; + else if (ctx->state & V3_PROCESS_CTX_CONT_TIME_VALID) + fTimePosition.frame = ctx->continuous_time_in_samples; + + if (ctx->state & V3_PROCESS_CTX_TEMPO_VALID) + fTimePosition.bbt.beatsPerMinute = ctx->bpm; + else + fTimePosition.bbt.beatsPerMinute = 120.0; + + if ((ctx->state & (V3_PROCESS_CTX_PROJECT_TIME_VALID|V3_PROCESS_CTX_TIME_SIG_VALID)) == (V3_PROCESS_CTX_PROJECT_TIME_VALID|V3_PROCESS_CTX_TIME_SIG_VALID)) + { + const double ppqPos = std::abs(ctx->project_time_quarters); + const int ppqPerBar = ctx->time_sig_numerator * 4 / ctx->time_sig_denom; + const double barBeats = (std::fmod(ppqPos, ppqPerBar) / ppqPerBar) * ctx->time_sig_numerator; + const double rest = std::fmod(barBeats, 1.0); + + fTimePosition.bbt.valid = true; + fTimePosition.bbt.bar = static_cast<int32_t>(ppqPos) / ppqPerBar + 1; + fTimePosition.bbt.beat = static_cast<int32_t>(barBeats - rest + 0.5) + 1; + fTimePosition.bbt.tick = rest * fTimePosition.bbt.ticksPerBeat; + fTimePosition.bbt.beatsPerBar = ctx->time_sig_numerator; + fTimePosition.bbt.beatType = ctx->time_sig_denom; + + if (ctx->project_time_quarters < 0.0) + { + --fTimePosition.bbt.bar; + fTimePosition.bbt.beat = ctx->time_sig_numerator - fTimePosition.bbt.beat + 1; + fTimePosition.bbt.tick = fTimePosition.bbt.ticksPerBeat - fTimePosition.bbt.tick - 1; + } + } + else + { + fTimePosition.bbt.valid = false; + fTimePosition.bbt.bar = 1; + fTimePosition.bbt.beat = 1; + fTimePosition.bbt.tick = 0.0; + fTimePosition.bbt.beatsPerBar = 4.0f; + fTimePosition.bbt.beatType = 4.0f; + } + + fTimePosition.bbt.barStartTick = fTimePosition.bbt.ticksPerBeat* + fTimePosition.bbt.beatsPerBar* + (fTimePosition.bbt.bar-1); + + fPlugin.setTimePosition(fTimePosition); + } + #endif + + if (data->nframes <= 0) + { + updateParametersFromProcessing(data->output_params, 0); + return V3_OK; + } + + const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS != 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1]; + /* */ float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS != 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1]; + + std::memset(fDummyAudioBuffer, 0, sizeof(float)*data->nframes); + + { + int32_t i = 0; + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + if (data->inputs != nullptr) + { + for (int32_t b = 0; b < data->num_input_buses; ++b) { + for (int32_t j = 0; j < data->inputs[b].num_channels; ++j) + { + DISTRHO_SAFE_ASSERT_INT_BREAK(i < DISTRHO_PLUGIN_NUM_INPUTS, i); + if (!fEnabledInputs[i] && i < DISTRHO_PLUGIN_NUM_INPUTS) { + inputs[i++] = fDummyAudioBuffer; + continue; + } + + inputs[i++] = data->inputs[b].channel_buffers_32[j]; + } + } + } + #endif + for (; i < std::max(1, DISTRHO_PLUGIN_NUM_INPUTS); ++i) + inputs[i] = fDummyAudioBuffer; + } + + { + int32_t i = 0; + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + if (data->outputs != nullptr) + { + for (int32_t b = 0; b < data->num_output_buses; ++b) { + for (int32_t j = 0; j < data->outputs[b].num_channels; ++j) + { + DISTRHO_SAFE_ASSERT_INT_BREAK(i < DISTRHO_PLUGIN_NUM_OUTPUTS, i); + if (!fEnabledOutputs[i] && i < DISTRHO_PLUGIN_NUM_OUTPUTS) { + outputs[i++] = fDummyAudioBuffer; + continue; + } + + outputs[i++] = data->outputs[b].channel_buffers_32[j]; + } + } + } + #endif + for (; i < std::max(1, DISTRHO_PLUGIN_NUM_OUTPUTS); ++i) + outputs[i] = fDummyAudioBuffer; + } + + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + fHostEventOutputHandle = data->output_events; + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + bool canAppendMoreEvents = true; + inputEventList.init(); + + #if DISTRHO_PLUGIN_HAS_UI + while (fNotesRingBuffer.isDataAvailableForReading()) + { + uint8_t midiData[3]; + if (! fNotesRingBuffer.readCustomData(midiData, 3)) + break; + + if (inputEventList.appendFromUI(midiData)) + { + canAppendMoreEvents = false; + break; + } + } + #endif + + if (canAppendMoreEvents) + { + if (v3_event_list** const eventptr = data->input_events) + { + v3_event event; + for (uint32_t i = 0, count = v3_cpp_obj(eventptr)->get_event_count(eventptr); i < count; ++i) + { + if (v3_cpp_obj(eventptr)->get_event(eventptr, i, &event) != V3_OK) + break; + + if (inputEventList.appendEvent(event)) + { + canAppendMoreEvents = false; + break; + } + } + } + } + #endif + + if (v3_param_changes** const inparamsptr = data->input_params) + { + int32_t offset; + double normalized; + + for (int32_t i = 0, count = v3_cpp_obj(inparamsptr)->get_param_count(inparamsptr); i < count; ++i) + { + v3_param_value_queue** const queue = v3_cpp_obj(inparamsptr)->get_param_data(inparamsptr, i); + DISTRHO_SAFE_ASSERT_BREAK(queue != nullptr); + + const v3_param_id rindex = v3_cpp_obj(queue)->get_param_id(queue); + DISTRHO_SAFE_ASSERT_UINT_BREAK(rindex < fVst3ParameterCount, rindex); + + #if DPF_VST3_HAS_INTERNAL_PARAMETERS + if (rindex < kVst3InternalParameterCount) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + // if there are any MIDI CC events as parameter changes, handle them here + if (canAppendMoreEvents && rindex >= kVst3InternalParameterMidiCC_start && rindex <= kVst3InternalParameterMidiCC_end) + { + for (int32_t j = 0, pcount = v3_cpp_obj(queue)->get_point_count(queue); j < pcount; ++j) + { + if (v3_cpp_obj(queue)->get_point(queue, j, &offset, &normalized) != V3_OK) + break; + + if (inputEventList.appendCC(offset, rindex, normalized)) + { + canAppendMoreEvents = false; + break; + } + } + } + #endif + continue; + } + #endif + + if (v3_cpp_obj(queue)->get_point_count(queue) <= 0) + continue; + + // if there are any parameter changes at frame 0, handle them here + if (v3_cpp_obj(queue)->get_point(queue, 0, &offset, &normalized) != V3_OK) + break; + + if (offset != 0) + continue; + + const uint32_t index = rindex - kVst3InternalParameterCount; + _setNormalizedPluginParameterValue(index, normalized); + } + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + const uint32_t midiEventCount = inputEventList.convert(fMidiEvents); + fPlugin.run(inputs, outputs, data->nframes, fMidiEvents, midiEventCount); + #else + fPlugin.run(inputs, outputs, data->nframes); + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + fHostEventOutputHandle = nullptr; + #endif + + // if there are any parameter changes after frame 0, set them here + if (v3_param_changes** const inparamsptr = data->input_params) + { + int32_t offset; + double normalized; + + for (int32_t i = 0, count = v3_cpp_obj(inparamsptr)->get_param_count(inparamsptr); i < count; ++i) + { + v3_param_value_queue** const queue = v3_cpp_obj(inparamsptr)->get_param_data(inparamsptr, i); + DISTRHO_SAFE_ASSERT_BREAK(queue != nullptr); + + const v3_param_id rindex = v3_cpp_obj(queue)->get_param_id(queue); + DISTRHO_SAFE_ASSERT_UINT_BREAK(rindex < fVst3ParameterCount, rindex); + + #if DPF_VST3_HAS_INTERNAL_PARAMETERS + if (rindex < kVst3InternalParameterCount) + continue; + #endif + + const int32_t pcount = v3_cpp_obj(queue)->get_point_count(queue); + + if (pcount <= 0) + continue; + + if (v3_cpp_obj(queue)->get_point(queue, pcount - 1, &offset, &normalized) != V3_OK) + break; + + if (offset == 0) + continue; + + const uint32_t index = rindex - kVst3InternalParameterCount; + _setNormalizedPluginParameterValue(index, normalized); + } + } + + updateParametersFromProcessing(data->output_params, data->nframes - 1); + return V3_OK; + } + + uint32_t getTailSamples() const noexcept + { + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_edit_controller interface calls + + int32_t getParameterCount() const noexcept + { + return fVst3ParameterCount; + } + + v3_result getParameterInfo(const int32_t rindex, v3_param_info* const info) const noexcept + { + std::memset(info, 0, sizeof(v3_param_info)); + DISTRHO_SAFE_ASSERT_RETURN(rindex >= 0, V3_INVALID_ARG); + + // TODO hash the parameter symbol + info->param_id = rindex; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + info->flags = V3_PARAM_READ_ONLY | V3_PARAM_IS_HIDDEN; + info->step_count = DPF_VST3_MAX_BUFFER_SIZE - 1; + strncpy_utf16(info->title, "Buffer Size", 128); + strncpy_utf16(info->short_title, "Buffer Size", 128); + strncpy_utf16(info->units, "frames", 128); + return V3_OK; + case kVst3InternalParameterSampleRate: + info->flags = V3_PARAM_READ_ONLY | V3_PARAM_IS_HIDDEN; + strncpy_utf16(info->title, "Sample Rate", 128); + strncpy_utf16(info->short_title, "Sample Rate", 128); + strncpy_utf16(info->units, "frames", 128); + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + info->flags = V3_PARAM_READ_ONLY | V3_PARAM_IS_HIDDEN; + strncpy_utf16(info->title, "Latency", 128); + strncpy_utf16(info->short_title, "Latency", 128); + strncpy_utf16(info->units, "frames", 128); + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + info->flags = V3_PARAM_CAN_AUTOMATE | V3_PARAM_IS_LIST | V3_PARAM_PROGRAM_CHANGE | V3_PARAM_IS_HIDDEN; + info->step_count = fProgramCountMinusOne; + strncpy_utf16(info->title, "Current Program", 128); + strncpy_utf16(info->short_title, "Program", 128); + return V3_OK; + #endif + } + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex < kVst3InternalParameterCount) + { + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterMidiCC_start); + info->flags = V3_PARAM_CAN_AUTOMATE | V3_PARAM_IS_HIDDEN; + info->step_count = 127; + char ccstr[24]; + snprintf(ccstr, sizeof(ccstr), "MIDI Ch. %d CC %d", static_cast<uint8_t>(index / 130) + 1, index % 130); + strncpy_utf16(info->title, ccstr, 128); + snprintf(ccstr, sizeof(ccstr), "Ch.%d CC%d", index / 130 + 1, index % 130); + strncpy_utf16(info->short_title, ccstr+5, 128); + return V3_OK; + } + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT_RETURN(index < fParameterCount, index, V3_INVALID_ARG); + + // set up flags + int32_t flags = 0; + + const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(index)); + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + const uint32_t hints = fPlugin.getParameterHints(index); + + switch (fPlugin.getParameterDesignation(index)) + { + case kParameterDesignationNull: + break; + case kParameterDesignationBypass: + flags |= V3_PARAM_IS_BYPASS; + break; + } + + if (hints & kParameterIsAutomatable) + flags |= V3_PARAM_CAN_AUTOMATE; + if (hints & kParameterIsOutput) + flags |= V3_PARAM_READ_ONLY; + + // set up step_count + int32_t step_count = 0; + + if (hints & kParameterIsBoolean) + step_count = 1; + else if (hints & kParameterIsInteger) + step_count = ranges.max - ranges.min; + + if (enumValues.count >= 2 && enumValues.restrictedMode) + { + flags |= V3_PARAM_IS_LIST; + step_count = enumValues.count - 1; + } + + info->flags = flags; + info->step_count = step_count; + info->default_normalised_value = ranges.getNormalizedValue(ranges.def); + // int32_t unit_id; + strncpy_utf16(info->title, fPlugin.getParameterName(index), 128); + strncpy_utf16(info->short_title, fPlugin.getParameterShortName(index), 128); + strncpy_utf16(info->units, fPlugin.getParameterUnit(index), 128); + return V3_OK; + } + + v3_result getParameterStringForValue(const v3_param_id rindex, const double normalized, v3_str_128 output) + { + DISTRHO_SAFE_ASSERT_RETURN(normalized >= 0.0 && normalized <= 1.0, V3_INVALID_ARG); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + snprintf_i32_utf16(output, static_cast<int>(normalized * DPF_VST3_MAX_BUFFER_SIZE + 0.5), 128); + return V3_OK; + case kVst3InternalParameterSampleRate: + snprintf_f32_utf16(output, std::round(normalized * DPF_VST3_MAX_SAMPLE_RATE), 128); + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + snprintf_f32_utf16(output, std::round(normalized * DPF_VST3_MAX_LATENCY), 128); + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + const uint32_t program = std::round(normalized * fProgramCountMinusOne); + strncpy_utf16(output, fPlugin.getProgramName(program), 128); + return V3_OK; + #endif + } + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex < kVst3InternalParameterCount) + { + snprintf_f32_utf16(output, std::round(normalized * 127), 128); + return V3_OK; + } + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT_RETURN(index < fParameterCount, index, V3_INVALID_ARG); + + const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(index)); + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + const uint32_t hints = fPlugin.getParameterHints(index); + float value = ranges.getUnnormalizedValue(normalized); + + if (hints & kParameterIsBoolean) + { + const float midRange = ranges.min + (ranges.max - ranges.min) * 0.5f; + value = value > midRange ? ranges.max : ranges.min; + } + else if (hints & kParameterIsInteger) + { + value = std::round(value); + } + + for (uint32_t i=0; i < enumValues.count; ++i) + { + if (d_isEqual(enumValues.values[i].value, value)) + { + strncpy_utf16(output, enumValues.values[i].label, 128); + return V3_OK; + } + } + + if (hints & kParameterIsInteger) + snprintf_i32_utf16(output, value, 128); + else + snprintf_f32_utf16(output, value, 128); + + return V3_OK; + } + + v3_result getParameterValueForString(const v3_param_id rindex, int16_t* const input, double* const output) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + *output = static_cast<double>(std::atoi(ScopedUTF8String(input))) / DPF_VST3_MAX_BUFFER_SIZE; + return V3_OK; + case kVst3InternalParameterSampleRate: + *output = std::atof(ScopedUTF8String(input)) / DPF_VST3_MAX_SAMPLE_RATE; + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + *output = std::atof(ScopedUTF8String(input)) / DPF_VST3_MAX_LATENCY; + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + for (uint32_t i=0, count=fPlugin.getProgramCount(); i < count; ++i) + { + if (strcmp_utf16(input, fPlugin.getProgramName(i))) + { + *output = static_cast<double>(i) / static_cast<double>(fProgramCountMinusOne); + return V3_OK; + } + } + return V3_INVALID_ARG; + #endif + } + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex < kVst3InternalParameterCount) + { + // TODO find CC/channel based on name + return V3_NOT_IMPLEMENTED; + } + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT_RETURN(index < fParameterCount, index, V3_INVALID_ARG); + + const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(index)); + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + + for (uint32_t i=0; i < enumValues.count; ++i) + { + if (strcmp_utf16(input, enumValues.values[i].label)) + { + *output = ranges.getNormalizedValue(enumValues.values[i].value); + return V3_OK; + } + } + + const ScopedUTF8String input8(input); + + float value; + if (fPlugin.getParameterHints(index) & kParameterIsInteger) + value = std::atoi(input8); + else + value = std::atof(input8); + + *output = ranges.getNormalizedValue(value); + return V3_OK; + } + + double normalizedParameterToPlain(const v3_param_id rindex, const double normalized) + { + DISTRHO_SAFE_ASSERT_RETURN(normalized >= 0.0 && normalized <= 1.0, 0.0); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + return std::round(normalized * DPF_VST3_MAX_BUFFER_SIZE); + case kVst3InternalParameterSampleRate: + return normalized * DPF_VST3_MAX_SAMPLE_RATE; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + return normalized * DPF_VST3_MAX_LATENCY; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + return std::round(normalized * fProgramCountMinusOne); + #endif + } + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex < kVst3InternalParameterCount) + return std::round(normalized * 127); + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < fParameterCount, index, fParameterCount, 0.0); + + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + const uint32_t hints = fPlugin.getParameterHints(index); + float value = ranges.getUnnormalizedValue(normalized); + + if (hints & kParameterIsBoolean) + { + const float midRange = ranges.min + (ranges.max - ranges.min) / 2.0f; + value = value > midRange ? ranges.max : ranges.min; + } + else if (hints & kParameterIsInteger) + { + value = std::round(value); + } + + return value; + } + + double plainParameterToNormalized(const v3_param_id rindex, const double plain) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + return std::max(0.0, std::min(1.0, plain / DPF_VST3_MAX_BUFFER_SIZE)); + case kVst3InternalParameterSampleRate: + return std::max(0.0, std::min(1.0, plain / DPF_VST3_MAX_SAMPLE_RATE)); + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + return std::max(0.0, std::min(1.0, plain / DPF_VST3_MAX_LATENCY)); + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + return std::max(0.0, std::min(1.0, plain / fProgramCountMinusOne)); + #endif + } + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex < kVst3InternalParameterCount) + return std::max(0.0, std::min(1.0, plain / 127)); + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < fParameterCount, index, fParameterCount, 0.0); + + return _getNormalizedParameterValue(index, plain); + } + + double getParameterNormalized(const v3_param_id rindex) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + // TODO something to do here? + if ( + #if !DPF_VST3_PURE_MIDI_INTERNAL_PARAMETERS + rindex >= kVst3InternalParameterMidiCC_start && + #endif + rindex <= kVst3InternalParameterMidiCC_end) + return 0.0; + #endif + + #if DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + case kVst3InternalParameterSampleRate: + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + #endif + return plainParameterToNormalized(rindex, fCachedParameterValues[rindex]); + } + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < fParameterCount, index, fParameterCount, 0.0); + + return _getNormalizedParameterValue(index, fCachedParameterValues[kVst3InternalParameterBaseCount + index]); + } + + v3_result setParameterNormalized(const v3_param_id rindex, const double normalized) + { + DISTRHO_SAFE_ASSERT_RETURN(normalized >= 0.0 && normalized <= 1.0, V3_INVALID_ARG); + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + // TODO something to do here? + if ( + #if !DPF_VST3_PURE_MIDI_INTERNAL_PARAMETERS + rindex >= kVst3InternalParameterMidiCC_start && + #endif + rindex <= kVst3InternalParameterMidiCC_end) + return V3_INVALID_ARG; + #endif + + #if DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS + if (rindex < kVst3InternalParameterBaseCount) + { + fCachedParameterValues[rindex] = normalizedParameterToPlain(rindex, normalized); + int flags = 0; + + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + fPlugin.setBufferSize(fCachedParameterValues[rindex], true); + break; + case kVst3InternalParameterSampleRate: + fPlugin.setSampleRate(fCachedParameterValues[rindex], true); + break; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + flags = V3_RESTART_LATENCY_CHANGED; + break; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + flags = V3_RESTART_PARAM_VALUES_CHANGED; + fCurrentProgram = fCachedParameterValues[rindex]; + fPlugin.loadProgram(fCurrentProgram); + + for (uint32_t i=0; i<fParameterCount; ++i) + { + if (fPlugin.isParameterOutputOrTrigger(i)) + continue; + fCachedParameterValues[kVst3InternalParameterBaseCount + i] = fPlugin.getParameterValue(i); + } + + #if DISTRHO_PLUGIN_HAS_UI + fParameterValueChangesForUI[kVst3InternalParameterProgram] = true; + #endif + break; + #endif + } + + if (fComponentHandler != nullptr && flags != 0) + v3_cpp_obj(fComponentHandler)->restart_component(fComponentHandler, flags); + + return V3_OK; + } + #endif + + DISTRHO_SAFE_ASSERT_UINT2_RETURN(rindex >= kVst3InternalParameterCount, rindex, kVst3InternalParameterCount, V3_INVALID_ARG); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < fParameterCount, index, fParameterCount, V3_INVALID_ARG); + + if (fIsComponent) { + DISTRHO_SAFE_ASSERT_RETURN(!fPlugin.isParameterOutputOrTrigger(index), V3_INVALID_ARG); + } + + _setNormalizedPluginParameterValue(index, normalized); + #endif + + return V3_OK; + } + + v3_result setComponentHandler(v3_component_handler** const handler) noexcept + { + fComponentHandler = handler; + return V3_OK; + } + +#if DISTRHO_PLUGIN_HAS_UI + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point interface calls + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + void comp2ctrl_connect(v3_connection_point** const other) + { + fConnectionFromCompToCtrl = other; + } + + void comp2ctrl_disconnect() + { + fConnectionFromCompToCtrl = nullptr; + } + + v3_result comp2ctrl_notify(v3_message** const message) + { + const char* const msgid = v3_cpp_obj(message)->get_message_id(message); + DISTRHO_SAFE_ASSERT_RETURN(msgid != nullptr, V3_INVALID_ARG); + + v3_attribute_list** const attrs = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrs != nullptr, V3_INVALID_ARG); + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (std::strcmp(msgid, "midi") == 0) + return notify_midi(attrs); + #endif + + #if DISTRHO_PLUGIN_WANT_STATE + if (std::strcmp(msgid, "state-set") == 0) + return notify_state(attrs); + #endif + + d_stderr("comp2ctrl_notify received unknown msg '%s'", msgid); + + return V3_NOT_IMPLEMENTED; + } + #endif // DPF_VST3_USES_SEPARATE_CONTROLLER + + // ---------------------------------------------------------------------------------------------------------------- + + void ctrl2view_connect(v3_connection_point** const other) + { + DISTRHO_SAFE_ASSERT(fConnectedToUI == false); + + fConnectionFromCtrlToView = other; + fConnectedToUI = false; + } + + void ctrl2view_disconnect() + { + fConnectedToUI = false; + fConnectionFromCtrlToView = nullptr; + } + + v3_result ctrl2view_notify(v3_message** const message) + { + DISTRHO_SAFE_ASSERT_RETURN(fConnectionFromCtrlToView != nullptr, V3_INTERNAL_ERR); + + const char* const msgid = v3_cpp_obj(message)->get_message_id(message); + DISTRHO_SAFE_ASSERT_RETURN(msgid != nullptr, V3_INVALID_ARG); + + if (std::strcmp(msgid, "init") == 0) + { + fConnectedToUI = true; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + fParameterValueChangesForUI[kVst3InternalParameterSampleRate] = false; + sendParameterSetToUI(kVst3InternalParameterSampleRate, + fCachedParameterValues[kVst3InternalParameterSampleRate]); + #endif + + #if DISTRHO_PLUGIN_WANT_PROGRAMS + fParameterValueChangesForUI[kVst3InternalParameterProgram] = false; + sendParameterSetToUI(kVst3InternalParameterProgram, fCurrentProgram); + #endif + + #if DISTRHO_PLUGIN_WANT_FULL_STATE + // Update current state from plugin side + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + fStateMap[key] = fPlugin.getStateValue(key); + } + #endif + + #if DISTRHO_PLUGIN_WANT_STATE + // Set state + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + const String& value = cit->second; + + sendStateSetToUI(key, value); + } + #endif + + for (uint32_t i=0; i<fParameterCount; ++i) + { + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + i] = false; + sendParameterSetToUI(kVst3InternalParameterCount + i, + fCachedParameterValues[kVst3InternalParameterBaseCount + i]); + } + + sendReadyToUI(); + return V3_OK; + } + + DISTRHO_SAFE_ASSERT_RETURN(fConnectedToUI, V3_INTERNAL_ERR); + + v3_attribute_list** const attrs = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrs != nullptr, V3_INVALID_ARG); + + if (std::strcmp(msgid, "idle") == 0) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + if (fParameterValueChangesForUI[kVst3InternalParameterSampleRate]) + { + fParameterValueChangesForUI[kVst3InternalParameterSampleRate] = false; + sendParameterSetToUI(kVst3InternalParameterSampleRate, + fCachedParameterValues[kVst3InternalParameterSampleRate]); + } + #endif + + #if DISTRHO_PLUGIN_WANT_PROGRAMS + if (fParameterValueChangesForUI[kVst3InternalParameterProgram]) + { + fParameterValueChangesForUI[kVst3InternalParameterProgram] = false; + sendParameterSetToUI(kVst3InternalParameterProgram, fCurrentProgram); + } + #endif + + for (uint32_t i=0; i<fParameterCount; ++i) + { + if (! fParameterValueChangesForUI[kVst3InternalParameterBaseCount + i]) + continue; + + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + i] = false; + sendParameterSetToUI(kVst3InternalParameterCount + i, + fCachedParameterValues[kVst3InternalParameterBaseCount + i]); + } + + sendReadyToUI(); + return V3_OK; + } + + if (std::strcmp(msgid, "close") == 0) + { + fConnectedToUI = false; + return V3_OK; + } + + if (std::strcmp(msgid, "parameter-edit") == 0) + { + DISTRHO_SAFE_ASSERT_RETURN(fComponentHandler != nullptr, V3_INTERNAL_ERR); + + int64_t rindex; + int64_t started; + v3_result res; + + res = v3_cpp_obj(attrs)->get_int(attrs, "rindex", &rindex); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT2_RETURN(rindex >= kVst3InternalParameterCount, + rindex, fParameterCount, V3_INTERNAL_ERR); + DISTRHO_SAFE_ASSERT_INT2_RETURN(rindex < kVst3InternalParameterCount + fParameterCount, + rindex, fParameterCount, V3_INTERNAL_ERR); + + res = v3_cpp_obj(attrs)->get_int(attrs, "started", &started); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(started == 0 || started == 1, started, V3_INTERNAL_ERR); + + return started != 0 ? v3_cpp_obj(fComponentHandler)->begin_edit(fComponentHandler, rindex) + : v3_cpp_obj(fComponentHandler)->end_edit(fComponentHandler, rindex); + } + + if (std::strcmp(msgid, "parameter-set") == 0) + { + DISTRHO_SAFE_ASSERT_RETURN(fComponentHandler != nullptr, V3_INTERNAL_ERR); + + int64_t rindex; + double value; + v3_result res; + + res = v3_cpp_obj(attrs)->get_int(attrs, "rindex", &rindex); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT2_RETURN(rindex >= kVst3InternalParameterCount, + rindex, fParameterCount, V3_INTERNAL_ERR); + DISTRHO_SAFE_ASSERT_INT2_RETURN(rindex < kVst3InternalParameterCount + fParameterCount, + rindex, fParameterCount, V3_INTERNAL_ERR); + + res = v3_cpp_obj(attrs)->get_float(attrs, "value", &value); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + const uint32_t index = rindex - kVst3InternalParameterCount; + const double normalized = _getNormalizedParameterValue(index, value); + + fCachedParameterValues[kVst3InternalParameterBaseCount + index] = value; + + if (! fPlugin.isParameterOutputOrTrigger(index)) + fPlugin.setParameterValue(index, value); + + return v3_cpp_obj(fComponentHandler)->perform_edit(fComponentHandler, rindex, normalized); + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (std::strcmp(msgid, "midi") == 0) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + DISTRHO_SAFE_ASSERT_RETURN(fConnectionFromCompToCtrl != nullptr, V3_INTERNAL_ERR); + return v3_cpp_obj(fConnectionFromCompToCtrl)->notify(fConnectionFromCompToCtrl, message); + #else + return notify_midi(attrs); + #endif + } + #endif + + #if DISTRHO_PLUGIN_WANT_STATE + if (std::strcmp(msgid, "state-set") == 0) + { + const v3_result res = notify_state(attrs); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + if (res != V3_OK) + return res; + + // notify component of the change + DISTRHO_SAFE_ASSERT_RETURN(fConnectionFromCompToCtrl != nullptr, V3_INTERNAL_ERR); + return v3_cpp_obj(fConnectionFromCompToCtrl)->notify(fConnectionFromCompToCtrl, message); + #else + return res; + #endif + } + #endif + + d_stderr("ctrl2view_notify received unknown msg '%s'", msgid); + + return V3_NOT_IMPLEMENTED; + } + + #if DISTRHO_PLUGIN_WANT_STATE + v3_result notify_state(v3_attribute_list** const attrs) + { + int64_t keyLength = -1; + int64_t valueLength = -1; + v3_result res; + + res = v3_cpp_obj(attrs)->get_int(attrs, "key:length", &keyLength); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(keyLength >= 0, keyLength, V3_INTERNAL_ERR); + + res = v3_cpp_obj(attrs)->get_int(attrs, "value:length", &valueLength); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(valueLength >= 0, valueLength, V3_INTERNAL_ERR); + + int16_t* const key16 = (int16_t*)std::malloc(sizeof(int16_t)*(keyLength + 1)); + DISTRHO_SAFE_ASSERT_RETURN(key16 != nullptr, V3_NOMEM); + + int16_t* const value16 = (int16_t*)std::malloc(sizeof(int16_t)*(valueLength + 1)); + DISTRHO_SAFE_ASSERT_RETURN(value16 != nullptr, V3_NOMEM); + + res = v3_cpp_obj(attrs)->get_string(attrs, "key", key16, sizeof(int16_t)*(keyLength+1)); + DISTRHO_SAFE_ASSERT_INT2_RETURN(res == V3_OK, res, keyLength, res); + + if (valueLength != 0) + { + res = v3_cpp_obj(attrs)->get_string(attrs, "value", value16, sizeof(int16_t)*(valueLength+1)); + DISTRHO_SAFE_ASSERT_INT2_RETURN(res == V3_OK, res, valueLength, res); + } + + // do cheap inline conversion + char* const key = (char*)key16; + char* const value = (char*)value16; + + for (int64_t i=0; i<keyLength; ++i) + key[i] = key16[i]; + for (int64_t i=0; i<valueLength; ++i) + value[i] = value16[i]; + + key[keyLength] = '\0'; + value[valueLength] = '\0'; + + fPlugin.setState(key, value); + + // save this key as needed + if (fPlugin.wantStateKey(key)) + { + for (StringMap::iterator it=fStateMap.begin(), ite=fStateMap.end(); it != ite; ++it) + { + const String& dkey(it->first); + + if (dkey == key) + { + it->second = value; + std::free(key16); + std::free(value16); + return V3_OK; + } + } + + d_stderr("Failed to find plugin state with key \"%s\"", key); + } + + std::free(key16); + std::free(value16); + return V3_OK; + } + #endif // DISTRHO_PLUGIN_WANT_STATE + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + v3_result notify_midi(v3_attribute_list** const attrs) + { + uint8_t* data; + uint32_t size; + v3_result res; + + res = v3_cpp_obj(attrs)->get_binary(attrs, "data", (const void**)&data, &size); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + // known maximum size + DISTRHO_SAFE_ASSERT_UINT_RETURN(size == 3, size, V3_INTERNAL_ERR); + + return fNotesRingBuffer.writeCustomData(data, size) && fNotesRingBuffer.commitWrite() ? V3_OK : V3_NOMEM; + } + #endif // DISTRHO_PLUGIN_WANT_MIDI_INPUT +#endif + + // ---------------------------------------------------------------------------------------------------------------- + +private: + // Plugin + PluginExporter fPlugin; + + // VST3 stuff + v3_component_handler** fComponentHandler; + #if DISTRHO_PLUGIN_HAS_UI + #if DPF_VST3_USES_SEPARATE_CONTROLLER + v3_connection_point** fConnectionFromCompToCtrl; + #endif + v3_connection_point** fConnectionFromCtrlToView; + v3_host_application** const fHostApplication; + #endif + + // Temporary data + const uint32_t fParameterCount; + const uint32_t fVst3ParameterCount; // full offset + real + float* fCachedParameterValues; // basic offset + real + float* fDummyAudioBuffer; + bool* fParameterValuesChangedDuringProcessing; // basic offset + real + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + bool fEnabledInputs[DISTRHO_PLUGIN_NUM_INPUTS]; + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + bool fEnabledOutputs[DISTRHO_PLUGIN_NUM_OUTPUTS]; + #endif + #if DPF_VST3_USES_SEPARATE_CONTROLLER + const bool fIsComponent; + #endif + #if DISTRHO_PLUGIN_HAS_UI + bool* fParameterValueChangesForUI; // basic offset + real + bool fConnectedToUI; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + uint32_t fLastKnownLatency; + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + MidiEvent fMidiEvents[kMaxMidiEvents]; + #if DISTRHO_PLUGIN_HAS_UI + SmallStackRingBuffer fNotesRingBuffer; + #endif + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + v3_event_list** fHostEventOutputHandle; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + uint32_t fCurrentProgram; + const uint32_t fProgramCountMinusOne; + #endif + #if DISTRHO_PLUGIN_WANT_STATE + StringMap fStateMap; + #endif + #if DISTRHO_PLUGIN_WANT_TIMEPOS + TimePosition fTimePosition; + #endif + + // ---------------------------------------------------------------------------------------------------------------- + // helper functions for dealing with buses + + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + template<bool isInput> + void fillInBusInfoDetails() + { + constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; + BusInfo& busInfo(isInput ? inputBuses : outputBuses); + bool* const enabledPorts = isInput + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + ? fEnabledInputs + #else + ? nullptr + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + : fEnabledOutputs; + #else + : nullptr; + #endif + + std::vector<uint32_t> visitedPortGroups; + for (uint32_t i=0; i<numPorts; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.groupId != kPortGroupNone) + { + const std::vector<uint32_t>::iterator end = visitedPortGroups.end(); + if (std::find(visitedPortGroups.begin(), end, port.groupId) == end) + { + visitedPortGroups.push_back(port.groupId); + ++busInfo.groups; + } + ++busInfo.groupPorts; + continue; + } + + if (port.hints & kAudioPortIsCV) + ++busInfo.cvPorts; + else if (port.hints & kAudioPortIsSidechain) + ++busInfo.sidechainPorts; + else + ++busInfo.audioPorts; + } + + if (busInfo.audioPorts != 0) + busInfo.audio = 1; + if (busInfo.sidechainPorts != 0) + busInfo.sidechain = 1; + + uint32_t busIdForCV = 0; + const std::vector<uint32_t>::iterator vpgStart = visitedPortGroups.begin(); + const std::vector<uint32_t>::iterator vpgEnd = visitedPortGroups.end(); + + for (uint32_t i=0; i<numPorts; ++i) + { + AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.groupId != kPortGroupNone) + { + port.busId = std::find(vpgStart, vpgEnd, port.groupId) - vpgStart; + + if (busInfo.audio == 0 && (port.hints & kAudioPortIsSidechain) == 0x0) + enabledPorts[i] = true; + } + else + { + if (port.hints & kAudioPortIsCV) + { + port.busId = busInfo.audio + busInfo.sidechain + busIdForCV++; + } + else if (port.hints & kAudioPortIsSidechain) + { + port.busId = busInfo.audio; + } + else + { + port.busId = 0; + enabledPorts[i] = true; + } + + port.busId += busInfo.groups; + } + } + } + + template<bool isInput> + v3_result getAudioBusInfo(const uint32_t busId, v3_bus_info* const info) const + { + constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; + const BusInfo& busInfo(isInput ? inputBuses : outputBuses); + + int32_t numChannels; + uint32_t flags; + v3_bus_types busType; + v3_str_128 busName = {}; + + if (busId < busInfo.groups) + { + numChannels = 0; + + for (uint32_t i=0; i<numPorts; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.busId == busId) + { + const PortGroupWithId& group(fPlugin.getPortGroupById(port.groupId)); + + switch (port.groupId) + { + case kPortGroupStereo: + case kPortGroupMono: + if (busId == 0) + { + strncpy_utf16(busName, isInput ? "Audio Input" : "Audio Output", 128); + break; + } + // fall-through + default: + if (group.name.isNotEmpty()) + strncpy_utf16(busName, group.name, 128); + else + strncpy_utf16(busName, port.name, 128); + break; + } + + numChannels = fPlugin.getAudioPortCountWithGroupId(isInput, port.groupId); + + if (port.hints & kAudioPortIsCV) + { + busType = V3_MAIN; + flags = V3_IS_CONTROL_VOLTAGE; + } + else if (port.hints & kAudioPortIsSidechain) + { + busType = V3_AUX; + flags = 0; + } + else + { + busType = V3_MAIN; + flags = busInfo.audio == 0 ? V3_DEFAULT_ACTIVE : 0; + } + break; + } + } + + DISTRHO_SAFE_ASSERT_RETURN(numChannels != 0, V3_INTERNAL_ERR); + } + else + { + switch (busId - busInfo.groups) + { + case 0: + if (busInfo.audio) + { + numChannels = busInfo.audioPorts; + busType = V3_MAIN; + flags = V3_DEFAULT_ACTIVE; + break; + } + // fall-through + case 1: + if (busInfo.sidechain) + { + numChannels = busInfo.sidechainPorts; + busType = V3_AUX; + flags = 0; + break; + } + // fall-through + default: + numChannels = 1; + busType = V3_MAIN; + flags = V3_IS_CONTROL_VOLTAGE; + break; + } + + if (busType == V3_MAIN && flags != V3_IS_CONTROL_VOLTAGE) + { + strncpy_utf16(busName, isInput ? "Audio Input" : "Audio Output", 128); + } + else + { + for (uint32_t i=0; i<numPorts; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.busId == busId) + { + String groupName; + if (busInfo.groups) + groupName = fPlugin.getPortGroupById(port.groupId).name; + if (groupName.isEmpty()) + groupName = port.name; + strncpy_utf16(busName, groupName, 128); + break; + } + } + } + } + + // d_debug("getAudioBusInfo %d %d %d", (int)isInput, busId, numChannels); + std::memset(info, 0, sizeof(v3_bus_info)); + info->media_type = V3_AUDIO; + info->direction = isInput ? V3_INPUT : V3_OUTPUT; + info->channel_count = numChannels; + std::memcpy(info->bus_name, busName, sizeof(busName)); + info->bus_type = busType; + info->flags = flags; + return V3_OK; + } + + // someone please tell me what is up with these.. + static inline v3_speaker_arrangement portCountToSpeaker(const uint32_t portCount) + { + DISTRHO_SAFE_ASSERT_RETURN(portCount != 0, 0); + + switch (portCount) + { + // regular mono + case 1: return V3_SPEAKER_M; + // regular stereo + case 2: return V3_SPEAKER_L | V3_SPEAKER_R; + // stereo with center channel + case 3: return V3_SPEAKER_L | V3_SPEAKER_R | V3_SPEAKER_C; + // stereo with surround (quadro) + case 4: return V3_SPEAKER_L | V3_SPEAKER_R | V3_SPEAKER_LS | V3_SPEAKER_RS; + // regular 5.0 + case 5: return V3_SPEAKER_L | V3_SPEAKER_R | V3_SPEAKER_LS | V3_SPEAKER_RS | V3_SPEAKER_C; + // regular 6.0 + case 6: return V3_SPEAKER_L | V3_SPEAKER_R | V3_SPEAKER_LS | V3_SPEAKER_RS | V3_SPEAKER_SL | V3_SPEAKER_SR; + // regular 7.0 + case 7: return V3_SPEAKER_L | V3_SPEAKER_R | V3_SPEAKER_LS | V3_SPEAKER_RS | V3_SPEAKER_SL | V3_SPEAKER_SR | V3_SPEAKER_C; + // regular 8.0 + case 8: return V3_SPEAKER_L | V3_SPEAKER_R | V3_SPEAKER_LS | V3_SPEAKER_RS | V3_SPEAKER_SL | V3_SPEAKER_SR | V3_SPEAKER_C | V3_SPEAKER_S; + // regular 8.1 + case 9: return V3_SPEAKER_L | V3_SPEAKER_R | V3_SPEAKER_LS | V3_SPEAKER_RS | V3_SPEAKER_SL | V3_SPEAKER_SR | V3_SPEAKER_C | V3_SPEAKER_S | V3_SPEAKER_LFE; + // cinema 10.0 + case 10: return ( + V3_SPEAKER_L | V3_SPEAKER_R | + V3_SPEAKER_LS | V3_SPEAKER_RS | + V3_SPEAKER_SL | V3_SPEAKER_SR | + V3_SPEAKER_LC | V3_SPEAKER_RC | + V3_SPEAKER_C | V3_SPEAKER_S); + // cinema 10.1 + case 11: return ( + V3_SPEAKER_L | V3_SPEAKER_R | + V3_SPEAKER_LS | V3_SPEAKER_RS | + V3_SPEAKER_SL | V3_SPEAKER_SR | + V3_SPEAKER_LC | V3_SPEAKER_RC | + V3_SPEAKER_C | V3_SPEAKER_S | V3_SPEAKER_LFE); + default: + d_stderr("portCountToSpeaker error: got weirdly big number ports %u in a single bus", portCount); + return 0; + } + } + + template<bool isInput> + v3_speaker_arrangement getSpeakerArrangementForAudioPort(const BusInfo& busInfo, const uint32_t portGroupId, const uint32_t busId) const noexcept + { + switch (portGroupId) + { + case kPortGroupMono: + return V3_SPEAKER_M; + case kPortGroupStereo: + return V3_SPEAKER_L | V3_SPEAKER_R; + } + + if (busId < busInfo.groups) + return portCountToSpeaker(fPlugin.getAudioPortCountWithGroupId(isInput, portGroupId)); + + if (busInfo.audio != 0 && busId == busInfo.groups) + return portCountToSpeaker(busInfo.audioPorts); + + if (busInfo.sidechain != 0 && busId == busInfo.groups + busInfo.audio) + return portCountToSpeaker(busInfo.sidechainPorts); + + return V3_SPEAKER_M; + } + + template<bool isInput> + bool getAudioBusArrangement(uint32_t busId, v3_speaker_arrangement* const speaker) const + { + constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; + const BusInfo& busInfo(isInput ? inputBuses : outputBuses); + + for (uint32_t i=0; i<numPorts; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.busId != busId) + { + // d_debug("port.busId != busId: %d %d", port.busId, busId); + continue; + } + + *speaker = getSpeakerArrangementForAudioPort<isInput>(busInfo, port.groupId, busId); + // d_debug("getAudioBusArrangement %d enabled by value %lx", busId, *speaker); + return true; + } + + return false; + } + + template<bool isInput> + bool setAudioBusArrangement(v3_speaker_arrangement* const speakers, const uint32_t numBuses) + { + constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; + BusInfo& busInfo(isInput ? inputBuses : outputBuses); + bool* const enabledPorts = isInput + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + ? fEnabledInputs + #else + ? nullptr + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + : fEnabledOutputs; + #else + : nullptr; + #endif + + bool ok = true; + + for (uint32_t busId=0; busId<numBuses; ++busId) + { + const v3_speaker_arrangement arr = speakers[busId]; + + // d_debug("setAudioBusArrangement %d %d | %d %lx", (int)isInput, numBuses, busId, arr); + + for (uint32_t i=0; i<numPorts; ++i) + { + AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.busId != busId) + { + // d_debug("setAudioBusArrangement port.busId != busId: %d %d", port.busId, busId); + continue; + } + + // get the only valid speaker arrangement for this bus, assuming enabled + const v3_speaker_arrangement earr = getSpeakerArrangementForAudioPort<isInput>(busInfo, port.groupId, busId); + + // fail if host tries to map it to anything else + // FIXME should we allow to map speaker to zero as a way to disable it? + if (earr != arr /* && arr != 0 */) + { + ok = false; + continue; + } + + enabledPorts[i] = arr != 0; + } + } + + // disable any buses outside of the requested arrangement + const uint32_t totalBuses = busInfo.audio + busInfo.sidechain + busInfo.groups + busInfo.cvPorts; + + for (uint32_t busId=numBuses; busId<totalBuses; ++busId) + { + for (uint32_t i=0; i<numPorts; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.busId == busId) + { + enabledPorts[i] = false; + break; + } + } + } + + return ok; + } + #endif + + // ---------------------------------------------------------------------------------------------------------------- + // helper functions called during process, cannot block + + void updateParametersFromProcessing(v3_param_changes** const outparamsptr, const int32_t offset) + { + DISTRHO_SAFE_ASSERT_RETURN(outparamsptr != nullptr,); + + float curValue; + double normalized; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + for (v3_param_id i=kVst3InternalParameterBufferSize; i<=kVst3InternalParameterSampleRate; ++i) + { + if (! fParameterValuesChangedDuringProcessing[i]) + continue; + + normalized = plainParameterToNormalized(i, fCachedParameterValues[i]); + fParameterValuesChangedDuringProcessing[i] = false; + addParameterDataToHostOutputEvents(outparamsptr, i, normalized); + } + #endif + + for (uint32_t i=0; i<fParameterCount; ++i) + { + if (fPlugin.isParameterOutput(i)) + { + // NOTE: no output parameter support in VST3, simulate it here + curValue = fPlugin.getParameterValue(i); + + if (d_isEqual(curValue, fCachedParameterValues[kVst3InternalParameterBaseCount + i])) + continue; + } + else if (fPlugin.isParameterTrigger(i)) + { + // NOTE: no trigger support in VST3 parameters, simulate it here + curValue = fPlugin.getParameterValue(i); + + if (d_isEqual(curValue, fPlugin.getParameterDefault(i))) + continue; + + fPlugin.setParameterValue(i, curValue); + } + else if (fParameterValuesChangedDuringProcessing[kVst3InternalParameterBaseCount + i]) + { + fParameterValuesChangedDuringProcessing[kVst3InternalParameterBaseCount + i] = false; + curValue = fPlugin.getParameterValue(i); + } + else + { + continue; + } + + fCachedParameterValues[kVst3InternalParameterBaseCount + i] = curValue; + #if DISTRHO_PLUGIN_HAS_UI + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + i] = true; + #endif + + normalized = _getNormalizedParameterValue(i, curValue); + + if (! addParameterDataToHostOutputEvents(outparamsptr, kVst3InternalParameterCount + i, normalized, offset)) + break; + } + + #if DISTRHO_PLUGIN_WANT_LATENCY + const uint32_t latency = fPlugin.getLatency(); + + if (fLastKnownLatency != latency) + { + fLastKnownLatency = latency; + + normalized = plainParameterToNormalized(kVst3InternalParameterLatency, + fCachedParameterValues[kVst3InternalParameterLatency]); + addParameterDataToHostOutputEvents(outparamsptr, kVst3InternalParameterLatency, normalized); + } + #endif + } + + bool addParameterDataToHostOutputEvents(v3_param_changes** const outparamsptr, + v3_param_id paramId, + const double normalized, + const int32_t offset = 0) + { + int32_t index = 0; + v3_param_value_queue** const queue = v3_cpp_obj(outparamsptr)->add_param_data(outparamsptr, + ¶mId, &index); + DISTRHO_SAFE_ASSERT_RETURN(queue != nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(v3_cpp_obj(queue)->add_point(queue, 0, normalized, &index) == V3_OK, false); + + /* FLStudio gets confused with this one, skip it for now + if (offset != 0) + v3_cpp_obj(queue)->add_point(queue, offset, normalized, &index); + */ + + return true; + + // unused at the moment, buggy VST3 hosts :/ + (void)offset; + } + + #if DISTRHO_PLUGIN_HAS_UI + // ---------------------------------------------------------------------------------------------------------------- + // helper functions called during message passing, can block + + v3_message** createMessage(const char* const id) const + { + DISTRHO_SAFE_ASSERT_RETURN(fHostApplication != nullptr, nullptr); + + v3_tuid iid; + memcpy(iid, v3_message_iid, sizeof(v3_tuid)); + v3_message** msg = nullptr; + const v3_result res = v3_cpp_obj(fHostApplication)->create_instance(fHostApplication, iid, iid, (void**)&msg); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_TRUE, res, nullptr); + DISTRHO_SAFE_ASSERT_RETURN(msg != nullptr, nullptr); + + v3_cpp_obj(msg)->set_message_id(msg, id); + return msg; + } + + void sendParameterSetToUI(const v3_param_id rindex, const double value) const + { + v3_message** const message = createMessage("parameter-set"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 2); + v3_cpp_obj(attrlist)->set_int(attrlist, "rindex", rindex); + v3_cpp_obj(attrlist)->set_float(attrlist, "value", value); + v3_cpp_obj(fConnectionFromCtrlToView)->notify(fConnectionFromCtrlToView, message); + + v3_cpp_obj_unref(message); + } + + void sendStateSetToUI(const char* const key, const char* const value) const + { + v3_message** const message = createMessage("state-set"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 2); + v3_cpp_obj(attrlist)->set_int(attrlist, "key:length", std::strlen(key)); + v3_cpp_obj(attrlist)->set_int(attrlist, "value:length", std::strlen(value)); + v3_cpp_obj(attrlist)->set_string(attrlist, "key", ScopedUTF16String(key)); + v3_cpp_obj(attrlist)->set_string(attrlist, "value", ScopedUTF16String(value)); + v3_cpp_obj(fConnectionFromCtrlToView)->notify(fConnectionFromCtrlToView, message); + + v3_cpp_obj_unref(message); + } + + void sendReadyToUI() const + { + v3_message** const message = createMessage("ready"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 2); + v3_cpp_obj(fConnectionFromCtrlToView)->notify(fConnectionFromCtrlToView, message); + + v3_cpp_obj_unref(message); + } + #endif + + // ---------------------------------------------------------------------------------------------------------------- + // DPF callbacks + + #if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST + bool requestParameterValueChange(const uint32_t index, float) + { + fParameterValuesChangedDuringProcessing[kVst3InternalParameterBaseCount + index] = true; + return true; + } + + static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value) + { + return ((PluginVst3*)ptr)->requestParameterValueChange(index, value); + } + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + bool writeMidi(const MidiEvent& midiEvent) + { + DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN("MIDI output unsupported", fHostEventOutputHandle != nullptr, false); + + v3_event event; + std::memset(&event, 0, sizeof(event)); + event.sample_offset = midiEvent.frame; + + const uint8_t* const data = midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data; + + switch (data[0] & 0xf0) + { + case 0x80: + event.type = V3_EVENT_NOTE_OFF; + event.note_off.channel = data[0] & 0xf; + event.note_off.pitch = data[1]; + event.note_off.velocity = (float)data[2] / 127.0f; + // int32_t note_id; + // float tuning; + break; + case 0x90: + event.type = V3_EVENT_NOTE_ON; + event.note_on.channel = data[0] & 0xf; + event.note_on.pitch = data[1]; + // float tuning; + event.note_on.velocity = (float)data[2] / 127.0f; + // int32_t length; + // int32_t note_id; + break; + case 0xA0: + event.type = V3_EVENT_POLY_PRESSURE; + event.poly_pressure.channel = data[0] & 0xf; + event.poly_pressure.pitch = data[1]; + event.poly_pressure.pressure = (float)data[2] / 127.0f; + // int32_t note_id; + break; + case 0xB0: + event.type = V3_EVENT_LEGACY_MIDI_CC_OUT; + event.midi_cc_out.channel = data[0] & 0xf; + event.midi_cc_out.cc_number = data[1]; + event.midi_cc_out.value = data[2]; + if (midiEvent.size == 4) + event.midi_cc_out.value2 = midiEvent.size == 4; + break; + /* TODO how do we deal with program changes?? + case 0xC0: + break; + */ + case 0xD0: + event.type = V3_EVENT_LEGACY_MIDI_CC_OUT; + event.midi_cc_out.channel = data[0] & 0xf; + event.midi_cc_out.cc_number = 128; + event.midi_cc_out.value = data[1]; + break; + case 0xE0: + event.type = V3_EVENT_LEGACY_MIDI_CC_OUT; + event.midi_cc_out.channel = data[0] & 0xf; + event.midi_cc_out.cc_number = 129; + event.midi_cc_out.value = data[1]; + event.midi_cc_out.value2 = data[2]; + break; + default: + return true; + } + + return v3_cpp_obj(fHostEventOutputHandle)->add_event(fHostEventOutputHandle, &event) == V3_OK; + } + + static bool writeMidiCallback(void* const ptr, const MidiEvent& midiEvent) + { + return ((PluginVst3*)ptr)->writeMidi(midiEvent); + } + #endif +}; + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * VST3 low-level pointer thingies follow, proceed with care. + */ + +// -------------------------------------------------------------------------------------------------------------------- +// v3_funknown for static instances + +static uint32_t V3_API dpf_static_ref(void*) { return 1; } +static uint32_t V3_API dpf_static_unref(void*) { return 0; } + +// -------------------------------------------------------------------------------------------------------------------- +// v3_funknown for classes with a single instance + +template<class T> +static uint32_t V3_API dpf_single_instance_ref(void* const self) +{ + return ++(*static_cast<T**>(self))->refcounter; +} + +template<class T> +static uint32_t V3_API dpf_single_instance_unref(void* const self) +{ + return --(*static_cast<T**>(self))->refcounter; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Store components that we can't delete properly, to be cleaned up on module unload + +struct dpf_component; + +static std::vector<dpf_component**> gComponentGarbage; + +static uint32_t handleUncleanComponent(dpf_component** const componentptr) +{ + gComponentGarbage.push_back(componentptr); + return 0; +} + +#if DPF_VST3_USES_SEPARATE_CONTROLLER +// -------------------------------------------------------------------------------------------------------------------- +// Store controllers that we can't delete properly, to be cleaned up on module unload + +struct dpf_edit_controller; + +static std::vector<dpf_edit_controller**> gControllerGarbage; + +static uint32_t handleUncleanController(dpf_edit_controller** const controllerptr) +{ + gControllerGarbage.push_back(controllerptr); + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_comp2ctrl_connection_point + +struct dpf_comp2ctrl_connection_point : v3_connection_point_cpp { + std::atomic_int refcounter; + ScopedPointer<PluginVst3>& vst3; + v3_connection_point** other; + + dpf_comp2ctrl_connection_point(ScopedPointer<PluginVst3>& v) + : refcounter(1), + vst3(v), + other(nullptr) + { + // v3_funknown, single instance + query_interface = query_interface_connection_point; + ref = dpf_single_instance_ref<dpf_comp2ctrl_connection_point>; + unref = dpf_single_instance_unref<dpf_comp2ctrl_connection_point>; + + // v3_connection_point + point.connect = connect; + point.disconnect = disconnect; + point.notify = notify; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_connection_point(void* const self, const v3_tuid iid, void** const iface) + { + dpf_comp2ctrl_connection_point* const point = *static_cast<dpf_comp2ctrl_connection_point**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_connection_point_iid)) + { + d_debug("dpf_comp2ctrl_connection_point => %p %s %p | OK", self, tuid2str(iid), iface); + ++point->refcounter; + *iface = self; + return V3_OK; + } + + d_debug("dpf_comp2ctrl_connection_point => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point + + static v3_result V3_API connect(void* const self, v3_connection_point** const other) + { + d_debug("dpf_comp2ctrl_connection_point::connect => %p %p", self, other); + dpf_comp2ctrl_connection_point* const point = *static_cast<dpf_comp2ctrl_connection_point**>(self); + DISTRHO_SAFE_ASSERT_RETURN(point->other == nullptr, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(point->other != other, V3_INVALID_ARG); + + point->other = other; + + if (PluginVst3* const vst3 = point->vst3) + vst3->comp2ctrl_connect(other); + + return V3_OK; + } + + static v3_result V3_API disconnect(void* const self, v3_connection_point** const other) + { + d_debug("dpf_comp2ctrl_connection_point => %p %p", self, other); + dpf_comp2ctrl_connection_point* const point = *static_cast<dpf_comp2ctrl_connection_point**>(self); + DISTRHO_SAFE_ASSERT_RETURN(point->other != nullptr, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(point->other == other, V3_INVALID_ARG); + + if (PluginVst3* const vst3 = point->vst3) + vst3->comp2ctrl_disconnect(); + + point->other = nullptr; + + return V3_OK; + } + + static v3_result V3_API notify(void* const self, v3_message** const message) + { + dpf_comp2ctrl_connection_point* const point = *static_cast<dpf_comp2ctrl_connection_point**>(self); + + PluginVst3* const vst3 = point->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + v3_connection_point** const other = point->other; + DISTRHO_SAFE_ASSERT_RETURN(other != nullptr, V3_NOT_INITIALIZED); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr, V3_INVALID_ARG); + + int64_t target = 0; + const v3_result res = v3_cpp_obj(attrlist)->get_int(attrlist, "__dpf_msg_target__", &target); + DISTRHO_SAFE_ASSERT_RETURN(res == V3_OK, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(target == 1, target, V3_INTERNAL_ERR); + + // view -> edit controller -> component + return vst3->comp2ctrl_notify(message); + } +}; +#endif // DPF_VST3_USES_SEPARATE_CONTROLLER + +#if DISTRHO_PLUGIN_HAS_UI +// -------------------------------------------------------------------------------------------------------------------- +// dpf_ctrl2view_connection_point + +struct dpf_ctrl2view_connection_point : v3_connection_point_cpp { + ScopedPointer<PluginVst3>& vst3; + v3_connection_point** other; + + dpf_ctrl2view_connection_point(ScopedPointer<PluginVst3>& v) + : vst3(v), + other(nullptr) + { + // v3_funknown, single instance, used internally + query_interface = nullptr; + ref = nullptr; + unref = nullptr; + + // v3_connection_point + point.connect = connect; + point.disconnect = disconnect; + point.notify = notify; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point + + static v3_result V3_API connect(void* const self, v3_connection_point** const other) + { + d_debug("dpf_ctrl2view_connection_point::connect => %p %p", self, other); + dpf_ctrl2view_connection_point* const point = *static_cast<dpf_ctrl2view_connection_point**>(self); + DISTRHO_SAFE_ASSERT_RETURN(point->other == nullptr, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(point->other != other, V3_INVALID_ARG); + + point->other = other; + + if (PluginVst3* const vst3 = point->vst3) + vst3->ctrl2view_connect(other); + + return V3_OK; + } + + static v3_result V3_API disconnect(void* const self, v3_connection_point** const other) + { + d_debug("dpf_ctrl2view_connection_point::disconnect => %p %p", self, other); + dpf_ctrl2view_connection_point* const point = *static_cast<dpf_ctrl2view_connection_point**>(self); + DISTRHO_SAFE_ASSERT_RETURN(point->other != nullptr, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(point->other == other, V3_INVALID_ARG); + + if (PluginVst3* const vst3 = point->vst3) + vst3->ctrl2view_disconnect(); + + v3_cpp_obj_unref(point->other); + point->other = nullptr; + + return V3_OK; + } + + static v3_result V3_API notify(void* const self, v3_message** const message) + { + dpf_ctrl2view_connection_point* const point = *static_cast<dpf_ctrl2view_connection_point**>(self); + + PluginVst3* const vst3 = point->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + v3_connection_point** const other = point->other; + DISTRHO_SAFE_ASSERT_RETURN(other != nullptr, V3_NOT_INITIALIZED); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr, V3_INVALID_ARG); + + int64_t target = 0; + const v3_result res = v3_cpp_obj(attrlist)->get_int(attrlist, "__dpf_msg_target__", &target); + DISTRHO_SAFE_ASSERT_RETURN(res == V3_OK, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(target == 1 || target == 2, target, V3_INTERNAL_ERR); + + if (target == 1) + { + // view -> edit controller + return vst3->ctrl2view_notify(message); + } + else + { + // edit controller -> view + return v3_cpp_obj(other)->notify(other, message); + } + } +}; +#endif // DISTRHO_PLUGIN_HAS_UI + +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT +// -------------------------------------------------------------------------------------------------------------------- +// dpf_midi_mapping + +struct dpf_midi_mapping : v3_midi_mapping_cpp { + dpf_midi_mapping() + { + // v3_funknown, static + query_interface = query_interface_midi_mapping; + ref = dpf_static_ref; + unref = dpf_static_unref; + + // v3_midi_mapping + map.get_midi_controller_assignment = get_midi_controller_assignment; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_midi_mapping(void* const self, const v3_tuid iid, void** const iface) + { + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_midi_mapping_iid)) + { + d_debug("query_interface_midi_mapping => %p %s %p | OK", self, tuid2str(iid), iface); + *iface = self; + return V3_OK; + } + + d_debug("query_interface_midi_mapping => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_midi_mapping + + static v3_result V3_API get_midi_controller_assignment(void*, const int32_t bus, const int16_t channel, const int16_t cc, v3_param_id* const id) + { + DISTRHO_SAFE_ASSERT_INT_RETURN(bus == 0, bus, V3_FALSE); + DISTRHO_SAFE_ASSERT_INT_RETURN(channel >= 0 && channel < 16, channel, V3_FALSE); + DISTRHO_SAFE_ASSERT_INT_RETURN(cc >= 0 && cc < 130, cc, V3_FALSE); + + *id = kVst3InternalParameterMidiCC_start + channel * 130 + cc; + return V3_TRUE; + } + + DISTRHO_PREVENT_HEAP_ALLOCATION +}; +#endif // DISTRHO_PLUGIN_WANT_MIDI_INPUT + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_edit_controller + +struct dpf_edit_controller : v3_edit_controller_cpp { + std::atomic_int refcounter; + #if DISTRHO_PLUGIN_HAS_UI + ScopedPointer<dpf_ctrl2view_connection_point> connectionCtrl2View; + #endif + #if DPF_VST3_USES_SEPARATE_CONTROLLER + ScopedPointer<dpf_comp2ctrl_connection_point> connectionComp2Ctrl; + ScopedPointer<PluginVst3> vst3; + #else + ScopedPointer<PluginVst3>& vst3; + bool initialized; + #endif + // cached values + v3_component_handler** handler; + v3_host_application** const hostApplicationFromFactory; + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + v3_host_application** const hostApplicationFromComponent; + v3_host_application** hostApplicationFromComponentInitialize; + #endif + v3_host_application** hostApplicationFromInitialize; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + dpf_edit_controller(v3_host_application** const hostApp) + : refcounter(1), + vst3(nullptr), + #else + dpf_edit_controller(ScopedPointer<PluginVst3>& v, v3_host_application** const hostApp, v3_host_application** const hostComp) + : refcounter(1), + vst3(v), + initialized(false), + #endif + handler(nullptr), + hostApplicationFromFactory(hostApp), + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + hostApplicationFromComponent(hostComp), + hostApplicationFromComponentInitialize(nullptr), + #endif + hostApplicationFromInitialize(nullptr) + { + d_debug("dpf_edit_controller() with hostApplication %p", hostApplicationFromFactory); + + // make sure host application is valid through out this controller lifetime + if (hostApplicationFromFactory != nullptr) + v3_cpp_obj_ref(hostApplicationFromFactory); + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + if (hostApplicationFromComponent != nullptr) + v3_cpp_obj_ref(hostApplicationFromComponent); + #endif + + // v3_funknown, everything custom + query_interface = query_interface_edit_controller; + ref = ref_edit_controller; + unref = unref_edit_controller; + + // v3_plugin_base + base.initialize = initialize; + base.terminate = terminate; + + // v3_edit_controller + ctrl.set_component_state = set_component_state; + ctrl.set_state = set_state; + ctrl.get_state = get_state; + ctrl.get_parameter_count = get_parameter_count; + ctrl.get_parameter_info = get_parameter_info; + ctrl.get_parameter_string_for_value = get_parameter_string_for_value; + ctrl.get_parameter_value_for_string = get_parameter_value_for_string; + ctrl.normalised_parameter_to_plain = normalised_parameter_to_plain; + ctrl.plain_parameter_to_normalised = plain_parameter_to_normalised; + ctrl.get_parameter_normalised = get_parameter_normalised; + ctrl.set_parameter_normalised = set_parameter_normalised; + ctrl.set_component_handler = set_component_handler; + ctrl.create_view = create_view; + } + + ~dpf_edit_controller() + { + d_debug("~dpf_edit_controller()"); + #if DISTRHO_PLUGIN_HAS_UI + connectionCtrl2View = nullptr; + #endif + #if DPF_VST3_USES_SEPARATE_CONTROLLER + connectionComp2Ctrl = nullptr; + vst3 = nullptr; + #endif + + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + if (hostApplicationFromComponent != nullptr) + v3_cpp_obj_unref(hostApplicationFromComponent); + #endif + if (hostApplicationFromFactory != nullptr) + v3_cpp_obj_unref(hostApplicationFromFactory); + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_edit_controller(void* const self, const v3_tuid iid, void** const iface) + { + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_plugin_base_iid) || + v3_tuid_match(iid, v3_edit_controller_iid)) + { + d_debug("query_interface_edit_controller => %p %s %p | OK", self, tuid2str(iid), iface); + ++controller->refcounter; + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_midi_mapping_iid)) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + d_debug("query_interface_edit_controller => %p %s %p | OK convert static", self, tuid2str(iid), iface); + static dpf_midi_mapping midi_mapping; + static dpf_midi_mapping* midi_mapping_ptr = &midi_mapping; + *iface = &midi_mapping_ptr; + return V3_OK; + #else + d_debug("query_interface_edit_controller => %p %s %p | reject unused", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + #endif + } + + if (v3_tuid_match(iid, v3_connection_point_iid)) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + d_debug("query_interface_edit_controller => %p %s %p | OK convert %p", + self, tuid2str(iid), iface, controller->connectionComp2Ctrl.get()); + + if (controller->connectionComp2Ctrl == nullptr) + controller->connectionComp2Ctrl = new dpf_comp2ctrl_connection_point(controller->vst3); + else + ++controller->connectionComp2Ctrl->refcounter; + *iface = &controller->connectionComp2Ctrl; + return V3_OK; + #else + d_debug("query_interface_edit_controller => %p %s %p | reject unwanted", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + #endif + } + + d_debug("query_interface_edit_controller => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + } + + static uint32_t V3_API ref_edit_controller(void* const self) + { + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + const int refcount = ++controller->refcounter; + d_debug("dpf_edit_controller::ref => %p | refcount %i", self, refcount); + return refcount; + } + + static uint32_t V3_API unref_edit_controller(void* const self) + { + dpf_edit_controller** const controllerptr = static_cast<dpf_edit_controller**>(self); + dpf_edit_controller* const controller = *controllerptr; + + if (const int refcount = --controller->refcounter) + { + d_debug("dpf_edit_controller::unref => %p | refcount %i", self, refcount); + return refcount; + } + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + /** + * Some hosts will have unclean instances of a few of the controller child classes at this point. + * We check for those here, going through the whole possible chain to see if it is safe to delete. + * If not, we add this controller to the `gControllerGarbage` global which will take care of it during unload. + */ + + bool unclean = false; + + if (dpf_comp2ctrl_connection_point* const point = controller->connectionComp2Ctrl) + { + if (const int refcount = point->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete controller while component connection point still active (refcount %d)", refcount); + } + } + + if (unclean) + return handleUncleanController(controllerptr); + + d_debug("dpf_edit_controller::unref => %p | refcount is zero, deleting everything now!", self); + + delete controller; + delete controllerptr; + #else + d_debug("dpf_edit_controller::unref => %p | refcount is zero, deletion will be done by component later", self); + #endif + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_base + + static v3_result V3_API initialize(void* const self, v3_funknown** const context) + { + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + // check if already initialized + #if DPF_VST3_USES_SEPARATE_CONTROLLER + DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 == nullptr, V3_INVALID_ARG); + #else + DISTRHO_SAFE_ASSERT_RETURN(! controller->initialized, V3_INVALID_ARG); + #endif + + // query for host application + v3_host_application** hostApplication = nullptr; + if (context != nullptr) + v3_cpp_obj_query_interface(context, v3_host_application_iid, &hostApplication); + + d_debug("dpf_edit_controller::initialize => %p %p | host %p", self, context, hostApplication); + + // save it for later so we can unref it + controller->hostApplicationFromInitialize = hostApplication; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + // provide the factory application to the plugin if this new one is missing + if (hostApplication == nullptr) + hostApplication = controller->hostApplicationFromFactory; + + // default early values + if (d_nextBufferSize == 0) + d_nextBufferSize = 1024; + if (d_nextSampleRate <= 0.0) + d_nextSampleRate = 44100.0; + + d_nextCanRequestParameterValueChanges = true; + + // create the actual plugin + controller->vst3 = new PluginVst3(hostApplication, false); + + // set connection point if needed + if (dpf_comp2ctrl_connection_point* const point = controller->connectionComp2Ctrl) + { + if (point->other != nullptr) + controller->vst3->comp2ctrl_connect(point->other); + } + #else + // mark as initialized + controller->initialized = true; + #endif + + return V3_OK; + } + + static v3_result V3_API terminate(void* self) + { + d_debug("dpf_edit_controller::terminate => %p", self); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + // check if already terminated + DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 != nullptr, V3_INVALID_ARG); + + // delete actual plugin + controller->vst3 = nullptr; + #else + // check if already terminated + DISTRHO_SAFE_ASSERT_RETURN(controller->initialized, V3_INVALID_ARG); + + // mark as uninitialzed + controller->initialized = false; + #endif + + // unref host application received during initialize + if (controller->hostApplicationFromInitialize != nullptr) + { + v3_cpp_obj_unref(controller->hostApplicationFromInitialize); + controller->hostApplicationFromInitialize = nullptr; + } + + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_edit_controller + + static v3_result V3_API set_component_state(void* const self, v3_bstream** const stream) + { + d_debug("dpf_edit_controller::set_component_state => %p %p", self, stream); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->setState(stream); + #else + return V3_OK; + + // unused + (void)self; + (void)stream; + #endif + } + + static v3_result V3_API set_state(void* const self, v3_bstream** const stream) + { + d_debug("dpf_edit_controller::set_state => %p %p", self, stream); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 != nullptr, V3_NOT_INITIALIZED); + #endif + + return V3_NOT_IMPLEMENTED; + + // maybe unused + (void)self; + (void)stream; + } + + static v3_result V3_API get_state(void* const self, v3_bstream** const stream) + { + d_debug("dpf_edit_controller::get_state => %p %p", self, stream); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 != nullptr, V3_NOT_INITIALIZED); + #endif + + return V3_NOT_IMPLEMENTED; + + // maybe unused + (void)self; + (void)stream; + } + + static int32_t V3_API get_parameter_count(void* self) + { + // d_debug("dpf_edit_controller::get_parameter_count => %p", self); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterCount(); + } + + static v3_result V3_API get_parameter_info(void* self, int32_t param_idx, v3_param_info* param_info) + { + // d_debug("dpf_edit_controller::get_parameter_info => %p %i", self, param_idx); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterInfo(param_idx, param_info); + } + + static v3_result V3_API get_parameter_string_for_value(void* self, v3_param_id index, double normalized, v3_str_128 output) + { + // NOTE very noisy, called many times + // d_debug("dpf_edit_controller::get_parameter_string_for_value => %p %u %f %p", self, index, normalized, output); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterStringForValue(index, normalized, output); + } + + static v3_result V3_API get_parameter_value_for_string(void* self, v3_param_id index, int16_t* input, double* output) + { + d_debug("dpf_edit_controller::get_parameter_value_for_string => %p %u %p %p", self, index, input, output); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterValueForString(index, input, output); + } + + static double V3_API normalised_parameter_to_plain(void* self, v3_param_id index, double normalized) + { + d_debug("dpf_edit_controller::normalised_parameter_to_plain => %p %u %f", self, index, normalized); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->normalizedParameterToPlain(index, normalized); + } + + static double V3_API plain_parameter_to_normalised(void* self, v3_param_id index, double plain) + { + d_debug("dpf_edit_controller::plain_parameter_to_normalised => %p %u %f", self, index, plain); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->plainParameterToNormalized(index, plain); + } + + static double V3_API get_parameter_normalised(void* self, v3_param_id index) + { + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, 0.0); + + return vst3->getParameterNormalized(index); + } + + static v3_result V3_API set_parameter_normalised(void* const self, const v3_param_id index, const double normalized) + { + // d_debug("dpf_edit_controller::set_parameter_normalised => %p %u %f", self, index, normalized); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->setParameterNormalized(index, normalized); + } + + static v3_result V3_API set_component_handler(void* self, v3_component_handler** handler) + { + d_debug("dpf_edit_controller::set_component_handler => %p %p", self, handler); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + controller->handler = handler; + + if (PluginVst3* const vst3 = controller->vst3) + return vst3->setComponentHandler(handler); + + return V3_NOT_INITIALIZED; + } + + static v3_plugin_view** V3_API create_view(void* self, const char* name) + { + d_debug("dpf_edit_controller::create_view => %p %s", self, name); + + #if DISTRHO_PLUGIN_HAS_UI + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + d_debug("create_view has contexts %p %p", + controller->hostApplicationFromFactory, controller->hostApplicationFromInitialize); + + // plugin must be initialized + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, nullptr); + + d_debug("dpf_edit_controller::create_view => %p %s | edit-ctrl %p, factory %p", + self, name, + controller->hostApplicationFromInitialize, + controller->hostApplicationFromFactory); + + // we require a host application for message creation + v3_host_application** const host = controller->hostApplicationFromInitialize != nullptr + ? controller->hostApplicationFromInitialize + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + : controller->hostApplicationFromComponent != nullptr + ? controller->hostApplicationFromComponent + : controller->hostApplicationFromComponentInitialize != nullptr + ? controller->hostApplicationFromComponentInitialize + #endif + : controller->hostApplicationFromFactory; + DISTRHO_SAFE_ASSERT_RETURN(host != nullptr, nullptr); + + v3_plugin_view** const view = dpf_plugin_view_create(host, + vst3->getInstancePointer(), + vst3->getSampleRate()); + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, nullptr); + + v3_connection_point** uiconn = nullptr; + if (v3_cpp_obj_query_interface(view, v3_connection_point_iid, &uiconn) == V3_OK) + { + d_debug("view connection query ok %p", uiconn); + controller->connectionCtrl2View = new dpf_ctrl2view_connection_point(controller->vst3); + + v3_connection_point** const ctrlconn = (v3_connection_point**)&controller->connectionCtrl2View; + + v3_cpp_obj(uiconn)->connect(uiconn, ctrlconn); + v3_cpp_obj(ctrlconn)->connect(ctrlconn, uiconn); + } + else + { + controller->connectionCtrl2View = nullptr; + } + + return view; + #else + return nullptr; + #endif + + // maybe unused + (void)self; + (void)name; + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_process_context_requirements + +struct dpf_process_context_requirements : v3_process_context_requirements_cpp { + dpf_process_context_requirements() + { + // v3_funknown, static + query_interface = query_interface_process_context_requirements; + ref = dpf_static_ref; + unref = dpf_static_unref; + + // v3_process_context_requirements + req.get_process_context_requirements = get_process_context_requirements; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_process_context_requirements(void* const self, const v3_tuid iid, void** const iface) + { + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_process_context_requirements_iid)) + { + d_debug("query_interface_process_context_requirements => %p %s %p | OK", self, tuid2str(iid), iface); + *iface = self; + return V3_OK; + } + + d_debug("query_interface_process_context_requirements => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_process_context_requirements + + static uint32_t V3_API get_process_context_requirements(void*) + { + #if DISTRHO_PLUGIN_WANT_TIMEPOS + return 0x0 + | V3_PROCESS_CTX_NEED_CONTINUOUS_TIME // V3_PROCESS_CTX_CONT_TIME_VALID + | V3_PROCESS_CTX_NEED_PROJECT_TIME // V3_PROCESS_CTX_PROJECT_TIME_VALID + | V3_PROCESS_CTX_NEED_TEMPO // V3_PROCESS_CTX_TEMPO_VALID + | V3_PROCESS_CTX_NEED_TIME_SIG // V3_PROCESS_CTX_TIME_SIG_VALID + | V3_PROCESS_CTX_NEED_TRANSPORT_STATE; // V3_PROCESS_CTX_PLAYING + #else + return 0x0; + #endif + } + + DISTRHO_PREVENT_HEAP_ALLOCATION +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_audio_processor + +struct dpf_audio_processor : v3_audio_processor_cpp { + std::atomic_int refcounter; + ScopedPointer<PluginVst3>& vst3; + + dpf_audio_processor(ScopedPointer<PluginVst3>& v) + : refcounter(1), + vst3(v) + { + // v3_funknown, single instance + query_interface = query_interface_audio_processor; + ref = dpf_single_instance_ref<dpf_audio_processor>; + unref = dpf_single_instance_unref<dpf_audio_processor>; + + // v3_audio_processor + proc.set_bus_arrangements = set_bus_arrangements; + proc.get_bus_arrangement = get_bus_arrangement; + proc.can_process_sample_size = can_process_sample_size; + proc.get_latency_samples = get_latency_samples; + proc.setup_processing = setup_processing; + proc.set_processing = set_processing; + proc.process = process; + proc.get_tail_samples = get_tail_samples; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_audio_processor(void* const self, const v3_tuid iid, void** const iface) + { + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_audio_processor_iid)) + { + d_debug("query_interface_audio_processor => %p %s %p | OK", self, tuid2str(iid), iface); + ++processor->refcounter; + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_process_context_requirements_iid)) + { + d_debug("query_interface_audio_processor => %p %s %p | OK convert static", self, tuid2str(iid), iface); + static dpf_process_context_requirements context_req; + static dpf_process_context_requirements* context_req_ptr = &context_req; + *iface = &context_req_ptr; + return V3_OK; + } + + d_debug("query_interface_audio_processor => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_audio_processor + + static v3_result V3_API set_bus_arrangements(void* const self, + v3_speaker_arrangement* const inputs, const int32_t num_inputs, + v3_speaker_arrangement* const outputs, const int32_t num_outputs) + { + // NOTE this is called a bunch of times in JUCE hosts + d_debug("dpf_audio_processor::set_bus_arrangements => %p %p %i %p %i", + self, inputs, num_inputs, outputs, num_outputs); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return processor->vst3->setBusArrangements(inputs, num_inputs, outputs, num_outputs); + } + + static v3_result V3_API get_bus_arrangement(void* const self, const int32_t bus_direction, + const int32_t idx, v3_speaker_arrangement* const arr) + { + d_debug("dpf_audio_processor::get_bus_arrangement => %p %s %i %p", + self, v3_bus_direction_str(bus_direction), idx, arr); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return processor->vst3->getBusArrangement(bus_direction, idx, arr); + } + + static v3_result V3_API can_process_sample_size(void*, const int32_t symbolic_sample_size) + { + // NOTE runs during RT + // d_debug("dpf_audio_processor::can_process_sample_size => %i", symbolic_sample_size); + return symbolic_sample_size == V3_SAMPLE_32 ? V3_OK : V3_NOT_IMPLEMENTED; + } + + static uint32_t V3_API get_latency_samples(void* const self) + { + d_debug("dpf_audio_processor::get_latency_samples => %p", self); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, 0); + + return processor->vst3->getLatencySamples(); + } + + static v3_result V3_API setup_processing(void* const self, v3_process_setup* const setup) + { + d_debug("dpf_audio_processor::setup_processing => %p %p", self, setup); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + d_debug("dpf_audio_processor::setup_processing => %p %p | %d %f", self, setup, setup->max_block_size, setup->sample_rate); + + d_nextBufferSize = setup->max_block_size; + d_nextSampleRate = setup->sample_rate; + return processor->vst3->setupProcessing(setup); + } + + static v3_result V3_API set_processing(void* const self, const v3_bool state) + { + d_debug("dpf_audio_processor::set_processing => %p %u", self, state); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return processor->vst3->setProcessing(state); + } + + static v3_result V3_API process(void* const self, v3_process_data* const data) + { + // NOTE runs during RT + // d_debug("dpf_audio_processor::process => %p", self); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return processor->vst3->process(data); + } + + static uint32_t V3_API get_tail_samples(void* const self) + { + d_debug("dpf_audio_processor::get_tail_samples => %p", self); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, 0); + + return processor->vst3->getTailSamples(); + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_component + +struct dpf_component : v3_component_cpp { + std::atomic_int refcounter; + ScopedPointer<dpf_audio_processor> processor; + #if DPF_VST3_USES_SEPARATE_CONTROLLER + ScopedPointer<dpf_comp2ctrl_connection_point> connectionComp2Ctrl; + #else + ScopedPointer<dpf_edit_controller> controller; + #endif + ScopedPointer<PluginVst3> vst3; + v3_host_application** const hostApplicationFromFactory; + v3_host_application** hostApplicationFromInitialize; + + dpf_component(v3_host_application** const host) + : refcounter(1), + hostApplicationFromFactory(host), + hostApplicationFromInitialize(nullptr) + { + d_debug("dpf_component() with hostApplication %p", hostApplicationFromFactory); + + // make sure host application is valid through out this component lifetime + if (hostApplicationFromFactory != nullptr) + v3_cpp_obj_ref(hostApplicationFromFactory); + + // v3_funknown, everything custom + query_interface = query_interface_component; + ref = ref_component; + unref = unref_component; + + // v3_plugin_base + base.initialize = initialize; + base.terminate = terminate; + + // v3_component + comp.get_controller_class_id = get_controller_class_id; + comp.set_io_mode = set_io_mode; + comp.get_bus_count = get_bus_count; + comp.get_bus_info = get_bus_info; + comp.get_routing_info = get_routing_info; + comp.activate_bus = activate_bus; + comp.set_active = set_active; + comp.set_state = set_state; + comp.get_state = get_state; + } + + ~dpf_component() + { + d_debug("~dpf_component()"); + processor = nullptr; + #if DPF_VST3_USES_SEPARATE_CONTROLLER + connectionComp2Ctrl = nullptr; + #else + controller = nullptr; + #endif + vst3 = nullptr; + + if (hostApplicationFromFactory != nullptr) + v3_cpp_obj_unref(hostApplicationFromFactory); + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_component(void* const self, const v3_tuid iid, void** const iface) + { + dpf_component* const component = *static_cast<dpf_component**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_plugin_base_iid) || + v3_tuid_match(iid, v3_component_iid)) + { + d_debug("query_interface_component => %p %s %p | OK", self, tuid2str(iid), iface); + ++component->refcounter; + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_midi_mapping_iid)) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + d_debug("query_interface_component => %p %s %p | OK convert static", self, tuid2str(iid), iface); + static dpf_midi_mapping midi_mapping; + static dpf_midi_mapping* midi_mapping_ptr = &midi_mapping; + *iface = &midi_mapping_ptr; + return V3_OK; + #else + d_debug("query_interface_component => %p %s %p | reject unused", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + #endif + } + + if (v3_tuid_match(iid, v3_audio_processor_iid)) + { + d_debug("query_interface_component => %p %s %p | OK convert %p", + self, tuid2str(iid), iface, component->processor.get()); + + if (component->processor == nullptr) + component->processor = new dpf_audio_processor(component->vst3); + else + ++component->processor->refcounter; + *iface = &component->processor; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_connection_point_iid)) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + d_debug("query_interface_component => %p %s %p | OK convert %p", + self, tuid2str(iid), iface, component->connectionComp2Ctrl.get()); + + if (component->connectionComp2Ctrl == nullptr) + component->connectionComp2Ctrl = new dpf_comp2ctrl_connection_point(component->vst3); + else + ++component->connectionComp2Ctrl->refcounter; + *iface = &component->connectionComp2Ctrl; + return V3_OK; + #else + d_debug("query_interface_component => %p %s %p | reject unwanted", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + #endif + } + + if (v3_tuid_match(iid, v3_edit_controller_iid)) + { + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + d_debug("query_interface_component => %p %s %p | OK convert %p", + self, tuid2str(iid), iface, component->controller.get()); + + if (component->controller == nullptr) + component->controller = new dpf_edit_controller(component->vst3, + component->hostApplicationFromFactory, + component->hostApplicationFromInitialize); + else + ++component->controller->refcounter; + *iface = &component->controller; + return V3_OK; + #else + d_debug("query_interface_component => %p %s %p | reject unwanted", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + #endif + } + + d_debug("query_interface_component => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + } + + static uint32_t V3_API ref_component(void* const self) + { + dpf_component* const component = *static_cast<dpf_component**>(self); + const int refcount = ++component->refcounter; + d_debug("dpf_component::ref => %p | refcount %i", self, refcount); + return refcount; + } + + static uint32_t V3_API unref_component(void* const self) + { + dpf_component** const componentptr = static_cast<dpf_component**>(self); + dpf_component* const component = *componentptr; + + if (const int refcount = --component->refcounter) + { + d_debug("dpf_component::unref => %p | refcount %i", self, refcount); + return refcount; + } + + /** + * Some hosts will have unclean instances of a few of the component child classes at this point. + * We check for those here, going through the whole possible chain to see if it is safe to delete. + * If not, we add this component to the `gComponentGarbage` global which will take care of it during unload. + */ + + bool unclean = false; + + if (dpf_audio_processor* const proc = component->processor) + { + if (const int refcount = proc->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete component while audio processor still active (refcount %d)", refcount); + } + } + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + if (dpf_comp2ctrl_connection_point* const point = component->connectionComp2Ctrl) + { + if (const int refcount = point->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete component while connection point still active (refcount %d)", refcount); + } + } + #else + if (dpf_edit_controller* const controller = component->controller) + { + if (const int refcount = controller->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete component while edit controller still active (refcount %d)", refcount); + } + } + #endif + + if (unclean) + return handleUncleanComponent(componentptr); + + d_debug("dpf_component::unref => %p | refcount is zero, deleting everything now!", self); + + delete component; + delete componentptr; + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_base + + static v3_result V3_API initialize(void* const self, v3_funknown** const context) + { + dpf_component* const component = *static_cast<dpf_component**>(self); + + // check if already initialized + DISTRHO_SAFE_ASSERT_RETURN(component->vst3 == nullptr, V3_INVALID_ARG); + + // query for host application + v3_host_application** hostApplication = nullptr; + if (context != nullptr) + v3_cpp_obj_query_interface(context, v3_host_application_iid, &hostApplication); + + d_debug("dpf_component::initialize => %p %p | hostApplication %p", self, context, hostApplication); + + // save it for later so we can unref it + component->hostApplicationFromInitialize = hostApplication; + + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + // save it in edit controller too, needed for some hosts + if (component->controller != nullptr) + component->controller->hostApplicationFromComponentInitialize = hostApplication; + #endif + + // provide the factory application to the plugin if this new one is missing + if (hostApplication == nullptr) + hostApplication = component->hostApplicationFromFactory; + + // default early values + if (d_nextBufferSize == 0) + d_nextBufferSize = 1024; + if (d_nextSampleRate <= 0.0) + d_nextSampleRate = 44100.0; + + d_nextCanRequestParameterValueChanges = true; + + // create the actual plugin + component->vst3 = new PluginVst3(hostApplication, true); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + // set connection point if needed + if (dpf_comp2ctrl_connection_point* const point = component->connectionComp2Ctrl) + { + if (point->other != nullptr) + component->vst3->comp2ctrl_connect(point->other); + } + #endif + + return V3_OK; + } + + static v3_result V3_API terminate(void* const self) + { + d_debug("dpf_component::terminate => %p", self); + dpf_component* const component = *static_cast<dpf_component**>(self); + + // check if already terminated + DISTRHO_SAFE_ASSERT_RETURN(component->vst3 != nullptr, V3_INVALID_ARG); + + // delete actual plugin + component->vst3 = nullptr; + + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + // remove previous host application saved during initialize + if (component->controller != nullptr) + component->controller->hostApplicationFromComponentInitialize = nullptr; + #endif + + // unref host application received during initialize + if (component->hostApplicationFromInitialize != nullptr) + { + v3_cpp_obj_unref(component->hostApplicationFromInitialize); + component->hostApplicationFromInitialize = nullptr; + } + + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_component + + static v3_result V3_API get_controller_class_id(void*, v3_tuid class_id) + { + d_debug("dpf_component::get_controller_class_id => %p", class_id); + + std::memcpy(class_id, dpf_tuid_controller, sizeof(v3_tuid)); + return V3_OK; + } + + static v3_result V3_API set_io_mode(void* const self, const int32_t io_mode) + { + d_debug("dpf_component::set_io_mode => %p %i", self, io_mode); + + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + // TODO + return V3_NOT_IMPLEMENTED; + + // unused + (void)io_mode; + } + + static int32_t V3_API get_bus_count(void* const self, const int32_t media_type, const int32_t bus_direction) + { + // NOTE runs during RT + // d_debug("dpf_component::get_bus_count => %p %s %s", + // self, v3_media_type_str(media_type), v3_bus_direction_str(bus_direction)); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + const int32_t ret = vst3->getBusCount(media_type, bus_direction); + // d_debug("dpf_component::get_bus_count returns %i", ret); + return ret; + } + + static v3_result V3_API get_bus_info(void* const self, const int32_t media_type, const int32_t bus_direction, + const int32_t bus_idx, v3_bus_info* const info) + { + d_debug("dpf_component::get_bus_info => %p %s %s %i %p", + self, v3_media_type_str(media_type), v3_bus_direction_str(bus_direction), bus_idx, info); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getBusInfo(media_type, bus_direction, bus_idx, info); + } + + static v3_result V3_API get_routing_info(void* const self, v3_routing_info* const input, v3_routing_info* const output) + { + d_debug("dpf_component::get_routing_info => %p %p %p", self, input, output); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getRoutingInfo(input, output); + } + + static v3_result V3_API activate_bus(void* const self, const int32_t media_type, const int32_t bus_direction, + const int32_t bus_idx, const v3_bool state) + { + // NOTE this is called a bunch of times + // d_debug("dpf_component::activate_bus => %p %s %s %i %u", + // self, v3_media_type_str(media_type), v3_bus_direction_str(bus_direction), bus_idx, state); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->activateBus(media_type, bus_direction, bus_idx, state); + } + + static v3_result V3_API set_active(void* const self, const v3_bool state) + { + d_debug("dpf_component::set_active => %p %u", self, state); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return component->vst3->setActive(state); + } + + static v3_result V3_API set_state(void* const self, v3_bstream** const stream) + { + d_debug("dpf_component::set_state => %p", self); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->setState(stream); + } + + static v3_result V3_API get_state(void* const self, v3_bstream** const stream) + { + d_debug("dpf_component::get_state => %p %p", self, stream); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getState(stream); + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// Dummy plugin to get data from + +static ScopedPointer<PluginExporter> sPlugin; + +static const char* getPluginCategories() +{ + static String categories; + static bool firstInit = true; + + if (firstInit) + { + #ifdef DISTRHO_PLUGIN_VST3_CATEGORIES + categories = DISTRHO_PLUGIN_VST3_CATEGORIES; + #elif DISTRHO_PLUGIN_IS_SYNTH + categories = "Instrument"; + #endif + firstInit = false; + } + + return categories.buffer(); +} + +static const char* getPluginVersion() +{ + static String version; + + if (version.isEmpty()) + { + const uint32_t versionNum = sPlugin->getVersion(); + + char versionBuf[64]; + std::snprintf(versionBuf, sizeof(versionBuf)-1, "%d.%d.%d", + (versionNum >> 16) & 0xff, + (versionNum >> 8) & 0xff, + (versionNum >> 0) & 0xff); + versionBuf[sizeof(versionBuf)-1] = '\0'; + version = versionBuf; + } + + return version.buffer(); +} + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_factory + +struct dpf_factory : v3_plugin_factory_cpp { + std::atomic_int refcounter; + + // cached values + v3_funknown** hostContext; + + dpf_factory() + : refcounter(1), + hostContext(nullptr) + { + // v3_funknown, static + query_interface = query_interface_factory; + ref = ref_factory; + unref = unref_factory; + + // v3_plugin_factory + v1.get_factory_info = get_factory_info; + v1.num_classes = num_classes; + v1.get_class_info = get_class_info; + v1.create_instance = create_instance; + + // v3_plugin_factory_2 + v2.get_class_info_2 = get_class_info_2; + + // v3_plugin_factory_3 + v3.get_class_info_utf16 = get_class_info_utf16; + v3.set_host_context = set_host_context; + } + + ~dpf_factory() + { + // unref old context if there is one + if (hostContext != nullptr) + v3_cpp_obj_unref(hostContext); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + if (gControllerGarbage.size() != 0) + { + d_debug("DPF notice: cleaning up previously undeleted controllers now"); + + for (std::vector<dpf_edit_controller**>::iterator it = gControllerGarbage.begin(); + it != gControllerGarbage.end(); ++it) + { + dpf_edit_controller** const controllerptr = *it; + dpf_edit_controller* const controller = *controllerptr; + delete controller; + delete controllerptr; + } + + gControllerGarbage.clear(); + } + #endif + + if (gComponentGarbage.size() != 0) + { + d_debug("DPF notice: cleaning up previously undeleted components now"); + + for (std::vector<dpf_component**>::iterator it = gComponentGarbage.begin(); + it != gComponentGarbage.end(); ++it) + { + dpf_component** const componentptr = *it; + dpf_component* const component = *componentptr; + delete component; + delete componentptr; + } + + gComponentGarbage.clear(); + } + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_factory(void* const self, const v3_tuid iid, void** const iface) + { + dpf_factory* const factory = *static_cast<dpf_factory**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_plugin_factory_iid) || + v3_tuid_match(iid, v3_plugin_factory_2_iid) || + v3_tuid_match(iid, v3_plugin_factory_3_iid)) + { + d_debug("query_interface_factory => %p %s %p | OK", self, tuid2str(iid), iface); + ++factory->refcounter; + *iface = self; + return V3_OK; + } + + d_debug("query_interface_factory => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + static uint32_t V3_API ref_factory(void* const self) + { + dpf_factory* const factory = *static_cast<dpf_factory**>(self); + const int refcount = ++factory->refcounter; + d_debug("ref_factory::ref => %p | refcount %i", self, refcount); + return refcount; + } + + static uint32_t V3_API unref_factory(void* const self) + { + dpf_factory** const factoryptr = static_cast<dpf_factory**>(self); + dpf_factory* const factory = *factoryptr; + + if (const int refcount = --factory->refcounter) + { + d_debug("unref_factory::unref => %p | refcount %i", self, refcount); + return refcount; + } + + d_debug("unref_factory::unref => %p | refcount is zero, deleting factory", self); + + delete factory; + delete factoryptr; + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_factory + + static v3_result V3_API get_factory_info(void*, v3_factory_info* const info) + { + d_debug("dpf_factory::get_factory_info => %p", info); + std::memset(info, 0, sizeof(*info)); + + info->flags = 0x10; // unicode + d_strncpy(info->vendor, sPlugin->getMaker(), ARRAY_SIZE(info->vendor)); + d_strncpy(info->url, sPlugin->getHomePage(), ARRAY_SIZE(info->url)); + // d_strncpy(info->email, "", ARRAY_SIZE(info->email)); // TODO + return V3_OK; + } + + static int32_t V3_API num_classes(void*) + { + d_debug("dpf_factory::num_classes"); + #if DPF_VST3_USES_SEPARATE_CONTROLLER + return 2; // factory can create component and edit-controller + #else + return 1; // factory can only create component, edit-controller must be casted + #endif + } + + static v3_result V3_API get_class_info(void*, const int32_t idx, v3_class_info* const info) + { + d_debug("dpf_factory::get_class_info => %i %p", idx, info); + std::memset(info, 0, sizeof(*info)); + DISTRHO_SAFE_ASSERT_RETURN(idx <= 2, V3_INVALID_ARG); + + info->cardinality = 0x7FFFFFFF; + d_strncpy(info->name, sPlugin->getName(), ARRAY_SIZE(info->name)); + + if (idx == 0) + { + std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); + d_strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category)); + } + else + { + std::memcpy(info->class_id, dpf_tuid_controller, sizeof(v3_tuid)); + d_strncpy(info->category, "Component Controller Class", ARRAY_SIZE(info->category)); + } + + return V3_OK; + } + + static v3_result V3_API create_instance(void* self, const v3_tuid class_id, const v3_tuid iid, void** const instance) + { + d_debug("dpf_factory::create_instance => %p %s %s %p", self, tuid2str(class_id), tuid2str(iid), instance); + dpf_factory* const factory = *static_cast<dpf_factory**>(self); + + // query for host application + v3_host_application** hostApplication = nullptr; + if (factory->hostContext != nullptr) + v3_cpp_obj_query_interface(factory->hostContext, v3_host_application_iid, &hostApplication); + + // create component + if (v3_tuid_match(class_id, *(const v3_tuid*)&dpf_tuid_class) && (v3_tuid_match(iid, v3_component_iid) || + v3_tuid_match(iid, v3_funknown_iid))) + { + dpf_component** const componentptr = new dpf_component*; + *componentptr = new dpf_component(hostApplication); + *instance = static_cast<void*>(componentptr); + return V3_OK; + } + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + // create edit controller + if (v3_tuid_match(class_id, *(const v3_tuid*)&dpf_tuid_controller) && (v3_tuid_match(iid, v3_edit_controller_iid) || + v3_tuid_match(iid, v3_funknown_iid))) + { + dpf_edit_controller** const controllerptr = new dpf_edit_controller*; + *controllerptr = new dpf_edit_controller(hostApplication); + *instance = static_cast<void*>(controllerptr); + return V3_OK; + } + #endif + + // unsupported, roll back host application + if (hostApplication != nullptr) + v3_cpp_obj_unref(hostApplication); + + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_factory_2 + + static v3_result V3_API get_class_info_2(void*, const int32_t idx, v3_class_info_2* const info) + { + d_debug("dpf_factory::get_class_info_2 => %i %p", idx, info); + std::memset(info, 0, sizeof(*info)); + DISTRHO_SAFE_ASSERT_RETURN(idx <= 2, V3_INVALID_ARG); + + info->cardinality = 0x7FFFFFFF; + #if DPF_VST3_USES_SEPARATE_CONTROLLER || !DISTRHO_PLUGIN_HAS_UI + info->class_flags = V3_DISTRIBUTABLE; + #endif + d_strncpy(info->sub_categories, getPluginCategories(), ARRAY_SIZE(info->sub_categories)); + d_strncpy(info->name, sPlugin->getName(), ARRAY_SIZE(info->name)); + d_strncpy(info->vendor, sPlugin->getMaker(), ARRAY_SIZE(info->vendor)); + d_strncpy(info->version, getPluginVersion(), ARRAY_SIZE(info->version)); + d_strncpy(info->sdk_version, "VST 3.7.4", ARRAY_SIZE(info->sdk_version)); + + if (idx == 0) + { + std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); + d_strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category)); + } + else + { + std::memcpy(info->class_id, dpf_tuid_controller, sizeof(v3_tuid)); + d_strncpy(info->category, "Component Controller Class", ARRAY_SIZE(info->category)); + } + + return V3_OK; + } + + // ------------------------------------------------------------------------------------------------------------ + // v3_plugin_factory_3 + + static v3_result V3_API get_class_info_utf16(void*, const int32_t idx, v3_class_info_3* const info) + { + d_debug("dpf_factory::get_class_info_utf16 => %i %p", idx, info); + std::memset(info, 0, sizeof(*info)); + DISTRHO_SAFE_ASSERT_RETURN(idx <= 2, V3_INVALID_ARG); + + info->cardinality = 0x7FFFFFFF; + #if DPF_VST3_USES_SEPARATE_CONTROLLER || !DISTRHO_PLUGIN_HAS_UI + info->class_flags = V3_DISTRIBUTABLE; + #endif + d_strncpy(info->sub_categories, getPluginCategories(), ARRAY_SIZE(info->sub_categories)); + DISTRHO_NAMESPACE::strncpy_utf16(info->name, sPlugin->getName(), ARRAY_SIZE(info->name)); + DISTRHO_NAMESPACE::strncpy_utf16(info->vendor, sPlugin->getMaker(), ARRAY_SIZE(info->vendor)); + DISTRHO_NAMESPACE::strncpy_utf16(info->version, getPluginVersion(), ARRAY_SIZE(info->version)); + DISTRHO_NAMESPACE::strncpy_utf16(info->sdk_version, "Travesty 3.7.4", ARRAY_SIZE(info->sdk_version)); + + if (idx == 0) + { + std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); + d_strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category)); + } + else + { + std::memcpy(info->class_id, dpf_tuid_controller, sizeof(v3_tuid)); + d_strncpy(info->category, "Component Controller Class", ARRAY_SIZE(info->category)); + } + + return V3_OK; + } + + static v3_result V3_API set_host_context(void* const self, v3_funknown** const context) + { + d_debug("dpf_factory::set_host_context => %p %p", self, context); + dpf_factory* const factory = *static_cast<dpf_factory**>(self); + + // unref old context if there is one + if (factory->hostContext != nullptr) + v3_cpp_obj_unref(factory->hostContext); + + // store new context + factory->hostContext = context; + + // make sure the object keeps being valid for a while + if (context != nullptr) + v3_cpp_obj_ref(context); + + return V3_OK; + } +}; + +END_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- +// VST3 entry point + +DISTRHO_PLUGIN_EXPORT +const void* GetPluginFactory(void); + +const void* GetPluginFactory(void) +{ + USE_NAMESPACE_DISTRHO; + dpf_factory** const factoryptr = new dpf_factory*; + *factoryptr = new dpf_factory; + return static_cast<void*>(factoryptr); +} + +// -------------------------------------------------------------------------------------------------------------------- +// OS specific module load + +#if defined(DISTRHO_OS_MAC) +# define ENTRYFNNAME bundleEntry +# define ENTRYFNNAMEARGS void* +# define EXITFNNAME bundleExit +#elif defined(DISTRHO_OS_WINDOWS) +# define ENTRYFNNAME InitDll +# define ENTRYFNNAMEARGS void +# define EXITFNNAME ExitDll +#else +# define ENTRYFNNAME ModuleEntry +# define ENTRYFNNAMEARGS void* +# define EXITFNNAME ModuleExit +#endif + +DISTRHO_PLUGIN_EXPORT +bool ENTRYFNNAME(ENTRYFNNAMEARGS); + +bool ENTRYFNNAME(ENTRYFNNAMEARGS) +{ + USE_NAMESPACE_DISTRHO; + + // find plugin bundle + static String bundlePath; + if (bundlePath.isEmpty()) + { + String tmpPath(getBinaryFilename()); + tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP)); + tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP)); + + if (tmpPath.endsWith(DISTRHO_OS_SEP_STR "Contents")) + { + tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP)); + bundlePath = tmpPath; + d_nextBundlePath = bundlePath.buffer(); + } + else + { + bundlePath = "error"; + } + } + + // init dummy plugin and set uniqueId + if (sPlugin == nullptr) + { + // set valid but dummy values + d_nextBufferSize = 512; + d_nextSampleRate = 44100.0; + d_nextPluginIsDummy = true; + d_nextCanRequestParameterValueChanges = true; + + // Create dummy plugin to get data from + sPlugin = new PluginExporter(nullptr, nullptr, nullptr, nullptr); + + // unset + d_nextBufferSize = 0; + d_nextSampleRate = 0.0; + d_nextPluginIsDummy = false; + d_nextCanRequestParameterValueChanges = false; + + dpf_tuid_class[2] = dpf_tuid_component[2] = dpf_tuid_controller[2] + = dpf_tuid_processor[2] = dpf_tuid_view[2] = sPlugin->getUniqueId(); + } + + return true; +} + +DISTRHO_PLUGIN_EXPORT +bool EXITFNNAME(void); + +bool EXITFNNAME(void) +{ + DISTRHO_NAMESPACE::sPlugin = nullptr; + return true; +} + +#undef ENTRYFNNAME +#undef EXITFNNAME + +// --------------------------------------------------------------------------------------------------------------------