From 1620436f84e4b54e2f3d4215517351b5be9070ec Mon Sep 17 00:00:00 2001 From: DouglasDwyer Date: Wed, 2 Sep 2026 03:23:23 +0000 Subject: [PATCH] Address PR feedback and document Emscripten in README Enable the WebAudio host (and its wasm-bindgen/js-sys/web-sys deps) for `wasm32-unknown-emscripten`, gated on `any(target_os = "emscripten", target_os = "unknown")` so nothing wasm-bindgen-related is pulled in for `wasm32-wasip1`/`wasip2`. The AudioWorklet host stays `wasm32-unknown-unknown`-only. The three WebAudio JS callbacks keep using `Closure::wrap`. Dropping the `as Box` cast keeps the closures concrete, so their captures (all `UnwindSafe`) satisfy the `panic=unwind` bound on Emscripten without `wrap_aborting` -- a callback panic still surfaces as a JS exception rather than aborting the instance. Minimum `wasm-bindgen` stays at 0.2. README: document the `wasm32-unknown-emscripten` target (Emscripten 6.0.3, wasm-bindgen 0.2.127) and list it under the `wasm-bindgen` feature. Adds an Emscripten CI job. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/platforms.yml | 28 ++++++++++++++++++++++++++++ .github/workflows/quality.yml | 6 ++++++ CHANGELOG.md | 1 + Cargo.toml | 2 +- README.md | 5 +++-- src/host/mod.rs | 20 +++++++++++++------- src/host/webaudio/mod.rs | 14 +++++++------- src/lib.rs | 16 ++++++++-------- src/platform/mod.rs | 12 ++++++++---- src/sample_format.rs | 4 ++-- 10 files changed, 77 insertions(+), 31 deletions(-) diff --git a/.github/workflows/platforms.yml b/.github/workflows/platforms.yml index 23728974e..c35fd9f58 100644 --- a/.github/workflows/platforms.yml +++ b/.github/workflows/platforms.yml @@ -522,6 +522,34 @@ jobs: env: RUSTUP_TOOLCHAIN: nightly + # WebAssembly - Emscripten (WebAudio host) + wasm-emscripten: + runs-on: ubuntu-latest + env: + TARGET: wasm32-unknown-emscripten + steps: + - uses: actions/checkout@v5 + + - name: Install Rust MSRV (${{ env.MSRV_WASM }}) + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.MSRV_WASM }} + targets: ${{ env.TARGET }} + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + with: + key: wasm-emscripten + + - name: Check examples (default features) + run: cargo +${{ env.MSRV_WASM }} check --examples --workspace --verbose --target ${{ env.TARGET }} + + - name: Check examples (wasm-bindgen feature) + run: cargo +${{ env.MSRV_WASM }} check --examples --features wasm-bindgen --workspace --verbose --target ${{ env.TARGET }} + + - name: Check all features + run: cargo +${{ env.MSRV_WASM }} check --workspace --all-features --verbose --target ${{ env.TARGET }} + # WebAssembly - WASI Preview 1 wasm-wasip1: runs-on: ubuntu-latest diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index bcf359374..e33a7f97d 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -77,6 +77,12 @@ jobs: features: --features wasm-bindgen os: ubuntu-latest + # WASM - Emscripten (WebAudio host) + - target: wasm32-unknown-emscripten + name: WASM-emscripten + features: --features wasm-bindgen + os: ubuntu-latest + # WASM - WASI - target: wasm32-wasip1 name: WASI diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ef3f9d6f..4f3cc150a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **ALSA**: Update `alsa` dependency to 0.12. - **Linux**: `realtime` can now promote threads without requiring `realtime-dbus`. - **PipeWire**: Set `node.rate` property so that `default.clock.allowed-rates` PipeWire config works. +- **WebAudio**: the WebAudio host now works on `wasm32-unknown-emscripten` via [wasm-bindgen/Emscripten integration](https://github.com/wasm-bindgen/wasm-bindgen/issues/5237). ### Deprecated diff --git a/Cargo.toml b/Cargo.toml index 06d86479f..64b5414f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,7 +199,7 @@ objc2-avf-audio = { version = "0.3", default-features = false, features = [ "AVAudioSessionTypes", ] } -[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +[target.'cfg(all(target_arch = "wasm32", any(target_os = "emscripten", target_os = "unknown")))'.dependencies] wasm-bindgen = { version = "0.2", optional = true } wasm-bindgen-futures = { version = "0.4", optional = true } futures-channel = { version = "0.3", optional = true } diff --git a/README.md b/README.md index e7bf06599..bdfd41997 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,8 @@ The minimum Rust version (MSRV) and minimum operating system / runtime version b | PipeWire | Linux, BSD | 1.85 | PipeWire 0.3.53 | | PulseAudio | Linux, BSD | 1.88 | — | | WASAPI / ASIO | Windows | 1.85 | Windows 8 | -| WASM (`wasm32-unknown`) | WebAssembly | 1.85 | — | +| WASM (`wasm32-unknown-unknown`) | WebAssembly | 1.85 | — | +| WASM (`wasm32-unknown-emscripten`) | WebAssembly | 1.85 | Emscripten 6.0.3, wasm-bindgen 0.2.127 | | WASM (`wasm32-wasip1`) | WebAssembly | 1.85 | — | | WASM (`audioworklet`) | WebAssembly | nightly | — | @@ -70,7 +71,7 @@ The `audioworklet` backend additionally requires `-Zbuild-std` with atomics supp | `pulseaudio` | Linux, BSD | PulseAudio sound server backend. Requires `libpulse-dev` (Debian/Ubuntu) or `pulseaudio-libs-devel` (Fedora). | | `realtime` | Android, Linux, Windows | Raises the audio callback thread to real-time or high-priority scheduling for lower latency. On Linux, requires `CAP_SYS_NICE`, root, or an `rtprio` limit granted via `limits.conf` or systemd, unless `realtime-dbus` is also enabled. | | `realtime-dbus` | Linux | Uses `rtkit` via D-Bus for RT scheduling on Linux desktop systems. Implies `realtime` on all platforms. Requires `libdbus-1-dev` on Linux. | -| `wasm-bindgen` | WebAssembly (`wasm32-unknown-unknown`) | Web Audio API backend for browser-based audio; required for any WebAssembly audio support. See the `webaudio` example. | +| `wasm-bindgen` | WebAssembly (`wasm32-unknown-emscripten`, `wasm32-unknown-unknown`) | Web Audio API backend for browser-based audio; required for any WebAssembly audio support. See the `webaudio` example. | See the [beep example](examples/beep.rs) for selecting the backend at runtime. diff --git a/src/host/mod.rs b/src/host/mod.rs index b1df58e1d..0040e59bf 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -78,7 +78,7 @@ pub(crate) mod wasapi; #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] pub(crate) mod webaudio; @@ -96,7 +96,7 @@ pub(crate) mod custom; target_os = "android", all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ), )))] @@ -246,7 +246,13 @@ pub(crate) use error_emit::try_emit_error; target_os = "netbsd", target_os = "windows", target_vendor = "apple", - feature = "audioworklet", + all( + target_arch = "wasm32", + target_os = "unknown", + feature = "wasm-bindgen", + feature = "audioworklet", + target_feature = "atomics" + ), ))] #[inline] pub(crate) fn frames_to_duration( @@ -333,7 +339,7 @@ where /// #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] pub(crate) fn get_user_media_error(js_err: &wasm_bindgen::JsValue) -> crate::Error { @@ -366,7 +372,7 @@ pub(crate) fn get_user_media_error(js_err: &wasm_bindgen::JsValue) -> crate::Err /// #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] pub(crate) async fn request_microphone() -> Result { @@ -387,7 +393,7 @@ pub(crate) async fn request_microphone() -> Result bool { @@ -398,7 +404,7 @@ pub(crate) fn is_get_user_media_available() -> bool { /// browser's capture indicator. Dropping a WebAudio graph alone does not do this. #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] pub(crate) fn stop_tracks(media_stream: &web_sys::MediaStream) { diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index f8cc3d700..c704d1345 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -395,7 +395,7 @@ impl DeviceTrait for Device { #[cfg(target_feature = "atomics")] let current_time_bits_cb = current_time_bits.clone(); - let on_audio_process = Closure::wrap(Box::new(move |event: AudioProcessingEvent| { + let on_audio_process_fn = Box::new(move |event: AudioProcessingEvent| { let now = ctx_cb.current_time(); #[cfg(target_feature = "atomics")] current_time_bits_cb.store(now.to_bits(), Ordering::Relaxed); @@ -456,8 +456,8 @@ impl DeviceTrait for Device { ), ), } - }) - as Box); + }); + let on_audio_process = Closure::wrap(on_audio_process_fn); processor.set_onaudioprocess(Some(on_audio_process.as_ref().unchecked_ref())); @@ -876,7 +876,7 @@ impl DeviceTrait for Device { // Keep track of when the next buffer worth of samples should be played. *time_handle.write().unwrap() = time_at_start_of_buffer + buffer_time_step_secs; - }) as Box)); + }))); on_ended_closures.push(on_ended_closure); } @@ -1082,7 +1082,7 @@ impl DeviceTrait for Device { js_sys::Float32Array::new(&temporary_channel_array) }; - let on_audio_process = Closure::wrap(Box::new(move |event: AudioProcessingEvent| { + let on_audio_process_fn = Box::new(move |event: AudioProcessingEvent| { let now = ctx_cb.current_time(); #[cfg(target_feature = "atomics")] current_time_bits_cb.store(now.to_bits(), Ordering::Relaxed); @@ -1219,8 +1219,8 @@ impl DeviceTrait for Device { return; } } - }) - as Box); + }); + let on_audio_process = Closure::wrap(on_audio_process_fn); processor.set_onaudioprocess(Some(on_audio_process.as_ref().unchecked_ref())); diff --git a/src/lib.rs b/src/lib.rs index bebc78946..8b4a11679 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,19 +198,19 @@ // Extern crate declarations with `#[macro_use]` must unfortunately be at crate root. #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] extern crate js_sys; #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] extern crate wasm_bindgen; #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] extern crate web_sys; @@ -226,7 +226,7 @@ pub use platform::{ pub use sample_format::{FromSample, I24, Sample, SampleFormat, SizedSample, U24}; #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] use wasm_bindgen::prelude::*; @@ -426,7 +426,7 @@ pub enum BufferSize { #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] impl wasm_bindgen::describe::WasmDescribe for BufferSize { @@ -437,7 +437,7 @@ impl wasm_bindgen::describe::WasmDescribe for BufferSize { #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] impl wasm_bindgen::convert::IntoWasmAbi for BufferSize { @@ -454,7 +454,7 @@ impl wasm_bindgen::convert::IntoWasmAbi for BufferSize { #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] impl wasm_bindgen::convert::FromWasmAbi for BufferSize { @@ -476,7 +476,7 @@ impl wasm_bindgen::convert::FromWasmAbi for BufferSize { #[cfg_attr( all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ), wasm_bindgen diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 5b2d5aeb6..96da987a3 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -924,18 +924,22 @@ mod platform_impl { #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] mod platform_impl { - #[cfg(all(feature = "audioworklet", target_feature = "atomics"))] + #[cfg(all( + target_os = "unknown", + feature = "audioworklet", + target_feature = "atomics" + ))] use crate::host::audioworklet::Host as AudioWorkletHost; use crate::host::webaudio::Host as WebAudioHost; use crate::traits::HostTrait as _; impl_platform_host!( WebAudio => WebAudioHost, - #[cfg(all(feature = "audioworklet", target_feature = "atomics"))] AudioWorklet => AudioWorkletHost, + #[cfg(all(target_os = "unknown", feature = "audioworklet", target_feature = "atomics"))] AudioWorklet => AudioWorkletHost, #[cfg(feature = "custom")] Custom => super::CustomHost ); @@ -1004,7 +1008,7 @@ mod platform_impl { target_os = "android", all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ), )))] diff --git a/src/sample_format.rs b/src/sample_format.rs index fd1f0ceb1..5b388b2b2 100644 --- a/src/sample_format.rs +++ b/src/sample_format.rs @@ -30,7 +30,7 @@ pub use dasp_sample::U24; pub use dasp_sample::{FromSample, Sample}; #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ))] use wasm_bindgen::prelude::*; @@ -56,7 +56,7 @@ use wasm_bindgen::prelude::*; #[cfg_attr( all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "emscripten", target_os = "unknown"), feature = "wasm-bindgen" ), wasm_bindgen