Signed-off-by: falkTX <falktx@falktx.com>pull/457/head
| @@ -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) | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * 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 | |||
| @@ -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 | |||
| @@ -415,10 +415,10 @@ WebViewHandle webViewCreate(const char* const url, | |||
| SetParent(hwnd, reinterpret_cast<HWND>(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]; | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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<UI::PrivateData*>(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<char*>(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 | |||
| } | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| @@ -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(); | |||
| } | |||
| @@ -32,6 +32,10 @@ | |||
| # include <string> | |||
| #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<PluginWindow> 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 | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| @@ -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 ".") | |||
| @@ -0,0 +1,43 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * 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 | |||
| @@ -0,0 +1,288 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * 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<frames; ++i) | |||
| { | |||
| // left | |||
| tmp = std::abs(inputs[0][i]); | |||
| if (tmp > 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 | |||
| @@ -0,0 +1,61 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * 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 | |||
| @@ -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 $@) | |||
| # -------------------------------------------------------------- | |||
| @@ -0,0 +1,8 @@ | |||
| # Meters example | |||
| This example will show how parameter outputs can be used for UI meters in DPF.<br/> | |||
| The plugin will inspect the host audio buffer but it won't change it in any way.<br/> | |||
| In this example the UI will display a simple meter based on the plugin's parameter outputs.<br/> | |||
| In order to make drawing easier the UI uses NanoVG instead of raw OpenGL.<br/> | |||
| Please see the Parameters and States examples before studying this one.<br/> | |||
| @@ -0,0 +1,150 @@ | |||
| <!DOCTYPE html> | |||
| <html lang=""> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <title></title> | |||
| <script> | |||
| const METER_COLOR_GREEN = 0; | |||
| const METER_COLOR_BLUE = 1; | |||
| const kSmoothMultiplier = 3.0; | |||
| let fColorValue = null; | |||
| let fColor = 'rgb(93, 231, 61)'; | |||
| let fOutLeft = 0.0; | |||
| let fOutRight = 0.0; | |||
| setTimeout(function() { | |||
| document.getElementById('user-agent').textContent = window.navigator.userAgent; | |||
| document.getElementById('left-meter').onclick = function() { | |||
| console.log("left meter clicked"); | |||
| fColorValue = fColorValue == 1 ? 0 : 1; | |||
| updateColor(fColorValue, true); | |||
| setParameterValue(0, fColorValue); | |||
| repaint(); | |||
| } | |||
| document.getElementById('right-meter').onclick = function() { | |||
| console.log("right meter clicked"); | |||
| fColorValue = fColorValue == 1 ? 0 : 1; | |||
| updateColor(fColorValue, true); | |||
| setParameterValue(0, fColorValue); | |||
| repaint(); | |||
| } | |||
| }, 1) | |||
| function repaint() { | |||
| const lmeter = document.getElementById('left-meter-x'); | |||
| const rmeter = document.getElementById('right-meter-x'); | |||
| lmeter.setAttribute('style', 'background:' + fColor + ';top:' + (100 * (1.0 - fOutLeft)) + '%;height:' + (100 * fOutLeft) + '%'); | |||
| rmeter.setAttribute('style', 'background:' + fColor + ';top:' + (100 * (1.0 - fOutRight)) + '%;height:' + (100 * fOutRight) + '%'); | |||
| setTimeout(function() { | |||
| setState('reset', ''); | |||
| }, 1) | |||
| } | |||
| function updateColor(color, forced) { | |||
| if (fColorValue === color && !forced) | |||
| return; | |||
| fColorValue = color; | |||
| switch (color) { | |||
| case METER_COLOR_GREEN: | |||
| fColor = "rgb(93, 231, 61)"; | |||
| break; | |||
| case METER_COLOR_BLUE: | |||
| fColor = "rgb(82, 238, 248)"; | |||
| break; | |||
| } | |||
| repaint(); | |||
| } | |||
| function parameterChanged(index, value) { | |||
| // console.log("paramChanged", index, value) | |||
| switch (index) { | |||
| case 0: // color | |||
| updateColor(parseInt(Math.round(value))); | |||
| break; | |||
| case 1: // out-left | |||
| value = (fOutLeft * kSmoothMultiplier + value) / (kSmoothMultiplier + 1.0); | |||
| /**/ if (value < 0.001) value = 0.0; | |||
| else if (value > 0.999) value = 1.0; | |||
| if (fOutLeft != value) | |||
| { | |||
| fOutLeft = value; | |||
| repaint(); | |||
| } | |||
| break; | |||
| case 2: // out-right | |||
| value = (fOutRight * kSmoothMultiplier + value) / (kSmoothMultiplier + 1.0); | |||
| /**/ if (value < 0.001) value = 0.0; | |||
| else if (value > 0.999) value = 1.0; | |||
| if (fOutRight != value) | |||
| { | |||
| fOutRight = value; | |||
| repaint(); | |||
| } | |||
| break; | |||
| } | |||
| } | |||
| </script> | |||
| <style> | |||
| html, body { | |||
| background: grey; | |||
| color: white; | |||
| margin: 0; | |||
| padding: 0; | |||
| } | |||
| body { | |||
| display: flex; | |||
| flex-direction: column; | |||
| } | |||
| p { | |||
| margin: 6px; | |||
| font-size: 15px; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| width: calc(100% - 12px); | |||
| height: 15px; | |||
| white-space: nowrap; | |||
| } | |||
| #meters { | |||
| display: flex; | |||
| flex-direction: row; | |||
| } | |||
| .meter { | |||
| background: black; | |||
| margin: 6px; | |||
| margin-top: 0px; | |||
| width: calc(50vw - 9px); | |||
| height: calc(100vh - 12px - 6px - 15px); | |||
| } | |||
| .meter:first-child { | |||
| margin-right: 3px; | |||
| } | |||
| .meter:last-child { | |||
| margin-left: 3px; | |||
| } | |||
| .meter-x { | |||
| background: rgb(93, 231, 61); | |||
| position: relative; | |||
| top: 0%; | |||
| left: 0; | |||
| width: 100%; | |||
| height: 0%; | |||
| } | |||
| </style> | |||
| </head> | |||
| <body> | |||
| <p id="user-agent"> </p> | |||
| <div id="meters"> | |||
| <div class="meter" id="left-meter"> | |||
| <div class="meter-x" id="left-meter-x"></div> | |||
| </div> | |||
| <div class="meter" id="right-meter"> | |||
| <div class="meter-x" id="right-meter-x"></div> | |||
| </div> | |||
| </div> | |||
| </body> | |||
| </html> | |||