Skip to content

Commit 23c0a97

Browse files
authored
[AUDIO_WORKLET] Inline .aw.js file into the main output .js file (#24190)
This is similar to what we already did for pthreads (#24163) and normal wasm workers (#24163). By integrating this file we can also share more startup code with normal wasm workers.
1 parent c0ae905 commit 23c0a97

17 files changed

+262
-277
lines changed

src/audio_worklet.js

Lines changed: 18 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,16 @@
1212
// the node constructor's "processorOptions" field, we can share the necessary
1313
// bootstrap information from the main thread to the AudioWorkletGlobalScope.
1414

15+
if (ENVIRONMENT_IS_AUDIO_WORKLET) {
16+
1517
function createWasmAudioWorkletProcessor(audioParams) {
1618
class WasmAudioWorkletProcessor extends AudioWorkletProcessor {
1719
constructor(args) {
1820
super();
1921

20-
// Copy needed stack allocation functions from the Module object
21-
// to global scope, these will be accessed in hot paths, so maybe
22-
// they'll be a bit faster to access directly, rather than referencing
23-
// them as properties of the Module object.
24-
globalThis.stackAlloc = Module['stackAlloc'];
25-
globalThis.stackSave = Module['stackSave'];
26-
globalThis.stackRestore = Module['stackRestore'];
27-
globalThis.HEAPU32 = Module['HEAPU32'];
28-
globalThis.HEAPF32 = Module['HEAPF32'];
29-
3022
// Capture the Wasm function callback to invoke.
3123
let opts = args.processorOptions;
32-
this.callbackFunction = Module['wasmTable'].get(opts['cb']);
24+
this.callbackFunction = wasmTable.get(opts['cb']);
3325
this.userData = opts['ud'];
3426
// Then the samples per channel to process, fixed for the lifetime of the
3527
// context that created this processor. Note for when moving to Web Audio
@@ -43,6 +35,9 @@ function createWasmAudioWorkletProcessor(audioParams) {
4335
return audioParams;
4436
}
4537

38+
/**
39+
* @param {Object} parameters
40+
*/
4641
process(inputList, outputList, parameters) {
4742
// Marshal all inputs and parameters to the Wasm memory on the thread stack,
4843
// then perform the wasm audio worklet call,
@@ -99,6 +94,7 @@ function createWasmAudioWorkletProcessor(audioParams) {
9994
paramsPtr = dataPtr;
10095
k = paramsPtr >> 2;
10196
dataPtr += numParams * {{{ C_STRUCTS.AudioParamFrame.__size__ }}};
97+
10298
for (i = 0; paramArray = parameters[i++];) {
10399
// Write the AudioParamFrame struct instance
104100
HEAPU32[k + {{{ C_STRUCTS.AudioParamFrame.length / 4 }}}] = paramArray.length;
@@ -134,53 +130,29 @@ function createWasmAudioWorkletProcessor(audioParams) {
134130
return WasmAudioWorkletProcessor;
135131
}
136132

133+
var messagePort;
134+
137135
// Specify a worklet processor that will be used to receive messages to this
138136
// AudioWorkletGlobalScope. We never connect this initial AudioWorkletProcessor
139137
// to the audio graph to do any audio processing.
140138
class BootstrapMessages extends AudioWorkletProcessor {
141139
constructor(arg) {
142140
super();
143-
// Audio worklets need to show up as wasm workers. This is the way we signal
144-
// that.
145-
globalThis.name = 'em-ww';
146-
// Initialize the global Emscripten Module object that contains e.g. the
147-
// Wasm Module and Memory objects. After this we are ready to load in the
148-
// main application JS script, which the main thread will addModule()
149-
// to this scope.
150-
globalThis.Module = arg['processorOptions'];
151-
#if !MINIMAL_RUNTIME
152-
// Default runtime relies on an injected instantiateWasm() function to
153-
// initialize the Wasm Module.
154-
globalThis.Module['instantiateWasm'] = (info, receiveInstance) => {
155-
var instance = new WebAssembly.Instance(Module['wasm'], info);
156-
return receiveInstance(instance, Module['wasm']);
157-
};
158-
#endif
141+
startWasmWorker(arg['processorOptions'])
159142
#if WEBAUDIO_DEBUG
160143
console.log('AudioWorklet global scope looks like this:');
161144
console.dir(globalThis);
162145
#endif
163146
// Listen to messages from the main thread. These messages will ask this
164147
// scope to create the real AudioWorkletProcessors that call out to Wasm to
165148
// do audio processing.
166-
let p = globalThis['messagePort'] = this.port;
167-
p.onmessage = async (msg) => {
149+
messagePort = this.port;
150+
/** @suppress {checkTypes} */
151+
messagePort.onmessage = async (msg) => {
168152
let d = msg.data;
169153
if (d['_wpn']) {
170154
// '_wpn' is short for 'Worklet Processor Node', using an identifier
171155
// that will never conflict with user messages
172-
#if MODULARIZE
173-
// Instantiate the MODULARIZEd Module function, which is stored for us
174-
// under the special global name AudioWorkletModule in
175-
// MODULARIZE+AUDIO_WORKLET builds.
176-
if (globalThis.AudioWorkletModule) {
177-
// This populates the Module object with all the Wasm properties
178-
globalThis.Module = await AudioWorkletModule(Module);
179-
// We have now instantiated the Module function, can discard it from
180-
// global scope
181-
delete globalThis.AudioWorkletModule;
182-
}
183-
#endif
184156
// Register a real AudioWorkletProcessor that will actually do audio processing.
185157
// 'ap' being the audio params
186158
registerProcessor(d['_wpn'], createWasmAudioWorkletProcessor(d['ap']));
@@ -196,14 +168,14 @@ class BootstrapMessages extends AudioWorkletProcessor {
196168
// 'cb' the callback function
197169
// 'ch' the context handle
198170
// 'ud' the passed user data
199-
p.postMessage({'_wsc': d['cb'], 'x': [d['ch'], 1/*EM_TRUE*/, d['ud']] });
171+
messagePort.postMessage({'_wsc': d['cb'], 'x': [d['ch'], 1/*EM_TRUE*/, d['ud']] });
200172
} else if (d['_wsc']) {
201173
#if MEMORY64
202174
var ptr = BigInt(d['_wsc']);
203175
#else
204176
var ptr = d['_wsc'];
205177
#endif
206-
Module['wasmTable'].get(ptr)(...d['x']);
178+
wasmTable.get(ptr)(...d['x']);
207179
};
208180
}
209181
}
@@ -220,4 +192,6 @@ class BootstrapMessages extends AudioWorkletProcessor {
220192
};
221193

222194
// Register the dummy processor that will just receive messages.
223-
registerProcessor('message', BootstrapMessages);
195+
registerProcessor('em-bootstrap', BootstrapMessages);
196+
197+
} // ENVIRONMENT_IS_AUDIO_WORKLET

src/closure-externs/closure-externs.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,5 @@ Symbol.dispose;
271271

272272
// Common between node-externs and v8-externs
273273
var os = {};
274+
275+
AudioWorkletProcessor.parameterDescriptors;

src/lib/libwebaudio.js

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -181,47 +181,27 @@ let LibraryWebAudio = {
181181
return audioWorkletCreationFailed();
182182
}
183183

184-
// TODO: In MINIMAL_RUNTIME builds, read this file off of a preloaded Blob,
185-
// and/or embed from a string like with WASM_WORKERS==2 mode.
186-
audioWorklet.addModule('{{{ TARGET_BASENAME }}}.aw.js').then(() => {
184+
audioWorklet.addModule({{{ wasmWorkerJs }}}).then(() => {
187185
#if WEBAUDIO_DEBUG
188-
console.log(`emscripten_start_wasm_audio_worklet_thread_async() addModule('audioworklet.js') completed`);
186+
console.log(`emscripten_start_wasm_audio_worklet_thread_async() addModule() completed`);
189187
#endif
190-
audioWorklet.bootstrapMessage = new AudioWorkletNode(audioContext, 'message', {
188+
audioWorklet.bootstrapMessage = new AudioWorkletNode(audioContext, 'em-bootstrap', {
191189
processorOptions: {
192190
// Assign the loaded AudioWorkletGlobalScope a Wasm Worker ID so that
193191
// it can utilized its own TLS slots, and it is recognized to not be
194192
// the main browser thread.
195193
'$ww': _wasmWorkersID++,
196194
#if MINIMAL_RUNTIME
197195
'wasm': Module['wasm'],
198-
'mem': wasmMemory,
199196
#else
200197
'wasm': wasmModule,
201-
'wasmMemory': wasmMemory,
202198
#endif
199+
'mem': wasmMemory,
203200
'sb': stackLowestAddress, // sb = stack base
204201
'sz': stackSize, // sz = stack size
205202
}
206203
});
207204
audioWorklet.bootstrapMessage.port.onmessage = _EmAudioDispatchProcessorCallback;
208-
209-
// AudioWorklets do not have a importScripts() function like Web Workers
210-
// do (and AudioWorkletGlobalScope does not allow dynamic import()
211-
// either), but instead, the main thread must load all JS code into the
212-
// worklet scope. Send the application main JS script to the audio
213-
// worklet.
214-
return audioWorklet.addModule(
215-
#if MINIMAL_RUNTIME
216-
Module['js']
217-
#else
218-
Module['mainScriptUrlOrBlob'] || _scriptName
219-
#endif
220-
);
221-
}).then(() => {
222-
#if WEBAUDIO_DEBUG
223-
console.log(`emscripten_start_wasm_audio_worklet_thread_async() addModule() of main application JS completed`);
224-
#endif
225205
{{{ makeDynCall('viii', 'callback') }}}(contextHandle, 1/*EM_TRUE*/, userData);
226206
}).catch(audioWorkletCreationFailed);
227207
},
@@ -335,11 +315,11 @@ let LibraryWebAudio = {
335315
emscripten_current_thread_is_audio_worklet: () => typeof AudioWorkletGlobalScope !== 'undefined',
336316

337317
emscripten_audio_worklet_post_function_v: (audioContext, funcPtr) => {
338-
(audioContext ? EmAudio[audioContext].audioWorklet.bootstrapMessage.port : globalThis['messagePort']).postMessage({'_wsc': funcPtr, 'x': [] }); // "WaSm Call"
318+
(audioContext ? EmAudio[audioContext].audioWorklet.bootstrapMessage.port : messagePort).postMessage({'_wsc': funcPtr, 'x': [] }); // "WaSm Call"
339319
},
340320

341321
$emscripten_audio_worklet_post_function_1: (audioContext, funcPtr, arg0) => {
342-
(audioContext ? EmAudio[audioContext].audioWorklet.bootstrapMessage.port : globalThis['messagePort']).postMessage({'_wsc': funcPtr, 'x': [arg0] }); // "WaSm Call"
322+
(audioContext ? EmAudio[audioContext].audioWorklet.bootstrapMessage.port : messagePort).postMessage({'_wsc': funcPtr, 'x': [arg0] }); // "WaSm Call"
343323
},
344324

345325
emscripten_audio_worklet_post_function_vi__deps: ['$emscripten_audio_worklet_post_function_1'],
@@ -353,7 +333,7 @@ let LibraryWebAudio = {
353333
},
354334

355335
$emscripten_audio_worklet_post_function_2: (audioContext, funcPtr, arg0, arg1) => {
356-
(audioContext ? EmAudio[audioContext].audioWorklet.bootstrapMessage.port : globalThis['messagePort']).postMessage({'_wsc': funcPtr, 'x': [arg0, arg1] }); // "WaSm Call"
336+
(audioContext ? EmAudio[audioContext].audioWorklet.bootstrapMessage.port : messagePort).postMessage({'_wsc': funcPtr, 'x': [arg0, arg1] }); // "WaSm Call"
357337
},
358338

359339
emscripten_audio_worklet_post_function_vii__deps: ['$emscripten_audio_worklet_post_function_2'],
@@ -367,7 +347,7 @@ let LibraryWebAudio = {
367347
},
368348

369349
$emscripten_audio_worklet_post_function_3: (audioContext, funcPtr, arg0, arg1, arg2) => {
370-
(audioContext ? EmAudio[audioContext].audioWorklet.bootstrapMessage.port : globalThis['messagePort']).postMessage({'_wsc': funcPtr, 'x': [arg0, arg1, arg2] }); // "WaSm Call"
350+
(audioContext ? EmAudio[audioContext].audioWorklet.bootstrapMessage.port : messagePort).postMessage({'_wsc': funcPtr, 'x': [arg0, arg1, arg2] }); // "WaSm Call"
371351
},
372352
emscripten_audio_worklet_post_function_viii__deps: ['$emscripten_audio_worklet_post_function_3'],
373353
emscripten_audio_worklet_post_function_viii: (audioContext, funcPtr, arg0, arg1, arg2) => {
@@ -387,7 +367,7 @@ let LibraryWebAudio = {
387367
assert(UTF8ToString(sigPtr)[0] != 'v', 'Do NOT specify the return argument in the signature string for a call to emscripten_audio_worklet_post_function_sig(), just pass the function arguments.');
388368
assert(varargs);
389369
#endif
390-
(audioContext ? EmAudio[audioContext].audioWorklet.bootstrapMessage.port : globalThis['messagePort']).postMessage({'_wsc': funcPtr, 'x': readEmAsmArgs(sigPtr, varargs) });
370+
(audioContext ? EmAudio[audioContext].audioWorklet.bootstrapMessage.port : messagePort).postMessage({'_wsc': funcPtr, 'x': readEmAsmArgs(sigPtr, varargs) });
391371
}
392372
};
393373

src/postamble_minimal.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ var imports = {
118118
#if ASSERTIONS && !WASM2JS
119119
// Module['wasm'] should contain a typed array of the Wasm object data, or a
120120
// precompiled WebAssembly Module.
121-
if (!WebAssembly.instantiateStreaming && !Module['wasm']) throw 'Must load WebAssembly Module in to variable Module.wasm before adding compiled output .js script to the DOM';
121+
assert(WebAssembly.instantiateStreaming || Module['wasm'], 'Must load WebAssembly Module in to variable Module.wasm before adding compiled output .js script to the DOM');
122122
#endif
123123
(WebAssembly.instantiateStreaming
124124
? WebAssembly.instantiateStreaming(fetch('{{{ TARGET_BASENAME }}}.wasm'), imports)
@@ -131,7 +131,7 @@ WebAssembly.instantiateStreaming(fetch('{{{ TARGET_BASENAME }}}.wasm'), imports)
131131
#if ASSERTIONS && !WASM2JS
132132
// Module['wasm'] should contain a typed array of the Wasm object data, or a
133133
// precompiled WebAssembly Module.
134-
if (!Module['wasm']) throw 'Must load WebAssembly Module in to variable Module.wasm before adding compiled output .js script to the DOM';
134+
assert(Module['wasm'], 'Must load WebAssembly Module in to variable Module.wasm before adding compiled output .js script to the DOM');
135135
#endif
136136

137137
<<< ATMODULES >>>
@@ -259,8 +259,5 @@ WebAssembly.instantiate(Module['wasm'], imports).then((output) => {
259259
}
260260

261261
// When running in a background thread we delay module loading until we have
262-
#if AUDIO_WORKLET
263-
if (ENVIRONMENT_IS_AUDIO_WORKLET) loadModule();
264-
#endif
265262
{{{ runIfMainThread('loadModule();') }}}
266263
#endif

src/runtime_init_memory.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
// check for full engine support (use string 'subarray' to avoid closure compiler confusion)
1313

1414
function initMemory() {
15-
#if AUDIO_WORKLET
16-
if (!ENVIRONMENT_IS_AUDIO_WORKLET)
17-
#endif
1815
{{{ runIfWorkerThread('return') }}}
1916

2017
#if expectToReceiveOnModule('wasmMemory')

src/runtime_shared.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ if (ENVIRONMENT_IS_NODE && {{{ ENVIRONMENT_IS_WORKER_THREAD() }}}) {
4747
#include "wasm_worker.js"
4848
#endif
4949

50+
#if AUDIO_WORKLET
51+
#include "audio_worklet.js"
52+
#endif
53+
5054
#if LOAD_SOURCE_MAP
5155
var wasmSourceMap;
5256
#include "source_map_support.js"
@@ -64,11 +68,6 @@ var wasmOffsetConverter;
6468
let shouldExport = false;
6569
if (MODULARIZE && EXPORT_ALL) {
6670
shouldExport = true;
67-
} else if (AUDIO_WORKLET && (x == 'HEAPU32' || x == 'HEAPF32')) {
68-
// Export to the AudioWorkletGlobalScope the needed variables to access
69-
// the heap. AudioWorkletGlobalScope is unable to access global JS vars
70-
// in the compiled main JS file.
71-
shouldExport = true;
7271
} else if (EXPORTED_RUNTIME_METHODS.includes(x)) {
7372
shouldExport = true;
7473
}

src/shell.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,21 @@ var readyPromise = new Promise((resolve, reject) => {
5353
});
5454
#endif
5555

56-
// Determine the runtime environment we are in. You can customize this by
57-
// setting the ENVIRONMENT setting at compile time (see settings.js).
56+
#if WASM_WORKERS
57+
// The way we signal to a worker that it is hosting a pthread is to construct
58+
// it with a specific name.
59+
var ENVIRONMENT_IS_WASM_WORKER = globalThis.name == 'em-ww';
60+
#endif
5861

5962
#if AUDIO_WORKLET
6063
var ENVIRONMENT_IS_AUDIO_WORKLET = typeof AudioWorkletGlobalScope !== 'undefined';
64+
// Audio worklets behave as wasm workers.
65+
if (ENVIRONMENT_IS_AUDIO_WORKLET) ENVIRONMENT_IS_WASM_WORKER = true;
6166
#endif
6267

68+
// Determine the runtime environment we are in. You can customize this by
69+
// setting the ENVIRONMENT setting at compile time (see settings.js).
70+
6371
#if ENVIRONMENT && !ENVIRONMENT.includes(',')
6472
var ENVIRONMENT_IS_WEB = {{{ ENVIRONMENT === 'web' }}};
6573
#if PTHREADS && ENVIRONMENT_MAY_BE_NODE
@@ -102,12 +110,6 @@ if (ENVIRONMENT_IS_PTHREAD) {
102110
#endif
103111
#endif
104112

105-
#if WASM_WORKERS
106-
// The way we signal to a worker that it is hosting a pthread is to construct
107-
// it with a specific name.
108-
var ENVIRONMENT_IS_WASM_WORKER = globalThis.name == 'em-ww';
109-
#endif
110-
111113
#if ENVIRONMENT_MAY_BE_NODE
112114
if (ENVIRONMENT_IS_NODE) {
113115
#if EXPORT_ES6

src/shell_minimal.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ var ENVIRONMENT_IS_NODE = typeof process == 'object' && process.type != 'rendere
5151
var ENVIRONMENT_IS_SHELL = typeof read == 'function';
5252
#endif
5353

54-
#if AUDIO_WORKLET
55-
var ENVIRONMENT_IS_AUDIO_WORKLET = typeof AudioWorkletGlobalScope !== 'undefined';
56-
#endif
57-
5854
#if ASSERTIONS || PTHREADS
5955
#if !ENVIRONMENT_MAY_BE_NODE && !ENVIRONMENT_MAY_BE_SHELL
6056
var ENVIRONMENT_IS_WEB = true
@@ -88,6 +84,11 @@ if (ENVIRONMENT_IS_NODE) {
8884
#endif
8985
#endif
9086

87+
#if AUDIO_WORKLET
88+
var ENVIRONMENT_IS_AUDIO_WORKLET = typeof AudioWorkletGlobalScope !== 'undefined';
89+
if (ENVIRONMENT_IS_AUDIO_WORKLET) ENVIRONMENT_IS_WASM_WORKER = true;
90+
#endif
91+
9192
#if ASSERTIONS && ENVIRONMENT_MAY_BE_NODE && ENVIRONMENT_MAY_BE_SHELL
9293
if (ENVIRONMENT_IS_NODE && ENVIRONMENT_IS_SHELL) {
9394
throw 'unclear environment';

0 commit comments

Comments
 (0)