Browse Source

Initial implementation of modgui over wasm

Signed-off-by: falkTX <falktx@falktx.com>
pull/397/head
falkTX 2 years ago
parent
commit
da582de935
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
7 changed files with 589 additions and 8 deletions
  1. +98
    -0
      Makefile.plugins.mk
  2. +8
    -1
      cmake/DPF-plugin.cmake
  3. +296
    -3
      distrho/src/DistrhoPluginLV2export.cpp
  4. +182
    -0
      distrho/src/DistrhoUILV2.cpp
  5. +1
    -0
      examples/Info/CMakeLists.txt
  6. +1
    -4
      examples/Info/DistrhoPluginInfo.h
  7. +3
    -0
      examples/Info/Makefile

+ 98
- 0
Makefile.plugins.mk View File

@@ -68,6 +68,10 @@ ifeq ($(HAVE_SDL2),true)
BASE_FLAGS += -DHAVE_SDL2
endif

ifneq ($(MODGUI_CLASS_NAME),)
BASE_FLAGS += -DDISTRHO_PLUGIN_MODGUI_CLASS_NAME='"$(MODGUI_CLASS_NAME)"'
endif

# always needed
ifneq ($(HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS),true)
ifneq ($(STATIC_BUILD),true)
@@ -524,6 +528,100 @@ $(lv2_ui): $(OBJS_UI) $(BUILD_DIR)/DistrhoUIMain_LV2.cpp.o $(DGL_LIB)
@echo "Creating LV2 plugin UI for $(NAME)"
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(EXTRA_UI_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_LV2UI) -o $@

# ---------------------------------------------------------------------------------------------------------------------
# LV2 modgui

ifeq ($(MODGUI_BUILD),true)
ifeq ($(MODGUI_CLASS_NAME),)
$(error MODGUI_CLASS_NAME undefined)
endif
endif

# clear all possible flags coming from DPF, while keeping any extra flags specified for this build
MODGUI_IGNORED_FLAGS = -fdata-sections
MODGUI_IGNORED_FLAGS += -ffast-math
MODGUI_IGNORED_FLAGS += -ffunction-sections
MODGUI_IGNORED_FLAGS += -fno-gnu-unique
MODGUI_IGNORED_FLAGS += -fprefetch-loop-arrays
MODGUI_IGNORED_FLAGS += -fvisibility=hidden
MODGUI_IGNORED_FLAGS += -fvisibility-inlines-hidden
MODGUI_IGNORED_FLAGS += -fPIC
MODGUI_IGNORED_FLAGS += -ldl
MODGUI_IGNORED_FLAGS += -mfpmath=sse
MODGUI_IGNORED_FLAGS += -msse
MODGUI_IGNORED_FLAGS += -msse2
MODGUI_IGNORED_FLAGS += -mtune=generic
MODGUI_IGNORED_FLAGS += -pipe
MODGUI_IGNORED_FLAGS += -std=gnu99
MODGUI_IGNORED_FLAGS += -std=gnu++11
MODGUI_IGNORED_FLAGS += -DDISTRHO_PLUGIN_MODGUI_CLASS_NAME='"$(MODGUI_CLASS_NAME)"'
MODGUI_IGNORED_FLAGS += -DDGL_OPENGL
MODGUI_IGNORED_FLAGS += -DGL_SILENCE_DEPRECATION=1
MODGUI_IGNORED_FLAGS += -DHAVE_ALSA
MODGUI_IGNORED_FLAGS += -DHAVE_DGL
MODGUI_IGNORED_FLAGS += -DHAVE_JACK
MODGUI_IGNORED_FLAGS += -DHAVE_LIBLO
MODGUI_IGNORED_FLAGS += -DHAVE_OPENGL
MODGUI_IGNORED_FLAGS += -DHAVE_PULSEAUDIO
MODGUI_IGNORED_FLAGS += -DHAVE_RTAUDIO
MODGUI_IGNORED_FLAGS += -DHAVE_SDL2
MODGUI_IGNORED_FLAGS += -DNDEBUG
MODGUI_IGNORED_FLAGS += -DPIC
MODGUI_IGNORED_FLAGS += -I.
MODGUI_IGNORED_FLAGS += -I$(DPF_PATH)/distrho
MODGUI_IGNORED_FLAGS += -I$(DPF_PATH)/dgl
MODGUI_IGNORED_FLAGS += -I$(MOD_WORKDIR)/modduo-static/staging/usr/include
MODGUI_IGNORED_FLAGS += -I$(MOD_WORKDIR)/modduox-static/staging/usr/include
MODGUI_IGNORED_FLAGS += -I$(MOD_WORKDIR)/moddwarf/staging/usr/include
MODGUI_IGNORED_FLAGS += -L$(MOD_WORKDIR)/modduo-static/staging/usr/lib
MODGUI_IGNORED_FLAGS += -L$(MOD_WORKDIR)/modduox-static/staging/usr/lib
MODGUI_IGNORED_FLAGS += -L$(MOD_WORKDIR)/moddwarf/staging/usr/lib
MODGUI_IGNORED_FLAGS += -MD
MODGUI_IGNORED_FLAGS += -MP
MODGUI_IGNORED_FLAGS += -O2
MODGUI_IGNORED_FLAGS += -O3
MODGUI_IGNORED_FLAGS += -Wall
MODGUI_IGNORED_FLAGS += -Wextra
MODGUI_IGNORED_FLAGS += -Wl,-O1,--as-needed,--gc-sections
MODGUI_IGNORED_FLAGS += -Wl,-dead_strip,-dead_strip_dylibs
MODGUI_IGNORED_FLAGS += -Wl,-x
MODGUI_IGNORED_FLAGS += -Wl,--gc-sections
MODGUI_IGNORED_FLAGS += -Wl,--no-undefined
MODGUI_IGNORED_FLAGS += -Wl,--strip-all
MODGUI_IGNORED_FLAGS += -Wno-deprecated-declarations
MODGUI_IGNORED_FLAGS += $(DGL_FLAGS)
MODGUI_CFLAGS = $(filter-out $(MODGUI_IGNORED_FLAGS),$(BUILD_C_FLAGS)) -D__MOD_DEVICES__
MODGUI_CXXFLAGS = $(filter-out $(MODGUI_IGNORED_FLAGS),$(BUILD_CXX_FLAGS)) -D__MOD_DEVICES__
MODGUI_LDFLAGS = $(filter-out $(MODGUI_IGNORED_FLAGS),$(LINK_FLAGS))

