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