Browse Source

Initial implementation for DISTRHO_UI_USE_WEB_VIEW

Signed-off-by: falkTX <falktx@falktx.com>
pull/457/head
falkTX 11 months ago
parent
commit
bcd834cc64
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
16 changed files with 895 additions and 37 deletions
  1. +28
    -3
      Makefile.plugins.mk
  2. +1
    -1
      distrho/DistrhoPluginUtils.hpp
  3. +1
    -2
      distrho/DistrhoUI.hpp
  4. +6
    -6
      distrho/extra/WebViewImpl.cpp
  5. +1
    -1
      distrho/extra/WebViewImpl.hpp
  6. +1
    -1
      distrho/src/DistrhoPluginChecks.h
  7. +195
    -15
      distrho/src/DistrhoUI.cpp
  8. +18
    -7
      distrho/src/DistrhoUIInternal.hpp
  9. +14
    -1
      distrho/src/DistrhoUIPrivateData.hpp
  10. +12
    -0
      examples/WebMeters/CMakeLists.txt
  11. +43
    -0
      examples/WebMeters/DistrhoPluginInfo.h
  12. +288
    -0
      examples/WebMeters/ExamplePluginWebMeters.cpp
  13. +61
    -0
      examples/WebMeters/ExampleUIWebMeters.cpp
  14. +68
    -0
      examples/WebMeters/Makefile
  15. +8
    -0
      examples/WebMeters/README.md
  16. +150
    -0
      examples/WebMeters/index.html

+ 28
- 3
Makefile.plugins.mk View File

@@ -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
- 1
distrho/DistrhoPluginUtils.hpp View File

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


+ 1
- 2
distrho/DistrhoUI.hpp View File

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


+ 6
- 6
distrho/extra/WebViewImpl.cpp View File

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


+ 1
- 1
distrho/extra/WebViewImpl.hpp View File

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


+ 1
- 1
distrho/src/DistrhoPluginChecks.h View File

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



+ 195
- 15
distrho/src/DistrhoUI.cpp View File

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

/* ------------------------------------------------------------------------------------------------------------


+ 18
- 7
distrho/src/DistrhoUIInternal.hpp View File

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


+ 14
- 1
distrho/src/DistrhoUIPrivateData.hpp View File

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

// -----------------------------------------------------------------------


+ 12
- 0
examples/WebMeters/CMakeLists.txt View File

@@ -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 ".")

+ 43
- 0
examples/WebMeters/DistrhoPluginInfo.h View File

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

+ 288
- 0
examples/WebMeters/ExamplePluginWebMeters.cpp View File

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

+ 61
- 0
examples/WebMeters/ExampleUIWebMeters.cpp View File

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

+ 68
- 0
examples/WebMeters/Makefile View File

@@ -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 $@)

# --------------------------------------------------------------

+ 8
- 0
examples/WebMeters/README.md View File

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

+ 150
- 0
examples/WebMeters/index.html View File

@@ -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">&nbsp;</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>

Loading…
Cancel
Save