$(TARGET_DIR)/$(NAME).lv2/modgui/module.js: $(OBJS_UI) $(BUILD_DIR)/DistrhoUIMain_LV2.cpp.o $(DGL_LIB)
-@mkdir -p $(shell dirname $@)
@echo "Creating LV2 plugin modgui for $(NAME)"
$(SILENT)$(CXX) $^ $(LINK_FLAGS) $(EXTRA_LIBS) $(EXTRA_UI_LIBS) $(DGL_LIBS) \
-sALLOW_TABLE_GROWTH -sMODULARIZE=1 -sMAIN_MODULE=2 -sDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 \
-sEXPORTED_FUNCTIONS="['_malloc','_free','_modgui_init','_modgui_param_set','_modgui_patch_set','_modgui_cleanup']" \
-sEXPORTED_RUNTIME_METHODS=['addFunction','lengthBytesUTF8','stringToUTF8','UTF8ToString'] \
-sEXPORT_NAME="Module_$(MODGUI_CLASS_NAME)" \
-o $@

modgui:
$(MAKE) $(TARGET_DIR)/$(NAME).lv2/modgui/module.js \
EXE_WRAPPER= \
FILE_BROWSER_DISABLED=true \
HAVE_OPENGL=true \
MODGUI_BUILD=true \
NOOPT=true \
PKG_CONFIG=false \
USE_GLES2=true \
AR=emar \
CC=emcc \
CXX=em++ \
CFLAGS="$(MODGUI_CFLAGS)" \
CXXFLAGS="$(MODGUI_CXXFLAGS)" \
LDFLAGS="$(MODGUI_LDFLAGS)"

.PHONY: modgui

# ---------------------------------------------------------------------------------------------------------------------
# VST2



+ 8
- 1
cmake/DPF-plugin.cmake View File

