├── Makefile ├── Notes.md ├── README.md ├── audio ├── loop.mp3 └── twang.m4a ├── audioworkletnode-clap.mjs ├── audioworkletprocessor-clap.mjs ├── clap-host ├── clap-interface.mjs ├── clap-module.mjs ├── ext │ ├── add-extension.mjs │ ├── audio-ports.mjs │ ├── log.mjs │ ├── params.mjs │ └── state.mjs ├── generate-forwarding-wasm.mjs ├── hosted-plugin.mjs ├── instantiate.mjs └── wasi │ └── wasi_snapshot_preview1.mjs ├── index.html ├── plugin ├── clap-validator-plugin.wclap │ └── module.wasm ├── clapfirst-bad-distortion.wclap.tar.gz ├── freq-shifter.wclap.tar.gz └── hello-clap.wclap.tar.gz ├── rainbow-texture.jpg └── wclap-service-worker.js /Makefile: -------------------------------------------------------------------------------- 1 | server: 2 | python3 -m http.server 3 | 4 | bundle: 5 | npx rollup clap-host/clasm.mjs --file clasm.mjs --format es --sourcemap 6 | 7 | npx rollup clap-host/clasm.mjs --file clasm.js --format umd --output.name Clasm --no-esModule --strict --sourcemap 8 | 9 | 10 | minify: 11 | npx uglify-js ./clasm.mjs -o ./clasm.min.mjs \ 12 | --source-map "content=clasm.mjs.map,url=clasm.min.mjs.map" \ 13 | --warn --compress passes=10 \ 14 | --mangle --mangle-props "regex=/^(m_|#)/" \ 15 | --output-opts ascii_only 16 | 17 | npx uglify-js ./clasm.js -o ./clasm.min.js \ 18 | --source-map "content=clasm.js.map,url=clasm.min.js.map" \ 19 | --warn --compress passes=10 \ 20 | --mangle --mangle-props "regex=/^(m_|#)/" \ 21 | --output-opts ascii_only 22 | -------------------------------------------------------------------------------- /Notes.md: -------------------------------------------------------------------------------- 1 | # CLAP WASM notes 2 | 3 | Here are some notes for compiing a CLAP plugin to a self-contained `.wasm` file (no JS support needed); 4 | 5 | ## General 6 | 7 | The WASM module needs to export: 8 | 9 | * `memory` 10 | * `clap_entry` - pointer to the entry object 11 | * `malloc(size)` - allocates memory for the host to use 12 | * a growable WebAssembly.Table (full of function references) 13 | 14 | We need `malloc` because the host needs to provide pointers to structures and audio buffers. Rather than structuring the WASM module as a shared library (which isn't currently standardised), the plugin module is self-contained but allows the host to inject itself. 15 | 16 | ### Finding the Table 17 | 18 | Plugins should only export one function Table, and hosts may throw if there's more than one candidate. LLVM calls this table `__indirect_function_table`, but hosts shouldn't assume that. 19 | 20 | ### Resizable Table/`memory` 21 | 22 | The Table export needs to be growable, so that the host can import an arbitrary number of its own functions. 23 | 24 | The `memory` export is *probably* resizable, since the plugin doesn't know how much memory the host will ask for with `malloc()`. A host might(?) also constructing an arbitrary number of plugin instances. 25 | 26 | ### Emscripten flags 27 | 28 | None of these requirements/behaviours are specific to Emscripten, but it's pretty common, so 'here are some useful flags for the Emscripten linker: 29 | 30 | * `-sEXPORTED_FUNCTIONS=_clap_entry,_malloc`: not sure what the `_` is about here - they're not present in the actual exports 31 | * `-sPURE_WASI`: no Emscripten-specific imports (`emscripten_notify_memory_growth`) 32 | * `-sSTANDALONE_WASM`: otherwise Emscripten does various things (e.g. renaming exports) which assume a matching custom JS loader 33 | * `--export-table`: exports the function Table 34 | * `-sALLOW_TABLE_GROWTH`: the Table is growable 35 | * `--no-entry`: don't expect a `main()` 36 | * `-sINITIAL_MEMORY=512kb -sALLOW_MEMORY_GROWTH`: up to you and your specific plugin - it'll complain at build time if the initial memory is too small 37 | 38 | ## Threads 39 | 40 | Each WebAssembly instance is single-threaded, and multi-threading is done by having multiple instances (perhaps of different modules!) using shared memory. This means that (if you import shared `memory` instead of exporting it) you could have calls being made on separate UI/audio threads. 41 | 42 | Many non-browser engines don't support shared memory anyway (and in a browser you need to [lock everything down](https://hacks.mozilla.org/2020/07/safely-reviving-shared-memory/) before you can pass memory around). Even where supported, you can't spawn your own threads (for now) from inside the WebAssembly module. 43 | 44 | However, threads are most commonly used for passing messages between the audio/UI components. If a WASM build doesn't include the `clap.gui` extension, then the threads might not be necessary either. 45 | 46 | ### C++ 47 | 48 | With Emscripten at least, you can still have a `std::thread` but actually starting one throws an error. Therefore, if you only start the `std::thread` when you actually need it (e.g. when the GUI is opened) that can avoid the problem. 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Browser-based Web-CLAP host 2 | 3 | This is a _very_ WIP proof-of-concept host for WCLAP ([Web CLAP](https://github.com/free-audio/web-clap)) plugins. Although this host is browser-based, a similar approach could be used for non-web contexts. 4 | 5 | ## Design 6 | 7 | There are three layers: 8 | 9 | ### CLAP host 10 | 11 | The source for this is in `clap-host/`, with the main entry point being `clapModule()` from `clap-module.mjs`. This takes the [WebAssembly Module](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Module) for a CLAP plugin, and lets you query/create plugin instances. 12 | 13 | Rather than wrap the CLAP API in any way, `clap-interface.mjs` makes it easy to define and read/write C structs in the WebAssembly memory, call functions (from function pointers), proxy your own JS functions as function pointers, and add CLAP extensions. The core CLAP types/extensions are already defined. 14 | 15 | ### AudioWorkletNode / AudioWorkletProcessor 16 | 17 | This fetches/compiles a `.wasm`, and uses the Module to construct an AudioWorkletProcessor. This processor uses the CLAP host helpers (above) to configure/activate the plugin and process audio blocks. 18 | 19 | The AudioWorkletNode has proxy methods added to it, which asynchronously scan/update parameters and save/load state, by posting messages across to the processor. It also receives events from the processor (such as "state marked dirty"). 20 | 21 | They also use the WIP `clap.web/1` draft extension, which (if supported) opens a web-page and passes arbitrary messages between the web-page and the plugin, allowing plugins to present a web-based UI. Since this host is running in the browser, this web-view is loaded in an `