ringtailsoftware / zig-wasm-audio-framebuffer

Examples of integrating Zig and Wasm for audio and graphics on the web
Other
53 stars 2 forks source link
wasm webassembly webaudio zig

zig-wasm-audio-framebuffer

Toby Jaffey https://mastodon.me.uk/@tobyjaffey

Straightforward examples of integrating Zig and Wasm for audio and graphics on the web.

Compiling against Zig v0.13.0.

Aims

Demos

Visit https://ringtailsoftware.github.io/zig-wasm-audio-framebuffer

Build and test (assumes you have zig installed)

zig build
cd zig-out && python3 -m http.server 8000

Build and test via docker

make

Browse to http://localhost:8000

Video system

An in-memory 32bpp ARGB framebuffer is created and controlled in Zig. To render onto a canvas element, JavaScript:

Audio pipeline

An AudioWorkletNode is used to move PCM samples from WebAssembly to the audio output device.

The system is hardcoded to 2 channels (stereo) and uses 32-bit floats for audio data throughout. In-memory arrays of audio data are created and controlled in Zig.

The AudioWorkletNode expects to pull chunks of audio to be rendered on-demand. However, being isolated from the main thread it cannot directly communicate with the main Wasm program. This is solved by using a shared ringbuffer. To render audio to the output device, JavaScript:

The WasmPcm (wasmpcm.js) class creates the AudioWorkletNode WASMWorkletProcessor using pcm-processor.js. At regular intervals WasmPcm calls pcmProcess() to request audio data from Wasm.

Compatibility

Tested on Safari/Chrome/Firefox on macOS, Safari on iPhone SE2/iPad, Chrome/Android Galaxy Tablet

iOS unmute

By default web audio plays under the same rules as the ringer. The unmute.js script loops a constant silence in the background to force playback while the mute button is on.

CORS (Cross-Origin Resource Sharing)

To share data between the main thread and the worklet, SharedArrayBuffer is used. This requires two HTTP headers to be set:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

However, this is worked around by using coi-serviceworker which reloads the page on startup.