@@ -87,6 +87,9 @@ include(CMakeParseArguments)
# list of sources which are part of the UI
# empty indicates the plugin does not have UI
#
# `MODGUI_CLASS_NAME`
# class name to use for modgui builds
#
# `MONOLITHIC`
# build LV2 as a single binary for UI and DSP
#
@@ -95,7 +98,7 @@ include(CMakeParseArguments)
#
function(dpf_add_plugin NAME)
set(options MONOLITHIC NO_SHARED_RESOURCES)
set(oneValueArgs UI_TYPE)
set(oneValueArgs MODGUI_CLASS_NAME UI_TYPE)
set(multiValueArgs FILES_COMMON FILES_DSP FILES_UI TARGETS)
cmake_parse_arguments(_dpf_plugin "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

@@ -134,6 +137,10 @@ function(dpf_add_plugin NAME)
target_include_directories("${NAME}" PUBLIC
"${DPF_ROOT_DIR}/distrho")

if(_dpf_plugin_MODGUI_CLASS_NAME)
target_compile_definitions("${NAME}" PUBLIC "DISTRHO_PLUGIN_MODGUI_CLASS_NAME=\"${_dpf_plugin_MODGUI_CLASS_NAME}\"")
endif()

if((NOT WIN32) AND (NOT APPLE) AND (NOT HAIKU))
target_link_libraries("${NAME}" PRIVATE "dl")
endif()


+ 296
- 3
distrho/src/DistrhoPluginLV2export.cpp View File

@@ -42,7 +42,11 @@
# include "mod-license.h"
#endif

#ifndef DISTRHO_OS_WINDOWS
#ifdef DISTRHO_OS_WINDOWS
# include <direct.h>
#else
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
#endif

@@ -944,11 +948,11 @@ void lv2_generate_ttl(const char* const basename)
}
}

#ifdef DISTRHO_PLUGIN_BRAND
#ifdef DISTRHO_PLUGIN_BRAND
// MOD
pluginString += " mod:brand \"" DISTRHO_PLUGIN_BRAND "\" ;\n";
pluginString += " mod:label \"" DISTRHO_PLUGIN_NAME "\" ;\n\n";
#endif
#endif

// name
{
@@ -1214,6 +1218,295 @@ void lv2_generate_ttl(const char* const basename)
std::cout << " done!" << std::endl;
}

#if DISTRHO_PLUGIN_USES_MODGUI
{
std::cout << "Writing modgui.ttl..."; std::cout.flush();
std::fstream modguiFile("modgui.ttl", std::ios::out);

String modguiString;
modguiString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
modguiString += "@prefix modgui: <http://moddevices.com/ns/modgui#> .\n";
modguiString += "\n";

modguiString += "<" DISTRHO_PLUGIN_URI ">\n";
modguiString += " modgui:gui [\n";
#ifdef DISTRHO_PLUGIN_BRAND
modguiString += " modgui:brand \"" DISTRHO_PLUGIN_BRAND "\" ;\n";
#endif
modguiString += " modgui:label \"" DISTRHO_PLUGIN_NAME "\" ;\n";
modguiString += " modgui:resourcesDirectory <modgui> ;\n";
modguiString += " modgui:iconTemplate <modgui/icon.html> ;\n";
modguiString += " modgui:javascript <modgui/javascript.js> ;\n";
modguiString += " modgui:stylesheet <modgui/stylesheet.css> ;\n";
modguiString += " modgui:screenshot <modgui/screenshot.png> ;\n";
modguiString += " modgui:thumbnail <modgui/thumbnail.png> ;\n";

uint32_t numParametersOutputs = 0;
for (uint32_t i=0, count=plugin.getParameterCount(); i < count; ++i)
{
if (plugin.isParameterOutput(i))
++numParametersOutputs;
}
if (numParametersOutputs != 0)
{
modguiString += " modgui:monitoredOutputs [\n";
for (uint32_t i=0, j=0, count=plugin.getParameterCount(); i < count; ++i)
{
if (!plugin.isParameterOutput(i))
continue;
modguiString += " lv2:symbol \"" + plugin.getParameterSymbol(i) + "\" ;\n";
if (++j != numParametersOutputs)
modguiString += " ] , [\n";
}
modguiString += " ] ;\n";
}

modguiString += " ] .\n";

modguiFile << modguiString;
modguiFile.close();
std::cout << " done!" << std::endl;
}

#ifdef DISTRHO_OS_WINDOWS
::_mkdir("modgui");
#else
::mkdir("modgui", 0755);
#endif

