commit 2351dbc3db370ded800b244a293660ca8caa9012
parent efeb647436269f47a07209f0b34282932beacecd
Author: falkTX <falktx@falktx.com>
Date: Tue, 6 Sep 2022 11:38:53 +0100
Begin CLAP parameters, add UI stubs
Diffstat:
3 files changed, 1096 insertions(+), 4 deletions(-)
diff --git a/distrho/src/DistrhoPluginCLAP.cpp b/distrho/src/DistrhoPluginCLAP.cpp
@@ -14,18 +14,132 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include "DistrhoPluginInfo.h"
#include "DistrhoPluginInternal.hpp"
#include "extra/ScopedPointer.hpp"
+#undef DISTRHO_PLUGIN_HAS_UI
+#define DISTRHO_PLUGIN_HAS_UI 0
+
+#if DISTRHO_PLUGIN_HAS_UI
+# include "DistrhoUIInternal.hpp"
+#endif
+
#include "clap/entry.h"
#include "clap/plugin-factory.h"
#include "clap/ext/audio-ports.h"
+#include "clap/ext/gui.h"
+#include "clap/ext/params.h"
START_NAMESPACE_DISTRHO
+#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:
+ ClapUI(const intptr_t winId,
+ const double sampleRate,
+ const char* const bundlePath,
+ void* const dspPtr,
+ const float scaleFactor)
+ : fUI(this, winId, sampleRate,
+ editParameterCallback,
+ setParameterCallback,
+ setStateCallback,
+ sendNoteCallback,
+ setSizeCallback,
+ fileRequestCallback,
+ bundlePath, dspPtr, scaleFactor)
+ {
+ }
+
+ // ----------------------------------------------------------------------------------------------------------------
+
+private:
+ // Stub stuff here
+
+ // Plugin UI (after Stub stuff so the UI can call into us during its constructor)
+ UIExporter fUI;
+
+ // ----------------------------------------------------------------------------------------------------------------
+ // DPF callbacks
+
+ void editParameter(uint32_t, bool) const
+ {
+ }
+
+ static void editParameterCallback(void* const ptr, const uint32_t rindex, const bool started)
+ {
+ static_cast<ClapUI*>(ptr)->editParameter(rindex, started);
+ }
+
+ void setParameterValue(uint32_t, float)
+ {
+ }
+
+ static void setParameterCallback(void* const ptr, const uint32_t rindex, const float value)
+ {
+ static_cast<ClapUI*>(ptr)->setParameterValue(rindex, value);
+ }
+
+ void setSize(uint, uint)
+ {
+ }
+
+ static void setSizeCallback(void* const ptr, const uint width, const uint height)
+ {
+ static_cast<ClapUI*>(ptr)->setSize(width, height);
+ }
+
+ #if DISTRHO_PLUGIN_WANT_STATE
+ void setState(const char*, const char*)
+ {
+ }
+
+ 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)
+ {
+ }
+
+ 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
+
+ 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
@@ -33,7 +147,7 @@ static constexpr const writeMidiFunc writeMidiCallback = nullptr;
static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr;
#endif
#if ! DISTRHO_PLUGIN_WANT_STATE
-static const updateStateValueFunc updateStateValueCallback = nullptr;
+static constexpr const updateStateValueFunc updateStateValueCallback = nullptr;
#endif
// --------------------------------------------------------------------------------------------------------------------
@@ -54,6 +168,9 @@ public:
{
}
+ // ----------------------------------------------------------------------------------------------------------------
+ // core
+
bool init()
{
if (!clap_version_is_compatible(fHost->clap_version))
@@ -161,7 +278,12 @@ public:
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:
@@ -205,11 +327,306 @@ public:
}
// ----------------------------------------------------------------------------------------------------------------
+ // 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 & kParameterIsAutomatable)
+ info->flags |= CLAP_PARAM_IS_AUTOMATABLE;
+ if (hints & (kParameterIsBoolean|kParameterIsInteger))
+ info->flags |= CLAP_PARAM_IS_STEPPED;
+ if (hints & kParameterIsOutput)
+ info->flags |= CLAP_PARAM_IS_READONLY;
+
+ DISTRHO_NAMESPACE::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;
+ }
+
+ DISTRHO_NAMESPACE::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
+ {
+ const float plain = fPlugin.getParameterValue(param_id);
+
+ if (fPlugin.isParameterInteger(param_id))
+ {
+ *value = plain;
+ return true;
+ }
+
+ *value = fPlugin.getParameterRanges(param_id).getNormalizedValue(static_cast<double>(plain));
+ return true;
+ }
+
+ bool getParameterStringForValue(const clap_id param_id, const 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);
+
+ double plain;
+ if (hints & kParameterIsInteger)
+ {
+ plain = value;
+ }
+ else if (hints & kParameterIsBoolean)
+ {
+ const float midRange = ranges.min + (ranges.max - ranges.min) * 0.5f;
+ plain = value > midRange ? ranges.max : ranges.min;
+ }
+ else
+ {
+ plain = ranges.getUnnormalizedValue(value);
+ }
+
+ for (uint32_t i=0; i < enumValues.count; ++i)
+ {
+ if (d_isEqual(static_cast<double>(enumValues.values[i].value), plain))
+ {
+ DISTRHO_NAMESPACE::strncpy(display, enumValues.values[i].label, size);
+ return true;
+ }
+ }
+
+ if (hints & kParameterIsInteger)
+ snprintf_i32(display, plain, size);
+ else
+ snprintf_f32(display, plain, 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 ParameterRanges& ranges(fPlugin.getParameterRanges(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 = isInteger
+ ? enumValues.values[i].value
+ : ranges.getNormalizedValue(enumValues.values[i].value);
+ return true;
+ }
+ }
+
+ double plain;
+ if (isInteger)
+ plain = std::atoi(display);
+ else
+ plain = std::atof(display);
+
+ *value = ranges.getNormalizedValue(plain);
+ return true;
+ }
+
+ void setParameterValueFromEvent(const clap_event_param_value* const param)
+ {
+ const double plain = fPlugin.isParameterInteger(param->param_id)
+ ? param->value
+ : fPlugin.getParameterRanges(param->param_id).getFixedAndNormalizedValue(param->value);
+
+ fPlugin.setParameterValue(param->param_id, plain);
+ }
+
+ void flushParameters(const clap_input_events_t* const in, const clap_output_events_t* /* const out */)
+ {
+ if (const uint32_t len = in->size(in))
+ {
+ 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)));
+ }
+ }
+ }
+
+ // ----------------------------------------------------------------------------------------------------------------
+ // gui
+
+ #if DISTRHO_PLUGIN_HAS_UI
+ bool createUI(const bool floating)
+ {
+ fUI = new ClapUI();
+ return true;
+ }
+
+ void destroyUI()
+ {
+ fUI = nullptr;
+ }
+
+ bool setScale(const double scale)
+ {
+ fUI.scale = scale;
+
+ if (ClapUI* const ui = fUI.instance)
+ ui->notifyScaleFactorChange(scale);
+
+ return true;
+ }
+
+ bool getSize(uint32_t* const width, uint32_t* const height) const
+ {
+ if (ClapUI* const ui = fUI.instance)
+ {
+ *width = ui->getWidth();
+ *height = ui->getHeight();
+ }
+ else
+ {
+ // TODO
+ }
+ return true;
+ }
+
+ bool canResize() const
+ {
+ if (ClapUI* const ui = fUI.instance)
+ return ui->canResize();
+
+ return DISTRHO_PLUGIN_IS_UI_USER_RESIZABLE != 0;
+ }
+
+ bool getResizeHints(clap_gui_resize_hints_t* const hints) const
+ {
+ // TODO
+ return true;
+ }
+
+ bool adjustSize(uint32_t* const width, uint32_t* const height) const
+ {
+ // TODO
+ return true;
+ }
+
+ bool setSize(const uint32_t width, const uint32_t height)
+ {
+ // TODO
+ return true;
+ }
+
+ bool setParent(const clap_window_t* const window)
+ {
+ // TODO
+
+ if (ClapUI* const ui = fUI.instance)
+ {
+ // TODO
+ }
+
+ return true;
+ }
+
+ bool setTransient(const clap_window_t* const window)
+ {
+ fUI.transient = window;
+
+ if (ClapUI* const ui = fUI.instance)
+ ui->setTransient(window);
+ }
+
+ void suggestTitle(const char* const title)
+ {
+ fUI.title = window;
+
+ if (ClapUI* const ui = fUI.instance)
+ ui->setTitle(title);
+ }
+
+ bool show()
+ {
+ if (fUI.instance == nullptr)
+ fUI.instance = new ClapUI();
+
+ fUI.instance->show();
+ return true;
+ }
+
+ bool hide()
+ {
+ if (ClapUI* const ui = fUI.instance)
+ ui->hide();
+ return true;
+ }
+ #endif
+
+ // ----------------------------------------------------------------------------------------------------------------
private:
// Plugin
PluginExporter fPlugin;
+ #if DISTRHO_PLUGIN_HAS_UI
+ // UI
+ struct UI {
+ double scale;
+ uint32_t hostSetWidth, hostSetHeight;
+ clap_window_t parent, transient;
+ String title;
+ ScopedPointer<ClapUI> instance;
+
+ UI()
+ : scale(0.0),
+ hostSetWidth(0),
+ hostSetHeight(0),
+ parent(0),
+ transient(0),
+ title(),
+ instance() {}
+ } fUI;
+ #endif
+
// CLAP stuff
const clap_host_t* const fHost;
const clap_output_events_t* fOutputEvents;
@@ -262,6 +679,111 @@ private:
static ScopedPointer<PluginExporter> sPlugin;
// --------------------------------------------------------------------------------------------------------------------
+// plugin gui
+
+#if DISTRHO_PLUGIN_HAS_UI
+static bool clap_gui_is_api_supported(const clap_plugin_t*, const char* const api, const bool is_floating)
+{
+ return true;
+}
+
+static bool clap_gui_get_preferred_api(const clap_plugin_t*, const char** const api, bool* const is_floating)
+{
+ return true;
+}
+
+static bool clap_gui_create(const clap_plugin_t* const plugin, const char* const api, const bool is_floating)
+{
+ PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+ return instance->createUI();
+}
+
+static void clap_gui_destroy(const clap_plugin_t* const plugin)
+{
+ PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+ instance->destroyUI();
+}
+
+static bool clap_gui_set_scale(const clap_plugin_t* const plugin, const double scale)
+{
+ PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+ UICLAP* const gui = instance->getUI();
+ return gui->setScale(scale);
+}
+
+static bool 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);
+ UICLAP* const gui = instance->getUI();
+ return gui->getSize(width, height);
+}
+
+static bool clap_gui_can_resize(const clap_plugin_t* const plugin)
+{
+ PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+ UICLAP* const gui = instance->getUI();
+ return gui->canResize();
+}
+
+static bool clap_gui_get_resize_hints(const clap_plugin_t* const plugin, clap_gui_resize_hints_t* const hints)
+{
+ return true;
+}
+
+static bool clap_gui_adjust_size(const clap_plugin_t* const plugin, uint32_t* const width, uint32_t* const height)
+{
+ return true;
+}
+
+static bool clap_gui_set_size(const clap_plugin_t* const plugin, const uint32_t width, const uint32_t height)
+{
+ return true;
+}
+
+static bool clap_gui_set_parent(const clap_plugin_t* const plugin, const clap_window_t* const window)
+{
+ return true;
+}
+
+static bool clap_gui_set_transient(const clap_plugin_t* const plugin, const clap_window_t* const window)
+{
+ return true;
+}
+
+static void clap_gui_suggest_title(const clap_plugin_t* const plugin, const char* const title)
+{
+}
+
+static bool clap_gui_show(const clap_plugin_t* const plugin)
+{
+ return true;
+}
+
+static bool clap_gui_hide(const clap_plugin_t* const plugin)
+{
+ return true;
+}
+
+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
+};
+#endif // DISTRHO_PLUGIN_HAS_UI
+
+// --------------------------------------------------------------------------------------------------------------------
// plugin audio ports
static uint32_t clap_plugin_audio_ports_count(const clap_plugin_t*, const bool is_input)
@@ -269,7 +791,7 @@ static uint32_t clap_plugin_audio_ports_count(const clap_plugin_t*, const bool i
return (is_input ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS) != 0 ? 1 : 0;
}
-static bool clap_plugin_audio_ports_get(const clap_plugin_t*,
+static bool 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)
@@ -277,11 +799,13 @@ static bool clap_plugin_audio_ports_get(const clap_plugin_t*,
const uint32_t maxPortCount = is_input ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS;
DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < maxPortCount, index, maxPortCount, false);
+ // PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
+
// TODO use groups
AudioPortWithBusId& audioPort(sPlugin->getAudioPort(is_input, index));
info->id = index;
- std::strncpy(info->name, audioPort.name, CLAP_NAME_SIZE-1);
+ DISTRHO_NAMESPACE::strncpy(info->name, audioPort.name, CLAP_NAME_SIZE);
// TODO bus stuff
info->flags = CLAP_AUDIO_PORT_IS_MAIN;
@@ -302,6 +826,54 @@ static const clap_plugin_audio_ports_t clap_plugin_audio_ports = {
};
// --------------------------------------------------------------------------------------------------------------------
+// plugin parameters
+
+static uint32_t 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_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_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_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_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_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);
+}
+
+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
+};
+
+// --------------------------------------------------------------------------------------------------------------------
// plugin
static bool clap_plugin_init(const clap_plugin_t* const plugin)
@@ -361,6 +933,12 @@ static const void* clap_plugin_get_extension(const clap_plugin_t*, const char* c
{
if (std::strcmp(id, CLAP_EXT_AUDIO_PORTS) == 0)
return &clap_plugin_audio_ports;
+ if (std::strcmp(id, CLAP_EXT_PARAMS) == 0)
+ return &clap_plugin_params;
+ #if DISTRHO_PLUGIN_HAS_UI
+ if (std::strcmp(id, CLAP_EXT_GUI) == 0)
+ return &clap_plugin_gui;
+ #endif
return nullptr;
}
diff --git a/distrho/src/clap/ext/gui.h b/distrho/src/clap/ext/gui.h
@@ -0,0 +1,218 @@
+#pragma once
+
+#include "../plugin.h"
+
+/// @page GUI
+///
+/// This extension defines how the plugin will present its GUI.
+///
+/// There are two approaches:
+/// 1. the plugin creates a window and embeds it into the host's window
+/// 2. the plugin creates a floating window
+///
+/// Embedding the window gives more control to the host, and feels more integrated.
+/// Floating window are sometimes the only option due to technical limitations.
+///
+/// Showing the GUI works as follow:
+/// 1. clap_plugin_gui->is_api_supported(), check what can work
+/// 2. clap_plugin_gui->create(), allocates gui resources
+/// 3. if the plugin window is floating
+/// 4. -> clap_plugin_gui->set_transient()
+/// 5. -> clap_plugin_gui->suggest_title()
+/// 6. else
+/// 7. -> clap_plugin_gui->set_scale()
+/// 8. -> clap_plugin_gui->can_resize()
+/// 9. -> if resizable and has known size from previous session, clap_plugin_gui->set_size()
+/// 10. -> else clap_plugin_gui->get_size(), gets initial size
+/// 11. -> clap_plugin_gui->set_parent()
+/// 12. clap_plugin_gui->show()
+/// 13. clap_plugin_gui->hide()/show() ...
+/// 14. clap_plugin_gui->destroy() when done with the gui
+///
+/// Resizing the window (initiated by the plugin, if embedded):
+/// 1. Plugins calls clap_host_gui->request_resize()
+/// 2. If the host returns true the new size is accepted,
+/// the host doesn't have to call clap_plugin_gui->set_size().
+/// If the host returns false, the new size is rejected.
+///
+/// Resizing the window (drag, if embedded)):
+/// 1. Only possible if clap_plugin_gui->can_resize() returns true
+/// 2. Mouse drag -> new_size
+/// 3. clap_plugin_gui->adjust_size(new_size) -> working_size
+/// 4. clap_plugin_gui->set_size(working_size)
+
+static CLAP_CONSTEXPR const char CLAP_EXT_GUI[] = "clap.gui";
+
+// If your windowing API is not listed here, please open an issue and we'll figure it out.
+// https://github.com/free-audio/clap/issues/new
+
+// uses physical size
+// embed using https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setparent
+static const CLAP_CONSTEXPR char CLAP_WINDOW_API_WIN32[] = "win32";
+
+// uses logical size, don't call clap_plugin_gui->set_scale()
+static const CLAP_CONSTEXPR char CLAP_WINDOW_API_COCOA[] = "cocoa";
+
+// uses physical size
+// embed using https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
+static const CLAP_CONSTEXPR char CLAP_WINDOW_API_X11[] = "x11";
+
+// uses physical size
+// embed is currently not supported, use floating windows
+static const CLAP_CONSTEXPR char CLAP_WINDOW_API_WAYLAND[] = "wayland";
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void *clap_hwnd;
+typedef void *clap_nsview;
+typedef unsigned long clap_xwnd;
+
+// Represent a window reference.
+typedef struct clap_window {
+ const char *api; // one of CLAP_WINDOW_API_XXX
+ union {
+ clap_nsview cocoa;
+ clap_xwnd x11;
+ clap_hwnd win32;
+ void *ptr; // for anything defined outside of clap
+ };
+} clap_window_t;
+
+// Information to improve window resizing when initiated by the host or window manager.
+typedef struct clap_gui_resize_hints {
+ bool can_resize_horizontally;
+ bool can_resize_vertically;
+
+ // only if can resize horizontally and vertically
+ bool preserve_aspect_ratio;
+ uint32_t aspect_ratio_width;
+ uint32_t aspect_ratio_height;
+} clap_gui_resize_hints_t;
+
+// Size (width, height) is in pixels; the corresponding windowing system extension is
+// responsible for defining if it is physical pixels or logical pixels.
+typedef struct clap_plugin_gui {
+ // Returns true if the requested gui api is supported
+ // [main-thread]
+ bool (*is_api_supported)(const clap_plugin_t *plugin, const char *api, bool is_floating);
+
+ // Returns true if the plugin has a preferred api.
+ // The host has no obligation to honor the plugin preferrence, this is just a hint.
+ // [main-thread]
+ bool (*get_preferred_api)(const clap_plugin_t *plugin, const char **api, bool *is_floating);
+
+ // Create and allocate all resources necessary for the gui.
+ //
+ // If is_floating is true, then the window will not be managed by the host. The plugin
+ // can set its window to stays above the parent window, see set_transient().
+ // api may be null or blank for floating window.
+ //
+ // If is_floating is false, then the plugin has to embbed its window into the parent window, see
+ // set_parent().
+ //
+ // After this call, the GUI may not be visible yet; don't forget to call show().
+ // [main-thread]
+ bool (*create)(const clap_plugin_t *plugin, const char *api, bool is_floating);
+
+ // Free all resources associated with the gui.
+ // [main-thread]
+ void (*destroy)(const clap_plugin_t *plugin);
+
+ // Set the absolute GUI scaling factor, and override any OS info.
+ // Should not be used if the windowing api relies upon logical pixels.
+ //
+ // If the plugin prefers to work out the scaling factor itself by querying the OS directly,
+ // then ignore the call.
+ //
+ // Returns true if the scaling could be applied
+ // Returns false if the call was ignored, or the scaling could not be applied.
+ // [main-thread]
+ bool (*set_scale)(const clap_plugin_t *plugin, double scale);
+
+ // Get the current size of the plugin UI.
+ // clap_plugin_gui->create() must have been called prior to asking the size.
+ // [main-thread]
+ bool (*get_size)(const clap_plugin_t *plugin, uint32_t *width, uint32_t *height);
+
+ // Returns true if the window is resizeable (mouse drag).
+ // Only for embedded windows.
+ // [main-thread]
+ bool (*can_resize)(const clap_plugin_t *plugin);
+
+ // Returns true if the plugin can provide hints on how to resize the window.
+ // [main-thread]
+ bool (*get_resize_hints)(const clap_plugin_t *plugin, clap_gui_resize_hints_t *hints);
+
+ // If the plugin gui is resizable, then the plugin will calculate the closest
+ // usable size which fits in the given size.
+ // This method does not change the size.
+ //
+ // Only for embedded windows.
+ // [main-thread]
+ bool (*adjust_size)(const clap_plugin_t *plugin, uint32_t *width, uint32_t *height);
+
+ // Sets the window size. Only for embedded windows.
+ // [main-thread]
+ bool (*set_size)(const clap_plugin_t *plugin, uint32_t width, uint32_t height);
+
+ // Embbeds the plugin window into the given window.
+ // [main-thread & !floating]
+ bool (*set_parent)(const clap_plugin_t *plugin, const clap_window_t *window);
+
+ // Set the plugin floating window to stay above the given window.
+ // [main-thread & floating]
+ bool (*set_transient)(const clap_plugin_t *plugin, const clap_window_t *window);
+
+ // Suggests a window title. Only for floating windows.
+ // [main-thread & floating]
+ void (*suggest_title)(const clap_plugin_t *plugin, const char *title);
+
+ // Show the window.
+ // [main-thread]
+ bool (*show)(const clap_plugin_t *plugin);
+
+ // Hide the window, this method does not free the resources, it just hides
+ // the window content. Yet it may be a good idea to stop painting timers.
+ // [main-thread]
+ bool (*hide)(const clap_plugin_t *plugin);
+} clap_plugin_gui_t;
+
+typedef struct clap_host_gui {
+ // The host should call get_resize_hints() again.
+ // [thread-safe]
+ void (*resize_hints_changed)(const clap_host_t *host);
+
+ /* Request the host to resize the client area to width, height.
+ * Return true if the new size is accepted, false otherwise.
+ * The host doesn't have to call set_size().
+ *
+ * Note: if not called from the main thread, then a return value simply means that the host
+ * acknowledged the request and will process it asynchronously. If the request then can't be
+ * satisfied then the host will call set_size() to revert the operation.
+ *
+ * [thread-safe] */
+ bool (*request_resize)(const clap_host_t *host, uint32_t width, uint32_t height);
+
+ /* Request the host to show the plugin gui.
+ * Return true on success, false otherwise.
+ * [thread-safe] */
+ bool (*request_show)(const clap_host_t *host);
+
+ /* Request the host to hide the plugin gui.
+ * Return true on success, false otherwise.
+ * [thread-safe] */
+ bool (*request_hide)(const clap_host_t *host);
+
+ // The floating window has been closed, or the connection to the gui has been lost.
+ //
+ // If was_destroyed is true, then the host must call clap_plugin_gui->destroy() to acknowledge
+ // the gui destruction.
+ // [thread-safe]
+ void (*closed)(const clap_host_t *host, bool was_destroyed);
+} clap_host_gui_t;
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/distrho/src/clap/ext/params.h b/distrho/src/clap/ext/params.h
@@ -0,0 +1,296 @@
+#pragma once
+
+#include "../plugin.h"
+#include "../string-sizes.h"
+
+/// @page Parameters
+/// @brief parameters management
+///
+/// Main idea:
+///
+/// The host sees the plugin as an atomic entity; and acts as a controller on top of its parameters.
+/// The plugin is responsible for keeping its audio processor and its GUI in sync.
+///
+/// The host can at any time read parameters' value on the [main-thread] using
+/// @ref clap_plugin_params.value().
+///
+/// There are two options to communicate parameter value changes, and they are not concurrent.
+/// - send automation points during clap_plugin.process()
+/// - send automation points during clap_plugin_params.flush(), for parameter changes
+/// without processing audio
+///
+/// When the plugin changes a parameter value, it must inform the host.
+/// It will send @ref CLAP_EVENT_PARAM_VALUE event during process() or flush().
+/// If the user is adjusting the value, don't forget to mark the begining and end
+/// of the gesture by sending CLAP_EVENT_PARAM_GESTURE_BEGIN and CLAP_EVENT_PARAM_GESTURE_END
+/// events.
+///
+/// @note MIDI CCs are tricky because you may not know when the parameter adjustment ends.
+/// Also if the host records incoming MIDI CC and parameter change automation at the same time,
+/// there will be a conflict at playback: MIDI CC vs Automation.
+/// The parameter automation will always target the same parameter because the param_id is stable.
+/// The MIDI CC may have a different mapping in the future and may result in a different playback.
+///
+/// When a MIDI CC changes a parameter's value, set the flag CLAP_EVENT_DONT_RECORD in
+/// clap_event_param.header.flags. That way the host may record the MIDI CC automation, but not the
+/// parameter change and there won't be conflict at playback.
+///
+/// Scenarios:
+///
+/// I. Loading a preset
+/// - load the preset in a temporary state
+/// - call @ref clap_host_params.rescan() if anything changed
+/// - call @ref clap_host_latency.changed() if latency changed
+/// - invalidate any other info that may be cached by the host
+/// - if the plugin is activated and the preset will introduce breaking changes
+/// (latency, audio ports, new parameters, ...) be sure to wait for the host
+/// to deactivate the plugin to apply those changes.
+/// If there are no breaking changes, the plugin can apply them them right away.
+/// The plugin is resonsible for updating both its audio processor and its gui.
+///
+/// II. Turning a knob on the DAW interface
+/// - the host will send an automation event to the plugin via a process() or flush()
+///
+/// III. Turning a knob on the Plugin interface
+/// - the plugin is responsible for sending the parameter value to its audio processor
+/// - call clap_host_params->request_flush() or clap_host->request_process().
+/// - when the host calls either clap_plugin->process() or clap_plugin_params->flush(),
+/// send an automation event and don't forget to set begin_adjust,
+/// end_adjust and should_record flags
+///
+/// IV. Turning a knob via automation
+/// - host sends an automation point during clap_plugin->process() or clap_plugin_params->flush().
+/// - the plugin is responsible for updating its GUI
+///
+/// V. Turning a knob via plugin's internal MIDI mapping
+/// - the plugin sends a CLAP_EVENT_PARAM_SET output event, set should_record to false
+/// - the plugin is responsible to update its GUI
+///
+/// VI. Adding or removing parameters
+/// - if the plugin is activated call clap_host->restart()
+/// - once the plugin isn't active:
+/// - apply the new state
+/// - if a parameter is gone or is created with an id that may have been used before,
+/// call clap_host_params.clear(host, param_id, CLAP_PARAM_CLEAR_ALL)
+/// - call clap_host_params->rescan(CLAP_PARAM_RESCAN_ALL)
+
+static CLAP_CONSTEXPR const char CLAP_EXT_PARAMS[] = "clap.params";
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum {
+ // Is this param stepped? (integer values only)
+ // if so the double value is converted to integer using a cast (equivalent to trunc).
+ CLAP_PARAM_IS_STEPPED = 1 << 0,
+
+ // Useful for for periodic parameters like a phase
+ CLAP_PARAM_IS_PERIODIC = 1 << 1,
+
+ // The parameter should not be shown to the user, because it is currently not used.
+ // It is not necessary to process automation for this parameter.
+ CLAP_PARAM_IS_HIDDEN = 1 << 2,
+
+ // The parameter can't be changed by the host.
+ CLAP_PARAM_IS_READONLY = 1 << 3,
+
+ // This parameter is used to merge the plugin and host bypass button.
+ // It implies that the parameter is stepped.
+ // min: 0 -> bypass off
+ // max: 1 -> bypass on
+ CLAP_PARAM_IS_BYPASS = 1 << 4,
+
+ // When set:
+ // - automation can be recorded
+ // - automation can be played back
+ //
+ // The host can send live user changes for this parameter regardless of this flag.
+ //
+ // If this parameters affect the internal processing structure of the plugin, ie: max delay, fft
+ // size, ... and the plugins needs to re-allocate its working buffers, then it should call
+ // host->request_restart(), and perform the change once the plugin is re-activated.
+ CLAP_PARAM_IS_AUTOMATABLE = 1 << 5,
+
+ // Does this parameter support per note automations?
+ CLAP_PARAM_IS_AUTOMATABLE_PER_NOTE_ID = 1 << 6,
+
+ // Does this parameter support per key automations?
+ CLAP_PARAM_IS_AUTOMATABLE_PER_KEY = 1 << 7,
+
+ // Does this parameter support per channel automations?
+ CLAP_PARAM_IS_AUTOMATABLE_PER_CHANNEL = 1 << 8,
+
+ // Does this parameter support per port automations?
+ CLAP_PARAM_IS_AUTOMATABLE_PER_PORT = 1 << 9,
+
+ // Does this parameter support the modulation signal?
+ CLAP_PARAM_IS_MODULATABLE = 1 << 10,
+
+ // Does this parameter support per note modulations?
+ CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID = 1 << 11,
+
+ // Does this parameter support per key modulations?
+ CLAP_PARAM_IS_MODULATABLE_PER_KEY = 1 << 12,
+
+ // Does this parameter support per channel modulations?
+ CLAP_PARAM_IS_MODULATABLE_PER_CHANNEL = 1 << 13,
+
+ // Does this parameter support per port modulations?
+ CLAP_PARAM_IS_MODULATABLE_PER_PORT = 1 << 14,
+
+ // Any change to this parameter will affect the plugin output and requires to be done via
+ // process() if the plugin is active.
+ //
+ // A simple example would be a DC Offset, changing it will change the output signal and must be
+ // processed.
+ CLAP_PARAM_REQUIRES_PROCESS = 1 << 15,
+};
+typedef uint32_t clap_param_info_flags;
+
+/* This describes a parameter */
+typedef struct clap_param_info {
+ // stable parameter identifier, it must never change.
+ clap_id id;
+
+ clap_param_info_flags flags;
+
+ // This value is optional and set by the plugin.
+ // Its purpose is to provide a fast access to the plugin parameter:
+ //
+ // Parameter *p = findParameter(param_id);
+ // param_info->cookie = p;
+ //
+ // /* and later on */
+ // Parameter *p = (Parameter *)cookie;
+ //
+ // It is invalidated on clap_host_params->rescan(CLAP_PARAM_RESCAN_ALL) and when the plugin is
+ // destroyed.
+ void *cookie;
+
+ // the display name
+ char name[CLAP_NAME_SIZE];
+
+ // the module path containing the param, eg:"oscillators/wt1"
+ // '/' will be used as a separator to show a tree like structure.
+ char module[CLAP_PATH_SIZE];
+
+ double min_value; // minimum plain value
+ double max_value; // maximum plain value
+ double default_value; // default plain value
+} clap_param_info_t;
+
+typedef struct clap_plugin_params {
+ // Returns the number of parameters.
+ // [main-thread]
+ uint32_t (*count)(const clap_plugin_t *plugin);
+
+ // Copies the parameter's info to param_info and returns true on success.
+ // [main-thread]
+ bool (*get_info)(const clap_plugin_t *plugin,
+ uint32_t param_index,
+ clap_param_info_t *param_info);
+
+ // Gets the parameter plain value.
+ // [main-thread]
+ bool (*get_value)(const clap_plugin_t *plugin, clap_id param_id, double *value);
+
+ // Formats the display text for the given parameter value.
+ // The host should always format the parameter value to text using this function
+ // before displaying it to the user.
+ // [main-thread]
+ bool (*value_to_text)(
+ const clap_plugin_t *plugin, clap_id param_id, double value, char *display, uint32_t size);
+
+ // Converts the display text to a parameter value.
+ // [main-thread]
+ bool (*text_to_value)(const clap_plugin_t *plugin,
+ clap_id param_id,
+ const char *display,
+ double *value);
+
+ // Flushes a set of parameter changes.
+ // This method must not be called concurrently to clap_plugin->process().
+ //
+ // [active ? audio-thread : main-thread]
+ void (*flush)(const clap_plugin_t *plugin,
+ const clap_input_events_t *in,
+ const clap_output_events_t *out);
+} clap_plugin_params_t;
+
+enum {
+ // The parameter values did change, eg. after loading a preset.
+ // The host will scan all the parameters value.
+ // The host will not record those changes as automation points.
+ // New values takes effect immediately.
+ CLAP_PARAM_RESCAN_VALUES = 1 << 0,
+
+ // The value to text conversion changed, and the text needs to be rendered again.
+ CLAP_PARAM_RESCAN_TEXT = 1 << 1,
+
+ // The parameter info did change, use this flag for:
+ // - name change
+ // - module change
+ // - is_periodic (flag)
+ // - is_hidden (flag)
+ // New info takes effect immediately.
+ CLAP_PARAM_RESCAN_INFO = 1 << 2,
+
+ // Invalidates everything the host knows about parameters.
+ // It can only be used while the plugin is deactivated.
+ // If the plugin is activated use clap_host->restart() and delay any change until the host calls
+ // clap_plugin->deactivate().
+ //
+ // You must use this flag if:
+ // - some parameters were added or removed.
+ // - some parameters had critical changes:
+ // - is_per_note (flag)
+ // - is_per_channel (flag)
+ // - is_readonly (flag)
+ // - is_bypass (flag)
+ // - is_stepped (flag)
+ // - is_modulatable (flag)
+ // - min_value
+ // - max_value
+ // - cookie
+ CLAP_PARAM_RESCAN_ALL = 1 << 3,
+};
+typedef uint32_t clap_param_rescan_flags;
+
+enum {
+ // Clears all possible references to a parameter
+ CLAP_PARAM_CLEAR_ALL = 1 << 0,
+
+ // Clears all automations to a parameter
+ CLAP_PARAM_CLEAR_AUTOMATIONS = 1 << 1,
+
+ // Clears all modulations to a parameter
+ CLAP_PARAM_CLEAR_MODULATIONS = 1 << 2,
+};
+typedef uint32_t clap_param_clear_flags;
+
+typedef struct clap_host_params {
+ // Rescan the full list of parameters according to the flags.
+ // [main-thread]
+ void (*rescan)(const clap_host_t *host, clap_param_rescan_flags flags);
+
+ // Clears references to a parameter.
+ // [main-thread]
+ void (*clear)(const clap_host_t *host, clap_id param_id, clap_param_clear_flags flags);
+
+ // Request a parameter flush.
+ //
+ // The host will then schedule a call to either:
+ // - clap_plugin.process()
+ // - clap_plugin_params->flush()
+ //
+ // This function is always safe to use and should not be called from an [audio-thread] as the
+ // plugin would already be within process() or flush().
+ //
+ // [thread-safe,!audio-thread]
+ void (*request_flush)(const clap_host_t *host);
+} clap_host_params_t;
+
+#ifdef __cplusplus
+}
+#endif