From da582de935521d60c0c74a6bca087f6a4ed55c9e Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 29 Dec 2022 01:07:56 +0000 Subject: [PATCH] Initial implementation of modgui over wasm Signed-off-by: falkTX --- Makefile.plugins.mk | 98 ++++++++ cmake/DPF-plugin.cmake | 9 +- distrho/src/DistrhoPluginLV2export.cpp | 299 ++++++++++++++++++++++++- distrho/src/DistrhoUILV2.cpp | 182 +++++++++++++++ examples/Info/CMakeLists.txt | 1 + examples/Info/DistrhoPluginInfo.h | 5 +- examples/Info/Makefile | 3 + 7 files changed, 589 insertions(+), 8 deletions(-) diff --git a/Makefile.plugins.mk b/Makefile.plugins.mk index 4511c478..ad8b3481 100644 --- a/Makefile.plugins.mk +++ b/Makefile.plugins.mk @@ -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 diff --git a/cmake/DPF-plugin.cmake b/cmake/DPF-plugin.cmake index 3e49541f..fcba2e12 100644 --- a/cmake/DPF-plugin.cmake +++ b/cmake/DPF-plugin.cmake @@ -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() diff --git a/distrho/src/DistrhoPluginLV2export.cpp b/distrho/src/DistrhoPluginLV2export.cpp index 41d8f2f8..9598cbf5 100644 --- a/distrho/src/DistrhoPluginLV2export.cpp +++ b/distrho/src/DistrhoPluginLV2export.cpp @@ -42,7 +42,11 @@ # include "mod-license.h" #endif -#ifndef DISTRHO_OS_WINDOWS +#ifdef DISTRHO_OS_WINDOWS +# include +#else +# include +# include # include #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: .\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 ;\n"; + modguiString += " modgui:iconTemplate ;\n"; + modguiString += " modgui:javascript ;\n"; + modguiString += " modgui:stylesheet ;\n"; + modguiString += " modgui:screenshot ;\n"; + modguiString += " modgui:thumbnail ;\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('
')*/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 << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << "

{{#brand}}{{brand}} | {{/brand}}{{label}}

" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << " " << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << " {{#effect.ports.audio.input}}" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << " {{/effect.ports.audio.input}}" << std::endl; + iconFile << " {{#effect.ports.midi.input}}" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << " {{/effect.ports.midi.input}}" << std::endl; + iconFile << " {{#effect.ports.cv.input}}" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << " {{/effect.ports.cv.input}}" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << " {{#effect.ports.audio.output}}" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << " {{/effect.ports.audio.output}}" << std::endl; + iconFile << " {{#effect.ports.midi.output}}" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << " {{/effect.ports.midi.output}}" << std::endl; + iconFile << " {{#effect.ports.cv.output}}" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << std::endl; + iconFile << " {{/effect.ports.cv.output}}" << std::endl; + iconFile << "
" << std::endl; + iconFile << "
" << 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 diff --git a/distrho/src/DistrhoUILV2.cpp b/distrho/src/DistrhoUILV2.cpp index 617a4362..caa1621d 100644 --- a/distrho/src/DistrhoUILV2.cpp +++ b/distrho/src/DistrhoUILV2.cpp @@ -715,4 +715,186 @@ const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) return (index == 0) ? &sLv2UiDescriptor : nullptr; } +#if defined(__MOD_DEVICES__) && defined(__EMSCRIPTEN__) +#include +#include + +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 kURIs; + +static LV2_URID lv2_urid_map(LV2_URID_Map_Handle, const char* const uri) +{ + for (size_t i=0, size=kURIs.size(); i= 1,); + + // d_stdout("lv2ui_write_function %p %u %u %u %p", controller, port_index, buffer_size, port_protocol, buffer); + ModguiHandle* const mhandle = static_cast(controller); + + switch (port_protocol) + { + case kUriNull: + mhandle->param_set(port_index, *static_cast(buffer)); + break; + case kUriAtomEventTransfer: + if (const LV2_Atom* const atom = static_cast(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(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(options) }; + static const LV2_Feature uridMapFt = { LV2_URID__map, static_cast(&uridMap) }; + static const LV2_Feature uridUnmapFt = { LV2_URID__unmap, static_cast(&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(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(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(std::malloc(atomSize)); + atom->size = atomSize; + atom->type = kUriDpfKeyValue; + + std::memcpy(static_cast(static_cast(atom + 1)), uri + URI_PREFIX_LEN, keySize); + std::memcpy(static_cast(static_cast(atom + 1)) + keySize, value, valueSize); + + lv2ui_port_event(static_cast(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(handle)->handle); + delete static_cast(handle); +} +#endif + // ----------------------------------------------------------------------- diff --git a/examples/Info/CMakeLists.txt b/examples/Info/CMakeLists.txt index b2a2dd52..ebb71786 100644 --- a/examples/Info/CMakeLists.txt +++ b/examples/Info/CMakeLists.txt @@ -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 diff --git a/examples/Info/DistrhoPluginInfo.h b/examples/Info/DistrhoPluginInfo.h index d0c4583d..a041f817 100644 --- a/examples/Info/DistrhoPluginInfo.h +++ b/examples/Info/DistrhoPluginInfo.h @@ -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, diff --git a/examples/Info/Makefile b/examples/Info/Makefile index 1ee6b4e0..ca751edb 100644 --- a/examples/Info/Makefile +++ b/examples/Info/Makefile @@ -18,6 +18,9 @@ FILES_DSP = \ FILES_UI = \ InfoExampleUI.cpp +# require for modgui builds +MODGUI_CLASS_NAME = distrho_examples_info + # -------------------------------------------------------------- # Do some magic