{
std::cout << "Writing modgui/javascript.js..."; std::cout.flush();
std::fstream jsFile("modgui/javascript.js", std::ios::out);

String jsString;
jsString += "function(e,f){\n";
jsString += "'use strict';\nvar ps=[";

for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
jsString += "'lv2_" + plugin.getAudioPort(false, i).symbol + "',";
for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
jsString += "'lv2_" + plugin.getAudioPort(true, i).symbol + "',";
#if DISTRHO_LV2_USE_EVENTS_IN
jsString += "'lv2_events_in',";
#endif
#if DISTRHO_LV2_USE_EVENTS_OUT
jsString += "'lv2_events_out',";
#endif
#if DISTRHO_PLUGIN_WANT_LATENCY
jsString += "'lv2_latency',";
#endif

int32_t enabledIndex = INT32_MAX;
for (uint32_t i=0, count=plugin.getParameterCount(); i < count; ++i)
{
jsString += "'" + plugin.getParameterSymbol(i) + "',";
if (plugin.getParameterDesignation(i) == kParameterDesignationBypass)
enabledIndex = i;
}
jsString += "];\n";
jsString += "var ei=" + String(enabledIndex != INT32_MAX ? enabledIndex : -1) + ";\n\n";
jsString += "if(e.type==='start'){\n";
jsString += "e.data.p={p:{},c:{},};\n\n";
jsString += "var err=[];\n";
jsString += "if(typeof(WebAssembly)==='undefined'){err.push('WebAssembly unsupported');}\n";
jsString += "else{\n";
jsString += "if(!WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,5,3,1,0,1,10,14,1,12,0,65,0,65,0,65,0,252,10,0,0,11])))";
jsString += "err.push('Bulk Memory Operations unsupported');\n";
jsString += "if(!WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,2,8,1,1,97,1,98,3,127,1,6,6,1,127,1,65,0,11,7,5,1,1,97,3,1])))";
jsString += "err.push('Importable/Exportable mutable globals unsupported');\n";
jsString += "}\n";
jsString += "if(err.length!==0){/* errors err.join('<br>')*/return;}\n\n";
jsString += "var s=document.createElement('script');\n";
jsString += "s.setAttribute('async',true);\n";
jsString += "s.setAttribute('src','/resources/module.js?uri='+escape(\"" DISTRHO_PLUGIN_URI "\")+'&r='+VERSION/*f.get_custom_resource_filename('module.js')*/);\n";
jsString += "s.setAttribute('type','text/javascript');\n";
jsString += "s.onload=function(){\n";
jsString += " Module_" DISTRHO_PLUGIN_MODGUI_CLASS_NAME "({\n";
jsString += " locateFile: function(p,_){return '/resources/'+p+'?uri='+escape(\"" DISTRHO_PLUGIN_URI "\")+'&r='+VERSION/*return f.get_custom_resource_filename(p);*/},\n";
jsString += " postRun:function(m){\n";
jsString += " var cn=e.icon.attr('mod-instance').replaceAll('/','_');\n";
jsString += " var cnl=m.lengthBytesUTF8(cn) + 1;\n";
jsString += " var cna=m._malloc(cnl);\n";
jsString += " m.stringToUTF8(cn, cna, cnl);\n";
jsString += " e.icon.find('canvas')[0].id=cn;\n";
jsString += " var a=m.addFunction(function(i,v){f.set_port_value(ps[i],v);},'vif');\n";
jsString += " var b=m.addFunction(function(u,v){f.patch_set(m.UTF8ToString(u),'s',m.UTF8ToString(v));},'vpp');\n";
jsString += " var h=m._modgui_init(cna,a,b);\n";
jsString += " m._free(cna);\n";
jsString += " e.data.h=h;\n";
jsString += " e.data.m=m;\n";
jsString += " for(var u in e.data.p.p){\n";
jsString += " var ul=m.lengthBytesUTF8(u)+1,ua=m._malloc(ul),v=e.data.p.p[u],vl=m.lengthBytesUTF8(v)+1,va=m._malloc(vl);\n";
jsString += " m.stringToUTF8(u,ua,ul);\n";
jsString += " m.stringToUTF8(v,va,vl);\n";
jsString += " m._modgui_patch_set(h, ua, va);\n";
jsString += " m._free(ua);\n";
jsString += " m._free(va);\n";
jsString += " }\n";
jsString += " for(var symbol in e.data.p.c){m._modgui_param_set(h,ps.indexOf(symbol),e.data.p.c[symbol]);}\n";
jsString += " delete e.data.p;\n";
jsString += " window.dispatchEvent(new Event('resize'));\n";
jsString += " },\n";
jsString += " canvas:(function(){var c=e.icon.find('canvas')[0];c.addEventListener('webglcontextlost',function(e2){alert('WebGL context lost. You will need to reload the page.');e2.preventDefault();},false);return c;})(),\n";
jsString += " });\n";
jsString += "};\n";
jsString += "document.head.appendChild(s);\n\n";
jsString += "}else if(e.type==='change'){\n\n";
jsString += "if(e.data.h && e.data.m){\n";
jsString += " var m=e.data.m;\n";
jsString += " if(e.uri){\n";
jsString += " var ul=m.lengthBytesUTF8(e.uri)+1,ua=m._malloc(ul),vl=m.lengthBytesUTF8(e.value)+1,va=m._malloc(vl);\n";
jsString += " m.stringToUTF8(e.uri,ua,ul);\n";
jsString += " m.stringToUTF8(e.value,va,vl);\n";
jsString += " m._modgui_patch_set(e.data.h,ua,va);\n";
jsString += " m._free(ua);\n";
jsString += " m._free(va);\n";
jsString += " }else if(e.symbol===':bypass'){return;\n";
jsString += " }else{m._modgui_param_set(e.data.h,ps.indexOf(e.symbol),e.value);}\n";
jsString += "}else{\n";
jsString += " if(e.symbol===':bypass')return;\n";
jsString += " if(e.uri){e.data.p.p[e.uri]=e.value;}else{e.data.p.c[e.symbol]=e.value;}\n";
jsString += "}\n\n";
jsString += "}\n}\n";
jsFile << jsString;
jsFile.close();
std::cout << " done!" << std::endl;
}

