Skip to content

Commit 43af76f

Browse files
authored
Add support for running output directly in Audio Worklet (#25942)
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: #22979, #25943
1 parent 1153278 commit 43af76f

File tree

9 files changed

+97
-8
lines changed

9 files changed

+97
-8
lines changed

ChangeLog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ See docs/process.md for more on how version tagging works.
2323
- The `select()` and `poll()` system calls can now block under certain
2424
circumstances. Specifically, if they are called from a background thread and
2525
file descriptors include pipes. (#25523, #25990)
26+
- It is now possible to load emscripten-generated code directly into an Audio
27+
Worklet context without using the `-sAUDIO_WORKLET` setting (which depends on
28+
shared memory and `-sWASM_WORKERS`). To do this, build with
29+
`-sENVIRONMENT=worklet`. In this environment, because audio worklets don't
30+
have a fetch API, you will need to either use `-sSINGLE_FILE` (to embed the
31+
Wasm file), or use a custom `instantiateWasm` callback to supply the
32+
Wasm module yourself. (#25942)
2633

2734
4.0.22 - 12/18/25
2835
-----------------

site/source/docs/api_reference/wasm_audio_worklets.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ Audio Worklets API is based on the Wasm Workers feature. It is possible to
2727
also enable the `-pthread` option while targeting Audio Worklets, but the
2828
audio worklets will always run in a Wasm Worker, and not in a Pthread.
2929

30+
.. note::
31+
If you want to load an emscripten-generated program into an AudioContext that
32+
you have created yourself, without depending on shared memory or
33+
:ref:`WASM_WORKERS` you can add ``worklet`` to :ref:`ENVIRONMENT`. In this
34+
mode, because Audio Worklets do not have any kind of fetch API, you will need
35+
either use `-sSINGLE_FILE` (to embed the Wasm file), or use a custom
36+
`instantiateWasm` to supply the Wasm module yourself (e.g. via `postMessage`).
37+
3038
Development Overview
3139
====================
3240

site/source/docs/tools_reference/settings_reference.rst

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

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

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

@@ -2504,6 +2509,11 @@ AUDIO_WORKLET
25042509
If true, enables targeting Wasm Web Audio AudioWorklets. Check out the
25052510
full documentation in site/source/docs/api_reference/wasm_audio_worklets.rst
25062511

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

25092519
.. _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
@@ -648,13 +648,18 @@ var LEGACY_VM_SUPPORT = false;
648648
// - 'webview' - just like web, but in a webview like Cordova; considered to be
649649
// same as "web" in almost every place
650650
// - 'worker' - a web worker environment.
651+
// - 'worklet' - Audio Worklet environment.
651652
// - 'node' - Node.js.
652653
// - 'shell' - a JS shell like d8, js, or jsc.
653654
//
654655
// This setting can be a comma-separated list of these environments, e.g.,
655656
// "web,worker". If this is the empty string, then all environments are
656657
// supported.
657658
//
659+
// Certain settings will automatically add to this list. For examble, building
660+
// with pthreads will automatically add `worker` and building with
661+
// ``AUDIO_WORKLET`` will automatically add `worklet`.
662+
//
658663
// Note that the set of environments recognized here is not identical to the
659664
// ones we identify at runtime using ``ENVIRONMENT_IS_*``. Specifically:
660665
//
@@ -1631,6 +1636,11 @@ var WASM_WORKERS = 0;
16311636

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

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
@@ -5480,6 +5480,44 @@ def test_audio_worklet_params_mixing(self, args):
54805480
def test_audio_worklet_emscripten_locks(self):
54815481
self.btest_exit('webaudio/audioworklet_emscripten_locks.c', cflags=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread'])
54825482

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