Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer
Examples of integrating Zig and Wasm (and C) for audio and graphics on the web
https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer
c doom wasm webassembly webaudio zig zig-package
Last synced: 13 days ago
JSON representation
Examples of integrating Zig and Wasm (and C) for audio and graphics on the web
- Host: GitHub
- URL: https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer
- Owner: ringtailsoftware
- License: other
- Created: 2023-03-03T15:59:29.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-12-09T11:18:22.000Z (27 days ago)
- Last Synced: 2024-12-14T09:02:02.987Z (22 days ago)
- Topics: c, doom, wasm, webassembly, webaudio, zig, zig-package
- Language: C++
- Homepage:
- Size: 51.4 MB
- Stars: 58
- Watchers: 2
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-zig - ringtailsoftware/zig-wasm-audio-framebuffer
README
# 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.
Where C code requires functions from the C library, https://github.com/ringtailsoftware/zeptolibc is used.
# Demos
Visit https://ringtailsoftware.github.io/zig-wasm-audio-framebuffer
- Sinetone, simple waveform generator
- Synth, HTML/CSS piano keyboard driving MIDI synth
- Mod, Pro-Tracker mod player
- Mandelbrot, mandelbrot set, mouse interaction
- Bat, arcade style game skeleton, keyboard control, interactive graphics, background music, sound effects
- Doom, Doom1 Shareware, keyboard control, MIDI music, sound effects
- TinyGL, software GL renderer in Wasm
- OliveC, graphics library with sprite blit, circle, rectangle, line, etc.
- Agnes, NES emulator (no sound)
- Terminal, libvterm based terminal emulator, playing a terminal tetris game## Aims
- Cross-platform (running on multiple browsers and operating systems)
- Simple understandable code. No hidden libraries, no "emscripten magic"
- Main loop in browser, wasm responds to function calls to init/update/render
- One wasm binary per program
- Use existing C libraries to do fun things# Build and test
zig build && zig build serve -- zig-out -p 8000
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:
- Requests the framebuffer Zig/WebAssembly pointer with `getGfxBufPtr()`
- Wraps the memory in a `Uint8ClampedArray`
- Creates an `ImageData` from the `Uint8ClampedArray`
- Shows the `ImageData` in canvas's graphics context## 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:
- Tells Zig/WebAssembly the expected sample rate in Hz for the output device, `setSampleRate(44100)`
- Forever, checks if the ringbuffer has space for more data
- Tells Zig/WebAssembly to fill its audio buffers with `renderSoundQuantum()`
- Fetches pointers to the left and right channels using `getLeftBufPtr()` `getRightBufPtr()`
- Copies from left and right channels into the ringbufferThe `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-corpHowever, this is worked around by using coi-serviceworker which reloads the page on startup.