{
std::cout << "Writing modgui/icon.html..."; std::cout.flush();
std::fstream iconFile("modgui/icon.html", std::ios::out);

iconFile << "<div class='" DISTRHO_PLUGIN_MODGUI_CLASS_NAME " mod-pedal'>" << std::endl;
iconFile << " <div mod-role='drag-handle' class='mod-drag-handle'></div>" << std::endl;
iconFile << " <div class='mod-plugin-title'><h1>{{#brand}}{{brand}} | {{/brand}}{{label}}</h1></div>" << std::endl;
iconFile << " <div class='mod-light on' mod-role='bypass-light'></div>" << std::endl;
iconFile << " <div class='mod-control-group mod-switch'>" << std::endl;
iconFile << " <div class='mod-control-group mod-switch-image mod-port transport' mod-role='bypass' mod-widget='film'></div>" << std::endl;
iconFile << " </div>" << std::endl;
iconFile << " <div class='canvas_wrapper'>" << std::endl;
iconFile << " <canvas oncontextmenu='event.preventDefault()' tabindex=-1></canvas>" << std::endl;
iconFile << " </div>" << std::endl;
iconFile << " <div class='mod-pedal-input'>" << std::endl;
iconFile << " {{#effect.ports.audio.input}}" << std::endl;
iconFile << " <div class='mod-input mod-input-disconnected' title='{{name}}' mod-role='input-audio-port' mod-port-symbol='{{symbol}}'>" << std::endl;
iconFile << " <div class='mod-pedal-input-image'></div>" << std::endl;
iconFile << " </div>" << std::endl;
iconFile << " {{/effect.ports.audio.input}}" << std::endl;
iconFile << " {{#effect.ports.midi.input}}" << std::endl;
iconFile << " <div class='mod-input mod-input-disconnected' title='{{name}}' mod-role='input-midi-port' mod-port-symbol='{{symbol}}'>" << std::endl;
iconFile << " <div class='mod-pedal-input-image'></div>" << std::endl;
iconFile << " </div>" << std::endl;
iconFile << " {{/effect.ports.midi.input}}" << std::endl;
iconFile << " {{#effect.ports.cv.input}}" << std::endl;
iconFile << " <div class='mod-input mod-input-disconnected' title='{{name}}' mod-role='input-cv-port' mod-port-symbol='{{symbol}}'>" << std::endl;
iconFile << " <div class='mod-pedal-input-image'></div>" << std::endl;
iconFile << " </div>" << std::endl;
iconFile << " {{/effect.ports.cv.input}}" << std::endl;
iconFile << " </div>" << std::endl;
iconFile << " <div class='mod-pedal-output'>" << std::endl;
iconFile << " {{#effect.ports.audio.output}}" << std::endl;
iconFile << " <div class='mod-output mod-output-disconnected' title='{{name}}' mod-role='output-audio-port' mod-port-symbol='{{symbol}}'>" << std::endl;
iconFile << " <div class='mod-pedal-output-image'></div>" << std::endl;
iconFile << " </div>" << std::endl;
iconFile << " {{/effect.ports.audio.output}}" << std::endl;
iconFile << " {{#effect.ports.midi.output}}" << std::endl;
iconFile << " <div class='mod-output mod-output-disconnected' title='{{name}}' mod-role='output-midi-port' mod-port-symbol='{{symbol}}'>" << std::endl;
iconFile << " <div class='mod-pedal-output-image'></div>" << std::endl;
iconFile << " </div>" << std::endl;
iconFile << " {{/effect.ports.midi.output}}" << std::endl;
iconFile << " {{#effect.ports.cv.output}}" << std::endl;
iconFile << " <div class='mod-output mod-output-disconnected' title='{{name}}' mod-role='output-cv-port' mod-port-symbol='{{symbol}}'>" << std::endl;
iconFile << " <div class='mod-pedal-output-image'></div>" << std::endl;
iconFile << " </div>" << std::endl;
iconFile << " {{/effect.ports.cv.output}}" << std::endl;
iconFile << " </div>" << std::endl;
iconFile << "</div>" << std::endl;

iconFile.close();
std::cout << " done!" << std::endl;
}

