From 77bd69efec2d4d1e3f90ace5e7c1d4d9fa553919 Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 11 Oct 2023 14:41:01 +0200 Subject: [PATCH] Add plugin-side parameters to cairoui example Signed-off-by: falkTX --- examples/CairoUI/CMakeLists.txt | 3 +- examples/CairoUI/CairoExamplePlugin.cpp | 135 +++++++++++++++++++---- examples/CairoUI/CairoExampleUI.cpp | 92 ++++++++++----- examples/CairoUI/DemoWidgetBanner.cpp | 99 ----------------- examples/CairoUI/DemoWidgetBanner.hpp | 79 ++++++++++++- examples/CairoUI/DemoWidgetClickable.hpp | 72 ++++++++---- examples/CairoUI/DistrhoPluginInfo.h | 34 +++++- examples/CairoUI/Makefile | 7 +- 8 files changed, 336 insertions(+), 185 deletions(-) delete mode 100644 examples/CairoUI/DemoWidgetBanner.cpp diff --git a/examples/CairoUI/CMakeLists.txt b/examples/CairoUI/CMakeLists.txt index 2d9b24818..4a4cca1dc 100644 --- a/examples/CairoUI/CMakeLists.txt +++ b/examples/CairoUI/CMakeLists.txt @@ -2,13 +2,12 @@ # ------------------------------ # dpf_add_plugin(d_cairoui - TARGETS jack dssi lv2 vst2 + TARGETS clap dssi jack lv2_sep vst2 vst3 UI_TYPE cairo FILES_DSP CairoExamplePlugin.cpp FILES_UI Artwork.cpp - DemoWidgetBanner.cpp CairoExampleUI.cpp) target_include_directories( diff --git a/examples/CairoUI/CairoExamplePlugin.cpp b/examples/CairoUI/CairoExamplePlugin.cpp index f22f1552f..ffbc17fd6 100644 --- a/examples/CairoUI/CairoExamplePlugin.cpp +++ b/examples/CairoUI/CairoExamplePlugin.cpp @@ -23,33 +23,62 @@ START_NAMESPACE_DISTRHO class CairoExamplePlugin : public Plugin { + float fParameters[kParameterCount]; + public: CairoExamplePlugin() - : Plugin(0, 0, 0) + : Plugin(kParameterCount, 0, 0) { + std::memset(fParameters, 0, sizeof(fParameters)); } - const char* getLabel() const + /** + Get the plugin label.@n + This label is a short restricted name consisting of only _, a-z, A-Z and 0-9 characters. + */ + const char* getLabel() const override + { + return "cairo_ui"; + } + + /** + Get an extensive comment/description about the plugin. + */ + const char* getDescription() const override { return "Cairo DPF Example"; } - const char* getMaker() const + /** + Get the plugin author/maker. + */ + const char* getMaker() const override { - return "Jean Pierre Cimalando"; + return "DISTRHO"; } - const char* getLicense() const + /** + Get the plugin license (a single line of text or a URL).@n + For commercial plugins this should return some short copyright information. + */ + const char* getLicense() const override { return "ISC"; } - uint32_t getVersion() const + /** + Get the plugin version, in hexadecimal. + */ + uint32_t getVersion() const override { return d_version(1, 0, 0); } - int64_t getUniqueId() const + /** + Get the plugin unique Id.@n + This value is used by LADSPA, DSSI, VST2 and VST3 plugin formats. + */ + int64_t getUniqueId() const override { return d_cconst('d', 'C', 'a', 'i'); } @@ -58,7 +87,7 @@ class CairoExamplePlugin : public Plugin Initialize the audio port @a index.@n This function will be called once, shortly after the plugin is created. */ - void initAudioPort(bool input, uint32_t index, AudioPort& port) override + void initAudioPort(const bool input, const uint32_t index, AudioPort& port) override { // treat meter audio ports as stereo port.groupId = kPortGroupMono; @@ -67,29 +96,91 @@ class CairoExamplePlugin : public Plugin Plugin::initAudioPort(input, index, port); } - void initParameter(uint32_t index, Parameter& parameter) + /** + Initialize the parameter @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initParameter(const uint32_t index, Parameter& parameter) override { - // unused - (void)index; - (void)parameter; + /** + All parameters in this plugin have the same ranges. + */ + switch (index) + { + case kParameterKnob: + parameter.hints = kParameterIsAutomatable; + parameter.name = "Knob"; + parameter.symbol = "knob"; + parameter.ranges.min = 0.0f; + parameter.ranges.max = 1.0f; + parameter.ranges.def = 0.0f; + break; + case kParameterTriState: + parameter.hints = kParameterIsAutomatable|kParameterIsInteger; + parameter.name = "Color"; + parameter.symbol = "color"; + parameter.ranges.min = 0.0f; + parameter.ranges.max = 2.0f; + parameter.ranges.def = 0.0f; + parameter.enumValues.count = 3; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const values = new ParameterEnumerationValue[3]; + parameter.enumValues.values = values; + values[0].label = "Red"; + values[0].value = 0; + values[1].label = "Green"; + values[1].value = 1; + values[2].label = "Blue"; + values[2].value = 2; + } + break; + case kParameterButton: + parameter.hints = kParameterIsAutomatable|kParameterIsBoolean; + parameter.name = "Button"; + parameter.symbol = "button"; + parameter.ranges.min = 0.0f; + parameter.ranges.max = 1.0f; + parameter.ranges.def = 0.0f; + parameter.enumValues.count = 2; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const values = new ParameterEnumerationValue[2]; + parameter.enumValues.values = values; + values[0].label = "Off"; + values[0].value = 0; + values[1].label = "On"; + values[1].value = 1; + } + break; + } } - float getParameterValue(uint32_t index) const + /** + Get the current value of a parameter.@n + The host may call this function from any context, including realtime processing. + */ + float getParameterValue(const uint32_t index) const override { - return 0; - - // unused - (void)index; + return fParameters[index]; } - void setParameterValue(uint32_t index, float value) + /** + Change a parameter value.@n + The host may call this function from any context, including realtime processing.@n + When a parameter is marked as automatable, you must ensure no non-realtime operations are performed. + @note This function will only be called for parameter inputs. + */ + void setParameterValue(const uint32_t index, const float value) override { - // unused - (void)index; - (void)value; + fParameters[index] = value; } - void run(const float** inputs, float** outputs, uint32_t frames) + /** + Run/process function for plugins without MIDI input. + @note Some parameters might be null if there are no audio inputs or outputs. + */ + void run(const float** const inputs, float** const outputs, const uint32_t frames) override { /** This plugin does nothing, it just demonstrates cairo UI usage. diff --git a/examples/CairoUI/CairoExampleUI.cpp b/examples/CairoUI/CairoExampleUI.cpp index 90a3c30d9..78c232f5c 100644 --- a/examples/CairoUI/CairoExampleUI.cpp +++ b/examples/CairoUI/CairoExampleUI.cpp @@ -23,42 +23,56 @@ START_NAMESPACE_DISTRHO -/** - We need a few classes from DGL. - */ +// We need a few classes from DGL. using DGL_NAMESPACE::CairoGraphicsContext; using DGL_NAMESPACE::CairoImage; -using DGL_NAMESPACE::CairoImageButton; using DGL_NAMESPACE::CairoImageKnob; +using DGL_NAMESPACE::CairoImageSwitch; + +// And from ourselves +using DGL_NAMESPACE::DemoWidgetBanner; -class CairoExampleUI : public UI +class CairoExampleUI : public UI, + public CairoImageKnob::Callback, + public CairoImageSwitch::Callback, + public DemoWidgetClickable::Callback { + ScopedPointer fKnob; + ScopedPointer fButton; + ScopedPointer fWidgetBanner; + ScopedPointer fWidgetClickable; + public: CairoExampleUI() - : UI(200, 200) { - fWidgetClickable = new DemoWidgetClickable(this); - fWidgetClickable->setSize(50, 50); - fWidgetClickable->setAbsolutePos(100, 100); + CairoImage knobSkin; + knobSkin.loadFromPNG(Artwork::knobData, Artwork::knobDataSize); fWidgetBanner = new DemoWidgetBanner(this); - fWidgetBanner->setSize(180, 80); fWidgetBanner->setAbsolutePos(10, 10); + fWidgetBanner->setSize(180, 80); - CairoImage knobSkin; - knobSkin.loadFromPNG(Artwork::knobData, Artwork::knobDataSize); + fWidgetClickable = new DemoWidgetClickable(this); + fWidgetClickable->setAbsolutePos(100, 100); + fWidgetClickable->setSize(50, 50); + fWidgetClickable->setCallback(this); + fWidgetClickable->setId(kParameterTriState); fKnob = new CairoImageKnob(this, knobSkin); - fKnob->setSize(80, 80); fKnob->setAbsolutePos(10, 100); + fKnob->setSize(80, 80); + fKnob->setCallback(this); + fKnob->setId(kParameterKnob); CairoImage buttonOn, buttonOff; buttonOn.loadFromPNG(Artwork::buttonOnData, Artwork::buttonOnDataSize); buttonOff.loadFromPNG(Artwork::buttonOffData, Artwork::buttonOffDataSize); - fButton = new CairoImageButton(this, buttonOff, buttonOn); - fButton->setSize(60, 35); + fButton = new CairoImageSwitch(this, buttonOff, buttonOn); fButton->setAbsolutePos(100, 160); + fButton->setSize(60, 35); + fButton->setCallback(this); + fButton->setId(kParameterButton); #if 0 // we can use this if/when our resources are scalable, for now they are PNGs @@ -67,7 +81,7 @@ class CairoExampleUI : public UI setSize(200 * scaleFactor, 200 * scaleFactor); #else // without scalable resources, let DPF handle the scaling internally - setGeometryConstraints(200, 200, true, true); + setGeometryConstraints(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT, true, true); #endif } @@ -101,18 +115,46 @@ class CairoExampleUI : public UI } #endif - void parameterChanged(uint32_t index, float value) override + void parameterChanged(const uint32_t index, const float value) override { - // unused - (void)index; - (void)value; + switch (index) + { + case kParameterKnob: + fKnob->setValue(value); + break; + case kParameterTriState: + fWidgetClickable->setColorId(static_cast(value + 0.5f)); + break; + case kParameterButton: + fButton->setDown(value > 0.5f); + break; + } } -private: - ScopedPointer fWidgetClickable; - ScopedPointer fWidgetBanner; - ScopedPointer fKnob; - ScopedPointer fButton; + void demoWidgetClicked(DemoWidgetClickable*, const uint8_t colorId) override + { + setParameterValue(kParameterTriState, colorId); + } + + void imageKnobDragStarted(CairoImageKnob*) override + { + editParameter(kParameterKnob, true); + } + + void imageKnobDragFinished(CairoImageKnob*) override + { + editParameter(kParameterKnob, false); + } + + void imageKnobValueChanged(CairoImageKnob*, const float value) override + { + setParameterValue(kParameterKnob, value); + } + + void imageSwitchClicked(CairoImageSwitch*, bool down) override + { + setParameterValue(kParameterButton, down ? 1.f : 0.f); + } }; UI* createUI() diff --git a/examples/CairoUI/DemoWidgetBanner.cpp b/examples/CairoUI/DemoWidgetBanner.cpp deleted file mode 100644 index 422378c8b..000000000 --- a/examples/CairoUI/DemoWidgetBanner.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho - * Copyright (C) 2019-2021 Jean Pierre Cimalando - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "DemoWidgetBanner.hpp" - -START_NAMESPACE_DGL - -// ----------------------------------------------------------------------- - -static const char* banner = -" " -" * * * * * " -" ** ** * * * * " -" * * * * * * * " -" * * * **** *** * **** * * ** **** * *** " -" * * * * ** * * * * * * ** * " -" * * ***** * * ****** * * * * * * * " -" * * * * * * * * * * * * * * " -" * * * ** * ** * * * * * * * * * * " -" * * *** * *** * **** ** ** ***** ** * * " -" " -" " -" " -" ***** **** ***** " -" * * * * * " -" * * * * * " -" * * * * * " -" * * **** **** " -" * * * * " -" * * * * " -" * * * * " -" ***** * * " -" "; - -enum -{ - rows = 23, - columns = 72, -}; - -DemoWidgetBanner::DemoWidgetBanner(SubWidget* parent) - : CairoSubWidget(parent) {} - -DemoWidgetBanner::DemoWidgetBanner(TopLevelWidget* parent) - : CairoSubWidget(parent) {} - -void DemoWidgetBanner::onCairoDisplay(const CairoGraphicsContext& context) -{ - cairo_t* cr = context.handle; - - Size sz = getSize(); - int w = sz.getWidth(); - int h = sz.getHeight(); - - double diameter = (double)w / columns; - double radius = 0.5 * diameter; - double xoff = 0; - double yoff = 0.5 * (h - rows * diameter); - for (int r = 0; r < rows; ++r) - { - for (int c = 0; c < columns; ++c) - { - double cx = xoff + radius + c * diameter; - double cy = yoff + radius + r * diameter; - - char ch = banner[c + r * columns]; - if (ch != ' ') - cairo_set_source_rgb(cr, 0.5, 0.9, 0.2); - else - cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); - - cairo_save(cr); - cairo_translate(cr, cx, cy); - cairo_scale(cr, radius, radius); - cairo_arc(cr, 0.0, 0.0, 1.0, 0.0, 2 * M_PI); - cairo_restore(cr); - - cairo_fill(cr); - } - } -} - -// ----------------------------------------------------------------------- - -END_NAMESPACE_DGL diff --git a/examples/CairoUI/DemoWidgetBanner.hpp b/examples/CairoUI/DemoWidgetBanner.hpp index c05df37b8..49143ce8b 100644 --- a/examples/CairoUI/DemoWidgetBanner.hpp +++ b/examples/CairoUI/DemoWidgetBanner.hpp @@ -1,7 +1,7 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho * Copyright (C) 2019-2021 Jean Pierre Cimalando + * Copyright (C) 2012-2023 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -23,17 +23,84 @@ START_NAMESPACE_DGL // ----------------------------------------------------------------------- +static constexpr const char banner[] = +" " +" * * * * * " +" ** ** * * * * " +" * * * * * * * " +" * * * **** *** * **** * * ** **** * *** " +" * * * * ** * * * * * * ** * " +" * * ***** * * ****** * * * * * * * " +" * * * * * * * * * * * * * * " +" * * * ** * ** * * * * * * * * * * " +" * * *** * *** * **** ** ** ***** ** * * " +" " +" " +" " +" ***** **** ***** " +" * * * * * " +" * * * * * " +" * * * * * " +" * * **** **** " +" * * * * " +" * * * * " +" * * * * " +" ***** * * " +" "; + +enum { + rows = 23, + columns = 72, +}; + class DemoWidgetBanner : public CairoSubWidget { public: - explicit DemoWidgetBanner(SubWidget* parent); - explicit DemoWidgetBanner(TopLevelWidget* parent); + explicit DemoWidgetBanner(SubWidget* const parent) + : CairoSubWidget(parent) {} + + explicit DemoWidgetBanner(TopLevelWidget* const parent) + : CairoSubWidget(parent) {} + protected: - void onCairoDisplay(const CairoGraphicsContext& context) override; + void onCairoDisplay(const CairoGraphicsContext& context) override + { + cairo_t* const cr = context.handle; + + Size sz = getSize(); + int w = sz.getWidth(); + int h = sz.getHeight(); + + const double diameter = (double)w / columns; + const double radius = 0.5 * diameter; + const double xoff = 0; + const double yoff = 0.5 * (h - rows * diameter); + + for (int r = 0; r < rows; ++r) + { + for (int c = 0; c < columns; ++c) + { + double cx = xoff + radius + c * diameter; + double cy = yoff + radius + r * diameter; + + char ch = banner[c + r * columns]; + if (ch != ' ') + cairo_set_source_rgb(cr, 0.5, 0.9, 0.2); + else + cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); + + cairo_save(cr); + cairo_translate(cr, cx, cy); + cairo_scale(cr, radius, radius); + cairo_arc(cr, 0.0, 0.0, 1.0, 0.0, 2 * M_PI); + cairo_restore(cr); + + cairo_fill(cr); + } + } + } }; // ----------------------------------------------------------------------- END_NAMESPACE_DGL - -using DGL_NAMESPACE::DemoWidgetBanner; diff --git a/examples/CairoUI/DemoWidgetClickable.hpp b/examples/CairoUI/DemoWidgetClickable.hpp index cef8ffb5a..5ef967b54 100644 --- a/examples/CairoUI/DemoWidgetClickable.hpp +++ b/examples/CairoUI/DemoWidgetClickable.hpp @@ -1,7 +1,7 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho * Copyright (C) 2019-2021 Jean Pierre Cimalando + * Copyright (C) 2012-2023 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -23,16 +23,51 @@ START_NAMESPACE_DGL // ----------------------------------------------------------------------- -class DemoWidgetClickable : public CairoSubWidget +class DemoWidgetClickable : public CairoSubWidget, + public ButtonEventHandler, + public ButtonEventHandler::Callback { public: + class Callback + { + public: + virtual ~Callback() {} + virtual void demoWidgetClicked(DemoWidgetClickable* widget, uint8_t colorId) = 0; + }; + explicit DemoWidgetClickable(SubWidget* const parent) : CairoSubWidget(parent), - colorId(0) {} + ButtonEventHandler(this), + fCallback(nullptr), + fColorId(0) + { + ButtonEventHandler::setCallback(this); + } explicit DemoWidgetClickable(TopLevelWidget* const parent) : CairoSubWidget(parent), - colorId(0) {} + ButtonEventHandler(this), + fCallback(nullptr), + fColorId(0) + { + ButtonEventHandler::setCallback(this); + } + + void setCallback(Callback* const callback) noexcept + { + fCallback = callback; + } + + uint8_t getColorId() const noexcept + { + return fColorId; + } + + void setColorId(uint8_t colorId) noexcept + { + fColorId = colorId; + repaint(); + } protected: void onCairoDisplay(const CairoGraphicsContext& context) override @@ -43,7 +78,7 @@ class DemoWidgetClickable : public CairoSubWidget const int w = sz.getWidth(); const int h = sz.getHeight(); - switch (colorId) + switch (fColorId) { case 0: cairo_set_source_rgb(cr, 0.75, 0.0, 0.0); @@ -72,26 +107,21 @@ class DemoWidgetClickable : public CairoSubWidget bool onMouse(const MouseEvent& event) override { - if (event.press) - { - const int w = getWidth(); - const int h = getHeight(); - const int mx = event.pos.getX(); - const int my = event.pos.getY(); - - // inside - if (mx >= 0 && my >= 0 && mx < w && my < h) - { - colorId = (colorId + 1) % 3; - repaint(); - } - } + return ButtonEventHandler::mouseEvent(event); + } + + void buttonClicked(SubWidget*, int) override + { + fColorId = (fColorId + 1) % 3; + repaint(); - return CairoSubWidget::onMouse(event); + if (fCallback != nullptr) + fCallback->demoWidgetClicked(this, fColorId); } private: - uint colorId; + Callback* fCallback; + uint8_t fColorId; }; // ----------------------------------------------------------------------- diff --git a/examples/CairoUI/DistrhoPluginInfo.h b/examples/CairoUI/DistrhoPluginInfo.h index fe9f2036c..1966426cd 100644 --- a/examples/CairoUI/DistrhoPluginInfo.h +++ b/examples/CairoUI/DistrhoPluginInfo.h @@ -118,10 +118,36 @@ #define DISTRHO_UI_USE_NANOVG 0 /** - The %UI URI when exporting in LV2 format.@n - By default this is set to @ref DISTRHO_PLUGIN_URI with "#UI" as suffix. + Default UI width to use when creating initial and temporary windows.@n + Setting this macro allows to skip a temporary UI from being created in certain VST2 and VST3 hosts. + (which would normally be done for knowing the UI size before host creates a window for it) + + Value must match 1x scale factor. + + When this macro is defined, the companion DISTRHO_UI_DEFAULT_HEIGHT macro must be defined as well. */ -#define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#UI" +#define DISTRHO_UI_DEFAULT_WIDTH 200 -#define DISTRHO_UI_FILE_BROWSER 0 +/** + Default UI height to use when creating initial and temporary windows.@n + Setting this macro allows to skip a temporary UI from being created in certain VST2 and VST3 hosts. + (which would normally be done for knowing the UI size before host creates a window for it) + + Value must match 1x scale factor. + + When this macro is defined, the companion DISTRHO_UI_DEFAULT_WIDTH macro must be defined as well. + */ +#define DISTRHO_UI_DEFAULT_HEIGHT 200 + +// TODO document this #define DISTRHO_UI_USE_CAIRO 1 + +// TODO document this +#define DISTRHO_UI_FILE_BROWSER 0 + +enum Parameters { + kParameterKnob, + kParameterTriState, + kParameterButton, + kParameterCount +}; diff --git a/examples/CairoUI/Makefile b/examples/CairoUI/Makefile index 4ecaa0bb5..4ed93200a 100644 --- a/examples/CairoUI/Makefile +++ b/examples/CairoUI/Makefile @@ -17,7 +17,6 @@ FILES_DSP = \ FILES_UI = \ Artwork.cpp \ - DemoWidgetBanner.cpp \ CairoExampleUI.cpp # -------------------------------------------------------------- @@ -29,11 +28,7 @@ include ../../Makefile.plugins.mk # -------------------------------------------------------------- # Enable all possible plugin types -TARGETS += clap -TARGETS += dssi -TARGETS += jack -TARGETS += lv2_sep -TARGETS += vst2 +TARGETS = clap dssi jack lv2_sep vst2 vst3 all: $(TARGETS)