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