Web Audio API Safety Review
Note — JS realtime model differs from native: JavaScript allocation does not look like
malloc, but it still creates garbage-collector work and can introduce render-thread jitter. The same cardinal rule applies: avoid allocations and blocking operations insideprocess().Invoke
audio-dsp-reviewconcepts (no allocations, no blocking) adapted for JavaScript: replace "mutex" with "GC pause" and "malloc" with "object/array creation".
Step 1 — Identify the architecture
Determine which Web Audio pattern is in use before scanning:
| Pattern | Where audio runs | Risk level |
|---|---|---|
AudioWorkletProcessor subclass | Dedicated worklet thread | Medium — GC and message overhead |
ScriptProcessorNode | Main thread | High — deprecated; blocks UI |
Main-thread AudioContext only | Main thread | Low — standard graph |
OfflineAudioContext | Offline render | Low — no real-time deadline |
Step 2 — Scan for violations
| Violation | Location | Risk |
|---|---|---|
Using ScriptProcessorNode | Anywhere | Deprecated; runs on main thread → dropouts under UI load |
new Float32Array(...) inside process() | AudioWorkletProcessor.process() | GC allocation → potential pause during render |
Creating objects/arrays/closures inside process() | AudioWorkletProcessor.process() | GC pressure → jitter |
port.postMessage() every process() block | AudioWorkletProcessor | High overhead; batch updates or use SharedArrayBuffer |
Accessing window, document, or DOM from worklet | process() or worklet scope | Worklet thread has no DOM access — throws ReferenceError |
Importing non-worklet-safe modules in addModule() | Worklet module file | DOM APIs throw; do network/file loading on the main thread and pass data in |
| Not handling autoplay policy | AudioContext creation | Context starts suspended; must resume on user gesture |
SharedArrayBuffer comms without integer Atomics protocol | Worklet ↔ main comms | Race-prone shared state — Atomics works on integer typed arrays, not Float32Array |
Changing AudioNode graph from worklet thread | process() | connect()/disconnect() are main-thread-only |
| Assuming a hardcoded render quantum in loops/buffers | process() | Spec and browsers use 128-frame quanta today, but code should use output[channel].length |
Forgetting await audioContext.resume() | After user gesture handler | Stays suspended; no audio output |
Step 3 — Write the review
## Web Audio Safety Review: `[file / component]`
### Verdict
[Safe | Has critical violations | Warnings only] — [one sentence summary]
### Critical Violations
**[Category]: [description]**
`file:line` — `offending code`
Why: [realtime or correctness risk]
Fix: [concrete suggestion]
### Warnings
[same format]
### What's Done Well
[correct patterns observed]
### Recommended Fixes (priority order)
1. ...
See references/webaudio-violations.md for BAD/GOOD code examples for every violation above.
Quick fix table
| Violation | Safe alternative |
|---|---|
ScriptProcessorNode | AudioWorkletProcessor subclass |
new Float32Array() in process() | Pre-allocate in constructor; reuse across calls |
port.postMessage() every block | Batch at lower rate, or use SharedArrayBuffer + integer Atomics |
Raw SharedArrayBuffer reads | Atomics.load() / Atomics.store() on Int32Array / Uint32Array views |
| Graph changes from worklet | Queue a message to main thread; apply in port.onmessage |
| Context suspended | button.addEventListener('click', () => ctx.resume()) |