/* * Carla LV2 Single Plugin * Copyright (C) 2017 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 * published by the Free Software Foundation; either version 2 of * the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ #ifndef BUILD_BRIDGE # error This file should not be compiled if not building bridge #endif #include "engine/CarlaEngineInternal.hpp" #include "CarlaPlugin.hpp" #include "CarlaBackendUtils.hpp" #include "CarlaEngineUtils.hpp" #include "CarlaLv2Utils.hpp" #include "CarlaUtils.h" #include "water/files/File.h" // --------------------------------------------------------------------------------------------------------------------- // -Weffc++ compat ext widget extern "C" { typedef struct _LV2_External_UI_Widget_Compat { void (*run )(struct _LV2_External_UI_Widget_Compat*); void (*show)(struct _LV2_External_UI_Widget_Compat*); void (*hide)(struct _LV2_External_UI_Widget_Compat*); _LV2_External_UI_Widget_Compat() noexcept : run(nullptr), show(nullptr), hide(nullptr) {} } LV2_External_UI_Widget_Compat; } // --------------------------------------------------------------------------------------------------------------------- CARLA_BACKEND_START_NAMESPACE #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Weffc++" #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Weffc++" #endif class CarlaEngineLV2Single : public CarlaEngine, public LV2_External_UI_Widget_Compat { #if defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) # pragma GCC diagnostic pop #endif public: CarlaEngineLV2Single(const uint32_t bufferSize, const double sampleRate, const char* const bundlePath, const LV2_URID_Map* uridMap) : fPlugin(nullptr), fIsActive(false), fIsOffline(false), fPorts(), fUI() { run = extui_run; show = extui_show; hide = extui_hide; CARLA_SAFE_ASSERT_RETURN(pData->curPluginCount == 0,) CARLA_SAFE_ASSERT_RETURN(pData->plugins[0].plugin == nullptr,); // xxxxx CarlaString binaryDir(bundlePath); binaryDir += CARLA_OS_SEP_STR "bin" CARLA_OS_SEP_STR; CarlaString resourceDir(bundlePath); resourceDir += CARLA_OS_SEP_STR "res" CARLA_OS_SEP_STR; pData->bufferSize = bufferSize; pData->sampleRate = sampleRate; pData->initTime(nullptr); pData->options.processMode = ENGINE_PROCESS_MODE_BRIDGE; pData->options.transportMode = ENGINE_TRANSPORT_MODE_PLUGIN; pData->options.forceStereo = false; pData->options.preferPluginBridges = false; pData->options.preferUiBridges = false; init("LV2-Export"); if (pData->options.resourceDir != nullptr) delete[] pData->options.resourceDir; if (pData->options.binaryDir != nullptr) delete[] pData->options.binaryDir; pData->options.binaryDir = binaryDir.dup(); pData->options.resourceDir = resourceDir.dup(); setCallback(_engine_callback, this); using water::File; const File pluginFile(File::getSpecialLocation(File::currentExecutableFile).withFileExtension("xml")); if (! loadProject(pluginFile.getFullPathName().toRawUTF8())) { carla_stderr2("Failed to init plugin, possible reasons: %s", getLastError()); return; } CARLA_SAFE_ASSERT_RETURN(pData->curPluginCount == 1,) fPlugin = pData->plugins[0].plugin; CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,); CARLA_SAFE_ASSERT_RETURN(fPlugin->isEnabled(),); fPorts.init(fPlugin); } ~CarlaEngineLV2Single() { if (fPlugin != nullptr && fIsActive) fPlugin->setActive(false, false, false); close(); } bool hasPlugin() noexcept { return fPlugin != nullptr; } // ----------------------------------------------------------------------------------------------------------------- // LV2 functions void lv2_connect_port(const uint32_t port, void* const dataLocation) noexcept { fPorts.connectPort(port, dataLocation); } void lv2_activate() noexcept { CARLA_SAFE_ASSERT_RETURN(! fIsActive,); fPlugin->setActive(true, false, false); fIsActive = true; } void lv2_deactivate() noexcept { CARLA_SAFE_ASSERT_RETURN(fIsActive,); fIsActive = false; fPlugin->setActive(false, false, false); } void lv2_run(const uint32_t frames) { fIsOffline = (fPorts.freewheel != nullptr && *fPorts.freewheel >= 0.5f); // Check for updated parameters float curValue; for (uint32_t i=0; i < fPorts.numParams; ++i) { if (fPorts.paramsOut[i]) continue; CARLA_SAFE_ASSERT_CONTINUE(fPorts.paramsPtr[i] != nullptr) curValue = *fPorts.paramsPtr[i]; if (carla_isEqual(fPorts.paramsLast[i], curValue)) continue; fPorts.paramsLast[i] = curValue; fPlugin->setParameterValue(i, curValue, false, false, false); } if (frames == 0) return fPorts.updateOutputs(); if (fPlugin->tryLock(fIsOffline)) { fPlugin->initBuffers(); fPlugin->process(fPorts.audioIns, fPorts.audioOuts, nullptr, nullptr, frames); fPlugin->unlock(); } else { for (uint32_t i=0; iURI, LV2_EXTERNAL_UI__Host) == 0 || std::strcmp(features[i]->URI, LV2_EXTERNAL_UI_DEPRECATED_URI) == 0) { fUI.host = (const LV2_External_UI_Host*)features[i]->data; } else if (std::strcmp(features[i]->URI, LV2_URID__map) == 0) { uridMap = (const LV2_URID_Map*)features[i]->data; } } if (fUI.host != nullptr) { fUI.name = fUI.host->plugin_human_id; *widget = (LV2_External_UI_Widget*)this; return; } // ------------------------------------------------------------------------------------------------------------- // no external-ui support, use showInterface for (int i=0; features[i] != nullptr; ++i) { if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) == 0) { const LV2_Options_Option* const options((const LV2_Options_Option*)features[i]->data); for (int j=0; options[j].key != 0; ++j) { if (options[j].key == uridMap->map(uridMap->handle, LV2_UI__windowTitle)) { fUI.name = (const char*)options[j].value; break; } } break; } } if (fUI.name.isEmpty()) fUI.name = fPlugin->getName(); *widget = nullptr; } void lv2ui_port_event(uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) const { if (format != 0 || bufferSize != sizeof(float) || buffer == nullptr) return; if (portIndex >= fPorts.indexOffset || ! fUI.visible) return; const float value(*(const float*)buffer); fPlugin->uiParameterChange(portIndex-fPorts.indexOffset, value); } void lv2ui_cleanup() { if (fUI.visible) handleUiHide(); fUI.writeFunction = nullptr; fUI.controller = nullptr; fUI.host = nullptr; } // ----------------------------------------------------------------------------------------------------------------- int lv2ui_idle() const { if (! fUI.visible) return 1; handleUiRun(); return 0; } int lv2ui_show() { handleUiShow(); return 0; } int lv2ui_hide() { handleUiHide(); return 0; } protected: // ----------------------------------------------------------------------------------------------------------------- // CarlaEngine virtual calls bool init(const char* const clientName) override { carla_stdout("CarlaEngineNative::init(\"%s\")", clientName); if (! pData->init(clientName)) { close(); setLastError("Failed to init internal data"); return false; } return true; } bool isRunning() const noexcept override { return fIsActive; } bool isOffline() const noexcept override { return fIsOffline; } bool usesConstantBufferSize() const noexcept override { return true; } EngineType getType() const noexcept override { return kEngineTypePlugin; } const char* getCurrentDriverName() const noexcept override { return "LV2 Plugin"; } void engineCallback(const EngineCallbackOpcode action, const uint pluginId, const int value1, const int value2, const float value3, const char* const valueStr) { switch (action) { case ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED: CARLA_SAFE_ASSERT_RETURN(value1 >= 0,); if (fUI.writeFunction != nullptr && fUI.controller != nullptr && fUI.visible) fUI.writeFunction(fUI.controller, static_cast(value1)+fPorts.indexOffset, sizeof(float), 0, &value3); break; case ENGINE_CALLBACK_UI_STATE_CHANGED: fUI.visible = value1 == 1; if (fUI.host != nullptr) fUI.host->ui_closed(fUI.controller); break; case ENGINE_CALLBACK_IDLE: break; default: carla_stdout("engineCallback(%i:%s, %u, %i, %i, %f, %s)", action, EngineCallbackOpcode2Str(action), pluginId, value1, value2, value3, valueStr); break; } } // ----------------------------------------------------------------------------------------------------------------- void handleUiRun() const { try { fPlugin->uiIdle(); } CARLA_SAFE_EXCEPTION("fPlugin->uiIdle()") } void handleUiShow() { fPlugin->showCustomUI(true); fUI.visible = true; } void handleUiHide() { fUI.visible = false; fPlugin->showCustomUI(false); } // ----------------------------------------------------------------------------------------------------------------- private: CarlaPlugin* fPlugin; // Lv2 host data bool fIsActive; bool fIsOffline; struct Ports { uint32_t numAudioIns; uint32_t numAudioOuts; uint32_t numParams; const float** audioIns; /* */ float** audioOuts; float* freewheel; float* paramsLast; float** paramsPtr; bool* paramsOut; uint32_t indexOffset; const CarlaPlugin* plugin; Ports() : numAudioIns(0), numAudioOuts(0), numParams(0), audioIns(nullptr), audioOuts(nullptr), freewheel(nullptr), paramsLast(nullptr), paramsPtr(nullptr), paramsOut(nullptr), indexOffset(1), plugin(nullptr) {} ~Ports() { if (audioIns != nullptr) { delete[] audioIns; audioIns = nullptr; } if (audioOuts != nullptr) { delete[] audioOuts; audioOuts = nullptr; } if (paramsLast != nullptr) { delete[] paramsLast; paramsLast = nullptr; } if (paramsPtr != nullptr) { delete[] paramsPtr; paramsPtr = nullptr; } if (paramsOut != nullptr) { delete[] paramsOut; paramsOut = nullptr; } } void init(const CarlaPlugin* const pl) { plugin = pl; if ((numAudioIns = plugin->getAudioInCount()) > 0) { audioIns = new const float*[numAudioIns]; for (uint32_t i=0; i < numAudioIns; ++i) audioIns[i] = nullptr; } if ((numAudioOuts = plugin->getAudioOutCount()) > 0) { audioOuts = new float*[numAudioOuts]; for (uint32_t i=0; i < numAudioOuts; ++i) audioOuts[i] = nullptr; } if ((numParams = plugin->getParameterCount()) > 0) { paramsLast = new float[numParams]; paramsPtr = new float*[numParams]; paramsOut = new bool[numParams]; for (uint32_t i=0; i < numParams; ++i) { paramsLast[i] = plugin->getParameterValue(i); paramsPtr [i] = nullptr; paramsOut [i] = plugin->isParameterOutput(i); } } indexOffset = numAudioIns + numAudioOuts + 1; } void connectPort(const uint32_t port, void* const dataLocation) noexcept { uint32_t index = 0; for (uint32_t i=0; i < numAudioIns; ++i) { if (port == index++) { audioIns[i] = (float*)dataLocation; return; } } for (uint32_t i=0; i < numAudioOuts; ++i) { if (port == index++) { audioOuts[i] = (float*)dataLocation; return; } } if (port == index++) { freewheel = (float*)dataLocation; return; } for (uint32_t i=0; i < numParams; ++i) { if (port == index++) { paramsPtr[i] = (float*)dataLocation; return; } } } void updateOutputs() noexcept { for (uint32_t i=0; i < numParams; ++i) { if (! paramsOut[i]) continue; paramsLast[i] = plugin->getParameterValue(i); if (paramsPtr[i] != nullptr) *paramsPtr[i] = paramsLast[i]; } } CARLA_DECLARE_NON_COPY_STRUCT(Ports); } fPorts; struct UI { LV2UI_Write_Function writeFunction; LV2UI_Controller controller; const LV2_External_UI_Host* host; CarlaString name; bool visible; UI() : writeFunction(nullptr), controller(nullptr), host(nullptr), name(), visible(false) {} CARLA_DECLARE_NON_COPY_STRUCT(UI) } fUI; // ------------------------------------------------------------------- #define handlePtr ((CarlaEngineLV2Single*)handle) static void extui_run(LV2_External_UI_Widget_Compat* handle) { handlePtr->handleUiRun(); } static void extui_show(LV2_External_UI_Widget_Compat* handle) { handlePtr->handleUiShow(); } static void extui_hide(LV2_External_UI_Widget_Compat* handle) { handlePtr->handleUiHide(); } static void _engine_callback(void* handle, EngineCallbackOpcode action, uint pluginId, int value1, int value2, float value3, const char* valueStr) { handlePtr->engineCallback(action, pluginId, value1, value2, value3, valueStr); } #undef handlePtr CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineLV2Single) }; CARLA_BACKEND_END_NAMESPACE CARLA_BACKEND_USE_NAMESPACE // --------------------------------------------------------------------------------------------------------------------- // LV2 DSP functions static LV2_Handle lv2_instantiate(const LV2_Descriptor* lv2Descriptor, double sampleRate, const char* bundlePath, const LV2_Feature* const* features) { carla_stdout("lv2_instantiate(%p, %g, %s, %p)", lv2Descriptor, sampleRate, bundlePath, features); const LV2_Options_Option* options = nullptr; const LV2_URID_Map* uridMap = nullptr; const LV2_URID_Unmap* uridUnmap = nullptr; for (int i=0; features[i] != nullptr; ++i) { if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) == 0) options = (const LV2_Options_Option*)features[i]->data; else if (std::strcmp(features[i]->URI, LV2_URID__map) == 0) uridMap = (const LV2_URID_Map*)features[i]->data; else if (std::strcmp(features[i]->URI, LV2_URID__unmap) == 0) uridUnmap = (const LV2_URID_Unmap*)features[i]->data; } if (options == nullptr || uridMap == nullptr) { carla_stderr("Host doesn't provide option or urid-map features"); return nullptr; } uint32_t bufferSize = 0; for (int i=0; options[i].key != 0; ++i) { if (uridUnmap != nullptr) { carla_stdout("Host option %i:\"%s\"", i, uridUnmap->unmap(uridUnmap->handle, options[i].key)); } if (options[i].key == uridMap->map(uridMap->handle, LV2_BUF_SIZE__nominalBlockLength)) { if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Int)) { const int value(*(const int*)options[i].value); CARLA_SAFE_ASSERT_CONTINUE(value > 0); bufferSize = static_cast(value); } else { carla_stderr("Host provides nominalBlockLength but has wrong value type"); } break; } if (options[i].key == uridMap->map(uridMap->handle, LV2_BUF_SIZE__maxBlockLength)) { if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Int)) { const int value(*(const int*)options[i].value); CARLA_SAFE_ASSERT_CONTINUE(value > 0); bufferSize = static_cast(value); } else { carla_stderr("Host provides maxBlockLength but has wrong value type"); } // no break, continue in case host supports nominalBlockLength } } if (bufferSize == 0) { carla_stderr("Host doesn't provide bufferSize feature"); return nullptr; } CarlaEngineLV2Single* const instance(new CarlaEngineLV2Single(bufferSize, sampleRate, bundlePath, uridMap)); if (instance->hasPlugin()) return (LV2_Handle)instance; delete instance; return nullptr; } #define instancePtr ((CarlaEngineLV2Single*)instance) static void lv2_connect_port(LV2_Handle instance, uint32_t port, void* dataLocation) { instancePtr->lv2_connect_port(port, dataLocation); } static void lv2_activate(LV2_Handle instance) { carla_stdout("lv2_activate(%p)", instance); instancePtr->lv2_activate(); } static void lv2_run(LV2_Handle instance, uint32_t sampleCount) { instancePtr->lv2_run(sampleCount); } static void lv2_deactivate(LV2_Handle instance) { carla_stdout("lv2_deactivate(%p)", instance); instancePtr->lv2_deactivate(); } static void lv2_cleanup(LV2_Handle instance) { carla_stdout("lv2_cleanup(%p)", instance); delete instancePtr; } static const void* lv2_extension_data(const char* uri) { carla_stdout("lv2_extension_data(\"%s\")", uri); return nullptr; } #undef instancePtr // --------------------------------------------------------------------------------------------------------------------- // LV2 UI functions static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, const char*, const char*, LV2UI_Write_Function writeFunction, LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature* const* features) { carla_debug("lv2ui_instantiate(..., %p, %p, %p)", writeFunction, controller, widget, features); CarlaEngineLV2Single* engine = nullptr; for (int i=0; features[i] != nullptr; ++i) { if (std::strcmp(features[i]->URI, LV2_INSTANCE_ACCESS_URI) == 0) { engine = (CarlaEngineLV2Single*)features[i]->data; break; } } if (engine == nullptr) { carla_stderr("Host doesn't support instance-access, cannot show UI"); return nullptr; } engine->lv2ui_instantiate(writeFunction, controller, widget, features); return (LV2UI_Handle)engine; } #define uiPtr ((CarlaEngineLV2Single*)ui) static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) { carla_debug("lv2ui_port_event(%p, %i, %i, %i, %p)", ui, portIndex, bufferSize, format, buffer); uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer); } static void lv2ui_cleanup(LV2UI_Handle ui) { carla_debug("lv2ui_cleanup(%p)", ui); uiPtr->lv2ui_cleanup(); } static int lv2ui_idle(LV2UI_Handle ui) { return uiPtr->lv2ui_idle(); } static int lv2ui_show(LV2UI_Handle ui) { carla_debug("lv2ui_show(%p)", ui); return uiPtr->lv2ui_show(); } static int lv2ui_hide(LV2UI_Handle ui) { carla_debug("lv2ui_hide(%p)", ui); return uiPtr->lv2ui_hide(); } static const void* lv2ui_extension_data(const char* uri) { carla_stdout("lv2ui_extension_data(\"%s\")", uri); static const LV2UI_Idle_Interface uiidle = { lv2ui_idle }; static const LV2UI_Show_Interface uishow = { lv2ui_show, lv2ui_hide }; if (std::strcmp(uri, LV2_UI__idleInterface) == 0) return &uiidle; if (std::strcmp(uri, LV2_UI__showInterface) == 0) return &uishow; return nullptr; } #undef uiPtr // --------------------------------------------------------------------------------------------------------------------- // Startup code CARLA_EXPORT const LV2_Descriptor* lv2_descriptor(uint32_t index) { carla_stdout("lv2_descriptor(%i)", index); if (index != 0) return nullptr; static CarlaString ret; if (ret.isEmpty()) { using namespace water; const File file(File::getSpecialLocation(File::currentExecutableFile).withFileExtension("ttl")); ret = String("file://" + file.getFullPathName()).toRawUTF8(); } static const LV2_Descriptor desc = { /* URI */ ret.buffer(), /* instantiate */ lv2_instantiate, /* connect_port */ lv2_connect_port, /* activate */ lv2_activate, /* run */ lv2_run, /* deactivate */ lv2_deactivate, /* cleanup */ lv2_cleanup, /* extension_data */ lv2_extension_data }; return &desc; } CARLA_EXPORT const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) { carla_stdout("lv2ui_descriptor(%i)", index); static CarlaString ret; if (ret.isEmpty()) { using namespace water; const File file(File::getSpecialLocation(File::currentExecutableFile).getSiblingFile("ext-ui")); ret = String("file://" + file.getFullPathName()).toRawUTF8(); } static const LV2UI_Descriptor lv2UiExtDesc = { /* URI */ ret.buffer(), /* instantiate */ lv2ui_instantiate, /* cleanup */ lv2ui_cleanup, /* port_event */ lv2ui_port_event, /* extension_data */ lv2ui_extension_data }; return (index == 0) ? &lv2UiExtDesc : nullptr; } // ---------------------------------------------------------------------------------------------------------------------