{
std::cout << "Writing modgui/stylesheet.css..."; std::cout.flush();
std::fstream stylesheetFile("modgui/stylesheet.css", std::ios::out);

stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal{" << std::endl;
stylesheetFile << " padding:0;" << std::endl;
stylesheetFile << " margin:0;" << std::endl;
stylesheetFile << " width:" + String(DISTRHO_UI_DEFAULT_WIDTH) + "px;" << std::endl;
stylesheetFile << " height:" + String(DISTRHO_UI_DEFAULT_HEIGHT + 50) + "px;" << std::endl;
stylesheetFile << " background:#2a2e32;" << std::endl;
stylesheetFile << " border-radius:20px 20px 0 0;" << std::endl;
stylesheetFile << " color:#fff;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .canvas_wrapper{" << std::endl;
stylesheetFile << " --device-pixel-ratio:1;" << std::endl;
stylesheetFile << " /*image-rendering:pixelated;*/" << std::endl;
stylesheetFile << " /*image-rendering:crisp-edges;*/" << std::endl;
stylesheetFile << " background:#000;" << std::endl;
stylesheetFile << " position:absolute;" << std::endl;
stylesheetFile << " top:50px;" << std::endl;
stylesheetFile << " transform-origin:0 0 0;" << std::endl;
stylesheetFile << " transform:scale(calc(1/var(--device-pixel-ratio)));" << std::endl;
stylesheetFile << " width:" + String(DISTRHO_UI_DEFAULT_WIDTH) + "px;" << std::endl;
stylesheetFile << " height:" + String(DISTRHO_UI_DEFAULT_HEIGHT) + "px;" << std::endl;
stylesheetFile << " z-index:21;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "/*" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .canvas_wrapper:focus-within{" << std::endl;
stylesheetFile << " z-index:21;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "*/" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-plugin-title{" << std::endl;
stylesheetFile << " position:absolute;" << std::endl;
stylesheetFile << " text-align:center;" << std::endl;
stylesheetFile << " width:100%;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal h1{" << std::endl;
stylesheetFile << " font-size:20px;" << std::endl;
stylesheetFile << " font-weight:bold;" << std::endl;
stylesheetFile << " line-height:50px;" << std::endl;
stylesheetFile << " margin:0;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-control-group{" << std::endl;
stylesheetFile << " position:absolute;" << std::endl;
stylesheetFile << " left:5px;" << std::endl;
stylesheetFile << " z-index:35;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-pedal-input," << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-pedal-output{" << std::endl;
stylesheetFile << " top:75px;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-audio-input," << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-audio-output{" << std::endl;
stylesheetFile << " margin-bottom:25px;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .jack-disconnected{" << std::endl;
stylesheetFile << " top:0px!important;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-switch-image{" << std::endl;
stylesheetFile << " background-image: url(/img/switch.png);" << std::endl;
stylesheetFile << " background-position: left center;" << std::endl;
stylesheetFile << " background-repeat: no-repeat;" << std::endl;
stylesheetFile << " background-size: auto 50px;" << std::endl;
stylesheetFile << " font-weight: bold;" << std::endl;
stylesheetFile << " width: 100px;" << std::endl;
stylesheetFile << " height: 50px;" << std::endl;
stylesheetFile << " cursor: pointer;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-switch-image.off{" << std::endl;
stylesheetFile << " background-position: right center !important;" << std::endl;
stylesheetFile << "}" << std::endl;
stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-switch-image.on{" << std::endl;
stylesheetFile << " background-position: left center !important;" << std::endl;
stylesheetFile << "}" << std::endl;

stylesheetFile.close();
std::cout << " done!" << std::endl;
}
#endif

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

#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS


+ 182
- 0
distrho/src/DistrhoUILV2.cpp View File

@@ -715,4 +715,186 @@ const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index)
return (index == 0) ? &sLv2UiDescriptor : nullptr;
}

#if defined(__MOD_DEVICES__) && defined(__EMSCRIPTEN__)
#include <emscripten/html5.h>
#include <string>

typedef void (*_custom_param_set)(uint32_t port_index, float value);
typedef void (*_custom_patch_set)(const char* uri, const char* value);

struct ModguiHandle {
LV2UI_Handle handle;
_custom_param_set param_set;
_custom_patch_set patch_set;
};

enum URIs {
kUriNull,
kUriAtomEventTransfer,
kUriDpfKeyValue,
};

static std::vector<std::string> kURIs;

static LV2_URID lv2_urid_map(LV2_URID_Map_Handle, const char* const uri)
{
for (size_t i=0, size=kURIs.size(); i<size; ++i)
{
if (kURIs[i] == uri)
return i;
}

kURIs.push_back(uri);
return kURIs.size() - 1u;
}

static const char* lv2_urid_unmap(LV2_URID_Map_Handle, const LV2_URID urid)
{
return kURIs[urid].c_str();
}

static void lv2ui_write_function(LV2UI_Controller controller,
uint32_t port_index,
uint32_t buffer_size,
uint32_t port_protocol,
const void* buffer)
{
DISTRHO_SAFE_ASSERT_RETURN(buffer_size >= 1,);

// d_stdout("lv2ui_write_function %p %u %u %u %p", controller, port_index, buffer_size, port_protocol, buffer);
ModguiHandle* const mhandle = static_cast<ModguiHandle*>(controller);

switch (port_protocol)
{
case kUriNull:
mhandle->param_set(port_index, *static_cast<const float*>(buffer));
break;
case kUriAtomEventTransfer:
if (const LV2_Atom* const atom = static_cast<const LV2_Atom*>(buffer))
{
// d_stdout("lv2ui_write_function %u %u:%s", atom->size, atom->type, kURIs[atom->type].c_str());

// if (kURIs[atom->type] == "urn:distrho:KeyValueState")
{
const char* const key = (const char*)(atom + 1);
const char* const value = key + (std::strlen(key) + 1U);
// d_stdout("lv2ui_write_function %s %s", key, value);

String urikey;
urikey = DISTRHO_PLUGIN_URI "#";
urikey += key;

mhandle->patch_set(urikey, value);
}
}
break;
}
}

static void app_idle(void* const handle)
{
static_cast<UiLv2*>(handle)->lv2ui_idle();
}

DISTRHO_PLUGIN_EXPORT
LV2UI_Handle modgui_init(const char* const className, _custom_param_set param_set, _custom_patch_set patch_set)
{
d_stdout("init \"%s\"", className);
DISTRHO_SAFE_ASSERT_RETURN(className != nullptr, nullptr);

static LV2_URID_Map uridMap = { nullptr, lv2_urid_map };
static LV2_URID_Unmap uridUnmap = { nullptr, lv2_urid_unmap };

// known first URIDs, matching URIs
if (kURIs.empty())
{
kURIs.push_back("");
kURIs.push_back("http://lv2plug.in/ns/ext/atom#eventTransfer");
kURIs.push_back(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState");
}

static float sampleRateValue = 48000.f;
static LV2_Options_Option options[3] = {
{
LV2_OPTIONS_INSTANCE,
0,
uridMap.map(uridMap.handle, LV2_PARAMETERS__sampleRate),
sizeof(float),
uridMap.map(uridMap.handle, LV2_ATOM__Float),
&sampleRateValue
},
{}
};

static const LV2_Feature optionsFt = { LV2_OPTIONS__options, static_cast<void*>(options) };
static const LV2_Feature uridMapFt = { LV2_URID__map, static_cast<void*>(&uridMap) };
static const LV2_Feature uridUnmapFt = { LV2_URID__unmap, static_cast<void*>(&uridUnmap) };

static const LV2_Feature* features[] = {
&optionsFt,
&uridMapFt,
&uridUnmapFt,
nullptr
};

ModguiHandle* const mhandle = new ModguiHandle;
mhandle->handle = nullptr;
mhandle->param_set = param_set;
mhandle->patch_set = patch_set;

LV2UI_Widget widget;
const LV2UI_Handle handle = lv2ui_instantiate(&sLv2UiDescriptor,
DISTRHO_PLUGIN_URI,
"", // bundlePath
lv2ui_write_function,
mhandle,
&widget,
features);
mhandle->handle = handle;

static_cast<UiLv2*>(handle)->lv2ui_show();
emscripten_set_interval(app_idle, 1000.0/60, handle);

return mhandle;
}

DISTRHO_PLUGIN_EXPORT
void modgui_param_set(const LV2UI_Handle handle, const uint32_t index, const float value)
{
lv2ui_port_event(static_cast<ModguiHandle*>(handle)->handle, index, sizeof(float), kUriNull, &value);
}

DISTRHO_PLUGIN_EXPORT
void modgui_patch_set(const LV2UI_Handle handle, const char* const uri, const char* const value)
{
static const constexpr uint32_t URI_PREFIX_LEN = sizeof(DISTRHO_PLUGIN_URI);
DISTRHO_SAFE_ASSERT_RETURN(std::strncmp(uri, DISTRHO_PLUGIN_URI "#", URI_PREFIX_LEN) == 0,);

const uint32_t keySize = std::strlen(uri + URI_PREFIX_LEN) + 1;
const uint32_t valueSize = std::strlen(value) + 1;
const uint32_t atomSize = sizeof(LV2_Atom) + keySize + valueSize;

LV2_Atom* const atom = static_cast<LV2_Atom*>(std::malloc(atomSize));
atom->size = atomSize;
atom->type = kUriDpfKeyValue;

std::memcpy(static_cast<uint8_t*>(static_cast<void*>(atom + 1)), uri + URI_PREFIX_LEN, keySize);
std::memcpy(static_cast<uint8_t*>(static_cast<void*>(atom + 1)) + keySize, value, valueSize);

lv2ui_port_event(static_cast<ModguiHandle*>(handle)->handle,
DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS, // events input port
atomSize, kUriAtomEventTransfer, atom);

std::free(atom);
}

DISTRHO_PLUGIN_EXPORT
void modgui_cleanup(const LV2UI_Handle handle)
{
d_stdout("cleanup");
lv2ui_cleanup(static_cast<ModguiHandle*>(handle)->handle);
delete static_cast<ModguiHandle*>(handle);
}
#endif

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

+ 1
- 0
examples/Info/CMakeLists.txt View File

@@ -3,6 +3,7 @@

dpf_add_plugin(d_info
TARGETS jack lv2 vst2 vst3 clap
MODGUI_CLASS_NAME distrho_examples_info
FILES_DSP
InfoExamplePlugin.cpp
FILES_UI


+ 1
- 4
examples/Info/DistrhoPluginInfo.h View File

@@ -26,6 +26,7 @@
#define DISTRHO_PLUGIN_IS_RT_SAFE 1
#define DISTRHO_PLUGIN_NUM_INPUTS 2
#define DISTRHO_PLUGIN_NUM_OUTPUTS 2
#define DISTRHO_PLUGIN_USES_MODGUI 1
#define DISTRHO_PLUGIN_WANT_TIMEPOS 1
#define DISTRHO_UI_DEFAULT_WIDTH 405
#define DISTRHO_UI_DEFAULT_HEIGHT 256
@@ -36,10 +37,6 @@
// only checking if supported, not actually used
#define DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST 1

#ifdef __MOD_DEVICES__
#define DISTRHO_PLUGIN_USES_MODGUI 1
#endif

enum Parameters {
kParameterBufferSize = 0,
kParameterCanRequestParameterValueChanges,


+ 3
- 0
examples/Info/Makefile View File

@@ -18,6 +18,9 @@ FILES_DSP = \
FILES_UI = \
InfoExampleUI.cpp

# require for modgui builds
MODGUI_CLASS_NAME = distrho_examples_info

# --------------------------------------------------------------
# Do some magic



Loading…
Cancel
Save