Mercurial > hg > pub > prymula > com
diff DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoUILV2.cpp @ 3:84e66ea83026
DPF-Prymula-audioplugins-0.231015-2
author | prymula <prymula76@outlook.com> |
---|---|
date | Mon, 16 Oct 2023 21:53:34 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoUILV2.cpp Mon Oct 16 21:53:34 2023 +0200 @@ -0,0 +1,929 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 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. + */ + +#include "DistrhoUIInternal.hpp" + +#include "../extra/String.hpp" + +#include "lv2/atom.h" +#include "lv2/atom-util.h" +#include "lv2/data-access.h" +#include "lv2/instance-access.h" +#include "lv2/midi.h" +#include "lv2/options.h" +#include "lv2/parameters.h" +#include "lv2/patch.h" +#include "lv2/ui.h" +#include "lv2/urid.h" +#include "lv2/lv2_kxstudio_properties.h" +#include "lv2/lv2_programs.h" + +#ifndef DISTRHO_PLUGIN_LV2_STATE_PREFIX +# define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:" +#endif + +START_NAMESPACE_DISTRHO + +typedef struct _LV2_Atom_MidiEvent { + LV2_Atom atom; /**< Atom header. */ + uint8_t data[3]; /**< MIDI data (body). */ +} LV2_Atom_MidiEvent; + +#if ! DISTRHO_PLUGIN_WANT_STATE +static constexpr const setStateFunc setStateCallback = nullptr; +#endif +#if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT +static constexpr const sendNoteFunc sendNoteCallback = nullptr; +#endif + +// ----------------------------------------------------------------------- + +template <class LV2F> +static const LV2F* getLv2Feature(const LV2_Feature* const* features, const char* const uri) +{ + for (int i=0; features[i] != nullptr; ++i) + { + if (std::strcmp(features[i]->URI, uri) == 0) + return (const LV2F*)features[i]->data; + } + + return nullptr; +} + +class UiLv2 +{ +public: + UiLv2(const char* const bundlePath, + const intptr_t winId, + const LV2_Options_Option* options, + const LV2_URID_Map* const uridMap, + const LV2_Feature* const* const features, + const LV2UI_Controller controller, + const LV2UI_Write_Function writeFunc, + LV2UI_Widget* const widget, + void* const dspPtr, + const float sampleRate, + const float scaleFactor, + const uint32_t bgColor, + const uint32_t fgColor, + const char* const appClassName) + : fUridMap(uridMap), + fUridUnmap(getLv2Feature<LV2_URID_Unmap>(features, LV2_URID__unmap)), + fUiPortMap(getLv2Feature<LV2UI_Port_Map>(features, LV2_UI__portMap)), + fUiRequestValue(getLv2Feature<LV2UI_Request_Value>(features, LV2_UI__requestValue)), + fUiTouch(getLv2Feature<LV2UI_Touch>(features, LV2_UI__touch)), + fController(controller), + fWriteFunction(writeFunc), + fURIDs(uridMap), + fBypassParameterIndex(fUiPortMap != nullptr ? fUiPortMap->port_index(fUiPortMap->handle, "lv2_enabled") + : LV2UI_INVALID_PORT_INDEX), + fWinIdWasNull(winId == 0), + fUI(this, winId, sampleRate, + editParameterCallback, + setParameterCallback, + setStateCallback, + sendNoteCallback, + nullptr, // resize is very messy, hosts can do it without extensions + fileRequestCallback, + bundlePath, dspPtr, scaleFactor, bgColor, fgColor, appClassName) + { + if (widget != nullptr) + *widget = (LV2UI_Widget)fUI.getNativeWindowHandle(); + + #if DISTRHO_PLUGIN_WANT_STATE + // tell the DSP we're ready to receive msgs + setState("__dpf_ui_data__", ""); + #endif + + if (winId != 0) + return; + + // if winId == 0 then options must not be null + DISTRHO_SAFE_ASSERT_RETURN(options != nullptr,); + + #ifndef __EMSCRIPTEN__ + const LV2_URID uridWindowTitle = uridMap->map(uridMap->handle, LV2_UI__windowTitle); + const LV2_URID uridTransientWinId = uridMap->map(uridMap->handle, LV2_KXSTUDIO_PROPERTIES__TransientWindowId); + + const char* windowTitle = nullptr; + + for (int i=0; options[i].key != 0; ++i) + { + if (options[i].key == uridTransientWinId) + { + if (options[i].type == fURIDs.atomLong) + { + if (const int64_t transientWinId = *(const int64_t*)options[i].value) + fUI.setWindowTransientWinId(static_cast<intptr_t>(transientWinId)); + } + else + d_stderr("Host provides transientWinId but has wrong value type"); + } + else if (options[i].key == uridWindowTitle) + { + if (options[i].type == fURIDs.atomString) + { + windowTitle = (const char*)options[i].value; + } + else + d_stderr("Host provides windowTitle but has wrong value type"); + } + } + + if (windowTitle == nullptr) + windowTitle = DISTRHO_PLUGIN_NAME; + + fUI.setWindowTitle(windowTitle); + #endif + } + + // ------------------------------------------------------------------- + + void lv2ui_port_event(const uint32_t rindex, const uint32_t bufferSize, const uint32_t format, const void* const buffer) + { + if (format == 0) + { + const uint32_t parameterOffset = fUI.getParameterOffset(); + + if (rindex < parameterOffset) + return; + + DISTRHO_SAFE_ASSERT_RETURN(bufferSize == sizeof(float),) + + float value = *(const float*)buffer; + + if (rindex == fBypassParameterIndex) + value = 1.0f - value; + + fUI.parameterChanged(rindex-parameterOffset, value); + } + #if DISTRHO_PLUGIN_WANT_STATE + else if (format == fURIDs.atomEventTransfer) + { + const LV2_Atom* const atom = (const LV2_Atom*)buffer; + + if (atom->type == fURIDs.dpfKeyValue) + { + const char* const key = (const char*)LV2_ATOM_BODY_CONST(atom); + const char* const value = key+(std::strlen(key)+1); + + fUI.stateChanged(key, value); + } + else if (atom->type == fURIDs.atomObject && fUridUnmap != nullptr) + { + const LV2_Atom_Object* const obj = (const LV2_Atom_Object*)atom; + + const LV2_Atom* property = nullptr; + const LV2_Atom* atomvalue = nullptr; + lv2_atom_object_get(obj, fURIDs.patchProperty, &property, fURIDs.patchValue, &atomvalue, 0); + + DISTRHO_SAFE_ASSERT_RETURN(property != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(atomvalue != nullptr,); + + DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID,); + DISTRHO_SAFE_ASSERT_RETURN(atomvalue->type == fURIDs.atomPath || atomvalue->type == fURIDs.atomString,); + + if (property != nullptr && property->type == fURIDs.atomURID && + atomvalue != nullptr && (atomvalue->type == fURIDs.atomPath || atomvalue->type == fURIDs.atomString)) + { + const LV2_URID dpf_lv2_urid = ((const LV2_Atom_URID*)property)->body; + DISTRHO_SAFE_ASSERT_RETURN(dpf_lv2_urid != 0,); + + const char* const dpf_lv2_key = fUridUnmap->unmap(fUridUnmap->handle, dpf_lv2_urid); + DISTRHO_SAFE_ASSERT_RETURN(dpf_lv2_key != nullptr,); + + /*constexpr*/ const size_t reqLen = std::strlen(DISTRHO_PLUGIN_URI "#"); + DISTRHO_SAFE_ASSERT_RETURN(std::strlen(dpf_lv2_key) > reqLen,); + + const char* const key = dpf_lv2_key + reqLen; + const char* const value = (const char*)LV2_ATOM_BODY_CONST(atomvalue); + + fUI.stateChanged(key, value); + } + } + else if (atom->type == fURIDs.midiEvent) + { + // ignore + } + else + { + d_stdout("DPF :: received atom not handled :: %s", + fUridUnmap != nullptr ? fUridUnmap->unmap(fUridUnmap->handle, atom->type) : "(null)"); + } + } + #endif + } + + // ------------------------------------------------------------------- + + int lv2ui_idle() + { + if (fWinIdWasNull) + return (fUI.plugin_idle() && fUI.isVisible()) ? 0 : 1; + + return fUI.plugin_idle() ? 0 : 1; + } + + int lv2ui_show() + { + return fUI.setWindowVisible(true) ? 0 : 1; + } + + int lv2ui_hide() + { + return fUI.setWindowVisible(false) ? 0 : 1; + } + + // ------------------------------------------------------------------- + + uint32_t lv2_get_options(LV2_Options_Option* const /*options*/) + { + // currently unused + return LV2_OPTIONS_ERR_UNKNOWN; + } + + uint32_t lv2_set_options(const LV2_Options_Option* const options) + { + for (int i=0; options[i].key != 0; ++i) + { + if (options[i].key == fURIDs.paramSampleRate) + { + if (options[i].type == fURIDs.atomFloat) + { + const float sampleRate = *(const float*)options[i].value; + fUI.setSampleRate(sampleRate, true); + continue; + } + else + { + d_stderr("Host changed UI sample-rate but with wrong value type"); + continue; + } + } + } + + return LV2_OPTIONS_SUCCESS; + } + + // ------------------------------------------------------------------- + + #if DISTRHO_PLUGIN_WANT_PROGRAMS + void lv2ui_select_program(const uint32_t bank, const uint32_t program) + { + const uint32_t realProgram = bank * 128 + program; + + fUI.programLoaded(realProgram); + } + #endif + + // ------------------------------------------------------------------- + +private: + // LV2 features + const LV2_URID_Map* const fUridMap; + const LV2_URID_Unmap* const fUridUnmap; + const LV2UI_Port_Map* const fUiPortMap; + const LV2UI_Request_Value* const fUiRequestValue; + const LV2UI_Touch* const fUiTouch; + + // LV2 UI stuff + const LV2UI_Controller fController; + const LV2UI_Write_Function fWriteFunction; + + // LV2 URIDs + const struct URIDs { + const LV2_URID_Map* _uridMap; + const LV2_URID dpfKeyValue; + const LV2_URID atomEventTransfer; + const LV2_URID atomFloat; + const LV2_URID atomLong; + const LV2_URID atomObject; + const LV2_URID atomPath; + const LV2_URID atomString; + const LV2_URID atomURID; + const LV2_URID midiEvent; + const LV2_URID paramSampleRate; + const LV2_URID patchProperty; + const LV2_URID patchSet; + const LV2_URID patchValue; + + URIDs(const LV2_URID_Map* const uridMap) + : _uridMap(uridMap), + dpfKeyValue(map(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")), + atomEventTransfer(map(LV2_ATOM__eventTransfer)), + atomFloat(map(LV2_ATOM__Float)), + atomLong(map(LV2_ATOM__Long)), + atomObject(map(LV2_ATOM__Object)), + atomPath(map(LV2_ATOM__Path)), + atomString(map(LV2_ATOM__String)), + atomURID(map(LV2_ATOM__URID)), + midiEvent(map(LV2_MIDI__MidiEvent)), + paramSampleRate(map(LV2_PARAMETERS__sampleRate)), + patchProperty(map(LV2_PATCH__property)), + patchSet(map(LV2_PATCH__Set)), + patchValue(map(LV2_PATCH__value)) {} + + inline LV2_URID map(const char* const uri) const + { + return _uridMap->map(_uridMap->handle, uri); + } + } fURIDs; + + // index of bypass parameter, if present + const uint32_t fBypassParameterIndex; + + // using ui:showInterface if true + const bool fWinIdWasNull; + + // Plugin UI (after LV2 stuff so the UI can call into us during its constructor) + UIExporter fUI; + + // ---------------------------------------------------------------------------------------------------------------- + // DPF callbacks + + void editParameterValue(const uint32_t rindex, const bool started) + { + if (fUiTouch != nullptr && fUiTouch->touch != nullptr) + fUiTouch->touch(fUiTouch->handle, rindex, started); + } + + static void editParameterCallback(void* const ptr, const uint32_t rindex, const bool started) + { + static_cast<UiLv2*>(ptr)->editParameterValue(rindex, started); + } + + void setParameterValue(const uint32_t rindex, float value) + { + DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); + + if (rindex == fBypassParameterIndex) + value = 1.0f - value; + + fWriteFunction(fController, rindex, sizeof(float), 0, &value); + } + + static void setParameterCallback(void* const ptr, const uint32_t rindex, const float value) + { + static_cast<UiLv2*>(ptr)->setParameterValue(rindex, value); + } + + #if DISTRHO_PLUGIN_WANT_STATE + void setState(const char* const key, const char* const value) + { + DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); + + const uint32_t eventInPortIndex = DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS; + + // join key and value + String tmpStr; + tmpStr += key; + tmpStr += "\xff"; + tmpStr += value; + + tmpStr[std::strlen(key)] = '\0'; + + // set msg size (key + separator + value + null terminator) + const uint32_t msgSize = static_cast<uint32_t>(tmpStr.length()) + 1U; + + // reserve atom space + const uint32_t atomSize = sizeof(LV2_Atom) + msgSize; + char* const atomBuf = (char*)malloc(atomSize); + DISTRHO_SAFE_ASSERT_RETURN(atomBuf != nullptr,); + + std::memset(atomBuf, 0, atomSize); + + // set atom info + LV2_Atom* const atom = (LV2_Atom*)atomBuf; + atom->size = msgSize; + atom->type = fURIDs.dpfKeyValue; + + // set atom data + std::memcpy(atomBuf + sizeof(LV2_Atom), tmpStr.buffer(), msgSize); + + // send to DSP side + fWriteFunction(fController, eventInPortIndex, atomSize, fURIDs.atomEventTransfer, atom); + + // free atom space + free(atomBuf); + } + + static void setStateCallback(void* const ptr, const char* const key, const char* const value) + { + static_cast<UiLv2*>(ptr)->setState(key, value); + } + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) + { + DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); + + if (channel > 0xF) + return; + + const uint32_t eventInPortIndex = DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS; + + LV2_Atom_MidiEvent atomMidiEvent; + atomMidiEvent.atom.size = 3; + atomMidiEvent.atom.type = fURIDs.midiEvent; + + atomMidiEvent.data[0] = channel + (velocity != 0 ? 0x90 : 0x80); + atomMidiEvent.data[1] = note; + atomMidiEvent.data[2] = velocity; + + // send to DSP side + fWriteFunction(fController, eventInPortIndex, lv2_atom_total_size(&atomMidiEvent.atom), + fURIDs.atomEventTransfer, &atomMidiEvent); + } + + static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity) + { + static_cast<UiLv2*>(ptr)->sendNote(channel, note, velocity); + } + #endif + + bool fileRequest(const char* const key) + { + d_stdout("UI file request %s %p", key, fUiRequestValue); + + if (fUiRequestValue == nullptr) + return false; + + String dpf_lv2_key(DISTRHO_PLUGIN_URI "#"); + dpf_lv2_key += key; + + const int r = fUiRequestValue->request(fUiRequestValue->handle, + fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()), + fURIDs.atomPath, + nullptr); + + d_stdout("UI file request %s %p => %s %i", key, fUiRequestValue, dpf_lv2_key.buffer(), r); + return r == LV2UI_REQUEST_VALUE_SUCCESS; + } + + static bool fileRequestCallback(void* ptr, const char* key) + { + return static_cast<UiLv2*>(ptr)->fileRequest(key); + } +}; + +// ----------------------------------------------------------------------- + +static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, + const char* const uri, + const char* const bundlePath, + const LV2UI_Write_Function writeFunction, + const LV2UI_Controller controller, + LV2UI_Widget* const widget, + const LV2_Feature* const* const features) +{ + if (uri == nullptr || std::strcmp(uri, DISTRHO_PLUGIN_URI) != 0) + { + d_stderr("Invalid plugin URI"); + return nullptr; + } + + const LV2_Options_Option* options = nullptr; + const LV2_URID_Map* uridMap = nullptr; + void* parentId = nullptr; + void* instance = nullptr; + +#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS + struct LV2_DirectAccess_Interface { + void* (*get_instance_pointer)(LV2_Handle handle); + }; + const LV2_Extension_Data_Feature* extData = nullptr; +#endif + + 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_UI__parent) == 0) + parentId = features[i]->data; +#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS + else if (std::strcmp(features[i]->URI, LV2_DATA_ACCESS_URI) == 0) + extData = (const LV2_Extension_Data_Feature*)features[i]->data; + else if (std::strcmp(features[i]->URI, LV2_INSTANCE_ACCESS_URI) == 0) + instance = features[i]->data; +#endif + } + + if (options == nullptr && parentId == nullptr) + { + d_stderr("Options feature missing (needed for show-interface), cannot continue!"); + return nullptr; + } + + if (uridMap == nullptr) + { + d_stderr("URID Map feature missing, cannot continue!"); + return nullptr; + } + + if (parentId == nullptr) + { + d_stdout("Parent Window Id missing, host should be using ui:showInterface..."); + } + +#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS + if (extData == nullptr || instance == nullptr) + { + d_stderr("Data or instance access missing, cannot continue!"); + return nullptr; + } + + if (const LV2_DirectAccess_Interface* const directAccess = (const LV2_DirectAccess_Interface*)extData->data_access(DISTRHO_PLUGIN_LV2_STATE_PREFIX "direct-access")) + instance = directAccess->get_instance_pointer(instance); + else + instance = nullptr; + + if (instance == nullptr) + { + d_stderr("Failed to get direct access, cannot continue!"); + return nullptr; + } +#endif + + const intptr_t winId = (intptr_t)parentId; + float sampleRate = 0.0f; + float scaleFactor = 0.0f; + uint32_t bgColor = 0; + uint32_t fgColor = 0xffffffff; + const char* appClassName = nullptr; + + if (options != nullptr) + { + const LV2_URID uridAtomInt = uridMap->map(uridMap->handle, LV2_ATOM__Int); + const LV2_URID uridAtomFloat = uridMap->map(uridMap->handle, LV2_ATOM__Float); + const LV2_URID uridAtomString = uridMap->map(uridMap->handle, LV2_ATOM__String); + const LV2_URID uridSampleRate = uridMap->map(uridMap->handle, LV2_PARAMETERS__sampleRate); + const LV2_URID uridBgColor = uridMap->map(uridMap->handle, LV2_UI__backgroundColor); + const LV2_URID uridFgColor = uridMap->map(uridMap->handle, LV2_UI__foregroundColor); + #ifndef DISTRHO_OS_MAC + const LV2_URID uridScaleFactor = uridMap->map(uridMap->handle, LV2_UI__scaleFactor); + #endif + const LV2_URID uridClassName = uridMap->map(uridMap->handle, "urn:distrho:className"); + + for (int i=0; options[i].key != 0; ++i) + { + /**/ if (options[i].key == uridSampleRate) + { + if (options[i].type == uridAtomFloat) + sampleRate = *(const float*)options[i].value; + else + d_stderr("Host provides UI sample-rate but has wrong value type"); + } + else if (options[i].key == uridBgColor) + { + if (options[i].type == uridAtomInt) + bgColor = (uint32_t)*(const int32_t*)options[i].value; + else + d_stderr("Host provides UI background color but has wrong value type"); + } + else if (options[i].key == uridFgColor) + { + if (options[i].type == uridAtomInt) + fgColor = (uint32_t)*(const int32_t*)options[i].value; + else + d_stderr("Host provides UI foreground color but has wrong value type"); + } + #ifndef DISTRHO_OS_MAC + else if (options[i].key == uridScaleFactor) + { + if (options[i].type == uridAtomFloat) + scaleFactor = *(const float*)options[i].value; + else + d_stderr("Host provides UI scale factor but has wrong value type"); + } + #endif + else if (options[i].key == uridClassName) + { + if (options[i].type == uridAtomString) + appClassName = (const char*)options[i].value; + else + d_stderr("Host provides UI scale factor but has wrong value type"); + } + } + } + + if (sampleRate < 1.0) + { + d_stdout("WARNING: this host does not send sample-rate information for LV2 UIs, using 44100 as fallback (this could be wrong)"); + sampleRate = 44100.0; + } + + return new UiLv2(bundlePath, winId, options, uridMap, features, + controller, writeFunction, widget, instance, + sampleRate, scaleFactor, bgColor, fgColor, appClassName); +} + +#define uiPtr ((UiLv2*)ui) + +static void lv2ui_cleanup(LV2UI_Handle ui) +{ + delete uiPtr; +} + +static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) +{ + uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer); +} + +// ----------------------------------------------------------------------- + +static int lv2ui_idle(LV2UI_Handle ui) +{ + return uiPtr->lv2ui_idle(); +} + +static int lv2ui_show(LV2UI_Handle ui) +{ + return uiPtr->lv2ui_show(); +} + +static int lv2ui_hide(LV2UI_Handle ui) +{ + return uiPtr->lv2ui_hide(); +} + +// ----------------------------------------------------------------------- + +static uint32_t lv2_get_options(LV2UI_Handle ui, LV2_Options_Option* options) +{ + return uiPtr->lv2_get_options(options); +} + +static uint32_t lv2_set_options(LV2UI_Handle ui, const LV2_Options_Option* options) +{ + return uiPtr->lv2_set_options(options); +} + +// ----------------------------------------------------------------------- + +#if DISTRHO_PLUGIN_WANT_PROGRAMS +static void lv2ui_select_program(LV2UI_Handle ui, uint32_t bank, uint32_t program) +{ + uiPtr->lv2ui_select_program(bank, program); +} +#endif + +// ----------------------------------------------------------------------- + +static const void* lv2ui_extension_data(const char* uri) +{ + static const LV2_Options_Interface options = { lv2_get_options, lv2_set_options }; + static const LV2UI_Idle_Interface uiIdle = { lv2ui_idle }; + static const LV2UI_Show_Interface uiShow = { lv2ui_show, lv2ui_hide }; + + if (std::strcmp(uri, LV2_OPTIONS__interface) == 0) + return &options; + if (std::strcmp(uri, LV2_UI__idleInterface) == 0) + return &uiIdle; + if (std::strcmp(uri, LV2_UI__showInterface) == 0) + return &uiShow; + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + static const LV2_Programs_UI_Interface uiPrograms = { lv2ui_select_program }; + + if (std::strcmp(uri, LV2_PROGRAMS__UIInterface) == 0) + return &uiPrograms; +#endif + + return nullptr; +} + +#undef instancePtr + +// ----------------------------------------------------------------------- + +static const LV2UI_Descriptor sLv2UiDescriptor = { + DISTRHO_UI_URI, + lv2ui_instantiate, + lv2ui_cleanup, + lv2ui_port_event, + lv2ui_extension_data +}; + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +DISTRHO_PLUGIN_EXPORT +const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) +{ + USE_NAMESPACE_DISTRHO + return (index == 0) ? &sLv2UiDescriptor : nullptr; +} + +#if defined(__MOD_DEVICES__) && defined(__EMSCRIPTEN__) +#include <emscripten/html5.h> +#include <string> + +typedef void (*_custom_param_set)(uint32_t port_index, float value); +typedef void (*_custom_patch_set)(const char* uri, const char* value); + +struct ModguiHandle { + LV2UI_Handle handle; + long loop_id; + _custom_param_set param_set; + _custom_patch_set patch_set; +}; + +enum URIs { + kUriNull, + kUriAtomEventTransfer, + kUriDpfKeyValue, +}; + +static std::vector<std::string> kURIs; + +static LV2_URID lv2_urid_map(LV2_URID_Map_Handle, const char* const uri) +{ + for (size_t i=0, size=kURIs.size(); i<size; ++i) + { + if (kURIs[i] == uri) + return i; + } + + kURIs.push_back(uri); + return kURIs.size() - 1u; +} + +static const char* lv2_urid_unmap(LV2_URID_Map_Handle, const LV2_URID urid) +{ + return kURIs[urid].c_str(); +} + +static void lv2ui_write_function(LV2UI_Controller controller, + uint32_t port_index, + uint32_t buffer_size, + uint32_t port_protocol, + const void* buffer) +{ + DISTRHO_SAFE_ASSERT_RETURN(buffer_size >= 1,); + + // d_stdout("lv2ui_write_function %p %u %u %u %p", controller, port_index, buffer_size, port_protocol, buffer); + ModguiHandle* const mhandle = static_cast<ModguiHandle*>(controller); + + switch (port_protocol) + { + case kUriNull: + mhandle->param_set(port_index, *static_cast<const float*>(buffer)); + break; + case kUriAtomEventTransfer: + if (const LV2_Atom* const atom = static_cast<const LV2_Atom*>(buffer)) + { + // d_stdout("lv2ui_write_function %u %u:%s", atom->size, atom->type, kURIs[atom->type].c_str()); + + // if (kURIs[atom->type] == "urn:distrho:KeyValueState") + { + const char* const key = (const char*)(atom + 1); + const char* const value = key + (std::strlen(key) + 1U); + // d_stdout("lv2ui_write_function %s %s", key, value); + + String urikey; + urikey = DISTRHO_PLUGIN_URI "#"; + urikey += key; + + mhandle->patch_set(urikey, value); + } + } + break; + } +} + +static void app_idle(void* const handle) +{ + static_cast<UiLv2*>(handle)->lv2ui_idle(); +} + +DISTRHO_PLUGIN_EXPORT +LV2UI_Handle modgui_init(const char* const className, _custom_param_set param_set, _custom_patch_set patch_set) +{ + d_stdout("init \"%s\"", className); + DISTRHO_SAFE_ASSERT_RETURN(className != nullptr, nullptr); + + static LV2_URID_Map uridMap = { nullptr, lv2_urid_map }; + static LV2_URID_Unmap uridUnmap = { nullptr, lv2_urid_unmap }; + + // known first URIDs, matching URIs + if (kURIs.empty()) + { + kURIs.push_back(""); + kURIs.push_back("http://lv2plug.in/ns/ext/atom#eventTransfer"); + kURIs.push_back(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState"); + } + + static float sampleRateValue = 48000.f; + static LV2_Options_Option options[3] = { + { + LV2_OPTIONS_INSTANCE, + 0, + uridMap.map(uridMap.handle, LV2_PARAMETERS__sampleRate), + sizeof(float), + uridMap.map(uridMap.handle, LV2_ATOM__Float), + &sampleRateValue + }, + { + LV2_OPTIONS_INSTANCE, + 0, + uridMap.map(uridMap.handle, "urn:distrho:className"), + std::strlen(className) + 1, + uridMap.map(uridMap.handle, LV2_ATOM__String), + className + }, + {} + }; + + static const LV2_Feature optionsFt = { LV2_OPTIONS__options, static_cast<void*>(options) }; + static const LV2_Feature uridMapFt = { LV2_URID__map, static_cast<void*>(&uridMap) }; + static const LV2_Feature uridUnmapFt = { LV2_URID__unmap, static_cast<void*>(&uridUnmap) }; + + static const LV2_Feature* features[] = { + &optionsFt, + &uridMapFt, + &uridUnmapFt, + nullptr + }; + + ModguiHandle* const mhandle = new ModguiHandle; + mhandle->handle = nullptr; + mhandle->loop_id = 0; + mhandle->param_set = param_set; + mhandle->patch_set = patch_set; + + LV2UI_Widget widget; + const LV2UI_Handle handle = lv2ui_instantiate(&sLv2UiDescriptor, + DISTRHO_PLUGIN_URI, + "", // bundlePath + lv2ui_write_function, + mhandle, + &widget, + features); + mhandle->handle = handle; + + static_cast<UiLv2*>(handle)->lv2ui_show(); + mhandle->loop_id = emscripten_set_interval(app_idle, 1000.0/60, handle); + + return mhandle; +} + +DISTRHO_PLUGIN_EXPORT +void modgui_param_set(const LV2UI_Handle handle, const uint32_t index, const float value) +{ + lv2ui_port_event(static_cast<ModguiHandle*>(handle)->handle, index, sizeof(float), kUriNull, &value); +} + +DISTRHO_PLUGIN_EXPORT +void modgui_patch_set(const LV2UI_Handle handle, const char* const uri, const char* const value) +{ + static const constexpr uint32_t URI_PREFIX_LEN = sizeof(DISTRHO_PLUGIN_URI); + DISTRHO_SAFE_ASSERT_RETURN(std::strncmp(uri, DISTRHO_PLUGIN_URI "#", URI_PREFIX_LEN) == 0,); + + const uint32_t keySize = std::strlen(uri + URI_PREFIX_LEN) + 1; + const uint32_t valueSize = std::strlen(value) + 1; + const uint32_t atomSize = sizeof(LV2_Atom) + keySize + valueSize; + + LV2_Atom* const atom = static_cast<LV2_Atom*>(std::malloc(atomSize)); + atom->size = atomSize; + atom->type = kUriDpfKeyValue; + + std::memcpy(static_cast<uint8_t*>(static_cast<void*>(atom + 1)), uri + URI_PREFIX_LEN, keySize); + std::memcpy(static_cast<uint8_t*>(static_cast<void*>(atom + 1)) + keySize, value, valueSize); + + lv2ui_port_event(static_cast<ModguiHandle*>(handle)->handle, + DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS, // events input port + atomSize, kUriAtomEventTransfer, atom); + + std::free(atom); +} + +DISTRHO_PLUGIN_EXPORT +void modgui_cleanup(const LV2UI_Handle handle) +{ + d_stdout("cleanup"); + ModguiHandle* const mhandle = static_cast<ModguiHandle*>(handle); + if (mhandle->loop_id != 0) + emscripten_clear_interval(mhandle->loop_id); + lv2ui_cleanup(mhandle->handle); + delete mhandle; +} +#endif + +// -----------------------------------------------------------------------