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