From bcd834cc6493e7fbcd6694ee67c389da5af6cd3e Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 15 May 2024 13:13:30 +0200 Subject: [PATCH] Initial implementation for DISTRHO_UI_USE_WEB_VIEW Signed-off-by: falkTX --- Makefile.plugins.mk | 31 +- distrho/DistrhoPluginUtils.hpp | 2 +- distrho/DistrhoUI.hpp | 3 +- distrho/extra/WebViewImpl.cpp | 12 +- distrho/extra/WebViewImpl.hpp | 2 +- distrho/src/DistrhoPluginChecks.h | 2 +- distrho/src/DistrhoUI.cpp | 210 ++++++++++++- distrho/src/DistrhoUIInternal.hpp | 25 +- distrho/src/DistrhoUIPrivateData.hpp | 15 +- examples/WebMeters/CMakeLists.txt | 12 + examples/WebMeters/DistrhoPluginInfo.h | 43 +++ examples/WebMeters/ExamplePluginWebMeters.cpp | 288 ++++++++++++++++++ examples/WebMeters/ExampleUIWebMeters.cpp | 61 ++++ examples/WebMeters/Makefile | 68 +++++ examples/WebMeters/README.md | 8 + examples/WebMeters/index.html | 150 +++++++++ 16 files changed, 895 insertions(+), 37 deletions(-) create mode 100644 examples/WebMeters/CMakeLists.txt create mode 100644 examples/WebMeters/DistrhoPluginInfo.h create mode 100644 examples/WebMeters/ExamplePluginWebMeters.cpp create mode 100644 examples/WebMeters/ExampleUIWebMeters.cpp create mode 100644 examples/WebMeters/Makefile create mode 100644 examples/WebMeters/README.md create mode 100644 examples/WebMeters/index.html diff --git a/Makefile.plugins.mk b/Makefile.plugins.mk index a5f2530d..3ee37fd6 100644 --- a/Makefile.plugins.mk +++ b/Makefile.plugins.mk @@ -48,6 +48,26 @@ endif endif +# --------------------------------------------------------------------------------------------------------------------- +# Check for proper UI_TYPE parameter + +ifeq ($(UI_TYPE),) +else ifeq ($(UI_TYPE),generic) +else ifeq ($(UI_TYPE),external) +else ifeq ($(UI_TYPE),cairo) +else ifeq ($(UI_TYPE),opengl) +else ifeq ($(UI_TYPE),opengl3) +USE_OPENGL3 = true +else ifeq ($(UI_TYPE),vulkan) +else ifeq ($(UI_TYPE),webview) +USE_WEB_VIEW = true +else +$(error unknown UI_TYPE $(UI_TYPE)) +endif + +# --------------------------------------------------------------------------------------------------------------------- +# Include DPF base setup + include $(DPF_PATH)/Makefile.base.mk # --------------------------------------------------------------------------------------------------------------------- @@ -251,11 +271,16 @@ HAVE_DGL = false endif endif -ifeq ($(UI_TYPE),web) -DGL_FLAGS += -DDGL_WEB -DHAVE_DGL +ifeq ($(UI_TYPE),webview) +DGL_FLAGS += -DDGL_EXTERNAL -DHAVE_DGL +ifeq ($(HAVE_STUB),true) +DGL_FLAGS += $(STUB_FLAGS) +DGL_LIBS += $(STUB_LIBS) DGL_LIB = $(DGL_BUILD_DIR)/libdgl-stub.a HAVE_DGL = true -USE_WEB_VIEW = true +else +HAVE_DGL = false +endif endif ifeq ($(HAVE_DGL)$(LINUX)$(USE_WEB_VIEW),truetruetrue) diff --git a/distrho/DistrhoPluginUtils.hpp b/distrho/DistrhoPluginUtils.hpp index 3f144d00..414dcd3b 100644 --- a/distrho/DistrhoPluginUtils.hpp +++ b/distrho/DistrhoPluginUtils.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho + * Copyright (C) 2012-2024 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 diff --git a/distrho/DistrhoUI.hpp b/distrho/DistrhoUI.hpp index 62fb4f2b..f2e67f03 100644 --- a/distrho/DistrhoUI.hpp +++ b/distrho/DistrhoUI.hpp @@ -192,8 +192,7 @@ public: This function does not block the event loop. - @note This is exactly the same API as provided by the Window class, - but redeclared here so that non-embed/DGL based UIs can still use file browser related functions. + @note This is exactly the same API as provided by the Window class, but redeclared here for convenience. */ bool openFileBrowser(const DISTRHO_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions()); #endif diff --git a/distrho/extra/WebViewImpl.cpp b/distrho/extra/WebViewImpl.cpp index 7df4fbb5..89efdabc 100644 --- a/distrho/extra/WebViewImpl.cpp +++ b/distrho/extra/WebViewImpl.cpp @@ -415,10 +415,10 @@ WebViewHandle webViewCreate(const char* const url, SetParent(hwnd, reinterpret_cast(windowId)); SetWindowPos(hwnd, nullptr, - options.offset.x * scaleFactor, - options.offset.y * scaleFactor, - (initialWidth - options.offset.x) * scaleFactor, - (initialHeight - options.offset.y) * scaleFactor, + options.offset.x, + options.offset.y, + initialWidth - options.offset.x, + initialHeight - options.offset.y, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); ShowWindow(hwnd, SW_SHOW); #endif @@ -448,8 +448,8 @@ WebViewHandle webViewCreate(const char* const url, const CGRect rect = CGRectMake(options.offset.x / scaleFactor, options.offset.y / scaleFactor, - initialWidth, - initialHeight); + initialWidth / scaleFactor, + initialHeight / scaleFactor); WKWebView* const webview = [[WKWebView alloc] initWithFrame:rect configuration:config]; diff --git a/distrho/extra/WebViewImpl.hpp b/distrho/extra/WebViewImpl.hpp index 627cff14..1b4f8195 100644 --- a/distrho/extra/WebViewImpl.hpp +++ b/distrho/extra/WebViewImpl.hpp @@ -84,7 +84,7 @@ struct WebViewOptions { This means it will draw on top of whatever is below it, something to take into consideration if mixing regular widgets with web views. - Provided metrics must not have scale factor pre-applied. + Provided metrics must have scale factor pre-applied. @p windowId: The native window id to attach this view to (X11 Window, HWND or NSView*) @p scaleFactor: Scale factor in use diff --git a/distrho/src/DistrhoPluginChecks.h b/distrho/src/DistrhoPluginChecks.h index 7552c88d..a7a4cdd9 100644 --- a/distrho/src/DistrhoPluginChecks.h +++ b/distrho/src/DistrhoPluginChecks.h @@ -163,7 +163,7 @@ # error invalid build config: file browser requested but `USE_FILE_BROWSER` build option is not set #endif -#if DISTRHO_UI_USE_WEB_VIEW && !defined(DGL_UI_USE_WEB_VIEW) +#if DISTRHO_UI_USE_WEB_VIEW && !defined(DGL_USE_WEB_VIEW) # error invalid build config: web view requested but `USE_WEB_VIEW` build option is not set #endif diff --git a/distrho/src/DistrhoUI.cpp b/distrho/src/DistrhoUI.cpp index e65608da..320afc51 100644 --- a/distrho/src/DistrhoUI.cpp +++ b/distrho/src/DistrhoUI.cpp @@ -82,11 +82,6 @@ END_NAMESPACE_DISTRHO START_NAMESPACE_DISTRHO -/* ------------------------------------------------------------------------------------------------------------ - * Static data, see DistrhoUIInternal.hpp */ - -const char* g_nextBundlePath = nullptr; - /* ------------------------------------------------------------------------------------------------------------ * get global scale factor */ @@ -178,8 +173,8 @@ UI::PrivateData* UI::PrivateData::s_nextPrivateData = nullptr; PluginWindow& UI::PrivateData::createNextWindow(UI* const ui, uint width, uint height, const bool adjustForScaleFactor) { - UI::PrivateData* const pData = s_nextPrivateData; - const double scaleFactor = d_isNotZero(pData->scaleFactor) ? pData->scaleFactor : getDesktopScaleFactor(pData->winId); + UI::PrivateData* const uiData = s_nextPrivateData; + const double scaleFactor = d_isNotZero(uiData->scaleFactor) ? uiData->scaleFactor : getDesktopScaleFactor(uiData->winId); if (adjustForScaleFactor && d_isNotZero(scaleFactor) && d_isNotEqual(scaleFactor, 1.0)) { @@ -188,15 +183,132 @@ PluginWindow& UI::PrivateData::createNextWindow(UI* const ui, uint width, uint h } d_stdout("createNextWindow %u %u %f %d", width, height, scaleFactor, adjustForScaleFactor); - pData->window = new PluginWindow(ui, pData->app, pData->winId, width, height, scaleFactor); + uiData->window = new PluginWindow(ui, uiData->app, uiData->winId, width, height, scaleFactor); + if (uiData->callbacksPtr != nullptr) + { + #if DISTRHO_UI_USE_WEB_VIEW + String path; + if (uiData->bundlePath != nullptr) + { + path = getResourcePath(uiData->bundlePath); + } + else + { + path = getBinaryFilename(); + path.truncate(path.rfind(DISTRHO_OS_SEP)); + path += "/resources"; + } + + // TODO convert win32 paths to web + // TODO encode paths (e.g. %20 for space) + + WebViewOptions opts; + opts.initialJS = "" +"editParameter = function(index, started){ postMessage('editparam '+index+' '+(started ? 1 : 0)) };" +"setParameterValue = function(index, value){ postMessage('setparam '+index+' '+value) };" +#if DISTRHO_PLUGIN_WANT_STATE +"setState = function(key, value){ postMessage('setstate '+key+' '+value) };" +"requestStateFile = function(key){ postMessage('reqstatefile '+key) };" +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT +"sendNote = function(channel, note, velocity){ postMessage('sendnote '+channel+' '+note+' '+velocity) };" +#endif + ; + opts.callback = webViewMessageCallback; + opts.callbackPtr = uiData; + uiData->webview = webViewCreate("file://" + path + "/index.html", uiData->winId, width, height, scaleFactor, opts); + #endif + } // If there are no callbacks, this is most likely a temporary window, so ignore idle callbacks - if (pData->callbacksPtr == nullptr) - pData->window->setIgnoreIdleCallbacks(); + else + { + uiData->window->setIgnoreIdleCallbacks(); + } - return pData->window.getObject(); + return uiData->window.getObject(); } +#if DISTRHO_UI_USE_WEB_VIEW +void UI::PrivateData::webViewMessageCallback(void* const arg, char* const msg) +{ + UI::PrivateData* const uiData = static_cast(arg); + + if (std::strncmp(msg, "setparam ", 9) == 0) + { + const char* const strindex = msg + 9; + char* strvalue = nullptr; + const ulong index = std::strtoul(strindex, &strvalue, 10); + DISTRHO_SAFE_ASSERT_RETURN(strvalue != nullptr && strindex != strvalue,); + + float value; + { + const ScopedSafeLocale ssl; + value = std::atof(strvalue); + } + uiData->setParamCallback(index + uiData->parameterOffset, value); + return; + } + + if (std::strncmp(msg, "editparam ", 10) == 0) + { + const char* const strindex = msg + 10; + char* strvalue = nullptr; + const ulong index = std::strtoul(strindex, &strvalue, 10); + DISTRHO_SAFE_ASSERT_RETURN(strvalue != nullptr && strindex != strvalue,); + + const bool started = strvalue[0] != '0'; + uiData->editParamCallback(index + uiData->parameterOffset, started); + return; + } + + #if DISTRHO_PLUGIN_WANT_STATE + if (std::strncmp(msg, "setstate ", 9) == 0) + { + char* const key = msg + 9; + char* const sep = std::strchr(key, ' '); + DISTRHO_SAFE_ASSERT_RETURN(sep != nullptr,); + *sep = 0; + char* const value = sep + 1; + + uiData->setStateCallback(key, value); + return; + } + + if (std::strncmp(msg, "reqstatefile ", 13) == 0) + { + const char* const key = msg + 13; + uiData->fileRequestCallback(key); + return; + } + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (std::strncmp(msg, "sendnote ", 9) == 0) + { + const char* const strchannel = msg + 9; + char* strnote = nullptr; + char* strvelocity = nullptr; + char* end = nullptr; + + const ulong channel = std::strtoul(strchannel, &strnote, 10); + DISTRHO_SAFE_ASSERT_RETURN(strnote != nullptr && strchannel != strnote,); + + const ulong note = std::strtoul(strnote, &strvelocity, 10); + DISTRHO_SAFE_ASSERT_RETURN(strvelocity != nullptr && strchannel != strvelocity,); + + const ulong velocity = std::strtoul(strvelocity, &end, 10); + DISTRHO_SAFE_ASSERT_RETURN(end != nullptr && strvelocity != end,); + + uiData->sendNoteCallback(channel, note, velocity); + return; + } + #endif + + d_stderr("UI received unknown message '%s'", msg); +} +#endif + /* ------------------------------------------------------------------------------------------------------------ * UI */ @@ -238,6 +350,10 @@ UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetA UI::~UI() { + #if DISTRHO_UI_USE_WEB_VIEW + if (uiData->webview != nullptr) + webViewDestroy(uiData->webview); + #endif } /* ------------------------------------------------------------------------------------------------------------ @@ -323,27 +439,91 @@ void* UI::getPluginInstancePointer() const noexcept /* ------------------------------------------------------------------------------------------------------------ * DSP/Plugin Callbacks */ -void UI::parameterChanged(uint32_t, float) +void UI::parameterChanged(const uint32_t index, const float value) { + #if DISTRHO_UI_USE_WEB_VIEW + if (uiData->webview != nullptr) + { + char msg[128]; + { + const ScopedSafeLocale ssl; + std::snprintf(msg, sizeof(msg) - 1, + "typeof(parameterChanged) === 'function' && parameterChanged(%u,%f)", index, value); + } + webViewEvaluateJS(uiData->webview, msg); + } + #else + // unused + (void)index; + (void)value; + #endif } #if DISTRHO_PLUGIN_WANT_PROGRAMS -void UI::programLoaded(uint32_t) +void UI::programLoaded(const uint32_t index) { + #if DISTRHO_UI_USE_WEB_VIEW + if (uiData->webview != nullptr) + { + char msg[128]; + std::snprintf(msg, sizeof(msg) - 1, + "typeof(programLoaded) === 'function' && programLoaded(%u)", index); + webViewEvaluateJS(uiData->webview, msg); + } + #else + // unused + (void)index; + #endif } #endif #if DISTRHO_PLUGIN_WANT_STATE -void UI::stateChanged(const char*, const char*) +void UI::stateChanged(const char* const key, const char* const value) { + #if DISTRHO_UI_USE_WEB_VIEW + if (uiData->webview != nullptr) + { + const size_t keylen = std::strlen(key); + const size_t valuelen = std::strlen(value); + const size_t msglen = keylen + valuelen + 60; + if (char* const msg = static_cast(std::malloc(msglen))) + { + // TODO escape \\' + std::snprintf(msg, sizeof(msglen) - 1, + "typeof(stateChanged) === 'function' && stateChanged('%s','%s')", key, value); + msg[msglen - 1] = '\0'; + webViewEvaluateJS(uiData->webview, msg); + std::free(msg); + } + } + #else + // unused + (void)key; + (void)value; + #endif } #endif /* ------------------------------------------------------------------------------------------------------------ * DSP/Plugin Callbacks (optional) */ -void UI::sampleRateChanged(double) +void UI::sampleRateChanged(const double sampleRate) { + #if DISTRHO_UI_USE_WEB_VIEW + if (uiData->webview != nullptr) + { + char msg[128]; + { + const ScopedSafeLocale ssl; + std::snprintf(msg, sizeof(msg) - 1, + "typeof(sampleRateChanged) === 'function' && sampleRateChanged(%f)", sampleRate); + } + webViewEvaluateJS(uiData->webview, msg); + } + #else + // unused + (void)sampleRate; + #endif } /* ------------------------------------------------------------------------------------------------------------ diff --git a/distrho/src/DistrhoUIInternal.hpp b/distrho/src/DistrhoUIInternal.hpp index 230f5892..e9ae05c5 100644 --- a/distrho/src/DistrhoUIInternal.hpp +++ b/distrho/src/DistrhoUIInternal.hpp @@ -21,11 +21,6 @@ START_NAMESPACE_DISTRHO -// ----------------------------------------------------------------------- -// Static data, see DistrhoUI.cpp - -extern const char* g_nextBundlePath; - // ----------------------------------------------------------------------- // UI exporter class @@ -75,12 +70,10 @@ public: uiData->setSizeCallbackFunc = setSizeCall; uiData->fileRequestCallbackFunc = fileRequestCall; - g_nextBundlePath = bundlePath; UI::PrivateData::s_nextPrivateData = uiData; UI* const uiPtr = createUI(); - g_nextBundlePath = nullptr; // enter context called in the PluginWindow constructor, see DistrhoUIPrivateData.hpp uiData->window->leaveContext(); UI::PrivateData::s_nextPrivateData = nullptr; @@ -201,12 +194,18 @@ public: uiData->window->focus(); uiData->app.addIdleCallback(cb); uiData->app.exec(); + uiData->app.removeIdleCallback(cb); } void exec_idle() { DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, ); + #if DISTRHO_UI_USE_WEB_VIEW + if (uiData->webview != nullptr) + webViewIdle(uiData->webview); + #endif + ui->uiIdle(); uiData->app.repaintIfNeeeded(); } @@ -223,6 +222,12 @@ public: DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, false); uiData->app.idle(); + + #if DISTRHO_UI_USE_WEB_VIEW + if (uiData->webview != nullptr) + webViewIdle(uiData->webview); + #endif + ui->uiIdle(); uiData->app.repaintIfNeeeded(); return ! uiData->app.isQuitting(); @@ -252,6 +257,12 @@ public: DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); uiData->app.triggerIdleCallbacks(); + + #if DISTRHO_UI_USE_WEB_VIEW + if (uiData->webview != nullptr) + webViewIdle(uiData->webview); + #endif + ui->uiIdle(); uiData->app.repaintIfNeeeded(); } diff --git a/distrho/src/DistrhoUIPrivateData.hpp b/distrho/src/DistrhoUIPrivateData.hpp index cbff4988..03a5b253 100644 --- a/distrho/src/DistrhoUIPrivateData.hpp +++ b/distrho/src/DistrhoUIPrivateData.hpp @@ -32,6 +32,10 @@ # include #endif +#if DISTRHO_UI_USE_WEB_VIEW +# include "extra/WebView.hpp" +#endif + #if defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_DSSI) # define DISTRHO_UI_IS_STANDALONE 1 #else @@ -247,6 +251,9 @@ struct UI::PrivateData { // DGL PluginApplication app; ScopedPointer window; + #if DISTRHO_UI_USE_WEB_VIEW + WebViewHandle webview; + #endif // DSP double sampleRate; @@ -279,6 +286,9 @@ struct UI::PrivateData { PrivateData(const char* const appClassName) noexcept : app(appClassName), window(nullptr), + #if DISTRHO_UI_USE_WEB_VIEW + webview(nullptr), + #endif sampleRate(0), parameterOffset(0), dspPtr(nullptr), @@ -359,10 +369,13 @@ struct UI::PrivateData { } // implemented below, after PluginWindow - bool fileRequestCallback(const char* const key); + bool fileRequestCallback(const char* key); static UI::PrivateData* s_nextPrivateData; static PluginWindow& createNextWindow(UI* ui, uint width, uint height, bool adjustForScaleFactor); + #if DISTRHO_UI_USE_WEB_VIEW + static void webViewMessageCallback(void* arg, char* msg); + #endif }; // ----------------------------------------------------------------------- diff --git a/examples/WebMeters/CMakeLists.txt b/examples/WebMeters/CMakeLists.txt new file mode 100644 index 00000000..fd136d36 --- /dev/null +++ b/examples/WebMeters/CMakeLists.txt @@ -0,0 +1,12 @@ +# CMake file for DISTRHO Plugins # +# ------------------------------ # + +dpf_add_plugin(d_meters + TARGETS jack dssi lv2 vst2 vst3 clap + FILES_DSP + ExamplePluginMeters.cpp + FILES_UI + ExampleUIMeters.cpp) + +target_include_directories( + d_meters PUBLIC ".") diff --git a/examples/WebMeters/DistrhoPluginInfo.h b/examples/WebMeters/DistrhoPluginInfo.h new file mode 100644 index 00000000..6500131e --- /dev/null +++ b/examples/WebMeters/DistrhoPluginInfo.h @@ -0,0 +1,43 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 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 + * 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. + */ + +#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED +#define DISTRHO_PLUGIN_INFO_H_INCLUDED + +#define DISTRHO_PLUGIN_BRAND "DISTRHO" +#define DISTRHO_PLUGIN_NAME "Web Meters" +#define DISTRHO_PLUGIN_URI "http://distrho.sf.net/examples/WebMeters" +#define DISTRHO_PLUGIN_CLAP_ID "studio.kx.distrho.examples.webmeters" + +#define DISTRHO_PLUGIN_BRAND_ID Dstr +#define DISTRHO_PLUGIN_UNIQUE_ID wMtr + +#define DISTRHO_PLUGIN_HAS_UI 1 +#define DISTRHO_PLUGIN_IS_RT_SAFE 1 +#define DISTRHO_PLUGIN_NUM_INPUTS 2 +#define DISTRHO_PLUGIN_NUM_OUTPUTS 2 +#define DISTRHO_PLUGIN_WANT_STATE 1 +#define DISTRHO_UI_FILE_BROWSER 0 +#define DISTRHO_UI_USER_RESIZABLE 1 +#define DISTRHO_UI_USE_WEB_VIEW 1 + +#define METER_COLOR_GREEN 0 +#define METER_COLOR_BLUE 1 + +#define DISTRHO_UI_DEFAULT_WIDTH 100 +#define DISTRHO_UI_DEFAULT_HEIGHT 500 + +#endif // DISTRHO_PLUGIN_INFO_H_INCLUDED diff --git a/examples/WebMeters/ExamplePluginWebMeters.cpp b/examples/WebMeters/ExamplePluginWebMeters.cpp new file mode 100644 index 00000000..0b188017 --- /dev/null +++ b/examples/WebMeters/ExamplePluginWebMeters.cpp @@ -0,0 +1,288 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 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 + * 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 "DistrhoPlugin.hpp" + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- + +/** + Plugin to demonstrate parameter outputs using meters. + */ +class ExamplePluginMeters : public Plugin +{ +public: + ExamplePluginMeters() + : Plugin(3, 0, 0), // 3 parameters, 0 programs, 0 states + fColor(0.0f), + fOutLeft(0.0f), + fOutRight(0.0f), + fNeedsReset(true) + { + } + +protected: + /* -------------------------------------------------------------------------------------------------------- + * Information */ + + /** + Get the plugin label. + A plugin label follows the same rules as Parameter::symbol, with the exception that it can start with numbers. + */ + const char* getLabel() const override + { + return "meters"; + } + + /** + Get an extensive comment/description about the plugin. + */ + const char* getDescription() const override + { + return "Plugin to demonstrate parameter outputs using meters."; + } + + /** + Get the plugin author/maker. + */ + const char* getMaker() const override + { + return "DISTRHO"; + } + + /** + Get the plugin homepage. + */ + const char* getHomePage() const override + { + return "https://github.com/DISTRHO/DPF"; + } + + /** + Get the plugin license name (a single line of text). + For commercial plugins this should return some short copyright information. + */ + const char* getLicense() const override + { + return "ISC"; + } + + /** + Get the plugin version, in hexadecimal. + */ + uint32_t getVersion() const override + { + return d_version(1, 0, 0); + } + + /* -------------------------------------------------------------------------------------------------------- + * Init */ + + /** + 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 + { + // treat meter audio ports as stereo + port.groupId = kPortGroupStereo; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } + + /** + Initialize the parameter @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initParameter(uint32_t index, Parameter& parameter) override + { + /** + All parameters in this plugin have the same ranges. + */ + parameter.ranges.min = 0.0f; + parameter.ranges.max = 1.0f; + parameter.ranges.def = 0.0f; + + /** + Set parameter data. + */ + switch (index) + { + case 0: + parameter.hints = kParameterIsAutomatable|kParameterIsInteger; + parameter.name = "color"; + parameter.symbol = "color"; + parameter.enumValues.count = 2; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const values = new ParameterEnumerationValue[2]; + parameter.enumValues.values = values; + + values[0].label = "Green"; + values[0].value = METER_COLOR_GREEN; + values[1].label = "Blue"; + values[1].value = METER_COLOR_BLUE; + } + break; + case 1: + parameter.hints = kParameterIsAutomatable|kParameterIsOutput; + parameter.name = "out-left"; + parameter.symbol = "out_left"; + break; + case 2: + parameter.hints = kParameterIsAutomatable|kParameterIsOutput; + parameter.name = "out-right"; + parameter.symbol = "out_right"; + break; + } + } + + /** + Set a state key and default value. + This function will be called once, shortly after the plugin is created. + */ + void initState(uint32_t, String&, String&) override + { + // we are using states but don't want them saved in the host + } + + /* -------------------------------------------------------------------------------------------------------- + * Internal data */ + + /** + Get the current value of a parameter. + */ + float getParameterValue(uint32_t index) const override + { + switch (index) + { + case 0: return fColor; + case 1: return fOutLeft; + case 2: return fOutRight; + } + + return 0.0f; + } + + /** + Change a parameter value. + */ + void setParameterValue(uint32_t index, float value) override + { + // this is only called for input paramters, and we only have one of those. + if (index != 0) return; + + fColor = value; + } + + /** + Change an internal state. + */ + void setState(const char* key, const char*) override + { + if (std::strcmp(key, "reset") != 0) + return; + + fNeedsReset = true; + } + + /* -------------------------------------------------------------------------------------------------------- + * Process */ + + /** + Run/process function for plugins without MIDI input. + */ + void run(const float** inputs, float** outputs, uint32_t frames) override + { + float tmp; + float tmpLeft = 0.0f; + float tmpRight = 0.0f; + + for (uint32_t i=0; i tmpLeft) + tmpLeft = tmp; + + // right + tmp = std::abs(inputs[1][i]); + + if (tmp > tmpRight) + tmpRight = tmp; + } + + if (tmpLeft > 1.0f) + tmpLeft = 1.0f; + if (tmpRight > 1.0f) + tmpRight = 1.0f; + + if (fNeedsReset) + { + fOutLeft = tmpLeft; + fOutRight = tmpRight; + fNeedsReset = false; + } + else + { + if (tmpLeft > fOutLeft) + fOutLeft = tmpLeft; + if (tmpRight > fOutRight) + fOutRight = tmpRight; + } + + // copy inputs over outputs if needed + if (outputs[0] != inputs[0]) + std::memcpy(outputs[0], inputs[0], sizeof(float)*frames); + + if (outputs[1] != inputs[1]) + std::memcpy(outputs[1], inputs[1], sizeof(float)*frames); + } + + // ------------------------------------------------------------------------------------------------------- + +private: + /** + Parameters. + */ + float fColor, fOutLeft, fOutRight; + + /** + Boolean used to reset meter values. + The UI will send a "reset" message which sets this as true. + */ + volatile bool fNeedsReset; + + /** + Set our plugin class as non-copyable and add a leak detector just in case. + */ + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExamplePluginMeters) +}; + +/* ------------------------------------------------------------------------------------------------------------ + * Plugin entry point, called by DPF to create a new plugin instance. */ + +Plugin* createPlugin() +{ + return new ExamplePluginMeters(); +} + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/examples/WebMeters/ExampleUIWebMeters.cpp b/examples/WebMeters/ExampleUIWebMeters.cpp new file mode 100644 index 00000000..7240d3f9 --- /dev/null +++ b/examples/WebMeters/ExampleUIWebMeters.cpp @@ -0,0 +1,61 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 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 + * 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 "DistrhoUI.hpp" + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- + +class ExampleUIMeters : public UI +{ +public: + ExampleUIMeters() + : UI() + { + /* + const double scaleFactor = getScaleFactor(); + + if (d_isNotEqual(scaleFactor, 1.0)) + { + setGeometryConstraints(DISTRHO_UI_DEFAULT_WIDTH * scaleFactor, DISTRHO_UI_DEFAULT_HEIGHT * scaleFactor); + setSize(DISTRHO_UI_DEFAULT_WIDTH * scaleFactor, DISTRHO_UI_DEFAULT_HEIGHT * scaleFactor); + } + else + { + setGeometryConstraints(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT); + } + */ + } + +private: + /** + Set our UI class as non-copyable and add a leak detector just in case. + */ + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExampleUIMeters) +}; + +/* ------------------------------------------------------------------------------------------------------------ + * UI entry point, called by DPF to create a new UI instance. */ + +UI* createUI() +{ + return new ExampleUIMeters(); +} + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/examples/WebMeters/Makefile b/examples/WebMeters/Makefile new file mode 100644 index 00000000..b2a0190d --- /dev/null +++ b/examples/WebMeters/Makefile @@ -0,0 +1,68 @@ +#!/usr/bin/make -f +# Makefile for DISTRHO Plugins # +# ---------------------------- # +# Created by falkTX +# + +# -------------------------------------------------------------- +# Project name, used for binaries + +NAME = d_web + +# -------------------------------------------------------------- +# Files to build + +FILES_DSP = \ + ExamplePluginWebMeters.cpp + +FILES_UI = \ + ExampleUIWebMeters.cpp + +# -------------------------------------------------------------- +# Do some magic + +# MACOS_NO_DEAD_STRIP = true +UI_TYPE = webview +include ../../Makefile.plugins.mk + +# BUILD_CXX_FLAGS += $(shell pkg-config --cflags gtk+-3.0 webkit2gtk-4.0) +BUILD_CXX_FLAGS += -Wno-unused-parameter -Wno-unused-result +BUILD_CXX_FLAGS += -Wno-deprecated-declarations +# LINK_FLAGS += $(shell pkg-config --libs gtk+-3.0 webkit2gtk-4.0) + +# -------------------------------------------------------------- +# Enable all possible plugin types + +ifeq ($(HAVE_OPENGL),true) + +TARGETS += jack + +ifneq ($(MACOS_OR_WINDOWS),true) +ifeq ($(HAVE_LIBLO),true) +TARGETS += dssi +endif # HAVE_LIBLO +endif # MACOS_OR_WINDOWS + +TARGETS += lv2_sep +TARGETS += vst2 +TARGETS += vst3 +TARGETS += clap +TARGETS += au + +endif # HAVE_OPENGL + +ifeq ($(MACOS_APP_BUNDLE),true) +jackfiles += $(TARGET_DIR)/$(NAME).app/Contents/Resources/index.html +else +jackfiles += $(TARGET_DIR)/resources/index.html +endif + +vst3files += $(TARGET_DIR)/$(NAME).vst3/Contents/Resources/index.html + +all: $(TARGETS) $(jackfiles) $(vst3files) + +%/index.html: index.html + -$(SILENT)$(shell mkdir -p "$(shell dirname $(abspath $@))") + install -m 644 $< $(abspath $@) + +# -------------------------------------------------------------- diff --git a/examples/WebMeters/README.md b/examples/WebMeters/README.md new file mode 100644 index 00000000..a2386d7b --- /dev/null +++ b/examples/WebMeters/README.md @@ -0,0 +1,8 @@ +# Meters example + +This example will show how parameter outputs can be used for UI meters in DPF.
+The plugin will inspect the host audio buffer but it won't change it in any way.
+ +In this example the UI will display a simple meter based on the plugin's parameter outputs.
+In order to make drawing easier the UI uses NanoVG instead of raw OpenGL.
+Please see the Parameters and States examples before studying this one.
diff --git a/examples/WebMeters/index.html b/examples/WebMeters/index.html new file mode 100644 index 00000000..5d2c12b5 --- /dev/null +++ b/examples/WebMeters/index.html @@ -0,0 +1,150 @@ + + + + + + + + + +

 

+
+
+
+
+
+
+
+
+ +