Browse Source

Begin CLAP parameters, add UI stubs

pull/321/merge
falkTX 3 years ago
parent
commit
2351dbc3db
3 changed files with 1096 additions and 4 deletions
  1. +582
    -4
      distrho/src/DistrhoPluginCLAP.cpp
  2. +218
    -0
      distrho/src/clap/ext/gui.h
  3. +296
    -0
      distrho/src/clap/ext/params.h

+ 582
- 4
distrho/src/DistrhoPluginCLAP.cpp View File

@@ -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;
@@ -261,6 +678,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

@@ -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;
@@ -301,6 +825,54 @@ static const clap_plugin_audio_ports_t clap_plugin_audio_ports = {
clap_plugin_audio_ports_get
};

// --------------------------------------------------------------------------------------------------------------------
// 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

@@ -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;
}



+ 218
- 0
distrho/src/clap/ext/gui.h View File

@@ -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

+ 296
- 0
distrho/src/clap/ext/params.h View File

@@ -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

Loading…
Cancel
Save