From 6e96ae86cb38cfca0898ad5abbb6a313b0c22ef6 Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 25 May 2022 23:39:10 +0100 Subject: [PATCH] Initial code for juce-less VST3 native hosting Signed-off-by: falkTX --- source/backend/plugin/CarlaPluginVST2.cpp | 46 +- source/backend/plugin/CarlaPluginVST3.cpp | 2064 ++++++++++++++++++++- source/discovery/carla-discovery.cpp | 6 +- 3 files changed, 2033 insertions(+), 83 deletions(-) diff --git a/source/backend/plugin/CarlaPluginVST2.cpp b/source/backend/plugin/CarlaPluginVST2.cpp index f3db14431..369857f50 100644 --- a/source/backend/plugin/CarlaPluginVST2.cpp +++ b/source/backend/plugin/CarlaPluginVST2.cpp @@ -1,6 +1,6 @@ /* * Carla VST Plugin - * Copyright (C) 2011-2021 Filipe Coelho + * Copyright (C) 2011-2022 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -158,18 +158,18 @@ public: clearBuffers(); #ifdef CARLA_OS_MAC - if (fMacBundleRef != nullptr) - { - CFBundleCloseBundleResourceMap(fMacBundleRef, fMacBundleRefNum); + if (fMacBundleRef != nullptr) + { + CFBundleCloseBundleResourceMap(fMacBundleRef, fMacBundleRefNum); - if (CFGetRetainCount(fMacBundleRef) == 1) - CFBundleUnloadExecutable(fMacBundleRef); + if (CFGetRetainCount(fMacBundleRef) == 1) + CFBundleUnloadExecutable(fMacBundleRef); - if (CFGetRetainCount(fMacBundleRef) > 0) - CFRelease(fMacBundleRef); + if (CFGetRetainCount(fMacBundleRef) > 0) + CFRelease(fMacBundleRef); - fMacBundleRef = nullptr; - } + fMacBundleRef = nullptr; + } #endif } @@ -1172,11 +1172,11 @@ public: try { dispatcher(effMainsChanged, 0, 1); - } catch(...) {} + } CARLA_SAFE_EXCEPTION("effMainsChanged on"); try { dispatcher(effStartProcess, 0, 0); - } catch(...) {} + } CARLA_SAFE_EXCEPTION("effStartProcess on"); fFirstActive = true; } @@ -1187,11 +1187,11 @@ public: try { dispatcher(effStopProcess); - } catch(...) {} + } CARLA_SAFE_EXCEPTION("effStartProcess off"); try { dispatcher(effMainsChanged); - } catch(...) {} + } CARLA_SAFE_EXCEPTION("effMainsChanged off"); } void process(const float* const* const audioIn, @@ -1261,7 +1261,7 @@ public: fTimeInfo.flags = 0; - if (fFirstActive || ! fLastTimeInfo.compareIgnoringRollingFrames(timeInfo, fBufferSize)) + if (fFirstActive || ! fLastTimeInfo.compareIgnoringRollingFrames(timeInfo, frames)) { fTimeInfo.flags |= kVstTransportChanged; fLastTimeInfo = timeInfo; @@ -1289,7 +1289,7 @@ public: // const double ppqTick = timeInfo.bbt.tick / timeInfo.bbt.ticksPerBeat; // PPQ Pos - fTimeInfo.ppqPos = fTimeInfo.samplePos / (fTimeInfo.sampleRate * 60 / fTimeInfo.tempo); + fTimeInfo.ppqPos = fTimeInfo.samplePos / (fTimeInfo.sampleRate * 60 / timeInfo.bbt.beatsPerMinute); // fTimeInfo.ppqPos = ppqBar + ppqBeat + ppqTick; fTimeInfo.flags |= kVstPpqPosValid; @@ -1302,8 +1302,8 @@ public: fTimeInfo.flags |= kVstBarsValid; // Time Signature - fTimeInfo.timeSigNumerator = static_cast(timeInfo.bbt.beatsPerBar); - fTimeInfo.timeSigDenominator = static_cast(timeInfo.bbt.beatType); + fTimeInfo.timeSigNumerator = static_cast(timeInfo.bbt.beatsPerBar + 0.5f); + fTimeInfo.timeSigDenominator = static_cast(timeInfo.bbt.beatType + 0.5f); fTimeInfo.flags |= kVstTimeSigValid; } else @@ -1313,7 +1313,7 @@ public: fTimeInfo.flags |= kVstTempoValid; // Time Signature - fTimeInfo.timeSigNumerator = 4; + fTimeInfo.timeSigNumerator = 4; fTimeInfo.timeSigDenominator = 4; fTimeInfo.flags |= kVstTimeSigValid; @@ -2500,7 +2500,7 @@ public: { CFRelease(fMacBundleRef); fMacBundleRef = nullptr; - pData->engine->setLastError("Failed to load VST bundle executable"); + pData->engine->setLastError("Failed to load VST2 bundle executable"); return false; } @@ -2514,7 +2514,7 @@ public: CFBundleUnloadExecutable(fMacBundleRef); CFRelease(fMacBundleRef); fMacBundleRef = nullptr; - pData->engine->setLastError("Not a VST plugin"); + pData->engine->setLastError("Not a VST2 plugin"); return false; } @@ -2543,7 +2543,7 @@ public: if (vstFn == nullptr) { - pData->engine->setLastError("Could not find the VST main entry in the plugin library"); + pData->engine->setLastError("Could not find the VST2 main entry in the plugin library"); return false; } } @@ -2695,7 +2695,7 @@ public: pData->options = 0x0; - if (pData->latency.frames != 0 || hasMidiOutput() || isPluginOptionEnabled(options, PLUGIN_OPTION_FIXED_BUFFERS)) + if (fEffect->initialDelay > 0 || hasMidiOutput() || isPluginOptionEnabled(options, PLUGIN_OPTION_FIXED_BUFFERS)) pData->options |= PLUGIN_OPTION_FIXED_BUFFERS; if (fEffect->flags & effFlagsProgramChunks) diff --git a/source/backend/plugin/CarlaPluginVST3.cpp b/source/backend/plugin/CarlaPluginVST3.cpp index e15731f66..d1f0636b8 100644 --- a/source/backend/plugin/CarlaPluginVST3.cpp +++ b/source/backend/plugin/CarlaPluginVST3.cpp @@ -1,6 +1,6 @@ /* * Carla VST3 Plugin - * Copyright (C) 2014-2021 Filipe Coelho + * Copyright (C) 2014-2022 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -23,87 +23,1814 @@ # define USE_JUCE_FOR_VST3 #endif +#include "CarlaBackendUtils.hpp" #include "CarlaVst3Utils.hpp" #include "CarlaPluginUI.hpp" +#include "water/files/File.h" + CARLA_BACKEND_START_NAMESPACE -// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- + +static inline +size_t strlen_utf16(const int16_t* const str) +{ + size_t i = 0; + + while (str[i] != 0) + ++i; + + return i; +} + +// -------------------------------------------------------------------------------------------------------------------- + +static inline +void strncpy_utf8(char* const dst, const int16_t* const src, const size_t length) +{ + CARLA_SAFE_ASSERT_RETURN(length > 0,); + + if (const size_t len = std::min(strlen_utf16(src), length-1U)) + { + for (size_t i=0; i= 0x80) + continue; + + dst[i] = src[i]; + } + dst[len] = 0; + } + else + { + dst[0] = 0; + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +static uint32_t V3_API v3_ref_static(void*) { return 1; } +static uint32_t V3_API v3_unref_static(void*) { return 0; } + +struct carla_v3_host_application : v3_host_application_cpp { + carla_v3_host_application() + { + query_interface = carla_query_interface; + ref = v3_ref_static; + unref = v3_unref_static; + app.get_name = carla_get_name; + app.create_instance = carla_create_instance; + } + + static v3_result V3_API carla_query_interface(void* const self, const v3_tuid iid, void** const iface) + { + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_host_application_iid)) + { + *iface = self; + return V3_OK; + } + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + static v3_result V3_API carla_get_name(void*, v3_str_128 name) + { + static const char hostname[] = "Carla-Discovery\0"; + + for (size_t i=0; ihints & PLUGIN_HAS_CUSTOM_UI) + { + if (! fUI.isEmbed) + showCustomUI(false); + + if (fUI.isAttached) + { + fUI.isAttached = false; + v3_cpp_obj(fV3.view)->removed(fV3.view); + } + } + + if (fV3.view != nullptr) + { + v3_cpp_obj_unref(fV3.view); + fV3.view = nullptr; + } + + pData->singleMutex.lock(); + pData->masterMutex.lock(); + + if (pData->client != nullptr && pData->client->isActive()) + pData->client->deactivate(true); + + if (pData->active) + { + deactivate(); + pData->active = false; + } + + clearBuffers(); + + fV3.exit(); + } + + // ------------------------------------------------------------------- + // Information (base) + + PluginType getType() const noexcept override + { + return PLUGIN_VST3; + } + + PluginCategory getCategory() const noexcept override + { + return getPluginCategoryFromV3SubCategories(fV3ClassInfo.v2.sub_categories); + } + + // ------------------------------------------------------------------- + // Information (count) + + // nothing + + // ------------------------------------------------------------------- + // Information (per-plugin data) + + uint getOptionsAvailable() const noexcept override + { + uint options = 0x0; + + // can't disable fixed buffers if using latency + if (pData->latency.frames == 0) + options |= PLUGIN_OPTION_FIXED_BUFFERS; + + /* + if (numPrograms > 1) + options |= PLUGIN_OPTION_MAP_PROGRAM_CHANGES; + */ + + options |= PLUGIN_OPTION_USE_CHUNKS; + + /* + if (hasMidiInput()) + { + options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES; + options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE; + options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH; + options |= PLUGIN_OPTION_SEND_PITCHBEND; + options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF; + options |= PLUGIN_OPTION_SEND_PROGRAM_CHANGES; + options |= PLUGIN_OPTION_SKIP_SENDING_NOTES; + } + */ + + return options; + } + + float getParameterValue(const uint32_t parameterId) const noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fV3.controller != nullptr, 0.0f); + CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, 0.0f); + + const double normalized = v3_cpp_obj(fV3.controller)->get_parameter_normalised(fV3.controller, parameterId); + return v3_cpp_obj(fV3.controller)->normalised_parameter_to_plain(fV3.controller, parameterId, normalized); + } + + bool getLabel(char* const strBuf) const noexcept override + { + std::strncpy(strBuf, fV3ClassInfo.v1.name, STR_MAX); + return true; + } + + bool getMaker(char* const strBuf) const noexcept override + { + std::strncpy(strBuf, fV3ClassInfo.v2.vendor, STR_MAX); + return true; + } + + bool getCopyright(char* const strBuf) const noexcept override + { + return getMaker(strBuf); + } + + bool getRealName(char* const strBuf) const noexcept override + { + std::strncpy(strBuf, fV3ClassInfo.v1.name, STR_MAX); + return true; + } + + bool getParameterName(const uint32_t parameterId, char* const strBuf) const noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fV3.controller != nullptr, 0.0f); + CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, false); + + v3_param_info paramInfo = {}; + CARLA_SAFE_ASSERT_RETURN(v3_cpp_obj(fV3.controller)->get_parameter_info(fV3.controller, parameterId, ¶mInfo) == V3_OK, false); + + strncpy_utf8(strBuf, paramInfo.title, STR_MAX); + return true; + } + + bool getParameterText(const uint32_t parameterId, char* const strBuf) noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fV3.controller != nullptr, false); + CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, false); + + const double normalized = v3_cpp_obj(fV3.controller)->get_parameter_normalised(fV3.controller, parameterId); + + v3_str_128 paramText; + CARLA_SAFE_ASSERT_RETURN(v3_cpp_obj(fV3.controller)->get_parameter_string_for_value(fV3.controller, parameterId, normalized, paramText) == V3_OK, false); + + if (paramText[0] != '\0') + strncpy_utf8(strBuf, paramText, STR_MAX); + else + std::snprintf(strBuf, STR_MAX, "%.12g", + v3_cpp_obj(fV3.controller)->normalised_parameter_to_plain(fV3.controller, parameterId, normalized)); + + return true; + } + + bool getParameterUnit(const uint32_t parameterId, char* const strBuf) const noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fV3.controller != nullptr, false); + CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, false); + + v3_param_info paramInfo = {}; + CARLA_SAFE_ASSERT_RETURN(v3_cpp_obj(fV3.controller)->get_parameter_info(fV3.controller, parameterId, ¶mInfo) == V3_OK, false); + + strncpy_utf8(strBuf, paramInfo.units, STR_MAX); + return true; + } + + // ------------------------------------------------------------------- + // Set data (state) + + // nothing + + // ------------------------------------------------------------------- + // Set data (internal stuff) + + // ------------------------------------------------------------------- + // Set data (plugin-specific stuff) + + void setParameterValue(const uint32_t parameterId, const float value, const bool sendGui, const bool sendOsc, const bool sendCallback) noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fV3.controller != nullptr,); + CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count,); + + const float fixedValue = pData->param.getFixedValue(parameterId, value); + const double normalized = v3_cpp_obj(fV3.controller)->plain_parameter_to_normalised(fV3.controller, parameterId, fixedValue); + + v3_cpp_obj(fV3.controller)->set_parameter_normalised(fV3.controller, parameterId, normalized); + + CarlaPlugin::setParameterValue(parameterId, fixedValue, sendGui, sendOsc, sendCallback); + } + + void setParameterValueRT(const uint32_t parameterId, const float value, const bool sendCallbackLater) noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fV3.controller != nullptr,); + CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count,); + + const float fixedValue = pData->param.getFixedValue(parameterId, value); + + // TODO append value to V3 changes queue list + + CarlaPlugin::setParameterValueRT(parameterId, fixedValue, sendCallbackLater); + } + + // ------------------------------------------------------------------- + // Set ui stuff + + void setCustomUITitle(const char* const title) noexcept override + { + if (fUI.window != nullptr) + { + try { + fUI.window->setTitle(title); + } CARLA_SAFE_EXCEPTION("set custom ui title"); + } + + CarlaPlugin::setCustomUITitle(title); + } + + void showCustomUI(const bool yesNo) override + { + if (fUI.isVisible == yesNo) + return; + + CARLA_SAFE_ASSERT_RETURN(fV3.view != nullptr,); + + if (yesNo) + { + CarlaString uiTitle; + + if (pData->uiTitle.isNotEmpty()) + { + uiTitle = pData->uiTitle; + } + else + { + uiTitle = pData->name; + uiTitle += " (GUI)"; + } + + if (fUI.window == nullptr) + { + const char* msg = nullptr; + const EngineOptions& opts(pData->engine->getOptions()); + +#if defined(CARLA_OS_MAC) + fUI.window = CarlaPluginUI::newCocoa(this, opts.frontendWinId, opts.pluginsAreStandalone, false); +#elif defined(CARLA_OS_WIN) + fUI.window = CarlaPluginUI::newWindows(this, opts.frontendWinId, opts.pluginsAreStandalone, false); +#elif defined(HAVE_X11) + fUI.window = CarlaPluginUI::newX11(this, opts.frontendWinId, opts.pluginsAreStandalone, false, false); +#else + msg = "Unsupported UI type"; +#endif + + if (fUI.window == nullptr) + return pData->engine->callback(true, true, + ENGINE_CALLBACK_UI_STATE_CHANGED, + pData->id, + -1, + 0, 0, 0.0f, + msg); + + fUI.window->setTitle(uiTitle.buffer()); + + #ifndef CARLA_OS_MAC + // TODO inform plugin of what UI scale we use + #endif + + if (v3_cpp_obj(fV3.view)->attached(fV3.view, fUI.window->getPtr(), V3_VIEW_PLATFORM_TYPE_NATIVE) == V3_OK) + { + v3_view_rect rect = {}; + if (v3_cpp_obj(fV3.view)->get_size(fV3.view, 0) == V3_OK) + { + const int32_t width = rect.right - rect.left; + const int32_t height = rect.bottom - rect.top; + + CARLA_SAFE_ASSERT_INT2(width > 1 && height > 1, width, height); + + if (width > 1 && height > 1) + fUI.window->setSize(static_cast(width), static_cast(height), true); + } + } + else + { + delete fUI.window; + fUI.window = nullptr; + + carla_stderr2("Plugin refused to open its own UI"); + return pData->engine->callback(true, true, + ENGINE_CALLBACK_UI_STATE_CHANGED, + pData->id, + -1, + 0, 0, 0.0f, + "Plugin refused to open its own UI"); + } + } + + fUI.window->show(); + fUI.isVisible = true; + pData->hints |= PLUGIN_NEEDS_UI_MAIN_THREAD; + } + else + { + fUI.isVisible = false; + pData->hints &= ~PLUGIN_NEEDS_UI_MAIN_THREAD; + + CARLA_SAFE_ASSERT_RETURN(fUI.window != nullptr,); + fUI.window->hide(); + } + } + // ------------------------------------------------------------------- + // Plugin state + + void reload() override + { + CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,); + CARLA_SAFE_ASSERT_RETURN(fV3.component != nullptr,); + CARLA_SAFE_ASSERT_RETURN(fV3.controller != nullptr,); + CARLA_SAFE_ASSERT_RETURN(fV3.processor != nullptr,); + carla_debug("CarlaPluginVST3::reload() - start"); + + // Safely disable plugin for reload + const ScopedDisabler sd(this); + + if (pData->active) + deactivate(); + + clearBuffers(); + + const int32_t numAudioInputBuses = v3_cpp_obj(fV3.component)->get_bus_count(fV3.component, V3_AUDIO, V3_INPUT); + const int32_t numEventInputBuses = v3_cpp_obj(fV3.component)->get_bus_count(fV3.component, V3_EVENT, V3_INPUT); + const int32_t numAudioOutputBuses = v3_cpp_obj(fV3.component)->get_bus_count(fV3.component, V3_AUDIO, V3_OUTPUT); + const int32_t numEventOutputBuses = v3_cpp_obj(fV3.component)->get_bus_count(fV3.component, V3_EVENT, V3_OUTPUT); + const int32_t numParameters = v3_cpp_obj(fV3.controller)->get_parameter_count(fV3.controller); + + CARLA_SAFE_ASSERT(numAudioInputBuses >= 0); + CARLA_SAFE_ASSERT(numEventInputBuses >= 0); + CARLA_SAFE_ASSERT(numAudioOutputBuses >= 0); + CARLA_SAFE_ASSERT(numEventOutputBuses >= 0); + CARLA_SAFE_ASSERT(numParameters >= 0); + + uint32_t aIns, aOuts, cvIns, cvOuts, params; + aIns = aOuts = cvIns = cvOuts = params = 0; + + bool needsCtrlIn, needsCtrlOut; + needsCtrlIn = needsCtrlOut = false; + + for (int32_t j=0; jget_bus_info(fV3.component, V3_AUDIO, V3_INPUT, j, &busInfo) == V3_OK); + + if (busInfo.flags & V3_IS_CONTROL_VOLTAGE) + cvIns += busInfo.channel_count; + else + aIns += busInfo.channel_count; + } + + for (int32_t j=0; jget_bus_info(fV3.component, V3_AUDIO, V3_OUTPUT, j, &busInfo) == V3_OK); + + if (busInfo.flags & V3_IS_CONTROL_VOLTAGE) + cvOuts += busInfo.channel_count; + else + aOuts += busInfo.channel_count; + } + + for (int32_t j=0; jget_parameter_info(fV3.controller, j, ¶mInfo) == V3_OK); + + if ((paramInfo.flags & (V3_PARAM_IS_BYPASS|V3_PARAM_IS_HIDDEN|V3_PARAM_PROGRAM_CHANGE)) == 0x0) + ++params; + } + + if (aIns > 0) + { + pData->audioIn.createNew(aIns); + } + + if (aOuts > 0) + { + pData->audioOut.createNew(aOuts); + needsCtrlIn = true; + } + + if (cvIns > 0) + pData->cvIn.createNew(cvIns); + + if (cvOuts > 0) + pData->cvOut.createNew(cvOuts); + + if (numEventInputBuses > 0) + needsCtrlIn = true; + + if (numEventOutputBuses > 0) + needsCtrlOut = true; + + if (params > 0) + { + pData->param.createNew(params, false); + needsCtrlIn = true; + } + + if (aOuts + cvIns > 0) + { + fAudioOutBuffers = new float*[aOuts + cvIns]; + + for (uint32_t i=0; i < aOuts + cvIns; ++i) + fAudioOutBuffers[i] = nullptr; + } + + const EngineProcessMode processMode = pData->engine->getProccessMode(); + const uint portNameSize = pData->engine->getMaxPortNameSize(); + CarlaString portName; + + // Audio Ins + for (uint32_t j=0; j < aIns; ++j) + { + portName.clear(); + + if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) + { + portName = pData->name; + portName += ":"; + } + + if (aIns > 1) + { + portName += "input_"; + portName += CarlaString(j+1); + } + else + portName += "input"; + + portName.truncate(portNameSize); + + pData->audioIn.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, true, j); + pData->audioIn.ports[j].rindex = j; + } + + // Audio Outs + for (uint32_t j=0; j < aOuts; ++j) + { + portName.clear(); + + if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) + { + portName = pData->name; + portName += ":"; + } + + if (aOuts > 1) + { + portName += "output_"; + portName += CarlaString(j+1); + } + else + portName += "output"; + + portName.truncate(portNameSize); + + pData->audioOut.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, j); + pData->audioOut.ports[j].rindex = j; + } + + // CV Ins + for (uint32_t j=0; j < cvIns; ++j) + { + portName.clear(); + + if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) + { + portName = pData->name; + portName += ":"; + } + + if (cvIns > 1) + { + portName += "cv_input_"; + portName += CarlaString(j+1); + } + else + portName += "cv_input"; + + portName.truncate(portNameSize); + + pData->cvIn.ports[j].port = (CarlaEngineCVPort*)pData->client->addPort(kEnginePortTypeCV, portName, true, j); + pData->cvIn.ports[j].rindex = j; + } + + // CV Outs + for (uint32_t j=0; j < cvOuts; ++j) + { + portName.clear(); + + if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) + { + portName = pData->name; + portName += ":"; + } + + if (cvOuts > 1) + { + portName += "cv_output_"; + portName += CarlaString(j+1); + } + else + portName += "cv_output"; + + portName.truncate(portNameSize); + + pData->cvOut.ports[j].port = (CarlaEngineCVPort*)pData->client->addPort(kEnginePortTypeCV, portName, false, j); + pData->cvOut.ports[j].rindex = j; + } + + for (uint32_t j=0; j < params; ++j) + { + const int32_t ij = static_cast(j); + pData->param.data[j].type = PARAMETER_INPUT; + pData->param.data[j].index = ij; + pData->param.data[j].rindex = ij; + + v3_param_info paramInfo = {}; + CARLA_SAFE_ASSERT_BREAK(v3_cpp_obj(fV3.controller)->get_parameter_info(fV3.controller, j, ¶mInfo) == V3_OK); + + if (paramInfo.flags & (V3_PARAM_IS_BYPASS|V3_PARAM_IS_HIDDEN|V3_PARAM_PROGRAM_CHANGE)) + continue; + + float min, max, def, step, stepSmall, stepLarge; + + min = v3_cpp_obj(fV3.controller)->normalised_parameter_to_plain(fV3.controller, j, 0.0); + max = v3_cpp_obj(fV3.controller)->normalised_parameter_to_plain(fV3.controller, j, 1.0); + def = v3_cpp_obj(fV3.controller)->normalised_parameter_to_plain(fV3.controller, j, paramInfo.default_normalised_value); + + if (min >= max) + max = min + 0.1f; + + if (def < min) + def = min; + else if (def > max) + def = max; + + if (paramInfo.flags & V3_PARAM_READ_ONLY) + pData->param.data[j].type = PARAMETER_OUTPUT; + else + pData->param.data[j].type = PARAMETER_INPUT; + + if (paramInfo.step_count == 1) + { + step = max - min; + stepSmall = step; + stepLarge = step; + pData->param.data[j].hints |= PARAMETER_IS_BOOLEAN; + } + /* + else if (paramInfo.step_count != 0 && (paramInfo.flags & V3_PARAM_IS_LIST) != 0x0) + { + step = 1.0f; + stepSmall = 1.0f; + stepLarge = 10.0f; + pData->param.data[j].hints |= PARAMETER_IS_INTEGER; + } + */ + else + { + float range = max - min; + step = range/100.0f; + stepSmall = range/1000.0f; + stepLarge = range/10.0f; + } + + pData->param.data[j].hints |= PARAMETER_IS_ENABLED; + pData->param.data[j].hints |= PARAMETER_USES_CUSTOM_TEXT; + + if (paramInfo.flags & V3_PARAM_CAN_AUTOMATE) + { + pData->param.data[j].hints |= PARAMETER_IS_AUTOMATABLE; + + if ((paramInfo.flags & V3_PARAM_IS_LIST) == 0x0) + pData->param.data[j].hints |= PARAMETER_CAN_BE_CV_CONTROLLED; + } + + pData->param.ranges[j].min = min; + pData->param.ranges[j].max = max; + pData->param.ranges[j].def = def; + pData->param.ranges[j].step = step; + pData->param.ranges[j].stepSmall = stepSmall; + pData->param.ranges[j].stepLarge = stepLarge; + } + + if (needsCtrlIn) + { + portName.clear(); + + if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) + { + portName = pData->name; + portName += ":"; + } + + portName += "events-in"; + portName.truncate(portNameSize); + + pData->event.portIn = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, true, 0); +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + pData->event.cvSourcePorts = pData->client->createCVSourcePorts(); +#endif + } + + if (needsCtrlOut) + { + portName.clear(); + + if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) + { + portName = pData->name; + portName += ":"; + } + + portName += "events-out"; + portName.truncate(portNameSize); + + pData->event.portOut = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, false, 0); + } + + // plugin hints + const PluginCategory v3category = getPluginCategoryFromV3SubCategories(fV3ClassInfo.v2.sub_categories); + + pData->hints = 0x0; + + if (v3category == PLUGIN_CATEGORY_SYNTH) + pData->hints |= PLUGIN_IS_SYNTH; + + if (fV3.view != nullptr && + v3_cpp_obj(fV3.view)->is_platform_type_supported(fV3.view, V3_VIEW_PLATFORM_TYPE_NATIVE) == V3_TRUE) + { + pData->hints |= PLUGIN_HAS_CUSTOM_UI; + pData->hints |= PLUGIN_HAS_CUSTOM_EMBED_UI; + } + + if (aOuts > 0 && (aIns == aOuts || aIns == 1)) + pData->hints |= PLUGIN_CAN_DRYWET; + + if (aOuts > 0) + pData->hints |= PLUGIN_CAN_VOLUME; + + if (aOuts >= 2 && aOuts % 2 == 0) + pData->hints |= PLUGIN_CAN_BALANCE; + + // extra plugin hints + pData->extraHints = 0x0; + + if (numEventInputBuses > 0) + pData->extraHints |= PLUGIN_EXTRA_HINT_HAS_MIDI_IN; + + if (numEventOutputBuses > 0) + pData->extraHints |= PLUGIN_EXTRA_HINT_HAS_MIDI_OUT; + + // check initial latency + if (const uint32_t latency = v3_cpp_obj(fV3.processor)->get_latency_samples(fV3.processor)) + { + pData->client->setLatency(latency); +#ifndef BUILD_BRIDGE + pData->latency.recreateBuffers(std::max(aIns+cvIns, aOuts+cvOuts), latency); +#endif + } + + // initial audio setup + v3_process_setup setup = { + pData->engine->isOffline() ? V3_OFFLINE : V3_REALTIME, + V3_SAMPLE_32, + static_cast(pData->engine->getBufferSize()), + pData->engine->getSampleRate() + }; + v3_cpp_obj(fV3.processor)->setup_processing(fV3.processor, &setup); + + // activate all buses + for (int32_t j=0; jget_bus_info(fV3.component, V3_AUDIO, V3_INPUT, j, &busInfo) == V3_OK); + + if ((busInfo.flags & V3_DEFAULT_ACTIVE) == 0x0) { + CARLA_SAFE_ASSERT_BREAK(v3_cpp_obj(fV3.component)->activate_bus(fV3.component, V3_AUDIO, V3_INPUT, j, true) == V3_OK); + } + } + + for (int32_t j=0; jget_bus_info(fV3.component, V3_AUDIO, V3_OUTPUT, j, &busInfo) == V3_OK); + + if ((busInfo.flags & V3_DEFAULT_ACTIVE) == 0x0) { + CARLA_SAFE_ASSERT_BREAK(v3_cpp_obj(fV3.component)->activate_bus(fV3.component, V3_AUDIO, V3_OUTPUT, j, true) == V3_OK); + } + } + + bufferSizeChanged(pData->engine->getBufferSize()); + reloadPrograms(true); + + if (pData->active) + activate(); + + carla_debug("CarlaPluginVST3::reload() - end"); + } + + // ------------------------------------------------------------------- + // Plugin processing + + void activate() noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fV3.component != nullptr,); + CARLA_SAFE_ASSERT_RETURN(fV3.processor != nullptr,); + + try { + v3_cpp_obj(fV3.component)->set_active(fV3.component, true); + } CARLA_SAFE_EXCEPTION("set_active on"); + + try { + v3_cpp_obj(fV3.processor)->set_processing(fV3.processor, true); + } CARLA_SAFE_EXCEPTION("set_processing on"); + + fFirstActive = true; + } + + void deactivate() noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fV3.component != nullptr,); + CARLA_SAFE_ASSERT_RETURN(fV3.processor != nullptr,); + + try { + v3_cpp_obj(fV3.processor)->set_processing(fV3.processor, false); + } CARLA_SAFE_EXCEPTION("set_processing off"); + + try { + v3_cpp_obj(fV3.component)->set_active(fV3.component, false); + } CARLA_SAFE_EXCEPTION("set_active off"); + } + + void process(const float* const* const audioIn, float** const audioOut, + const float* const* const cvIn, float** const cvOut, const uint32_t frames) override + { + // -------------------------------------------------------------------------------------------------------- + // Check if active + + if (! pData->active) + { + // disable any output sound + for (uint32_t i=0; i < pData->audioOut.count; ++i) + carla_zeroFloats(audioOut[i], frames); + for (uint32_t i=0; i < pData->cvOut.count; ++i) + carla_zeroFloats(cvOut[i], frames); + return; + } + + // -------------------------------------------------------------------------------------------------------- + // Check if needs reset + + if (pData->needsReset) + { + + pData->needsReset = false; + } + + // -------------------------------------------------------------------------------------------------------- + // Set TimeInfo + + const EngineTimeInfo timeInfo(pData->engine->getTimeInfo()); + + fV3TimeContext.state = V3_PROCESS_CTX_PROJECT_TIME_VALID | V3_PROCESS_CTX_CONT_TIME_VALID; + fV3TimeContext.sample_rate = pData->engine->getSampleRate(); + fV3TimeContext.project_time_in_samples = fV3TimeContext.continuous_time_in_samples + = static_cast(timeInfo.frame); + + if (fFirstActive || ! fLastTimeInfo.compareIgnoringRollingFrames(timeInfo, frames)) + fLastTimeInfo = timeInfo; + + if (timeInfo.playing) + fV3TimeContext.state |= V3_PROCESS_CTX_PLAYING; + + if (timeInfo.usecs != 0) + { + fV3TimeContext.system_time_ns = static_cast(timeInfo.usecs); + fV3TimeContext.state |= V3_PROCESS_CTX_SYSTEM_TIME_VALID; + } + + if (timeInfo.bbt.valid) + { + CARLA_SAFE_ASSERT_INT(timeInfo.bbt.bar > 0, timeInfo.bbt.bar); + CARLA_SAFE_ASSERT_INT(timeInfo.bbt.beat > 0, timeInfo.bbt.beat); + + const double ppqBar = static_cast(timeInfo.bbt.beatsPerBar) * (timeInfo.bbt.bar - 1); + // const double ppqBeat = static_cast(timeInfo.bbt.beat - 1); + // const double ppqTick = timeInfo.bbt.tick / timeInfo.bbt.ticksPerBeat; + + // PPQ Pos + fV3TimeContext.project_time_quarters = static_cast(timeInfo.frame) / (fV3TimeContext.sample_rate * 60 / timeInfo.bbt.beatsPerMinute); + // fTimeInfo.project_time_quarters = ppqBar + ppqBeat + ppqTick; + fV3TimeContext.state |= V3_PROCESS_CTX_PROJECT_TIME_VALID; + + // Tempo + fV3TimeContext.bpm = timeInfo.bbt.beatsPerMinute; + fV3TimeContext.state |= V3_PROCESS_CTX_TEMPO_VALID; + + // Bars + fV3TimeContext.bar_position_quarters = ppqBar; + fV3TimeContext.state |= V3_PROCESS_CTX_BAR_POSITION_VALID; + + // Time Signature + fV3TimeContext.time_sig_numerator = static_cast(timeInfo.bbt.beatsPerBar + 0.5f); + fV3TimeContext.time_sig_denom = static_cast(timeInfo.bbt.beatType + 0.5f); + fV3TimeContext.state |= V3_PROCESS_CTX_TIME_SIG_VALID; + } + else + { + // Tempo + fV3TimeContext.bpm = 120.0; + fV3TimeContext.state |= V3_PROCESS_CTX_TEMPO_VALID; + + // Time Signature + fV3TimeContext.time_sig_numerator = 4; + fV3TimeContext.time_sig_denom = 4; + fV3TimeContext.state |= V3_PROCESS_CTX_TIME_SIG_VALID; + + // Missing info + fV3TimeContext.project_time_quarters = 0.0; + fV3TimeContext.bar_position_quarters = 0.0; + } + + // -------------------------------------------------------------------------------------------------------- + // Event Input and Processing + + if (pData->event.portIn != nullptr) + { + // ---------------------------------------------------------------------------------------------------- + // MIDI Input (External) + + if (pData->extNotes.mutex.tryLock()) + { + // TODO + + pData->extNotes.mutex.unlock(); + + } // End of MIDI Input (External) + + // ---------------------------------------------------------------------------------------------------- + // Event Input (System) + +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + bool allNotesOffSent = false; +#endif + bool isSampleAccurate = (pData->options & PLUGIN_OPTION_FIXED_BUFFERS) == 0; + + uint32_t startTime = 0; + uint32_t timeOffset = 0; + +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + if (cvIn != nullptr && pData->event.cvSourcePorts != nullptr) + pData->event.cvSourcePorts->initPortBuffers(cvIn, frames, isSampleAccurate, pData->event.portIn); +#endif + + for (uint32_t i=0, numEvents = pData->event.portIn->getEventCount(); i < numEvents; ++i) + { + EngineEvent& event(pData->event.portIn->getEvent(i)); + + uint32_t eventTime = event.time; + CARLA_SAFE_ASSERT_UINT2_CONTINUE(eventTime < frames, eventTime, frames); + + if (eventTime < timeOffset) + { + carla_stderr2("Timing error, eventTime:%u < timeOffset:%u for '%s'", + eventTime, timeOffset, pData->name); + eventTime = timeOffset; + } + + if (isSampleAccurate && eventTime > timeOffset) + { + if (processSingle(audioIn, audioOut, eventTime - timeOffset, timeOffset)) + { + startTime = 0; + timeOffset = eventTime; + + // TODO + } + else + { + startTime += timeOffset; + } + } + + switch (event.type) + { + case kEngineEventTypeNull: + break; + + case kEngineEventTypeControl: { + EngineControlEvent& ctrlEvent(event.ctrl); + + switch (ctrlEvent.type) + { + case kEngineControlEventTypeNull: + break; + + case kEngineControlEventTypeParameter: { + float value; + +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + // non-midi + if (event.channel == kEngineEventNonMidiChannel) + { + const uint32_t k = ctrlEvent.param; + CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count); + + ctrlEvent.handled = true; + value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.normalizedValue); + setParameterValueRT(k, value, true); + continue; + } + + // Control backend stuff + if (event.channel == pData->ctrlChannel) + { + if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0) + { + ctrlEvent.handled = true; + value = ctrlEvent.normalizedValue; + setDryWetRT(value, true); + } + else if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0) + { + ctrlEvent.handled = true; + value = ctrlEvent.normalizedValue*127.0f/100.0f; + setVolumeRT(value, true); + } + else if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0) + { + float left, right; + value = ctrlEvent.normalizedValue/0.5f - 1.0f; + + if (value < 0.0f) + { + left = -1.0f; + right = (value*2.0f)+1.0f; + } + else if (value > 0.0f) + { + left = (value*2.0f)-1.0f; + right = 1.0f; + } + else + { + left = -1.0f; + right = 1.0f; + } + + ctrlEvent.handled = true; + setBalanceLeftRT(left, true); + setBalanceRightRT(right, true); + } + } +#endif + // Control plugin parameters + uint32_t k; + for (k=0; k < pData->param.count; ++k) + { + if (pData->param.data[k].midiChannel != event.channel) + continue; + if (pData->param.data[k].mappedControlIndex != ctrlEvent.param) + continue; + if (pData->param.data[k].type != PARAMETER_INPUT) + continue; + if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMATABLE) == 0) + continue; + + ctrlEvent.handled = true; + value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.normalizedValue); + setParameterValueRT(k, value, true); + } + + if ((pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) != 0 && ctrlEvent.param < MAX_MIDI_VALUE) + { + // TODO + } + +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + if (! ctrlEvent.handled) + checkForMidiLearn(event); +#endif + break; + } // case kEngineControlEventTypeParameter + + case kEngineControlEventTypeMidiBank: + if ((pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0) + { + // TODO + } + break; + + case kEngineControlEventTypeMidiProgram: + if (event.channel == pData->ctrlChannel && (pData->options & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) != 0) + { + if (ctrlEvent.param < pData->prog.count) + { + setProgramRT(ctrlEvent.param, true); + break; + } + } + else if (pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) + { + // TODO + } + break; + + case kEngineControlEventTypeAllSoundOff: + if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) + { + // TODO + } + break; + + case kEngineControlEventTypeAllNotesOff: + if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) + { +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + if (event.channel == pData->ctrlChannel && ! allNotesOffSent) + { + allNotesOffSent = true; + postponeRtAllNotesOff(); + } +#endif + // TODO + } + break; + } // switch (ctrlEvent.type) + break; + } // case kEngineEventTypeControl + + case kEngineEventTypeMidi: { + const EngineMidiEvent& midiEvent(event.midi); + + if (midiEvent.size > 3) + continue; +#ifdef CARLA_PROPER_CPP11_SUPPORT + static_assert(3 <= EngineMidiEvent::kDataSize, "Incorrect data"); +#endif + + uint8_t status = uint8_t(MIDI_GET_STATUS_FROM_DATA(midiEvent.data)); + + if ((status == MIDI_STATUS_NOTE_OFF || status == MIDI_STATUS_NOTE_ON) && (pData->options & PLUGIN_OPTION_SKIP_SENDING_NOTES)) + continue; + if (status == MIDI_STATUS_CHANNEL_PRESSURE && (pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0) + continue; + if (status == MIDI_STATUS_CONTROL_CHANGE && (pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0) + continue; + if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0) + continue; + if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && (pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0) + continue; + + // Fix bad note-off + if (status == MIDI_STATUS_NOTE_ON && midiEvent.data[2] == 0) + status = MIDI_STATUS_NOTE_OFF; + + // TODO + + if (status == MIDI_STATUS_NOTE_ON) + { + pData->postponeNoteOnRtEvent(true, event.channel, midiEvent.data[1], midiEvent.data[2]); + } + else if (status == MIDI_STATUS_NOTE_OFF) + { + pData->postponeNoteOffRtEvent(true, event.channel, midiEvent.data[1]); + } + } break; + } // switch (event.type) + } + + pData->postRtEvents.trySplice(); + + if (frames > timeOffset) + processSingle(audioIn, audioOut, frames - timeOffset, timeOffset); + + } // End of Event Input and Processing + + // -------------------------------------------------------------------------------------------------------- + // Plugin processing (no events) + + else + { + processSingle(audioIn, audioOut, frames, 0); + + } // End of Plugin processing (no events) + + // -------------------------------------------------------------------------------------------------------- + // MIDI Output + + if (pData->event.portOut != nullptr) + { + // TODO + + } // End of MIDI Output + + fFirstActive = false; + + // -------------------------------------------------------------------------------------------------------- + } + + bool processSingle(const float* const* const inBuffer, float** const outBuffer, const uint32_t frames, const uint32_t timeOffset) + { + CARLA_SAFE_ASSERT_RETURN(frames > 0, false); + + if (pData->audioIn.count > 0) + { + CARLA_SAFE_ASSERT_RETURN(inBuffer != nullptr, false); + } + if (pData->audioOut.count > 0) + { + CARLA_SAFE_ASSERT_RETURN(outBuffer != nullptr, false); + CARLA_SAFE_ASSERT_RETURN(fAudioOutBuffers != nullptr, false); + } + + // -------------------------------------------------------------------------------------------------------- + // Try lock, silence otherwise + +#ifndef STOAT_TEST_BUILD + if (pData->engine->isOffline()) + { + pData->singleMutex.lock(); + } + else +#endif + if (! pData->singleMutex.tryLock()) + { + for (uint32_t i=0; i < pData->audioOut.count; ++i) + { + for (uint32_t k=0; k < frames; ++k) + outBuffer[i][k+timeOffset] = 0.0f; + } + + return false; + } + + // -------------------------------------------------------------------------------------------------------- + // Set audio buffers + + float* bufferAudioIn[std::max(1u, pData->audioIn.count + pData->cvIn.count)]; + float* bufferAudioOut[std::max(1u, pData->audioOut.count + pData->cvOut.count)]; + + { + uint32_t i=0; + for (; i < pData->audioIn.count; ++i) + bufferAudioIn[i] = const_cast(inBuffer[i]+timeOffset); + for (; i < pData->cvIn.count; ++i) + bufferAudioIn[i] = const_cast(inBuffer[i]+timeOffset); + } + + { + uint32_t i=0; + for (; i < pData->audioOut.count; ++i) + bufferAudioOut[i] = fAudioOutBuffers[i]+timeOffset; + for (; i < pData->cvOut.count; ++i) + bufferAudioOut[i] = fAudioOutBuffers[i]+timeOffset; + } + + for (uint32_t i=0; i < pData->audioOut.count + pData->cvOut.count; ++i) + carla_zeroFloats(fAudioOutBuffers[i], frames); + + // -------------------------------------------------------------------------------------------------------- + // Set MIDI events + + // TODO -class CarlaPluginVST3 : public CarlaPlugin, - private CarlaPluginUI::Callback -{ -public: - CarlaPluginVST3(CarlaEngine* const engine, const uint id) - : CarlaPlugin(engine, id), - fUI() + // -------------------------------------------------------------------------------------------------------- + // Run plugin + + carla_v3_event_list eventList; + carla_v3_event_list* eventListPtr = &eventList; + + carla_v3_param_changes paramChanges; + carla_v3_param_changes* paramChangesPtr = ¶mChanges; + + v3_audio_bus_buffers processInputs = { + static_cast(pData->audioIn.count + pData->cvIn.count), + 0, { bufferAudioIn } + }; + v3_audio_bus_buffers processOutputs = { + static_cast(pData->audioOut.count + pData->cvOut.count), + 0, { bufferAudioOut } + }; + + v3_process_data processData = { + pData->engine->isOffline() ? V3_OFFLINE : V3_REALTIME, + V3_SAMPLE_32, + static_cast(frames), + static_cast(pData->audioIn.count + pData->cvIn.count), + static_cast(pData->audioOut.count + pData->cvOut.count), + &processInputs, + &processOutputs, + (v3_param_changes**)¶mChangesPtr, + (v3_param_changes**)¶mChangesPtr, + (v3_event_list**)&eventListPtr, + (v3_event_list**)&eventListPtr, + &fV3TimeContext + }; + + try { + v3_cpp_obj(fV3.processor)->process(fV3.processor, &processData); + } CARLA_SAFE_EXCEPTION("process"); + +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + // -------------------------------------------------------------------------------------------------------- + // Post-processing (dry/wet, volume and balance) + + { + const bool doDryWet = (pData->hints & PLUGIN_CAN_DRYWET) != 0 && carla_isNotEqual(pData->postProc.dryWet, 1.0f); + const bool doBalance = (pData->hints & PLUGIN_CAN_BALANCE) != 0 && ! (carla_isEqual(pData->postProc.balanceLeft, -1.0f) && carla_isEqual(pData->postProc.balanceRight, 1.0f)); + const bool isMono = (pData->audioIn.count == 1); + + bool isPair; + float bufValue, oldBufLeft[doBalance ? frames : 1]; + + uint32_t i=0; + + for (; i < pData->audioOut.count; ++i) + { + // Dry/Wet + if (doDryWet) + { + const uint32_t c = isMono ? 0 : i; + + for (uint32_t k=0; k < frames; ++k) + { + bufValue = inBuffer[c][k+timeOffset]; + fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); + } + } + + // Balance + if (doBalance) + { + isPair = (i % 2 == 0); + + if (isPair) + { + CARLA_ASSERT(i+1 < pData->audioOut.count); + carla_copyFloats(oldBufLeft, fAudioOutBuffers[i], frames); + } + + float balRangeL = (pData->postProc.balanceLeft + 1.0f)/2.0f; + float balRangeR = (pData->postProc.balanceRight + 1.0f)/2.0f; + + for (uint32_t k=0; k < frames; ++k) + { + if (isPair) + { + // left + fAudioOutBuffers[i][k] = oldBufLeft[k] * (1.0f - balRangeL); + fAudioOutBuffers[i][k] += fAudioOutBuffers[i+1][k] * (1.0f - balRangeR); + } + else + { + // right + fAudioOutBuffers[i][k] = fAudioOutBuffers[i][k] * balRangeR; + fAudioOutBuffers[i][k] += oldBufLeft[k] * balRangeL; + } + } + } + + // Volume (and buffer copy) + { + for (uint32_t k=0; k < frames; ++k) + outBuffer[i][k+timeOffset] = fAudioOutBuffers[i][k] * pData->postProc.volume; + } + } + + for (; i < pData->cvOut.count; ++i) + carla_copyFloats(outBuffer[i] + timeOffset, fAudioOutBuffers[i] + timeOffset, frames); + + } // End of Post-processing +#else // BUILD_BRIDGE_ALTERNATIVE_ARCH + for (uint32_t i=0; i < pData->audioOut.count + pData->cvOut.count; ++i) + carla_copyFloats(outBuffer[i] + timeOffset, fAudioOutBuffers[i] + timeOffset, frames); +#endif + + // -------------------------------------------------------------------------------------------------------- + + pData->singleMutex.unlock(); + return true; + } + + void bufferSizeChanged(const uint32_t newBufferSize) override { - carla_debug("CarlaPluginVST3::CarlaPluginVST3(%p, %i)", engine, id); + CARLA_ASSERT_INT(newBufferSize > 0, newBufferSize); + carla_debug("CarlaPluginVST2::bufferSizeChanged(%i)", newBufferSize); + + if (pData->active) + deactivate(); + + for (uint32_t i=0; i < pData->audioOut.count + pData->cvOut.count; ++i) + { + if (fAudioOutBuffers[i] != nullptr) + delete[] fAudioOutBuffers[i]; + fAudioOutBuffers[i] = new float[newBufferSize]; + } + + v3_process_setup setup = { + pData->engine->isOffline() ? V3_OFFLINE : V3_REALTIME, + V3_SAMPLE_32, + static_cast(newBufferSize), + pData->engine->getSampleRate() + }; + v3_cpp_obj(fV3.processor)->setup_processing(fV3.processor, &setup); + + if (pData->active) + activate(); } - ~CarlaPluginVST3() override + void sampleRateChanged(const double newSampleRate) override { - carla_debug("CarlaPluginVST3::~CarlaPluginVST3()"); + CARLA_ASSERT_INT(newSampleRate > 0.0, newSampleRate); + carla_debug("CarlaPluginVST3::sampleRateChanged(%g)", newSampleRate); - pData->singleMutex.lock(); - pData->masterMutex.lock(); + if (pData->active) + deactivate(); - if (pData->client != nullptr && pData->client->isActive()) - pData->client->deactivate(true); + v3_process_setup setup = { + pData->engine->isOffline() ? V3_OFFLINE : V3_REALTIME, + V3_SAMPLE_32, + static_cast(pData->engine->getBufferSize()), + newSampleRate + }; + v3_cpp_obj(fV3.processor)->setup_processing(fV3.processor, &setup); + + if (pData->active) + activate(); + } + + void offlineModeChanged(const bool isOffline) override + { + carla_debug("CarlaPluginVST3::offlineModeChanged(%d)", isOffline); if (pData->active) - { deactivate(); - pData->active = false; - } + + v3_process_setup setup = { + isOffline ? V3_OFFLINE : V3_REALTIME, + V3_SAMPLE_32, + static_cast(pData->engine->getBufferSize()), + pData->engine->getSampleRate() + }; + v3_cpp_obj(fV3.processor)->setup_processing(fV3.processor, &setup); + + if (pData->active) + activate(); } // ------------------------------------------------------------------- - // Information (base) + // Plugin buffers - PluginType getType() const noexcept override + void clearBuffers() noexcept override { - return PLUGIN_VST3; + carla_debug("CarlaPluginVST2::clearBuffers() - start"); + + if (fAudioOutBuffers != nullptr) + { + for (uint32_t i=0; i < pData->audioOut.count + pData->cvOut.count; ++i) + { + if (fAudioOutBuffers[i] != nullptr) + { + delete[] fAudioOutBuffers[i]; + fAudioOutBuffers[i] = nullptr; + } + } + + delete[] fAudioOutBuffers; + fAudioOutBuffers = nullptr; + } + + CarlaPlugin::clearBuffers(); + + carla_debug("CarlaPluginVST2::clearBuffers() - end"); } // ------------------------------------------------------------------- - // Information (count) + // Post-poned UI Stuff - // nothing + void uiParameterChange(const uint32_t index, const float value) noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fV3.controller != nullptr,); + CARLA_SAFE_ASSERT_RETURN(index < pData->param.count,); - // ------------------------------------------------------------------- - // Information (per-plugin data) + const double normalized = v3_cpp_obj(fV3.controller)->plain_parameter_to_normalised(fV3.controller, index, value); + v3_cpp_obj(fV3.controller)->set_parameter_normalised(fV3.controller, index, normalized); + } - // ------------------------------------------------------------------- - // Set data (state) + // ---------------------------------------------------------------------------------------------------------------- - // nothing + const void* getNativeDescriptor() const noexcept override + { + return fV3.component; + } - // ------------------------------------------------------------------- - // Set data (internal stuff) + const void* getExtraStuff() const noexcept override + { + return fV3.controller; + } - // ------------------------------------------------------------------- - // Set data (plugin-specific stuff) + // ---------------------------------------------------------------------------------------------------------------- - // ------------------------------------------------------------------- - // Set ui stuff + bool init(const CarlaPluginPtr plugin, + const char* const filename, + const char* name, + const char* const label, + const uint options) + { + CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr, false); - // ------------------------------------------------------------------- - // Plugin state + // ------------------------------------------------------------------------------------------------------------ + // first checks - // ------------------------------------------------------------------- - // Plugin processing + if (pData->client != nullptr) + { + pData->engine->setLastError("Plugin client is already registered"); + return false; + } - // ------------------------------------------------------------------- - // Plugin buffers + if (filename == nullptr || filename[0] == '\0') + { + pData->engine->setLastError("null filename"); + return false; + } - // ------------------------------------------------------------------- - // Post-poned UI Stuff + V3_ENTRYFN v3_entry; + V3_EXITFN v3_exit; + V3_GETFN v3_get; - // nothing + // filename is full path to binary + if (water::File(filename).existsAsFile()) + { + if (! pData->libOpen(filename)) + { + pData->engine->setLastError(pData->libError(filename)); + return false; + } - // ------------------------------------------------------------------- + v3_entry = pData->libSymbol(V3_ENTRYFNNAME); + v3_exit = pData->libSymbol(V3_EXITFNNAME); + v3_get = pData->libSymbol(V3_GETFNNAME); + } + // assume filename is a vst3 bundle + else + { + #ifdef CARLA_OS_MAC + if (! fMacBundleLoader.load(filename)) + { + pData->engine->setLastError("Failed to load VST3 bundle executable"); + return false; + } + + v3_entry = (V3_ENTRYFN)CFBundleGetFunctionPointerForName(fMacBundleLoader.getRef(), CFSTR(V3_ENTRYFNNAME)); + v3_exit = (V3_EXITFN)CFBundleGetFunctionPointerForName(fMacBundleLoader.getRef(), CFSTR(V3_EXITFNNAME)); + v3_get = (V3_GETFN)CFBundleGetFunctionPointerForName(fMacBundleLoader.getRef(), CFSTR(V3_GETFNNAME)); + #else + water::String binaryfilename = filename; + + if (!binaryfilename.endsWithChar(CARLA_OS_SEP)) + binaryfilename += CARLA_OS_SEP_STR; + + binaryfilename += "Contents" CARLA_OS_SEP_STR V3_CONTENT_DIR CARLA_OS_SEP_STR; + binaryfilename += water::File(filename).getFileNameWithoutExtension(); + #ifdef CARLA_OS_WIN + binaryfilename += ".vst3"; + #else + binaryfilename += ".so"; + #endif + + if (! water::File(binaryfilename).existsAsFile()) + { + pData->engine->setLastError("Failed to find a suitable VST3 bundle binary"); + return false; + } + + if (! pData->libOpen(binaryfilename.toRawUTF8())) + { + pData->engine->setLastError(pData->libError(binaryfilename.toRawUTF8())); + return false; + } + + v3_entry = pData->libSymbol(V3_ENTRYFNNAME); + v3_exit = pData->libSymbol(V3_EXITFNNAME); + v3_get = pData->libSymbol(V3_GETFNNAME); + #endif + } + + // ------------------------------------------------------------------------------------------------------------ + // ensure entry and exit points are available + + if (v3_entry == nullptr || v3_exit == nullptr || v3_get == nullptr) + { + pData->engine->setLastError("Not a VST3 plugin"); + return false; + } + + // ------------------------------------------------------------------------------------------------------------ + // call entry point + + #if defined(CARLA_OS_MAC) + v3_entry(pData->lib != nullptr ? pData->lib : fMacBundleLoader.ref); + #elif defined(CARLA_OS_WIN) + v3_entry(); + #else + v3_entry(pData->lib); + #endif + + // ------------------------------------------------------------------------------------------------------------ + // fetch initial factory + + v3_plugin_factory** const factory = v3_get(); + + if (factory == nullptr) + { + pData->engine->setLastError("VST3 factory failed to create a valid instance"); + return false; + } + + // ------------------------------------------------------------------------------------------------------------ + // initialize and find requested plugin + + fV3.exitfn = v3_exit; + fV3.factory1 = factory; + + if (! fV3.queryFactories(getHostContext())) + { + pData->engine->setLastError("VST3 plugin failed to properly create factories"); + return false; + } + + if (! fV3.findPlugin(fV3ClassInfo)) + { + pData->engine->setLastError("Failed to find the requested plugin in the VST3 bundle"); + return false; + } + + if (! fV3.initializePlugin(fV3ClassInfo.v1.class_id, getHostContext())) + { + pData->engine->setLastError("VST3 plugin failed to initialize"); + return false; + } + + // ------------------------------------------------------------------------------------------------------------ + // do some basic safety checks + + if (v3_cpp_obj(fV3.processor)->can_process_sample_size(fV3.processor, V3_SAMPLE_32) != V3_OK) + { + pData->engine->setLastError("VST3 plugin does not support 32bit audio, cannot continue"); + return false; + } + + // ------------------------------------------------------------------------------------------------------------ + // get info + + if (name != nullptr && name[0] != '\0') + { + pData->name = pData->engine->getUniquePluginName(name); + } + else + { + if (fV3ClassInfo.v1.name[0] != '\0') + pData->name = pData->engine->getUniquePluginName(fV3ClassInfo.v1.name); + else if (const char* const shortname = std::strrchr(filename, CARLA_OS_SEP)) + pData->name = pData->engine->getUniquePluginName(shortname+1); + else + pData->name = pData->engine->getUniquePluginName("unknown"); + } + + pData->filename = carla_strdup(filename); + + // ------------------------------------------------------------------------------------------------------------ + // register client + + pData->client = pData->engine->addClient(plugin); + + if (pData->client == nullptr || ! pData->client->isOk()) + { + pData->engine->setLastError("Failed to register plugin client"); + return false; + } + + // ------------------------------------------------------------------------------------------------------------ + // set default options + + pData->options = 0x0; + + if (v3_cpp_obj(fV3.processor)->get_latency_samples(fV3.processor) != 0 /*|| hasMidiOutput()*/ || + isPluginOptionEnabled(options, PLUGIN_OPTION_FIXED_BUFFERS)) + pData->options |= PLUGIN_OPTION_FIXED_BUFFERS; + + if (isPluginOptionEnabled(options, PLUGIN_OPTION_USE_CHUNKS)) + pData->options |= PLUGIN_OPTION_USE_CHUNKS; + + /* + if (hasMidiInput()) + { + if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CONTROL_CHANGES)) + pData->options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES; + if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CHANNEL_PRESSURE)) + pData->options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE; + if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH)) + pData->options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH; + if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_PITCHBEND)) + pData->options |= PLUGIN_OPTION_SEND_PITCHBEND; + if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_ALL_SOUND_OFF)) + pData->options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF; + if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_PROGRAM_CHANGES)) + pData->options |= PLUGIN_OPTION_SEND_PROGRAM_CHANGES; + if (isPluginOptionInverseEnabled(options, PLUGIN_OPTION_SKIP_SENDING_NOTES)) + pData->options |= PLUGIN_OPTION_SKIP_SENDING_NOTES; + } + */ + + /* + if (numPrograms > 1 && (pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) == 0) + if (isPluginOptionEnabled(options, PLUGIN_OPTION_MAP_PROGRAM_CHANGES)) + pData->options |= PLUGIN_OPTION_MAP_PROGRAM_CHANGES; + */ + + // ------------------------------------------------------------------------------------------------------------ + + return true; + } protected: void handlePluginUIClosed() override @@ -129,15 +1856,243 @@ protected: } private: +#ifdef CARLA_OS_MAC + BundleLoader fMacBundleLoader; +#endif + + bool fFirstActive; // first process() call after activate() + float** fAudioOutBuffers; + EngineTimeInfo fLastTimeInfo; + v3_process_context fV3TimeContext; + + CarlaScopedPointer fV3Application; + inline v3_funknown** getHostContext() const noexcept { return (v3_funknown**)&fV3Application; } + + // v3_class_info_2 is ABI compatible with v3_class_info + union ClassInfo { + v3_class_info v1; + v3_class_info_2 v2; + } fV3ClassInfo; + + struct Pointers { + V3_EXITFN exitfn; + v3_plugin_factory** factory1; + v3_plugin_factory_2** factory2; + v3_plugin_factory_3** factory3; + v3_component** component; + v3_edit_controller** controller; + v3_audio_processor** processor; + v3_plugin_view** view; + bool shouldTerminateComponent; + bool shouldTerminateController; + + Pointers() + : exitfn(nullptr), + factory1(nullptr), + factory2(nullptr), + factory3(nullptr), + component(nullptr), + controller(nullptr), + processor(nullptr), + view(nullptr), + shouldTerminateComponent(false), + shouldTerminateController(false) {} + + ~Pointers() + { + // must have been cleaned up by now + CARLA_SAFE_ASSERT(exitfn == nullptr); + } + + // must have exitfn and factory1 set + bool queryFactories(v3_funknown** const hostContext) + { + // query 2nd factory + if (v3_cpp_obj_query_interface(factory1, v3_plugin_factory_2_iid, &factory2) == V3_OK) + { + CARLA_SAFE_ASSERT_RETURN(factory2 != nullptr, exit()); + } + else + { + CARLA_SAFE_ASSERT(factory2 == nullptr); + factory2 = nullptr; + } + + // query 3rd factory + if (factory2 != nullptr && v3_cpp_obj_query_interface(factory2, v3_plugin_factory_3_iid, &factory3) == V3_OK) + { + CARLA_SAFE_ASSERT_RETURN(factory3 != nullptr, exit()); + } + else + { + CARLA_SAFE_ASSERT(factory3 == nullptr); + factory3 = nullptr; + } + + // set host context (application) if 3rd factory provided + if (factory3 != nullptr) + v3_cpp_obj(factory3)->set_host_context(factory3, hostContext); + + return true; + } + + // must have all possible factories and exitfn set + bool findPlugin(ClassInfo& classInfo) + { + // get factory info + v3_factory_info factoryInfo = {}; + CARLA_SAFE_ASSERT_RETURN(v3_cpp_obj(factory1)->get_factory_info(factory1, &factoryInfo) == V3_OK, exit()); + + // get num classes + const int32_t numClasses = v3_cpp_obj(factory1)->num_classes(factory1); + CARLA_SAFE_ASSERT_RETURN(numClasses > 0, exit()); + + // go through all relevant classes + for (int32_t i=0; iget_class_info_2(factory2, i, &classInfo.v2); + else + v3_cpp_obj(factory1)->get_class_info(factory1, i, &classInfo.v1); + + // safety check + CARLA_SAFE_ASSERT_CONTINUE(classInfo.v1.cardinality == 0x7FFFFFFF); + + // only check for audio plugins + if (std::strcmp(classInfo.v1.category, "Audio Module Class") != 0) + continue; + + // FIXME multi-plugin bundle + break; + } + + return true; + } + + bool initializePlugin(const v3_tuid uid, v3_funknown** const hostContext) + { + // create instance + void* instance = nullptr; + CARLA_SAFE_ASSERT_RETURN(v3_cpp_obj(factory1)->create_instance(factory1, uid, v3_component_iid, + &instance) == V3_OK, + exit()); + CARLA_SAFE_ASSERT_RETURN(instance != nullptr, exit()); + + component = static_cast(instance); + + // initialize instance + CARLA_SAFE_ASSERT_RETURN(v3_cpp_obj_initialize(component, hostContext) == V3_OK, exit()); + shouldTerminateComponent = true; + + // create edit controller + if (v3_cpp_obj_query_interface(component, v3_edit_controller_iid, &controller) != V3_OK) + controller = nullptr; + + // if we cannot cast from component, try to create edit controller from factory + if (controller == nullptr) + { + v3_tuid uid = {}; + if (v3_cpp_obj(component)->get_controller_class_id(component, uid) == V3_OK) + { + instance = nullptr; + if (v3_cpp_obj(factory1)->create_instance(factory1, uid, v3_edit_controller_iid, &instance) == V3_OK && instance != nullptr) + controller = static_cast(instance); + } + + CARLA_SAFE_ASSERT_RETURN(controller != nullptr, exit()); + + // component is separate from controller, needs its dedicated initialize and terminate + CARLA_SAFE_ASSERT_RETURN(v3_cpp_obj_initialize(controller, hostContext) == V3_OK, exit()); + shouldTerminateController = true; + } + + // create processor + CARLA_SAFE_ASSERT_RETURN(v3_cpp_obj_query_interface(component, v3_audio_processor_iid, + &processor) == V3_OK, exit()); + CARLA_SAFE_ASSERT_RETURN(processor != nullptr, exit()); + + // create view, ignoring result + view = v3_cpp_obj(controller)->create_view(controller, "view"); + + return true; + } + + bool exit() + { + // must be deleted by now + CARLA_SAFE_ASSERT(view == nullptr); + + if (processor != nullptr) + { + v3_cpp_obj_unref(processor); + processor = nullptr; + } + + if (controller != nullptr) + { + if (shouldTerminateController) + { + v3_cpp_obj_terminate(controller); + shouldTerminateController = false; + } + + v3_cpp_obj_unref(controller); + component = nullptr; + } + + if (component != nullptr) + { + if (shouldTerminateComponent) + { + v3_cpp_obj_terminate(component); + shouldTerminateComponent = false; + } + + v3_cpp_obj_unref(component); + component = nullptr; + } + + if (factory3 != nullptr) + { + v3_cpp_obj_unref(factory3); + factory3 = nullptr; + } + + if (factory2 != nullptr) + { + v3_cpp_obj_unref(factory2); + factory2 = nullptr; + } + + if (factory1 != nullptr) + { + v3_cpp_obj_unref(factory1); + factory1 = nullptr; + } + + if (exitfn != nullptr) + { + exitfn(); + exitfn = nullptr; + } + + // return false so it can be used as error/fail condition + return false; + } + + } fV3; + struct UI { + bool isAttached; bool isEmbed; - bool isOpen; bool isVisible; CarlaPluginUI* window; UI() noexcept - : isEmbed(false), - isOpen(false), + : isAttached(false), + isEmbed(false), isVisible(false), window(nullptr) {} @@ -160,25 +2115,20 @@ private: CarlaPluginPtr CarlaPlugin::newVST3(const Initializer& init) { - carla_debug("CarlaPlugin::newVST3({%p, \"%s\", \"%s\", " P_INT64 "})", - init.engine, init.filename, init.name, init.uniqueId); + carla_debug("CarlaPlugin::newVST3({%p, \"%s\", \"%s\", \"%s\"})", + init.engine, init.filename, init.name, init.label); #ifdef USE_JUCE_FOR_VST3 if (std::getenv("CARLA_DO_NOT_USE_JUCE_FOR_VST3") == nullptr) return newJuce(init, "VST3"); #endif - init.engine->setLastError("VST3 support not available"); - return nullptr; - - /* - std::shared_ptr plugin(new CarlaPluginVST2(init.engine, init.id)); + std::shared_ptr plugin(new CarlaPluginVST3(init.engine, init.id)); - if (! plugin->init(plugin, init.filename, init.name, init.uniqueId, init.options)) + if (! plugin->init(plugin, init.filename, init.name, init.label, init.options)) return nullptr; return plugin; - */ } // ------------------------------------------------------------------------------------------------------------------- diff --git a/source/discovery/carla-discovery.cpp b/source/discovery/carla-discovery.cpp index b54b7a318..81595cf73 100644 --- a/source/discovery/carla-discovery.cpp +++ b/source/discovery/carla-discovery.cpp @@ -1477,14 +1477,14 @@ static void do_vst3_check(lib_t& libHandle, const char* const filename, const bo binaryfilename += CARLA_OS_SEP_STR; binaryfilename += "Contents" CARLA_OS_SEP_STR V3_CONTENT_DIR CARLA_OS_SEP_STR; - binaryfilename += File(filename).getFileNameWithoutExtension(); + binaryfilename += water::File(filename).getFileNameWithoutExtension(); # ifdef CARLA_OS_WIN binaryfilename += ".vst3"; # else binaryfilename += ".so"; # endif - if (! File(binaryfilename).existsAsFile()) + if (! water::File(binaryfilename).existsAsFile()) { DISCOVERY_OUT("error", "Failed to find a suitable VST3 bundle binary"); return; @@ -1501,12 +1501,12 @@ static void do_vst3_check(lib_t& libHandle, const char* const filename, const bo } #ifndef CARLA_OS_MAC - // ensure entry and exist points are available v3_entry = lib_symbol(libHandle, V3_ENTRYFNNAME); v3_exit = lib_symbol(libHandle, V3_EXITFNNAME); v3_get = lib_symbol(libHandle, V3_GETFNNAME); #endif + // ensure entry and exit points are available if (v3_entry == nullptr || v3_exit == nullptr || v3_get == nullptr) { DISCOVERY_OUT("error", "Not a VST3 plugin");