Skip to main content
iPlug2 supports compiling audio plugins to WebAssembly (WASM) for running in web browsers using the modern split DSP/UI architecture.

Architecture Overview

The WASM build system creates two separate modules:

DSP Module

Thread: AudioWorklet (real-time audio thread)
  • Audio processing only
  • Embedded as BASE64 for synchronous loading
  • No UI code

UI Module

Thread: Main thread
  • IGraphics rendering
  • User interaction
  • Asynchronous loading

Communication

  • Parameters/MIDI: postMessage API
  • Visualization: SharedArrayBuffer (low-latency) or postMessage (fallback)
Why split architecture?
  • Isolates audio processing from UI jank
  • Smaller initial load (DSP embedded, UI loads async)
  • No WAM SDK dependency
  • Multi-instance support

Prerequisites

1

Install Emscripten SDK

cd ~
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
Add to shell profile for persistence:
echo 'source ~/emsdk/emsdk_env.sh' >> ~/.bashrc  # or ~/.zshrc
2

Verify installation

emcc --version
# Should output: emcc (Emscripten gcc/clang-like replacement) ...
3

Download WAM SDK (optional)

Only needed for WAM builds (not split WASM):
cd iPlug2/Dependencies
./download-iplug-sdks.sh

Building with Makefiles

Each example project includes build scripts:
cd Examples/IPlugEffect/scripts
./makedist-wasm.sh        # Build and launch in Chrome
./makedist-wasm.sh off    # Build only, don't launch
./makedist-wasm.sh on safari  # Build and launch in Safari

Build Process

The script:
  1. Packages resources (fonts, images, SVGs)
  2. Builds DSP module with SINGLE_FILE=1 (BASE64 embedded)
  3. Builds UI module (if PLUG_HAS_UI=1)
  4. Copies templates and generates web bundle
  5. Outputs to build-web-wasm/

Headless Plugins

For plugins without IGraphics (PLUG_HAS_UI=0), only the DSP module is built:
# In config.h
#define PLUG_HAS_UI 0
#define NO_IGRAPHICS
The template auto-generates parameter controls.

Project Configuration

Makefile Config

Create config/YourPlugin-wasm.mk:
# Project name
PLUG_NAME = YourPlugin

# Extra source files
EXTRA_SRC = $(PROJECT_ROOT)/MyDSP.cpp

# Extra include paths
EXTRA_INCLUDES = -I$(PROJECT_ROOT)/libs

# Extra defines
EXTRA_CFLAGS = -DUSE_MY_FEATURE=1

DSP/UI Project Files

projects/YourPlugin-wasm-dsp.mk:
include $(IPLUG2_ROOT)/common-wasm.mk
include ../config/YourPlugin-wasm.mk
projects/YourPlugin-wasm-ui.mk:
include $(IPLUG2_ROOT)/common-wasm.mk
include ../config/YourPlugin-wasm.mk

Build Output

build-web-wasm/
└── YourPlugin/
    ├── scripts/
    │   ├── YourPlugin-wam.js       # DSP loader
    │   ├── YourPlugin-wam.wasm     # DSP module
    │   ├── YourPlugin-web.js       # UI loader
    │   └── YourPlugin-web.wasm     # UI module
    ├── index.html                  # Test page
    ├── descriptor.json             # WAM metadata
    └── styles/                     # CSS assets

Server Requirements

For SharedArrayBuffer support (low-latency visualization), your server must send these headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Without these headers, the plugin falls back to postMessage for all communication (higher latency for visualization).

Development Server

Use the included Python server:
Create serve.py:
#!/usr/bin/env python3
from http.server import HTTPServer, SimpleHTTPRequestHandler
import sys

