Skip to content

Commit a39ca89

Browse files
committed
Add support for running output directly in Audio Worklet
There are some use cases where its useful to be able to run emscripten generated code in an audio worklet without building with `-sAUDIO_WORKLET`. One major one is for deploying in a context where shared memory not available (i.e. no cross origin isolation) Fixes: #25943, #25943
1 parent 0f3d2e6 commit a39ca89

File tree

8 files changed

+89
-8
lines changed

8 files changed

+89
-8
lines changed

ChangeLog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ See docs/process.md for more on how version tagging works.
2020

2121
4.0.22 (in development)
2222
-----------------------
23+
- It is now possible to load emscripten-generated code directly into an Audio
24+
Worklet without using the `-sAUDIO_WORKLET` setting (which depends on shared
25+
memory and `-sWASM_WORKERS`). To do this simply build with
26+
`-sENVIRONMENT=worklet`. In this environment, because audio worklets don't
27+
have a fetch API, you will need to either using `-sSINGLE_FILE` to embed the
28+
Wasm file, or use a custom `instantiateWasm` callback while performing
29+
instantation externally. (#25942)
2330
- Source maps now support 'names' field with function name information.
2431
emsymbolizer will show function names when used with a source map. The size
2532
of source maps may increase 2-3x and the link time can increase slightly due

site/source/docs/tools_reference/settings_reference.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,13 +991,18 @@ are:
991991
- 'webview' - just like web, but in a webview like Cordova; considered to be
992992
same as "web" in almost every place
993993
- 'worker' - a web worker environment.
994+
- 'worklet' - Audio Worklet environment.
994995
- 'node' - Node.js.
995996
- 'shell' - a JS shell like d8, js, or jsc.
996997

997998
This setting can be a comma-separated list of these environments, e.g.,
998999
"web,worker". If this is the empty string, then all environments are
9991000
supported.
10001001

1002+
Certain settings will automatically add to this list. For examble, building
1003+
with pthreads will automatically add `worker` and building with
1004+
``AUDIO_WORKLET`` will automatically add `worklet`.
1005+
10011006
Note that the set of environments recognized here is not identical to the
10021007
ones we identify at runtime using ``ENVIRONMENT_IS_*``. Specifically:
10031008

@@ -2502,6 +2507,11 @@ AUDIO_WORKLET
25022507
If true, enables targeting Wasm Web Audio AudioWorklets. Check out the
25032508
full documentation in site/source/docs/api_reference/wasm_audio_worklets.rst
25042509

2510+
Note: The setting will implicitly add ``worklet`` to the :ref:`ENVIRONMENT`,
2511+
(i.e. the resulting code and run in a worklet environment) but additionaly
2512+
depends on ``WASM_WORKERS`` and Wasm SharedArrayBuffer to run new Audio
2513+
Worklets.
2514+
25052515
Default value: 0
25062516

25072517
.. _audio_worklet_support_audio_params:

src/preamble.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,12 @@ function findWasmBinary() {
443443
}
444444
#endif
445445

446+
#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET && !AUDIO_WORKLET // AUDIO_WORKLET handled above
447+
if (ENVIRONMENT_IS_AUDIO_WORKLET) {
448+
return '{{{ WASM_BINARY_FILE }}}';
449+
}
450+
#endif
451+
446452
if (Module['locateFile']) {
447453
return locateFile('{{{ WASM_BINARY_FILE }}}');
448454
}

src/settings.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,13 +646,18 @@ var LEGACY_VM_SUPPORT = false;
646646
// - 'webview' - just like web, but in a webview like Cordova; considered to be
647647
// same as "web" in almost every place
648648
// - 'worker' - a web worker environment.
649+
// - 'worklet' - Audio Worklet environment.
649650
// - 'node' - Node.js.
650651
// - 'shell' - a JS shell like d8, js, or jsc.
651652
//
652653
// This setting can be a comma-separated list of these environments, e.g.,
653654
// "web,worker". If this is the empty string, then all environments are
654655
// supported.
655656
//
657+
// Certain settings will automatically add to this list. For examble, building
658+
// with pthreads will automatically add `worker` and building with
659+
// ``AUDIO_WORKLET`` will automatically add `worklet`.
660+
//
656661
// Note that the set of environments recognized here is not identical to the
657662
// ones we identify at runtime using ``ENVIRONMENT_IS_*``. Specifically:
658663
//
@@ -1629,6 +1634,11 @@ var WASM_WORKERS = 0;
16291634

16301635
// If true, enables targeting Wasm Web Audio AudioWorklets. Check out the
16311636
// full documentation in site/source/docs/api_reference/wasm_audio_worklets.rst
1637+
//
1638+
// Note: The setting will implicitly add ``worklet`` to the :ref:`ENVIRONMENT`,
1639+
// (i.e. the resulting code and run in a worklet environment) but additionaly
1640+
// depends on ``WASM_WORKERS`` and Wasm SharedArrayBuffer to run new Audio
1641+
// Worklets.
16321642
// [link]
16331643
var AUDIO_WORKLET = 0;
16341644

src/settings_internal.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ var ENVIRONMENT_MAY_BE_WORKER = true;
140140
var ENVIRONMENT_MAY_BE_NODE = true;
141141
var ENVIRONMENT_MAY_BE_SHELL = true;
142142
var ENVIRONMENT_MAY_BE_WEBVIEW = true;
143+
var ENVIRONMENT_MAY_BE_AUDIO_WORKLET = true;
143144

144145
// Whether to minify import and export names in the minify_wasm_js stage.
145146
// Currently always off for MEMORY64.

src/shell.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ var Module = moduleArg;
3434
var Module;
3535
// if (!Module)` is crucial for Closure Compiler here as it will otherwise replace every `Module` occurrence with a string
3636
if (!Module) /** @suppress{checkTypes}*/Module = {"__EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__":1};
37-
#elif AUDIO_WORKLET
37+
#elif ENVIRONMENT_MAY_BE_AUDIO_WORKLET
3838
var Module = globalThis.Module || (typeof {{{ EXPORT_NAME }}} != 'undefined' ? {{{ EXPORT_NAME }}} : {});
3939
#else
4040
var Module = typeof {{{ EXPORT_NAME }}} != 'undefined' ? {{{ EXPORT_NAME }}} : {};
@@ -53,8 +53,11 @@ var Module = typeof {{{ EXPORT_NAME }}} != 'undefined' ? {{{ EXPORT_NAME }}} : {
5353
var ENVIRONMENT_IS_WASM_WORKER = globalThis.name == 'em-ww';
5454
#endif
5555

56-
#if AUDIO_WORKLET
56+
#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET
5757
var ENVIRONMENT_IS_AUDIO_WORKLET = !!globalThis.AudioWorkletGlobalScope;
58+
#endif
59+
60+
#if AUDIO_WORKLET
5861
// Audio worklets behave as wasm workers.
5962
if (ENVIRONMENT_IS_AUDIO_WORKLET) ENVIRONMENT_IS_WASM_WORKER = true;
6063
#endif
@@ -79,7 +82,7 @@ var ENVIRONMENT_IS_WORKER = !!globalThis.WorkerGlobalScope;
7982
// N.b. Electron.js environment is simultaneously a NODE-environment, but
8083
// also a web environment.
8184
var ENVIRONMENT_IS_NODE = {{{ nodeDetectionCode() }}};
82-
#if AUDIO_WORKLET
85+
#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET
8386
var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER && !ENVIRONMENT_IS_AUDIO_WORKLET;
8487
#else
8588
var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
@@ -352,7 +355,9 @@ if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
352355
}
353356
} else
354357
#endif // ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
355-
#if AUDIO_WORKLET && ASSERTIONS
358+
#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET
359+
#endif
360+
#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET && ASSERTIONS
356361
if (!ENVIRONMENT_IS_AUDIO_WORKLET)
357362
#endif
358363
{
@@ -401,7 +406,7 @@ if (ENVIRONMENT_IS_NODE) {
401406
// if an assertion fails it cannot print the message
402407
#if PTHREADS
403408
assert(
404-
#if AUDIO_WORKLET
409+
#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET
405410
ENVIRONMENT_IS_AUDIO_WORKLET ||
406411
#endif
407412
ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER || ENVIRONMENT_IS_NODE, 'Pthreads do not work in this environment yet (need Web Workers, or an alternative to them)');

test/test_browser.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5478,6 +5478,44 @@ def test_audio_worklet_params_mixing(self, args):
54785478
def test_audio_worklet_emscripten_locks(self):
54795479
self.btest_exit('webaudio/audioworklet_emscripten_locks.c', cflags=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread'])
54805480

5481+
def test_audio_worklet_direct(self):
5482+
self.add_browser_reporting()
5483+
self.emcc('hello_world.c', ['-o', 'hello_world.mjs', '-sEXPORT_ES6', '-sSINGLE_FILE', '-sENVIRONMENT=worklet'])
5484+
create_file('worklet.mjs', '''
5485+
import Module from "./hello_world.mjs"
5486+
console.log("in worklet");
5487+
class MyProcessor extends AudioWorkletProcessor {
5488+
constructor() {
5489+
super();
5490+
Module().then(() => {
5491+
console.log("done Module constructor");
5492+
this.port.postMessage("ready");
5493+
});
5494+
}
5495+
process(inputs, outputs, parameters) {
5496+
return true;
5497+
}
5498+
}
5499+
registerProcessor('my-processor', MyProcessor);
5500+
console.log("done register");
5501+
''')
5502+
create_file('test.html', '''
5503+
<script src="browser_reporting.js"></script>
5504+
<script>
5505+
async function createContext() {
5506+
const context = new window.AudioContext();
5507+
await context.audioWorklet.addModule('worklet.mjs');
5508+
const node = new AudioWorkletNode(context, 'my-processor');
5509+
node.port.onmessage = (event) => {
5510+
console.log(event);
5511+
reportResultToServer(event.data);
5512+
}
5513+
}
5514+
createContext();
5515+
</script>
5516+
''')
5517+
self.run_browser('test.html', '/report_result?ready')
5518+
54815519
# Verifies setting audio context sample rate, and that emscripten_audio_context_sample_rate() works.
54825520
@requires_sound_hardware
54835521
@also_with_minimal_runtime

tools/link.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
'__main_argc_argv',
6868
]
6969

70-
VALID_ENVIRONMENTS = ('web', 'webview', 'worker', 'node', 'shell')
70+
VALID_ENVIRONMENTS = {'web', 'webview', 'worker', 'node', 'shell', 'worklet'}
7171

7272
EXECUTABLE_EXTENSIONS = ['.wasm', '.html', '.js', '.mjs', '.out', '']
7373

@@ -184,6 +184,9 @@ def setup_environment_settings():
184184
if settings.SHARED_MEMORY and settings.ENVIRONMENT:
185185
settings.ENVIRONMENT.append('worker')
186186

187+
if settings.AUDIO_WORKLET:
188+
settings.ENVIRONMENT.append('worklet')
189+
187190
# Environment setting based on user input
188191
if any(x for x in settings.ENVIRONMENT if x not in VALID_ENVIRONMENTS):
189192
exit_with_error(f'Invalid environment specified in "ENVIRONMENT": {settings.ENVIRONMENT}. Should be one of: {",".join(VALID_ENVIRONMENTS)}')
@@ -193,6 +196,7 @@ def setup_environment_settings():
193196
settings.ENVIRONMENT_MAY_BE_NODE = not settings.ENVIRONMENT or 'node' in settings.ENVIRONMENT
194197
settings.ENVIRONMENT_MAY_BE_SHELL = not settings.ENVIRONMENT or 'shell' in settings.ENVIRONMENT
195198
settings.ENVIRONMENT_MAY_BE_WORKER = not settings.ENVIRONMENT or 'worker' in settings.ENVIRONMENT
199+
settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET = not settings.ENVIRONMENT or 'worklet' in settings.ENVIRONMENT
196200

197201
if not settings.ENVIRONMENT_MAY_BE_NODE:
198202
if 'MIN_NODE_VERSION' in user_settings and settings.MIN_NODE_VERSION != feature_matrix.UNSUPPORTED:
@@ -1175,7 +1179,7 @@ def limit_incoming_module_api():
11751179
# In Audio Worklets TextDecoder API is intentionally not exposed
11761180
# (https://github.com/WebAudio/web-audio-api/issues/2499) so we also need to
11771181
# keep the JavaScript-based fallback.
1178-
if settings.SHRINK_LEVEL >= 2 and not settings.AUDIO_WORKLET and \
1182+
if settings.SHRINK_LEVEL >= 2 and not settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET and \
11791183
not settings.ENVIRONMENT_MAY_BE_SHELL:
11801184
default_setting('TEXTDECODER', 2)
11811185

@@ -2472,7 +2476,7 @@ def module_export_name_substitution():
24722476
logger.debug(f'Private module export name substitution with {settings.EXPORT_NAME}')
24732477
src = read_file(final_js)
24742478
final_js += '.module_export_name_substitution.js'
2475-
if settings.MINIMAL_RUNTIME and not settings.ENVIRONMENT_MAY_BE_NODE and not settings.ENVIRONMENT_MAY_BE_SHELL and not settings.AUDIO_WORKLET:
2479+
if settings.MINIMAL_RUNTIME and not settings.ENVIRONMENT_MAY_BE_NODE and not settings.ENVIRONMENT_MAY_BE_SHELL and not settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET:
24762480
# On the web, with MINIMAL_RUNTIME, the Module object is always provided
24772481
# via the shell html in order to provide the .asm.js/.wasm content.
24782482
replacement = settings.EXPORT_NAME

0 commit comments

Comments
 (0)