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.
Visit https://ringtailsoftware.github.io/zig-wasm-audio-framebuffer
zig build
cd zig-out && python3 -m http.server 8000
make
Browse to http://localhost:8000
An in-memory 32bpp ARGB framebuffer is created and controlled in Zig. To render onto a canvas element, JavaScript:
getGfxBufPtr()
Uint8ClampedArray
ImageData
from the Uint8ClampedArray
ImageData
in canvas's graphics contextAn 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:
setSampleRate(44100)
renderSoundQuantum()
getLeftBufPtr()
getRightBufPtr()
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.
Tested on Safari/Chrome/Firefox on macOS, Safari on iPhone SE2/iPad, Chrome/Android Galaxy Tablet
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.
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.