diff DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoPluginCLAP.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/DistrhoPluginCLAP.cpp	Mon Oct 16 21:53:34 2023 +0200
@@ -0,0 +1,2628 @@
+/*
+ * DISTRHO Plugin Framework (DPF)
+ * Copyright (C) 2012-2023 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.
+ */
+
+/* TODO items:
+ * CV: write a specification
+ * INFO: define url, manual url, support url and string version
+ * PARAMETERS: test parameter triggers
+ * States: skip DSP/UI only states as appropriate
+ * UI: expose external-only UIs
+ */
+
+#include "DistrhoPluginInternal.hpp"
+#include "extra/ScopedPointer.hpp"
+
+#ifndef DISTRHO_PLUGIN_CLAP_ID
+# error DISTRHO_PLUGIN_CLAP_ID undefined!
+#endif
+
+#if DISTRHO_PLUGIN_HAS_UI && ! defined(HAVE_DGL) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+# undef DISTRHO_PLUGIN_HAS_UI
+# define DISTRHO_PLUGIN_HAS_UI 0
+#endif
+
+#if DISTRHO_PLUGIN_HAS_UI
+# include "DistrhoUIInternal.hpp"
+# include "../extra/Mutex.hpp"
+#endif
+
+#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+# include "../extra/RingBuffer.hpp"
+#endif
+
+#include <map>
+#include <vector>
+
+#include "clap/entry.h"
+#include "clap/plugin-factory.h"
+#include "clap/ext/audio-ports.h"
+#include "clap/ext/latency.h"
+#include "clap/ext/gui.h"
+#include "clap/ext/note-ports.h"
+#include "clap/ext/params.h"
+#include "clap/ext/state.h"
+#include "clap/ext/thread-check.h"
+#include "clap/ext/timer-support.h"
+
+#if (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+# define DPF_CLAP_USING_HOST_TIMER 0
+#else
+# define DPF_CLAP_USING_HOST_TIMER 1
+#endif
+
+#ifndef DPF_CLAP_TIMER_INTERVAL
+# define DPF_CLAP_TIMER_INTERVAL 16 /* ~60 fps */
+#endif
+
+START_NAMESPACE_DISTRHO
+
+// --------------------------------------------------------------------------------------------------------------------
+
+typedef std::map<const String, String> StringMap;
+
+struct ClapEventQueue
+{
+  #if DISTRHO_PLUGIN_HAS_UI
+    enum EventType {
+        kEventGestureBegin,
+        kEventGestureEnd,
+        kEventParamSet
+    };
+
+    struct Event {
+        EventType type;
+        uint32_t index;
+        float value;
+    };
+
+    struct Queue {
+        RecursiveMutex lock;
+        uint allocated;
+        uint used;
+        Event* events;
+
+        Queue()
+            : allocated(0),
+              used(0),
+              events(nullptr) {}
+
+        ~Queue()
+        {
+            delete[] events;
+        }
+
+        void addEventFromUI(const Event& event)
+        {
+            const RecursiveMutexLocker crml(lock);
+
+            if (events == nullptr)
+            {
+                events = static_cast<Event*>(std::malloc(sizeof(Event) * 8));
+                allocated = 8;
+            }
+            else if (used + 1 > allocated)
+            {
+                allocated = used * 2;
+                events = static_cast<Event*>(std::realloc(events, sizeof(Event) * allocated));
+            }
+
+            std::memcpy(&events[used++], &event, sizeof(Event));
+        }
+    } fEventQueue;
+
+   #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+    SmallStackBuffer fNotesBuffer;
+   #endif
+  #endif
+
+   #if DISTRHO_PLUGIN_WANT_PROGRAMS
+    uint32_t fCurrentProgram;
+   #endif
+
+  #if DISTRHO_PLUGIN_WANT_STATE
+    StringMap fStateMap;
+   #if DISTRHO_PLUGIN_HAS_UI
+    virtual void setStateFromUI(const char* key, const char* value) = 0;
+   #endif
+  #endif
+
+    struct CachedParameters {
+        uint numParams;
+        bool* changed;
+        float* values;
+
+        CachedParameters()
+            : numParams(0),
+              changed(nullptr),
+              values(nullptr) {}
+
+        ~CachedParameters()
+        {
+            delete[] changed;
+            delete[] values;
+        }
+
+        void setup(const uint numParameters)
+        {
+            if (numParameters == 0)
+                return;
+
+            numParams = numParameters;
+            changed = new bool[numParameters];
+            values = new float[numParameters];
+
+            std::memset(changed, 0, sizeof(bool)*numParameters);
+            std::memset(values, 0, sizeof(float)*numParameters);
+        }
+    } fCachedParameters;
+
+    ClapEventQueue()
+       #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+        : fNotesBuffer(StackBuffer_INIT)
+       #endif
+    {
+       #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT && ! defined(DISTRHO_PROPER_CPP11_SUPPORT)
+        std::memset(&fNotesBuffer, 0, sizeof(fNotesBuffer));
+       #endif
+       #if DISTRHO_PLUGIN_WANT_PROGRAMS
+        fCurrentProgram = 0;
+       #endif
+    }
+
+    virtual ~ClapEventQueue() {}
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#if DISTRHO_PLUGIN_HAS_UI
+
+#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
+
+/**
+ * CLAP UI class.
+ */
+class ClapUI : public DGL_NAMESPACE::IdleCallback
+{
+public:
+    ClapUI(PluginExporter& plugin,
+           ClapEventQueue* const eventQueue,
+           const clap_host_t* const host,
+           const clap_host_gui_t* const hostGui,
+          #if DPF_CLAP_USING_HOST_TIMER
+           const clap_host_timer_support_t* const hostTimer,
+          #endif
+           const bool isFloating)
+        : fPlugin(plugin),
+          fPluginEventQueue(eventQueue),
+          fEventQueue(eventQueue->fEventQueue),
+          fCachedParameters(eventQueue->fCachedParameters),
+         #if DISTRHO_PLUGIN_WANT_PROGRAMS
+          fCurrentProgram(eventQueue->fCurrentProgram),
+         #endif
+         #if DISTRHO_PLUGIN_WANT_STATE
+          fStateMap(eventQueue->fStateMap),
+         #endif
+          fHost(host),
+          fHostGui(hostGui),
+         #if DPF_CLAP_USING_HOST_TIMER
+          fTimerId(0),
+          fHostTimer(hostTimer),
+         #else
+          fCallbackRegistered(false),
+         #endif
+          fIsFloating(isFloating),
+          fScaleFactor(0.0),
+          fParentWindow(0),
+          fTransientWindow(0)
+    {
+       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+        fNotesRingBuffer.setRingBuffer(&eventQueue->fNotesBuffer, false);
+       #endif
+    }
+
+    ~ClapUI() override
+    {
+       #if DPF_CLAP_USING_HOST_TIMER
+        if (fTimerId != 0)
+            fHostTimer->unregister_timer(fHost, fTimerId);
+       #else
+        if (fCallbackRegistered && fUI != nullptr)
+            fUI->removeIdleCallbackForNativeIdle(this);
+       #endif
+    }
+
+   #ifndef DISTRHO_OS_MAC
+    bool setScaleFactor(const double scaleFactor)
+    {
+        if (d_isEqual(fScaleFactor, scaleFactor))
+            return true;
+
+        fScaleFactor = scaleFactor;
+
+        if (UIExporter* const ui = fUI.get())
+            ui->notifyScaleFactorChanged(scaleFactor);
+
+        return true;
+    }
+   #endif
+
+    bool getSize(uint32_t* const width, uint32_t* const height) const
+    {
+        if (UIExporter* const ui = fUI.get())
+        {
+            *width = ui->getWidth();
+            *height = ui->getHeight();
+           #ifdef DISTRHO_OS_MAC
+            const double scaleFactor = ui->getScaleFactor();
+            *width /= scaleFactor;
+            *height /= scaleFactor;
+           #endif
+            return true;
+        }
+
+        double scaleFactor = fScaleFactor;
+       #if defined(DISTRHO_UI_DEFAULT_WIDTH) && defined(DISTRHO_UI_DEFAULT_HEIGHT)
+        *width = DISTRHO_UI_DEFAULT_WIDTH;
+        *height = DISTRHO_UI_DEFAULT_HEIGHT;
+        if (d_isZero(scaleFactor))
+            scaleFactor = 1.0;
+       #else
+        UIExporter tmpUI(nullptr, 0, fPlugin.getSampleRate(),
+                         nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, d_nextBundlePath,
+                         fPlugin.getInstancePointer(), scaleFactor);
+        *width = tmpUI.getWidth();
+        *height = tmpUI.getHeight();
+        scaleFactor = tmpUI.getScaleFactor();
+        tmpUI.quit();
+       #endif
+
+       #ifdef DISTRHO_OS_MAC
+        *width /= scaleFactor;
+        *height /= scaleFactor;
+       #endif
+
+        return true;
+    }
+
+    bool canResize() const noexcept
+    {
+       #if DISTRHO_UI_USER_RESIZABLE
+        if (UIExporter* const ui = fUI.get())
+            return ui->isResizable();
+       #endif
+        return false;
+    }
+
+    bool getResizeHints(clap_gui_resize_hints_t* const hints) const
+    {
+        if (canResize())
+        {
+            uint minimumWidth, minimumHeight;
+            bool keepAspectRatio;
+            fUI->getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio);
+
+           #ifdef DISTRHO_OS_MAC
+            const double scaleFactor = fUI->getScaleFactor();
+            minimumWidth /= scaleFactor;
+            minimumHeight /= scaleFactor;
+           #endif
+
+            hints->can_resize_horizontally = true;
+            hints->can_resize_vertically = true;
+            hints->preserve_aspect_ratio = keepAspectRatio;
+            hints->aspect_ratio_width = minimumWidth;
+            hints->aspect_ratio_height = minimumHeight;
+
+            return true;
+        }
+
+        hints->can_resize_horizontally = false;
+        hints->can_resize_vertically = false;
+        hints->preserve_aspect_ratio = false;
+        hints->aspect_ratio_width = 0;
+        hints->aspect_ratio_height = 0;
+
+        return false;
+    }
+
+    bool adjustSize(uint32_t* const width, uint32_t* const height) const
+    {
+        if (canResize())
+        {
+            uint minimumWidth, minimumHeight;
+            bool keepAspectRatio;
+            fUI->getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio);
+
+           #ifdef DISTRHO_OS_MAC
+            const double scaleFactor = fUI->getScaleFactor();
+            minimumWidth /= scaleFactor;
+            minimumHeight /= scaleFactor;
+           #endif
+
+            if (keepAspectRatio)
+            {
+                if (*width < 1)
+                    *width = 1;
+                if (*height < 1)
+                    *height = 1;
+
+                const double ratio = static_cast<double>(minimumWidth) / static_cast<double>(minimumHeight);
+                const double reqRatio = static_cast<double>(*width) / static_cast<double>(*height);
+
+                if (d_isNotEqual(ratio, reqRatio))
+                {
+                    // fix width
+                    if (reqRatio > ratio)
+                        *width = static_cast<int32_t>(*height * ratio + 0.5);
+                    // fix height
+                    else
+                        *height = static_cast<int32_t>(static_cast<double>(*width) / ratio + 0.5);
+                }
+            }
+
+            if (minimumWidth > *width)
+                *width = minimumWidth;
+            if (minimumHeight > *height)
+                *height = minimumHeight;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    bool setSizeFromHost(uint32_t width, uint32_t height)
+    {
+        if (UIExporter* const ui = fUI.get())
+        {
+           #ifdef DISTRHO_OS_MAC
+            const double scaleFactor = ui->getScaleFactor();
+            width *= scaleFactor;
+            height *= scaleFactor;
+           #endif
+            ui->setWindowSizeFromHost(width, height);
+            return true;
+        }
+
+        return false;
+    }
+
+    bool setParent(const clap_window_t* const window)
+    {
+        if (fIsFloating)
+            return false;
+
+        fParentWindow = window->uptr;
+
+        if (fUI == nullptr)
+        {
+            createUI();
+            fHostGui->resize_hints_changed(fHost);
+        }
+
+        return true;
+    }
+
+    bool setTransient(const clap_window_t* const window)
+    {
+        if (! fIsFloating)
+            return false;
+
+        fTransientWindow = window->uptr;
+
+        if (UIExporter* const ui = fUI.get())
+            ui->setWindowTransientWinId(window->uptr);
+
+        return true;
+    }
+
+    void suggestTitle(const char* const title)
+    {
+        if (! fIsFloating)
+            return;
+
+        fWindowTitle = title;
+
+        if (UIExporter* const ui = fUI.get())
+            ui->setWindowTitle(title);
+    }
+
+    bool show()
+    {
+        if (fUI == nullptr)
+        {
+            createUI();
+            fHostGui->resize_hints_changed(fHost);
+        }
+
+        if (fIsFloating)
+            fUI->setWindowVisible(true);
+
+       #if DPF_CLAP_USING_HOST_TIMER
+        fHostTimer->register_timer(fHost, DPF_CLAP_TIMER_INTERVAL, &fTimerId);
+       #else
+        fCallbackRegistered = true;
+        fUI->addIdleCallbackForNativeIdle(this, DPF_CLAP_TIMER_INTERVAL);
+       #endif
+        return true;
+    }
+
+    bool hide()
+    {
+        if (UIExporter* const ui = fUI.get())
+        {
+            ui->setWindowVisible(false);
+           #if DPF_CLAP_USING_HOST_TIMER
+            fHostTimer->unregister_timer(fHost, fTimerId);
+            fTimerId = 0;
+           #else
+            ui->removeIdleCallbackForNativeIdle(this);
+            fCallbackRegistered = false;
+           #endif
+        }
+
+        return true;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+
+    void idleCallback() override
+    {
+        if (UIExporter* const ui = fUI.get())
+        {
+           #if DPF_CLAP_USING_HOST_TIMER
+            ui->plugin_idle();
+           #else
+            ui->idleFromNativeIdle();
+           #endif
+
+            for (uint i=0; i<fCachedParameters.numParams; ++i)
+            {
+                if (fCachedParameters.changed[i])
+                {
+                    fCachedParameters.changed[i] = false;
+                    ui->parameterChanged(i, fCachedParameters.values[i]);
+                }
+            }
+        }
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+
+    void setParameterValueFromPlugin(const uint index, const float value)
+    {
+        if (UIExporter* const ui = fUI.get())
+            ui->parameterChanged(index, value);
+    }
+
+   #if DISTRHO_PLUGIN_WANT_PROGRAMS
+    void setProgramFromPlugin(const uint index)
+    {
+        if (UIExporter* const ui = fUI.get())
+            ui->programLoaded(index);
+    }
+   #endif
+
+   #if DISTRHO_PLUGIN_WANT_STATE
+    void setStateFromPlugin(const char* const key, const char* const value)
+    {
+        if (UIExporter* const ui = fUI.get())
+            ui->stateChanged(key, value);
+    }
+   #endif
+
+    // ----------------------------------------------------------------------------------------------------------------
+
+private:
+    // Plugin and UI
+    PluginExporter& fPlugin;
+    ClapEventQueue* const fPluginEventQueue;
+    ClapEventQueue::Queue& fEventQueue;
+    ClapEventQueue::CachedParameters& fCachedParameters;
+   #if DISTRHO_PLUGIN_WANT_PROGRAMS
+    uint32_t& fCurrentProgram;
+   #endif
+   #if DISTRHO_PLUGIN_WANT_STATE
+    StringMap& fStateMap;
+   #endif
+    const clap_host_t* const fHost;
+    const clap_host_gui_t* const fHostGui;
+   #if DPF_CLAP_USING_HOST_TIMER
+    clap_id fTimerId;
+    const clap_host_timer_support_t* const fHostTimer;
+   #else
+    bool fCallbackRegistered;
+   #endif
+   #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+    RingBufferControl<SmallStackBuffer> fNotesRingBuffer;
+   #endif
+    ScopedPointer<UIExporter> fUI;
+
+    const bool fIsFloating;
+
+    // Temporary data
+    double fScaleFactor;
+    uintptr_t fParentWindow;
+    uintptr_t fTransientWindow;
+    String fWindowTitle;
+
+    // ----------------------------------------------------------------------------------------------------------------
+
+    void createUI()
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fUI == nullptr,);
+
+        fUI = new UIExporter(this,
+                             fParentWindow,
+                             fPlugin.getSampleRate(),
+                             editParameterCallback,
+                             setParameterCallback,
+                             setStateCallback,
+                             sendNoteCallback,
+                             setSizeCallback,
+                             nullptr, // TODO fileRequestCallback,
+                             d_nextBundlePath,
+                             fPlugin.getInstancePointer(),
+                             fScaleFactor);
+
+       #if DISTRHO_PLUGIN_WANT_PROGRAMS
+        fUI->programLoaded(fCurrentProgram);
+       #endif
+
+       #if DISTRHO_PLUGIN_WANT_FULL_STATE
+        // Update current state from plugin side
+        for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
+        {
+            const String& key = cit->first;
+            fStateMap[key] = fPlugin.getStateValue(key);
+        }
+       #endif
+
+       #if DISTRHO_PLUGIN_WANT_STATE
+        // Set state
+        for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
+        {
+            const String& key   = cit->first;
+            const String& value = cit->second;
+
+            // TODO skip DSP only states
+
+            fUI->stateChanged(key, value);
+        }
+       #endif
+
+        for (uint32_t i=0; i<fCachedParameters.numParams; ++i)
+        {
+            const float value = fCachedParameters.values[i] = fPlugin.getParameterValue(i);
+            fCachedParameters.changed[i] = false;
+            fUI->parameterChanged(i, value);
+        }
+
+        if (fIsFloating)
+        {
+            if (fWindowTitle.isNotEmpty())
+                fUI->setWindowTitle(fWindowTitle);
+
+            if (fTransientWindow != 0)
+                fUI->setWindowTransientWinId(fTransientWindow);
+        }
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // DPF callbacks
+
+    void editParameter(const uint32_t rindex, const bool started) const
+    {
+        const ClapEventQueue::Event ev = {
+            started ? ClapEventQueue::kEventGestureBegin : ClapEventQueue::kEventGestureEnd,
+            rindex, 0.f
+        };
+        fEventQueue.addEventFromUI(ev);
+    }
+
+    static void editParameterCallback(void* const ptr, const uint32_t rindex, const bool started)
+    {
+        static_cast<ClapUI*>(ptr)->editParameter(rindex, started);
+    }
+
+    void setParameterValue(const uint32_t rindex, const float value)
+    {
+        const ClapEventQueue::Event ev = {
+            ClapEventQueue::kEventParamSet,
+            rindex, value
+        };
+        fEventQueue.addEventFromUI(ev);
+    }
+
+    static void setParameterCallback(void* const ptr, const uint32_t rindex, const float value)
+    {
+        static_cast<ClapUI*>(ptr)->setParameterValue(rindex, value);
+    }
+
+    void setSizeFromPlugin(const uint width, const uint height)
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,);
+
+       #ifdef DISTRHO_OS_MAC
+        const double scaleFactor = fUI->getScaleFactor();
+        const uint hostWidth = width / scaleFactor;
+        const uint hostHeight = height / scaleFactor;
+       #else
+        const uint hostWidth = width;
+        const uint hostHeight = height;
+       #endif
+
+        if (fHostGui->request_resize(fHost, hostWidth, hostHeight))
+            fUI->setWindowSizeFromHost(width, height);
+    }
+
+    static void setSizeCallback(void* const ptr, const uint width, const uint height)
+    {
+        static_cast<ClapUI*>(ptr)->setSizeFromPlugin(width, height);
+    }
+
+   #if DISTRHO_PLUGIN_WANT_STATE
+    void setState(const char* const key, const char* const value)
+    {
+        fPluginEventQueue->setStateFromUI(key, value);
+    }
+
+    static void setStateCallback(void* const ptr, const char* key, const char* value)
+    {
+        static_cast<ClapUI*>(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)
+    {
+        uint8_t midiData[3];
+        midiData[0] = (velocity != 0 ? 0x90 : 0x80) | channel;
+        midiData[1] = note;
+        midiData[2] = velocity;
+        fNotesRingBuffer.writeCustomData(midiData, 3);
+        fNotesRingBuffer.commitWrite();
+    }
+
+    static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity)
+    {
+        static_cast<ClapUI*>(ptr)->sendNote(channel, note, velocity);
+    }
+   #endif
+
+    /* TODO
+    bool fileRequest(const char*)
+    {
+        return true;
+    }
+
+    static bool fileRequestCallback(void* const ptr, const char* const key)
+    {
+        return static_cast<ClapUI*>(ptr)->fileRequest(key);
+    }
+    */
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#endif // DISTRHO_PLUGIN_HAS_UI
+
+#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+static constexpr const writeMidiFunc writeMidiCallback = nullptr;
+#endif
+#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
+static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr;
+#endif
+#if ! DISTRHO_PLUGIN_WANT_STATE
+static constexpr const updateStateValueFunc updateStateValueCallback = nullptr;
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+
+/**
+ * CLAP plugin class.
+ */
+class PluginCLAP : ClapEventQueue
+{
+public:
+    PluginCLAP(const clap_host_t* const host)
+        : fPlugin(this,
+                  writeMidiCallback,
+                  requestParameterValueChangeCallback,
+                  updateStateValueCallback),
+          fHost(host),
+          fOutputEvents(nullptr),
+         #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+          fUsingCV(false),
+         #endif
+         #if DISTRHO_PLUGIN_WANT_LATENCY
+          fLatencyChanged(false),
+          fLastKnownLatency(0),
+         #endif
+         #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+          fMidiEventCount(0),
+         #endif
+          fHostExtensions(host)
+    {
+        fCachedParameters.setup(fPlugin.getParameterCount());
+
+       #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+        fNotesRingBuffer.setRingBuffer(&fNotesBuffer, true);
+       #endif
+
+       #if DISTRHO_PLUGIN_WANT_STATE
+        for (uint32_t i=0, count=fPlugin.getStateCount(); i<count; ++i)
+        {
+            const String& dkey(fPlugin.getStateKey(i));
+            fStateMap[dkey] = fPlugin.getStateDefaultValue(i);
+        }
+       #endif
+
+       #if DISTRHO_PLUGIN_NUM_INPUTS != 0
+        fillInBusInfoDetails<true>();
+       #endif
+       #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+        fillInBusInfoDetails<false>();
+       #endif
+       #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+        fillInBusInfoPairs();
+       #endif
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // core
+
+    template <class T>
+    const T* getHostExtension(const char* const extensionId) const
+    {
+        return static_cast<const T*>(fHost->get_extension(fHost, extensionId));
+    }
+
+    bool init()
+    {
+        if (!clap_version_is_compatible(fHost->clap_version))
+            return false;
+
+        return fHostExtensions.init();
+    }
+
+    void activate(const double sampleRate, const uint32_t maxFramesCount)
+    {
+        fPlugin.setSampleRate(sampleRate, true);
+        fPlugin.setBufferSize(maxFramesCount, true);
+        fPlugin.activate();
+    }
+
+    void deactivate()
+    {
+        fPlugin.deactivate();
+       #if DISTRHO_PLUGIN_WANT_LATENCY
+        checkForLatencyChanges(false, true);
+        reportLatencyChangeIfNeeded();
+       #endif
+    }
+
+    bool process(const clap_process_t* const process)
+    {
+       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+        fMidiEventCount = 0;
+       #endif
+
+       #if DISTRHO_PLUGIN_HAS_UI
+        if (const clap_output_events_t* const outputEvents = process->out_events)
+        {
+            const RecursiveMutexTryLocker crmtl(fEventQueue.lock);
+
+            if (crmtl.wasLocked())
+            {
+                // reuse the same struct for gesture and parameters, they are compatible up to where it matters
+                clap_event_param_value_t clapEvent = {
+                    { 0, 0, 0, 0, CLAP_EVENT_IS_LIVE },
+                    0, nullptr, 0, 0, 0, 0, 0.0
+                };
+
+                for (uint32_t i=0; i<fEventQueue.used; ++i)
+                {
+                    const Event& event(fEventQueue.events[i]);
+
+                    switch (event.type)
+                    {
+                    case kEventGestureBegin:
+                        clapEvent.header.size = sizeof(clap_event_param_gesture_t);
+                        clapEvent.header.type = CLAP_EVENT_PARAM_GESTURE_BEGIN;
+                        clapEvent.param_id = event.index;
+                        break;
+                    case kEventGestureEnd:
+                        clapEvent.header.size = sizeof(clap_event_param_gesture_t);
+                        clapEvent.header.type = CLAP_EVENT_PARAM_GESTURE_END;
+                        clapEvent.param_id = event.index;
+                        break;
+                    case kEventParamSet:
+                        clapEvent.header.size = sizeof(clap_event_param_value_t);
+                        clapEvent.header.type = CLAP_EVENT_PARAM_VALUE;
+                        clapEvent.param_id = event.index;
+                        clapEvent.value = event.value;
+                        fPlugin.setParameterValue(event.index, event.value);
+                        break;
+                    default:
+                        continue;
+                    }
+
+                    outputEvents->try_push(outputEvents, &clapEvent.header);
+                }
+
+                fEventQueue.used = 0;
+            }
+        }
+       #endif
+
+       #if DISTRHO_PLUGIN_WANT_TIMEPOS
+        if (const clap_event_transport_t* const transport = process->transport)
+        {
+            fTimePosition.playing = (transport->flags & CLAP_TRANSPORT_IS_PLAYING) != 0 &&
+                                    (transport->flags & CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL) == 0;
+
+            fTimePosition.frame = process->steady_time >= 0 ? process->steady_time : 0;
+
+            if (transport->flags & CLAP_TRANSPORT_HAS_TEMPO)
+                fTimePosition.bbt.beatsPerMinute = transport->tempo;
+            else
+                fTimePosition.bbt.beatsPerMinute = 120.0;
+
+            // ticksPerBeat is not possible with CLAP
+            fTimePosition.bbt.ticksPerBeat = 1920.0;
+
+            if ((transport->flags & (CLAP_TRANSPORT_HAS_BEATS_TIMELINE|CLAP_TRANSPORT_HAS_TIME_SIGNATURE)) == (CLAP_TRANSPORT_HAS_BEATS_TIMELINE|CLAP_TRANSPORT_HAS_TIME_SIGNATURE))
+            {
+                if (transport->song_pos_beats >= 0)
+                {
+                    const int64_t clapPos   = std::abs(transport->song_pos_beats);
+                    const int64_t clapBeats = clapPos >> 31;
+                    const double  clapRest  = static_cast<double>(clapPos & 0x7fffffff) / CLAP_BEATTIME_FACTOR;
+
+                    fTimePosition.bbt.bar  = static_cast<int32_t>(clapBeats) / transport->tsig_num + 1;
+                    fTimePosition.bbt.beat = static_cast<int32_t>(clapBeats % transport->tsig_num) + 1;
+                    fTimePosition.bbt.tick = clapRest * fTimePosition.bbt.ticksPerBeat;
+                }
+                else
+                {
+                    fTimePosition.bbt.bar  = 1;
+                    fTimePosition.bbt.beat = 1;
+                    fTimePosition.bbt.tick = 0.0;
+                }
+
+                fTimePosition.bbt.valid       = true;
+                fTimePosition.bbt.beatsPerBar = transport->tsig_num;
+                fTimePosition.bbt.beatType    = transport->tsig_denom;
+            }
+            else
+            {
+                fTimePosition.bbt.valid       = false;
+                fTimePosition.bbt.bar         = 1;
+                fTimePosition.bbt.beat        = 1;
+                fTimePosition.bbt.tick        = 0.0;
+                fTimePosition.bbt.beatsPerBar = 4.0f;
+                fTimePosition.bbt.beatType    = 4.0f;
+            }
+
+            fTimePosition.bbt.barStartTick = fTimePosition.bbt.ticksPerBeat*
+                                             fTimePosition.bbt.beatsPerBar*
+                                             (fTimePosition.bbt.bar-1);
+        }
+        else
+        {
+            fTimePosition.playing = false;
+            fTimePosition.frame = 0;
+            fTimePosition.bbt.valid          = false;
+            fTimePosition.bbt.beatsPerMinute = 120.0;
+            fTimePosition.bbt.bar            = 1;
+            fTimePosition.bbt.beat           = 1;
+            fTimePosition.bbt.tick           = 0.0;
+            fTimePosition.bbt.beatsPerBar    = 4.f;
+            fTimePosition.bbt.beatType       = 4.f;
+            fTimePosition.bbt.barStartTick   = 0;
+        }
+
+        fPlugin.setTimePosition(fTimePosition);
+       #endif
+
+        if (const clap_input_events_t* const inputEvents = process->in_events)
+        {
+            if (const uint32_t len = inputEvents->size(inputEvents))
+            {
+                for (uint32_t i=0; i<len; ++i)
+                {
+                    const clap_event_header_t* const event = inputEvents->get(inputEvents, i);
+
+                    switch (event->type)
+                    {
+                    case CLAP_EVENT_NOTE_ON:
+                       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+                        // BUG: even though we only report CLAP_NOTE_DIALECT_MIDI as supported, Bitwig sends us this anyway
+                        addNoteEvent(static_cast<const clap_event_note_t*>(static_cast<const void*>(event)), true);
+                       #endif
+                        break;
+                    case CLAP_EVENT_NOTE_OFF:
+                       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+                        // BUG: even though we only report CLAP_NOTE_DIALECT_MIDI as supported, Bitwig sends us this anyway
+                        addNoteEvent(static_cast<const clap_event_note_t*>(static_cast<const void*>(event)), false);
+                       #endif
+                        break;
+                    case CLAP_EVENT_NOTE_CHOKE:
+                    case CLAP_EVENT_NOTE_END:
+                    case CLAP_EVENT_NOTE_EXPRESSION:
+                        break;
+                    case CLAP_EVENT_PARAM_VALUE:
+                        DISTRHO_SAFE_ASSERT_UINT2_BREAK(event->size == sizeof(clap_event_param_value),
+                                                        event->size, sizeof(clap_event_param_value));
+                        setParameterValueFromEvent(static_cast<const clap_event_param_value*>(static_cast<const void*>(event)));
+                        break;
+                    case CLAP_EVENT_PARAM_MOD:
+                    case CLAP_EVENT_PARAM_GESTURE_BEGIN:
+                    case CLAP_EVENT_PARAM_GESTURE_END:
+                    case CLAP_EVENT_TRANSPORT:
+                        break;
+                    case CLAP_EVENT_MIDI:
+                        DISTRHO_SAFE_ASSERT_UINT2_BREAK(event->size == sizeof(clap_event_midi_t),
+                                                        event->size, sizeof(clap_event_midi_t));
+                       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+                        addMidiEvent(static_cast<const clap_event_midi_t*>(static_cast<const void*>(event)));
+                       #endif
+                        break;
+                    case CLAP_EVENT_MIDI_SYSEX:
+                    case CLAP_EVENT_MIDI2:
+                        break;
+                    }
+                }
+            }
+        }
+
+       #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+        if (fMidiEventCount != kMaxMidiEvents && fNotesRingBuffer.isDataAvailableForReading())
+        {
+            uint8_t midiData[3];
+            const uint32_t frame = fMidiEventCount != 0 ? fMidiEvents[fMidiEventCount-1].frame : 0;
+
+            while (fNotesRingBuffer.isDataAvailableForReading())
+            {
+                if (! fNotesRingBuffer.readCustomData(midiData, 3))
+                    break;
+
+                MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
+                midiEvent.frame = frame;
+                midiEvent.size  = 3;
+                std::memcpy(midiEvent.data, midiData, 3);
+
+                if (fMidiEventCount == kMaxMidiEvents)
+                    break;
+            }
+        }
+       #endif
+
+        if (const uint32_t frames = process->frames_count)
+        {
+           #if DISTRHO_PLUGIN_NUM_INPUTS != 0
+            const float** const audioInputs = fAudioInputs;
+
+            uint32_t in=0;
+            for (uint32_t i=0; i<process->audio_inputs_count; ++i)
+            {
+                const clap_audio_buffer_t& inputs(process->audio_inputs[i]);
+                DISTRHO_SAFE_ASSERT_CONTINUE(inputs.channel_count != 0);
+
+                for (uint32_t j=0; j<inputs.channel_count; ++j, ++in)
+                    audioInputs[in] = const_cast<const float*>(inputs.data32[j]);
+            }
+
+            if (fUsingCV)
+            {
+                for (; in<DISTRHO_PLUGIN_NUM_INPUTS; ++in)
+                    audioInputs[in] = nullptr;
+            }
+            else
+            {
+                DISTRHO_SAFE_ASSERT_UINT2_RETURN(in == DISTRHO_PLUGIN_NUM_INPUTS,
+                                                 in, process->audio_inputs_count, false);
+            }
+           #else
+            constexpr const float** const audioInputs = nullptr;
+           #endif
+
+           #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+            float** const audioOutputs = fAudioOutputs;
+
+            uint32_t out=0;
+            for (uint32_t i=0; i<process->audio_outputs_count; ++i)
+            {
+                const clap_audio_buffer_t& outputs(process->audio_outputs[i]);
+                DISTRHO_SAFE_ASSERT_CONTINUE(outputs.channel_count != 0);
+
+                for (uint32_t j=0; j<outputs.channel_count; ++j, ++out)
+                    audioOutputs[out] = outputs.data32[j];
+            }
+
+            if (fUsingCV)
+            {
+                for (; out<DISTRHO_PLUGIN_NUM_OUTPUTS; ++out)
+                    audioOutputs[out] = nullptr;
+            }
+            else
+            {
+                DISTRHO_SAFE_ASSERT_UINT2_RETURN(out == DISTRHO_PLUGIN_NUM_OUTPUTS,
+                                                 out, DISTRHO_PLUGIN_NUM_OUTPUTS, false);
+            }
+           #else
+            constexpr float** const audioOutputs = nullptr;
+           #endif
+
+            fOutputEvents = process->out_events;
+
+           #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+            fPlugin.run(audioInputs, audioOutputs, frames, fMidiEvents, fMidiEventCount);
+           #else
+            fPlugin.run(audioInputs, audioOutputs, frames);
+           #endif
+
+            flushParameters(nullptr, process->out_events, frames - 1);
+
+            fOutputEvents = nullptr;
+        }
+
+       #if DISTRHO_PLUGIN_WANT_LATENCY
+        checkForLatencyChanges(true, false);
+       #endif
+
+        return true;
+    }
+
+    void onMainThread()
+    {
+       #if DISTRHO_PLUGIN_WANT_LATENCY
+        reportLatencyChangeIfNeeded();
+       #endif
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // parameters
+
+    uint32_t getParameterCount() const
+    {
+        return fPlugin.getParameterCount();
+    }
+
+    bool getParameterInfo(const uint32_t index, clap_param_info_t* const info) const
+    {
+        const ParameterRanges& ranges(fPlugin.getParameterRanges(index));
+
+        if (fPlugin.getParameterDesignation(index) == kParameterDesignationBypass)
+        {
+            info->flags = CLAP_PARAM_IS_STEPPED|CLAP_PARAM_IS_BYPASS|CLAP_PARAM_IS_AUTOMATABLE;
+            std::strcpy(info->name, "Bypass");
+            std::strcpy(info->module, "dpf_bypass");
+        }
+        else
+        {
+            const uint32_t hints = fPlugin.getParameterHints(index);
+            const uint32_t groupId = fPlugin.getParameterGroupId(index);
+
+            info->flags = 0;
+            if (hints & kParameterIsOutput)
+                info->flags |= CLAP_PARAM_IS_READONLY;
+            else if (hints & kParameterIsAutomatable)
+                info->flags |= CLAP_PARAM_IS_AUTOMATABLE;
+
+            if (hints & (kParameterIsBoolean|kParameterIsInteger))
+                info->flags |= CLAP_PARAM_IS_STEPPED;
+
+            d_strncpy(info->name, fPlugin.getParameterName(index), CLAP_NAME_SIZE);
+
+            uint wrtn;
+            if (groupId != kPortGroupNone)
+            {
+                const PortGroupWithId& portGroup(fPlugin.getPortGroupById(groupId));
+                strncpy(info->module, portGroup.symbol, CLAP_PATH_SIZE / 2);
+                info->module[CLAP_PATH_SIZE / 2] = '\0';
+                wrtn = std::strlen(info->module);
+                info->module[wrtn++] = '/';
+            }
+            else
+            {
+                wrtn = 0;
+            }
+
+            d_strncpy(info->module + wrtn, fPlugin.getParameterSymbol(index), CLAP_PATH_SIZE - wrtn);
+        }
+
+        info->id = index;
+        info->cookie = nullptr;
+        info->min_value = ranges.min;
+        info->max_value = ranges.max;
+        info->default_value = ranges.def;
+        return true;
+    }
+
+    bool getParameterValue(const clap_id param_id, double* const value) const
+    {
+        *value = fPlugin.getParameterValue(param_id);
+        return true;
+    }
+
+    bool getParameterStringForValue(const clap_id param_id, double value, char* const display, const uint32_t size) const
+    {
+        const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(param_id));
+        const ParameterRanges& ranges(fPlugin.getParameterRanges(param_id));
+        const uint32_t hints = fPlugin.getParameterHints(param_id);
+
+        if (hints & kParameterIsBoolean)
+        {
+            const float midRange = ranges.min + (ranges.max - ranges.min) * 0.5f;
+            value = value > midRange ? ranges.max : ranges.min;
+        }
+        else if (hints & kParameterIsInteger)
+        {
+            value = std::round(value);
+        }
+
+        for (uint32_t i=0; i < enumValues.count; ++i)
+        {
+            if (d_isEqual(static_cast<double>(enumValues.values[i].value), value))
+            {
+                d_strncpy(display, enumValues.values[i].label, size);
+                return true;
+            }
+        }
+
+        if (hints & kParameterIsInteger)
+            snprintf_i32(display, value, size);
+        else
+            snprintf_f32(display, value, size);
+
+        return true;
+    }
+
+    bool getParameterValueForString(const clap_id param_id, const char* const display, double* const value) const
+    {
+        const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(param_id));
+        const bool isInteger = fPlugin.isParameterInteger(param_id);
+
+        for (uint32_t i=0; i < enumValues.count; ++i)
+        {
+            if (std::strcmp(display, enumValues.values[i].label) == 0)
+            {
+                *value = enumValues.values[i].value;
+                return true;
+            }
+        }
+
+        if (isInteger)
+            *value = std::atoi(display);
+        else
+            *value = std::atof(display);
+
+        return true;
+    }
+
+    void flushParameters(const clap_input_events_t* const in,
+                         const clap_output_events_t* const out,
+                         const uint32_t frameOffset)
+    {
+        if (const uint32_t len = in != nullptr ? in->size(in) : 0)
+        {
+            for (uint32_t i=0; i<len; ++i)
+            {
+                const clap_event_header_t* const event = in->get(in, i);
+
+                if (event->type != CLAP_EVENT_PARAM_VALUE)
+                    continue;
+
+                DISTRHO_SAFE_ASSERT_UINT2_BREAK(event->size == sizeof(clap_event_param_value),
+                                                event->size, sizeof(clap_event_param_value));
+
+                setParameterValueFromEvent(static_cast<const clap_event_param_value*>(static_cast<const void*>(event)));
+            }
+        }
+
+        if (out != nullptr)
+        {
+            clap_event_param_value_t clapEvent = {
+                { sizeof(clap_event_param_value_t), frameOffset, 0, CLAP_EVENT_PARAM_VALUE, CLAP_EVENT_IS_LIVE },
+                0, nullptr, 0, 0, 0, 0, 0.0
+            };
+
+            float value;
+            for (uint i=0; i<fCachedParameters.numParams; ++i)
+            {
+                if (fPlugin.isParameterOutputOrTrigger(i))
+                {
+                    value = fPlugin.getParameterValue(i);
+
+                    if (d_isEqual(fCachedParameters.values[i], value))
+                        continue;
+
+                    fCachedParameters.values[i] = value;
+                    fCachedParameters.changed[i] = true;
+
+                    clapEvent.param_id = i;
+                    clapEvent.value = value;
+                    out->try_push(out, &clapEvent.header);
+                }
+            }
+        }
+
+       #if DISTRHO_PLUGIN_WANT_LATENCY
+        const bool active = fPlugin.isActive();
+        checkForLatencyChanges(active, !active);
+       #endif
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+
+   #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+    void addNoteEvent(const clap_event_note_t* const event, const bool isOn) noexcept
+    {
+        DISTRHO_SAFE_ASSERT_UINT_RETURN(event->port_index == 0, event->port_index,);
+
+        if (fMidiEventCount == kMaxMidiEvents)
+            return;
+
+        MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
+        midiEvent.frame = event->header.time;
+        midiEvent.size  = 3;
+        midiEvent.data[0] = (isOn ? 0x90 : 0x80) | (event->channel & 0x0F);
+        midiEvent.data[1] = std::max(0, std::min(127, static_cast<int>(event->key)));
+        midiEvent.data[2] = std::max(0, std::min(127, static_cast<int>(event->velocity * 127 + 0.5)));
+    }
+
+    void addMidiEvent(const clap_event_midi_t* const event) noexcept
+    {
+        DISTRHO_SAFE_ASSERT_UINT_RETURN(event->port_index == 0, event->port_index,);
+
+        if (fMidiEventCount == kMaxMidiEvents)
+            return;
+
+        MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
+        midiEvent.frame = event->header.time;
+        midiEvent.size  = 3;
+        std::memcpy(midiEvent.data, event->data, 3);
+    }
+   #endif
+
+    void setParameterValueFromEvent(const clap_event_param_value* const event)
+    {
+        fCachedParameters.values[event->param_id] = event->value;
+        fCachedParameters.changed[event->param_id] = true;
+        fPlugin.setParameterValue(event->param_id, event->value);
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // audio ports
+
+   #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+    template<bool isInput>
+    uint32_t getAudioPortCount() const noexcept
+    {
+        return (isInput ? fAudioInputBuses : fAudioOutputBuses).size();
+    }
+
+    template<bool isInput>
+    bool getAudioPortInfo(const uint32_t index, clap_audio_port_info_t* const info) const noexcept
+    {
+        const std::vector<BusInfo>& busInfos(isInput ? fAudioInputBuses : fAudioOutputBuses);
+        DISTRHO_SAFE_ASSERT_RETURN(index < busInfos.size(), false);
+
+        const BusInfo& busInfo(busInfos[index]);
+
+        info->id = busInfo.groupId;
+        d_strncpy(info->name, busInfo.name, CLAP_NAME_SIZE);
+
+        info->flags = busInfo.isMain ? CLAP_AUDIO_PORT_IS_MAIN : 0x0;
+        info->channel_count = busInfo.numChannels;
+
+        switch (busInfo.groupId)
+        {
+        case kPortGroupMono:
+            info->port_type = CLAP_PORT_MONO;
+            break;
+        case kPortGroupStereo:
+            info->port_type = CLAP_PORT_STEREO;
+            break;
+        default:
+            info->port_type = nullptr;
+            break;
+        }
+
+        info->in_place_pair = busInfo.hasPair ? busInfo.groupId : CLAP_INVALID_ID;
+        return true;
+    }
+   #endif
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // latency
+
+   #if DISTRHO_PLUGIN_WANT_LATENCY
+    uint32_t getLatency() const noexcept
+    {
+        return fPlugin.getLatency();
+    }
+
+    void checkForLatencyChanges(const bool isActive, const bool fromMainThread)
+    {
+        const uint32_t latency = fPlugin.getLatency();
+
+        if (fLastKnownLatency == latency)
+            return;
+
+        fLastKnownLatency = latency;
+
+        if (isActive)
+        {
+            fLatencyChanged = true;
+            fHost->request_restart(fHost);
+        }
+        else
+        {
+            // if this is main-thread we can report latency change directly
+            if (fromMainThread || (fHostExtensions.threadCheck != nullptr && fHostExtensions.threadCheck->is_main_thread(fHost)))
+            {
+                fLatencyChanged = false;
+                fHostExtensions.latency->changed(fHost);
+            }
+            // otherwise we need to request a main-thread callback
+            else
+            {
+                fLatencyChanged = true;
+                fHost->request_callback(fHost);
+            }
+        }
+    }
+
+    // called from main thread
+    void reportLatencyChangeIfNeeded()
+    {
+        if (fLatencyChanged)
+        {
+            fLatencyChanged = false;
+            fHostExtensions.latency->changed(fHost);
+        }
+    }
+   #endif
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // state
+
+    bool stateSave(const clap_ostream_t* const stream)
+    {
+        const uint32_t paramCount = fPlugin.getParameterCount();
+       #if DISTRHO_PLUGIN_WANT_STATE
+        const uint32_t stateCount = fPlugin.getStateCount();
+       #else
+        const uint32_t stateCount = 0;
+       #endif
+
+        if (stateCount == 0 && paramCount == 0)
+        {
+            char buffer = '\0';
+            return stream->write(stream, &buffer, 1) == 1;
+        }
+
+       #if DISTRHO_PLUGIN_WANT_FULL_STATE
+        // Update current state
+        for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
+        {
+            const String& key = cit->first;
+            fStateMap[key] = fPlugin.getStateValue(key);
+        }
+       #endif
+
+        String state;
+
+       #if DISTRHO_PLUGIN_WANT_PROGRAMS
+        {
+            String tmpStr("__dpf_program__\xff");
+            tmpStr += String(fCurrentProgram);
+            tmpStr += "\xff";
+
+            state += tmpStr;
+        }
+       #endif
+
+       #if DISTRHO_PLUGIN_WANT_STATE
+        if (stateCount != 0)
+        {
+            state += "__dpf_state_begin__\xff";
+
+            for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
+            {
+                const String& key   = cit->first;
+                const String& value = cit->second;
+
+                // join key and value
+                String tmpStr;
+                tmpStr  = key;
+                tmpStr += "\xff";
+                tmpStr += value;
+                tmpStr += "\xff";
+
+                state += tmpStr;
+            }
+
+            state += "__dpf_state_end__\xff";
+        }
+       #endif
+
+        if (paramCount != 0)
+        {
+            state += "__dpf_parameters_begin__\xff";
+
+            for (uint32_t i=0; i<paramCount; ++i)
+            {
+                if (fPlugin.isParameterOutputOrTrigger(i))
+                    continue;
+
+                // join key and value
+                String tmpStr;
+                tmpStr  = fPlugin.getParameterSymbol(i);
+                tmpStr += "\xff";
+                if (fPlugin.getParameterHints(i) & kParameterIsInteger)
+                    tmpStr += String(static_cast<int>(std::round(fPlugin.getParameterValue(i))));
+                else
+                    tmpStr += String(fPlugin.getParameterValue(i));
+                tmpStr += "\xff";
+
+                state += tmpStr;
+            }
+
+            state += "__dpf_parameters_end__\xff";
+        }
+
+        // terminator
+        state += "\xfe";
+
+        state.replace('\xff', '\0');
+
+        // now saving state, carefully until host written bytes matches full state size
+        const char* buffer = state.buffer();
+        const int32_t size = static_cast<int32_t>(state.length())+1;
+
+        for (int32_t wrtntotal = 0, wrtn; wrtntotal < size; wrtntotal += wrtn)
+        {
+            wrtn = stream->write(stream, buffer, size - wrtntotal);
+            DISTRHO_SAFE_ASSERT_INT_RETURN(wrtn > 0, wrtn, false);
+        }
+
+        return true;
+    }
+
+    bool stateLoad(const clap_istream_t* const stream)
+    {
+       #if DISTRHO_PLUGIN_HAS_UI
+        ClapUI* const ui = fUI.get();
+       #endif
+        String key, value;
+        bool hasValue = false;
+        bool fillingKey = true; // if filling key or value
+        char queryingType = 'i'; // can be 'n', 's' or 'p' (none, states, parameters)
+
+        char buffer[512], orig;
+        buffer[sizeof(buffer)-1] = '\xff';
+
+        for (int32_t terminated = 0, read; terminated == 0;)
+        {
+            read = stream->read(stream, buffer, sizeof(buffer)-1);
+            DISTRHO_SAFE_ASSERT_INT_RETURN(read >= 0, read, false);
+
+            if (read == 0)
+                return true;
+
+            for (int32_t i = 0; i < read; ++i)
+            {
+                // found terminator, stop here
+                if (buffer[i] == '\xfe')
+                {
+                    terminated = 1;
+                    break;
+                }
+
+                // store character at read position
+                orig = buffer[read];
+
+                // place null character to create valid string
+                buffer[read] = '\0';
+
+                // append to temporary vars
+                if (fillingKey)
+                {
+                    key += buffer + i;
+                }
+                else
+                {
+                    value += buffer + i;
+                    hasValue = true;
+                }
+
+                // increase buffer offset by length of string
+                i += std::strlen(buffer + i);
+
+                // restore read character
+                buffer[read] = orig;
+
+                // if buffer offset points to null, we found the end of a string, lets check
+                if (buffer[i] == '\0')
+                {
+                    // special keys
+                    if (key == "__dpf_state_begin__")
+                    {
+                        DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n',
+                                                       queryingType, false);
+                        queryingType = 's';
+                        key.clear();
+                        value.clear();
+                        hasValue = false;
+                        continue;
+                    }
+                    if (key == "__dpf_state_end__")
+                    {
+                        DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 's', queryingType, false);
+                        queryingType = 'n';
+                        key.clear();
+                        value.clear();
+                        hasValue = false;
+                        continue;
+                    }
+                    if (key == "__dpf_parameters_begin__")
+                    {
+                        DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n',
+                                                       queryingType, false);
+                        queryingType = 'p';
+                        key.clear();
+                        value.clear();
+                        hasValue = false;
+                        continue;
+                    }
+                    if (key == "__dpf_parameters_end__")
+                    {
+                        DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'p', queryingType, false);
+                        queryingType = 'x';
+                        key.clear();
+                        value.clear();
+                        hasValue = false;
+                        continue;
+                    }
+
+                    // no special key, swap between reading real key and value
+                    fillingKey = !fillingKey;
+
+                    // if there is no value yet keep reading until we have one
+                    if (! hasValue)
+                        continue;
+
+                    if (key == "__dpf_program__")
+                    {
+                        DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i', queryingType, false);
+                        queryingType = 'n';
+
+                        d_debug("found program '%s'", value.buffer());
+
+                      #if DISTRHO_PLUGIN_WANT_PROGRAMS
+                        const int program = std::atoi(value.buffer());
+                        DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0);
+
+                        fCurrentProgram = static_cast<uint32_t>(program);
+                        fPlugin.loadProgram(fCurrentProgram);
+
+                       #if DISTRHO_PLUGIN_HAS_UI
+                        if (ui != nullptr)
+                            ui->setProgramFromPlugin(fCurrentProgram);
+                       #endif
+                      #endif
+                    }
+                    else if (queryingType == 's')
+                    {
+                        d_debug("found state '%s' '%s'", key.buffer(), value.buffer());
+
+                       #if DISTRHO_PLUGIN_WANT_STATE
+                        if (fPlugin.wantStateKey(key))
+                        {
+                            fStateMap[key] = value;
+                            fPlugin.setState(key, value);
+
+                           #if DISTRHO_PLUGIN_HAS_UI
+                            if (ui != nullptr)
+                                ui->setStateFromPlugin(key, value);
+                           #endif
+                        }
+                       #endif
+                    }
+                    else if (queryingType == 'p')
+                    {
+                        d_debug("found parameter '%s' '%s'", key.buffer(), value.buffer());
+                        float fvalue;
+
+                        // find parameter with this symbol, and set its value
+                        for (uint32_t j=0; j<fCachedParameters.numParams; ++j)
+                        {
+                            if (fPlugin.isParameterOutputOrTrigger(j))
+                                continue;
+                            if (fPlugin.getParameterSymbol(j) != key)
+                                continue;
+
+                            if (fPlugin.getParameterHints(j) & kParameterIsInteger)
+                                fvalue = std::atoi(value.buffer());
+                            else
+                                fvalue = std::atof(value.buffer());
+
+                            fCachedParameters.values[j] = fvalue;
+                           #if DISTRHO_PLUGIN_HAS_UI
+                            if (ui != nullptr)
+                            {
+                                // UI parameter updates are handled outside the read loop (after host param restart)
+                                fCachedParameters.changed[j] = true;
+                            }
+                           #endif
+                            fPlugin.setParameterValue(j, fvalue);
+                            break;
+                        }
+                    }
+
+                    key.clear();
+                    value.clear();
+                    hasValue = false;
+                }
+            }
+        }
+
+        if (fHostExtensions.params != nullptr)
+            fHostExtensions.params->rescan(fHost, CLAP_PARAM_RESCAN_VALUES|CLAP_PARAM_RESCAN_TEXT);
+
+       #if DISTRHO_PLUGIN_WANT_LATENCY
+        checkForLatencyChanges(fPlugin.isActive(), true);
+        reportLatencyChangeIfNeeded();
+       #endif
+
+       #if DISTRHO_PLUGIN_HAS_UI
+        if (ui != nullptr)
+        {
+            for (uint32_t i=0; i<fCachedParameters.numParams; ++i)
+            {
+                if (fPlugin.isParameterOutputOrTrigger(i))
+                    continue;
+                fCachedParameters.changed[i] = false;
+                ui->setParameterValueFromPlugin(i, fCachedParameters.values[i]);
+            }
+        }
+       #endif
+
+        return true;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // gui
+
+   #if DISTRHO_PLUGIN_HAS_UI
+    bool createUI(const bool isFloating)
+    {
+        const clap_host_gui_t* const hostGui = getHostExtension<clap_host_gui_t>(CLAP_EXT_GUI);
+        DISTRHO_SAFE_ASSERT_RETURN(hostGui != nullptr, false);
+
+       #if DPF_CLAP_USING_HOST_TIMER
+        const clap_host_timer_support_t* const hostTimer = getHostExtension<clap_host_timer_support_t>(CLAP_EXT_TIMER_SUPPORT);
+        DISTRHO_SAFE_ASSERT_RETURN(hostTimer != nullptr, false);
+       #endif
+
+        fUI = new ClapUI(fPlugin, this, fHost, hostGui,
+                        #if DPF_CLAP_USING_HOST_TIMER
+                         hostTimer,
+                        #endif
+                         isFloating);
+        return true;
+    }
+
+    void destroyUI()
+    {
+        fUI = nullptr;
+    }
+
+    ClapUI* getUI() const noexcept
+    {
+        return fUI.get();
+    }
+   #endif
+
+   #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_STATE
+    void setStateFromUI(const char* const key, const char* const value) override
+    {
+        fPlugin.setState(key, value);
+
+        // check if we want to save this key
+        if (! fPlugin.wantStateKey(key))
+            return;
+
+        // check if key already exists
+        for (StringMap::iterator it=fStateMap.begin(), ite=fStateMap.end(); it != ite; ++it)
+        {
+            const String& dkey(it->first);
+
+            if (dkey == key)
+            {
+                it->second = value;
+                return;
+            }
+        }
+
+        d_stderr("Failed to find plugin state with key \"%s\"", key);
+    }
+   #endif
+
+    // ----------------------------------------------------------------------------------------------------------------
+
+private:
+    // Plugin and UI
+    PluginExporter fPlugin;
+   #if DISTRHO_PLUGIN_HAS_UI
+    ScopedPointer<ClapUI> fUI;
+   #endif
+
+    // CLAP stuff
+    const clap_host_t* const fHost;
+    const clap_output_events_t* fOutputEvents;
+
+   #if DISTRHO_PLUGIN_NUM_INPUTS != 0
+    const float* fAudioInputs[DISTRHO_PLUGIN_NUM_INPUTS];
+   #endif
+   #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+    float* fAudioOutputs[DISTRHO_PLUGIN_NUM_OUTPUTS];
+   #endif
+   #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+    bool fUsingCV;
+   #endif
+   #if DISTRHO_PLUGIN_WANT_LATENCY
+    bool fLatencyChanged;
+    uint32_t fLastKnownLatency;
+   #endif
+  #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+    uint32_t fMidiEventCount;
+    MidiEvent fMidiEvents[kMaxMidiEvents];
+   #if DISTRHO_PLUGIN_HAS_UI
+    RingBufferControl<SmallStackBuffer> fNotesRingBuffer;
+   #endif
+  #endif
+   #if DISTRHO_PLUGIN_WANT_TIMEPOS
+    TimePosition fTimePosition;
+   #endif
+
+    struct HostExtensions {
+        const clap_host_t* const host;
+        const clap_host_params_t* params;
+       #if DISTRHO_PLUGIN_WANT_LATENCY
+        const clap_host_latency_t* latency;
+        const clap_host_thread_check_t* threadCheck;
+       #endif
+
+        HostExtensions(const clap_host_t* const host)
+            : host(host),
+              params(nullptr)
+           #if DISTRHO_PLUGIN_WANT_LATENCY
+            , latency(nullptr)
+            , threadCheck(nullptr)
+           #endif
+        {}
+
+        bool init()
+        {
+            params = static_cast<const clap_host_params_t*>(host->get_extension(host, CLAP_EXT_PARAMS));
+           #if DISTRHO_PLUGIN_WANT_LATENCY
+            DISTRHO_SAFE_ASSERT_RETURN(host->request_restart != nullptr, false);
+            DISTRHO_SAFE_ASSERT_RETURN(host->request_callback != nullptr, false);
+            latency = static_cast<const clap_host_latency_t*>(host->get_extension(host, CLAP_EXT_LATENCY));
+            threadCheck = static_cast<const clap_host_thread_check_t*>(host->get_extension(host, CLAP_EXT_THREAD_CHECK));
+           #endif
+            return true;
+        }
+    } fHostExtensions;
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // helper functions for dealing with buses
+
+   #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+    struct BusInfo {
+        char name[CLAP_NAME_SIZE];
+        uint32_t numChannels;
+        bool hasPair;
+        bool isCV;
+        bool isMain;
+        uint32_t groupId;
+    };
+    std::vector<BusInfo> fAudioInputBuses, fAudioOutputBuses;
+
+    template<bool isInput>
+    void fillInBusInfoDetails()
+    {
+        constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS;
+        std::vector<BusInfo>& busInfos(isInput ? fAudioInputBuses : fAudioOutputBuses);
+
+        enum {
+            kPortTypeNull,
+            kPortTypeAudio,
+            kPortTypeSidechain,
+            kPortTypeCV,
+            kPortTypeGroup
+        } lastSeenPortType = kPortTypeNull;
+        uint32_t lastSeenGroupId = kPortGroupNone;
+        uint32_t nonGroupAudioId = 0;
+        uint32_t nonGroupSidechainId = 0x20000000;
+
+        for (uint32_t i=0; i<numPorts; ++i)
+        {
+            const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i));
+
+            if (port.groupId != kPortGroupNone)
+            {
+                if (lastSeenPortType != kPortTypeGroup || lastSeenGroupId != port.groupId)
+                {
+                    lastSeenPortType = kPortTypeGroup;
+                    lastSeenGroupId = port.groupId;
+
+                    BusInfo busInfo = {
+                        {}, 1, false, false,
+                        // if this is the first port, set it as main
+                        busInfos.empty(),
+                        // user given group id with extra safety offset
+                        port.groupId + 0x80000000
+                    };
+
+                    const PortGroupWithId& group(fPlugin.getPortGroupById(port.groupId));
+
+                    switch (port.groupId)
+                    {
+                    case kPortGroupStereo:
+                    case kPortGroupMono:
+                        if (busInfo.isMain)
+                        {
+                            d_strncpy(busInfo.name, isInput ? "Audio Input" : "Audio Output", CLAP_NAME_SIZE);
+                            break;
+                        }
+                    // fall-through
+                    default:
+                        if (group.name.isNotEmpty())
+                            d_strncpy(busInfo.name, group.name, CLAP_NAME_SIZE);
+                        else
+                            d_strncpy(busInfo.name, port.name, CLAP_NAME_SIZE);
+                        break;
+                    }
+
+                    busInfos.push_back(busInfo);
+                }
+                else
+                {
+                    ++busInfos.back().numChannels;
+                }
+            }
+            else if (port.hints & kAudioPortIsCV)
+            {
+                // TODO
+                lastSeenPortType = kPortTypeCV;
+                lastSeenGroupId = kPortGroupNone;
+                fUsingCV = true;
+            }
+            else if (port.hints & kAudioPortIsSidechain)
+            {
+                if (lastSeenPortType != kPortTypeSidechain)
+                {
+                    lastSeenPortType = kPortTypeSidechain;
+                    lastSeenGroupId = kPortGroupNone;
+
+                    BusInfo busInfo = {
+                        {}, 1, false, false,
+                        // not main
+                        false,
+                        // give unique id
+                        nonGroupSidechainId++
+                    };
+
+                    d_strncpy(busInfo.name, port.name, CLAP_NAME_SIZE);
+
+                    busInfos.push_back(busInfo);
+                }
+                else
+                {
+                    ++busInfos.back().numChannels;
+                }
+            }
+            else
+            {
+                if (lastSeenPortType != kPortTypeAudio)
+                {
+                    lastSeenPortType = kPortTypeAudio;
+                    lastSeenGroupId = kPortGroupNone;
+
+                    BusInfo busInfo = {
+                        {}, 1, false, false,
+                        // if this is the first port, set it as main
+                        busInfos.empty(),
+                        // give unique id
+                        nonGroupAudioId++
+                    };
+
+                    if (busInfo.isMain)
+                    {
+                        d_strncpy(busInfo.name, isInput ? "Audio Input" : "Audio Output", CLAP_NAME_SIZE);
+                    }
+                    else
+                    {
+                        d_strncpy(busInfo.name, port.name, CLAP_NAME_SIZE);
+                    }
+
+                    busInfos.push_back(busInfo);
+                }
+                else
+                {
+                    ++busInfos.back().numChannels;
+                }
+            }
+        }
+    }
+   #endif
+
+   #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+    void fillInBusInfoPairs()
+    {
+        const size_t numChannels = std::min(fAudioInputBuses.size(), fAudioOutputBuses.size());
+
+        for (size_t i=0; i<numChannels; ++i)
+        {
+            if (fAudioInputBuses[i].groupId != fAudioOutputBuses[i].groupId)
+                break;
+            if (fAudioInputBuses[i].numChannels != fAudioOutputBuses[i].numChannels)
+                break;
+            if (fAudioInputBuses[i].isMain != fAudioOutputBuses[i].isMain)
+                break;
+
+            fAudioInputBuses[i].hasPair = fAudioOutputBuses[i].hasPair = true;
+        }
+    }
+   #endif
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // DPF callbacks
+
+   #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+    bool writeMidi(const MidiEvent& midiEvent)
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fOutputEvents != nullptr, false);
+
+        if (midiEvent.size > 3)
+            return true;
+
+        const clap_event_midi clapEvent = {
+            { sizeof(clap_event_midi), midiEvent.frame, 0, CLAP_EVENT_MIDI, CLAP_EVENT_IS_LIVE },
+            0, { midiEvent.data[0],
+                 static_cast<uint8_t>(midiEvent.size >= 2 ? midiEvent.data[1] : 0),
+                 static_cast<uint8_t>(midiEvent.size >= 3 ? midiEvent.data[2] : 0) }
+        };
+        return fOutputEvents->try_push(fOutputEvents, &clapEvent.header);
+    }
+
+    static bool writeMidiCallback(void* const ptr, const MidiEvent& midiEvent)
+    {
+        return static_cast<PluginCLAP*>(ptr)->writeMidi(midiEvent);
+    }
+   #endif
+
+   #if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
+    bool requestParameterValueChange(uint32_t, float)
+    {
+        return true;
+    }
+
+    static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value)
+    {
+        return static_cast<PluginCLAP*>(ptr)->requestParameterValueChange(index, value);
+    }
+   #endif
+
+   #if DISTRHO_PLUGIN_WANT_STATE
+    bool updateState(const char*, const char*)
+    {
+        return true;
+    }
+
+    static bool updateStateValueCallback(void* const ptr, const char* const key, const char* const value)
+    {
+        return static_cast<PluginCLAP*>(ptr)->updateState(key, value);
+    }
+   #endif
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+static ScopedPointer<PluginExporter> sPlugin;
+
+// --------------------------------------------------------------------------------------------------------------------
+// plugin gui
+
+#if DISTRHO_PLUGIN_HAS_UI
+
+static const char* const kSupportedAPIs[] = {
+#if defined(DISTRHO_OS_WINDOWS)
+    CLAP_WINDOW_API_WIN32,
+#elif defined(DISTRHO_OS_MAC)
+    CLAP_WINDOW_API_COCOA,
+#else
+    CLAP_WINDOW_API_X11,
+#endif
+};
+
+// TODO DPF external UI
+static bool CLAP_ABI clap_gui_is_api_supported(const clap_plugin_t*, const char* const api, bool)
+{
+    for (size_t i=0; i<ARRAY_SIZE(kSupportedAPIs); ++i)
+    {
+        if (std::strcmp(kSupportedAPIs[i], api) == 0)
+            return true;
+    }
+
+    return false;
+}
+
+// TODO DPF external UI
+static bool CLAP_ABI clap_gui_get_preferred_api(const clap_plugin_t*, const char** const api, bool* const is_floating)
+{
+    *api = kSupportedAPIs[0];
+    *is_floating = false;
+    return true;
+}
+
+static bool CLAP_ABI clap_gui_create(const clap_plugin_t* const plugin, const char* const api, const bool is_floating)
+{
+    for (size_t i=0; i<ARRAY_SIZE(kSupportedAPIs); ++i)
+    {
+        if (std::strcmp(kSupportedAPIs[i], api) == 0)
+        {
+            PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+            return instance->createUI(is_floating);
+        }
+    }
+
+    return false;
+}
+
+static void CLAP_ABI clap_gui_destroy(const clap_plugin_t* const plugin)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    instance->destroyUI();
+}
+
+static bool CLAP_ABI clap_gui_set_scale(const clap_plugin_t* const plugin, const double scale)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
+   #ifndef DISTRHO_OS_MAC
+    return gui->setScaleFactor(scale);
+   #else
+    return true;
+    // unused
+    (void)scale;
+   #endif
+}
+
+static bool CLAP_ABI clap_gui_get_size(const clap_plugin_t* const plugin, uint32_t* const width, uint32_t* const height)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
+    return gui->getSize(width, height);
+}
+
+static bool CLAP_ABI clap_gui_can_resize(const clap_plugin_t* const plugin)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
+    return gui->canResize();
+}
+
+static bool CLAP_ABI clap_gui_get_resize_hints(const clap_plugin_t* const plugin, clap_gui_resize_hints_t* const hints)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
+    return gui->getResizeHints(hints);
+}
+
+static bool CLAP_ABI clap_gui_adjust_size(const clap_plugin_t* const plugin, uint32_t* const width, uint32_t* const height)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
+    return gui->adjustSize(width, height);
+}
+
+static bool CLAP_ABI clap_gui_set_size(const clap_plugin_t* const plugin, const uint32_t width, const uint32_t height)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
+    return gui->setSizeFromHost(width, height);
+}
+
+static bool CLAP_ABI clap_gui_set_parent(const clap_plugin_t* const plugin, const clap_window_t* const window)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
+    return gui->setParent(window);
+}
+
+static bool CLAP_ABI clap_gui_set_transient(const clap_plugin_t* const plugin, const clap_window_t* const window)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
+    return gui->setTransient(window);
+}
+
+static void CLAP_ABI clap_gui_suggest_title(const clap_plugin_t* const plugin, const char* const title)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr,);
+    return gui->suggestTitle(title);
+}
+
+static bool CLAP_ABI clap_gui_show(const clap_plugin_t* const plugin)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
+    return gui->show();
+}
+
+static bool CLAP_ABI clap_gui_hide(const clap_plugin_t* const plugin)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
+    return gui->hide();
+}
+
+static const clap_plugin_gui_t clap_plugin_gui = {
+    clap_gui_is_api_supported,
+    clap_gui_get_preferred_api,
+    clap_gui_create,
+    clap_gui_destroy,
+    clap_gui_set_scale,
+    clap_gui_get_size,
+    clap_gui_can_resize,
+    clap_gui_get_resize_hints,
+    clap_gui_adjust_size,
+    clap_gui_set_size,
+    clap_gui_set_parent,
+    clap_gui_set_transient,
+    clap_gui_suggest_title,
+    clap_gui_show,
+    clap_gui_hide
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// plugin timer
+
+#if DPF_CLAP_USING_HOST_TIMER
+static void CLAP_ABI clap_plugin_on_timer(const clap_plugin_t* const plugin, clap_id)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    ClapUI* const gui = instance->getUI();
+    DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr,);
+    return gui->idleCallback();
+}
+
+static const clap_plugin_timer_support_t clap_timer = {
+    clap_plugin_on_timer
+};
+#endif
+
+#endif // DISTRHO_PLUGIN_HAS_UI
+
+// --------------------------------------------------------------------------------------------------------------------
+// plugin audio ports
+
+#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+static uint32_t CLAP_ABI clap_plugin_audio_ports_count(const clap_plugin_t* const plugin, const bool is_input)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return is_input ? instance->getAudioPortCount<true>()
+                    : instance->getAudioPortCount<false>();
+}
+
+static bool CLAP_ABI clap_plugin_audio_ports_get(const clap_plugin_t* const plugin,
+                                        const uint32_t index,
+                                        const bool is_input,
+                                        clap_audio_port_info_t* const info)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return is_input ? instance->getAudioPortInfo<true>(index, info)
+                    : instance->getAudioPortInfo<false>(index, info);
+}
+
+static const clap_plugin_audio_ports_t clap_plugin_audio_ports = {
+    clap_plugin_audio_ports_count,
+    clap_plugin_audio_ports_get
+};
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+// plugin note ports
+
+#if DISTRHO_PLUGIN_WANT_MIDI_INPUT+DISTRHO_PLUGIN_WANT_MIDI_OUTPUT != 0
+static uint32_t CLAP_ABI clap_plugin_note_ports_count(const clap_plugin_t*, const bool is_input)
+{
+    return (is_input ? DISTRHO_PLUGIN_WANT_MIDI_INPUT : DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) != 0 ? 1 : 0;
+}
+
+static bool CLAP_ABI clap_plugin_note_ports_get(const clap_plugin_t*, uint32_t,
+                                                const bool is_input, clap_note_port_info_t* const info)
+{
+    if (is_input)
+    {
+       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+        info->id = 0;
+        info->supported_dialects = CLAP_NOTE_DIALECT_MIDI;
+        info->preferred_dialect = CLAP_NOTE_DIALECT_MIDI;
+        std::strcpy(info->name, "Event/MIDI Input");
+        return true;
+       #endif
+    }
+    else
+    {
+       #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+        info->id = 0;
+        info->supported_dialects = CLAP_NOTE_DIALECT_MIDI;
+        info->preferred_dialect = CLAP_NOTE_DIALECT_MIDI;
+        std::strcpy(info->name, "Event/MIDI Output");
+        return true;
+       #endif
+    }
+
+    return false;
+}
+
+static const clap_plugin_note_ports_t clap_plugin_note_ports = {
+    clap_plugin_note_ports_count,
+    clap_plugin_note_ports_get
+};
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+// plugin parameters
+
+static uint32_t CLAP_ABI clap_plugin_params_count(const clap_plugin_t* const plugin)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->getParameterCount();
+}
+
+static bool CLAP_ABI clap_plugin_params_get_info(const clap_plugin_t* const plugin, const uint32_t index, clap_param_info_t* const info)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->getParameterInfo(index, info);
+}
+
+static bool CLAP_ABI clap_plugin_params_get_value(const clap_plugin_t* const plugin, const clap_id param_id, double* const value)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->getParameterValue(param_id, value);
+}
+
+static bool CLAP_ABI clap_plugin_params_value_to_text(const clap_plugin_t* plugin, const clap_id param_id, const double value, char* const display, const uint32_t size)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->getParameterStringForValue(param_id, value, display, size);
+}
+
+static bool CLAP_ABI clap_plugin_params_text_to_value(const clap_plugin_t* plugin, const clap_id param_id, const char* const display, double* const value)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->getParameterValueForString(param_id, display, value);
+}
+
+static void CLAP_ABI clap_plugin_params_flush(const clap_plugin_t* plugin, const clap_input_events_t* in, const clap_output_events_t* out)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->flushParameters(in, out, 0);
+}
+
+static const clap_plugin_params_t clap_plugin_params = {
+    clap_plugin_params_count,
+    clap_plugin_params_get_info,
+    clap_plugin_params_get_value,
+    clap_plugin_params_value_to_text,
+    clap_plugin_params_text_to_value,
+    clap_plugin_params_flush
+};
+
+#if DISTRHO_PLUGIN_WANT_LATENCY
+// --------------------------------------------------------------------------------------------------------------------
+// plugin latency
+
+static uint32_t CLAP_ABI clap_plugin_latency_get(const clap_plugin_t* const plugin)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->getLatency();
+}
+
+static const clap_plugin_latency_t clap_plugin_latency = {
+    clap_plugin_latency_get
+};
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+// plugin state
+
+static bool CLAP_ABI clap_plugin_state_save(const clap_plugin_t* const plugin, const clap_ostream_t* const stream)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->stateSave(stream);
+}
+
+static bool CLAP_ABI clap_plugin_state_load(const clap_plugin_t* const plugin, const clap_istream_t* const stream)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->stateLoad(stream);
+}
+
+static const clap_plugin_state_t clap_plugin_state = {
+    clap_plugin_state_save,
+    clap_plugin_state_load
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// plugin
+
+static bool CLAP_ABI clap_plugin_init(const clap_plugin_t* const plugin)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->init();
+}
+
+static void CLAP_ABI clap_plugin_destroy(const clap_plugin_t* const plugin)
+{
+    delete static_cast<PluginCLAP*>(plugin->plugin_data);
+    std::free(const_cast<clap_plugin_t*>(plugin));
+}
+
+static bool CLAP_ABI clap_plugin_activate(const clap_plugin_t* const plugin,
+                                          const double sample_rate,
+                                          uint32_t,
+                                          const uint32_t max_frames_count)
+{
+    d_nextBufferSize = max_frames_count;
+    d_nextSampleRate = sample_rate;
+
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    instance->activate(sample_rate, max_frames_count);
+    return true;
+}
+
+static void CLAP_ABI clap_plugin_deactivate(const clap_plugin_t* const plugin)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    instance->deactivate();
+}
+
+static bool CLAP_ABI clap_plugin_start_processing(const clap_plugin_t*)
+{
+    // nothing to do
+    return true;
+}
+
+static void CLAP_ABI clap_plugin_stop_processing(const clap_plugin_t*)
+{
+    // nothing to do
+}
+
+static void CLAP_ABI clap_plugin_reset(const clap_plugin_t*)
+{
+    // nothing to do
+}
+
+static clap_process_status CLAP_ABI clap_plugin_process(const clap_plugin_t* const plugin, const clap_process_t* const process)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    return instance->process(process) ? CLAP_PROCESS_CONTINUE : CLAP_PROCESS_ERROR;
+}
+
+static const void* CLAP_ABI clap_plugin_get_extension(const clap_plugin_t*, const char* const id)
+{
+    if (std::strcmp(id, CLAP_EXT_PARAMS) == 0)
+        return &clap_plugin_params;
+    if (std::strcmp(id, CLAP_EXT_STATE) == 0)
+        return &clap_plugin_state;
+   #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+    if (std::strcmp(id, CLAP_EXT_AUDIO_PORTS) == 0)
+        return &clap_plugin_audio_ports;
+   #endif
+   #if DISTRHO_PLUGIN_WANT_MIDI_INPUT+DISTRHO_PLUGIN_WANT_MIDI_OUTPUT != 0
+    if (std::strcmp(id, CLAP_EXT_NOTE_PORTS) == 0)
+        return &clap_plugin_note_ports;
+   #endif
+   #if DISTRHO_PLUGIN_WANT_LATENCY
+    if (std::strcmp(id, CLAP_EXT_LATENCY) == 0)
+        return &clap_plugin_latency;
+   #endif
+  #if DISTRHO_PLUGIN_HAS_UI
+    if (std::strcmp(id, CLAP_EXT_GUI) == 0)
+        return &clap_plugin_gui;
+   #if DPF_CLAP_USING_HOST_TIMER
+    if (std::strcmp(id, CLAP_EXT_TIMER_SUPPORT) == 0)
+        return &clap_timer;
+   #endif
+  #endif
+    return nullptr;
+}
+
+static void CLAP_ABI clap_plugin_on_main_thread(const clap_plugin_t* const plugin)
+{
+    PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+    instance->onMainThread();
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// plugin factory
+
+static uint32_t CLAP_ABI clap_get_plugin_count(const clap_plugin_factory_t*)
+{
+    return 1;
+}
+
+static const clap_plugin_descriptor_t* CLAP_ABI clap_get_plugin_descriptor(const clap_plugin_factory_t*,
+                                                                           const uint32_t index)
+{
+    DISTRHO_SAFE_ASSERT_UINT_RETURN(index == 0, index, nullptr);
+
+    static const char* features[] = {
+       #ifdef DISTRHO_PLUGIN_CLAP_FEATURES
+        DISTRHO_PLUGIN_CLAP_FEATURES,
+       #elif DISTRHO_PLUGIN_IS_SYNTH
+        "instrument",
+       #endif
+        nullptr
+    };
+
+    static const clap_plugin_descriptor_t descriptor = {
+        CLAP_VERSION,
+        DISTRHO_PLUGIN_CLAP_ID,
+        sPlugin->getName(),
+        sPlugin->getMaker(),
+        // TODO url
+        "",
+        // TODO manual url
+        "",
+        // TODO support url
+        "",
+        // TODO version string
+        "",
+        sPlugin->getDescription(),
+        features
+    };
+
+    return &descriptor;
+}
+
+static const clap_plugin_t* CLAP_ABI clap_create_plugin(const clap_plugin_factory_t* const factory,
+                                                        const clap_host_t* const host,
+                                                        const char*)
+{
+    clap_plugin_t* const pluginptr = static_cast<clap_plugin_t*>(std::malloc(sizeof(clap_plugin_t)));
+    DISTRHO_SAFE_ASSERT_RETURN(pluginptr != nullptr, nullptr);
+
+    // default early values
+    if (d_nextBufferSize == 0)
+        d_nextBufferSize = 1024;
+    if (d_nextSampleRate <= 0.0)
+        d_nextSampleRate = 44100.0;
+
+    d_nextCanRequestParameterValueChanges = true;
+
+    const clap_plugin_t plugin = {
+        clap_get_plugin_descriptor(factory, 0),
+        new PluginCLAP(host),
+        clap_plugin_init,
+        clap_plugin_destroy,
+        clap_plugin_activate,
+        clap_plugin_deactivate,
+        clap_plugin_start_processing,
+        clap_plugin_stop_processing,
+        clap_plugin_reset,
+        clap_plugin_process,
+        clap_plugin_get_extension,
+        clap_plugin_on_main_thread
+    };
+
+    std::memcpy(pluginptr, &plugin, sizeof(clap_plugin_t));
+    return pluginptr;
+}
+
+static const clap_plugin_factory_t clap_plugin_factory = {
+    clap_get_plugin_count,
+    clap_get_plugin_descriptor,
+    clap_create_plugin
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// plugin entry
+
+static bool CLAP_ABI clap_plugin_entry_init(const char* const plugin_path)
+{
+    static String bundlePath;
+    bundlePath = plugin_path;
+    d_nextBundlePath = bundlePath.buffer();
+
+    // init dummy plugin
+    if (sPlugin == nullptr)
+    {
+        // set valid but dummy values
+        d_nextBufferSize = 512;
+        d_nextSampleRate = 44100.0;
+        d_nextPluginIsDummy = true;
+        d_nextCanRequestParameterValueChanges = true;
+
+        // Create dummy plugin to get data from
+        sPlugin = new PluginExporter(nullptr, nullptr, nullptr, nullptr);
+
+        // unset
+        d_nextBufferSize = 0;
+        d_nextSampleRate = 0.0;
+        d_nextPluginIsDummy = false;
+        d_nextCanRequestParameterValueChanges = false;
+    }
+
+    return true;
+}
+
+static void CLAP_ABI clap_plugin_entry_deinit(void)
+{
+    sPlugin = nullptr;
+}
+
+static const void* CLAP_ABI clap_plugin_entry_get_factory(const char* const factory_id)
+{
+    if (std::strcmp(factory_id, CLAP_PLUGIN_FACTORY_ID) == 0)
+        return &clap_plugin_factory;
+    return nullptr;
+}
+
+static const clap_plugin_entry_t clap_plugin_entry = {
+    CLAP_VERSION,
+    clap_plugin_entry_init,
+    clap_plugin_entry_deinit,
+    clap_plugin_entry_get_factory
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+END_NAMESPACE_DISTRHO
+
+// --------------------------------------------------------------------------------------------------------------------
+
+DISTRHO_PLUGIN_EXPORT
+const clap_plugin_entry_t clap_entry = DISTRHO_NAMESPACE::clap_plugin_entry;
+
+// --------------------------------------------------------------------------------------------------------------------