Mercurial > hg > pub > prymula > com
view 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 source
/* * 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 // --------------------------------------------------------------------------------------------------------------------