|  | /*
 * Carla Bridge UI, LV2 version
 * Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com>
 *
 * 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 GPL.txt file
 */
#ifdef BRIDGE_LV2
#include "CarlaBridgeClient.hpp"
#include "CarlaLv2Utils.hpp"
#include "CarlaMIDI.h"
extern "C" {
#include "rtmempool/rtmempool-lv2.h"
}
#include <vector>
#include <QtCore/QDir>
Lv2WorldClass gLv2World;
CARLA_BRIDGE_START_NAMESPACE
// -------------------------------------------------------------------------
// fake values
uint32_t bufferSize = 512;
double   sampleRate = 44100.0;
// static max values
const unsigned int MAX_EVENT_BUFFER = 8192; // 0x2000
// feature ids
const uint32_t lv2_feature_id_bufsize_bounded  =  0;
const uint32_t lv2_feature_id_bufsize_fixed    =  1;
const uint32_t lv2_feature_id_bufsize_powerof2 =  2;
const uint32_t lv2_feature_id_event            =  3;
const uint32_t lv2_feature_id_logs             =  4;
const uint32_t lv2_feature_id_options          =  5;
const uint32_t lv2_feature_id_programs         =  6;
const uint32_t lv2_feature_id_rtmempool        =  7;
const uint32_t lv2_feature_id_state_make_path  =  8;
const uint32_t lv2_feature_id_state_map_path   =  9;
const uint32_t lv2_feature_id_strict_bounds    = 10;
const uint32_t lv2_feature_id_uri_map          = 11;
const uint32_t lv2_feature_id_urid_map         = 12;
const uint32_t lv2_feature_id_urid_unmap       = 13;
const uint32_t lv2_feature_id_ui_parent        = 14;
const uint32_t lv2_feature_id_ui_port_map      = 15;
const uint32_t lv2_feature_id_ui_resize        = 16;
const uint32_t lv2_feature_count               = 17;
// pre-set uri[d] map ids
const uint32_t CARLA_URI_MAP_ID_NULL                = 0;
const uint32_t CARLA_URI_MAP_ID_ATOM_CHUNK          = 1;
const uint32_t CARLA_URI_MAP_ID_ATOM_DOUBLE         = 2;
const uint32_t CARLA_URI_MAP_ID_ATOM_INT            = 3;
const uint32_t CARLA_URI_MAP_ID_ATOM_PATH           = 4;
const uint32_t CARLA_URI_MAP_ID_ATOM_SEQUENCE       = 5;
const uint32_t CARLA_URI_MAP_ID_ATOM_STRING         = 6;
const uint32_t CARLA_URI_MAP_ID_ATOM_WORKER         = 7;
const uint32_t CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM  = 8;
const uint32_t CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT = 9;
const uint32_t CARLA_URI_MAP_ID_BUF_MAX_LENGTH      = 10;
const uint32_t CARLA_URI_MAP_ID_BUF_MIN_LENGTH      = 11;
const uint32_t CARLA_URI_MAP_ID_BUF_SEQUENCE_SIZE   = 12;
const uint32_t CARLA_URI_MAP_ID_LOG_ERROR           = 13;
const uint32_t CARLA_URI_MAP_ID_LOG_NOTE            = 14;
const uint32_t CARLA_URI_MAP_ID_LOG_TRACE           = 15;
const uint32_t CARLA_URI_MAP_ID_LOG_WARNING         = 16;
const uint32_t CARLA_URI_MAP_ID_MIDI_EVENT          = 17;
const uint32_t CARLA_URI_MAP_ID_PARAM_SAMPLE_RATE   = 18;
const uint32_t CARLA_URI_MAP_ID_COUNT               = 19;
// -------------------------------------------------------------------------
struct Lv2PluginOptions {
    uint32_t eventSize;
    uint32_t bufferSize;
    double   sampleRate;
    LV2_Options_Option oNull;
    LV2_Options_Option oMaxBlockLenth;
    LV2_Options_Option oMinBlockLenth;
    LV2_Options_Option oSequenceSize;
    LV2_Options_Option oSampleRate;
    Lv2PluginOptions()
        : eventSize(MAX_EVENT_BUFFER),
          bufferSize(0),
          sampleRate(0.0) {}
};
Lv2PluginOptions lv2Options;
class CarlaLv2Client : public CarlaBridgeClient
{
public:
    CarlaLv2Client(const char* const uiTitle)
        : CarlaBridgeClient(uiTitle)
    {
        handle = nullptr;
        widget = nullptr;
        descriptor = nullptr;
        rdf_descriptor = nullptr;
        rdf_ui_descriptor = nullptr;
        programs = nullptr;
#ifdef BRIDGE_LV2_X11
        m_resizable = false;
#else
        m_resizable = true;
#endif
        for (uint32_t i=0; i < CARLA_URI_MAP_ID_COUNT; i++)
            customURIDs.push_back(nullptr);
        for (uint32_t i=0; i < lv2_feature_count+1; i++)
            features[i] = nullptr;
        // -----------------------------------------------------------------
        // initialize options
        lv2Options.bufferSize = bufferSize;
        lv2Options.sampleRate = sampleRate;
        lv2Options.oNull.key   = CARLA_URI_MAP_ID_NULL;
        lv2Options.oNull.size  = 0;
        lv2Options.oNull.type  = CARLA_URI_MAP_ID_NULL;
        lv2Options.oNull.value = nullptr;
        lv2Options.oMaxBlockLenth.key   = CARLA_URI_MAP_ID_BUF_MAX_LENGTH;
        lv2Options.oMaxBlockLenth.size  = sizeof(uint32_t);
        lv2Options.oMaxBlockLenth.type  = CARLA_URI_MAP_ID_ATOM_INT;
        lv2Options.oMaxBlockLenth.value = &lv2Options.bufferSize;
        lv2Options.oMinBlockLenth.key   = CARLA_URI_MAP_ID_BUF_MIN_LENGTH;
        lv2Options.oMinBlockLenth.size  = sizeof(uint32_t);
        lv2Options.oMinBlockLenth.type  = CARLA_URI_MAP_ID_ATOM_INT;
        lv2Options.oMinBlockLenth.value = &lv2Options.bufferSize;
        lv2Options.oSequenceSize.key   = CARLA_URI_MAP_ID_BUF_SEQUENCE_SIZE;
        lv2Options.oSequenceSize.size  = sizeof(uint32_t);
        lv2Options.oSequenceSize.type  = CARLA_URI_MAP_ID_ATOM_INT;
        lv2Options.oSequenceSize.value = &lv2Options.eventSize;
        lv2Options.oSampleRate.key     = CARLA_URI_MAP_ID_PARAM_SAMPLE_RATE;
        lv2Options.oSampleRate.size    = sizeof(double);
        lv2Options.oSampleRate.type    = CARLA_URI_MAP_ID_ATOM_DOUBLE;
        lv2Options.oSampleRate.value   = &lv2Options.sampleRate;
        // -----------------------------------------------------------------
        // initialize features
        LV2_Event_Feature* const eventFt = new LV2_Event_Feature;
        eventFt->callback_data           = this;
        eventFt->lv2_event_ref           = carla_lv2_event_ref;
        eventFt->lv2_event_unref         = carla_lv2_event_unref;
        LV2_Log_Log* const logFt         = new LV2_Log_Log;
        logFt->handle                    = this;
        logFt->printf                    = carla_lv2_log_printf;
        logFt->vprintf                   = carla_lv2_log_vprintf;
        LV2_Programs_Host* const programsFt        = new LV2_Programs_Host;
        programsFt->handle                         = this;
        programsFt->program_changed                = carla_lv2_program_changed;
        LV2_RtMemPool_Pool* const rtMemPoolFt      = new LV2_RtMemPool_Pool;
        lv2_rtmempool_init(rtMemPoolFt);
        LV2_State_Make_Path* const stateMakePathFt = new LV2_State_Make_Path;
        stateMakePathFt->handle                    = this;
        stateMakePathFt->path                      = carla_lv2_state_make_path;
        LV2_State_Map_Path* const stateMapPathFt   = new LV2_State_Map_Path;
        stateMapPathFt->handle                     = this;
        stateMapPathFt->abstract_path              = carla_lv2_state_map_abstract_path;
        stateMapPathFt->absolute_path              = carla_lv2_state_map_absolute_path;
        LV2_URI_Map_Feature* const uriMapFt = new LV2_URI_Map_Feature;
        uriMapFt->callback_data             = this;
        uriMapFt->uri_to_id                 = carla_lv2_uri_to_id;
        LV2_URID_Map* const uridMapFt       = new LV2_URID_Map;
        uridMapFt->handle                   = this;
        uridMapFt->map                      = carla_lv2_urid_map;
        LV2_URID_Unmap* const uridUnmapFt   = new LV2_URID_Unmap;
        uridUnmapFt->handle                 = this;
        uridUnmapFt->unmap                  = carla_lv2_urid_unmap;
        LV2UI_Port_Map* const uiPortMapFt   = new LV2UI_Port_Map;
        uiPortMapFt->handle                 = this;
        uiPortMapFt->port_index             = carla_lv2_ui_port_map;
        LV2UI_Resize* const uiResizeFt      = new LV2UI_Resize;
        uiResizeFt->handle                  = this;
        uiResizeFt->ui_resize               = carla_lv2_ui_resize;
        LV2_Options_Option* const optionsFt = new LV2_Options_Option [5];
        optionsFt[0] = lv2Options.oMaxBlockLenth;
        optionsFt[1] = lv2Options.oMinBlockLenth;
        optionsFt[2] = lv2Options.oSequenceSize;
        optionsFt[3] = lv2Options.oSampleRate;
        optionsFt[4] = lv2Options.oNull;
        features[lv2_feature_id_bufsize_bounded]        = new LV2_Feature;
        features[lv2_feature_id_bufsize_bounded]->URI   = LV2_BUF_SIZE__boundedBlockLength;
        features[lv2_feature_id_bufsize_bounded]->data  = nullptr;
        features[lv2_feature_id_bufsize_fixed]          = new LV2_Feature;
        features[lv2_feature_id_bufsize_fixed]->URI     = LV2_BUF_SIZE__fixedBlockLength;
        features[lv2_feature_id_bufsize_fixed]->data    = nullptr;
        features[lv2_feature_id_bufsize_powerof2]       = new LV2_Feature;
        features[lv2_feature_id_bufsize_powerof2]->URI  = LV2_BUF_SIZE__powerOf2BlockLength;
        features[lv2_feature_id_bufsize_powerof2]->data = nullptr;
        features[lv2_feature_id_event]          = new LV2_Feature;
        features[lv2_feature_id_event]->URI     = LV2_EVENT_URI;
        features[lv2_feature_id_event]->data    = eventFt;
        features[lv2_feature_id_logs]           = new LV2_Feature;
        features[lv2_feature_id_logs]->URI      = LV2_LOG__log;
        features[lv2_feature_id_logs]->data     = logFt;
        features[lv2_feature_id_options]        = new LV2_Feature;
        features[lv2_feature_id_options]->URI   = LV2_OPTIONS__options;
        features[lv2_feature_id_options]->data  = optionsFt;
        features[lv2_feature_id_programs]       = new LV2_Feature;
        features[lv2_feature_id_programs]->URI  = LV2_PROGRAMS__Host;
        features[lv2_feature_id_programs]->data = programsFt;
        features[lv2_feature_id_rtmempool]       = new LV2_Feature;
        features[lv2_feature_id_rtmempool]->URI  = LV2_RTSAFE_MEMORY_POOL__Pool;
        features[lv2_feature_id_rtmempool]->data = rtMemPoolFt;
        features[lv2_feature_id_state_make_path]       = new LV2_Feature;
        features[lv2_feature_id_state_make_path]->URI  = LV2_STATE__makePath;
        features[lv2_feature_id_state_make_path]->data = stateMakePathFt;
        features[lv2_feature_id_state_map_path]        = new LV2_Feature;
        features[lv2_feature_id_state_map_path]->URI   = LV2_STATE__mapPath;
        features[lv2_feature_id_state_map_path]->data  = stateMapPathFt;
        features[lv2_feature_id_strict_bounds]         = new LV2_Feature;
        features[lv2_feature_id_strict_bounds]->URI    = LV2_PORT_PROPS__supportsStrictBounds;
        features[lv2_feature_id_strict_bounds]->data   = nullptr;
        features[lv2_feature_id_uri_map]          = new LV2_Feature;
        features[lv2_feature_id_uri_map]->URI     = LV2_URI_MAP_URI;
        features[lv2_feature_id_uri_map]->data    = uriMapFt;
        features[lv2_feature_id_urid_map]         = new LV2_Feature;
        features[lv2_feature_id_urid_map]->URI    = LV2_URID__map;
        features[lv2_feature_id_urid_map]->data   = uridMapFt;
        features[lv2_feature_id_urid_unmap]       = new LV2_Feature;
        features[lv2_feature_id_urid_unmap]->URI  = LV2_URID__unmap;
        features[lv2_feature_id_urid_unmap]->data = uridUnmapFt;
        features[lv2_feature_id_ui_parent]         = new LV2_Feature;
        features[lv2_feature_id_ui_parent]->URI    = LV2_UI__parent;
#ifdef BRIDGE_LV2_X11
        features[lv2_feature_id_ui_parent]->data   = getContainerId();
#else
        features[lv2_feature_id_ui_parent]->data   = nullptr;
#endif
        features[lv2_feature_id_ui_port_map]           = new LV2_Feature;
        features[lv2_feature_id_ui_port_map]->URI      = LV2_UI__portMap;
        features[lv2_feature_id_ui_port_map]->data     = uiPortMapFt;
        features[lv2_feature_id_ui_resize]             = new LV2_Feature;
        features[lv2_feature_id_ui_resize]->URI        = LV2_UI__resize;
        features[lv2_feature_id_ui_resize]->data       = uiResizeFt;
    }
    ~CarlaLv2Client()
    {
        if (rdf_descriptor)
            delete rdf_descriptor;
        const LV2_Options_Option* const options = (const LV2_Options_Option*)features[lv2_feature_id_options]->data;
        delete[] options;
        delete (LV2_Event_Feature*)features[lv2_feature_id_event]->data;
        delete (LV2_Log_Log*)features[lv2_feature_id_logs]->data;
        delete (LV2_Programs_Host*)features[lv2_feature_id_programs]->data;
        delete (LV2_RtMemPool_Pool*)features[lv2_feature_id_rtmempool]->data;
        delete (LV2_State_Make_Path*)features[lv2_feature_id_state_make_path]->data;
        delete (LV2_State_Map_Path*)features[lv2_feature_id_state_map_path]->data;
        delete (LV2_URI_Map_Feature*)features[lv2_feature_id_uri_map]->data;
        delete (LV2_URID_Map*)features[lv2_feature_id_urid_map]->data;
        delete (LV2_URID_Unmap*)features[lv2_feature_id_urid_unmap]->data;
        delete (LV2UI_Port_Map*)features[lv2_feature_id_ui_port_map]->data;
        delete (LV2UI_Resize*)features[lv2_feature_id_ui_resize]->data;
        for (uint32_t i=0; i < lv2_feature_count; i++)
        {
            if (features[i])
                delete features[i];
        }
        for (size_t i=0; i < customURIDs.size(); i++)
        {
            if (customURIDs[i])
                free((void*)customURIDs[i]);
        }
        customURIDs.clear();
    }
    // ---------------------------------------------------------------------
    // ui initialization
    bool uiInit(const char* pluginURI, const char* uiURI)
    {
        // -----------------------------------------------------------------
        // init
        CarlaBridgeClient::uiInit(pluginURI, uiURI);
        // -----------------------------------------------------------------
        // get plugin from lv2_rdf (lilv)
        gLv2World.init();
        rdf_descriptor = lv2_rdf_new(pluginURI);
        if (! rdf_descriptor)
            return false;
        // -----------------------------------------------------------------
        // find requested UI
        for (uint32_t i=0; i < rdf_descriptor->UICount; i++)
        {
            if (std::strcmp(rdf_descriptor->UIs[i].URI, uiURI) == 0)
            {
                rdf_ui_descriptor = &rdf_descriptor->UIs[i];
                break;
            }
        }
        if (! rdf_ui_descriptor)
            return false;
        // -----------------------------------------------------------------
        // open DLL
        if (! uiLibOpen(rdf_ui_descriptor->Binary))
            return false;
        // -----------------------------------------------------------------
        // get DLL main entry
        const LV2UI_DescriptorFunction ui_descFn = (LV2UI_DescriptorFunction)uiLibSymbol("lv2ui_descriptor");
        if (! ui_descFn)
            return false;
        // -----------------------------------------------------------
        // get descriptor that matches URI
        uint32_t i = 0;
        while ((descriptor = ui_descFn(i++)))
        {
            if (std::strcmp(descriptor->URI, uiURI) == 0)
                break;
        }
        if (! descriptor)
            return false;
        // -----------------------------------------------------------
        // initialize UI
        handle = descriptor->instantiate(descriptor, pluginURI, rdf_ui_descriptor->Bundle, carla_lv2_ui_write_function, this, &widget, features);
        if (! handle)
            return false;
        // -----------------------------------------------------------
        // check if not resizable
#ifndef BRIDGE_LV2_X11
        for (uint32_t i=0; i < rdf_ui_descriptor->FeatureCount; i++)
        {
            if (std::strcmp(rdf_ui_descriptor->Features[i].URI, LV2_UI__fixedSize) == 0 || std::strcmp(rdf_ui_descriptor->Features[i].URI, LV2_UI__noUserResize) == 0)
            {
                m_resizable = false;
                break;
            }
        }
#endif
        // -----------------------------------------------------------
        // check for known extensions
        for (uint32_t i=0; descriptor->extension_data && i < rdf_ui_descriptor->ExtensionCount; i++)
        {
            if (std::strcmp(rdf_ui_descriptor->Extensions[i], LV2_PROGRAMS__UIInterface) == 0)
            {
                programs = (LV2_Programs_UI_Interface*)descriptor->extension_data(LV2_PROGRAMS__UIInterface);
                if (programs && ! programs->select_program)
                    // invalid
                    programs = nullptr;
                break;
            }
        }
        return true;
    }
    void uiClose()
    {
        CarlaBridgeClient::uiClose();
        if (handle && descriptor && descriptor->cleanup)
            descriptor->cleanup(handle);
        uiLibClose();
    }
    // ---------------------------------------------------------------------
    // ui management
    void* getWidget() const
    {
        return widget;
    }
    bool isResizable() const
    {
        return m_resizable;
    }
    bool needsReparent() const
    {
#ifdef BRIDGE_LV2_X11
        return true;
#else
        return false;
#endif
    }
    // ---------------------------------------------------------------------
    // processing
    void setParameter(const int32_t rindex, const float value)
    {
        CARLA_ASSERT(handle && descriptor);
        if (handle && descriptor && descriptor->port_event)
        {
            float fvalue = value;
            descriptor->port_event(handle, rindex, sizeof(float), 0, &fvalue);
        }
    }
    void setProgram(const uint32_t)
    {
    }
    void setMidiProgram(const uint32_t bank, const uint32_t program)
    {
        CARLA_ASSERT(handle);
        if (handle && programs)
            programs->select_program(handle, bank, program);
    }
    void noteOn(const uint8_t channel, const uint8_t note, const uint8_t velo)
    {
        CARLA_ASSERT(handle && descriptor);
        if (handle && descriptor && descriptor->port_event)
        {
            LV2_Atom_MidiEvent midiEv;
            midiEv.event.time.frames = 0;
            midiEv.event.body.type   = CARLA_URI_MAP_ID_MIDI_EVENT;
            midiEv.event.body.size   = 3;
            midiEv.data[0] = MIDI_STATUS_NOTE_ON + channel;
            midiEv.data[1] = note;
            midiEv.data[2] = velo;
            descriptor->port_event(handle, 0, 3, CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM, &midiEv);
        }
    }
    void noteOff(const uint8_t channel, const uint8_t note)
    {
        CARLA_ASSERT(handle && descriptor);
        if (handle && descriptor && descriptor->port_event)
        {
            LV2_Atom_MidiEvent midiEv;
            midiEv.event.time.frames = 0;
            midiEv.event.body.type   = CARLA_URI_MAP_ID_MIDI_EVENT;
            midiEv.event.body.size   = 3;
            midiEv.data[0] = MIDI_STATUS_NOTE_OFF + channel;
            midiEv.data[1] = note;
            midiEv.data[2] = 0;
            descriptor->port_event(handle, 0, 3, CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM, &midiEv);
        }
    }
    // ---------------------------------------------------------------------
    uint32_t getCustomURID(const char* const uri)
    {
        qDebug("CarlaLv2Client::getCustomURID(%s)", uri);
        CARLA_ASSERT(uri);
        if (! uri)
            return CARLA_URI_MAP_ID_NULL;
        for (size_t i=0; i < customURIDs.size(); i++)
        {
            if (customURIDs[i] && std::strcmp(customURIDs[i], uri) == 0)
                return i;
        }
        customURIDs.push_back(strdup(uri));
        return customURIDs.size()-1;
    }
    const char* getCustomURIString(const LV2_URID urid) const
    {
        qDebug("CarlaLv2Client::getCustomURIString(%i)", urid);
        CARLA_ASSERT(urid > CARLA_URI_MAP_ID_NULL);
        if (urid == CARLA_URI_MAP_ID_NULL)
            return nullptr;
        if (urid < customURIDs.size())
            return customURIDs[urid];
        return nullptr;
    }
    // ---------------------------------------------------------------------
    void handleTransferAtom(const int32_t portIndex, const LV2_Atom* const atom)
    {
        qDebug("CarlaLv2Client::handleTransferEvent(%i, %p)", portIndex, atom);
        CARLA_ASSERT(portIndex >= 0);
        CARLA_ASSERT(atom);
        if (atom && handle && descriptor && descriptor->port_event)
            descriptor->port_event(handle, portIndex, atom->size, CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM, atom);
    }
    void handleTransferEvent(const int32_t portIndex, const LV2_Atom* const atom)
    {
        qDebug("CarlaLv2Client::handleTransferEvent(%i, %p)", portIndex, atom);
        CARLA_ASSERT(portIndex >= 0);
        CARLA_ASSERT(atom);
        if (atom && handle && descriptor && descriptor->port_event)
            descriptor->port_event(handle, portIndex, atom->size, CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT, atom);
#if 0
        if (handle && descriptor && descriptor->port_event)
        {
            LV2_URID_Map* const URID_Map = (LV2_URID_Map*)features[lv2_feature_id_urid_map]->data;
            const LV2_URID uridPatchSet  = getCustomURID(LV2_PATCH__Set);
            const LV2_URID uridPatchBody = getCustomURID(LV2_PATCH__body);
            Sratom*   sratom = sratom_new(URID_Map);
            SerdChunk chunk  = { nullptr, 0 };
            LV2_Atom_Forge forge;
            lv2_atom_forge_init(&forge, URID_Map);
            lv2_atom_forge_set_sink(&forge, sratom_forge_sink, sratom_forge_deref, &chunk);
            LV2_Atom_Forge_Frame refFrame, bodyFrame;
            LV2_Atom_Forge_Ref   ref = lv2_atom_forge_blank(&forge, &refFrame, 1, uridPatchSet);
            lv2_atom_forge_property_head(&forge, uridPatchBody, CARLA_URI_MAP_ID_NULL);
            lv2_atom_forge_blank(&forge, &bodyFrame, 2, CARLA_URI_MAP_ID_NULL);
            //lv2_atom_forge_property_head(&forge, getCustomURID(key), CARLA_URI_MAP_ID_NULL);
            if (std::strcmp(type, "string") == 0)
                lv2_atom_forge_string(&forge, value, std::strlen(value));
            else if (std::strcmp(type, "path") == 0)
                lv2_atom_forge_path(&forge, value, std::strlen(value));
            else if (std::strcmp(type, "chunk") == 0)
                lv2_atom_forge_literal(&forge, value, std::strlen(value), CARLA_URI_MAP_ID_ATOM_CHUNK, CARLA_URI_MAP_ID_NULL);
            //else
            //    lv2_atom_forge_literal(&forge, value, std::strlen(value), getCustomURID(key), CARLA_URI_MAP_ID_NULL);
            lv2_atom_forge_pop(&forge, &bodyFrame);
            lv2_atom_forge_pop(&forge, &refFrame);
            const LV2_Atom* const atom = lv2_atom_forge_deref(&forge, ref);
            descriptor->port_event(handle, 0, atom->size, CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT, atom);
            free((void*)chunk.buf);
            sratom_free(sratom);
        }
#endif
    }
    // ---------------------------------------------------------------------
    void handleProgramChanged(int32_t /*index*/)
    {
        if (isOscControlRegistered())
            sendOscConfigure("reloadprograms", "");
    }
    uint32_t handleUiPortMap(const char* const symbol)
    {
        CARLA_ASSERT(symbol);
        if (! symbol)
            return LV2UI_INVALID_PORT_INDEX;
        for (uint32_t i=0; i < rdf_descriptor->PortCount; i++)
        {
            if (std::strcmp(rdf_descriptor->Ports[i].Symbol, symbol) == 0)
                return i;
        }
        return LV2UI_INVALID_PORT_INDEX;
    }
    int handleUiResize(int width, int height)
    {
        CARLA_ASSERT(width > 0);
        CARLA_ASSERT(height > 0);
        if (width <= 0 || height <= 0)
            return 1;
        toolkitResize(width, height);
        return 0;
    }
    void handleUiWrite(uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
    {
        if (! (buffer && isOscControlRegistered()))
            return;
        if (format == 0)
        {
            CARLA_ASSERT(buffer);
            CARLA_ASSERT(bufferSize == sizeof(float));
            if (bufferSize != sizeof(float))
                return;
            float value = *(float*)buffer;
            sendOscControl(portIndex, value);
        }
        else if (format == CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM)
        {
            CARLA_ASSERT(buffer);
            const LV2_Atom* const atom = (const LV2_Atom*)buffer;
            QByteArray chunk((const char*)buffer, bufferSize);
            sendOscLv2TransferAtom(portIndex, getCustomURIString(atom->type), chunk.toBase64().constData());
        }
        else if (format == CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT)
        {
            CARLA_ASSERT(buffer);
            const LV2_Atom* const atom = (const LV2_Atom*)buffer;
            QByteArray chunk((const char*)buffer, bufferSize);
            sendOscLv2TransferEvent(portIndex, getCustomURIString(atom->type), chunk.toBase64().constData());
        }
    }
    // ----------------- Event Feature ---------------------------------------------------
    static uint32_t carla_lv2_event_ref(const LV2_Event_Callback_Data callback_data, LV2_Event* const event)
    {
        qDebug("CarlaLv2Client::carla_lv2_event_ref(%p, %p)", callback_data, event);
        CARLA_ASSERT(callback_data);
        CARLA_ASSERT(event);
        return 0;
    }
    static uint32_t carla_lv2_event_unref(const LV2_Event_Callback_Data callback_data, LV2_Event* const event)
    {
        qDebug("CarlaLv2Client::carla_lv2_event_unref(%p, %p)", callback_data, event);
        CARLA_ASSERT(callback_data);
        CARLA_ASSERT(event);
        return 0;
    }
    // ----------------- Logs Feature ----------------------------------------------------
    static int carla_lv2_log_printf(const LV2_Log_Handle handle, const LV2_URID type, const char* const fmt, ...)
    {
        qDebug("CarlaLv2Client::carla_lv2_log_printf(%p, %i, %s, ...)", handle, type, fmt);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(type > 0);
#ifndef DEBUG
        if (type == CARLA_URI_MAP_ID_LOG_TRACE)
            return 0;
#endif
        va_list args;
        va_start(args, fmt);
        const int ret = carla_lv2_log_vprintf(handle, type, fmt, args);
        va_end(args);
        return ret;
    }
    static int carla_lv2_log_vprintf(const LV2_Log_Handle handle, const LV2_URID type, const char* const fmt, va_list ap)
    {
        qDebug("CarlaLv2Client::carla_lv2_log_vprintf(%p, %i, %s, ...)", handle, type, fmt);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(type > 0);
#ifndef DEBUG
        if (type == CARLA_URI_MAP_ID_LOG_TRACE)
            return 0;
#endif
        char buf[8196];
        vsprintf(buf, fmt, ap);
        if (*buf == 0)
            return 0;
        switch (type)
        {
        case CARLA_URI_MAP_ID_LOG_ERROR:
            qCritical("%s", buf);
            break;
        case CARLA_URI_MAP_ID_LOG_NOTE:
            printf("%s\n", buf);
            break;
        case CARLA_URI_MAP_ID_LOG_TRACE:
            qDebug("%s", buf);
            break;
        case CARLA_URI_MAP_ID_LOG_WARNING:
            carla_stderr("%s", buf);
            break;
        default:
            break;
        }
        return std::strlen(buf);
    }
    // ----------------- Programs Feature ------------------------------------------------
    static void carla_lv2_program_changed(const LV2_Programs_Handle handle, const int32_t index)
    {
        qDebug("CarlaLv2Client::carla_lv2_program_changed(%p, %i)", handle, index);
        CARLA_ASSERT(handle);
        if (! handle)
            return;
        CarlaLv2Client* const client = (CarlaLv2Client*)handle;
        client->handleProgramChanged(index);
    }
    // ----------------- State Feature ---------------------------------------------------
    static char* carla_lv2_state_make_path(const LV2_State_Make_Path_Handle handle, const char* const path)
    {
        qDebug("CarlaLv2Client::carla_lv2_state_make_path(%p, %p)", handle, path);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(path);
        if (! path)
            return nullptr;
        QDir dir;
        dir.mkpath(path);
        return strdup(path);
    }
    static char* carla_lv2_state_map_abstract_path(const LV2_State_Map_Path_Handle handle, const char* const absolute_path)
    {
        qDebug("CarlaLv2Client::carla_lv2_state_map_abstract_path(%p, %p)", handle, absolute_path);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(absolute_path);
        if (! absolute_path)
            return nullptr;
        QDir dir(absolute_path);
        return strdup(dir.canonicalPath().toUtf8().constData());
    }
    static char* carla_lv2_state_map_absolute_path(const LV2_State_Map_Path_Handle handle, const char* const abstract_path)
    {
        qDebug("CarlaLv2Client::carla_lv2_state_map_absolute_path(%p, %p)", handle, abstract_path);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(abstract_path);
        if (! abstract_path)
            return nullptr;
        QDir dir(abstract_path);
        return strdup(dir.absolutePath().toUtf8().constData());
    }
    // ----------------- URI-Map Feature ---------------------------------------
    static uint32_t carla_lv2_uri_to_id(const LV2_URI_Map_Callback_Data data, const char* const map, const char* const uri)
    {
        qDebug("CarlaLv2Client::carla_lv2_uri_to_id(%p, %s, %s)", data, map, uri);
        return carla_lv2_urid_map((LV2_URID_Map_Handle*)data, uri);
    }
    // ----------------- URID Feature ------------------------------------------
    static LV2_URID carla_lv2_urid_map(const LV2_URID_Map_Handle handle, const char* const uri)
    {
        qDebug("CarlaLv2Client::carla_lv2_urid_map(%p, %s)", handle, uri);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(uri);
        if (! uri)
            return CARLA_URI_MAP_ID_NULL;
        // Atom types
        if (std::strcmp(uri, LV2_ATOM__Chunk) == 0)
            return CARLA_URI_MAP_ID_ATOM_CHUNK;
        if (std::strcmp(uri, LV2_ATOM__Double) == 0)
            return CARLA_URI_MAP_ID_ATOM_DOUBLE;
        if (std::strcmp(uri, LV2_ATOM__Int) == 0)
            return CARLA_URI_MAP_ID_ATOM_INT;
        if (std::strcmp(uri, LV2_ATOM__Path) == 0)
            return CARLA_URI_MAP_ID_ATOM_PATH;
        if (std::strcmp(uri, LV2_ATOM__Sequence) == 0)
            return CARLA_URI_MAP_ID_ATOM_SEQUENCE;
        if (std::strcmp(uri, LV2_ATOM__String) == 0)
            return CARLA_URI_MAP_ID_ATOM_STRING;
        if (std::strcmp(uri, LV2_ATOM__atomTransfer) == 0)
            return CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM;
        if (std::strcmp(uri, LV2_ATOM__eventTransfer) == 0)
            return CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT;
        // BufSize types
        if (std::strcmp(uri, LV2_BUF_SIZE__maxBlockLength) == 0)
            return CARLA_URI_MAP_ID_BUF_MAX_LENGTH;
        if (std::strcmp(uri, LV2_BUF_SIZE__minBlockLength) == 0)
            return CARLA_URI_MAP_ID_BUF_MIN_LENGTH;
        if (std::strcmp(uri, LV2_BUF_SIZE__sequenceSize) == 0)
            return CARLA_URI_MAP_ID_BUF_SEQUENCE_SIZE;
        // Log types
        if (std::strcmp(uri, LV2_LOG__Error) == 0)
            return CARLA_URI_MAP_ID_LOG_ERROR;
        if (std::strcmp(uri, LV2_LOG__Note) == 0)
            return CARLA_URI_MAP_ID_LOG_NOTE;
        if (std::strcmp(uri, LV2_LOG__Trace) == 0)
            return CARLA_URI_MAP_ID_LOG_TRACE;
        if (std::strcmp(uri, LV2_LOG__Warning) == 0)
            return CARLA_URI_MAP_ID_LOG_WARNING;
        // Others
        if (std::strcmp(uri, LV2_MIDI__MidiEvent) == 0)
            return CARLA_URI_MAP_ID_MIDI_EVENT;
        if (std::strcmp(uri, LV2_PARAMETERS__sampleRate) == 0)
            return CARLA_URI_MAP_ID_PARAM_SAMPLE_RATE;
        if (! handle)
            return CARLA_URI_MAP_ID_NULL;
        // Custom types
        CarlaLv2Client* const client = (CarlaLv2Client*)handle;
        return client->getCustomURID(uri);
    }
    static const char* carla_lv2_urid_unmap(const LV2_URID_Map_Handle handle, const LV2_URID urid)
    {
        qDebug("CarlaLv2Client::carla_lv2_urid_unmap(%p, %i)", handle, urid);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(urid > CARLA_URI_MAP_ID_NULL);
        if (urid == CARLA_URI_MAP_ID_NULL)
            return nullptr;
        // Atom types
        if (urid == CARLA_URI_MAP_ID_ATOM_CHUNK)
            return LV2_ATOM__Chunk;
        if (urid == CARLA_URI_MAP_ID_ATOM_DOUBLE)
            return LV2_ATOM__Double;
        if (urid == CARLA_URI_MAP_ID_ATOM_INT)
            return LV2_ATOM__Int;
        if (urid == CARLA_URI_MAP_ID_ATOM_PATH)
            return LV2_ATOM__Path;
        if (urid == CARLA_URI_MAP_ID_ATOM_SEQUENCE)
            return LV2_ATOM__Sequence;
        if (urid == CARLA_URI_MAP_ID_ATOM_STRING)
            return LV2_ATOM__String;
        if (urid == CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM)
            return LV2_ATOM__atomTransfer;
        if (urid == CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT)
            return LV2_ATOM__eventTransfer;
        // BufSize types
        if (urid == CARLA_URI_MAP_ID_BUF_MAX_LENGTH)
            return LV2_BUF_SIZE__maxBlockLength;
        if (urid == CARLA_URI_MAP_ID_BUF_MIN_LENGTH)
            return LV2_BUF_SIZE__minBlockLength;
        if (urid == CARLA_URI_MAP_ID_BUF_SEQUENCE_SIZE)
            return LV2_BUF_SIZE__sequenceSize;
        // Log types
        if (urid == CARLA_URI_MAP_ID_LOG_ERROR)
            return LV2_LOG__Error;
        if (urid == CARLA_URI_MAP_ID_LOG_NOTE)
            return LV2_LOG__Note;
        if (urid == CARLA_URI_MAP_ID_LOG_TRACE)
            return LV2_LOG__Trace;
        if (urid == CARLA_URI_MAP_ID_LOG_WARNING)
            return LV2_LOG__Warning;
        // Others
        if (urid == CARLA_URI_MAP_ID_MIDI_EVENT)
            return LV2_MIDI__MidiEvent;
        if (urid == CARLA_URI_MAP_ID_PARAM_SAMPLE_RATE)
            return LV2_PARAMETERS__sampleRate;
        if (! handle)
            return nullptr;
        // Custom types
        CarlaLv2Client* const client = (CarlaLv2Client*)handle;
        return client->getCustomURIString(urid);
    }
    // ----------------- UI Port-Map Feature ---------------------------------------------
    static uint32_t carla_lv2_ui_port_map(const LV2UI_Feature_Handle handle, const char* const symbol)
    {
        qDebug("CarlaLv2Client::carla_lv2_ui_port_map(%p, %s)", handle, symbol);
        CARLA_ASSERT(handle);
        if (! handle)
            return LV2UI_INVALID_PORT_INDEX;
        CarlaLv2Client* const client = (CarlaLv2Client*)handle;
        return client->handleUiPortMap(symbol);
    }
    // ----------------- UI Resize Feature -------------------------------------
    static int carla_lv2_ui_resize(const LV2UI_Feature_Handle handle, const int width, const int height)
    {
        qDebug("CarlaLv2Client::carla_lv2_ui_resize(%p, %i, %i)", handle, width, height);
        CARLA_ASSERT(handle);
        if (! handle)
            return 1;
        CarlaLv2Client* const client = (CarlaLv2Client*)handle;
        return client->handleUiResize(width, height);
    }
    // ----------------- UI Extension ------------------------------------------
    static void carla_lv2_ui_write_function(const LV2UI_Controller controller, const uint32_t port_index, const uint32_t buffer_size, const uint32_t format, const void* const buffer)
    {
        qDebug("CarlaLv2Client::carla_lv2_ui_write_function(%p, %i, %i, %i, %p)", controller, port_index, buffer_size, format, buffer);
        CARLA_ASSERT(controller);
        if (! controller)
            return;
        CarlaLv2Client* const client = (CarlaLv2Client*)controller;
        client->handleUiWrite(port_index, buffer_size, format, buffer);
    }
private:
    LV2UI_Handle handle;
    LV2UI_Widget widget;
    const LV2UI_Descriptor* descriptor;
    LV2_Feature* features[lv2_feature_count+1];
    const LV2_RDF_Descriptor* rdf_descriptor;
    const LV2_RDF_UI* rdf_ui_descriptor;
    const LV2_Programs_UI_Interface* programs;
    bool m_resizable;
    std::vector<const char*> customURIDs;
};
int CarlaBridgeOsc::handleMsgLv2TransferAtom(CARLA_BRIDGE_OSC_HANDLE_ARGS)
{
    qDebug("CarlaBridgeOsc::handleMsgLv2TransferAtom()");
    CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(3, "iss");
    if (! kClient)
        return 1;
    const int32_t portIndex   = argv[0]->i;
    const char* const typeStr = (const char*)&argv[1]->s;
    const char* const atomBuf = (const char*)&argv[2]->s;
    QByteArray chunk;
    chunk = QByteArray::fromBase64(atomBuf);
    LV2_Atom* const atom = (LV2_Atom*)chunk.constData();
    CarlaLv2Client* const lv2Client = (CarlaLv2Client*)kClient;
    atom->type = lv2Client->getCustomURID(typeStr);
    lv2Client->handleTransferAtom(portIndex, atom);
    return 0;
}
int CarlaBridgeOsc::handleMsgLv2TransferEvent(CARLA_BRIDGE_OSC_HANDLE_ARGS)
{
    qDebug("CarlaBridgeOsc::handleMsgLv2TransferEvent()");
    CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(3, "iss");
    if (! kClient)
        return 1;
    const int32_t portIndex   = argv[0]->i;
    const char* const typeStr = (const char*)&argv[1]->s;
    const char* const atomBuf = (const char*)&argv[2]->s;
    QByteArray chunk;
    chunk = QByteArray::fromBase64(atomBuf);
    LV2_Atom* const atom = (LV2_Atom*)chunk.constData();
    CarlaLv2Client* const lv2Client = (CarlaLv2Client*)kClient;
    atom->type = lv2Client->getCustomURID(typeStr);
    lv2Client->handleTransferEvent(portIndex, atom);
    return 0;
}
CARLA_BRIDGE_END_NAMESPACE
int main(int argc, char* argv[])
{
    CARLA_BRIDGE_USE_NAMESPACE
    if (argc != 5)
    {
        carla_stderr("usage: %s <osc-url|\"null\"> <plugin-uri> <ui-uri> <ui-title>", argv[0]);
        return 1;
    }
    const char* oscUrl    = argv[1];
    const char* pluginURI = argv[2];
    const char* uiURI     = argv[3];
    const char* uiTitle   = argv[4];
    const bool useOsc = std::strcmp(oscUrl, "null");
    // try to get sampleRate value
    const char* const sampleRateStr = getenv("CARLA_SAMPLE_RATE");
    if (sampleRateStr)
        sampleRate = atof(sampleRateStr);
    // Init LV2 client
    CarlaLv2Client client(uiTitle);
    // Init OSC
    if (useOsc)
        client.oscInit(oscUrl);
    // Load UI
    int ret;
    if (client.uiInit(pluginURI, uiURI))
    {
        client.toolkitExec(!useOsc);
        ret = 0;
    }
    else
    {
        qCritical("Failed to load LV2 UI");
        ret = 1;
    }
    // Close OSC
    if (useOsc)
        client.oscClose();
    // Close LV2 client
    client.uiClose();
    return ret;
}
#endif // BRIDGE_LV2
 |