Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/audio_worklet.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function createWasmAudioWorkletProcessor() {
// 64 frames, for the case where a multi-MB stack is passed.
this.outputViews = new Array(Math.min(((wwParams.stackSize - {{{ STACK_ALIGN }}}) / this.bytesPerChannel) | 0, /*sensible limit*/ 64));
#if ASSERTIONS
console.assert(this.outputViews.length > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`);
assert(this.outputViews.length > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`);
#endif
this.createOutputViews();

Expand Down Expand Up @@ -138,8 +138,8 @@ function createWasmAudioWorkletProcessor() {
#endif
var oldStackPtr = stackSave();
#if ASSERTIONS
console.assert(oldStackPtr == this.ctorOldStackPtr, 'AudioWorklet stack address has unexpectedly moved');
console.assert(outputViewsNeeded <= this.outputViews.length, `Too many AudioWorklet outputs (need ${outputViewsNeeded} but have stack space for ${this.outputViews.length})`);
assert(oldStackPtr == this.ctorOldStackPtr, 'AudioWorklet stack address has unexpectedly moved');
assert(outputViewsNeeded <= this.outputViews.length, `Too many AudioWorklet outputs (need ${outputViewsNeeded} but have stack space for ${this.outputViews.length})`);
#endif

// Allocate the necessary stack space. All pointer variables are in bytes;
Expand All @@ -154,6 +154,10 @@ function createWasmAudioWorkletProcessor() {
var stackMemoryAligned = (stackMemoryStruct + stackMemoryData + 15) & ~15;
var structPtr = stackAlloc(stackMemoryAligned);
var dataPtr = structPtr + (stackMemoryAligned - stackMemoryData);
#if ASSERTIONS
// TODO: look at why stackAlloc isn't tripping the assertions
assert(stackMemoryAligned <= wwParams.stackSize, `Not enough stack allocated to the AudioWorklet (need ${stackMemoryAligned}, got ${wwParams.stackSize})`);
#endif

// Copy input audio descriptor structs and data to Wasm (recall, structs
// first, audio data after). 'inputsPtr' is the start of the C callback's
Expand Down Expand Up @@ -221,7 +225,7 @@ function createWasmAudioWorkletProcessor() {
// And that the views' size match the passed in output buffers
for (entry of outputList) {
for (subentry of entry) {
console.assert(subentry.byteLength == this.bytesPerChannel, `AudioWorklet unexpected output buffer size (expected ${this.bytesPerChannel} got ${subentry.byteLength})`);
assert(subentry.byteLength == this.bytesPerChannel, `AudioWorklet unexpected output buffer size (expected ${this.bytesPerChannel} got ${subentry.byteLength})`);
}
}
}
Expand Down
15 changes: 8 additions & 7 deletions src/lib/libwebaudio.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,7 @@ var LibraryWebAudio = {

// Performs the work of getting the AudioContext's render quantum size.
$emscriptenGetContextQuantumSize: (contextHandle) => {
// TODO: in a future release this will be something like:
// return EmAudio[contextHandle].renderQuantumSize || 128;
// It comes two caveats: it needs the hint when generating the context adding to
// emscripten_create_audio_context(), and altering the quantum requires a secure
// context and fallback implementing. Until then we simply use the 1.0 API value:
return 128;
return EmAudio[contextHandle]['renderQuantumSize'] || 128;
},

// emscripten_create_audio_context() does not itself use the
Expand All @@ -100,9 +95,15 @@ var LibraryWebAudio = {
#endif
#endif

// AUDIO_CONTEXT_RENDER_SIZE_DEFAULT and AUDIO_CONTEXT_RENDER_SIZE_HARDWARE
// into their AudioContextRenderSizeCategory enum, or take the positive int.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if a verb got cut off from this comment, or maybe this was intentionally brief?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It made sense when I typed it... I'll clarify it.

function readRenderSizeHint(val) {
return (val < 0) ? 'hardware' : (val || 'default');
}
var opts = options ? {
latencyHint: UTF8ToString({{{ makeGetValue('options', C_STRUCTS.EmscriptenWebAudioCreateAttributes.latencyHint, '*') }}}) || undefined,
sampleRate: {{{ makeGetValue('options', C_STRUCTS.EmscriptenWebAudioCreateAttributes.sampleRate, 'u32') }}} || undefined
sampleRate: {{{ makeGetValue('options', C_STRUCTS.EmscriptenWebAudioCreateAttributes.sampleRate, 'u32') }}} || undefined,
renderSizeHint: readRenderSizeHint({{{ makeGetValue('options', C_STRUCTS.EmscriptenWebAudioCreateAttributes.renderSizeHint, 'i32') }}})
} : undefined;

#if WEBAUDIO_DEBUG
Expand Down
3 changes: 2 additions & 1 deletion src/struct_info.json
Original file line number Diff line number Diff line change
Expand Up @@ -1269,7 +1269,8 @@
"structs": {
"EmscriptenWebAudioCreateAttributes": [
"latencyHint",
"sampleRate"
"sampleRate",
"renderSizeHint"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not quantumSizeHint?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not quantumSizeHint?

I copied what the JS API now calls it:

renderSizeHint: readRenderSizeHint({{{ makeGetValue('options', C_STRUCTS.EmscriptenWebAudioCreateAttributes.renderSizeHint, 'i32') }}})

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there are any users of this API yet, could we rename quantum -> render as well? E.g. in

$emscriptenGetContextQuantumSize -> $emscriptenGetContextRenderSize
emscripten_audio_context_quantum_size -> emscripten_audio_context_render_size
?

Copy link
Collaborator Author

@cwoffenden cwoffenden Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec mixes the two, even for the renderSizeHint it says "This allows users to ask for a particular render quantum size".

Currently there's no reason to use this call, since it's always been 128. It's handy before the callback for calculating the stack size, and I should write an example of this.

],
"WebAudioParamDescriptor": [
"defaultValue",
Expand Down
3 changes: 2 additions & 1 deletion src/struct_info_generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -700,8 +700,9 @@
"visibilityState": 4
},
"EmscriptenWebAudioCreateAttributes": {
"__size__": 8,
"__size__": 12,
"latencyHint": 0,
"renderSizeHint": 8,
"sampleRate": 4
},
"EmscriptenWebGLContextAttributes": {
Expand Down
1 change: 1 addition & 0 deletions src/struct_info_generated_wasm64.json
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@
"EmscriptenWebAudioCreateAttributes": {
"__size__": 16,
"latencyHint": 0,
"renderSizeHint": 12,
"sampleRate": 8
},
"EmscriptenWebGLContextAttributes": {
Expand Down
6 changes: 6 additions & 0 deletions system/include/emscripten/webaudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ extern "C" {

typedef int EMSCRIPTEN_WEBAUDIO_T;

// Default render size of 128 frames
#define AUDIO_CONTEXT_RENDER_SIZE_DEFAULT 0
// Let the hardware determine the best render size
#define AUDIO_CONTEXT_RENDER_SIZE_HARDWARE -1

typedef struct EmscriptenWebAudioCreateAttributes
{
const char *latencyHint; // Specify one of "balanced", "interactive" or "playback"
uint32_t sampleRate; // E.g. 44100 or 48000
int32_t renderSizeHint; // AUDIO_CONTEXT_RENDER_SIZE_* or number of samples
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume since this is a hint, the context creation is allowed to not honor it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume since this is a hint, the context creation is allowed to not honor it?

From the spec:

https://webaudio.github.io/web-audio-api/#dom-audiocontextoptions-rendersizehint

"It is a hint that might not be honored."

} EmscriptenWebAudioCreateAttributes;

// Creates a new Web Audio AudioContext, and returns a handle to it.
Expand Down
41 changes: 22 additions & 19 deletions test/codesize/audio_worklet_wasm.expected.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var m = globalThis.Module || "undefined" != typeof Module ? Module : {}, r = !!globalThis.AudioWorkletGlobalScope, t = "em-ww" == globalThis.name || r, u, z, I, J, G, E, w, X, F, D, C, Y, A, Z;
var m = globalThis.Module || "undefined" != typeof Module ? Module : {}, p = !!globalThis.AudioWorkletGlobalScope, t = "em-ww" == globalThis.name || p, u, z, I, J, G, E, w, X, F, D, C, Y, A, Z;

function v(a) {
u = a;
Expand All @@ -10,12 +10,12 @@ function v(a) {
a.G = a.M = 0;
}

t && !r && (onmessage = a => {
t && !p && (onmessage = a => {
onmessage = null;
v(a.data);
});

if (r) {
if (p) {
function a(b) {
class h extends AudioWorkletProcessor {
constructor(d) {
Expand All @@ -36,7 +36,7 @@ if (r) {
return b;
}
process(d, g, e) {
var l = d.length, p = g.length, f, q, k = 12 * (l + p), n = 0;
var l = d.length, q = g.length, f, r, k = 12 * (l + q), n = 0;
for (f of d) n += f.length;
n *= this.s;
var H = 0;
Expand All @@ -53,15 +53,15 @@ if (r) {
G[k + 4 >> 2] = this.u;
G[k + 8 >> 2] = n;
k += 12;
for (q of f) E.set(q, n >> 2), n += this.s;
for (r of f) E.set(r, n >> 2), n += this.s;
}
d = k;
for (f = 0; q = e[f++]; ) G[k >> 2] = q.length, G[k + 4 >> 2] = n, k += 8, E.set(q, n >> 2),
n += 4 * q.length;
for (f = 0; r = e[f++]; ) G[k >> 2] = r.length, G[k + 4 >> 2] = n, k += 8, E.set(r, n >> 2),
n += 4 * r.length;
e = k;
for (f of g) G[k >> 2] = f.length, G[k + 4 >> 2] = this.u, G[k + 8 >> 2] = n, k += 12,
n += this.s * f.length;
if (l = this.v(l, B, p, e, N, d, this.A)) for (f of g) for (q of f) q.set(this.B[--H]);
if (l = this.v(l, B, q, e, N, d, this.A)) for (f of g) for (r of f) r.set(this.B[--H]);
F(U);
return !!l;
}
Expand Down Expand Up @@ -129,9 +129,12 @@ var K = [], L = a => {
}, T = a => {
if (a) {
var c = G[a >> 2];
c = (c ? S(c) : "") || void 0;
var b = J[a + 8 >> 2];
a = {
latencyHint: (c ? S(c) : "") || void 0,
sampleRate: G[a + 4 >> 2] || void 0
latencyHint: c,
sampleRate: G[a + 4 >> 2] || void 0,
N: 0 > b ? "hardware" : b || "default"
};
} else a = void 0;
a = new AudioContext(a);
Expand All @@ -140,10 +143,10 @@ var K = [], L = a => {
}, V = (a, c, b, h, d) => {
var g = b ? J[b + 4 >> 2] : 0;
if (b) {
var e = J[b >> 2], l = G[b + 8 >> 2], p = g;
var e = J[b >> 2], l = G[b + 8 >> 2], q = g;
if (l) {
l >>= 2;
for (var f = []; p--; ) f.push(G[l++]);
for (var f = []; q--; ) f.push(G[l++]);
l = f;
} else l = void 0;
b = {
Expand All @@ -156,7 +159,7 @@ var K = [], L = a => {
processorOptions: {
v: h,
A: d,
u: 128
u: O[a].renderQuantumSize || 128
}
};
} else b = void 0;
Expand Down Expand Up @@ -191,17 +194,17 @@ var K = [], L = a => {
if (!e) return l();
e.addModule(m.js).then((() => {
e.port || (e.port = {
postMessage: p => {
p._boot ? (e.D = new AudioWorkletNode(g, "em-bootstrap", {
processorOptions: p
postMessage: q => {
q._boot ? (e.D = new AudioWorkletNode(g, "em-bootstrap", {
processorOptions: q
}), e.D.port.onmessage = f => {
e.port.onmessage(f);
}) : e.D.port.postMessage(p);
}) : e.D.port.postMessage(q);
}
});
e.port.postMessage({
_boot: 1,
N: ba++,
O: ba++,
G: m.wasm,
L: w,
J: c,
Expand Down Expand Up @@ -243,7 +246,7 @@ function y() {
C = a.n;
Y = a.o;
A = a.k;
t ? (Y(u.J, u.F), r || (removeEventListener("message", M), K = K.forEach(L), addEventListener("message", L))) : a.i();
t ? (Y(u.J, u.F), p || (removeEventListener("message", M), K = K.forEach(L), addEventListener("message", L))) : a.i();
t || X();
}));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"a.html": 519,
"a.html.gz": 357,
"a.js": 4235,
"a.js.gz": 2170,
"a.js": 4309,
"a.js.gz": 2219,
"a.wasm": 1329,
"a.wasm.gz": 895,
"total": 6083,
"total_gz": 3422
"total": 6157,
"total_gz": 3471
}
8 changes: 8 additions & 0 deletions test/webaudio/audioworklet_params_mixing.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@
// create variable parameter data sizes, depending on the browser, it's also the
// ideal to test audio worklets don't corrupt TLS variables.

// Large render size (approx 42ms)
#define RENDER_SIZE_HINT 2048

// This needs to be big enough for the stereo output, 2x inputs, 2x params and
// the worker stack. To note that different browsers have different stack size
// requirement (see notes in process() plus the expansion of the params).
#ifndef RENDER_SIZE_HINT
#define AUDIO_STACK_SIZE 6144
#else
// float bytes * stereo * ins/outs + extra stack
#define AUDIO_STACK_SIZE ((RENDER_SIZE_HINT * 4 * 2 * 5) + 1024)
#endif

// Shared file playback and bootstrap
#include "audioworklet_test_shared.inc"
Expand Down
6 changes: 5 additions & 1 deletion test/webaudio/audioworklet_test_shared.inc
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,12 @@ int main(void) {
assert(workletStack);

// Set at least the latency hint to test the attribute setting
// (The render size we take from the calling code if set)
EmscriptenWebAudioCreateAttributes attrs = {
.latencyHint = "balanced"
.latencyHint = "balanced",
#ifdef RENDER_SIZE_HINT
.renderSizeHint = RENDER_SIZE_HINT
#endif
};
EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(&attrs);
emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, AUDIO_STACK_SIZE, getStartCallback(), NULL);
Expand Down