From 2bce44e686a53de7ff478acc163bb51facbc849f Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 8 Aug 2025 18:31:23 +0200 Subject: [PATCH 01/22] Work-in-progress (fails with wasm64) --- src/audio_worklet.js | 147 +++++++++++++++++++++++++++++++------------ 1 file changed, 108 insertions(+), 39 deletions(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 29fa48b3fc76f..e5c159328ff3e 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -28,11 +28,43 @@ function createWasmAudioWorkletProcessor(audioParams) { this.callback = {{{ makeDynCall('iipipipp', 'opts.callback') }}}; this.userData = opts.userData; // Then the samples per channel to process, fixed for the lifetime of the - // context that created this processor. Note for when moving to Web Audio - // 1.1: the typed array passed to process() should be the same size as this - // 'render quantum size', and this exercise of passing in the value - // shouldn't be required (to be verified) + // context that created this processor. Even though this 'render quantum + // size' is fixed at 128 samples in the 1.0 spec, it will be variable in + // the 1.1 spec. It's passed in now, just to prove it's settable, but will + // eventually be a property of the AudioWorkletGlobalScope (globalThis). this.samplesPerChannel = opts.samplesPerChannel; + this.bytesPerChannel = this.samplesPerChannel * {{{ getNativeTypeSize('float') }}}; + + // Create up-front as many typed views for marshalling the output data as + // may be required (with an arbitrary maximum of 16, for the case where a + // multi-MB stack is passed), allocated at the *top* of the worklet's + // stack (and whose addresses are fixed). The 'minimum alloc' firstly + // stops STACK_OVERFLOW_CHECK failing (since the stack will be full, and + // 16 being the minimum allocation size due to alignments) and leaves room + // for a single AudioSampleFrame as a minumum. + this.maxBuffers = Math.min(((wwParams.stackSize - /*minimum alloc*/ 16) / this.bytesPerChannel) | 0, /*sensible limit*/ 16); +#if ASSERTIONS + console.assert(this.maxBuffers > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`); +#endif + // These are still alloc'd to take advantage of the overflow checks, etc. + var oldStackPtr = stackSave(); + var viewDataIdx = {{{ getHeapOffset('stackAlloc(this.maxBuffers * this.bytesPerChannel)', 'float') }}}; +#if WEBAUDIO_DEBUG + console.log(`AudioWorklet creating ${this.maxBuffers} buffer one-time views (for a stack size of ${wwParams.stackSize} at address 0x${(viewDataIdx * 4).toString(16)})`); +#endif + this.outputViews = []; + for (var n = this.maxBuffers; n > 0; n--) { + // Added in reverse so the lowest indices are closest to the stack top + this.outputViews.unshift( + HEAPF32.subarray(viewDataIdx, viewDataIdx += this.samplesPerChannel) + ); + } + stackRestore(oldStackPtr); + +#if ASSERTIONS + // Explicitly verify this later in process() + this.ctorOldStackPtr = oldStackPtr; +#endif } static get parameterDescriptors() { @@ -53,26 +85,42 @@ function createWasmAudioWorkletProcessor(audioParams) { var entry; // reused list entry or index var subentry; // reused channel or other array in each list entry or index - // Calculate how much stack space is needed. - var bytesPerChannel = this.samplesPerChannel * {{{ getNativeTypeSize('float') }}}; + // Calculate the required stack and output buffer views var stackMemoryNeeded = (numInputs + numOutputs) * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; + for (entry of inputList) { + stackMemoryNeeded += entry.length * this.bytesPerChannel; + } + var outputViewsNeeded = 0; + for (entry of outputList) { + outputViewsNeeded += entry.length; + } + stackMemoryNeeded += outputViewsNeeded * this.bytesPerChannel; var numParams = 0; - for (entry of inputList) stackMemoryNeeded += entry.length * bytesPerChannel; - for (entry of outputList) stackMemoryNeeded += entry.length * bytesPerChannel; for (entry in parameters) { stackMemoryNeeded += parameters[entry].byteLength + {{{ C_STRUCTS.AudioParamFrame.__size__ }}}; ++numParams; } +#if ASSERTIONS + console.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. var oldStackPtr = stackSave(); - var inputsPtr = stackAlloc(stackMemoryNeeded); + // Allocate the necessary stack space. All pointer variables are always in + // bytes; 'dataPtr' is the start of the data section, and advances as + // space for structs and data is taken; 'structPtr' is reused as the + // working start to each struct record. + // Here 'dataPtr' will be 16-byte aligned, from _emscripten_stack_alloc(), + // as were the output views, so we round up and advance the required bytes + // to ensure the addresses all work out at the end. + entry = (stackMemoryNeeded + 15) & ~15; + var dataPtr = stackAlloc(entry) + (entry - stackMemoryNeeded); - // Copy input audio descriptor structs and data to Wasm ('structPtr' is - // reused as the working start to each struct record, 'dataPtr' start of - // the data section, usually after all structs). + // Copy input audio descriptor structs and data to Wasm (recall, structs + // first, audio data after). 'inputsPtr' is the start of the C callback's + // input AudioSampleFrame. + var /*const*/ inputsPtr = dataPtr; var structPtr = inputsPtr; - var dataPtr = inputsPtr + numInputs * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; + dataPtr += numInputs * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; for (entry of inputList) { // Write the AudioSampleFrame struct instance {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'entry.length', 'u32') }}}; @@ -82,26 +130,13 @@ function createWasmAudioWorkletProcessor(audioParams) { // Marshal the input audio sample data for each audio channel of this input for (subentry of entry) { HEAPF32.set(subentry, {{{ getHeapOffset('dataPtr', 'float') }}}); - dataPtr += bytesPerChannel; + dataPtr += this.bytesPerChannel; } } - // Copy output audio descriptor structs to Wasm - var outputsPtr = dataPtr; - structPtr = outputsPtr; - var outputDataPtr = (dataPtr += numOutputs * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}); - for (entry of outputList) { - // Write the AudioSampleFrame struct instance - {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'entry.length', 'u32') }}}; - {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.samplesPerChannel, 'this.samplesPerChannel', 'u32') }}}; - {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.data, 'dataPtr', '*') }}}; - structPtr += {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; - // Reserve space for the output data - dataPtr += bytesPerChannel * entry.length; - } - - // Copy parameters descriptor structs and data to Wasm - var paramsPtr = dataPtr; + // Copy parameters descriptor structs and data to Wasm. 'paramsPtr' is the + // start of the C callback's input AudioParamFrame. + var /*const*/ paramsPtr = dataPtr; structPtr = paramsPtr; dataPtr += numParams * {{{ C_STRUCTS.AudioParamFrame.__size__ }}}; for (entry = 0; subentry = parameters[entry++];) { @@ -114,20 +149,54 @@ function createWasmAudioWorkletProcessor(audioParams) { dataPtr += subentry.length * {{{ getNativeTypeSize('float') }}}; } + // Copy output audio descriptor structs to Wasm. 'outputsPtr' is the start + // of the C callback's output AudioSampleFrame. + // Note: dataPtr after the struct offsets should now be 16-byte aligned. + var /*const*/ outputsPtr = dataPtr; + structPtr = outputsPtr; + dataPtr += numOutputs * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; + for (entry of outputList) { + // Write the AudioSampleFrame struct instance + {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'entry.length', 'u32') }}}; + {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.samplesPerChannel, 'this.samplesPerChannel', 'u32') }}}; + {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.data, 'dataPtr', '*') }}}; + structPtr += {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; + // Advance the output pointer to the next output (matching the pre-allocated views) + dataPtr += this.bytesPerChannel * entry.length; + } + +#if ASSERTIONS + // If all the maths worked out, we arrived at the original stack address + console.assert(dataPtr == oldStackPtr, `AudioWorklet stack missmatch (audio data finishes at ${dataPtr} instead of ${oldStackPtr})`); + + // Sanity checks. If these trip the most likely cause, beyond unforeseen + // stack shenanigans, is that the 'render quantum size' changed after + // construction (which shouldn't be possible). + if (numOutputs) { + // First that the output view addresses match the stack positions + dataPtr -= this.bytesPerChannel; + for (entry = 0; entry < outputViewsNeeded; entry++) { + console.assert(dataPtr == this.outputViews[entry].byteOffset, 'AudioWorklet internal error in addresses of the output array views'); + dataPtr -= this.bytesPerChannel; + } + // 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})`); + } + } + } +#endif + // Call out to Wasm callback to perform audio processing var didProduceAudio = this.callback(numInputs, inputsPtr, numOutputs, outputsPtr, numParams, paramsPtr, this.userData); if (didProduceAudio) { // Read back the produced audio data to all outputs and their channels. - // (A garbage-free function TypedArray.copy(dstTypedArray, dstOffset, - // srcTypedArray, srcOffset, count) would sure be handy.. but web does - // not have one, so manually copy all bytes in) - outputDataPtr = {{{ getHeapOffset('outputDataPtr', 'float') }}}; + // The preallocated 'outputViews' already have the correct offsets and + // sizes into the stack (recall from the ctor that they run backwards). for (entry of outputList) { for (subentry of entry) { - // repurposing structPtr for now - for (structPtr = 0; structPtr < this.samplesPerChannel; ++structPtr) { - subentry[structPtr] = HEAPF32[outputDataPtr++]; - } + subentry.set(this.outputViews[--outputViewsNeeded]); } } } From 17e4415d5398178d67bf57ddfc175bb357d8d73c Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 8 Aug 2025 19:10:09 +0200 Subject: [PATCH 02/22] Minor docs --- src/audio_worklet.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index e5c159328ff3e..e6dfbd21f70f9 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -109,9 +109,10 @@ function createWasmAudioWorkletProcessor(audioParams) { // bytes; 'dataPtr' is the start of the data section, and advances as // space for structs and data is taken; 'structPtr' is reused as the // working start to each struct record. - // Here 'dataPtr' will be 16-byte aligned, from _emscripten_stack_alloc(), - // as were the output views, so we round up and advance the required bytes - // to ensure the addresses all work out at the end. + // Ordinarily 'dataPtr' would be 16-byte aligned, from the internal + // _emscripten_stack_alloc(), as were the output views, but we further + // round up the requested size and advance the required bytes to ensure + // the addresses finish at the stacktop in the end. entry = (stackMemoryNeeded + 15) & ~15; var dataPtr = stackAlloc(entry) + (entry - stackMemoryNeeded); From f4710d5d48acbccca1fd883ff3c482c5bb554bfa Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 8 Aug 2025 19:49:55 +0200 Subject: [PATCH 03/22] Minor docs --- test/webaudio/audioworklet_params_mixing.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/webaudio/audioworklet_params_mixing.c b/test/webaudio/audioworklet_params_mixing.c index 7b2f56a80cae0..21f8661cd0817 100644 --- a/test/webaudio/audioworklet_params_mixing.c +++ b/test/webaudio/audioworklet_params_mixing.c @@ -33,7 +33,7 @@ bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, Audi } // Interestingly, params varies per browser. Chrome won't have a length > 1 // unless the value changes, and FF has all 128 entries even for a k-rate - // parameter. The only given is that two params are incoming: + // parameter. The only given for this test is that two params are incoming: assert(numParams == 2); assert(params[0].length == 1 || params[0].length == outSamplesPerChannel); assert(params[1].length == 1 || params[1].length == outSamplesPerChannel); From 85fe599eb3419ab27d2b5247a707039139030024 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 8 Aug 2025 19:56:34 +0200 Subject: [PATCH 04/22] Minor docs --- src/audio_worklet.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index e6dfbd21f70f9..040bc7bf7f04f 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -106,9 +106,9 @@ function createWasmAudioWorkletProcessor(audioParams) { var oldStackPtr = stackSave(); // Allocate the necessary stack space. All pointer variables are always in - // bytes; 'dataPtr' is the start of the data section, and advances as - // space for structs and data is taken; 'structPtr' is reused as the - // working start to each struct record. + // bytes; 'dataPtr' is the start of the data section, advancing as space + // for structs and data is taken; 'structPtr' is reused as the working + // start to each struct record. // Ordinarily 'dataPtr' would be 16-byte aligned, from the internal // _emscripten_stack_alloc(), as were the output views, but we further // round up the requested size and advance the required bytes to ensure From 09cdfedeeba62364bbdbf651f4ebdede966e51df Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 8 Aug 2025 19:59:55 +0200 Subject: [PATCH 05/22] Clarification --- src/audio_worklet.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 040bc7bf7f04f..61dfba2e60e8f 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -110,9 +110,9 @@ function createWasmAudioWorkletProcessor(audioParams) { // for structs and data is taken; 'structPtr' is reused as the working // start to each struct record. // Ordinarily 'dataPtr' would be 16-byte aligned, from the internal - // _emscripten_stack_alloc(), as were the output views, but we further - // round up the requested size and advance the required bytes to ensure - // the addresses finish at the stacktop in the end. + // _emscripten_stack_alloc(), as were the output views, and so to ensure + // the views fall on the correct addresses (and we finish at the stacktop) + // bytes are added and the start advanced. entry = (stackMemoryNeeded + 15) & ~15; var dataPtr = stackAlloc(entry) + (entry - stackMemoryNeeded); From 62cb5d95a7c718d53e32b8a2f1d29ae45f4b2430 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 8 Aug 2025 20:16:40 +0200 Subject: [PATCH 06/22] Temp workaround for (pos.) unaligned structs --- src/audio_worklet.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 61dfba2e60e8f..552dd0c19c669 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -113,8 +113,8 @@ function createWasmAudioWorkletProcessor(audioParams) { // _emscripten_stack_alloc(), as were the output views, and so to ensure // the views fall on the correct addresses (and we finish at the stacktop) // bytes are added and the start advanced. - entry = (stackMemoryNeeded + 15) & ~15; - var dataPtr = stackAlloc(entry) + (entry - stackMemoryNeeded); + var alignedNeededStack = (stackMemoryNeeded + 15) & ~15; + var dataPtr = stackAlloc(alignedNeededStack); // Copy input audio descriptor structs and data to Wasm (recall, structs // first, audio data after). 'inputsPtr' is the start of the C callback's @@ -150,6 +150,12 @@ function createWasmAudioWorkletProcessor(audioParams) { dataPtr += subentry.length * {{{ getNativeTypeSize('float') }}}; } + // TODO: why does Chrome wasm64 (Chrome has weird rules for params) need + // the manual alignment here? An off-by-one somewhere? outputsPtr is + // getting clobbered otherwise, is the struct correctly aligned? Do more + // stack allocs instead? Probably needs alignments between struct writes? + dataPtr += alignedNeededStack - stackMemoryNeeded; + // Copy output audio descriptor structs to Wasm. 'outputsPtr' is the start // of the C callback's output AudioSampleFrame. // Note: dataPtr after the struct offsets should now be 16-byte aligned. From c06c3c4158fe711b2fe8daf957efce85474b5d20 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 8 Aug 2025 20:16:54 +0200 Subject: [PATCH 07/22] Code size updates --- test/code_size/audio_worklet_wasm.js | 247 +++++++++--------- ...nimal_runtime_code_size_audio_worklet.json | 8 +- 2 files changed, 130 insertions(+), 125 deletions(-) diff --git a/test/code_size/audio_worklet_wasm.js b/test/code_size/audio_worklet_wasm.js index 6792f50a603dd..96048c30f62df 100644 --- a/test/code_size/audio_worklet_wasm.js +++ b/test/code_size/audio_worklet_wasm.js @@ -1,18 +1,18 @@ -var l = globalThis.Module || "undefined" != typeof Module ? Module : {}, n = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, t, v, J, K, E, F, A, X, H, C, B, Y, Z; +var k = globalThis.Module || "undefined" != typeof Module ? Module : {}, p = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, t, v, J, K, F, D, A, X, E, C, B, Y, Z; -q && (n = !0); +q && (p = !0); function u(a) { t = a; - v = a.I; - y(); - l ||= {}; - l.wasm = a.C; + v = a.L; + x(); + k ||= {}; + k.wasm = a.H; z(); - a.C = a.J = 0; + a.H = a.M = 0; } -n && !q && (onmessage = a => { +p && !q && (onmessage = a => { onmessage = null; u(a.data); }); @@ -23,43 +23,48 @@ if (q) { constructor(d) { super(); d = d.processorOptions; - this.u = A.get(d.u); - this.v = d.v; - this.s = d.s; + this.v = A.get(d.v); + this.A = d.A; + this.u = d.u; + this.s = 4 * this.u; + this.F = Math.min((t.D - 16) / this.s | 0, 16); + d = B(); + var f = C(this.F * this.s) >> 2; + this.G = []; + for (var g = this.F; 0 < g; g--) this.G.unshift(D.subarray(f, f += this.u)); + E(d); } static get parameterDescriptors() { return c; } - process(d, g, h) { - var p = d.length, w = g.length, k, r, x = 4 * this.s, f = 12 * (p + w), D = 0; - for (k of d) f += k.length * x; - for (k of g) f += k.length * x; - for (k in h) f += h[k].byteLength + 8, ++D; - var V = B(), G = C(f); - f = G; - var m = G + 12 * p; - for (k of d) { - E[f >> 2] = k.length; - E[f + 4 >> 2] = this.s; - E[f + 8 >> 2] = m; - f += 12; - for (r of k) F.set(r, m >> 2), m += x; + process(d, f, g) { + var n = d.length, w = f.length, h, r, y = 12 * (n + w); + for (h of d) y += h.length * this.s; + var G = 0; + for (h of f) G += h.length; + y += G * this.s; + var H = 0; + for (h in g) y += g[h].byteLength + 8, ++H; + var W = B(), O = y + 15 & -16, l = C(O), P = l, m = P; + l += 12 * n; + for (h of d) { + F[m >> 2] = h.length; + F[m + 4 >> 2] = this.u; + F[m + 8 >> 2] = l; + m += 12; + for (r of h) D.set(r, l >> 2), l += this.s; } - var O = m; - f = O; - d = m += 12 * w; - for (k of g) E[f >> 2] = k.length, E[f + 4 >> 2] = this.s, E[f + 8 >> 2] = m, f += 12, - m += x * k.length; - f = x = m; - m += 8 * D; - for (k = 0; r = h[k++]; ) E[f >> 2] = r.length, E[f + 4 >> 2] = m, f += 8, F.set(r, m >> 2), - m += 4 * r.length; - if (h = this.u(p, G, w, O, D, x, this.v)) { - d >>= 2; - for (k of g) for (r of k) for (f = 0; f < this.s; ++f) r[f] = F[d++]; - } - H(V); - return !!h; + m = d = l; + l += 8 * H; + for (h = 0; r = g[h++]; ) F[m >> 2] = r.length, F[m + 4 >> 2] = l, m += 8, D.set(r, l >> 2), + l += 4 * r.length; + m = g = l += O - y; + l += 12 * w; + for (h of f) F[m >> 2] = h.length, F[m + 4 >> 2] = this.u, F[m + 8 >> 2] = l, m += 12, + l += this.s * h.length; + if (n = this.v(n, P, w, g, H, d, this.A)) for (h of f) for (r of h) r.set(this.G[--G]); + E(W); + return !!n; } } return e; @@ -72,10 +77,10 @@ if (q) { I = this.port; I.onmessage = async e => { e = e.data; - e._wpn ? (registerProcessor(e._wpn, a(e.D)), I.postMessage({ - _wsc: e.u, - A: [ e.F, 1, e.v ] - })) : e._wsc && A.get(e._wsc)(...e.A); + e._wpn ? (registerProcessor(e._wpn, a(e.I)), I.postMessage({ + _wsc: e.v, + B: [ e.J, 1, e.A ] + })) : e._wsc && A.get(e._wsc)(...e.B); }; } process() {} @@ -83,19 +88,19 @@ if (q) { registerProcessor("em-bootstrap", b); } -function y() { +function x() { var a = v.buffer; J = new Uint8Array(a); K = new Int32Array(a); - E = new Uint32Array(a); - F = new Float32Array(a); + F = new Uint32Array(a); + D = new Float32Array(a); } -n || (v = l.mem || new WebAssembly.Memory({ +p || (v = k.mem || new WebAssembly.Memory({ initial: 256, maximum: 256, shared: !0 -}), y()); +}), x()); var L = [], M = a => { a = a.data; @@ -103,106 +108,106 @@ var L = [], M = a => { b && A.get(b)(...a.x); }, N = a => { L.push(a); -}, P = a => H(a), Q = () => B(), S = (a, b, c, e) => { - b = R[b]; - R[a].connect(b.destination || b, c, e); -}, R = {}, T = 0, U = "undefined" != typeof TextDecoder ? new TextDecoder : void 0, W = (a = 0) => { +}, Q = a => E(a), R = () => B(), aa = (a, b, c, e) => { + b = S[b]; + S[a].connect(b.destination || b, c, e); +}, S = {}, T = 0, U = "undefined" != typeof TextDecoder ? new TextDecoder : void 0, V = (a = 0) => { for (var b = J, c = a, e = c + void 0; b[c] && !(c >= e); ) ++c; if (16 < c - a && b.buffer && U) return U.decode(b.slice(a, c)); for (e = ""; a < c; ) { var d = b[a++]; if (d & 128) { - var g = b[a++] & 63; - if (192 == (d & 224)) e += String.fromCharCode((d & 31) << 6 | g); else { - var h = b[a++] & 63; - d = 224 == (d & 240) ? (d & 15) << 12 | g << 6 | h : (d & 7) << 18 | g << 12 | h << 6 | b[a++] & 63; + var f = b[a++] & 63; + if (192 == (d & 224)) e += String.fromCharCode((d & 31) << 6 | f); else { + var g = b[a++] & 63; + d = 224 == (d & 240) ? (d & 15) << 12 | f << 6 | g : (d & 7) << 18 | f << 12 | g << 6 | b[a++] & 63; 65536 > d ? e += String.fromCharCode(d) : (d -= 65536, e += String.fromCharCode(55296 | d >> 10, 56320 | d & 1023)); } } else e += String.fromCharCode(d); } return e; -}, aa = a => { +}, ba = a => { var b = window.AudioContext || window.webkitAudioContext; if (a) { - var c = E[a >> 2]; + var c = F[a >> 2]; a = { - latencyHint: (c ? W(c) : "") || void 0, - sampleRate: E[a + 4 >> 2] || void 0 + latencyHint: (c ? V(c) : "") || void 0, + sampleRate: F[a + 4 >> 2] || void 0 }; } else a = void 0; - if (c = b) b = new b(a), R[++T] = b, c = T; + if (c = b) b = new b(a), S[++T] = b, c = T; return c; -}, ba = (a, b, c, e, d) => { - var g = c ? K[c + 4 >> 2] : 0; +}, ca = (a, b, c, e, d) => { + var f = c ? K[c + 4 >> 2] : 0; if (c) { - var h = K[c >> 2]; - c = E[c + 8 >> 2]; - var p = g; + var g = K[c >> 2]; + c = F[c + 8 >> 2]; + var n = f; if (c) { c >>= 2; - for (var w = []; p--; ) w.push(E[c++]); + for (var w = []; n--; ) w.push(F[c++]); c = w; } else c = void 0; e = { - numberOfInputs: h, - numberOfOutputs: g, + numberOfInputs: g, + numberOfOutputs: f, outputChannelCount: c, processorOptions: { - u: e, - v: d, - s: 128 + v: e, + A: d, + u: 128 } }; } else e = void 0; - a = new AudioWorkletNode(R[a], b ? W(b) : "", e); - R[++T] = a; + a = new AudioWorkletNode(S[a], b ? V(b) : "", e); + S[++T] = a; return T; -}, ca = (a, b, c, e) => { - var d = [], g = (g = E[b >> 2]) ? W(g) : "", h = K[b + 4 >> 2]; - b = E[b + 8 >> 2]; - for (var p = 0; h--; ) d.push({ - name: p++, - defaultValue: F[b >> 2], - minValue: F[b + 4 >> 2], - maxValue: F[b + 8 >> 2], +}, da = (a, b, c, e) => { + var d = [], f = (f = F[b >> 2]) ? V(f) : "", g = K[b + 4 >> 2]; + b = F[b + 8 >> 2]; + for (var n = 0; g--; ) d.push({ + name: n++, + defaultValue: D[b >> 2], + minValue: D[b + 4 >> 2], + maxValue: D[b + 8 >> 2], automationRate: (K[b + 12 >> 2] ? "k" : "a") + "-rate" }), b += 16; - R[a].audioWorklet.B.port.postMessage({ - _wpn: g, - D: d, - F: a, - u: c, - v: e + S[a].audioWorklet.C.port.postMessage({ + _wpn: f, + I: d, + J: a, + v: c, + A: e }); -}, da = () => !1, ea = 1, fa = a => { +}, ea = () => !1, fa = 1, ha = a => { a = a.data; var b = a._wsc; - b && A.get(b)(...a.A); -}, ha = a => C(a), ia = (a, b, c, e, d) => { - var g = R[a], h = g.audioWorklet, p = () => { + b && A.get(b)(...a.B); +}, ia = a => C(a), ja = (a, b, c, e, d) => { + var f = S[a], g = f.audioWorklet, n = () => { A.get(e)(a, 0, d); }; - if (!h) return p(); - h.addModule(l.js).then((() => { - h.B = new AudioWorkletNode(g, "em-bootstrap", { + if (!g) return n(); + g.addModule(k.js).then((() => { + g.C = new AudioWorkletNode(f, "em-bootstrap", { processorOptions: { - K: ea++, - C: l.wasm, - I: v, - G: b, - H: c + N: fa++, + H: k.wasm, + L: v, + K: b, + D: c } }); - h.B.port.onmessage = fa; + g.C.port.onmessage = ha; A.get(e)(a, 1, d); - })).catch(p); + })).catch(n); }; -function ja(a) { +function ka(a) { let b = document.createElement("button"); b.innerHTML = "Toggle playback"; document.body.appendChild(b); - a = R[a]; + a = S[a]; b.onclick = () => { "running" != a.state ? a.resume() : a.suspend(); }; @@ -210,33 +215,33 @@ function ja(a) { function z() { Z = { - f: ja, - g: S, - d: aa, - h: ba, - e: ca, - b: da, - c: ia, + f: ka, + g: aa, + d: ba, + h: ca, + e: da, + b: ea, + c: ja, a: v }; - WebAssembly.instantiate(l.wasm, { + WebAssembly.instantiate(k.wasm, { a: Z }).then((a => { a = (a.instance || a).exports; X = a.j; - H = a.l; + E = a.l; C = a.m; B = a.n; Y = a.o; A = a.k; - l.stackSave = Q; - l.stackAlloc = ha; - l.stackRestore = P; - l.wasmTable = A; - n ? (Y(t.G, t.H), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", N), + k.stackSave = R; + k.stackAlloc = ia; + k.stackRestore = Q; + k.wasmTable = A; + p ? (Y(t.K, t.D), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", N), L = L.forEach(M), addEventListener("message", M))) : a.i(); - n || X(); + p || X(); })); } -n || z(); \ No newline at end of file +p || z(); \ No newline at end of file diff --git a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json index cf4f0e7fce368..ed6a08c3cd13e 100644 --- a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json +++ b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json @@ -1,10 +1,10 @@ { "a.html": 519, "a.html.gz": 357, - "a.js": 3882, - "a.js.gz": 2038, + "a.js": 4062, + "a.js.gz": 2128, "a.wasm": 1288, "a.wasm.gz": 860, - "total": 5689, - "total_gz": 3255 + "total": 5869, + "total_gz": 3345 } From 52e6ee4af1be053fd7d658e8d797fdfb18a62fd6 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 12 Aug 2025 18:16:47 +0200 Subject: [PATCH 08/22] Simplify struct fills, fix wasm64 --- src/audio_worklet.js | 66 ++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 552dd0c19c669..6ef9e5ff67dda 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -72,56 +72,60 @@ function createWasmAudioWorkletProcessor(audioParams) { } /** + * Marshals all inputs and parameters to the Wasm memory on the thread's + * stack, then performs the wasm audio worklet call, and finally marshals + * audio output data back. + * * @param {Object} parameters */ process(inputList, outputList, parameters) { - // Marshal all inputs and parameters to the Wasm memory on the thread stack, - // then perform the wasm audio worklet call, - // and finally marshal audio output data back. - var numInputs = inputList.length; var numOutputs = outputList.length; var entry; // reused list entry or index var subentry; // reused channel or other array in each list entry or index - // Calculate the required stack and output buffer views - var stackMemoryNeeded = (numInputs + numOutputs) * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; + // Calculate the required stack and output buffer views (stack is further + // split into aligned structs and the raw float data). + var stackMemoryStruct = (numInputs + numOutputs) * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; + var stackMemoryData = 0; for (entry of inputList) { - stackMemoryNeeded += entry.length * this.bytesPerChannel; + stackMemoryData += entry.length * this.bytesPerChannel; } + // Collect the total number of output channels (mapped to array views) var outputViewsNeeded = 0; for (entry of outputList) { outputViewsNeeded += entry.length; } - stackMemoryNeeded += outputViewsNeeded * this.bytesPerChannel; + stackMemoryData += outputViewsNeeded * this.bytesPerChannel; var numParams = 0; for (entry in parameters) { - stackMemoryNeeded += parameters[entry].byteLength + {{{ C_STRUCTS.AudioParamFrame.__size__ }}}; ++numParams; + stackMemoryStruct += {{{ C_STRUCTS.AudioParamFrame.__size__ }}}; + stackMemoryData += parameters[entry].byteLength; } #if ASSERTIONS console.assert(outputViewsNeeded <= this.outputViews.length, `Too many AudioWorklet outputs (need ${outputViewsNeeded} but have stack space for ${this.outputViews.length})`); #endif var oldStackPtr = stackSave(); - // Allocate the necessary stack space. All pointer variables are always in - // bytes; 'dataPtr' is the start of the data section, advancing as space - // for structs and data is taken; 'structPtr' is reused as the working - // start to each struct record. - // Ordinarily 'dataPtr' would be 16-byte aligned, from the internal - // _emscripten_stack_alloc(), as were the output views, and so to ensure - // the views fall on the correct addresses (and we finish at the stacktop) - // bytes are added and the start advanced. - var alignedNeededStack = (stackMemoryNeeded + 15) & ~15; - var dataPtr = stackAlloc(alignedNeededStack); + // Allocate the necessary stack space. All pointer variables are in bytes; + // 'structPtr' starts at the first struct entry (all run sequentially) + // and is the working start to each record; 'dataPtr' is the same for the + // audio/params data, starting after *all* the structs. + // 'structPtr' begins 16-byte aligned, allocated from the internal + // _emscripten_stack_alloc(), as are the output views, and so to ensure + // the views fall on the correct addresses (and we finish at stacktop) we + // request additional bytes, taking this alignment into account, then + // offset `dataPtr` by the difference. + var stackMemoryAligned = (stackMemoryStruct + stackMemoryData + 15) & ~15; + var structPtr = stackAlloc(stackMemoryAligned); + var dataPtr = structPtr + (stackMemoryAligned - stackMemoryData); // Copy input audio descriptor structs and data to Wasm (recall, structs // first, audio data after). 'inputsPtr' is the start of the C callback's // input AudioSampleFrame. - var /*const*/ inputsPtr = dataPtr; - var structPtr = inputsPtr; - dataPtr += numInputs * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; + var /*const*/ inputsPtr = structPtr; for (entry of inputList) { // Write the AudioSampleFrame struct instance {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'entry.length', 'u32') }}}; @@ -137,9 +141,7 @@ function createWasmAudioWorkletProcessor(audioParams) { // Copy parameters descriptor structs and data to Wasm. 'paramsPtr' is the // start of the C callback's input AudioParamFrame. - var /*const*/ paramsPtr = dataPtr; - structPtr = paramsPtr; - dataPtr += numParams * {{{ C_STRUCTS.AudioParamFrame.__size__ }}}; + var /*const*/ paramsPtr = structPtr; for (entry = 0; subentry = parameters[entry++];) { // Write the AudioParamFrame struct instance {{{ makeSetValue('structPtr', C_STRUCTS.AudioParamFrame.length, 'subentry.length', 'u32') }}}; @@ -150,18 +152,10 @@ function createWasmAudioWorkletProcessor(audioParams) { dataPtr += subentry.length * {{{ getNativeTypeSize('float') }}}; } - // TODO: why does Chrome wasm64 (Chrome has weird rules for params) need - // the manual alignment here? An off-by-one somewhere? outputsPtr is - // getting clobbered otherwise, is the struct correctly aligned? Do more - // stack allocs instead? Probably needs alignments between struct writes? - dataPtr += alignedNeededStack - stackMemoryNeeded; - // Copy output audio descriptor structs to Wasm. 'outputsPtr' is the start - // of the C callback's output AudioSampleFrame. - // Note: dataPtr after the struct offsets should now be 16-byte aligned. - var /*const*/ outputsPtr = dataPtr; - structPtr = outputsPtr; - dataPtr += numOutputs * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; + // of the C callback's output AudioSampleFrame. 'dataPtr' will now be + // aligned with the output views, ending at stacktop. + var /*const*/ outputsPtr = structPtr; for (entry of outputList) { // Write the AudioSampleFrame struct instance {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'entry.length', 'u32') }}}; From c7359d57c425193558943e4bcec7662140ca5b07 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 12 Aug 2025 18:16:59 +0200 Subject: [PATCH 09/22] Code size --- test/code_size/audio_worklet_wasm.js | 222 +++++++++--------- ...nimal_runtime_code_size_audio_worklet.json | 8 +- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/test/code_size/audio_worklet_wasm.js b/test/code_size/audio_worklet_wasm.js index 96048c30f62df..c44e6b8954d98 100644 --- a/test/code_size/audio_worklet_wasm.js +++ b/test/code_size/audio_worklet_wasm.js @@ -1,18 +1,18 @@ -var k = globalThis.Module || "undefined" != typeof Module ? Module : {}, p = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, t, v, J, K, F, D, A, X, E, C, B, Y, Z; +var l = globalThis.Module || "undefined" != typeof Module ? Module : {}, n = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, t, v, I, J, F, C, z, X, E, B, A, Y, Z; -q && (p = !0); +q && (n = !0); function u(a) { t = a; v = a.L; - x(); - k ||= {}; - k.wasm = a.H; - z(); + w(); + l ||= {}; + l.wasm = a.H; + y(); a.H = a.M = 0; } -p && !q && (onmessage = a => { +n && !q && (onmessage = a => { onmessage = null; u(a.data); }); @@ -23,64 +23,64 @@ if (q) { constructor(d) { super(); d = d.processorOptions; - this.v = A.get(d.v); + this.v = z.get(d.v); this.A = d.A; this.u = d.u; this.s = 4 * this.u; this.F = Math.min((t.D - 16) / this.s | 0, 16); - d = B(); - var f = C(this.F * this.s) >> 2; + d = A(); + var f = B(this.F * this.s) >> 2; this.G = []; - for (var g = this.F; 0 < g; g--) this.G.unshift(D.subarray(f, f += this.u)); + for (var g = this.F; 0 < g; g--) this.G.unshift(C.subarray(f, f += this.u)); E(d); } static get parameterDescriptors() { return c; } process(d, f, g) { - var n = d.length, w = f.length, h, r, y = 12 * (n + w); - for (h of d) y += h.length * this.s; + var p = d.length, x = f.length, k, r, h = 12 * (p + x), m = 0; + for (k of d) m += k.length * this.s; var G = 0; - for (h of f) G += h.length; - y += G * this.s; - var H = 0; - for (h in g) y += g[h].byteLength + 8, ++H; - var W = B(), O = y + 15 & -16, l = C(O), P = l, m = P; - l += 12 * n; - for (h of d) { - F[m >> 2] = h.length; - F[m + 4 >> 2] = this.u; - F[m + 8 >> 2] = l; - m += 12; - for (r of h) D.set(r, l >> 2), l += this.s; + for (k of f) G += k.length; + m += G * this.s; + var N = 0; + for (k in g) ++N, h += 8, m += g[k].byteLength; + var U = A(), D = h + m + 15 & -16; + h = B(D); + m = h + (D - m); + D = h; + for (k of d) { + F[h >> 2] = k.length; + F[h + 4 >> 2] = this.u; + F[h + 8 >> 2] = m; + h += 12; + for (r of k) C.set(r, m >> 2), m += this.s; } - m = d = l; - l += 8 * H; - for (h = 0; r = g[h++]; ) F[m >> 2] = r.length, F[m + 4 >> 2] = l, m += 8, D.set(r, l >> 2), - l += 4 * r.length; - m = g = l += O - y; - l += 12 * w; - for (h of f) F[m >> 2] = h.length, F[m + 4 >> 2] = this.u, F[m + 8 >> 2] = l, m += 12, - l += this.s * h.length; - if (n = this.v(n, P, w, g, H, d, this.A)) for (h of f) for (r of h) r.set(this.G[--G]); - E(W); - return !!n; + d = h; + for (k = 0; r = g[k++]; ) F[h >> 2] = r.length, F[h + 4 >> 2] = m, h += 8, C.set(r, m >> 2), + m += 4 * r.length; + g = h; + for (k of f) F[h >> 2] = k.length, F[h + 4 >> 2] = this.u, F[h + 8 >> 2] = m, h += 12, + m += this.s * k.length; + if (p = this.v(p, D, x, g, N, d, this.A)) for (k of f) for (r of k) r.set(this.G[--G]); + E(U); + return !!p; } } return e; } - var I; + var H; class b extends AudioWorkletProcessor { constructor(c) { super(); u(c.processorOptions); - I = this.port; - I.onmessage = async e => { + H = this.port; + H.onmessage = async e => { e = e.data; - e._wpn ? (registerProcessor(e._wpn, a(e.I)), I.postMessage({ + e._wpn ? (registerProcessor(e._wpn, a(e.I)), H.postMessage({ _wsc: e.v, B: [ e.J, 1, e.A ] - })) : e._wsc && A.get(e._wsc)(...e.B); + })) : e._wsc && z.get(e._wsc)(...e.B); }; } process() {} @@ -88,32 +88,32 @@ if (q) { registerProcessor("em-bootstrap", b); } -function x() { +function w() { var a = v.buffer; - J = new Uint8Array(a); - K = new Int32Array(a); + I = new Uint8Array(a); + J = new Int32Array(a); F = new Uint32Array(a); - D = new Float32Array(a); + C = new Float32Array(a); } -p || (v = k.mem || new WebAssembly.Memory({ +n || (v = l.mem || new WebAssembly.Memory({ initial: 256, maximum: 256, shared: !0 -}), x()); +}), w()); -var L = [], M = a => { +var K = [], L = a => { a = a.data; let b = a._wsc; - b && A.get(b)(...a.x); -}, N = a => { - L.push(a); -}, Q = a => E(a), R = () => B(), aa = (a, b, c, e) => { - b = S[b]; - S[a].connect(b.destination || b, c, e); -}, S = {}, T = 0, U = "undefined" != typeof TextDecoder ? new TextDecoder : void 0, V = (a = 0) => { - for (var b = J, c = a, e = c + void 0; b[c] && !(c >= e); ) ++c; - if (16 < c - a && b.buffer && U) return U.decode(b.slice(a, c)); + b && z.get(b)(...a.x); +}, M = a => { + K.push(a); +}, O = a => E(a), P = () => A(), R = (a, b, c, e) => { + b = Q[b]; + Q[a].connect(b.destination || b, c, e); +}, Q = {}, S = 0, T = "undefined" != typeof TextDecoder ? new TextDecoder : void 0, V = (a = 0) => { + for (var b = I, c = a, e = c + void 0; b[c] && !(c >= e); ) ++c; + if (16 < c - a && b.buffer && T) return T.decode(b.slice(a, c)); for (e = ""; a < c; ) { var d = b[a++]; if (d & 128) { @@ -126,7 +126,7 @@ var L = [], M = a => { } else e += String.fromCharCode(d); } return e; -}, ba = a => { +}, W = a => { var b = window.AudioContext || window.webkitAudioContext; if (a) { var c = F[a >> 2]; @@ -135,18 +135,18 @@ var L = [], M = a => { sampleRate: F[a + 4 >> 2] || void 0 }; } else a = void 0; - if (c = b) b = new b(a), S[++T] = b, c = T; + if (c = b) b = new b(a), Q[++S] = b, c = S; return c; -}, ca = (a, b, c, e, d) => { - var f = c ? K[c + 4 >> 2] : 0; +}, aa = (a, b, c, e, d) => { + var f = c ? J[c + 4 >> 2] : 0; if (c) { - var g = K[c >> 2]; + var g = J[c >> 2]; c = F[c + 8 >> 2]; - var n = f; + var p = f; if (c) { c >>= 2; - for (var w = []; n--; ) w.push(F[c++]); - c = w; + for (var x = []; p--; ) x.push(F[c++]); + c = x; } else c = void 0; e = { numberOfInputs: g, @@ -159,89 +159,89 @@ var L = [], M = a => { } }; } else e = void 0; - a = new AudioWorkletNode(S[a], b ? V(b) : "", e); - S[++T] = a; - return T; -}, da = (a, b, c, e) => { - var d = [], f = (f = F[b >> 2]) ? V(f) : "", g = K[b + 4 >> 2]; + a = new AudioWorkletNode(Q[a], b ? V(b) : "", e); + Q[++S] = a; + return S; +}, ba = (a, b, c, e) => { + var d = [], f = (f = F[b >> 2]) ? V(f) : "", g = J[b + 4 >> 2]; b = F[b + 8 >> 2]; - for (var n = 0; g--; ) d.push({ - name: n++, - defaultValue: D[b >> 2], - minValue: D[b + 4 >> 2], - maxValue: D[b + 8 >> 2], - automationRate: (K[b + 12 >> 2] ? "k" : "a") + "-rate" + for (var p = 0; g--; ) d.push({ + name: p++, + defaultValue: C[b >> 2], + minValue: C[b + 4 >> 2], + maxValue: C[b + 8 >> 2], + automationRate: (J[b + 12 >> 2] ? "k" : "a") + "-rate" }), b += 16; - S[a].audioWorklet.C.port.postMessage({ + Q[a].audioWorklet.C.port.postMessage({ _wpn: f, I: d, J: a, v: c, A: e }); -}, ea = () => !1, fa = 1, ha = a => { +}, ca = () => !1, da = 1, ea = a => { a = a.data; var b = a._wsc; - b && A.get(b)(...a.B); -}, ia = a => C(a), ja = (a, b, c, e, d) => { - var f = S[a], g = f.audioWorklet, n = () => { - A.get(e)(a, 0, d); + b && z.get(b)(...a.B); +}, fa = a => B(a), ha = (a, b, c, e, d) => { + var f = Q[a], g = f.audioWorklet, p = () => { + z.get(e)(a, 0, d); }; - if (!g) return n(); - g.addModule(k.js).then((() => { + if (!g) return p(); + g.addModule(l.js).then((() => { g.C = new AudioWorkletNode(f, "em-bootstrap", { processorOptions: { - N: fa++, - H: k.wasm, + N: da++, + H: l.wasm, L: v, K: b, D: c } }); - g.C.port.onmessage = ha; - A.get(e)(a, 1, d); - })).catch(n); + g.C.port.onmessage = ea; + z.get(e)(a, 1, d); + })).catch(p); }; -function ka(a) { +function ia(a) { let b = document.createElement("button"); b.innerHTML = "Toggle playback"; document.body.appendChild(b); - a = S[a]; + a = Q[a]; b.onclick = () => { "running" != a.state ? a.resume() : a.suspend(); }; } -function z() { +function y() { Z = { - f: ka, - g: aa, - d: ba, - h: ca, - e: da, - b: ea, - c: ja, + f: ia, + g: R, + d: W, + h: aa, + e: ba, + b: ca, + c: ha, a: v }; - WebAssembly.instantiate(k.wasm, { + WebAssembly.instantiate(l.wasm, { a: Z }).then((a => { a = (a.instance || a).exports; X = a.j; E = a.l; - C = a.m; - B = a.n; + B = a.m; + A = a.n; Y = a.o; - A = a.k; - k.stackSave = R; - k.stackAlloc = ia; - k.stackRestore = Q; - k.wasmTable = A; - p ? (Y(t.K, t.D), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", N), - L = L.forEach(M), addEventListener("message", M))) : a.i(); - p || X(); + z = a.k; + l.stackSave = P; + l.stackAlloc = fa; + l.stackRestore = O; + l.wasmTable = z; + n ? (Y(t.K, t.D), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", M), + K = K.forEach(L), addEventListener("message", L))) : a.i(); + n || X(); })); } -p || z(); \ No newline at end of file +n || y(); \ No newline at end of file diff --git a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json index ed6a08c3cd13e..7c676bdc48419 100644 --- a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json +++ b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json @@ -1,10 +1,10 @@ { "a.html": 519, "a.html.gz": 357, - "a.js": 4062, - "a.js.gz": 2128, + "a.js": 4041, + "a.js.gz": 2114, "a.wasm": 1288, "a.wasm.gz": 860, - "total": 5869, - "total_gz": 3345 + "total": 5848, + "total_gz": 3331 } From 2bb992b46efc3900e2d043b8b6f2de1def624d8b Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Wed, 13 Aug 2025 10:30:17 +0200 Subject: [PATCH 10/22] Increased the max buffer count This takes into account multiple multi-speaker outputs (the spec labels 18). --- src/audio_worklet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 6ef9e5ff67dda..6e3963bf6495b 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -36,13 +36,13 @@ function createWasmAudioWorkletProcessor(audioParams) { this.bytesPerChannel = this.samplesPerChannel * {{{ getNativeTypeSize('float') }}}; // Create up-front as many typed views for marshalling the output data as - // may be required (with an arbitrary maximum of 16, for the case where a + // may be required (with an arbitrary maximum of 64, for the case where a // multi-MB stack is passed), allocated at the *top* of the worklet's // stack (and whose addresses are fixed). The 'minimum alloc' firstly // stops STACK_OVERFLOW_CHECK failing (since the stack will be full, and // 16 being the minimum allocation size due to alignments) and leaves room // for a single AudioSampleFrame as a minumum. - this.maxBuffers = Math.min(((wwParams.stackSize - /*minimum alloc*/ 16) / this.bytesPerChannel) | 0, /*sensible limit*/ 16); + this.maxBuffers = Math.min(((wwParams.stackSize - /*minimum alloc*/ 16) / this.bytesPerChannel) | 0, /*sensible limit*/ 64); #if ASSERTIONS console.assert(this.maxBuffers > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`); #endif From 50a4ce649ae3db7a3302588cd625e55f98c2389b Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Wed, 13 Aug 2025 12:02:20 +0200 Subject: [PATCH 11/22] Code size changes --- test/code_size/audio_worklet_wasm.js | 2 +- .../test_minimal_runtime_code_size_audio_worklet.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/code_size/audio_worklet_wasm.js b/test/code_size/audio_worklet_wasm.js index c44e6b8954d98..49892f0e9e589 100644 --- a/test/code_size/audio_worklet_wasm.js +++ b/test/code_size/audio_worklet_wasm.js @@ -27,7 +27,7 @@ if (q) { this.A = d.A; this.u = d.u; this.s = 4 * this.u; - this.F = Math.min((t.D - 16) / this.s | 0, 16); + this.F = Math.min((t.D - 16) / this.s | 0, 64); d = A(); var f = B(this.F * this.s) >> 2; this.G = []; diff --git a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json index 7c676bdc48419..cb6676a35cf9d 100644 --- a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json +++ b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json @@ -2,9 +2,9 @@ "a.html": 519, "a.html.gz": 357, "a.js": 4041, - "a.js.gz": 2114, + "a.js.gz": 2115, "a.wasm": 1288, "a.wasm.gz": 860, "total": 5848, - "total_gz": 3331 + "total_gz": 3332 } From e6a834f97fe0d11a23026cf8d28eabfb5bdf0b44 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 14 Aug 2025 16:45:18 +0200 Subject: [PATCH 12/22] Recreate views as heap grows --- src/audio_worklet.js | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 6e3963bf6495b..9ac32f6c8e4ac 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -35,24 +35,37 @@ function createWasmAudioWorkletProcessor(audioParams) { this.samplesPerChannel = opts.samplesPerChannel; this.bytesPerChannel = this.samplesPerChannel * {{{ getNativeTypeSize('float') }}}; - // Create up-front as many typed views for marshalling the output data as - // may be required (with an arbitrary maximum of 64, for the case where a - // multi-MB stack is passed), allocated at the *top* of the worklet's - // stack (and whose addresses are fixed). The 'minimum alloc' firstly - // stops STACK_OVERFLOW_CHECK failing (since the stack will be full, and - // 16 being the minimum allocation size due to alignments) and leaves room - // for a single AudioSampleFrame as a minumum. + // Creates the output views (see createOutputViews() docs) this.maxBuffers = Math.min(((wwParams.stackSize - /*minimum alloc*/ 16) / this.bytesPerChannel) | 0, /*sensible limit*/ 64); + this.outputViews = []; #if ASSERTIONS console.assert(this.maxBuffers > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`); #endif + this.createOutputViews(); + +#if ASSERTIONS + // Explicitly verify this later in process() + this.ctorOldStackPtr = stackSave(); +#endif + } + + /** + * Create up-front as many typed views for marshalling the output data as + * may be required (with an arbitrary maximum of 64, for the case where a + * multi-MB stack is passed), allocated at the *top* of the worklet's stack + * (and whose addresses are fixed). The 'minimum alloc' firstly stops + * STACK_OVERFLOW_CHECK failing (since the stack will be full, and 16 bytes + * being the minimum allocation size due to alignments) and leaves room for + * a single AudioSampleFrame as a minumum. + */ + createOutputViews() { // These are still alloc'd to take advantage of the overflow checks, etc. var oldStackPtr = stackSave(); var viewDataIdx = {{{ getHeapOffset('stackAlloc(this.maxBuffers * this.bytesPerChannel)', 'float') }}}; #if WEBAUDIO_DEBUG console.log(`AudioWorklet creating ${this.maxBuffers} buffer one-time views (for a stack size of ${wwParams.stackSize} at address 0x${(viewDataIdx * 4).toString(16)})`); #endif - this.outputViews = []; + this.outputViews.length = 0; for (var n = this.maxBuffers; n > 0; n--) { // Added in reverse so the lowest indices are closest to the stack top this.outputViews.unshift( @@ -60,11 +73,6 @@ function createWasmAudioWorkletProcessor(audioParams) { ); } stackRestore(oldStackPtr); - -#if ASSERTIONS - // Explicitly verify this later in process() - this.ctorOldStackPtr = oldStackPtr; -#endif } static get parameterDescriptors() { @@ -79,6 +87,9 @@ function createWasmAudioWorkletProcessor(audioParams) { * @param {Object} parameters */ process(inputList, outputList, parameters) { + if (HEAPF32.buffer != this.outputViews[0].buffer) { + this.createOutputViews(); + } var numInputs = inputList.length; var numOutputs = outputList.length; From f3bf6b2f5f82c17c2958eda0a13dced017eab9d0 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 14 Aug 2025 17:20:30 +0200 Subject: [PATCH 13/22] Use ptrToString for address --- src/audio_worklet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 9ac32f6c8e4ac..9c13e8db75de4 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -63,7 +63,7 @@ function createWasmAudioWorkletProcessor(audioParams) { var oldStackPtr = stackSave(); var viewDataIdx = {{{ getHeapOffset('stackAlloc(this.maxBuffers * this.bytesPerChannel)', 'float') }}}; #if WEBAUDIO_DEBUG - console.log(`AudioWorklet creating ${this.maxBuffers} buffer one-time views (for a stack size of ${wwParams.stackSize} at address 0x${(viewDataIdx * 4).toString(16)})`); + console.log(`AudioWorklet creating ${this.maxBuffers} buffer one-time views (for a stack size of ${wwParams.stackSize} at address ${ptrToString(viewDataIdx * 4)})`); #endif this.outputViews.length = 0; for (var n = this.maxBuffers; n > 0; n--) { From 51866864c28463ef1a32eca6aed66f4d5ddf63ab Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 14 Aug 2025 17:20:39 +0200 Subject: [PATCH 14/22] Code size --- test/code_size/audio_worklet_wasm.js | 64 ++++++++++--------- ...nimal_runtime_code_size_audio_worklet.json | 8 +-- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/test/code_size/audio_worklet_wasm.js b/test/code_size/audio_worklet_wasm.js index 49892f0e9e589..4c7c8b2a6f793 100644 --- a/test/code_size/audio_worklet_wasm.js +++ b/test/code_size/audio_worklet_wasm.js @@ -1,15 +1,15 @@ -var l = globalThis.Module || "undefined" != typeof Module ? Module : {}, n = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, t, v, I, J, F, C, z, X, E, B, A, Y, Z; +var l = globalThis.Module || "undefined" != typeof Module ? Module : {}, n = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, r, v, I, J, F, C, z, X, E, B, A, Y, Z; q && (n = !0); function u(a) { - t = a; - v = a.L; + r = a; + v = a.M; w(); l ||= {}; - l.wasm = a.H; + l.wasm = a.I; y(); - a.H = a.M = 0; + a.I = a.N = 0; } n && !q && (onmessage = a => { @@ -27,18 +27,22 @@ if (q) { this.A = d.A; this.u = d.u; this.s = 4 * this.u; - this.F = Math.min((t.D - 16) / this.s | 0, 64); - d = A(); - var f = B(this.F * this.s) >> 2; - this.G = []; - for (var g = this.F; 0 < g; g--) this.G.unshift(C.subarray(f, f += this.u)); + this.C = Math.min((r.G - 16) / this.s | 0, 64); + this.B = []; + this.H(); + } + H() { + var d = A(), f = B(this.C * this.s) >> 2; + this.B.length = 0; + for (var g = this.C; 0 < g; g--) this.B.unshift(C.subarray(f, f += this.u)); E(d); } static get parameterDescriptors() { return c; } process(d, f, g) { - var p = d.length, x = f.length, k, r, h = 12 * (p + x), m = 0; + C.buffer != this.B[0].buffer && this.H(); + var p = d.length, x = f.length, k, t, h = 12 * (p + x), m = 0; for (k of d) m += k.length * this.s; var G = 0; for (k of f) G += k.length; @@ -54,15 +58,15 @@ if (q) { F[h + 4 >> 2] = this.u; F[h + 8 >> 2] = m; h += 12; - for (r of k) C.set(r, m >> 2), m += this.s; + for (t of k) C.set(t, m >> 2), m += this.s; } d = h; - for (k = 0; r = g[k++]; ) F[h >> 2] = r.length, F[h + 4 >> 2] = m, h += 8, C.set(r, m >> 2), - m += 4 * r.length; + for (k = 0; t = g[k++]; ) F[h >> 2] = t.length, F[h + 4 >> 2] = m, h += 8, C.set(t, m >> 2), + m += 4 * t.length; g = h; for (k of f) F[h >> 2] = k.length, F[h + 4 >> 2] = this.u, F[h + 8 >> 2] = m, h += 12, m += this.s * k.length; - if (p = this.v(p, D, x, g, N, d, this.A)) for (k of f) for (r of k) r.set(this.G[--G]); + if (p = this.v(p, D, x, g, N, d, this.A)) for (k of f) for (t of k) t.set(this.B[--G]); E(U); return !!p; } @@ -77,10 +81,10 @@ if (q) { H = this.port; H.onmessage = async e => { e = e.data; - e._wpn ? (registerProcessor(e._wpn, a(e.I)), H.postMessage({ + e._wpn ? (registerProcessor(e._wpn, a(e.J)), H.postMessage({ _wsc: e.v, - B: [ e.J, 1, e.A ] - })) : e._wsc && z.get(e._wsc)(...e.B); + D: [ e.K, 1, e.A ] + })) : e._wsc && z.get(e._wsc)(...e.D); }; } process() {} @@ -172,33 +176,33 @@ var K = [], L = a => { maxValue: C[b + 8 >> 2], automationRate: (J[b + 12 >> 2] ? "k" : "a") + "-rate" }), b += 16; - Q[a].audioWorklet.C.port.postMessage({ + Q[a].audioWorklet.F.port.postMessage({ _wpn: f, - I: d, - J: a, + J: d, + K: a, v: c, A: e }); }, ca = () => !1, da = 1, ea = a => { a = a.data; var b = a._wsc; - b && z.get(b)(...a.B); + b && z.get(b)(...a.D); }, fa = a => B(a), ha = (a, b, c, e, d) => { var f = Q[a], g = f.audioWorklet, p = () => { z.get(e)(a, 0, d); }; if (!g) return p(); g.addModule(l.js).then((() => { - g.C = new AudioWorkletNode(f, "em-bootstrap", { + g.F = new AudioWorkletNode(f, "em-bootstrap", { processorOptions: { - N: da++, - H: l.wasm, - L: v, - K: b, - D: c + O: da++, + I: l.wasm, + M: v, + L: b, + G: c } }); - g.C.port.onmessage = ea; + g.F.port.onmessage = ea; z.get(e)(a, 1, d); })).catch(p); }; @@ -238,7 +242,7 @@ function y() { l.stackAlloc = fa; l.stackRestore = O; l.wasmTable = z; - n ? (Y(t.K, t.D), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", M), + n ? (Y(r.L, r.G), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", M), K = K.forEach(L), addEventListener("message", L))) : a.i(); n || X(); })); diff --git a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json index cb6676a35cf9d..111ff68b618b5 100644 --- a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json +++ b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json @@ -1,10 +1,10 @@ { "a.html": 519, "a.html.gz": 357, - "a.js": 4041, - "a.js.gz": 2115, + "a.js": 4107, + "a.js.gz": 2137, "a.wasm": 1288, "a.wasm.gz": 860, - "total": 5848, - "total_gz": 3332 + "total": 5914, + "total_gz": 3354 } From 0cc964702e79934d75b01dd6303710748616c412 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 15 Aug 2025 12:47:21 +0200 Subject: [PATCH 15/22] Don't test for heap changes if memory growth is not enabled --- src/audio_worklet.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 9c13e8db75de4..169082efe6c20 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -87,9 +87,13 @@ function createWasmAudioWorkletProcessor(audioParams) { * @param {Object} parameters */ process(inputList, outputList, parameters) { +#if ALLOW_MEMORY_GROWTH + // recreate the output views if the heap has changed if (HEAPF32.buffer != this.outputViews[0].buffer) { this.createOutputViews(); } +#endif + var numInputs = inputList.length; var numOutputs = outputList.length; @@ -205,7 +209,8 @@ function createWasmAudioWorkletProcessor(audioParams) { if (didProduceAudio) { // Read back the produced audio data to all outputs and their channels. // The preallocated 'outputViews' already have the correct offsets and - // sizes into the stack (recall from the ctor that they run backwards). + // sizes into the stack (recall from createOutputViews() that they run + // backwards). for (entry of outputList) { for (subentry of entry) { subentry.set(this.outputViews[--outputViewsNeeded]); From 2c550c32c7a8341e7b9464de2e71c4a1cd8d4ce8 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 15 Aug 2025 12:47:51 +0200 Subject: [PATCH 16/22] Code size --- test/code_size/audio_worklet_wasm.js | 47 +++++++++---------- ...nimal_runtime_code_size_audio_worklet.json | 8 ++-- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/test/code_size/audio_worklet_wasm.js b/test/code_size/audio_worklet_wasm.js index 4c7c8b2a6f793..05b6a50ea5605 100644 --- a/test/code_size/audio_worklet_wasm.js +++ b/test/code_size/audio_worklet_wasm.js @@ -1,15 +1,15 @@ -var l = globalThis.Module || "undefined" != typeof Module ? Module : {}, n = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, r, v, I, J, F, C, z, X, E, B, A, Y, Z; +var l = globalThis.Module || "undefined" != typeof Module ? Module : {}, n = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, t, v, I, J, F, C, z, X, E, B, A, Y, Z; q && (n = !0); function u(a) { - r = a; + t = a; v = a.M; w(); l ||= {}; - l.wasm = a.I; + l.wasm = a.H; y(); - a.I = a.N = 0; + a.H = a.N = 0; } n && !q && (onmessage = a => { @@ -27,22 +27,21 @@ if (q) { this.A = d.A; this.u = d.u; this.s = 4 * this.u; - this.C = Math.min((r.G - 16) / this.s | 0, 64); + this.G = Math.min((t.F - 16) / this.s | 0, 64); this.B = []; - this.H(); + this.I(); } - H() { - var d = A(), f = B(this.C * this.s) >> 2; + I() { + var d = A(), f = B(this.G * this.s) >> 2; this.B.length = 0; - for (var g = this.C; 0 < g; g--) this.B.unshift(C.subarray(f, f += this.u)); + for (var g = this.G; 0 < g; g--) this.B.unshift(C.subarray(f, f += this.u)); E(d); } static get parameterDescriptors() { return c; } process(d, f, g) { - C.buffer != this.B[0].buffer && this.H(); - var p = d.length, x = f.length, k, t, h = 12 * (p + x), m = 0; + var p = d.length, x = f.length, k, r, h = 12 * (p + x), m = 0; for (k of d) m += k.length * this.s; var G = 0; for (k of f) G += k.length; @@ -58,15 +57,15 @@ if (q) { F[h + 4 >> 2] = this.u; F[h + 8 >> 2] = m; h += 12; - for (t of k) C.set(t, m >> 2), m += this.s; + for (r of k) C.set(r, m >> 2), m += this.s; } d = h; - for (k = 0; t = g[k++]; ) F[h >> 2] = t.length, F[h + 4 >> 2] = m, h += 8, C.set(t, m >> 2), - m += 4 * t.length; + for (k = 0; r = g[k++]; ) F[h >> 2] = r.length, F[h + 4 >> 2] = m, h += 8, C.set(r, m >> 2), + m += 4 * r.length; g = h; for (k of f) F[h >> 2] = k.length, F[h + 4 >> 2] = this.u, F[h + 8 >> 2] = m, h += 12, m += this.s * k.length; - if (p = this.v(p, D, x, g, N, d, this.A)) for (k of f) for (t of k) t.set(this.B[--G]); + if (p = this.v(p, D, x, g, N, d, this.A)) for (k of f) for (r of k) r.set(this.B[--G]); E(U); return !!p; } @@ -83,8 +82,8 @@ if (q) { e = e.data; e._wpn ? (registerProcessor(e._wpn, a(e.J)), H.postMessage({ _wsc: e.v, - D: [ e.K, 1, e.A ] - })) : e._wsc && z.get(e._wsc)(...e.D); + C: [ e.K, 1, e.A ] + })) : e._wsc && z.get(e._wsc)(...e.C); }; } process() {} @@ -176,7 +175,7 @@ var K = [], L = a => { maxValue: C[b + 8 >> 2], automationRate: (J[b + 12 >> 2] ? "k" : "a") + "-rate" }), b += 16; - Q[a].audioWorklet.F.port.postMessage({ + Q[a].audioWorklet.D.port.postMessage({ _wpn: f, J: d, K: a, @@ -186,23 +185,23 @@ var K = [], L = a => { }, ca = () => !1, da = 1, ea = a => { a = a.data; var b = a._wsc; - b && z.get(b)(...a.D); + b && z.get(b)(...a.C); }, fa = a => B(a), ha = (a, b, c, e, d) => { var f = Q[a], g = f.audioWorklet, p = () => { z.get(e)(a, 0, d); }; if (!g) return p(); g.addModule(l.js).then((() => { - g.F = new AudioWorkletNode(f, "em-bootstrap", { + g.D = new AudioWorkletNode(f, "em-bootstrap", { processorOptions: { O: da++, - I: l.wasm, + H: l.wasm, M: v, L: b, - G: c + F: c } }); - g.F.port.onmessage = ea; + g.D.port.onmessage = ea; z.get(e)(a, 1, d); })).catch(p); }; @@ -242,7 +241,7 @@ function y() { l.stackAlloc = fa; l.stackRestore = O; l.wasmTable = z; - n ? (Y(r.L, r.G), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", M), + n ? (Y(t.L, t.F), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", M), K = K.forEach(L), addEventListener("message", L))) : a.i(); n || X(); })); diff --git a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json index 111ff68b618b5..b0a19282cec6f 100644 --- a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json +++ b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json @@ -1,10 +1,10 @@ { "a.html": 519, "a.html.gz": 357, - "a.js": 4107, - "a.js.gz": 2137, + "a.js": 4070, + "a.js.gz": 2124, "a.wasm": 1288, "a.wasm.gz": 860, - "total": 5914, - "total_gz": 3354 + "total": 5877, + "total_gz": 3341 } From 56d0662a21fa4178a8d9a18da17e6a9aa7f6225e Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Wed, 20 Aug 2025 03:53:32 +0200 Subject: [PATCH 17/22] Code size --- test/code_size/audio_worklet_wasm.js | 233 +++++++++--------- ...nimal_runtime_code_size_audio_worklet.json | 8 +- 2 files changed, 124 insertions(+), 117 deletions(-) diff --git a/test/code_size/audio_worklet_wasm.js b/test/code_size/audio_worklet_wasm.js index 75e5c3aa64c15..7e7b6e491c75d 100644 --- a/test/code_size/audio_worklet_wasm.js +++ b/test/code_size/audio_worklet_wasm.js @@ -1,5 +1,4 @@ - -var l = globalThis.Module || "undefined" != typeof Module ? Module : {}, n = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, t, A, v, K, L, F, H, B, X, I, E, C, Y, Z; +var l = globalThis.Module || "undefined" != typeof Module ? Module : {}, n = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, t, z, v, J, K, H, E, A, X, F, C, B, Y, Z; q && (n = !0); @@ -24,60 +23,68 @@ if (q) { constructor(d) { super(); d = d.processorOptions; - this.u = B.get(d.u); - this.v = d.v; - this.s = d.s; + this.v = A.get(d.v); + this.A = d.A; + this.u = d.u; + this.s = 4 * this.u; + this.G = Math.min((t.F - 16) / this.s | 0, 64); + this.B = []; + this.I(); + } + I() { + var d = B(), f = C(this.G * this.s) >> 2; + this.B.length = 0; + for (var g = this.G; 0 < g; g--) this.B.unshift(E.subarray(f, f += this.u)); + F(d); } static get parameterDescriptors() { return c; } - process(d, g, h) { - var p = d.length, w = g.length, k, r, x = 4 * this.s, f = 12 * (p + w), D = 0; - for (k of d) f += k.length * x; - for (k of g) f += k.length * x; - for (k in h) f += h[k].byteLength + 8, ++D; - var W = C(), G = E(f); - f = G; - var m = G + 12 * p; + process(d, f, g) { + var p = d.length, x = f.length, k, r, h = 12 * (p + x), m = 0; + for (k of d) m += k.length * this.s; + var G = 0; + for (k of f) G += k.length; + m += G * this.s; + var O = 0; + for (k in g) ++O, h += 8, m += g[k].byteLength; + var V = B(), D = h + m + 15 & -16; + h = C(D); + m = h + (D - m); + D = h; for (k of d) { - F[f >> 2] = k.length; - F[f + 4 >> 2] = this.s; - F[f + 8 >> 2] = m; - f += 12; - for (r of k) H.set(r, m >> 2), m += x; + H[h >> 2] = k.length; + H[h + 4 >> 2] = this.u; + H[h + 8 >> 2] = m; + h += 12; + for (r of k) E.set(r, m >> 2), m += this.s; } - var P = m; - f = P; - d = m += 12 * w; - for (k of g) F[f >> 2] = k.length, F[f + 4 >> 2] = this.s, F[f + 8 >> 2] = m, f += 12, - m += x * k.length; - f = x = m; - m += 8 * D; - for (k = 0; r = h[k++]; ) F[f >> 2] = r.length, F[f + 4 >> 2] = m, f += 8, H.set(r, m >> 2), + d = h; + for (k = 0; r = g[k++]; ) H[h >> 2] = r.length, H[h + 4 >> 2] = m, h += 8, E.set(r, m >> 2), m += 4 * r.length; - if (h = this.u(p, G, w, P, D, x, this.v)) { - d >>= 2; - for (k of g) for (r of k) for (f = 0; f < this.s; ++f) r[f] = H[d++]; - } - I(W); - return !!h; + g = h; + for (k of f) H[h >> 2] = k.length, H[h + 4 >> 2] = this.u, H[h + 8 >> 2] = m, h += 12, + m += this.s * k.length; + if (p = this.v(p, D, x, g, O, d, this.A)) for (k of f) for (r of k) r.set(this.B[--G]); + F(V); + return !!p; } } return e; } - var J; + var I; class b extends AudioWorkletProcessor { constructor(c) { super(); u(c.processorOptions); - J = this.port; - J.onmessage = async e => { - await A; + I = this.port; + I.onmessage = async e => { + await z; e = e.data; - e._wpn ? (registerProcessor(e._wpn, a(e.D)), J.postMessage({ - _wsc: e.u, - A: [ e.F, 1, e.v ] - })) : e._wsc && B.get(e._wsc)(...e.A); + e._wpn ? (registerProcessor(e._wpn, a(e.J)), I.postMessage({ + _wsc: e.v, + C: [ e.K, 1, e.A ] + })) : e._wsc && A.get(e._wsc)(...e.C); }; } process() {} @@ -87,10 +94,10 @@ if (q) { function w() { var a = v.buffer; - K = new Uint8Array(a); - L = new Int32Array(a); - F = new Uint32Array(a); - H = new Float32Array(a); + J = new Uint8Array(a); + K = new Int32Array(a); + H = new Uint32Array(a); + E = new Float32Array(a); } n || (v = l.mem || new WebAssembly.Memory({ @@ -99,17 +106,17 @@ n || (v = l.mem || new WebAssembly.Memory({ shared: !0 }), w()); -var M = [], N = a => { +var L = [], M = a => { a = a.data; let b = a._wsc; - b && B.get(b)(...a.x); -}, O = a => { - M.push(a); -}, Q = a => I(a), R = () => C(), aa = (a, b, c, e) => { - b = S[b]; - S[a].connect(b.destination || b, c, e); -}, S = {}, T = 0, U = "undefined" != typeof TextDecoder ? new TextDecoder : void 0, V = (a = 0) => { - for (var b = K, c = a, e = c + void 0; b[c] && !(c >= e); ) ++c; + b && A.get(b)(...a.x); +}, N = a => { + L.push(a); +}, P = a => F(a), Q = () => B(), S = (a, b, c, e) => { + b = R[b]; + R[a].connect(b.destination || b, c, e); +}, R = {}, T = 0, U = "undefined" != typeof TextDecoder ? new TextDecoder : void 0, W = (a = 0) => { + for (var b = J, c = a, e = c + void 0; b[c] && !(c >= e); ) ++c; if (16 < c - a && b.buffer && U) return U.decode(b.slice(a, c)); for (e = ""; a < c; ) { var d = b[a++]; @@ -123,27 +130,27 @@ var M = [], N = a => { } else e += String.fromCharCode(d); } return e; -}, ba = a => { +}, aa = a => { var b = window.AudioContext || window.webkitAudioContext; if (a) { - var c = F[a >> 2]; + var c = H[a >> 2]; a = { - latencyHint: (c ? V(c) : "") || void 0, - sampleRate: F[a + 4 >> 2] || void 0 + latencyHint: (c ? W(c) : "") || void 0, + sampleRate: H[a + 4 >> 2] || void 0 }; } else a = void 0; - if (c = b) b = new b(a), S[++T] = b, c = T; + if (c = b) b = new b(a), R[++T] = b, c = T; return c; -}, ca = (a, b, c, e, d) => { - var g = c ? L[c + 4 >> 2] : 0; +}, ba = (a, b, c, e, d) => { + var f = c ? K[c + 4 >> 2] : 0; if (c) { - var h = L[c >> 2]; - c = F[c + 8 >> 2]; - var p = g; + var g = K[c >> 2]; + c = H[c + 8 >> 2]; + var p = f; if (c) { c >>= 2; - for (var w = []; p--; ) w.push(F[c++]); - c = w; + for (var x = []; p--; ) x.push(H[c++]); + c = x; } else c = void 0; e = { numberOfInputs: g, @@ -156,55 +163,55 @@ var M = [], N = a => { } }; } else e = void 0; - a = new AudioWorkletNode(S[a], b ? V(b) : "", e); - S[++T] = a; + a = new AudioWorkletNode(R[a], b ? W(b) : "", e); + R[++T] = a; return T; -}, da = (a, b, c, e) => { - var d = [], g = (g = F[b >> 2]) ? V(g) : "", h = L[b + 4 >> 2]; - b = F[b + 8 >> 2]; - for (var p = 0; h--; ) d.push({ +}, ca = (a, b, c, e) => { + var d = [], f = (f = H[b >> 2]) ? W(f) : "", g = K[b + 4 >> 2]; + b = H[b + 8 >> 2]; + for (var p = 0; g--; ) d.push({ name: p++, - defaultValue: H[b >> 2], - minValue: H[b + 4 >> 2], - maxValue: H[b + 8 >> 2], - automationRate: (L[b + 12 >> 2] ? "k" : "a") + "-rate" + defaultValue: E[b >> 2], + minValue: E[b + 4 >> 2], + maxValue: E[b + 8 >> 2], + automationRate: (K[b + 12 >> 2] ? "k" : "a") + "-rate" }), b += 16; - S[a].audioWorklet.B.port.postMessage({ - _wpn: g, - D: d, - F: a, - u: c, - v: e + R[a].audioWorklet.D.port.postMessage({ + _wpn: f, + J: d, + K: a, + v: c, + A: e }); -}, ea = () => !1, fa = 1, ha = a => { +}, da = () => !1, ea = 1, fa = a => { a = a.data; var b = a._wsc; - b && B.get(b)(...a.A); -}, ia = a => E(a), ja = (a, b, c, e, d) => { - var g = S[a], h = g.audioWorklet, p = () => { - B.get(e)(a, 0, d); + b && A.get(b)(...a.C); +}, ha = a => C(a), ia = (a, b, c, e, d) => { + var f = R[a], g = f.audioWorklet, p = () => { + A.get(e)(a, 0, d); }; if (!g) return p(); g.addModule(l.js).then((() => { g.D = new AudioWorkletNode(f, "em-bootstrap", { processorOptions: { - K: fa++, - C: l.wasm, - I: v, - G: b, - H: c + O: ea++, + H: l.wasm, + M: v, + L: b, + F: c } }); - h.B.port.onmessage = ha; - B.get(e)(a, 1, d); + g.D.port.onmessage = fa; + A.get(e)(a, 1, d); })).catch(p); }; -function ka(a) { +function ja(a) { let b = document.createElement("button"); b.innerHTML = "Toggle playback"; document.body.appendChild(b); - a = S[a]; + a = R[a]; b.onclick = () => { "running" != a.state ? a.resume() : a.suspend(); }; @@ -212,31 +219,31 @@ function ka(a) { function y() { Z = { - f: ka, - g: aa, - d: ba, - h: ca, - e: da, - b: ea, - c: ja, + f: ja, + g: S, + d: aa, + h: ba, + e: ca, + b: da, + c: ia, a: v }; - A = WebAssembly.instantiate(l.wasm, { + z = WebAssembly.instantiate(l.wasm, { a: Z }).then((a => { a = (a.instance || a).exports; X = a.j; - I = a.l; - E = a.m; - C = a.n; + F = a.l; + C = a.m; + B = a.n; Y = a.o; - B = a.k; - l.stackSave = R; - l.stackAlloc = ia; - l.stackRestore = Q; - l.wasmTable = B; - n ? (Y(t.G, t.H), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", O), - M = M.forEach(N), addEventListener("message", N))) : a.i(); + A = a.k; + l.stackSave = Q; + l.stackAlloc = ha; + l.stackRestore = P; + l.wasmTable = A; + n ? (Y(t.L, t.F), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", N), + L = L.forEach(M), addEventListener("message", M))) : a.i(); n || X(); })); } diff --git a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json index 396f6f055b6f9..3130cc6363ed8 100644 --- a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json +++ b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json @@ -1,10 +1,10 @@ { "a.html": 519, "a.html.gz": 357, - "a.js": 3896, - "a.js.gz": 2049, + "a.js": 4084, + "a.js.gz": 2134, "a.wasm": 1288, "a.wasm.gz": 860, - "total": 5703, - "total_gz": 3266 + "total": 5891, + "total_gz": 3351 } From 2288d8833fdb11cc8a7139af8e6034f225c0af00 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 21 Aug 2025 17:19:46 +0200 Subject: [PATCH 18/22] Simplify output view array size tracking --- src/audio_worklet.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 8e323401712a3..220c5e675ff70 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -40,15 +40,15 @@ function createWasmAudioWorkletProcessor(audioParams) { this.bytesPerChannel = this.samplesPerChannel * {{{ getNativeTypeSize('float') }}}; // Creates the output views (see createOutputViews() docs) - this.maxBuffers = Math.min(((wwParams.stackSize - /*minimum alloc*/ 16) / this.bytesPerChannel) | 0, /*sensible limit*/ 64); - this.outputViews = []; + this.outputViews = new Array(Math.min(((wwParams.stackSize - /*minimum alloc*/ 16) / this.bytesPerChannel) | 0, /*sensible limit*/ 64)); #if ASSERTIONS - console.assert(this.maxBuffers > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`); + console.assert(this.outputViews.length > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`); #endif this.createOutputViews(); #if ASSERTIONS - // Explicitly verify this later in process() + // Explicitly verify this later in process(). Note to self, stackSave is a + // bit of a misnomer as it simply gets the stack address. this.ctorOldStackPtr = stackSave(); #endif } @@ -65,16 +65,13 @@ function createWasmAudioWorkletProcessor(audioParams) { createOutputViews() { // These are still alloc'd to take advantage of the overflow checks, etc. var oldStackPtr = stackSave(); - var viewDataIdx = {{{ getHeapOffset('stackAlloc(this.maxBuffers * this.bytesPerChannel)', 'float') }}}; + var viewDataIdx = {{{ getHeapOffset('stackAlloc(this.outputViews.length * this.bytesPerChannel)', 'float') }}}; #if WEBAUDIO_DEBUG - console.log(`AudioWorklet creating ${this.maxBuffers} buffer one-time views (for a stack size of ${wwParams.stackSize} at address ${ptrToString(viewDataIdx * 4)})`); + console.log(`AudioWorklet creating ${this.outputViews.length} buffer one-time views (for a stack size of ${wwParams.stackSize} at address ${ptrToString(viewDataIdx * 4)})`); #endif - this.outputViews.length = 0; - for (var n = this.maxBuffers; n > 0; n--) { - // Added in reverse so the lowest indices are closest to the stack top - this.outputViews.unshift( - HEAPF32.subarray(viewDataIdx, viewDataIdx += this.samplesPerChannel) - ); + // Inserted in reverse so the lowest indices are closest to the stack top + for (var n = this.outputViews.length - 1; n >= 0; n--) { + this.outputViews[n] = HEAPF32.subarray(viewDataIdx, viewDataIdx += this.samplesPerChannel); } stackRestore(oldStackPtr); } From c42bc56b9e6eef46561338a000f71745315c6d4a Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 21 Aug 2025 17:40:17 +0200 Subject: [PATCH 19/22] Micro-opt to calculate the stack bytes once --- src/audio_worklet.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 220c5e675ff70..692271e3d3156 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -106,8 +106,9 @@ function createWasmAudioWorkletProcessor(audioParams) { var stackMemoryStruct = (numInputs + numOutputs) * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; var stackMemoryData = 0; for (entry of inputList) { - stackMemoryData += entry.length * this.bytesPerChannel; + stackMemoryData += entry.length; } + stackMemoryData *= this.bytesPerChannel; // Collect the total number of output channels (mapped to array views) var outputViewsNeeded = 0; for (entry of outputList) { From b931338d90a11e4ce58a31d0eba58cd9655fda79 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 21 Aug 2025 17:41:15 +0200 Subject: [PATCH 20/22] Code size --- test/code_size/audio_worklet_wasm.js | 78 +++++++++---------- ...nimal_runtime_code_size_audio_worklet.json | 8 +- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/test/code_size/audio_worklet_wasm.js b/test/code_size/audio_worklet_wasm.js index 7e7b6e491c75d..f150ab4f8115e 100644 --- a/test/code_size/audio_worklet_wasm.js +++ b/test/code_size/audio_worklet_wasm.js @@ -1,15 +1,15 @@ -var l = globalThis.Module || "undefined" != typeof Module ? Module : {}, n = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, t, z, v, J, K, H, E, A, X, F, C, B, Y, Z; +var m = globalThis.Module || "undefined" != typeof Module ? Module : {}, n = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, t, z, v, J, K, H, E, A, X, F, C, B, Y, Z; q && (n = !0); function u(a) { t = a; - v = a.M; + v = a.L; w(); - l ||= {}; - l.wasm = a.H; + m ||= {}; + m.wasm = a.G; y(); - a.H = a.N = 0; + a.G = a.M = 0; } n && !q && (onmessage = a => { @@ -27,44 +27,42 @@ if (q) { this.A = d.A; this.u = d.u; this.s = 4 * this.u; - this.G = Math.min((t.F - 16) / this.s | 0, 64); - this.B = []; - this.I(); + this.B = Array(Math.min((t.F - 16) / this.s | 0, 64)); + this.K(); } - I() { - var d = B(), f = C(this.G * this.s) >> 2; - this.B.length = 0; - for (var g = this.G; 0 < g; g--) this.B.unshift(E.subarray(f, f += this.u)); + K() { + for (var d = B(), f = C(this.B.length * this.s) >> 2, g = this.B.length - 1; 0 <= g; g--) this.B[g] = E.subarray(f, f += this.u); F(d); } static get parameterDescriptors() { return c; } process(d, f, g) { - var p = d.length, x = f.length, k, r, h = 12 * (p + x), m = 0; - for (k of d) m += k.length * this.s; + var p = d.length, x = f.length, k, r, h = 12 * (p + x), l = 0; + for (k of d) l += k.length; + l *= this.s; var G = 0; for (k of f) G += k.length; - m += G * this.s; + l += G * this.s; var O = 0; - for (k in g) ++O, h += 8, m += g[k].byteLength; - var V = B(), D = h + m + 15 & -16; + for (k in g) ++O, h += 8, l += g[k].byteLength; + var V = B(), D = h + l + 15 & -16; h = C(D); - m = h + (D - m); + l = h + (D - l); D = h; for (k of d) { H[h >> 2] = k.length; H[h + 4 >> 2] = this.u; - H[h + 8 >> 2] = m; + H[h + 8 >> 2] = l; h += 12; - for (r of k) E.set(r, m >> 2), m += this.s; + for (r of k) E.set(r, l >> 2), l += this.s; } d = h; - for (k = 0; r = g[k++]; ) H[h >> 2] = r.length, H[h + 4 >> 2] = m, h += 8, E.set(r, m >> 2), - m += 4 * r.length; + for (k = 0; r = g[k++]; ) H[h >> 2] = r.length, H[h + 4 >> 2] = l, h += 8, E.set(r, l >> 2), + l += 4 * r.length; g = h; - for (k of f) H[h >> 2] = k.length, H[h + 4 >> 2] = this.u, H[h + 8 >> 2] = m, h += 12, - m += this.s * k.length; + for (k of f) H[h >> 2] = k.length, H[h + 4 >> 2] = this.u, H[h + 8 >> 2] = l, h += 12, + l += this.s * k.length; if (p = this.v(p, D, x, g, O, d, this.A)) for (k of f) for (r of k) r.set(this.B[--G]); F(V); return !!p; @@ -81,9 +79,9 @@ if (q) { I.onmessage = async e => { await z; e = e.data; - e._wpn ? (registerProcessor(e._wpn, a(e.J)), I.postMessage({ + e._wpn ? (registerProcessor(e._wpn, a(e.H)), I.postMessage({ _wsc: e.v, - C: [ e.K, 1, e.A ] + C: [ e.I, 1, e.A ] })) : e._wsc && A.get(e._wsc)(...e.C); }; } @@ -100,7 +98,7 @@ function w() { E = new Float32Array(a); } -n || (v = l.mem || new WebAssembly.Memory({ +n || (v = m.mem || new WebAssembly.Memory({ initial: 256, maximum: 256, shared: !0 @@ -178,8 +176,8 @@ var L = [], M = a => { }), b += 16; R[a].audioWorklet.D.port.postMessage({ _wpn: f, - J: d, - K: a, + H: d, + I: a, v: c, A: e }); @@ -192,13 +190,13 @@ var L = [], M = a => { A.get(e)(a, 0, d); }; if (!g) return p(); - g.addModule(l.js).then((() => { + g.addModule(m.js).then((() => { g.D = new AudioWorkletNode(f, "em-bootstrap", { processorOptions: { - O: ea++, - H: l.wasm, - M: v, - L: b, + N: ea++, + G: m.wasm, + L: v, + J: b, F: c } }); @@ -228,7 +226,7 @@ function y() { c: ia, a: v }; - z = WebAssembly.instantiate(l.wasm, { + z = WebAssembly.instantiate(m.wasm, { a: Z }).then((a => { a = (a.instance || a).exports; @@ -238,11 +236,11 @@ function y() { B = a.n; Y = a.o; A = a.k; - l.stackSave = Q; - l.stackAlloc = ha; - l.stackRestore = P; - l.wasmTable = A; - n ? (Y(t.L, t.F), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", N), + m.stackSave = Q; + m.stackAlloc = ha; + m.stackRestore = P; + m.wasmTable = A; + n ? (Y(t.J, t.F), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", N), L = L.forEach(M), addEventListener("message", M))) : a.i(); n || X(); })); diff --git a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json index 3130cc6363ed8..e20765fae01d0 100644 --- a/test/code_size/test_minimal_runtime_code_size_audio_worklet.json +++ b/test/code_size/test_minimal_runtime_code_size_audio_worklet.json @@ -1,10 +1,10 @@ { "a.html": 519, "a.html.gz": 357, - "a.js": 4084, - "a.js.gz": 2134, + "a.js": 4075, + "a.js.gz": 2135, "a.wasm": 1288, "a.wasm.gz": 860, - "total": 5891, - "total_gz": 3351 + "total": 5882, + "total_gz": 3352 } From 4b926793de34f32108b8ca3bc46c2ae0a8332df9 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 26 Aug 2025 16:48:37 +0200 Subject: [PATCH 21/22] Changes following reviews --- src/audio_worklet.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 692271e3d3156..2806649ed709d 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -39,7 +39,12 @@ function createWasmAudioWorkletProcessor(audioParams) { this.samplesPerChannel = opts.samplesPerChannel; this.bytesPerChannel = this.samplesPerChannel * {{{ getNativeTypeSize('float') }}}; - // Creates the output views (see createOutputViews() docs) + // Prepare the output views; see createOutputViews(). The 'minimum alloc' + // firstly stops STACK_OVERFLOW_CHECK failing (since the stack will be + // full if we allocate all the available space, with 16 bytes being the + // minimum allo size due to alignments) leaving room for a single + // AudioSampleFrame as a minumum. There's an arbitrary maximum of 64, for + // the case where a multi-MB stack is passed. this.outputViews = new Array(Math.min(((wwParams.stackSize - /*minimum alloc*/ 16) / this.bytesPerChannel) | 0, /*sensible limit*/ 64)); #if ASSERTIONS console.assert(this.outputViews.length > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`); @@ -55,12 +60,8 @@ function createWasmAudioWorkletProcessor(audioParams) { /** * Create up-front as many typed views for marshalling the output data as - * may be required (with an arbitrary maximum of 64, for the case where a - * multi-MB stack is passed), allocated at the *top* of the worklet's stack - * (and whose addresses are fixed). The 'minimum alloc' firstly stops - * STACK_OVERFLOW_CHECK failing (since the stack will be full, and 16 bytes - * being the minimum allocation size due to alignments) and leaves room for - * a single AudioSampleFrame as a minumum. + * may be required, allocated at the *top* of the worklet's stack (and whose + * addresses are fixed). */ createOutputViews() { // These are still alloc'd to take advantage of the overflow checks, etc. @@ -89,7 +90,8 @@ function createWasmAudioWorkletProcessor(audioParams) { */ process(inputList, outputList, parameters) { #if ALLOW_MEMORY_GROWTH - // recreate the output views if the heap has changed + // Recreate the output views if the heap has changed + // TODO: add support for GROWABLE_ARRAYBUFFERS if (HEAPF32.buffer != this.outputViews[0].buffer) { this.createOutputViews(); } @@ -121,11 +123,12 @@ function createWasmAudioWorkletProcessor(audioParams) { stackMemoryStruct += {{{ C_STRUCTS.AudioParamFrame.__size__ }}}; stackMemoryData += parameters[entry].byteLength; } + 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})`); #endif - var oldStackPtr = stackSave(); // Allocate the necessary stack space. All pointer variables are in bytes; // 'structPtr' starts at the first struct entry (all run sequentially) // and is the working start to each record; 'dataPtr' is the same for the From 53627356a7cba871e9fbf205c34a5f05766e53a3 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 26 Aug 2025 16:58:51 +0200 Subject: [PATCH 22/22] Added more docs --- src/audio_worklet.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 2806649ed709d..f719f9c3b188b 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -174,7 +174,8 @@ function createWasmAudioWorkletProcessor(audioParams) { // Copy output audio descriptor structs to Wasm. 'outputsPtr' is the start // of the C callback's output AudioSampleFrame. 'dataPtr' will now be - // aligned with the output views, ending at stacktop. + // aligned with the output views, ending at stacktop (which is why this + // needs to be last). var /*const*/ outputsPtr = structPtr; for (entry of outputList) { // Write the AudioSampleFrame struct instance