diff --git a/ChangeLog.md b/ChangeLog.md
index 1a6ad5c9d683c..7a9ea0ea609da 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -20,6 +20,13 @@ See docs/process.md for more on how version tagging works.
4.0.22 (in development)
-----------------------
+- It is now possible to load emscripten-generated code directly into an Audio
+ Worklet without using the `-sAUDIO_WORKLET` setting (which depends on shared
+ memory and `-sWASM_WORKERS`). To do this simply build with
+ `-sENVIRONMENT=worklet`. In this environment, because audio worklets don't
+ have a fetch API, you will need to either use `-sSINGLE_FILE` (to embed the
+ Wasm file), or use a custom `instantiateWasm` callback to supply the
+ Wasm module yourself. (#25942)
- Source maps now support 'names' field with function name information.
emsymbolizer will show function names when used with a source map. The size
of source maps may increase 2-3x and the link time can increase slightly due
diff --git a/site/source/docs/api_reference/wasm_audio_worklets.rst b/site/source/docs/api_reference/wasm_audio_worklets.rst
index 7f92d13bdf3d5..6aa51378fba65 100644
--- a/site/source/docs/api_reference/wasm_audio_worklets.rst
+++ b/site/source/docs/api_reference/wasm_audio_worklets.rst
@@ -27,6 +27,14 @@ Audio Worklets API is based on the Wasm Workers feature. It is possible to
also enable the `-pthread` option while targeting Audio Worklets, but the
audio worklets will always run in a Wasm Worker, and not in a Pthread.
+.. note::
+ If you want to load an emscripten-generated program into an AudioContext that
+ you have created yourself, without depending on shared memory or
+ :ref:`WASM_WORKERS` you can add ``worklet`` to :ref:`ENVIRONMENT`. In this
+ mode, because Audio Worklets do not have any kind of fetch API, you will need
+ either use `-sSINGLE_FILE` (to embed the Wasm file), or use a custom
+ `instantiateWasm` to supply the Wasm module yourself (e.g. via `postMessage`).
+
Development Overview
====================
diff --git a/site/source/docs/tools_reference/settings_reference.rst b/site/source/docs/tools_reference/settings_reference.rst
index 77871dc3d80af..5c9f3468d483e 100644
--- a/site/source/docs/tools_reference/settings_reference.rst
+++ b/site/source/docs/tools_reference/settings_reference.rst
@@ -991,6 +991,7 @@ are:
- 'webview' - just like web, but in a webview like Cordova; considered to be
same as "web" in almost every place
- 'worker' - a web worker environment.
+- 'worklet' - Audio Worklet environment.
- 'node' - Node.js.
- 'shell' - a JS shell like d8, js, or jsc.
@@ -998,6 +999,10 @@ This setting can be a comma-separated list of these environments, e.g.,
"web,worker". If this is the empty string, then all environments are
supported.
+Certain settings will automatically add to this list. For examble, building
+with pthreads will automatically add `worker` and building with
+``AUDIO_WORKLET`` will automatically add `worklet`.
+
Note that the set of environments recognized here is not identical to the
ones we identify at runtime using ``ENVIRONMENT_IS_*``. Specifically:
@@ -2502,6 +2507,11 @@ AUDIO_WORKLET
If true, enables targeting Wasm Web Audio AudioWorklets. Check out the
full documentation in site/source/docs/api_reference/wasm_audio_worklets.rst
+Note: The setting will implicitly add ``worklet`` to the :ref:`ENVIRONMENT`,
+(i.e. the resulting code and run in a worklet environment) but additionaly
+depends on ``WASM_WORKERS`` and Wasm SharedArrayBuffer to run new Audio
+Worklets.
+
Default value: 0
.. _audio_worklet_support_audio_params:
diff --git a/src/preamble.js b/src/preamble.js
index 1df464281ee21..f2221458c4081 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -443,6 +443,12 @@ function findWasmBinary() {
}
#endif
+#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET && !AUDIO_WORKLET // AUDIO_WORKLET handled above
+ if (ENVIRONMENT_IS_AUDIO_WORKLET) {
+ return '{{{ WASM_BINARY_FILE }}}';
+ }
+#endif
+
if (Module['locateFile']) {
return locateFile('{{{ WASM_BINARY_FILE }}}');
}
diff --git a/src/settings.js b/src/settings.js
index 1ba3b7856b8da..3b62f6b0c6650 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -646,6 +646,7 @@ var LEGACY_VM_SUPPORT = false;
// - 'webview' - just like web, but in a webview like Cordova; considered to be
// same as "web" in almost every place
// - 'worker' - a web worker environment.
+// - 'worklet' - Audio Worklet environment.
// - 'node' - Node.js.
// - 'shell' - a JS shell like d8, js, or jsc.
//
@@ -653,6 +654,10 @@ var LEGACY_VM_SUPPORT = false;
// "web,worker". If this is the empty string, then all environments are
// supported.
//
+// Certain settings will automatically add to this list. For examble, building
+// with pthreads will automatically add `worker` and building with
+// ``AUDIO_WORKLET`` will automatically add `worklet`.
+//
// Note that the set of environments recognized here is not identical to the
// ones we identify at runtime using ``ENVIRONMENT_IS_*``. Specifically:
//
@@ -1629,6 +1634,11 @@ var WASM_WORKERS = 0;
// If true, enables targeting Wasm Web Audio AudioWorklets. Check out the
// full documentation in site/source/docs/api_reference/wasm_audio_worklets.rst
+//
+// Note: The setting will implicitly add ``worklet`` to the :ref:`ENVIRONMENT`,
+// (i.e. the resulting code and run in a worklet environment) but additionaly
+// depends on ``WASM_WORKERS`` and Wasm SharedArrayBuffer to run new Audio
+// Worklets.
// [link]
var AUDIO_WORKLET = 0;
diff --git a/src/settings_internal.js b/src/settings_internal.js
index 0932bf6519db7..9f1f8ca63b5a5 100644
--- a/src/settings_internal.js
+++ b/src/settings_internal.js
@@ -140,6 +140,7 @@ var ENVIRONMENT_MAY_BE_WORKER = true;
var ENVIRONMENT_MAY_BE_NODE = true;
var ENVIRONMENT_MAY_BE_SHELL = true;
var ENVIRONMENT_MAY_BE_WEBVIEW = true;
+var ENVIRONMENT_MAY_BE_AUDIO_WORKLET = true;
// Whether to minify import and export names in the minify_wasm_js stage.
// Currently always off for MEMORY64.
diff --git a/src/shell.js b/src/shell.js
index 51d82c1f91c38..d96d1c47599cf 100644
--- a/src/shell.js
+++ b/src/shell.js
@@ -34,7 +34,7 @@ var Module = moduleArg;
var Module;
// if (!Module)` is crucial for Closure Compiler here as it will otherwise replace every `Module` occurrence with a string
if (!Module) /** @suppress{checkTypes}*/Module = {"__EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__":1};
-#elif AUDIO_WORKLET
+#elif ENVIRONMENT_MAY_BE_AUDIO_WORKLET
var Module = globalThis.Module || (typeof {{{ EXPORT_NAME }}} != 'undefined' ? {{{ EXPORT_NAME }}} : {});
#else
var Module = typeof {{{ EXPORT_NAME }}} != 'undefined' ? {{{ EXPORT_NAME }}} : {};
@@ -53,8 +53,11 @@ var Module = typeof {{{ EXPORT_NAME }}} != 'undefined' ? {{{ EXPORT_NAME }}} : {
var ENVIRONMENT_IS_WASM_WORKER = globalThis.name == 'em-ww';
#endif
-#if AUDIO_WORKLET
+#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET
var ENVIRONMENT_IS_AUDIO_WORKLET = !!globalThis.AudioWorkletGlobalScope;
+#endif
+
+#if AUDIO_WORKLET
// Audio worklets behave as wasm workers.
if (ENVIRONMENT_IS_AUDIO_WORKLET) ENVIRONMENT_IS_WASM_WORKER = true;
#endif
@@ -79,7 +82,7 @@ var ENVIRONMENT_IS_WORKER = !!globalThis.WorkerGlobalScope;
// N.b. Electron.js environment is simultaneously a NODE-environment, but
// also a web environment.
var ENVIRONMENT_IS_NODE = {{{ nodeDetectionCode() }}};
-#if AUDIO_WORKLET
+#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET
var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER && !ENVIRONMENT_IS_AUDIO_WORKLET;
#else
var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
@@ -352,7 +355,9 @@ if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
}
} else
#endif // ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
-#if AUDIO_WORKLET && ASSERTIONS
+#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET
+#endif
+#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET && ASSERTIONS
if (!ENVIRONMENT_IS_AUDIO_WORKLET)
#endif
{
@@ -401,7 +406,7 @@ if (ENVIRONMENT_IS_NODE) {
// if an assertion fails it cannot print the message
#if PTHREADS
assert(
-#if AUDIO_WORKLET
+#if ENVIRONMENT_MAY_BE_AUDIO_WORKLET
ENVIRONMENT_IS_AUDIO_WORKLET ||
#endif
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)');
diff --git a/test/test_browser.py b/test/test_browser.py
index a50d5b89d0386..041c5f38f1404 100644
--- a/test/test_browser.py
+++ b/test/test_browser.py
@@ -5478,6 +5478,44 @@ def test_audio_worklet_params_mixing(self, args):
def test_audio_worklet_emscripten_locks(self):
self.btest_exit('webaudio/audioworklet_emscripten_locks.c', cflags=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread'])
+ def test_audio_worklet_direct(self):
+ self.add_browser_reporting()
+ self.emcc('hello_world.c', ['-o', 'hello_world.mjs', '-sEXPORT_ES6', '-sSINGLE_FILE', '-sENVIRONMENT=worklet'])
+ create_file('worklet.mjs', '''
+ import Module from "./hello_world.mjs"
+ console.log("in worklet");
+ class MyProcessor extends AudioWorkletProcessor {
+ constructor() {
+ super();
+ Module().then(() => {
+ console.log("done Module constructor");
+ this.port.postMessage("ready");
+ });
+ }
+ process(inputs, outputs, parameters) {
+ return true;
+ }
+ }
+ registerProcessor('my-processor', MyProcessor);
+ console.log("done register");
+ ''')
+ create_file('test.html', '''
+
+
+ ''')
+ self.run_browser('test.html', '/report_result?ready')
+
# Verifies setting audio context sample rate, and that emscripten_audio_context_sample_rate() works.
@requires_sound_hardware
@also_with_minimal_runtime
diff --git a/tools/link.py b/tools/link.py
index ecb2df25adaa6..67de2fbf49c08 100644
--- a/tools/link.py
+++ b/tools/link.py
@@ -67,7 +67,7 @@
'__main_argc_argv',
]
-VALID_ENVIRONMENTS = ('web', 'webview', 'worker', 'node', 'shell')
+VALID_ENVIRONMENTS = {'web', 'webview', 'worker', 'node', 'shell', 'worklet'}
EXECUTABLE_EXTENSIONS = ['.wasm', '.html', '.js', '.mjs', '.out', '']
@@ -184,6 +184,9 @@ def setup_environment_settings():
if settings.SHARED_MEMORY and settings.ENVIRONMENT:
settings.ENVIRONMENT.append('worker')
+ if settings.AUDIO_WORKLET:
+ settings.ENVIRONMENT.append('worklet')
+
# Environment setting based on user input
if any(x for x in settings.ENVIRONMENT if x not in VALID_ENVIRONMENTS):
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():
settings.ENVIRONMENT_MAY_BE_NODE = not settings.ENVIRONMENT or 'node' in settings.ENVIRONMENT
settings.ENVIRONMENT_MAY_BE_SHELL = not settings.ENVIRONMENT or 'shell' in settings.ENVIRONMENT
settings.ENVIRONMENT_MAY_BE_WORKER = not settings.ENVIRONMENT or 'worker' in settings.ENVIRONMENT
+ settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET = not settings.ENVIRONMENT or 'worklet' in settings.ENVIRONMENT
if not settings.ENVIRONMENT_MAY_BE_NODE:
if 'MIN_NODE_VERSION' in user_settings and settings.MIN_NODE_VERSION != feature_matrix.UNSUPPORTED:
@@ -1175,7 +1179,7 @@ def limit_incoming_module_api():
# In Audio Worklets TextDecoder API is intentionally not exposed
# (https://github.com/WebAudio/web-audio-api/issues/2499) so we also need to
# keep the JavaScript-based fallback.
- if settings.SHRINK_LEVEL >= 2 and not settings.AUDIO_WORKLET and \
+ if settings.SHRINK_LEVEL >= 2 and not settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET and \
not settings.ENVIRONMENT_MAY_BE_SHELL:
default_setting('TEXTDECODER', 2)
@@ -2472,7 +2476,7 @@ def module_export_name_substitution():
logger.debug(f'Private module export name substitution with {settings.EXPORT_NAME}')
src = read_file(final_js)
final_js += '.module_export_name_substitution.js'
- if settings.MINIMAL_RUNTIME and not settings.ENVIRONMENT_MAY_BE_NODE and not settings.ENVIRONMENT_MAY_BE_SHELL and not settings.AUDIO_WORKLET:
+ 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:
# On the web, with MINIMAL_RUNTIME, the Module object is always provided
# via the shell html in order to provide the .asm.js/.wasm content.
replacement = settings.EXPORT_NAME