DPF

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

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:
Mdistrho/src/DistrhoPluginCLAP.cpp | 586++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Adistrho/src/clap/ext/gui.h | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/ext/params.h | 296+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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