Skip to content

Commit 6bba1e5

Browse files
committed
Interim commit, work-in-progress
1 parent 1791a2e commit 6bba1e5

File tree

1 file changed

+48
-45
lines changed

1 file changed

+48
-45
lines changed

src/audio_worklet.js

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,40 @@ function createWasmAudioWorkletProcessor(audioParams) {
3131
let opts = args.processorOptions;
3232
this.callbackFunction = Module['wasmTable'].get(opts['cb']);
3333
this.userData = opts['ud'];
34+
3435
// Then the samples per channel to process, fixed for the lifetime of the
35-
// context that created this processor. Note for when moving to Web Audio
36-
// 1.1: the typed array passed to process() should be the same size as
37-
// this 'render quantum size', and this exercise of passing in the value
38-
// shouldn't be required (to be verified with the in/out data lengths).
36+
// context that created this processor. Even though this 'render quantum
37+
// size' is fixed at 128 samples in the 1.0 spec, it will be variable in
38+
// the 1.1 spec. It's passed in now, just to prove it's settable, but will
39+
// eventually be a property of the AudioWorkletGlobalScope (globalThis).
3940
this.samplesPerChannel = opts['sc'];
40-
// Typed views of the output buffers on the worklet's stack, which after
41-
// creation should only change if the audio chain changes.
41+
42+
// Create up-front as many typed views for marshalling the output data as
43+
// may be required (with an arbitrary maximum of 16, for the case where a
44+
// multi-MB stack is passed), allocated at the *top* of the worklet's
45+
// stack (and whose addresses are fixed).
46+
this.maxBuffers = Math.min((Module['sz'] / (this.samplesPerChannel * 4)) | 0, /*sensible limit*/ 16);
47+
// These are still alloc'd to take advantage of the overflow checks, etc.
48+
var oldStackPtr = stackSave();
49+
var viewDataIdx = stackAlloc(this.maxBuffers * this.samplesPerChannel * 4) >> 2;
50+
#if WEBAUDIO_DEBUG
51+
console.log(`AudioWorklet creating ${this.maxBuffers} buffer one-time views (for a stack size of ${Module['sz']})`);
52+
#endif
4253
this.outputViews = [];
54+
for (var i = this.maxBuffers; i > 0; i--) {
55+
// Added in reverse so the lowest indices are closest to the stack top
56+
this.outputViews.unshift(
57+
HEAPF32.subarray(viewDataIdx, viewDataIdx += this.samplesPerChannel)
58+
);
59+
}
60+
stackRestore(oldStackPtr);
4361
}
4462

4563
static get parameterDescriptors() {
4664
return audioParams;
4765
}
4866

4967
process(inputList, outputList, parameters) {
50-
var time = Date.now();
5168
// Marshal all inputs and parameters to the Wasm memory on the thread stack,
5269
// then perform the wasm audio worklet call,
5370
// and finally marshal audio output data back.
@@ -56,53 +73,42 @@ function createWasmAudioWorkletProcessor(audioParams) {
5673
numOutputs = outputList.length,
5774
numParams = 0, i, j, k, dataPtr,
5875
bytesPerChannel = this.samplesPerChannel * 4,
76+
outputStackNeeded = 0, outputViewsNeeded = 0,
5977
stackMemoryNeeded = (numInputs + numOutputs) * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}},
6078
oldStackPtr = stackSave(),
61-
inputsPtr, outputsPtr, outputDataPtr, paramsPtr, requiredViews = 0,
79+
inputsPtr, outputsPtr, outputDataPtr, paramsPtr,
6280
didProduceAudio, paramArray;
6381

64-
// Calculate how much stack space is needed.
65-
for (i of inputList) stackMemoryNeeded += i.length * bytesPerChannel;
66-
for (i of outputList) stackMemoryNeeded += i.length * bytesPerChannel;
67-
for (i in parameters) stackMemoryNeeded += parameters[i].byteLength + {{{ C_STRUCTS.AudioParamFrame.__size__ }}}, ++numParams;
68-
69-
// Allocate the necessary stack space (dataPtr is always in bytes, and
70-
// advances as space for structs as data is taken, but note the switching
71-
// between bytes and indices into the various heaps).
72-
dataPtr = stackAlloc(stackMemoryNeeded);
73-
74-
// But start the output view allocs first, since once views are created we
75-
// want them to always start from the same address. Even if in the next
76-
// process() the outputList is empty, as soon as there are channels again
77-
// the views' addresses will still be the same (and only if more views are
78-
// required we will recreate them).
79-
outputDataPtr = dataPtr;
80-
for (/*which output*/i of outputList) {
82+
// Calculate how much stack space is needed, first for the outputs
83+
// pre-allocated in the constructor from the stack top.
84+
for (i of outputList) {
85+
outputStackNeeded += i.length * bytesPerChannel;
86+
outputViewsNeeded += i.length;
8187
#if ASSERTIONS
8288
for (/*which channel*/ j of i) {
8389
console.assert(j.byteLength === bytesPerChannel, `Unexpected AudioWorklet output buffer size (expected ${bytesPerChannel} got ${j.byteLength})`);
8490
}
8591
#endif
86-
// Keep advancing to make room for the output views
87-
dataPtr += bytesPerChannel * i.length;
88-
// How many output views are needed in total?
89-
requiredViews += i.length;
9092
}
91-
// Verify we have enough views (it doesn't matter if we have too many, any
92-
// excess won't be accessed) then also verify the views' start address
93-
// haven't changed.
94-
if (this.outputViews.length < requiredViews || (this.outputViews.length && this.outputViews[0].byteOffset != outputDataPtr)) {
95-
this.outputViews = [];
96-
k = outputDataPtr >> 2;
97-
for (i of outputList) {
98-
for (j of i) {
99-
this.outputViews.push(
100-
HEAPF32.subarray(k, k += this.samplesPerChannel)
101-
);
102-
}
103-
}
93+
// Then the remaining space all together
94+
for (i of inputList) stackMemoryNeeded += i.length * bytesPerChannel;
95+
for (i in parameters) stackMemoryNeeded += parameters[i].byteLength + {{{ C_STRUCTS.AudioParamFrame.__size__ }}}, ++numParams;
96+
97+
outputDataPtr = stackAlloc(outputStackNeeded);
98+
//#if ASSERTIONS
99+
console.assert(outputViewsNeeded <= this.outputViews.length, `Too many AudioWorklet outputs (need ${outputViewsNeeded} but have stack space for ${this.outputViews.length})`);
100+
k = outputDataPtr;
101+
for (i = outputViewsNeeded - 1; i >= 0; i--) {
102+
console.assert(this.outputViews[i].byteOffset == k, 'Internal error in addresses of the output array views');
103+
k += bytesPerChannel;
104104
}
105+
//#endif
106+
107+
// Allocate the necessary stack space (dataPtr is always in bytes, and
108+
// advances as space for structs as data is taken, but note the switching
109+
// between bytes and indices into the various heaps).
105110

111+
dataPtr = stackAlloc(stackMemoryNeeded);
106112
// Copy input audio descriptor structs and data to Wasm
107113
inputsPtr = dataPtr;
108114
k = inputsPtr >> 2;
@@ -163,9 +169,6 @@ function createWasmAudioWorkletProcessor(audioParams) {
163169
}
164170

165171
stackRestore(oldStackPtr);
166-
167-
time = Date.now() - time;
168-
//console.log(time);
169172

170173
// Return 'true' to tell the browser to continue running this processor.
171174
// (Returning 1 or any other truthy value won't work in Chrome)

0 commit comments

Comments
 (0)