class CORPHandler(SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
        self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
        super().end_headers()

    extensions_map = {
        **SimpleHTTPRequestHandler.extensions_map,
        '.wasm': 'application/wasm',
    }

port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
print(f'Serving at http://localhost:{port}')
HTTPServer(('localhost', port), CORPHandler).serve_forever()
Run:
python3 serve.py 8080

Message Protocol

UI → DSP (via postMessage)

TypeFieldsDescription
paramparamIdx, valueParameter change
midistatus, data1, data2MIDI message
sysexdata (ArrayBuffer)SysEx message
arbitrarymsgTag, ctrlTag, dataCustom message
tick-Idle tick (flush queue)

DSP → UI (via postMessage)

VerbFieldsDescription
SPVFDparamIdx, valueParameter value from DSP
SCVFDctrlTag, valueControl value (visualization)
SCMFDctrlTag, msgTag, dataControl message
SAMFDmsgTag, dataArbitrary message
SSMFDdataSysEx from DSP
pluginInfodataPlugin metadata

SharedArrayBuffer (Low-Latency)

For high-frequency visualization data:
Header (16 bytes):
  [0-3]   writeIdx (Uint32, atomic)
  [4-7]   readIdx (Uint32, atomic)
  [8-11]  capacity (Uint32)
  [12-15] reserved

Message:
  [0]     msgType (0=SCVFD, 1=SCMFD, 2=SAMFD)
  [1]     reserved
  [2-3]   dataSize (Uint16)
  [4-7]   ctrlTag (Int32)
  [8-11]  msgTag (Int32)
  [12+]   payload

Multi-Instance Support

The WASM DSP module supports multiple plugin instances in the same AudioWorklet context:
// In AudioWorkletProcessor constructor
this.instanceId = Module.createInstance();  // Unique ID

// All calls include instance ID
Module.init(this.instanceId, sampleRate, blockSize);
Module.processBlock(this.instanceId, inputPtrs, outputPtrs, nFrames);
Each instance has isolated state and parameters, but shares the WASM module code.

Template Files

Build copies templates from IPlug/WEB/TemplateWasm/:
FilePurpose
index.htmlMain page with Web Audio setup
scripts/IPlugWasmBundle.js.templateController, connects DSP↔UI
scripts/IPlugWasmProcessor.js.templateAudioWorkletProcessor wrapper
styles/style.cssDefault styling
Placeholders like NAME_PLACEHOLDER are replaced during build.

Debugging

Browser Console

1

Open DevTools

  • Chrome: F12 or ⌘⌥I (Mac)
  • Safari: Enable Develop menu in Preferences
2

Check console messages

Both DSP and UI modules log to consoleDSP messages are prefixed with plugin name
3

Inspect WASM modules

Sources tab shows loaded WASM modulesSome browsers support WASM debugging

Common Issues

Cause: Server missing COOP/COEP headersSolution:
  • Verify headers are sent (check DevTools Network tab)
  • Use development server with proper headers
  • Plugin will still work, but uses slower postMessage path
Cause: DSP module failed to loadSolution:
  • Check browser console for WASM compilation errors
  • Verify WASM file is accessible (check Network tab)
  • Ensure MIME type is application/wasm
Cause: DSP module too heavy for real-timeSolution:
  • Profile with Chrome DevTools Performance panel
  • Reduce buffer processing complexity
  • Check for allocations in ProcessBlock
  • Consider disabling SIMD if causing issues
Cause: IGraphics initialization failureSolution:
  • Check browser console for errors
  • Verify resources (fonts, images) are loaded
  • Test with headless build first
  • Ensure canvas element is created

Performance Profiling

1

Open Performance panel

Chrome DevTools > Performance tab
2

Record profile

  1. Click Record
  2. Play audio through plugin
  3. Click Stop after a few seconds
3

Analyze

Look for:
  • Long-running ProcessBlock calls
  • Memory allocations (garbage collection)
  • Main thread blocking (UI jank)

WAM vs Split WASM

FeatureWASM (Split)WAM
SDK dependencyNoneWAM SDK required
ArchitectureSplit DSP/UICombined
AudioWorkletNativeVia SDK
VisualizationSAB + postMessageWAM events
DAW integrationBasicWAM host support
DeploymentSimplerStandardized
Use WASM Split for standalone web plugins and simple deployment.Use WAM for DAW integration and WAM-compatible hosts.

Building WAM (Alternative)

For Web Audio Module builds:
cd Examples/IPlugEffect/scripts
./makedist-wam.sh        # Build WAM version
WAM builds create a standardized plugin format compatible with WAM hosts.

CMake Build (Alternative)

iPlug2 web projects can also be built with CMake:
mkdir build && cd build

# Configure with Emscripten
emcmake cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ..

# Build WAM and Web targets
ninja MyPlugin-wam       # Audio processor (WASM)
ninja MyPlugin-web       # UI controller (WASM)
ninja MyPlugin-wam-dist  # Complete distribution package
See CMake Guide for details.

Deployment

Hosting Requirements

1

Upload files

Copy build-web-wasm/YourPlugin/ to your web server
2

Configure headers

Ensure server sends COOP/COEP headers:Apache (.htaccess):
Header set Cross-Origin-Opener-Policy "same-origin"
Header set Cross-Origin-Embedder-Policy "require-corp"
Header set Content-Type "application/wasm" "expr=%{REQUEST_URI} =~ /\.wasm$/"
Nginx (nginx.conf):
add_header Cross-Origin-Opener-Policy "same-origin";
add_header Cross-Origin-Embedder-Policy "require-corp";

location ~* \.wasm$ {
    types { application/wasm wasm; }
}
3

Test deployment

Open https://yourdomain.com/YourPlugin/index.htmlVerify:
  • Plugin loads without errors
  • Audio processing works
  • UI appears correctly

CDN Deployment

For serving via CDN:
CDN must support custom headers (COOP/COEP). Many CDNs don’t allow these headers.
Workarounds:
  • Host on your own server with proper headers
  • Use Cloudflare Workers to add headers
  • Use Service Worker to add headers client-side

Embedding in Web Pages

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>My Plugin</title>
</head>
<body>
    <h1>YourPlugin Demo</h1>
    <div id="plugin-container"></div>
    
    <script type="module">
        import { YourPluginBundle } from './scripts/YourPlugin-wam.js';
        
        // Create AudioContext
        const audioContext = new AudioContext();
        
        // Create plugin instance
        const plugin = await YourPluginBundle.createInstance(audioContext);
        
        // Attach UI
        const container = document.getElementById('plugin-container');
        plugin.attachUI(container);
        
        // Connect to audio graph
        const mediaSource = await navigator.mediaDevices.getUserMedia({ audio: true });
        const source = audioContext.createMediaStreamSource(mediaSource);
        source.connect(plugin.audioNode);
        plugin.audioNode.connect(audioContext.destination);
    </script>
</body>
</html>

Next Steps

WAM Integration

Integrate with WAM hosts

Web UI

Building web-based UIs

CMake Build

Alternative build system

Deployment

Production deployment