Analysis of compiling the existing C++ codebase to WebAssembly via Emscripten.
The build system already has substantial WASM support:
- Platform config:
obtools/build/Tuprules.luadefines awebplatform usingem++/emar - Build command:
obtools/build/init.sh -t release -p web && tup - Compiler flags:
-DPLATFORM_WEB -sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sUSE_FREETYPE=1 - Dynamic linking: Main exe uses
-s MAIN_MODULE=1 -s WASM=1; modules use-s SIDE_MODULE=1 - Entry point:
web/main.cc— simplified engine with hardcoded test graph - Packaging:
create-wasm.shuses Emscriptenfile_packager.pyto bundle module.sofiles - Module list:
web/WEB/fileslists ~120 modules to package - Platform guard:
modules/module.hhasPLATFORM_WEBguard for logger init - ext- deps skipped: Build rules silently skip
ext-*dependencies for web platform (Emscripten provides SDL2/FreeType as built-in ports)
Modules declare PLATFORMS in their Tupfiles to control what gets built:
PLATFORMS = web—web/onlyPLATFORMS = posix web— SDL audio/bitmap, image-in, wav-in, service, nexus-client, web-fetchPLATFORMS = linux— ALSA, OLA, Mosquitto (excluded from WASM)PLATFORMS = posix— engine, desktop, pitch-shift (excluded)- No declaration — all platforms (most core/vector/maths/trigger/colour/binary modules)
web/main.cc has a while(true) loop with sleep_for which blocks the browser main thread. Must use emscripten_set_main_loop() or build with -s ASYNCIFY=1.
Fix (~10 lines):
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
// tick_callback calls engine.tick(Time::Duration::clock())
emscripten_set_main_loop(tick_callback, 25, 1);
#else
// existing while(true) loop
#endifThe web entry point bypasses the service/REST layer (which needs sockets). For interactive graph manipulation, the vg-json get/set/delete API needs to be exposed to JavaScript via embind or EMSCRIPTEN_KEEPALIVE. This replaces the REST API without requiring raw sockets.
The parallel tick engine uses pthreads, condition variables, and RWMutex. Browser pthreads require SharedArrayBuffer which needs COOP/COEP HTTP headers. Options:
- Enable with
-pthread -s PROXY_TO_PTHREAD=1and configure server headers - Or run single-threaded (engine supports
threads=0)
Several ObTools libraries are transitively included but have no PLATFORM_WEB guards:
ot-mt.h— usespthread_kill(),pthread_setschedparam()ot-net.h— raw socket APIs (socket(),bind(),listen(),accept())ot-daemon.h—fork(),signal(),syslog()ot-ssl-openssl.h— OpenSSL not available in Emscripten
The web entry point avoids calling most of this code, but the headers still get included. Need #ifndef PLATFORM_WEB guards or stub implementations.
Browser audio requires user interaction before playback starts. The SDL audio modules don't handle this. Need a JS-side click handler to unpause audio.
The current approach loads modules as Emscripten side modules via dlopen. This works but is fragile (-s ERROR_ON_UNDEFINED_SYMBOLS=0 suggests unresolved symbol issues). Statically linking all modules into one binary would:
- Eliminate dynamic loading complexity
- Produce a smaller, faster WASM binary
- Require a
vg_init_all()that calls each module'svg_init(can be auto-generated)
| Category | Modules |
|---|---|
| core | pin, oscillator, filter, gate, compare, clone, average, interpolator, log, memory, random, switch, graph |
| maths | binary-ops, unary-ops, differentiate, integrate, limit, polar-position, wrap |
| trigger | beat, count, gate, latch, memory, pin, resample, selector, start |
| binary | clear, set, test |
| colour | blend, hsl, rgb, split, pin, switch |
| vector | add, clip, fade, figure, pattern, pin, rotate, scale, slice, stroke, svg, switch, translate |
| bitmap | blend, fade, fill, pin, rectangle, switch, tile, translate, twist, vector-fill |
| laser | add-blanking-anchors, add-vertex-repeats, beamify, infill-lines, reorder-segments, show-blanking |
| waveform | enum, pin, switch |
| time | clock, timer, now |
| time-series | average, capture, last, offset, plot, scale |
| midi (logic) | arpeggiate, assign-voice, button-in, control-in/out, keyboard-in, key-in/out, pin, switch |
| dmx (format) | artnet-out, bitmap-render, get-value, set-value |
- audio/sdl-in, audio/sdl-out, audio/wav-in
- bitmap/sdl-out, bitmap/image-in
- audio/alsa-in, audio/alsa-out (
PLATFORMS = linux) - audio/pitch-shift (
PLATFORMS = posix) - midi/alsa-in, midi/alsa-out (
PLATFORMS = linux) - midi/winmm-in, midi/winmm-out (
PLATFORMS = windows) - dmx/ola-in, dmx/ola-out (
PLATFORMS = linux) - iot/mqtt-in, iot/mqtt-out (
PLATFORMS = linux)
- laser/etherdream-out — hardware TCP to laser projector
- laser/idn-out — hardware UDP to laser projector
The gap to a working WASM build is smaller than it appears. The build system, module exclusion, and platform gating are already in place. The critical path is:
- Fix the event loop (
emscripten_set_main_loop) - Add a JS bridge to expose graph manipulation (replacing REST)
- Decide threading strategy (pthreads with headers, or single-threaded)
- Add
PLATFORM_WEBguards to ObTools headers - Optionally switch to static module linking for reliability
The ~90 core computation modules and the dataflow engine need zero changes.