DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

DistrhoUILV2.cpp (32138B)


      1 /*
      2  * DISTRHO Plugin Framework (DPF)
      3  * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
      4  *
      5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
      6  * or without fee is hereby granted, provided that the above copyright notice and this
      7  * permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
     10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
     11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
     12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
     13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
     14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include "DistrhoUIInternal.hpp"
     18 
     19 #include "../extra/String.hpp"
     20 
     21 #include "lv2/atom.h"
     22 #include "lv2/atom-util.h"
     23 #include "lv2/data-access.h"
     24 #include "lv2/instance-access.h"
     25 #include "lv2/midi.h"
     26 #include "lv2/options.h"
     27 #include "lv2/parameters.h"
     28 #include "lv2/patch.h"
     29 #include "lv2/ui.h"
     30 #include "lv2/urid.h"
     31 #include "lv2/lv2_kxstudio_properties.h"
     32 #include "lv2/lv2_programs.h"
     33 
     34 #ifndef DISTRHO_PLUGIN_LV2_STATE_PREFIX
     35 # define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:"
     36 #endif
     37 
     38 START_NAMESPACE_DISTRHO
     39 
     40 typedef struct _LV2_Atom_MidiEvent {
     41     LV2_Atom atom;    /**< Atom header. */
     42     uint8_t  data[3]; /**< MIDI data (body). */
     43 } LV2_Atom_MidiEvent;
     44 
     45 #if ! DISTRHO_PLUGIN_WANT_STATE
     46 static constexpr const setStateFunc setStateCallback = nullptr;
     47 #endif
     48 #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
     49 static constexpr const sendNoteFunc sendNoteCallback = nullptr;
     50 #endif
     51 
     52 // unwanted in LV2, resize extension is deprecated and hosts can do it without extensions
     53 static constexpr const setSizeFunc setSizeCallback = nullptr;
     54 
     55 // -----------------------------------------------------------------------
     56 
     57 template <class LV2F>
     58 static const LV2F* getLv2Feature(const LV2_Feature* const* features, const char* const uri)
     59 {
     60     for (int i=0; features[i] != nullptr; ++i)
     61     {
     62         if (std::strcmp(features[i]->URI, uri) == 0)
     63             return (const LV2F*)features[i]->data;
     64     }
     65 
     66     return nullptr;
     67 }
     68 
     69 class UiLv2
     70 {
     71 public:
     72     UiLv2(const char* const bundlePath,
     73           const intptr_t winId,
     74           const LV2_Options_Option* options,
     75           const LV2_URID_Map* const uridMap,
     76           const LV2_Feature* const* const features,
     77           const LV2UI_Controller controller,
     78           const LV2UI_Write_Function writeFunc,
     79           LV2UI_Widget* const widget,
     80           void* const dspPtr,
     81           const float sampleRate,
     82           const float scaleFactor,
     83           const uint32_t bgColor,
     84           const uint32_t fgColor,
     85           const char* const appClassName)
     86         : fUridMap(uridMap),
     87           fUridUnmap(getLv2Feature<LV2_URID_Unmap>(features, LV2_URID__unmap)),
     88           fUiPortMap(getLv2Feature<LV2UI_Port_Map>(features, LV2_UI__portMap)),
     89           fUiRequestValue(getLv2Feature<LV2UI_Request_Value>(features, LV2_UI__requestValue)),
     90           fUiTouch(getLv2Feature<LV2UI_Touch>(features, LV2_UI__touch)),
     91           fController(controller),
     92           fWriteFunction(writeFunc),
     93           fURIDs(uridMap),
     94           fBypassParameterIndex(fUiPortMap != nullptr ? fUiPortMap->port_index(fUiPortMap->handle, ParameterDesignationSymbols::bypass_lv2)
     95                                                       : LV2UI_INVALID_PORT_INDEX),
     96           fWinIdWasNull(winId == 0),
     97           fUI(this, winId, sampleRate,
     98               editParameterCallback,
     99               setParameterCallback,
    100               setStateCallback,
    101               sendNoteCallback,
    102               setSizeCallback,
    103               fileRequestCallback,
    104               bundlePath, dspPtr, scaleFactor, bgColor, fgColor, appClassName)
    105     {
    106         if (widget != nullptr)
    107             *widget = (LV2UI_Widget)fUI.getNativeWindowHandle();
    108 
    109        #if DISTRHO_PLUGIN_WANT_STATE
    110         // tell the DSP we're ready to receive msgs
    111         setState("__dpf_ui_data__", "");
    112        #endif
    113 
    114         if (winId != 0)
    115             return;
    116 
    117         // if winId == 0 then options must not be null
    118         DISTRHO_SAFE_ASSERT_RETURN(options != nullptr,);
    119 
    120        #ifndef __EMSCRIPTEN__
    121         const LV2_URID uridWindowTitle    = uridMap->map(uridMap->handle, LV2_UI__windowTitle);
    122         const LV2_URID uridTransientWinId = uridMap->map(uridMap->handle, LV2_KXSTUDIO_PROPERTIES__TransientWindowId);
    123 
    124         const char* windowTitle = nullptr;
    125 
    126         for (int i=0; options[i].key != 0; ++i)
    127         {
    128             if (options[i].key == uridTransientWinId)
    129             {
    130                 if (options[i].type == fURIDs.atomLong)
    131                 {
    132                     if (const int64_t transientWinId = *(const int64_t*)options[i].value)
    133                         fUI.setWindowTransientWinId(static_cast<intptr_t>(transientWinId));
    134                 }
    135                 else
    136                     d_stderr("Host provides transientWinId but has wrong value type");
    137             }
    138             else if (options[i].key == uridWindowTitle)
    139             {
    140                 if (options[i].type == fURIDs.atomString)
    141                 {
    142                     windowTitle = (const char*)options[i].value;
    143                 }
    144                 else
    145                     d_stderr("Host provides windowTitle but has wrong value type");
    146             }
    147         }
    148 
    149         if (windowTitle == nullptr)
    150             windowTitle = DISTRHO_PLUGIN_NAME;
    151 
    152         fUI.setWindowTitle(windowTitle);
    153        #endif
    154     }
    155 
    156     // -------------------------------------------------------------------
    157 
    158     void lv2ui_port_event(const uint32_t rindex, const uint32_t bufferSize, const uint32_t format, const void* const buffer)
    159     {
    160         if (format == 0)
    161         {
    162             const uint32_t parameterOffset = fUI.getParameterOffset();
    163 
    164             if (rindex < parameterOffset)
    165                 return;
    166 
    167             DISTRHO_SAFE_ASSERT_RETURN(bufferSize == sizeof(float),)
    168 
    169             float value = *(const float*)buffer;
    170 
    171             if (rindex == fBypassParameterIndex)
    172                 value = 1.0f - value;
    173 
    174             fUI.parameterChanged(rindex-parameterOffset, value);
    175         }
    176        #if DISTRHO_PLUGIN_WANT_STATE
    177         else if (format == fURIDs.atomEventTransfer)
    178         {
    179             const LV2_Atom* const atom = (const LV2_Atom*)buffer;
    180 
    181             if (atom->type == fURIDs.dpfKeyValue)
    182             {
    183                 const char* const key   = (const char*)LV2_ATOM_BODY_CONST(atom);
    184                 const char* const value = key+(std::strlen(key)+1);
    185 
    186                 fUI.stateChanged(key, value);
    187             }
    188             else if (atom->type == fURIDs.atomObject && fUridUnmap != nullptr)
    189             {
    190                 const LV2_Atom_Object* const obj = (const LV2_Atom_Object*)atom;
    191 
    192                 const LV2_Atom* property = nullptr;
    193                 const LV2_Atom* atomvalue = nullptr;
    194                 lv2_atom_object_get(obj, fURIDs.patchProperty, &property, fURIDs.patchValue, &atomvalue, 0);
    195 
    196                 DISTRHO_SAFE_ASSERT_RETURN(property != nullptr,);
    197                 DISTRHO_SAFE_ASSERT_RETURN(atomvalue != nullptr,);
    198 
    199                 DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID,);
    200                 DISTRHO_SAFE_ASSERT_RETURN(atomvalue->type == fURIDs.atomPath || atomvalue->type == fURIDs.atomString,);
    201 
    202                 if (property != nullptr && property->type == fURIDs.atomURID &&
    203                     atomvalue != nullptr && (atomvalue->type == fURIDs.atomPath || atomvalue->type == fURIDs.atomString))
    204                 {
    205                     const LV2_URID dpf_lv2_urid = ((const LV2_Atom_URID*)property)->body;
    206                     DISTRHO_SAFE_ASSERT_RETURN(dpf_lv2_urid != 0,);
    207 
    208                     const char* const dpf_lv2_key = fUridUnmap->unmap(fUridUnmap->handle, dpf_lv2_urid);
    209                     DISTRHO_SAFE_ASSERT_RETURN(dpf_lv2_key != nullptr,);
    210 
    211                     /*constexpr*/ const size_t reqLen = std::strlen(DISTRHO_PLUGIN_URI "#");
    212                     DISTRHO_SAFE_ASSERT_RETURN(std::strlen(dpf_lv2_key) > reqLen,);
    213 
    214                     const char* const key   = dpf_lv2_key + reqLen;
    215                     const char* const value = (const char*)LV2_ATOM_BODY_CONST(atomvalue);
    216 
    217                     fUI.stateChanged(key, value);
    218                 }
    219             }
    220             else if (atom->type == fURIDs.midiEvent)
    221             {
    222                 // ignore
    223             }
    224             else
    225             {
    226                 d_stdout("DPF :: received atom not handled :: %s",
    227                          fUridUnmap != nullptr ? fUridUnmap->unmap(fUridUnmap->handle, atom->type) : "(null)");
    228             }
    229         }
    230        #endif
    231     }
    232 
    233     // -------------------------------------------------------------------
    234 
    235     int lv2ui_idle()
    236     {
    237         if (fWinIdWasNull)
    238             return (fUI.plugin_idle() && fUI.isVisible()) ? 0 : 1;
    239 
    240         return fUI.plugin_idle() ? 0 : 1;
    241     }
    242 
    243     int lv2ui_show()
    244     {
    245         return fUI.setWindowVisible(true) ? 0 : 1;
    246     }
    247 
    248     int lv2ui_hide()
    249     {
    250         return fUI.setWindowVisible(false) ? 0 : 1;
    251     }
    252 
    253     // -------------------------------------------------------------------
    254 
    255     uint32_t lv2_get_options(LV2_Options_Option* const /*options*/)
    256     {
    257         // currently unused
    258         return LV2_OPTIONS_ERR_UNKNOWN;
    259     }
    260 
    261     uint32_t lv2_set_options(const LV2_Options_Option* const options)
    262     {
    263         for (int i=0; options[i].key != 0; ++i)
    264         {
    265             if (options[i].key == fURIDs.paramSampleRate)
    266             {
    267                 if (options[i].type == fURIDs.atomFloat)
    268                 {
    269                     const float sampleRate = *(const float*)options[i].value;
    270                     fUI.setSampleRate(sampleRate, true);
    271                     continue;
    272                 }
    273                 else
    274                 {
    275                     d_stderr("Host changed UI sample-rate but with wrong value type");
    276                     continue;
    277                 }
    278             }
    279         }
    280 
    281         return LV2_OPTIONS_SUCCESS;
    282     }
    283 
    284     // -------------------------------------------------------------------
    285 
    286    #if DISTRHO_PLUGIN_WANT_PROGRAMS
    287     void lv2ui_select_program(const uint32_t bank, const uint32_t program)
    288     {
    289         const uint32_t realProgram = bank * 128 + program;
    290 
    291         fUI.programLoaded(realProgram);
    292     }
    293    #endif
    294 
    295     // -------------------------------------------------------------------
    296 
    297 private:
    298     // LV2 features
    299     const LV2_URID_Map*        const fUridMap;
    300     const LV2_URID_Unmap*      const fUridUnmap;
    301     const LV2UI_Port_Map*      const fUiPortMap;
    302     const LV2UI_Request_Value* const fUiRequestValue;
    303     const LV2UI_Touch*         const fUiTouch;
    304 
    305     // LV2 UI stuff
    306     const LV2UI_Controller     fController;
    307     const LV2UI_Write_Function fWriteFunction;
    308 
    309     // LV2 URIDs
    310     const struct URIDs {
    311         const LV2_URID_Map* _uridMap;
    312         const LV2_URID dpfKeyValue;
    313         const LV2_URID atomEventTransfer;
    314         const LV2_URID atomFloat;
    315         const LV2_URID atomLong;
    316         const LV2_URID atomObject;
    317         const LV2_URID atomPath;
    318         const LV2_URID atomString;
    319         const LV2_URID atomURID;
    320         const LV2_URID midiEvent;
    321         const LV2_URID paramSampleRate;
    322         const LV2_URID patchProperty;
    323         const LV2_URID patchSet;
    324         const LV2_URID patchValue;
    325 
    326         URIDs(const LV2_URID_Map* const uridMap)
    327             : _uridMap(uridMap),
    328               dpfKeyValue(map(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")),
    329               atomEventTransfer(map(LV2_ATOM__eventTransfer)),
    330               atomFloat(map(LV2_ATOM__Float)),
    331               atomLong(map(LV2_ATOM__Long)),
    332               atomObject(map(LV2_ATOM__Object)),
    333               atomPath(map(LV2_ATOM__Path)),
    334               atomString(map(LV2_ATOM__String)),
    335               atomURID(map(LV2_ATOM__URID)),
    336               midiEvent(map(LV2_MIDI__MidiEvent)),
    337               paramSampleRate(map(LV2_PARAMETERS__sampleRate)),
    338               patchProperty(map(LV2_PATCH__property)),
    339               patchSet(map(LV2_PATCH__Set)),
    340               patchValue(map(LV2_PATCH__value)) {}
    341 
    342         inline LV2_URID map(const char* const uri) const
    343         {
    344             return _uridMap->map(_uridMap->handle, uri);
    345         }
    346     } fURIDs;
    347 
    348     // index of bypass parameter, if present
    349     const uint32_t fBypassParameterIndex;
    350 
    351     // using ui:showInterface if true
    352     const bool fWinIdWasNull;
    353 
    354     // Plugin UI (after LV2 stuff so the UI can call into us during its constructor)
    355     UIExporter fUI;
    356 
    357     // ----------------------------------------------------------------------------------------------------------------
    358     // DPF callbacks
    359 
    360     void editParameterValue(const uint32_t rindex, const bool started)
    361     {
    362         if (fUiTouch != nullptr && fUiTouch->touch != nullptr)
    363             fUiTouch->touch(fUiTouch->handle, rindex, started);
    364     }
    365 
    366     static void editParameterCallback(void* const ptr, const uint32_t rindex, const bool started)
    367     {
    368         static_cast<UiLv2*>(ptr)->editParameterValue(rindex, started);
    369     }
    370 
    371     void setParameterValue(const uint32_t rindex, float value)
    372     {
    373         DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
    374 
    375         if (rindex == fBypassParameterIndex)
    376             value = 1.0f - value;
    377 
    378         fWriteFunction(fController, rindex, sizeof(float), 0, &value);
    379     }
    380 
    381     static void setParameterCallback(void* const ptr, const uint32_t rindex, const float value)
    382     {
    383         static_cast<UiLv2*>(ptr)->setParameterValue(rindex, value);
    384     }
    385 
    386    #if DISTRHO_PLUGIN_WANT_STATE
    387     void setState(const char* const key, const char* const value)
    388     {
    389         DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
    390 
    391         const uint32_t eventInPortIndex = DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS;
    392 
    393         // join key and value
    394         String tmpStr;
    395         tmpStr += key;
    396         tmpStr += "\xff";
    397         tmpStr += value;
    398 
    399         tmpStr[std::strlen(key)] = '\0';
    400 
    401         // set msg size (key + separator + value + null terminator)
    402         const uint32_t msgSize = static_cast<uint32_t>(tmpStr.length()) + 1U;
    403 
    404         // reserve atom space
    405         const uint32_t atomSize = sizeof(LV2_Atom) + msgSize;
    406         char* const  atomBuf = (char*)malloc(atomSize);
    407         DISTRHO_SAFE_ASSERT_RETURN(atomBuf != nullptr,);
    408 
    409         std::memset(atomBuf, 0, atomSize);
    410 
    411         // set atom info
    412         LV2_Atom* const atom = (LV2_Atom*)atomBuf;
    413         atom->size = msgSize;
    414         atom->type = fURIDs.dpfKeyValue;
    415 
    416         // set atom data
    417         std::memcpy(atomBuf + sizeof(LV2_Atom), tmpStr.buffer(), msgSize);
    418 
    419         // send to DSP side
    420         fWriteFunction(fController, eventInPortIndex, atomSize, fURIDs.atomEventTransfer, atom);
    421 
    422         // free atom space
    423         free(atomBuf);
    424     }
    425 
    426     static void setStateCallback(void* const ptr, const char* const key, const char* const value)
    427     {
    428         static_cast<UiLv2*>(ptr)->setState(key, value);
    429     }
    430    #endif
    431 
    432    #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    433     void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
    434     {
    435         DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
    436 
    437         if (channel > 0xF)
    438             return;
    439 
    440         const uint32_t eventInPortIndex = DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS;
    441 
    442         LV2_Atom_MidiEvent atomMidiEvent;
    443         atomMidiEvent.atom.size = 3;
    444         atomMidiEvent.atom.type = fURIDs.midiEvent;
    445 
    446         atomMidiEvent.data[0] = channel + (velocity != 0 ? 0x90 : 0x80);
    447         atomMidiEvent.data[1] = note;
    448         atomMidiEvent.data[2] = velocity;
    449 
    450         // send to DSP side
    451         fWriteFunction(fController, eventInPortIndex, lv2_atom_total_size(&atomMidiEvent.atom),
    452                        fURIDs.atomEventTransfer, &atomMidiEvent);
    453     }
    454 
    455     static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity)
    456     {
    457         static_cast<UiLv2*>(ptr)->sendNote(channel, note, velocity);
    458     }
    459    #endif
    460 
    461     bool fileRequest(const char* const key)
    462     {
    463         d_stdout("UI file request %s %p", key, fUiRequestValue);
    464 
    465         if (fUiRequestValue == nullptr)
    466             return false;
    467 
    468         String dpf_lv2_key(DISTRHO_PLUGIN_URI "#");
    469         dpf_lv2_key += key;
    470 
    471         const int r = fUiRequestValue->request(fUiRequestValue->handle,
    472                                         fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()),
    473                                         fURIDs.atomPath,
    474                                         nullptr);
    475 
    476         d_stdout("UI file request %s %p => %s %i", key, fUiRequestValue, dpf_lv2_key.buffer(), r);
    477         return r == LV2UI_REQUEST_VALUE_SUCCESS;
    478     }
    479 
    480     static bool fileRequestCallback(void* ptr, const char* key)
    481     {
    482         return static_cast<UiLv2*>(ptr)->fileRequest(key);
    483     }
    484 };
    485 
    486 // -----------------------------------------------------------------------
    487 
    488 static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*,
    489                                       const char* const uri,
    490                                       const char* const bundlePath,
    491                                       const LV2UI_Write_Function writeFunction,
    492                                       const LV2UI_Controller controller,
    493                                       LV2UI_Widget* const widget,
    494                                       const LV2_Feature* const* const features)
    495 {
    496     if (uri == nullptr || std::strcmp(uri, DISTRHO_PLUGIN_URI) != 0)
    497     {
    498         d_stderr("Invalid plugin URI");
    499         return nullptr;
    500     }
    501 
    502     const LV2_Options_Option* options   = nullptr;
    503     const LV2_URID_Map*       uridMap   = nullptr;
    504     void*                     parentId  = nullptr;
    505     void*                     instance  = nullptr;
    506 
    507 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
    508     struct LV2_DirectAccess_Interface {
    509         void* (*get_instance_pointer)(LV2_Handle handle);
    510     };
    511     const LV2_Extension_Data_Feature* extData = nullptr;
    512 #endif
    513 
    514     for (int i=0; features[i] != nullptr; ++i)
    515     {
    516         /**/ if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) == 0)
    517             options = (const LV2_Options_Option*)features[i]->data;
    518         else if (std::strcmp(features[i]->URI, LV2_URID__map) == 0)
    519             uridMap = (const LV2_URID_Map*)features[i]->data;
    520         else if (std::strcmp(features[i]->URI, LV2_UI__parent) == 0)
    521             parentId = features[i]->data;
    522 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
    523         else if (std::strcmp(features[i]->URI, LV2_DATA_ACCESS_URI) == 0)
    524             extData = (const LV2_Extension_Data_Feature*)features[i]->data;
    525         else if (std::strcmp(features[i]->URI, LV2_INSTANCE_ACCESS_URI) == 0)
    526             instance = features[i]->data;
    527 #endif
    528     }
    529 
    530     if (options == nullptr && parentId == nullptr)
    531     {
    532         d_stderr("Options feature missing (needed for show-interface), cannot continue!");
    533         return nullptr;
    534     }
    535 
    536     if (uridMap == nullptr)
    537     {
    538         d_stderr("URID Map feature missing, cannot continue!");
    539         return nullptr;
    540     }
    541 
    542     if (parentId == nullptr)
    543     {
    544         d_stdout("Parent Window Id missing, host should be using ui:showInterface...");
    545     }
    546 
    547 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
    548     if (extData == nullptr || instance == nullptr)
    549     {
    550         d_stderr("Data or instance access missing, cannot continue!");
    551         return nullptr;
    552     }
    553 
    554     if (const LV2_DirectAccess_Interface* const directAccess = (const LV2_DirectAccess_Interface*)extData->data_access(DISTRHO_PLUGIN_LV2_STATE_PREFIX "direct-access"))
    555         instance = directAccess->get_instance_pointer(instance);
    556     else
    557         instance = nullptr;
    558 
    559     if (instance == nullptr)
    560     {
    561         d_stderr("Failed to get direct access, cannot continue!");
    562         return nullptr;
    563     }
    564 #endif
    565 
    566     const intptr_t winId = (intptr_t)parentId;
    567     float sampleRate = 0.0f;
    568     float scaleFactor = 0.0f;
    569     uint32_t bgColor = 0;
    570     uint32_t fgColor = 0xffffffff;
    571     const char* appClassName = nullptr;
    572 
    573     if (options != nullptr)
    574     {
    575         const LV2_URID uridAtomInt     = uridMap->map(uridMap->handle, LV2_ATOM__Int);
    576         const LV2_URID uridAtomFloat   = uridMap->map(uridMap->handle, LV2_ATOM__Float);
    577         const LV2_URID uridAtomString  = uridMap->map(uridMap->handle, LV2_ATOM__String);
    578         const LV2_URID uridSampleRate  = uridMap->map(uridMap->handle, LV2_PARAMETERS__sampleRate);
    579         const LV2_URID uridBgColor     = uridMap->map(uridMap->handle, LV2_UI__backgroundColor);
    580         const LV2_URID uridFgColor     = uridMap->map(uridMap->handle, LV2_UI__foregroundColor);
    581        #ifndef DISTRHO_OS_MAC
    582         const LV2_URID uridScaleFactor = uridMap->map(uridMap->handle, LV2_UI__scaleFactor);
    583        #endif
    584         const LV2_URID uridClassName   = uridMap->map(uridMap->handle, "urn:distrho:className");
    585 
    586         for (int i=0; options[i].key != 0; ++i)
    587         {
    588             /**/ if (options[i].key == uridSampleRate)
    589             {
    590                 if (options[i].type == uridAtomFloat)
    591                     sampleRate = *(const float*)options[i].value;
    592                 else
    593                     d_stderr("Host provides UI sample-rate but has wrong value type");
    594             }
    595             else if (options[i].key == uridBgColor)
    596             {
    597                 if (options[i].type == uridAtomInt)
    598                     bgColor = (uint32_t)*(const int32_t*)options[i].value;
    599                 else
    600                     d_stderr("Host provides UI background color but has wrong value type");
    601             }
    602             else if (options[i].key == uridFgColor)
    603             {
    604                 if (options[i].type == uridAtomInt)
    605                     fgColor = (uint32_t)*(const int32_t*)options[i].value;
    606                 else
    607                     d_stderr("Host provides UI foreground color but has wrong value type");
    608             }
    609            #ifndef DISTRHO_OS_MAC
    610             else if (options[i].key == uridScaleFactor)
    611             {
    612                 if (options[i].type == uridAtomFloat)
    613                     scaleFactor = *(const float*)options[i].value;
    614                 else
    615                     d_stderr("Host provides UI scale factor but has wrong value type");
    616             }
    617            #endif
    618             else if (options[i].key == uridClassName)
    619             {
    620                 if (options[i].type == uridAtomString)
    621                     appClassName = (const char*)options[i].value;
    622                 else
    623                     d_stderr("Host provides UI scale factor but has wrong value type");
    624             }
    625         }
    626     }
    627 
    628     if (sampleRate < 1.0)
    629     {
    630         d_stdout("WARNING: this host does not send sample-rate information for LV2 UIs, using 44100 as fallback (this could be wrong)");
    631         sampleRate = 44100.0;
    632     }
    633 
    634     return new UiLv2(bundlePath, winId, options, uridMap, features,
    635                      controller, writeFunction, widget, instance,
    636                      sampleRate, scaleFactor, bgColor, fgColor, appClassName);
    637 }
    638 
    639 #define uiPtr ((UiLv2*)ui)
    640 
    641 static void lv2ui_cleanup(LV2UI_Handle ui)
    642 {
    643     delete uiPtr;
    644 }
    645 
    646 static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
    647 {
    648     uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer);
    649 }
    650 
    651 // -----------------------------------------------------------------------
    652 
    653 static int lv2ui_idle(LV2UI_Handle ui)
    654 {
    655     return uiPtr->lv2ui_idle();
    656 }
    657 
    658 static int lv2ui_show(LV2UI_Handle ui)
    659 {
    660     return uiPtr->lv2ui_show();
    661 }
    662 
    663 static int lv2ui_hide(LV2UI_Handle ui)
    664 {
    665     return uiPtr->lv2ui_hide();
    666 }
    667 
    668 // -----------------------------------------------------------------------
    669 
    670 static uint32_t lv2_get_options(LV2UI_Handle ui, LV2_Options_Option* options)
    671 {
    672     return uiPtr->lv2_get_options(options);
    673 }
    674 
    675 static uint32_t lv2_set_options(LV2UI_Handle ui, const LV2_Options_Option* options)
    676 {
    677     return uiPtr->lv2_set_options(options);
    678 }
    679 
    680 // -----------------------------------------------------------------------
    681 
    682 #if DISTRHO_PLUGIN_WANT_PROGRAMS
    683 static void lv2ui_select_program(LV2UI_Handle ui, uint32_t bank, uint32_t program)
    684 {
    685     uiPtr->lv2ui_select_program(bank, program);
    686 }
    687 #endif
    688 
    689 // -----------------------------------------------------------------------
    690 
    691 static const void* lv2ui_extension_data(const char* uri)
    692 {
    693     static const LV2_Options_Interface options = { lv2_get_options, lv2_set_options };
    694     static const LV2UI_Idle_Interface  uiIdle  = { lv2ui_idle };
    695     static const LV2UI_Show_Interface  uiShow  = { lv2ui_show, lv2ui_hide };
    696 
    697     if (std::strcmp(uri, LV2_OPTIONS__interface) == 0)
    698         return &options;
    699     if (std::strcmp(uri, LV2_UI__idleInterface) == 0)
    700         return &uiIdle;
    701     if (std::strcmp(uri, LV2_UI__showInterface) == 0)
    702         return &uiShow;
    703 
    704 #if DISTRHO_PLUGIN_WANT_PROGRAMS
    705     static const LV2_Programs_UI_Interface uiPrograms = { lv2ui_select_program };
    706 
    707     if (std::strcmp(uri, LV2_PROGRAMS__UIInterface) == 0)
    708         return &uiPrograms;
    709 #endif
    710 
    711     return nullptr;
    712 }
    713 
    714 #undef instancePtr
    715 
    716 // -----------------------------------------------------------------------
    717 
    718 static const LV2UI_Descriptor sLv2UiDescriptor = {
    719     DISTRHO_UI_URI,
    720     lv2ui_instantiate,
    721     lv2ui_cleanup,
    722     lv2ui_port_event,
    723     lv2ui_extension_data
    724 };
    725 
    726 // -----------------------------------------------------------------------
    727 
    728 END_NAMESPACE_DISTRHO
    729 
    730 DISTRHO_PLUGIN_EXPORT
    731 const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index)
    732 {
    733     USE_NAMESPACE_DISTRHO
    734     return (index == 0) ? &sLv2UiDescriptor : nullptr;
    735 }
    736 
    737 #if defined(__MOD_DEVICES__) && defined(__EMSCRIPTEN__)
    738 #include <emscripten/html5.h>
    739 #include <string>
    740 
    741 typedef void (*_custom_param_set)(uint32_t port_index, float value);
    742 typedef void (*_custom_patch_set)(const char* uri, const char* value);
    743 
    744 struct ModguiHandle {
    745     LV2UI_Handle handle;
    746     long loop_id;
    747     _custom_param_set param_set;
    748     _custom_patch_set patch_set;
    749 };
    750 
    751 enum URIs {
    752     kUriNull,
    753     kUriAtomEventTransfer,
    754     kUriDpfKeyValue,
    755 };
    756 
    757 static std::vector<std::string> kURIs;
    758 
    759 static LV2_URID lv2_urid_map(LV2_URID_Map_Handle, const char* const uri)
    760 {
    761     for (size_t i=0, size=kURIs.size(); i<size; ++i)
    762     {
    763         if (kURIs[i] == uri)
    764             return i;
    765     }
    766 
    767     kURIs.push_back(uri);
    768     return kURIs.size() - 1u;
    769 }
    770 
    771 static const char* lv2_urid_unmap(LV2_URID_Map_Handle, const LV2_URID urid)
    772 {
    773     return kURIs[urid].c_str();
    774 }
    775 
    776 static void lv2ui_write_function(LV2UI_Controller controller,
    777                                  uint32_t         port_index,
    778                                  uint32_t         buffer_size,
    779                                  uint32_t         port_protocol,
    780                                  const void*      buffer)
    781 {
    782     DISTRHO_SAFE_ASSERT_RETURN(buffer_size >= 1,);
    783 
    784     // d_stdout("lv2ui_write_function %p %u %u %u %p", controller, port_index, buffer_size, port_protocol, buffer);
    785     ModguiHandle* const mhandle = static_cast<ModguiHandle*>(controller);
    786 
    787     switch (port_protocol)
    788     {
    789     case kUriNull:
    790         mhandle->param_set(port_index, *static_cast<const float*>(buffer));
    791         break;
    792     case kUriAtomEventTransfer:
    793         if (const LV2_Atom* const atom = static_cast<const LV2_Atom*>(buffer))
    794         {
    795             // d_stdout("lv2ui_write_function %u %u:%s", atom->size, atom->type, kURIs[atom->type].c_str());
    796 
    797             // if (kURIs[atom->type] == "urn:distrho:KeyValueState")
    798             {
    799                 const char* const key   = (const char*)(atom + 1);
    800                 const char* const value = key + (std::strlen(key) + 1U);
    801                 // d_stdout("lv2ui_write_function %s %s", key, value);
    802 
    803                 String urikey;
    804                 urikey  = DISTRHO_PLUGIN_URI "#";
    805                 urikey += key;
    806 
    807                 mhandle->patch_set(urikey, value);
    808             }
    809         }
    810         break;
    811     }
    812 }
    813 
    814 static void app_idle(void* const handle)
    815 {
    816     static_cast<UiLv2*>(handle)->lv2ui_idle();
    817 }
    818 
    819 DISTRHO_PLUGIN_EXPORT
    820 LV2UI_Handle modgui_init(const char* const className, _custom_param_set param_set, _custom_patch_set patch_set)
    821 {
    822     d_stdout("init \"%s\"", className);
    823     DISTRHO_SAFE_ASSERT_RETURN(className != nullptr, nullptr);
    824 
    825     static LV2_URID_Map uridMap = { nullptr, lv2_urid_map };
    826     static LV2_URID_Unmap uridUnmap = { nullptr, lv2_urid_unmap };
    827 
    828     // known first URIDs, matching URIs
    829     if (kURIs.empty())
    830     {
    831         kURIs.push_back("");
    832         kURIs.push_back("http://lv2plug.in/ns/ext/atom#eventTransfer");
    833         kURIs.push_back(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState");
    834     }
    835 
    836     static float sampleRateValue = 48000.f;
    837     static LV2_Options_Option options[3] = {
    838         {
    839             LV2_OPTIONS_INSTANCE,
    840             0,
    841             uridMap.map(uridMap.handle, LV2_PARAMETERS__sampleRate),
    842             sizeof(float),
    843             uridMap.map(uridMap.handle, LV2_ATOM__Float),
    844             &sampleRateValue
    845         },
    846         {
    847             LV2_OPTIONS_INSTANCE,
    848             0,
    849             uridMap.map(uridMap.handle, "urn:distrho:className"),
    850             std::strlen(className) + 1,
    851             uridMap.map(uridMap.handle, LV2_ATOM__String),
    852             className
    853         },
    854         {}
    855     };
    856 
    857     static const LV2_Feature optionsFt = { LV2_OPTIONS__options, static_cast<void*>(options) };
    858     static const LV2_Feature uridMapFt = { LV2_URID__map, static_cast<void*>(&uridMap) };
    859     static const LV2_Feature uridUnmapFt = { LV2_URID__unmap, static_cast<void*>(&uridUnmap) };
    860 
    861     static const LV2_Feature* features[] = {
    862         &optionsFt,
    863         &uridMapFt,
    864         &uridUnmapFt,
    865         nullptr
    866     };
    867 
    868     ModguiHandle* const mhandle = new ModguiHandle;
    869     mhandle->handle = nullptr;
    870     mhandle->loop_id = 0;
    871     mhandle->param_set = param_set;
    872     mhandle->patch_set = patch_set;
    873 
    874     LV2UI_Widget widget;
    875     const LV2UI_Handle handle = lv2ui_instantiate(&sLv2UiDescriptor,
    876                                                   DISTRHO_PLUGIN_URI,
    877                                                   "", // bundlePath
    878                                                   lv2ui_write_function,
    879                                                   mhandle,
    880                                                   &widget,
    881                                                   features);
    882     mhandle->handle = handle;
    883 
    884     static_cast<UiLv2*>(handle)->lv2ui_show();
    885     mhandle->loop_id = emscripten_set_interval(app_idle, 1000.0/60, handle);
    886 
    887     return mhandle;
    888 }
    889 
    890 DISTRHO_PLUGIN_EXPORT
    891 void modgui_param_set(const LV2UI_Handle handle, const uint32_t index, const float value)
    892 {
    893     lv2ui_port_event(static_cast<ModguiHandle*>(handle)->handle, index, sizeof(float), kUriNull, &value);
    894 }
    895 
    896 DISTRHO_PLUGIN_EXPORT
    897 void modgui_patch_set(const LV2UI_Handle handle, const char* const uri, const char* const value)
    898 {
    899     static const constexpr uint32_t URI_PREFIX_LEN = sizeof(DISTRHO_PLUGIN_URI);
    900     DISTRHO_SAFE_ASSERT_RETURN(std::strncmp(uri, DISTRHO_PLUGIN_URI "#", URI_PREFIX_LEN) == 0,);
    901 
    902     const uint32_t keySize = std::strlen(uri + URI_PREFIX_LEN) + 1;
    903     const uint32_t valueSize = std::strlen(value) + 1;
    904     const uint32_t atomSize = sizeof(LV2_Atom) + keySize + valueSize;
    905 
    906     LV2_Atom* const atom = static_cast<LV2_Atom*>(std::malloc(atomSize));
    907     atom->size = atomSize;
    908     atom->type = kUriDpfKeyValue;
    909 
    910     std::memcpy(static_cast<uint8_t*>(static_cast<void*>(atom + 1)), uri + URI_PREFIX_LEN, keySize);
    911     std::memcpy(static_cast<uint8_t*>(static_cast<void*>(atom + 1)) + keySize, value, valueSize);
    912 
    913     lv2ui_port_event(static_cast<ModguiHandle*>(handle)->handle,
    914                      DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS, // events input port
    915                      atomSize, kUriAtomEventTransfer, atom);
    916 
    917     std::free(atom);
    918 }
    919 
    920 DISTRHO_PLUGIN_EXPORT
    921 void modgui_cleanup(const LV2UI_Handle handle)
    922 {
    923     d_stdout("cleanup");
    924     ModguiHandle* const mhandle = static_cast<ModguiHandle*>(handle);
    925     if (mhandle->loop_id != 0)
    926         emscripten_clear_interval(mhandle->loop_id);
    927     lv2ui_cleanup(mhandle->handle);
    928     delete mhandle;
    929 }
    930 #endif
    931 
    932 // -----------------------------------------------------------------------