From 7ff85233121428b5c7933880d378fc8aa5f17d48 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Wed, 9 Sep 2026 14:47:50 +0800 Subject: [PATCH] fix(audio): render native Web Audio graphs and analyser data Replace synthetic offline samples with band-limited oscillators, biquad filtering, lookahead compression, native parameter timelines and real analyser history/FFT. Preserve the module-only AudioWorklet control surface and enforce reentrant WebIDL boundaries. Vendor web-audio-api 1.7.0 with traceable quantum-boundary parameter and lazy HRTF initialization fixes; retain upstream and Blink notices. Close renderer-owned realtime contexts on collection or teardown. Validation: Chromium differential fixtures, 52 backend parameter tests, 43 audio tests across five stress iterations, cargo fmt --all, workspace all-target/all-feature Clippy, and 17,314 passing nextest tests. --- .gitignore | 4 + Cargo.lock | 815 +++- Cargo.toml | 3 +- licenses/Blink-WebAudio-NOTICE.txt | 33 + licenses/web-audio-api-MIT.txt | 24 + .../audio_worklet_wasm_source_phase.html | 13 + moli-core/tests/scripts.rs | 6 + moli-renderer-v8/Cargo.toml | 2 + .../context_bootstrap/web_audio_runtime.rs | 704 +--- .../web_audio_runtime/analyser.rs | 263 ++ .../web_audio_runtime/audio_param.rs | 158 +- .../web_audio_runtime/backend.rs | 467 +++ .../web_audio_runtime/biquad.rs | 171 +- .../web_audio_runtime/compressor.rs | 237 ++ .../web_audio_runtime/graph.rs | 190 +- .../web_audio_runtime/oscillator.rs | 78 + .../web_audio_runtime/oscillator_dsp.rs | 153 + .../web_audio_runtime/wavetable.rs | 141 + .../src/script_vm/tests/browser_api/media.rs | 11 +- .../script_vm/tests/browser_api/web_audio.rs | 127 +- .../fixtures/audio-graph-reentrancy-check.js | 35 + .../fixtures/offline-audio-analyser-check.js | 51 + .../offline-audio-boundaries-check.js | 102 + moli/tests/fetch_cli/eval.rs | 10 +- vendor/web-audio-api-1.7.0/Cargo.toml | 179 + vendor/web-audio-api-1.7.0/Cargo.toml.orig | 105 + vendor/web-audio-api-1.7.0/LICENSE | 21 + vendor/web-audio-api-1.7.0/MOLI_PATCHES.md | 26 + vendor/web-audio-api-1.7.0/README.md | 170 + .../resources/IRC_1003_C.bin | Bin 0 -> 772656 bytes vendor/web-audio-api-1.7.0/src/analysis.rs | 842 ++++ vendor/web-audio-api-1.7.0/src/buffer.rs | 817 ++++ vendor/web-audio-api-1.7.0/src/capacity.rs | 283 ++ .../web-audio-api-1.7.0/src/context/base.rs | 452 +++ .../src/context/concrete_base.rs | 620 +++ .../src/context/diagnostics.rs | 104 + vendor/web-audio-api-1.7.0/src/context/mod.rs | 188 + .../src/context/offline.rs | 653 +++ .../web-audio-api-1.7.0/src/context/online.rs | 1011 +++++ vendor/web-audio-api-1.7.0/src/decoding.rs | 310 ++ vendor/web-audio-api-1.7.0/src/events.rs | 264 ++ vendor/web-audio-api-1.7.0/src/io/cpal.rs | 831 ++++ vendor/web-audio-api-1.7.0/src/io/cubeb.rs | 701 ++++ .../web-audio-api-1.7.0/src/io/microphone.rs | 109 + vendor/web-audio-api-1.7.0/src/io/mod.rs | 301 ++ vendor/web-audio-api-1.7.0/src/io/none.rs | 217 + vendor/web-audio-api-1.7.0/src/lib.rs | 328 ++ .../src/media_devices/mod.rs | 250 ++ .../web-audio-api-1.7.0/src/media_element.rs | 223 + .../src/media_recorder/mod.rs | 448 +++ .../src/media_streams/mod.rs | 251 ++ vendor/web-audio-api-1.7.0/src/message.rs | 105 + .../web-audio-api-1.7.0/src/message_port.rs | 54 + .../web-audio-api-1.7.0/src/node/analyser.rs | 344 ++ .../src/node/audio_buffer_source.rs | 2075 ++++++++++ .../src/node/audio_node.rs | 466 +++ .../src/node/biquad_filter.rs | 1437 +++++++ .../src/node/channel_merger.rs | 272 ++ .../src/node/channel_splitter.rs | 283 ++ .../src/node/constant_source.rs | 378 ++ .../web-audio-api-1.7.0/src/node/convolver.rs | 992 +++++ vendor/web-audio-api-1.7.0/src/node/delay.rs | 1206 ++++++ .../src/node/destination.rs | 163 + .../src/node/dynamics_compressor.rs | 615 +++ vendor/web-audio-api-1.7.0/src/node/gain.rs | 218 + .../src/node/iir_filter.rs | 957 +++++ .../src/node/media_element_source.rs | 105 + .../src/node/media_stream_destination.rs | 159 + .../src/node/media_stream_source.rs | 78 + .../src/node/media_stream_track_source.rs | 107 + vendor/web-audio-api-1.7.0/src/node/mod.rs | 113 + .../src/node/oscillator.rs | 1459 +++++++ vendor/web-audio-api-1.7.0/src/node/panner.rs | 1351 +++++++ .../src/node/scheduled_source.rs | 339 ++ .../src/node/script_processor.rs | 396 ++ .../src/node/stereo_panner.rs | 553 +++ .../src/node/waveshaper.rs | 742 ++++ vendor/web-audio-api-1.7.0/src/param.rs | 3580 +++++++++++++++++ .../web-audio-api-1.7.0/src/periodic_wave.rs | 357 ++ .../web-audio-api-1.7.0/src/playback_stats.rs | 201 + .../web-audio-api-1.7.0/src/render/graph.rs | 1003 +++++ .../src/render/graph/test.rs | 386 ++ vendor/web-audio-api-1.7.0/src/render/mod.rs | 18 + .../src/render/node_collection.rs | 123 + .../src/render/processor.rs | 262 ++ .../web-audio-api-1.7.0/src/render/quantum.rs | 1566 +++++++ .../web-audio-api-1.7.0/src/render/thread.rs | 581 +++ vendor/web-audio-api-1.7.0/src/resampling.rs | 171 + vendor/web-audio-api-1.7.0/src/spatial.rs | 394 ++ vendor/web-audio-api-1.7.0/src/stats.rs | 217 + vendor/web-audio-api-1.7.0/src/worklet.rs | 731 ++++ 91 files changed, 37368 insertions(+), 691 deletions(-) create mode 100644 licenses/Blink-WebAudio-NOTICE.txt create mode 100644 licenses/web-audio-api-MIT.txt create mode 100644 moli-renderer-v8/src/context_bootstrap/web_audio_runtime/analyser.rs create mode 100644 moli-renderer-v8/src/context_bootstrap/web_audio_runtime/backend.rs create mode 100644 moli-renderer-v8/src/context_bootstrap/web_audio_runtime/compressor.rs create mode 100644 moli-renderer-v8/src/context_bootstrap/web_audio_runtime/oscillator.rs create mode 100644 moli-renderer-v8/src/context_bootstrap/web_audio_runtime/oscillator_dsp.rs create mode 100644 moli-renderer-v8/src/context_bootstrap/web_audio_runtime/wavetable.rs create mode 100644 moli-renderer-v8/tests/fixtures/audio-graph-reentrancy-check.js create mode 100644 moli-renderer-v8/tests/fixtures/offline-audio-analyser-check.js create mode 100644 moli-renderer-v8/tests/fixtures/offline-audio-boundaries-check.js create mode 100644 vendor/web-audio-api-1.7.0/Cargo.toml create mode 100644 vendor/web-audio-api-1.7.0/Cargo.toml.orig create mode 100644 vendor/web-audio-api-1.7.0/LICENSE create mode 100644 vendor/web-audio-api-1.7.0/MOLI_PATCHES.md create mode 100644 vendor/web-audio-api-1.7.0/README.md create mode 100644 vendor/web-audio-api-1.7.0/resources/IRC_1003_C.bin create mode 100644 vendor/web-audio-api-1.7.0/src/analysis.rs create mode 100644 vendor/web-audio-api-1.7.0/src/buffer.rs create mode 100644 vendor/web-audio-api-1.7.0/src/capacity.rs create mode 100644 vendor/web-audio-api-1.7.0/src/context/base.rs create mode 100644 vendor/web-audio-api-1.7.0/src/context/concrete_base.rs create mode 100644 vendor/web-audio-api-1.7.0/src/context/diagnostics.rs create mode 100644 vendor/web-audio-api-1.7.0/src/context/mod.rs create mode 100644 vendor/web-audio-api-1.7.0/src/context/offline.rs create mode 100644 vendor/web-audio-api-1.7.0/src/context/online.rs create mode 100644 vendor/web-audio-api-1.7.0/src/decoding.rs create mode 100644 vendor/web-audio-api-1.7.0/src/events.rs create mode 100644 vendor/web-audio-api-1.7.0/src/io/cpal.rs create mode 100644 vendor/web-audio-api-1.7.0/src/io/cubeb.rs create mode 100644 vendor/web-audio-api-1.7.0/src/io/microphone.rs create mode 100644 vendor/web-audio-api-1.7.0/src/io/mod.rs create mode 100644 vendor/web-audio-api-1.7.0/src/io/none.rs create mode 100644 vendor/web-audio-api-1.7.0/src/lib.rs create mode 100644 vendor/web-audio-api-1.7.0/src/media_devices/mod.rs create mode 100644 vendor/web-audio-api-1.7.0/src/media_element.rs create mode 100644 vendor/web-audio-api-1.7.0/src/media_recorder/mod.rs create mode 100644 vendor/web-audio-api-1.7.0/src/media_streams/mod.rs create mode 100644 vendor/web-audio-api-1.7.0/src/message.rs create mode 100644 vendor/web-audio-api-1.7.0/src/message_port.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/analyser.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/audio_buffer_source.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/audio_node.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/biquad_filter.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/channel_merger.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/channel_splitter.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/constant_source.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/convolver.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/delay.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/destination.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/dynamics_compressor.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/gain.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/iir_filter.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/media_element_source.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/media_stream_destination.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/media_stream_source.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/media_stream_track_source.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/mod.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/oscillator.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/panner.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/scheduled_source.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/script_processor.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/stereo_panner.rs create mode 100644 vendor/web-audio-api-1.7.0/src/node/waveshaper.rs create mode 100644 vendor/web-audio-api-1.7.0/src/param.rs create mode 100644 vendor/web-audio-api-1.7.0/src/periodic_wave.rs create mode 100644 vendor/web-audio-api-1.7.0/src/playback_stats.rs create mode 100644 vendor/web-audio-api-1.7.0/src/render/graph.rs create mode 100644 vendor/web-audio-api-1.7.0/src/render/graph/test.rs create mode 100644 vendor/web-audio-api-1.7.0/src/render/mod.rs create mode 100644 vendor/web-audio-api-1.7.0/src/render/node_collection.rs create mode 100644 vendor/web-audio-api-1.7.0/src/render/processor.rs create mode 100644 vendor/web-audio-api-1.7.0/src/render/quantum.rs create mode 100644 vendor/web-audio-api-1.7.0/src/render/thread.rs create mode 100644 vendor/web-audio-api-1.7.0/src/resampling.rs create mode 100644 vendor/web-audio-api-1.7.0/src/spatial.rs create mode 100644 vendor/web-audio-api-1.7.0/src/stats.rs create mode 100644 vendor/web-audio-api-1.7.0/src/worklet.rs diff --git a/.gitignore b/.gitignore index 984fa217d6..4812025436 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ !/vendor/deno_v8-0.3.0/** !/vendor/xml5ever-0.39.0/ !/vendor/xml5ever-0.39.0/** +!/vendor/web-audio-api-1.7.0/ +!/vendor/web-audio-api-1.7.0/** +/vendor/web-audio-api-1.7.0/target/ +/vendor/web-audio-api-1.7.0/Cargo.lock /.moli __pycache__ *.pyc diff --git a/Cargo.lock b/Cargo.lock index 9571b102fe..051f301bd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "almost" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aa2999eb46af81abb65c2d30d446778d7e613b60bbf4e174a027e80f90a3c14" + [[package]] name = "anstream" version = "1.0.0" @@ -140,6 +146,15 @@ dependencies = [ "serde", ] +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -164,6 +179,43 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" +[[package]] +name = "audio-core" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ebbf82d06013f4c41fe71303feb980cddd78496d904d06be627972de51a24" + +[[package]] +name = "audioadapter" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f87b70b051c5866680ad79f6743a42ccab264c009d1a71f4d33a3872ae60c8" +dependencies = [ + "audio-core", + "num-traits", +] + +[[package]] +name = "audioadapter-buffers" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9097d67933fb083d382ce980430afdb758aada60846010aee6be068c06cef0ca" +dependencies = [ + "audioadapter", + "audioadapter-sample", + "num-traits", +] + +[[package]] +name = "audioadapter-sample" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ab94f2bc04a14e1f49ee5f222f66460e8a1b51627bdfedf34eed394d747938" +dependencies = [ + "audio-core", + "num-traits", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -278,7 +330,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -298,7 +350,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -312,6 +364,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.0" @@ -399,6 +457,12 @@ dependencies = [ "cc", ] +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "bytemuck" version = "1.25.2" @@ -740,6 +804,47 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "creek" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3106d374fa70e8a7a8cf3abf6afbb7f3016e923a1e8941a3eee2b3a21583ee" +dependencies = [ + "creek-core", + "creek-decode-symphonia", + "creek-encode-wav", +] + +[[package]] +name = "creek-core" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8d299b2001167b9a51ac46458cbdcfafc42d6a59125a265d8c1de2e16b28a3" +dependencies = [ + "rtrb", +] + +[[package]] +name = "creek-decode-symphonia" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416063cb8f815ee27036075e544a778770b577e82348f017d145a704df90f246" +dependencies = [ + "creek-core", + "log", + "symphonia 0.5.5", +] + +[[package]] +name = "creek-encode-wav" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37cb9ea02c12a68eb40a0576086394b3129b297970612592222284ff060bbf31" +dependencies = [ + "byte-slice-cast", + "creek-core", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -970,6 +1075,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + [[package]] name = "data-encoding" version = "2.10.0" @@ -1251,6 +1362,12 @@ dependencies = [ "serde", ] +[[package]] +name = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + [[package]] name = "fastrand" version = "2.3.0" @@ -1282,6 +1399,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "fft-convolver" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb4fbed063c755ecaa4cd1356cd82cc48e9d5c7edd5c30cd144656833fc0661" +dependencies = [ + "realfft", + "rtsan-standalone", + "thiserror", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1305,6 +1433,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +[[package]] +name = "float_eq" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a80e3145d8ad11ba0995949bbcf48b9df2be62772b3d351ef017dff6ecb853" + [[package]] name = "fnv" version = "1.0.7" @@ -1417,6 +1551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1582,7 +1717,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9" dependencies = [ - "bitflags", + "bitflags 2.11.0", "bytemuck", "read-fonts", "smallvec", @@ -1635,6 +1770,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + +[[package]] +name = "hrtf" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1344e2123ddd443aceddc30d60a7fa78b9099121d57726e96611fb43f6d6b191" +dependencies = [ + "byteorder", + "rubato 3.0.0", + "rustfft", +] + [[package]] name = "html-escape" version = "0.2.13" @@ -2164,6 +2316,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.1" @@ -2176,6 +2334,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +[[package]] +name = "llq" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22edcb39a6fc7e511be7ba578a719af09125391ecde44e0bd61ad3c8e003a4a3" + [[package]] name = "lock_api" version = "0.4.14" @@ -2696,7 +2860,7 @@ dependencies = [ name = "moli-page-types" version = "0.1.0" dependencies = [ - "bitflags", + "bitflags 2.11.0", "http-auth", "moli-cookie-jar", "moli-dom", @@ -2936,6 +3100,7 @@ dependencies = [ "percent-encoding", "png", "precomputed-hash", + "realfft", "selectors", "serde", "serde_json", @@ -2952,6 +3117,7 @@ dependencies = [ "uuid", "v8", "wasm_dep_analyzer", + "web-audio-api", "widestring", "wuff", "xml5ever", @@ -3302,6 +3468,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "no_denormals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bcfe410abc339c9f8c226aceebf946bf26e3e2eca738be3fec9e9ee97d54aa" + [[package]] name = "nom" version = "7.1.3" @@ -3337,6 +3509,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -3406,7 +3587,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags", + "bitflags 2.11.0", ] [[package]] @@ -3415,7 +3596,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags", + "bitflags 2.11.0", "objc2-core-foundation", ] @@ -3431,7 +3612,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags", + "bitflags 2.11.0", "objc2", ] @@ -3453,7 +3634,7 @@ version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -3541,7 +3722,7 @@ version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381ae8356082431bd7e217dd78c7179bfc379dbbe7a32494e28be4fc678812c7" dependencies = [ - "bitflags", + "bitflags 2.11.0", "oxc_allocator", "oxc_ast_macros", "oxc_data_structures", @@ -3632,7 +3813,7 @@ version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71acdb67749ff68bfbbd346da7dd2fe4947964be49ac9ec34d73d10a2396dcd" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cow-utils", "memchr", "num-bigint", @@ -3656,7 +3837,7 @@ version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08a1273168ec6d8083e161565d264847249b9aad51c430d92d344303ede058b2" dependencies = [ - "bitflags", + "bitflags 2.11.0", "oxc_allocator", "oxc_ast_macros", "oxc_diagnostics", @@ -3699,7 +3880,7 @@ version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b448a086623714675f66b79271e25fa2b51708255fa6af7dad83be88cc6e8726" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cow-utils", "dragonbox_ecma", "nonmax", @@ -3875,6 +4056,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piston-float" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad78bf43dcf80e8f950c92b84f938a0fc7590b7f6866fbcbeca781609c115590" + [[package]] name = "pkcs8" version = "0.11.0" @@ -3897,7 +4084,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags", + "bitflags 2.11.0", "crc32fast", "fdeflate", "flate2", @@ -3955,6 +4142,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "primal-check" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +dependencies = [ + "num-integer", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -4082,13 +4278,22 @@ dependencies = [ "once_cell", ] +[[package]] +name = "realfft" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677" +dependencies = [ + "rustfft", +] + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.11.0", ] [[package]] @@ -4114,6 +4319,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.10" @@ -4159,6 +4370,70 @@ dependencies = [ "memchr", ] +[[package]] +name = "rtrb" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fae8ee26b0371a29a77d2b2d6b3ae13aa81def6f9bf1b1b92a32d279a5e709b7" + +[[package]] +name = "rtsan-standalone" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd1b6d61d69481a68e555916d3a52213846f5c2d2140bdc74ebc308e2338fc3" +dependencies = [ + "rtsan-standalone-macros", + "rtsan-standalone-sys", +] + +[[package]] +name = "rtsan-standalone-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8320af894782374141c8e0d4521ee613a8f24d7ab08b6ad359881311e37b9ef" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rtsan-standalone-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "362a9c531f731e574a870cdb28ee7d81178ba7a87ed54380eb9b8d8f0c3b5301" +dependencies = [ + "num_cpus", + "tempfile", +] + +[[package]] +name = "rubato" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5258099699851cfd0082aeb645feb9c084d9a5e1f1b8d5372086b989fc5e56a1" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "realfft", +] + +[[package]] +name = "rubato" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4be9c88e3d722d3d36939e41941f6b6f52810c1235bf19998a7d4535b402892" +dependencies = [ + "audioadapter", + "audioadapter-buffers", + "num-complex", + "num-integer", + "num-traits", + "realfft", + "visibility", + "windowfunctions", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -4180,19 +4455,46 @@ dependencies = [ "semver", ] +[[package]] +name = "rustfft" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", +] + [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.38" @@ -4299,7 +4601,7 @@ name = "selectors" version = "0.40.0" source = "git+https://github.com/ldm0/stylo?rev=671d13d31b3b00e7b1e0088ba2b36a4e35c0858c#671d13d31b3b00e7b1e0088ba2b36a4e35c0858c" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cssparser", "derive_more", "log", @@ -4603,6 +4905,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "strict-num" version = "0.1.1" @@ -4681,7 +4989,7 @@ dependencies = [ "app_units", "arrayvec", "atomic_refcell", - "bitflags", + "bitflags 2.11.0", "byteorder", "cssparser", "derive_more", @@ -4755,7 +5063,7 @@ name = "stylo_dom" version = "0.20.0" source = "git+https://github.com/ldm0/stylo?rev=671d13d31b3b00e7b1e0088ba2b36a4e35c0858c#671d13d31b3b00e7b1e0088ba2b36a4e35c0858c" dependencies = [ - "bitflags", + "bitflags 2.11.0", "stylo_malloc_size_of", ] @@ -4800,7 +5108,7 @@ version = "0.20.0" source = "git+https://github.com/ldm0/stylo?rev=671d13d31b3b00e7b1e0088ba2b36a4e35c0858c#671d13d31b3b00e7b1e0088ba2b36a4e35c0858c" dependencies = [ "app_units", - "bitflags", + "bitflags 2.11.0", "cssparser", "euclid", "malloc_size_of_derive", @@ -4831,6 +5139,387 @@ dependencies = [ "siphasher", ] +[[package]] +name = "symphonia" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac 0.5.5", + "symphonia-bundle-mp3 0.5.5", + "symphonia-codec-aac 0.5.5", + "symphonia-codec-adpcm 0.5.5", + "symphonia-codec-alac 0.5.5", + "symphonia-codec-pcm 0.5.5", + "symphonia-codec-vorbis 0.5.5", + "symphonia-core 0.5.5", + "symphonia-format-isomp4 0.5.5", + "symphonia-format-mkv 0.5.5", + "symphonia-format-ogg 0.5.5", + "symphonia-format-riff 0.5.5", + "symphonia-metadata 0.5.5", +] + +[[package]] +name = "symphonia" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7edef6a96b696d4e0cab5ee9ebb7ca155ed95f30a6b45bbb8b97d2727f02424" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac 0.6.1", + "symphonia-bundle-mp3 0.6.1", + "symphonia-codec-aac 0.6.1", + "symphonia-codec-adpcm 0.6.1", + "symphonia-codec-alac 0.6.1", + "symphonia-codec-pcm 0.6.1", + "symphonia-codec-vorbis 0.6.1", + "symphonia-core 0.6.1", + "symphonia-format-caf", + "symphonia-format-isomp4 0.6.1", + "symphonia-format-mkv 0.6.1", + "symphonia-format-ogg 0.6.1", + "symphonia-format-riff 0.6.1", + "symphonia-metadata 0.6.1", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976" +dependencies = [ + "log", + "symphonia-core 0.5.5", + "symphonia-metadata 0.5.5", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6405d5c34ff6f8f7ca08a4101efe66d21e180f7322ef9359900a0e55698a7892" +dependencies = [ + "log", + "symphonia-common", + "symphonia-core 0.6.1", + "symphonia-metadata 0.6.1", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" +dependencies = [ + "lazy_static", + "log", + "symphonia-core 0.5.5", + "symphonia-metadata 0.5.5", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ea5ffc8716bff677dfb3b01b420c7b758de901a72b8c330bf2040ab74b4add" +dependencies = [ + "lazy_static", + "log", + "symphonia-core 0.6.1", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790" +dependencies = [ + "lazy_static", + "log", + "symphonia-core 0.5.5", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5bf8e39552d34a3c4c98333370e62f48c92456d2e814f273b5c3ad7c4a5f45c" +dependencies = [ + "lazy_static", + "log", + "symphonia-common", + "symphonia-core 0.6.1", +] + +[[package]] +name = "symphonia-codec-adpcm" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dddc50e2bbea4cfe027441eece77c46b9f319748605ab8f3443350129ddd07f" +dependencies = [ + "log", + "symphonia-core 0.5.5", +] + +[[package]] +name = "symphonia-codec-adpcm" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445932ecb0c59362fde9c082dd63a75380650c5077fdb8b80b8cac850442a74c" +dependencies = [ + "log", + "symphonia-core 0.6.1", +] + +[[package]] +name = "symphonia-codec-alac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8413fa754942ac16a73634c9dfd1500ed5c61430956b33728567f667fdd393ab" +dependencies = [ + "log", + "symphonia-core 0.5.5", +] + +[[package]] +name = "symphonia-codec-alac" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920a78f96f3cf62932d0c497959c0cc2100ef36b03c7ca1417b260f432a482d8" +dependencies = [ + "log", + "symphonia-common", + "symphonia-core 0.6.1", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95" +dependencies = [ + "log", + "symphonia-core 0.5.5", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04ba75686acbe43542fdd374571195f0530c0b7785ca25cc6840e9c6c4b6eea" +dependencies = [ + "log", + "symphonia-core 0.6.1", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73" +dependencies = [ + "log", + "symphonia-core 0.5.5", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73d90b4fcf796137cc683c538282804ff9629f8ad9dbfd881fcbba331ac4e986" +dependencies = [ + "log", + "symphonia-common", + "symphonia-core 0.6.1", +] + +[[package]] +name = "symphonia-common" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acc3fcc18ec9b8cdd48614e259c4cf0d27b71d41e5d9b120b42c5adab12d7c4" +dependencies = [ + "log", + "symphonia-core 0.6.1", + "symphonia-metadata 0.6.1", +] + +[[package]] +name = "symphonia-core" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-core" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c412864d599d4750d0c3d684d7e093ec05e5309681ef5252cc1096a437f6e0" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "lazy_static", + "log", + "num-complex", + "rustfft", + "smallvec", +] + +[[package]] +name = "symphonia-format-caf" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab64327ee1920531c5bf86cf7e9cd1a599d57013ddc30691da62683cefc65191" +dependencies = [ + "log", + "symphonia-common", + "symphonia-core 0.6.1", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core 0.5.5", + "symphonia-metadata 0.5.5", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e681a70e1870d34e02abf1dbc51e4267c3f1827801474e8870be8c689fc4dc3" +dependencies = [ + "log", + "symphonia-common", + "symphonia-core 0.6.1", + "symphonia-metadata 0.6.1", +] + +[[package]] +name = "symphonia-format-mkv" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122d786d2c43a49beb6f397551b4a050d8229eaa54c7ddf9ee4b98899b8742d0" +dependencies = [ + "lazy_static", + "log", + "symphonia-core 0.5.5", + "symphonia-metadata 0.5.5", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-mkv" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d015c5c0558864665894b3f4cbd95e10abb01b9c868e751c72670f326a56360e" +dependencies = [ + "lazy_static", + "log", + "symphonia-common", + "symphonia-core 0.6.1", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb" +dependencies = [ + "log", + "symphonia-core 0.5.5", + "symphonia-metadata 0.5.5", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5495e7f7e3c7035328d82b6d6e377eef289bb0c4105bdeb557fc93a833f994" +dependencies = [ + "log", + "symphonia-common", + "symphonia-core 0.6.1", + "symphonia-metadata 0.6.1", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f" +dependencies = [ + "extended", + "log", + "symphonia-core 0.5.5", + "symphonia-metadata 0.5.5", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff70929083a8c1a5f6cd7c904b6071c7914ad04739b510c2f7239dfc9b7dabe" +dependencies = [ + "extended", + "log", + "symphonia-core 0.6.1", + "symphonia-metadata 0.6.1", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core 0.5.5", +] + +[[package]] +name = "symphonia-metadata" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83713a97705d77bdef7cdbc0768fd6e5a54e4cd7e48d60a806ae85639e2c87c6" +dependencies = [ + "lazy_static", + "log", + "regex-lite", + "smallvec", + "symphonia-core 0.6.1", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16" +dependencies = [ + "symphonia-core 0.5.5", + "symphonia-metadata 0.5.5", +] + [[package]] name = "syn" version = "2.0.117" @@ -4881,6 +5570,19 @@ dependencies = [ "smallvec", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + [[package]] name = "temporal_capi" version = "0.2.6" @@ -5311,6 +6013,16 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "tungstenite" version = "0.28.0" @@ -5484,7 +6196,7 @@ name = "v8" version = "152.2.0" dependencies = [ "bindgen 0.72.1", - "bitflags", + "bitflags 2.11.0", "cc", "fslock", "gzip-header", @@ -5502,7 +6214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3701ffb3692a24808503d17f758b0a3f688d2c451779247982566b5941033e63" dependencies = [ "bindgen 0.70.1", - "bitflags", + "bitflags 2.11.0", "cc", "paste", "temporal_capi", @@ -5522,6 +6234,15 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vecmath" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ae1e0d85bca567dee1dcf87fb1ca2e792792f66f87dced8381f99cd91156a" +dependencies = [ + "piston-float", +] + [[package]] name = "vello_common" version = "0.2.0" @@ -5594,6 +6315,17 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "void" version = "1.0.2" @@ -5643,6 +6375,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "web-audio-api" +version = "1.7.0" +dependencies = [ + "almost", + "arc-swap", + "arrayvec", + "creek", + "crossbeam-channel", + "dasp_sample", + "fft-convolver", + "float_eq", + "futures-channel", + "futures-core", + "futures-util", + "hound", + "hrtf", + "llq", + "log", + "no_denormals", + "num-complex", + "realfft", + "rubato 0.16.2", + "smallvec", + "symphonia 0.6.1", + "vecmath", +] + [[package]] name = "web_atoms" version = "0.2.3" @@ -5678,7 +6438,7 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "rustix", + "rustix 0.38.44", "winsafe", ] @@ -5728,6 +6488,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windowfunctions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90628d739333b7c5d2ee0b70210b97b8cddc38440c682c96fd9e2c24c2db5f3a" +dependencies = [ + "num-traits", +] + [[package]] name = "windows" version = "0.62.2" diff --git a/Cargo.toml b/Cargo.toml index 314dcfbccc..934fd5917d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,12 +82,13 @@ members = [ "moli-http-cache", "moli-wpt-compat" ] -exclude = ["vendor/xml5ever-0.39.0"] +exclude = ["vendor/xml5ever-0.39.0", "vendor/web-audio-api-1.7.0"] [workspace.package] license = "MIT OR Apache-2.0" [patch.crates-io] +web-audio-api = { path = "vendor/web-audio-api-1.7.0" } xml5ever = { path = "vendor/xml5ever-0.39.0" } curl = { git = "https://github.com/ldm0/curl-rust", branch = "moli" } curl-sys = { git = "https://github.com/ldm0/curl-rust", branch = "moli" } diff --git a/licenses/Blink-WebAudio-NOTICE.txt b/licenses/Blink-WebAudio-NOTICE.txt new file mode 100644 index 0000000000..e2bcfbd37d --- /dev/null +++ b/licenses/Blink-WebAudio-NOTICE.txt @@ -0,0 +1,33 @@ +Blink Web Audio algorithms used by Moli: + third_party/blink/renderer/platform/audio/dynamics_compressor.cc + third_party/blink/renderer/modules/webaudio/periodic_wave.cc + +Source: https://chromium.googlesource.com/chromium/src/ +The Rust ports are in moli-renderer-v8/src/context_bootstrap/web_audio_runtime/. + +Copyright (C) 2011, 2012 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +! Stateful look-ahead compressor, ported from Blink's +! `third_party/blink/renderer/platform/audio/dynamics_compressor.cc`. diff --git a/licenses/web-audio-api-MIT.txt b/licenses/web-audio-api-MIT.txt new file mode 100644 index 0000000000..d6e5087db2 --- /dev/null +++ b/licenses/web-audio-api-MIT.txt @@ -0,0 +1,24 @@ +web-audio-api 1.7.0 +https://github.com/orottier/web-audio-api-rs + +MIT License + +Copyright (c) 2021 Otto Rottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/moli-core/tests/fixtures/runtime/audio_worklet_wasm_source_phase.html b/moli-core/tests/fixtures/runtime/audio_worklet_wasm_source_phase.html index 2850296e00..a68ea936d8 100644 --- a/moli-core/tests/fixtures/runtime/audio_worklet_wasm_source_phase.html +++ b/moli-core/tests/fixtures/runtime/audio_worklet_wasm_source_phase.html @@ -5,6 +5,7 @@ globalThis.audioWorkletWasmDone = false; globalThis.audioWorkletWasmError = ""; globalThis.audioWorkletWasmResult = ""; + globalThis.audioWorkletWasmConnections = ""; try { const audioContext = new AudioContext(); @@ -25,6 +26,18 @@ ); }; audioNode.port.postMessage(""); + const connected = audioNode.connect(audioContext.destination); + audioNode.disconnect(audioContext.destination); + let disconnectedError = ""; + try { + audioNode.disconnect(audioContext.destination); + } catch (error) { + disconnectedError = error.name; + } + globalThis.audioWorkletWasmConnections = [ + connected === audioContext.destination, + disconnectedError + ].join("|"); audioNode.connect(audioContext.destination); } catch (error) { globalThis.audioWorkletWasmDone = true; diff --git a/moli-core/tests/scripts.rs b/moli-core/tests/scripts.rs index e03d6af4b0..6c88b71f01 100644 --- a/moli-core/tests/scripts.rs +++ b/moli-core/tests/scripts.rs @@ -5062,6 +5062,12 @@ async fn audio_worklet_module_imports_wasm_source_phase() -> Result<()> { diagnostic_global(&page, "audioWorkletWasmError"), Some(&JsValueSnapshot::String(String::new())) ); + assert_eq!( + diagnostic_global(&page, "audioWorkletWasmConnections"), + Some(&JsValueSnapshot::String( + "true|InvalidAccessError".to_owned() + )) + ); assert_eq!( diagnostic_global(&page, "audioWorkletWasmResult"), Some(&JsValueSnapshot::String( diff --git a/moli-renderer-v8/Cargo.toml b/moli-renderer-v8/Cargo.toml index d1def308a4..2372c2dbf6 100644 --- a/moli-renderer-v8/Cargo.toml +++ b/moli-renderer-v8/Cargo.toml @@ -28,6 +28,8 @@ flate2 = { version = "1.1.9", default-features = false, features = ["zlib"] } euclid = "0.22" kurbo = "0.13.1" futures-util = "0.3.31" +web-audio-api = { version = "=1.7.0", default-features = false, features = ["spec-compliant-worklet-inputs"] } +realfft = "3.5.0" precomputed-hash = "0.1" percent-encoding = "2.3" selectors = "0.40" diff --git a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime.rs b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime.rs index 991f6a4d7f..8f1c0a4859 100644 --- a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime.rs +++ b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime.rs @@ -12,13 +12,19 @@ use crate::util::{ use crate::webidl; use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject}; +mod analyser; mod audio_buffer; mod audio_param; +mod backend; mod biquad; +mod compressor; mod graph; +mod oscillator; +mod oscillator_dsp; +mod wavetable; pub(in crate::context_bootstrap) use audio_buffer::build_constructor_template as build_audio_buffer_constructor_template; -use audio_param::{audio_param, detune_param}; +use backend::{Node, NodeKind, State}; const AUDIO_CONTEXT_LISTENERS_SLOT: &str = "__moliAudioContextListeners"; const AUDIO_CONTEXT_MODULES_SLOT: &str = "__moliAudioContextModules"; @@ -32,20 +38,12 @@ const AUDIO_WORKLET_MODULE_RESOLVER_SLOT: &str = "__moliAudioWorkletModuleResolv const AUDIO_WORKLET_MODULE_LOADED_SLOT: &str = "__moliAudioWorkletModuleLoaded"; const AUDIO_WORKLET_MODULE_SETTLED_SLOT: &str = "__moliAudioWorkletModuleSettled"; const AUDIO_WORKLET_CALLBACK_MODULE_SLOT: &str = "__moliAudioWorkletCallbackModule"; -const OFFLINE_AUDIO_LENGTH_SLOT: &str = "__moliOfflineAudioLength"; -const OFFLINE_AUDIO_SAMPLE_RATE_SLOT: &str = "__moliOfflineAudioSampleRate"; -const OFFLINE_AUDIO_CHANNEL_COUNT_SLOT: &str = "__moliOfflineAudioChannelCount"; const OFFLINE_AUDIO_COMPLETE_CONTEXT_SLOT: &str = "__moliOfflineAudioCompleteContext"; -const OFFLINE_AUDIO_COMPLETE_BUFFER_SLOT: &str = "__moliOfflineAudioCompleteBuffer"; -const OFFLINE_AUDIO_COMPRESSORS_SLOT: &str = "__moliOfflineAudioCompressors"; -const DYNAMICS_COMPRESSOR_REDUCTION_SLOT: &str = "__moliDynamicsCompressorReduction"; -const ANALYSER_FFT_SIZE_SLOT: &str = "__moliAnalyserFftSize"; +const OFFLINE_AUDIO_COMPLETE_RESOLVER_SLOT: &str = "__moliOfflineAudioCompleteResolver"; #[derive(WebApiObject)] #[webapi(interface = "AudioContext")] struct AudioContextObjectDeclaration<'scope> { - #[webapi(data_property = "currentTime")] - current_time: f64, #[webapi(data_property = "sampleRate")] sample_rate: f64, #[webapi(data_property)] @@ -132,20 +130,10 @@ struct AudioWorkletProcessorConstructMessageDeclaration<'scope> { #[derive(WebApiObject)] #[webapi(interface = "OfflineAudioContext")] struct OfflineAudioContextObjectDeclaration<'scope> { - #[webapi(data_property = "currentTime")] - current_time: f64, #[webapi(data_property)] length: f64, #[webapi(data_property = "sampleRate")] sample_rate: f64, - #[webapi(slot = OFFLINE_AUDIO_LENGTH_SLOT)] - internal_length: f64, - #[webapi(slot = OFFLINE_AUDIO_SAMPLE_RATE_SLOT)] - internal_sample_rate: f64, - #[webapi(slot = OFFLINE_AUDIO_CHANNEL_COUNT_SLOT)] - channel_count: f64, - #[webapi(slot = OFFLINE_AUDIO_COMPRESSORS_SLOT)] - compressors: v8::Local<'scope, v8::Array>, #[webapi(slot = SIMPLE_EVENT_TARGET_SLOT)] event_target_slot: &'static str, #[webapi(data_property)] @@ -169,9 +157,7 @@ struct OfflineAudioContextObjectDeclaration<'scope> { #[derive(WebApiObject)] #[webapi(interface = "OscillatorNode")] struct OscillatorNodeObjectDeclaration<'scope> { - #[webapi(data_property = "type")] - kind: &'static str, - #[webapi(data_property)] + #[webapi(data_property, readonly)] frequency: v8::Local<'scope, v8::Object>, #[webapi(data_property, readonly)] detune: v8::Local<'scope, v8::Object>, @@ -186,18 +172,16 @@ struct OscillatorNodeObjectDeclaration<'scope> { #[derive(WebApiObject)] #[webapi(interface = "DynamicsCompressorNode")] struct DynamicsCompressorNodeObjectDeclaration<'scope> { - #[webapi(data_property)] + #[webapi(data_property, readonly)] threshold: v8::Local<'scope, v8::Object>, - #[webapi(data_property)] + #[webapi(data_property, readonly)] knee: v8::Local<'scope, v8::Object>, - #[webapi(data_property)] + #[webapi(data_property, readonly)] ratio: v8::Local<'scope, v8::Object>, - #[webapi(data_property)] + #[webapi(data_property, readonly)] attack: v8::Local<'scope, v8::Object>, - #[webapi(data_property)] + #[webapi(data_property, readonly)] release: v8::Local<'scope, v8::Object>, - #[webapi(slot = DYNAMICS_COMPRESSOR_REDUCTION_SLOT)] - reduction: f64, #[webapi(method, length = 1, callback = audio_node_connect_callback)] connect: (), #[webapi(method, length = 0, callback = audio_node_disconnect_callback)] @@ -211,47 +195,13 @@ struct DynamicsCompressorNodePrototypeDeclaration { reduction: (), } -#[derive(WebApiObject)] -#[webapi(interface = "AnalyserNode")] -struct AnalyserNodeObjectDeclaration { - #[webapi(slot = ANALYSER_FFT_SIZE_SLOT)] - fft_size: f64, - #[webapi(data_property = "minDecibels")] - min_decibels: f64, - #[webapi(data_property = "maxDecibels")] - max_decibels: f64, - #[webapi(data_property = "smoothingTimeConstant")] - smoothing_time_constant: f64, - #[webapi(method, length = 1, callback = audio_node_connect_callback)] - connect: (), - #[webapi(method, length = 0, callback = audio_node_disconnect_callback)] - disconnect: (), -} - -#[derive(WebApiFunctionTemplate)] -#[webapi(name = "AnalyserNode", enumerable)] -struct AnalyserNodePrototypeDeclaration { - #[webapi(accessor_property = "fftSize", getter = analyser_fft_size_getter_callback, setter = analyser_fft_size_setter_callback)] - fft_size: (), - #[webapi(accessor_property = "frequencyBinCount", getter = analyser_frequency_bin_count_getter_callback)] - frequency_bin_count: (), - #[webapi(method, length = 1, callback = analyser_get_float_frequency_data_callback)] - get_float_frequency_data: (), - #[webapi(method, length = 1, callback = analyser_get_float_time_domain_data_callback)] - get_float_time_domain_data: (), - #[webapi(method, length = 1, callback = analyser_get_byte_frequency_data_callback)] - get_byte_frequency_data: (), - #[webapi(method, length = 1, callback = analyser_get_byte_time_domain_data_callback)] - get_byte_time_domain_data: (), -} - #[derive(WebApiObject)] #[webapi(interface = "Object")] struct OfflineAudioCompletePayloadDeclaration<'scope> { #[webapi(slot = OFFLINE_AUDIO_COMPLETE_CONTEXT_SLOT)] context: v8::Local<'scope, v8::Object>, - #[webapi(slot = OFFLINE_AUDIO_COMPLETE_BUFFER_SLOT)] - rendered_buffer: v8::Local<'scope, v8::Object>, + #[webapi(slot = OFFLINE_AUDIO_COMPLETE_RESOLVER_SLOT)] + resolver: v8::Local<'scope, v8::Object>, } #[derive(WebApiObject)] @@ -267,85 +217,13 @@ struct OfflineAudioCompletionEventDeclaration<'scope> { #[webapi(interface = "AudioDestinationNode", allow_empty)] struct AudioDestinationNodeObjectDeclaration {} -// Captured from the Chromium-on-Linux baseline we use for Zhihu probe parity. -// If that browser profile changes, update this together with the audio probe -// assertions in `script_vm/tests.rs`. -const TARGET_AUDIO_FINGERPRINT_SUM: f64 = 124.04347527516074; -const SYNTHETIC_COMPRESSOR_REDUCTION: f64 = -82.26815795898438; -const SYNTHETIC_ANALYSER_FREQUENCY_BINS: &[f64] = &[ - -90.25955200195312, - -90.22233581542969, - -90.11856842041016, - -89.96821594238281, - -89.79446411132812, - -89.61327362060547, - -89.42459106445312, - -89.20698547363281, - -88.91907501220703, - -88.51146697998047, - -87.94953918457031, - -87.2354736328125, - -86.41204833984375, - -85.54572296142578, - -84.70391082763672, - -83.94123077392578, - -83.29612731933594, - -82.79357147216797, - -82.4489974975586, - -82.27188110351562, - -82.26815795898438, - -82.4417953491211, - -82.79552459716797, - -83.33106231689453, - -84.04853820800781, - -84.94512939453125, - -86.0123062133789, - -87.2312240600586, - -88.56591796875, - -89.95575714111328, - -91.31378936767578, - -92.54295349121094, - -93.5753173828125, - -94.40994262695312, - -95.10946655273438, - -95.75737762451172, - -96.4164810180664, - -97.1112060546875, - -97.82959747314453, - -98.53618621826172, - -99.19161987304688, - -99.77345275878906, - -100.28733825683594, - -100.76123046875, - -101.2282485961914, - -101.71035766601562, - -102.21070098876953, - -102.71531677246094, - -103.20209503173828, - -103.65292358398438, - -104.06330108642578, - -104.44380950927734, - -104.81304168701172, - -105.1873779296875, - -105.57337951660156, - -105.9659423828125, - -106.35223388671875, - -106.71916961669922, - -107.06056213378906, - -107.38026428222656, - -107.68936920166016, - -107.99988555908203, - -108.31855773925781, - -108.6440658569336, -]; - #[derive(webidl::WebIdlArgs)] #[webidl(prefix = "OfflineAudioContext")] struct OfflineAudioContextConstructorArgs { #[webidl(required)] - channel_count: f64, + channel_count: u32, #[webidl(required)] - length: f64, + length: u32, #[webidl(required)] sample_rate: f64, } @@ -359,13 +237,6 @@ struct AudioParamSetValueAtTimeArgs { start_time: f64, } -#[derive(webidl::WebIdlArgs)] -#[webidl(prefix = "AnalyserNode.fftSize")] -struct AnalyserFftSizeArgs { - #[webidl(required)] - value: u32, -} - #[derive(WebApiFunctionTemplate)] #[webapi( name = "AudioContext", @@ -395,6 +266,9 @@ struct AudioWorkletNodeTemplateDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(name = "BaseAudioContext", enumerable)] struct BaseAudioContextPrototypeDeclaration { + #[webapi(accessor_property, getter = audio_context_current_time)] + current_time: (), + #[webapi(method = "createBuffer", length = 3, callback = audio_buffer::create_buffer)] create_buffer: (), @@ -463,6 +337,7 @@ pub(in crate::context_bootstrap) fn install_web_audio_template_bindings<'s>( } match interface_name { "AudioParam" => audio_param::install(scope, template), + "OscillatorNode" => oscillator::install(scope, template), "BiquadFilterNode" => biquad::install(scope, template), "BaseAudioContext" => { BaseAudioContextPrototypeDeclaration::initialize_prototype_template( @@ -476,12 +351,8 @@ pub(in crate::context_bootstrap) fn install_web_audio_template_bindings<'s>( template.prototype_template(scope), ); } - "AnalyserNode" => { - AnalyserNodePrototypeDeclaration::initialize_prototype_template( - scope, - template.prototype_template(scope), - ); - } + "AnalyserNode" => analyser::install(scope, template), + "DynamicsCompressorNode" => { DynamicsCompressorNodePrototypeDeclaration::initialize_prototype_template( scope, @@ -506,6 +377,7 @@ fn audio_context_constructor_callback<'s>( } let context = args.this(); + backend::initialize(scope, context, State::Context(backend::Context::realtime())); let destination = audio_destination_node(scope, context); let modules = new_web_audio_map_object(scope); let module_list = v8::Array::new(scope, 0); @@ -516,7 +388,6 @@ fn audio_context_constructor_callback<'s>( set_symbol_to_string_tag(scope, audio_worklet, "AudioWorklet"); AudioContextObjectDeclaration::new( - 0.0, 44_100.0, "running", destination, @@ -537,12 +408,35 @@ pub(in crate::context_bootstrap) fn is_audio_context_object<'s>( get_private_value(scope, object, AUDIO_CONTEXT_MODULES_SLOT).is_some() } +fn audio_context_current_time<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s, v8::Value>, +) { + if let Some(state) = backend::get(scope, args.this()) + && let State::Context(context) = &*state.borrow() + { + rv.set(v8::Number::new(scope, context.current_time()).into()); + return; + } + throw_type_error(scope, "Illegal invocation: expected a BaseAudioContext."); +} + fn audio_context_close_callback<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s, v8::Value>, ) { let context = args.this(); + if !is_audio_context_object(scope, context) { + throw_type_error(scope, "Illegal invocation: expected an AudioContext."); + return; + } + if let Some(state) = backend::get(scope, context) + && let State::Context(backend::Context::Realtime(native)) = &*state.borrow() + { + native.close_sync(); + } if let Some(module_list) = web_audio_array_slot(scope, context, AUDIO_CONTEXT_MODULE_LIST_SLOT) { let length = module_list @@ -841,6 +735,7 @@ fn audio_worklet_node_constructor_callback<'s>( AudioWorkletNodeObjectDeclaration::new(context, port1) .initialize(scope, node) .expect("AudioWorkletNode declaration should initialize object"); + backend::initialize(scope, node, State::ModuleWorklet); graph::initialize_node(scope, node, context); if let Some(worker) = web_audio_object_slot(scope, module_state, AUDIO_WORKLET_MODULE_WORKER_SLOT) @@ -1207,9 +1102,17 @@ fn dynamics_compressor_reduction_getter_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s, v8::Value>, ) { - let value = web_audio_number_slot(scope, args.this(), DYNAMICS_COMPRESSOR_REDUCTION_SLOT) - .unwrap_or(0.0); - rv.set(v8::Number::new(scope, value).into()); + if let Some(state) = backend::get(scope, args.this()) + && let State::Node(Node::Compressor { reduction, .. }) = &*state.borrow() + { + let value = f32::from_bits(reduction.load(std::sync::atomic::Ordering::Relaxed)); + rv.set(v8::Number::new(scope, f64::from(value)).into()); + return; + } + throw_type_error( + scope, + "Illegal invocation: expected a DynamicsCompressorNode.", + ); } pub(in crate::context_bootstrap) fn offline_audio_context_constructor_callback<'s>( @@ -1230,34 +1133,43 @@ pub(in crate::context_bootstrap) fn offline_audio_context_constructor_callback<' return; }; - let channel_count = parsed.channel_count.trunc(); - let length = parsed.length.trunc(); - let sample_rate = parsed.sample_rate; - if !channel_count.is_finite() - || !length.is_finite() - || !sample_rate.is_finite() - || channel_count <= 0.0 - || length <= 0.0 - || sample_rate <= 0.0 - { + let channel_count = parsed.channel_count; + let length = parsed.length; + let sample_rate = f64::from(parsed.sample_rate as f32); + if !sample_rate.is_finite() { throw_type_error( scope, + "OfflineAudioContext sampleRate must be a finite float.", + ); + return; + } + if !(1..=32).contains(&channel_count) + || !(1..=i32::MAX as u32).contains(&length) + || !(3000.0..=768000.0).contains(&sample_rate) + { + throw_dom_exception( + scope, + "NotSupportedError", + 9, "Failed to construct 'OfflineAudioContext': invalid channel count, length, or sample rate.", ); return; } let context = args.this(); + backend::initialize( + scope, + context, + State::Context(backend::Context::offline( + channel_count as usize, + length as usize, + sample_rate as f32, + )), + ); let destination = audio_destination_node(scope, context); - let compressors = v8::Array::new(scope, 0); OfflineAudioContextObjectDeclaration::new( - 0.0, - length, + f64::from(length), sample_rate, - length, - sample_rate, - channel_count, - compressors, OFFLINE_AUDIO_LISTENERS_SLOT, "suspended", destination, @@ -1271,8 +1183,8 @@ fn require_base_audio_context<'s>( scope: &mut v8::PinScope<'s, '_>, object: v8::Local<'s, v8::Object>, ) -> bool { - if is_audio_context_object(scope, object) - || get_private_value(scope, object, OFFLINE_AUDIO_LENGTH_SLOT).is_some() + if backend::get(scope, object) + .is_some_and(|state| matches!(&*state.borrow(), State::Context(_))) { return true; } @@ -1288,14 +1200,15 @@ fn audio_context_create_oscillator_callback<'s>( if !require_base_audio_context(scope, args.this()) { return; } - let nyquist = audio_context_sample_rate(scope, args.this()) / 2.0; - let frequency = audio_param(scope, 440.0, -nyquist, nyquist); - let detune = detune_param(scope); - let node = OscillatorNodeObjectDeclaration::new("sine", frequency, detune) + let native = backend::create_node(scope, args.this(), NodeKind::Oscillator); + let frequency = audio_param::wrap(scope, native.param("frequency")); + let detune = audio_param::wrap(scope, native.param("detune")); + let node = OscillatorNodeObjectDeclaration::new(frequency, detune) .bind(scope) .expect("OscillatorNode declaration should bind"); graph::initialize_node(scope, node, args.this()); graph::initialize_source(scope, node); + backend::initialize(scope, node, State::Node(native)); rv.set(node.into()); } @@ -1307,17 +1220,18 @@ fn audio_context_create_dynamics_compressor_callback<'s>( if !require_base_audio_context(scope, args.this()) { return; } - let threshold = audio_param(scope, -24.0, -100.0, 0.0); - let knee = audio_param(scope, 30.0, 0.0, 40.0); - let ratio = audio_param(scope, 12.0, 1.0, 20.0); - let attack = audio_param(scope, 0.003, 0.0, 1.0); - let release = audio_param(scope, 0.25, 0.0, 1.0); + let native = backend::create_node(scope, args.this(), NodeKind::Compressor); + let threshold = audio_param::wrap(scope, native.param("threshold")); + let knee = audio_param::wrap(scope, native.param("knee")); + let ratio = audio_param::wrap(scope, native.param("ratio")); + let attack = audio_param::wrap(scope, native.param("attack")); + let release = audio_param::wrap(scope, native.param("release")); let node = - DynamicsCompressorNodeObjectDeclaration::new(threshold, knee, ratio, attack, release, 0.0) + DynamicsCompressorNodeObjectDeclaration::new(threshold, knee, ratio, attack, release) .bind(scope) .expect("DynamicsCompressorNode declaration should bind"); graph::initialize_node(scope, node, args.this()); - remember_context_compressor(scope, args.this(), node); + backend::initialize(scope, node, State::Node(native)); rv.set(node.into()); } @@ -1329,10 +1243,7 @@ fn audio_context_create_analyser_callback<'s>( if !require_base_audio_context(scope, args.this()) { return; } - let node = AnalyserNodeObjectDeclaration::new(2048.0, -100.0, -30.0, 0.8) - .bind(scope) - .expect("AnalyserNode declaration should bind"); - graph::initialize_node(scope, node, args.this()); + let node = analyser::create(scope, args.this()); rv.set(node.into()); } @@ -1342,38 +1253,50 @@ fn offline_audio_context_start_rendering_callback<'s>( mut rv: v8::ReturnValue<'s, v8::Value>, ) { let context = args.this(); - let length = web_audio_number_slot(scope, context, OFFLINE_AUDIO_LENGTH_SLOT) - .and_then(float_to_usize) - .unwrap_or(44_100); - let sample_rate = - web_audio_number_slot(scope, context, OFFLINE_AUDIO_SAMPLE_RATE_SLOT).unwrap_or(44_100.0); - let has_input = graph::prepare_offline_render(scope, context, length as f64 / sample_rate); - let channel_count = web_audio_number_slot(scope, context, OFFLINE_AUDIO_CHANNEL_COUNT_SLOT) - .unwrap_or(1.0) as u32; - let Some(rendered_buffer) = - build_audio_buffer(scope, channel_count, length, sample_rate, has_input) + let Some(state) = backend::get(scope, context) else { + throw_type_error( + scope, + "Illegal invocation: expected an OfflineAudioContext.", + ); + return; + }; + { + let mut state = state.borrow_mut(); + let State::Context(backend::Context::Offline { rendered, .. }) = &mut *state else { + throw_type_error( + scope, + "Illegal invocation: expected an OfflineAudioContext.", + ); + return; + }; + if *rendered { + let error = new_dom_exception_value( + scope, + "Offline rendering has already started.", + "InvalidStateError", + ); + set_rejected_promise_return(scope, &mut rv, error); + return; + } + *rendered = true; + } + let Some(resolver) = v8::PromiseResolver::new(scope) else { + return; + }; + let payload = OfflineAudioCompletePayloadDeclaration::new(context, resolver.into()) + .bind(scope) + .expect("OfflineAudio payload should bind"); + let Some(callback) = v8::Function::builder(offline_audio_context_complete_microtask_callback) + .data(payload.into()) + .build(scope) else { return; }; - define_non_enumerable_string_property(scope, context, "state", "closed"); - - let payload = OfflineAudioCompletePayloadDeclaration::new(context, rendered_buffer) - .bind(scope) - .expect("OfflineAudio complete payload declaration should bind"); - if let Some(callback) = v8::Function::builder(offline_audio_context_complete_microtask_callback) - .data(payload.into()) - .build(scope) - { - scope.enqueue_microtask(callback); - } - - if let Some(resolver) = v8::PromiseResolver::new(scope) { - let promise = resolver.get_promise(scope); - let _ = resolver.resolve(scope, rendered_buffer.into()); - rv.set(promise.into()); - return; - } - rv.set(v8::undefined(scope).into()); + // Defer work and observable results until after the initiating JS call. + // Both PCM and analyser history are produced by the same native graph. + scope.enqueue_microtask(callback); + define_non_enumerable_string_property(scope, context, "state", "running"); + rv.set(resolver.get_promise(scope).into()); } fn offline_audio_context_complete_microtask_callback<'s>( @@ -1388,16 +1311,45 @@ fn offline_audio_context_complete_microtask_callback<'s>( else { return; }; - let Some(rendered_buffer) = - web_audio_object_slot(scope, payload, OFFLINE_AUDIO_COMPLETE_BUFFER_SLOT) + let Some(resolver) = + web_audio_object_slot(scope, payload, OFFLINE_AUDIO_COMPLETE_RESOLVER_SLOT) else { return; }; - mark_context_compressors_rendered(scope, context); - - let event = OfflineAudioCompletionEventDeclaration::new("complete", rendered_buffer) + // Only this callback's native payload can provide this private slot. + let resolver = unsafe { v8::Local::::cast_unchecked(resolver) }; + let state = backend::get(scope, context).expect("rendering context must retain its backend"); + let rendered = { + let mut state = state.borrow_mut(); + let State::Context(backend::Context::Offline { context, .. }) = &mut *state else { + unreachable!() + }; + context.start_rendering_sync() + }; + let Some(buffer) = audio_buffer::new_buffer( + scope, + rendered.number_of_channels() as u32, + rendered.length() as u32, + f64::from(rendered.sample_rate()), + ) else { + return; + }; + for channel in 0..rendered.number_of_channels() { + if audio_buffer::write_channel( + scope, + buffer, + channel as u32, + rendered.get_channel_data(channel), + ) + .is_none() + { + return; + } + } + define_non_enumerable_string_property(scope, context, "state", "closed"); + let event = OfflineAudioCompletionEventDeclaration::new("complete", buffer) .bind(scope) - .expect("OfflineAudio completion event declaration should bind"); + .expect("OfflineAudio completion event should bind"); let _ = dispatch_simple_event_target_event( scope, context, @@ -1405,50 +1357,7 @@ fn offline_audio_context_complete_microtask_callback<'s>( "complete", event, ); -} - -fn remember_context_compressor<'s>( - scope: &mut v8::PinScope<'s, '_>, - context: v8::Local<'s, v8::Object>, - node: v8::Local<'s, v8::Object>, -) { - let Some(compressors) = web_audio_object_slot(scope, context, OFFLINE_AUDIO_COMPRESSORS_SLOT) - else { - return; - }; - let length = compressors - .get(scope, v8str(scope, "length").into()) - .and_then(|value| value.uint32_value(scope)) - .unwrap_or(0); - let _ = compressors.set_index(scope, length, node.into()); -} - -fn mark_context_compressors_rendered<'s>( - scope: &mut v8::PinScope<'s, '_>, - context: v8::Local<'s, v8::Object>, -) { - let Some(compressors) = web_audio_object_slot(scope, context, OFFLINE_AUDIO_COMPRESSORS_SLOT) - else { - return; - }; - let length = compressors - .get(scope, v8str(scope, "length").into()) - .and_then(|value| value.uint32_value(scope)) - .unwrap_or(0); - for index in 0..length { - let Some(value) = compressors.get_index(scope, index) else { - continue; - }; - let Ok(node) = v8::Local::::try_from(value) else { - continue; - }; - let reduction = if graph::rendered_with_input(scope, node) { - SYNTHETIC_COMPRESSOR_REDUCTION - } else { - 0.0 - }; - set_web_audio_number_slot(scope, node, DYNAMICS_COMPRESSOR_REDUCTION_SLOT, reduction); - } + let _ = resolver.resolve(scope, buffer.into()); } fn audio_node_connect_callback<'s>( @@ -1469,173 +1378,6 @@ fn audio_node_disconnect_callback<'s>( graph::disconnect(scope, &args); } -fn analyser_get_float_frequency_data_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - _rv: v8::ReturnValue<'s, v8::Value>, -) { - copy_analyser_data(scope, &args, AnalyserData::FloatFrequency); -} - -fn analyser_get_float_time_domain_data_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - _rv: v8::ReturnValue<'s, v8::Value>, -) { - copy_analyser_data(scope, &args, AnalyserData::FloatTimeDomain); -} - -fn analyser_get_byte_frequency_data_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - _rv: v8::ReturnValue<'s, v8::Value>, -) { - copy_analyser_data(scope, &args, AnalyserData::ByteFrequency); -} - -fn analyser_get_byte_time_domain_data_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - _rv: v8::ReturnValue<'s, v8::Value>, -) { - copy_analyser_data(scope, &args, AnalyserData::ByteTimeDomain); -} - -fn analyser_fft_size<'s>( - scope: &mut v8::PinScope<'s, '_>, - object: v8::Local<'s, v8::Object>, -) -> Option { - let Some(size) = web_audio_number_slot(scope, object, ANALYSER_FFT_SIZE_SLOT) else { - throw_type_error(scope, "Illegal invocation: expected an AnalyserNode."); - return None; - }; - Some(size as u32) -} - -fn analyser_fft_size_getter_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - mut rv: v8::ReturnValue<'s, v8::Value>, -) { - if let Some(size) = analyser_fft_size(scope, args.this()) { - rv.set_uint32(size); - } -} - -fn analyser_fft_size_setter_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - _rv: v8::ReturnValue<'s, v8::Value>, -) { - if analyser_fft_size(scope, args.this()).is_none() { - return; - } - let Some(parsed) = webidl::parse_args::(scope, &args) else { - return; - }; - if !(32..=32768).contains(&parsed.value) || !parsed.value.is_power_of_two() { - throw_dom_exception( - scope, - "IndexSizeError", - 1, - "AnalyserNode.fftSize must be a power of two between 32 and 32768.", - ); - return; - } - set_web_audio_number_slot( - scope, - args.this(), - ANALYSER_FFT_SIZE_SLOT, - parsed.value as f64, - ); -} - -fn analyser_frequency_bin_count_getter_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - mut rv: v8::ReturnValue<'s, v8::Value>, -) { - if let Some(size) = analyser_fft_size(scope, args.this()) { - rv.set_uint32(size / 2); - } -} - -enum AnalyserData { - FloatFrequency, - FloatTimeDomain, - ByteFrequency, - ByteTimeDomain, -} - -fn copy_analyser_data<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: &v8::FunctionCallbackArguments<'s>, - kind: AnalyserData, -) { - let Some(fft_size) = analyser_fft_size(scope, args.this()) else { - return; - }; - let array: Option> = match kind { - AnalyserData::FloatFrequency | AnalyserData::FloatTimeDomain => { - v8::Local::::try_from(args.get(0)) - .ok() - .map(Into::into) - } - AnalyserData::ByteFrequency | AnalyserData::ByteTimeDomain => { - v8::Local::::try_from(args.get(0)) - .ok() - .map(Into::into) - } - }; - let Some(array) = array else { - let message = match kind { - AnalyserData::FloatFrequency | AnalyserData::FloatTimeDomain => { - "AnalyserNode data argument must be a Float32Array." - } - AnalyserData::ByteFrequency | AnalyserData::ByteTimeDomain => { - "AnalyserNode data argument must be a Uint8Array." - } - }; - throw_type_error(scope, message); - return; - }; - if let Some(store) = array.get_backing_store() - && (store.is_shared() || store.is_resizable_by_user_javascript()) - { - throw_type_error( - scope, - "AnalyserNode data requires a non-shared, fixed-length buffer.", - ); - return; - } - let source_length = match kind { - AnalyserData::FloatFrequency | AnalyserData::ByteFrequency => fft_size / 2, - AnalyserData::FloatTimeDomain | AnalyserData::ByteTimeDomain => fft_size, - }; - // Use the intrinsic view length, not a user-overridable JS property. The - // validated fftSize bounds the work, and excess destination entries stay intact. - let length = array.length().min(source_length as usize); - let has_input = graph::rendered_with_input(scope, args.this()); - for index in 0..length { - // The existing connected-input profile is still synthetic, not an FFT. - // Never expose it before rendering or for a graph with no started source. - let fill = match kind { - AnalyserData::FloatFrequency if !has_input => f64::NEG_INFINITY, - AnalyserData::FloatFrequency => SYNTHETIC_ANALYSER_FREQUENCY_BINS - .get(index) - .or_else(|| SYNTHETIC_ANALYSER_FREQUENCY_BINS.last()) - .copied() - .unwrap_or(-100.0), - AnalyserData::FloatTimeDomain | AnalyserData::ByteFrequency => 0.0, - AnalyserData::ByteTimeDomain => 128.0, - }; - let value = v8::Number::new(scope, fill); - if array.set_index(scope, index as u32, value.into()).is_none() { - return; - } - } -} - fn oscillator_start_callback<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, @@ -1644,29 +1386,6 @@ fn oscillator_start_callback<'s>( graph::start_source(scope, &args); } -fn audio_param_set_value_at_time_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - mut rv: v8::ReturnValue<'s, v8::Value>, -) { - let param = args.this(); - let Some(parsed) = webidl::parse_args::(scope, &args) else { - return; - }; - let _ = parsed.start_time; - define_non_enumerable_number_property(scope, param, "value", parsed.value); - rv.set(param.into()); -} - -fn audio_context_sample_rate<'s>( - scope: &mut v8::PinScope<'s, '_>, - context: v8::Local<'s, v8::Object>, -) -> f64 { - // Realtime contexts currently have a fixed 44.1 kHz sample rate. Do not - // consult an overridable JS property when constructing native parameters. - web_audio_number_slot(scope, context, OFFLINE_AUDIO_SAMPLE_RATE_SLOT).unwrap_or(44_100.0) -} - fn web_audio_number_slot<'s>( scope: &mut v8::PinScope<'s, '_>, object: v8::Local<'s, v8::Object>, @@ -1712,68 +1431,7 @@ fn audio_destination_node<'s>( .expect("AudioDestinationNode declaration should bind"); graph::initialize_node(scope, node, context); graph::set_destination(scope, context, node); + let native = backend::create_node(scope, context, NodeKind::Destination); + backend::initialize(scope, node, State::Node(native)); node } - -fn build_audio_buffer<'s>( - scope: &mut v8::PinScope<'s, '_>, - channel_count: u32, - length: usize, - sample_rate: f64, - has_input: bool, -) -> Option> { - let Ok(length) = u32::try_from(length) else { - throw_dom_exception( - scope, - "NotSupportedError", - 9, - "AudioBuffer length is too large.", - ); - return None; - }; - let buffer = audio_buffer::new_buffer(scope, channel_count, length, sample_rate)?; - if has_input { - // The existing input renderer remains synthetic. Buffer ownership and - // channel copies are independent of that backend and use actual PCM. - let samples = synthetic_audio_samples(length as usize); - for channel in 0..channel_count { - audio_buffer::write_channel(scope, buffer, channel, &samples)?; - } - } - Some(buffer) -} - -fn synthetic_audio_samples(length: usize) -> Vec { - let mut samples = vec![0.0_f32; length]; - let (start, end) = if length >= 5_000 { - (4_500, 5_000) - } else { - (0, length) - }; - let window_len = end.saturating_sub(start); - if window_len == 0 { - return samples; - } - - let mut weights = Vec::with_capacity(window_len); - for index in 0..window_len { - let x = index as f64; - let weight = (x * 0.137).sin().abs() + 0.31 * (x * 0.053).cos().abs() + 0.07; - weights.push(weight); - } - let total = weights.iter().sum::().max(f64::EPSILON); - let target_sum = TARGET_AUDIO_FINGERPRINT_SUM * (window_len as f64 / 500.0).min(1.0); - let scale = target_sum / total; - for (index, weight) in weights.into_iter().enumerate() { - let signed = if index % 2 == 0 { weight } else { -weight }; - samples[start + index] = (signed * scale) as f32; - } - samples -} - -fn float_to_usize(value: f64) -> Option { - if !value.is_finite() || value < 0.0 || value.fract() != 0.0 { - return None; - } - usize::try_from(value as u64).ok() -} diff --git a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/analyser.rs b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/analyser.rs new file mode 100644 index 0000000000..ed0c26f3be --- /dev/null +++ b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/analyser.rs @@ -0,0 +1,263 @@ +//! Analyser views are copies of native graph history, never fabricated samples. +use super::*; +use web_audio_api::node::AnalyserNode; + +#[derive(WebApiObject)] +#[webapi(interface = "AnalyserNode")] +struct AnalyserObject { + #[webapi(method, length = 1, callback = audio_node_connect_callback)] + connect: (), + #[webapi(method, length = 0, callback = audio_node_disconnect_callback)] + disconnect: (), +} + +#[derive(WebApiFunctionTemplate)] +#[webapi(name = "AnalyserNode", enumerable)] +struct AnalyserPrototype { + #[webapi(accessor_property = "fftSize", getter = fft_size, setter = set_fft_size)] + fft_size: (), + #[webapi(accessor_property = "frequencyBinCount", getter = frequency_bin_count)] + frequency_bin_count: (), + #[webapi(accessor_property, getter = min_decibels, setter = set_min_decibels)] + min_decibels: (), + #[webapi(accessor_property, getter = max_decibels, setter = set_max_decibels)] + max_decibels: (), + #[webapi(accessor_property, getter = smoothing_time_constant, setter = set_smoothing_time_constant)] + smoothing_time_constant: (), + #[webapi(method, length = 1, callback = get_float_frequency_data)] + get_float_frequency_data: (), + #[webapi(method, length = 1, callback = get_float_time_domain_data)] + get_float_time_domain_data: (), + #[webapi(method, length = 1, callback = get_byte_frequency_data)] + get_byte_frequency_data: (), + #[webapi(method, length = 1, callback = get_byte_time_domain_data)] + get_byte_time_domain_data: (), +} + +pub(super) fn install<'s>( + scope: &mut v8::PinScope<'s, '_, ()>, + template: v8::Local<'s, v8::FunctionTemplate>, +) { + AnalyserPrototype::initialize_prototype_template(scope, template.prototype_template(scope)); +} + +pub(super) fn create<'s>( + scope: &mut v8::PinScope<'s, '_>, + context: v8::Local<'s, v8::Object>, +) -> v8::Local<'s, v8::Object> { + let native = backend::create_node(scope, context, NodeKind::Analyser); + let object = AnalyserObject::new() + .bind(scope) + .expect("AnalyserNode should bind"); + graph::initialize_node(scope, object, context); + backend::initialize(scope, object, State::Node(native)); + object +} + +fn with_analyser<'s, T>( + scope: &mut v8::PinScope<'s, '_>, + object: v8::Local<'s, v8::Object>, + operation: impl FnOnce(&mut AnalyserNode) -> T, +) -> Option { + if let Some(state) = backend::get(scope, object) + && let State::Node(Node::Analyser(node)) = &mut *state.borrow_mut() + { + return Some(operation(node)); + } + throw_type_error(scope, "Illegal invocation: expected an AnalyserNode."); + None +} + +fn fft_size<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s, v8::Value>, +) { + if let Some(size) = with_analyser(scope, args.this(), |node| node.fft_size()) { + rv.set_uint32(size as u32); + } +} + +fn frequency_bin_count<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s, v8::Value>, +) { + if let Some(size) = with_analyser(scope, args.this(), |node| node.frequency_bin_count()) { + rv.set_uint32(size as u32); + } +} + +fn set_fft_size<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s, v8::Value>, +) { + if with_analyser(scope, args.this(), |_| ()).is_none() { + return; + } + let Some(size) = args.get(0).uint32_value(scope) else { + return; + }; + if !(32..=32768).contains(&size) || !size.is_power_of_two() { + throw_dom_exception( + scope, + "IndexSizeError", + 1, + "AnalyserNode.fftSize must be a power of two between 32 and 32768.", + ); + return; + } + with_analyser(scope, args.this(), |node| node.set_fft_size(size as usize)); +} + +macro_rules! configuration { + ($get:ident, $set:ident, $valid:expr) => { + fn $get<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s, v8::Value>, + ) { + if let Some(value) = with_analyser(scope, args.this(), |node| node.$get()) { + rv.set(v8::Number::new(scope, value).into()); + } + } + fn $set<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s, v8::Value>, + ) { + if with_analyser(scope, args.this(), |_| ()).is_none() { + return; + } + let Some(value) = args.get(0).number_value(scope) else { + return; + }; + if !value.is_finite() { + throw_type_error(scope, "AnalyserNode configuration must be finite."); + return; + } + let valid = with_analyser(scope, args.this(), |node| { + if !($valid)(node, value) { + return false; + } + node.$set(value); + true + }); + if valid == Some(false) { + throw_dom_exception( + scope, + "IndexSizeError", + 1, + "AnalyserNode configuration is out of range.", + ); + } + } + }; +} +configuration!( + min_decibels, + set_min_decibels, + |node: &AnalyserNode, value| value < node.max_decibels() +); +configuration!( + max_decibels, + set_max_decibels, + |node: &AnalyserNode, value| value > node.min_decibels() +); +configuration!( + smoothing_time_constant, + set_smoothing_time_constant, + |_: &AnalyserNode, value| (0.0..=1.0).contains(&value) +); + +enum Data { + FloatFrequency, + FloatTime, + ByteFrequency, + ByteTime, +} +macro_rules! data_method { + ($name:ident, $kind:ident) => { + fn $name<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s, v8::Value>, + ) { + copy_data(scope, &args, Data::$kind); + } + }; +} +data_method!(get_float_frequency_data, FloatFrequency); +data_method!(get_float_time_domain_data, FloatTime); +data_method!(get_byte_frequency_data, ByteFrequency); +data_method!(get_byte_time_domain_data, ByteTime); + +fn copy_data<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: &v8::FunctionCallbackArguments<'s>, + kind: Data, +) { + let Some(size) = with_analyser(scope, args.this(), |node| node.fft_size()) else { + return; + }; + let array: Option> = match kind { + Data::FloatFrequency | Data::FloatTime => { + v8::Local::::try_from(args.get(0)) + .ok() + .map(Into::into) + } + Data::ByteFrequency | Data::ByteTime => v8::Local::::try_from(args.get(0)) + .ok() + .map(Into::into), + }; + let Some(array) = array else { + throw_type_error( + scope, + "AnalyserNode requires the matching Float32Array or Uint8Array.", + ); + return; + }; + if let Some(store) = array.get_backing_store() + && (store.is_shared() || store.is_resizable_by_user_javascript()) + { + throw_type_error( + scope, + "AnalyserNode data requires a non-shared, fixed-length buffer.", + ); + return; + } + let count = match kind { + Data::FloatFrequency | Data::ByteFrequency => size / 2, + _ => size, + }; + let count = count.min(array.length()); + let Some(values) = with_analyser(scope, args.this(), |node| match kind { + Data::FloatFrequency | Data::FloatTime => { + let mut values = vec![0.0_f32; count]; + match kind { + Data::FloatFrequency => node.get_float_frequency_data(&mut values), + _ => node.get_float_time_domain_data(&mut values), + } + values.into_iter().map(f64::from).collect::>() + } + Data::ByteFrequency | Data::ByteTime => { + let mut values = vec![0_u8; count]; + match kind { + Data::ByteFrequency => node.get_byte_frequency_data(&mut values), + _ => node.get_byte_time_domain_data(&mut values), + } + values.into_iter().map(f64::from).collect::>() + } + }) else { + return; + }; + // Numeric indexed writes to a validated fixed typed array cannot invoke + // page getters. The intrinsic length respects subarrays and detached views. + for (index, value) in values.into_iter().enumerate() { + let value = v8::Number::new(scope, value); + if array.set_index(scope, index as u32, value.into()).is_none() { + return; + } + } +} diff --git a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/audio_param.rs b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/audio_param.rs index 5e860b9d3f..6cdd1572a7 100644 --- a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/audio_param.rs +++ b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/audio_param.rs @@ -1,27 +1,21 @@ use super::*; -const DEFAULT_VALUE: &str = "__moliAudioParamDefaultValue"; -const MIN_VALUE: &str = "__moliAudioParamMinValue"; -const MAX_VALUE: &str = "__moliAudioParamMaxValue"; - +// The wrapper retains the actual graph parameter. Public property overrides do +// not replace the value consumed by the audio render thread. #[derive(WebApiObject)] #[webapi(interface = "AudioParam")] struct AudioParamObjectDeclaration { - #[webapi(data_property)] - value: f64, - #[webapi(slot = DEFAULT_VALUE)] - default_value: f64, - #[webapi(slot = MIN_VALUE)] - min_value: f64, - #[webapi(slot = MAX_VALUE)] - max_value: f64, - #[webapi(method, length = 2, callback = audio_param_set_value_at_time_callback)] + #[webapi(method, length = 2, callback = set_value_at_time)] set_value_at_time: (), } #[derive(WebApiFunctionTemplate)] #[webapi(name = "AudioParam", enumerable)] struct AudioParamPrototypeDeclaration { + #[webapi(accessor_property, getter = automation_rate, setter = set_automation_rate)] + automation_rate: (), + #[webapi(accessor_property, getter = value, setter = set_value)] + value: (), #[webapi(accessor_property, getter = default_value)] default_value: (), #[webapi(accessor_property, getter = min_value)] @@ -40,56 +34,146 @@ pub(super) fn install<'s>( ); } -pub(super) fn audio_param<'s>( +pub(super) fn wrap<'s>( scope: &mut v8::PinScope<'s, '_>, - value: f64, - min: f64, - max: f64, + param: backend::Parameter, ) -> v8::Local<'s, v8::Object> { - let value = value as f32 as f64; - AudioParamObjectDeclaration::new(value, value, min as f32 as f64, max as f32 as f64) + let object = AudioParamObjectDeclaration::new() .bind(scope) - .expect("AudioParam declaration should bind") + .expect("AudioParam declaration should bind"); + backend::initialize(scope, object, backend::State::Param(param)); + object } -pub(super) fn detune_param<'s>(scope: &mut v8::PinScope<'s, '_>) -> v8::Local<'s, v8::Object> { - let limit = (1200.0_f32 * f32::MAX.log2()) as f64; - audio_param(scope, 0.0, -limit, limit) -} - -fn read_metadata<'s>( +fn automation_rate<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s, v8::Value>, - slot: &'static str, ) { - let Some(value) = web_audio_number_slot(scope, args.this(), slot) else { - throw_type_error(scope, "Illegal invocation: expected an AudioParam."); - return; - }; - rv.set(v8::Number::new(scope, value).into()); + if let Some(param) = backend::param(scope, args.this()) { + let value = match param.automation_rate() { + web_audio_api::AutomationRate::A => "a-rate", + web_audio_api::AutomationRate::K => "k-rate", + }; + rv.set(v8str(scope, value).into()); + } } +fn set_automation_rate<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s, v8::Value>, +) { + let Some(param) = backend::param(scope, args.this()) else { + return; + }; + let Some(value) = args.get(0).to_string(scope) else { + return; + }; + let value = value.to_rust_string_lossy(scope); + let rate = match value.as_str() { + "a-rate" => web_audio_api::AutomationRate::A, + "k-rate" => web_audio_api::AutomationRate::K, + _ => { + throw_type_error(scope, "Invalid AutomationRate."); + return; + } + }; + if !param.set_automation_rate(rate) { + throw_dom_exception( + scope, + "InvalidStateError", + 11, + "This AudioParam has a fixed automation rate.", + ); + } +} + +fn read<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s, v8::Value>, + get: fn(&backend::Parameter) -> f32, +) { + if let Some(param) = backend::param(scope, args.this()) { + rv.set(v8::Number::new(scope, f64::from(get(¶m))).into()); + } +} + +fn value<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'s, v8::Value>, +) { + read(scope, args, rv, backend::Parameter::value); +} fn default_value<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, rv: v8::ReturnValue<'s, v8::Value>, ) { - read_metadata(scope, args, rv, DEFAULT_VALUE); + read(scope, args, rv, backend::Parameter::default_value); } - fn min_value<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, rv: v8::ReturnValue<'s, v8::Value>, ) { - read_metadata(scope, args, rv, MIN_VALUE); + read(scope, args, rv, backend::Parameter::min_value); } - fn max_value<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, rv: v8::ReturnValue<'s, v8::Value>, ) { - read_metadata(scope, args, rv, MAX_VALUE); + read(scope, args, rv, backend::Parameter::max_value); +} + +fn finite_float<'s>( + scope: &mut v8::PinScope<'s, '_>, + value: v8::Local<'s, v8::Value>, +) -> Option { + let value = value.number_value(scope)? as f32; + if !value.is_finite() { + throw_type_error(scope, "AudioParam value must be a finite float."); + return None; + } + Some(value) +} + +fn set_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s, v8::Value>, +) { + let Some(param) = backend::param(scope, args.this()) else { + return; + }; + let Some(value) = finite_float(scope, args.get(0)) else { + return; + }; + param.set_value(value); +} + +fn set_value_at_time<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s, v8::Value>, +) { + let Some(param) = backend::param(scope, args.this()) else { + return; + }; + let Some(parsed) = webidl::parse_args::(scope, &args) else { + return; + }; + if !(parsed.value as f32).is_finite() || !parsed.start_time.is_finite() { + throw_type_error(scope, "AudioParam value and time must be finite."); + return; + } + if parsed.start_time < 0.0 { + throw_range_error(scope, "AudioParam start time must not be negative."); + return; + } + param.set_value_at_time(parsed.value as f32, parsed.start_time); + rv.set(args.this().into()); } diff --git a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/backend.rs b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/backend.rs new file mode 100644 index 0000000000..a5f66e9e48 --- /dev/null +++ b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/backend.rs @@ -0,0 +1,467 @@ +//! Native Web Audio state. The JS wrapper, not a page-global strong root, owns +//! its lifetime. GC reclaims abandoned nodes/contexts; isolate drop also drains +//! the store. No raw pointers or JavaScript-visible state IDs are exposed. + +use crate::util::{get_private_value, set_private_value}; +use std::{ + cell::RefCell, + collections::HashMap, + rc::Rc, + sync::{ + Arc, + atomic::{AtomicBool, AtomicU32, Ordering}, + }, +}; +use web_audio_api::context::{ + AudioContext, AudioContextOptions, BaseAudioContext, OfflineAudioContext, +}; +use web_audio_api::node::{AnalyserNode, AudioDestinationNode, AudioNode, BiquadFilterNode}; +use web_audio_api::worklet::{ + AudioParamValues, AudioWorkletGlobalScope, AudioWorkletNode, AudioWorkletNodeOptions, + AudioWorkletProcessor, +}; +use web_audio_api::{AudioParam, AudioParamDescriptor, AutomationRate}; + +const SLOT: &str = "__moliWebAudioBackend"; +type Store = Rc>; +pub(super) type StateRef = Rc>; + +#[derive(Default)] +struct States { + next_id: u64, + entries: HashMap, StateRef)>, +} + +pub(super) enum State { + Context(Context), + Node(Node), + Param(Parameter), + // Existing JS worklets load modules and exchange messages, but do not yet + // run process() on the audio thread. Preserve their control graph without + // inventing a silent or pass-through native processor. + ModuleWorklet, +} + +/// The nominal range can depend on the context's sample rate, whereas an +/// AudioWorklet's descriptors are static. Keep that immutable range alongside +/// its actual timeline; the oscillator also applies it at a-rate on the DSP side. +#[derive(Clone)] +pub(super) struct Parameter { + native: AudioParam, + minimum: f32, + maximum: f32, + fixed_rate: Option, +} + +impl Parameter { + pub(super) fn new(native: AudioParam) -> Self { + let minimum = native.min_value(); + let maximum = native.max_value(); + Self { + native, + minimum, + maximum, + fixed_rate: None, + } + } + pub(super) fn with_range(mut self, minimum: f32, maximum: f32) -> Self { + self.minimum = minimum; + self.maximum = maximum; + self + } + pub(super) fn automation_rate(&self) -> AutomationRate { + self.fixed_rate + .unwrap_or_else(|| self.native.automation_rate()) + } + pub(super) fn set_automation_rate(&self, rate: AutomationRate) -> bool { + if self.fixed_rate.is_some() { + return false; + } + self.native.set_automation_rate(rate); + true + } + pub(super) fn value(&self) -> f32 { + self.native.value().clamp(self.minimum, self.maximum) + } + pub(super) fn default_value(&self) -> f32 { + self.native.default_value() + } + pub(super) fn min_value(&self) -> f32 { + self.minimum + } + pub(super) fn max_value(&self) -> f32 { + self.maximum + } + pub(super) fn set_value(&self, value: f32) { + self.native.set_value(value); + } + pub(super) fn set_value_at_time(&self, value: f32, when: f64) { + self.native.set_value_at_time(value, when); + } +} + +pub(super) enum Context { + Realtime(Box), + Offline { + context: Box, + rendered: bool, + }, +} + +impl Drop for Context { + fn drop(&mut self) { + if let Self::Realtime(context) = self { + // The upstream AudioContext intentionally keeps playback alive on + // drop. A collected wrapper or disposed document must instead stop + // its headless audio thread, including contexts never explicitly + // closed by page code. + context.close_sync(); + } + } +} + +#[derive(Clone, Copy)] +pub(super) enum NodeKind { + Destination, + Oscillator, + Compressor, + Biquad, + Analyser, +} + +pub(super) enum Node { + Destination(AudioDestinationNode), + Oscillator(super::oscillator_dsp::Oscillator), + Compressor { + node: AudioWorkletNode, + reduction: Arc, + demanded: Arc, + }, + Biquad(BiquadFilterNode), + Analyser(Box), +} + +impl Context { + pub(super) fn realtime() -> Self { + // Headless processing must not open the host's speakers or microphone. + Self::Realtime(Box::new(AudioContext::new(AudioContextOptions { + sample_rate: Some(44_100.0), + sink_id: "none".into(), + ..Default::default() + }))) + } + + pub(super) fn offline(channels: usize, length: usize, sample_rate: f32) -> Self { + Self::Offline { + context: Box::new(OfflineAudioContext::new(channels, length, sample_rate)), + rendered: false, + } + } + + pub(super) fn create_node(&self, kind: NodeKind) -> Node { + match self { + Self::Realtime(context) => Node::create(context.as_ref(), kind), + Self::Offline { context, .. } => Node::create(context.as_ref(), kind), + } + } + + pub(super) fn current_time(&self) -> f64 { + match self { + Self::Realtime(context) => context.current_time(), + Self::Offline { context, .. } => context.current_time(), + } + } +} + +impl Node { + fn create(context: &C, kind: NodeKind) -> Self { + match kind { + NodeKind::Destination => Self::Destination(context.destination()), + NodeKind::Oscillator => { + Self::Oscillator(super::oscillator_dsp::Oscillator::new(context)) + } + NodeKind::Biquad => Self::Biquad(context.create_biquad_filter()), + NodeKind::Analyser => Self::Analyser(Box::new(context.create_analyser())), + NodeKind::Compressor => { + let reduction = Arc::new(AtomicU32::new(0)); + let demanded = Arc::new(AtomicBool::new(false)); + let node = AudioWorkletNode::new::( + context, + AudioWorkletNodeOptions { + processor_options: ( + context.sample_rate(), + Arc::clone(&reduction), + Arc::clone(&demanded), + ), + output_channel_count: vec![2], + ..Default::default() + }, + ); + Self::Compressor { + node, + reduction, + demanded, + } + } + } + } + + pub(super) fn audio_node(&self) -> &dyn AudioNode { + match self { + Self::Destination(node) => node, + Self::Oscillator(node) => &node.node, + Self::Compressor { node, .. } => node, + Self::Biquad(node) => node, + Self::Analyser(node) => node.as_ref(), + } + } + + pub(super) fn param(&self, name: &str) -> Parameter { + let native = match self { + Self::Oscillator(node) => match name { + "frequency" => node.frequency().clone(), + "detune" => node.detune().clone(), + _ => unreachable!("unknown oscillator parameter"), + }, + Self::Compressor { node, .. } => node.parameters()[name].clone(), + Self::Biquad(node) => match name { + "frequency" => node.frequency().clone(), + "detune" => node.detune().clone(), + "Q" => node.q().clone(), + "gain" => node.gain().clone(), + _ => unreachable!("unknown biquad parameter"), + }, + _ => unreachable!("node has no parameters"), + }; + let mut parameter = Parameter::new(native); + match self { + Self::Oscillator(node) if name == "frequency" => { + let nyquist = node.node.context().sample_rate() / 2.0; + parameter = parameter.with_range(-nyquist, nyquist); + } + Self::Compressor { .. } => parameter.fixed_rate = Some(AutomationRate::K), + _ => {} + } + parameter + } +} + +struct CompressorProcessor { + dsp: super::compressor::Compressor, + reduction: Arc, + demanded: Arc, + sample_rate: f32, + tail_frames: usize, +} + +impl AudioWorkletProcessor for CompressorProcessor { + type ProcessorOptions = (f32, Arc, Arc); + + fn constructor((sample_rate, reduction, demanded): Self::ProcessorOptions) -> Self { + Self { + dsp: super::compressor::Compressor::new(sample_rate), + reduction, + demanded, + sample_rate, + tail_frames: 0, + } + } + + fn parameter_descriptors() -> Vec { + [ + ("threshold", -24.0, -100.0, 0.0), + ("knee", 30.0, 0.0, 40.0), + ("ratio", 12.0, 1.0, 20.0), + ("attack", 0.003, 0.0, 1.0), + ("release", 0.25, 0.0, 1.0), + ] + .into_iter() + .map( + |(name, default_value, min_value, max_value)| AudioParamDescriptor { + name: name.into(), + default_value, + min_value, + max_value, + automation_rate: AutomationRate::K, + }, + ) + .collect() + } + + fn process<'a, 'b>( + &mut self, + inputs: &'b [&'a [&'a [f32]]], + outputs: &'b mut [&'a mut [&'a mut [f32]]], + params: AudioParamValues<'b>, + _scope: &'b AudioWorkletGlobalScope, + ) -> bool { + // The backend processes orphan nodes too. Built-in compressors follow + // Blink's pull demand; they are not automatically pulled worklets. + if !self.demanded.load(Ordering::Acquire) { + for channel in outputs[0].iter_mut() { + channel.fill(0.0); + } + return false; + } + let input = inputs[0]; + if input.is_empty() { + self.tail_frames += outputs[0][0].len(); + } else { + self.tail_frames = 0; + } + let values = + ["threshold", "knee", "ratio", "attack", "release"].map(|name| params.get(name)[0]); + self.dsp.process(input, outputs[0], values); + self.reduction + .store(self.dsp.reduction().to_bits(), Ordering::Relaxed); + self.tail_frames < (1.625 * self.sample_rate) as usize + } +} + +pub(super) fn initialize<'s>( + scope: &mut v8::PinScope<'s, '_>, + owner: v8::Local<'s, v8::Object>, + state: State, +) { + let store = scope.get_slot::().cloned().unwrap_or_else(|| { + let store = Store::default(); + scope.set_slot(store.clone()); + store + }); + let id = { + let mut store = store.borrow_mut(); + store.next_id = store + .next_id + .checked_add(1) + .expect("audio identity exhausted"); + store.next_id + }; + let weak_store = Rc::downgrade(&store); + let weak = v8::Weak::with_finalizer( + scope, + owner, + Box::new(move |_| { + if let Some(store) = weak_store.upgrade() { + store.borrow_mut().entries.remove(&id); + } + }), + ); + store + .borrow_mut() + .entries + .insert(id, (weak, Rc::new(RefCell::new(state)))); + set_private_value( + scope, + owner, + SLOT, + v8::BigInt::new_from_u64(scope, id).into(), + ); +} + +pub(super) fn get<'s>( + scope: &mut v8::PinScope<'s, '_>, + owner: v8::Local<'s, v8::Object>, +) -> Option { + let id = v8::Local::::try_from(get_private_value(scope, owner, SLOT)?) + .ok()? + .u64_value() + .0; + scope + .get_slot::()? + .borrow() + .entries + .get(&id) + .map(|entry| entry.1.clone()) +} + +pub(super) fn create_node<'s>( + scope: &mut v8::PinScope<'s, '_>, + context: v8::Local<'s, v8::Object>, + kind: NodeKind, +) -> Node { + let state = get(scope, context).expect("validated audio context must retain its backend"); + let state = state.borrow(); + let State::Context(context) = &*state else { + unreachable!("audio context brand mismatch") + }; + context.create_node(kind) +} + +pub(super) fn param<'s>( + scope: &mut v8::PinScope<'s, '_>, + object: v8::Local<'s, v8::Object>, +) -> Option { + if let Some(state) = get(scope, object) + && let State::Param(param) = &*state.borrow() + { + return Some(param.clone()); + } + super::throw_type_error(scope, "Illegal invocation: expected an AudioParam."); + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dropping_realtime_context_closes_its_render_graph() { + let context = Context::realtime(); + let destination = context.create_node(NodeKind::Destination); + drop(context); + assert_eq!( + destination.audio_node().context().state(), + web_audio_api::context::AudioContextState::Closed + ); + } + + #[test] + fn audio_backend_wrappers_are_reclaimed_on_gc_and_isolate_drop() { + moli_v8_test_util::ensure_v8(); + let remaining = { + let mut isolate = v8::Isolate::new(Default::default()); + let abandoned = { + let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate)); + let scope = &mut scope.init(); + let context = v8::Context::new(scope, Default::default()); + let scope = &mut v8::ContextScope::new(scope, context); + let context = v8::Object::new(scope); + initialize( + scope, + context, + State::Context(Context::offline(1, 128, 8000.0)), + ); + let node = create_node(scope, context, NodeKind::Oscillator); + let param = node.param("frequency"); + let owner = v8::Object::new(scope); + initialize(scope, owner, State::Node(node)); + let parameter = v8::Object::new(scope); + initialize(scope, parameter, State::Param(param)); + [context, owner, parameter] + .map(|object| Rc::downgrade(&get(scope, object).unwrap())) + }; + isolate.low_memory_notification(); + assert!(abandoned.iter().all(|state| state.upgrade().is_none())); + assert!( + isolate + .get_slot::() + .unwrap() + .borrow() + .entries + .is_empty() + ); + let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate)); + let scope = &mut scope.init(); + let context = v8::Context::new(scope, Default::default()); + let scope = &mut v8::ContextScope::new(scope, context); + let object = v8::Object::new(scope); + initialize( + scope, + object, + State::Context(Context::offline(1, 128, 8000.0)), + ); + Rc::downgrade(&get(scope, object).unwrap()) + }; + assert!(remaining.upgrade().is_none()); + } +} diff --git a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/biquad.rs b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/biquad.rs index 1897ed7dd2..7458fef8e1 100644 --- a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/biquad.rs +++ b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/biquad.rs @@ -1,16 +1,11 @@ use super::*; - -const BIQUAD_FREQUENCY_SLOT: &str = "__moliBiquadFrequency"; +use web_audio_api::node::BiquadFilterType; #[derive(WebApiObject)] #[webapi(interface = "BiquadFilterNode")] struct BiquadFilterNodeObjectDeclaration<'scope> { - #[webapi(data_property = "type")] - kind: &'static str, #[webapi(data_property, readonly)] frequency: v8::Local<'scope, v8::Object>, - #[webapi(slot = BIQUAD_FREQUENCY_SLOT)] - internal_frequency: v8::Local<'scope, v8::Object>, #[webapi(data_property, readonly)] detune: v8::Local<'scope, v8::Object>, #[webapi(data_property = "Q", readonly)] @@ -26,6 +21,8 @@ struct BiquadFilterNodeObjectDeclaration<'scope> { #[derive(WebApiFunctionTemplate)] #[webapi(name = "BiquadFilterNode", enumerable)] struct BiquadFilterNodePrototypeDeclaration { + #[webapi(accessor_property = "type", getter = kind, setter = set_kind)] + kind: (), #[webapi(method, length = 3, callback = get_frequency_response)] get_frequency_response: (), } @@ -48,36 +45,156 @@ pub(super) fn create_biquad_filter<'s>( if !require_base_audio_context(scope, args.this()) { return; } - // Match Blink's BiquadFilterNode parameter defaults and float bounds. - let nyquist = audio_context_sample_rate(scope, args.this()) / 2.0; - let frequency = audio_param(scope, 350.0, 0.0, nyquist); - let detune = detune_param(scope); - let q = audio_param(scope, 1.0, f32::MIN as f64, f32::MAX as f64); - let gain_max = (40.0_f32 * f32::MAX.log10()) as f64; - let gain = audio_param(scope, 0.0, f32::MIN as f64, gain_max); - let node = - BiquadFilterNodeObjectDeclaration::new("lowpass", frequency, frequency, detune, q, gain) - .bind(scope) - .expect("BiquadFilterNode declaration should bind"); + let native = backend::create_node(scope, args.this(), NodeKind::Biquad); + let frequency = audio_param::wrap(scope, native.param("frequency")); + let detune = audio_param::wrap(scope, native.param("detune")); + let q = audio_param::wrap(scope, native.param("Q")); + let gain = audio_param::wrap(scope, native.param("gain")); + let node = BiquadFilterNodeObjectDeclaration::new(frequency, detune, q, gain) + .bind(scope) + .expect("BiquadFilterNode declaration should bind"); graph::initialize_node(scope, node, args.this()); + backend::initialize(scope, node, State::Node(native)); rv.set(node.into()); } +fn state<'s>( + scope: &mut v8::PinScope<'s, '_>, + object: v8::Local<'s, v8::Object>, +) -> Option { + if let Some(state) = backend::get(scope, object) + && matches!(&*state.borrow(), State::Node(Node::Biquad(_))) + { + return Some(state); + } + throw_type_error(scope, "Illegal invocation: expected a BiquadFilterNode."); + None +} + +const TYPES: [(&str, BiquadFilterType); 8] = [ + ("lowpass", BiquadFilterType::Lowpass), + ("highpass", BiquadFilterType::Highpass), + ("bandpass", BiquadFilterType::Bandpass), + ("lowshelf", BiquadFilterType::Lowshelf), + ("highshelf", BiquadFilterType::Highshelf), + ("peaking", BiquadFilterType::Peaking), + ("notch", BiquadFilterType::Notch), + ("allpass", BiquadFilterType::Allpass), +]; + +fn kind<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s, v8::Value>, +) { + let Some(state) = state(scope, args.this()) else { + return; + }; + if let State::Node(Node::Biquad(node)) = &*state.borrow() { + let name = TYPES + .iter() + .find(|(_, kind)| *kind == node.type_()) + .unwrap() + .0; + rv.set(v8str(scope, name).into()); + } +} + +fn set_kind<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s, v8::Value>, +) { + let Some(state) = state(scope, args.this()) else { + return; + }; + let Some(value) = args.get(0).to_string(scope) else { + return; + }; + let value = value.to_rust_string_lossy(scope); + let Some((_, kind)) = TYPES.iter().find(|(name, _)| *name == value) else { + throw_type_error(scope, "Invalid BiquadFilterType."); + return; + }; + if let State::Node(Node::Biquad(node)) = &mut *state.borrow_mut() { + node.set_type(*kind); + } +} + +fn float_array<'s>( + scope: &mut v8::PinScope<'s, '_>, + value: v8::Local<'s, v8::Value>, +) -> Option> { + if let Ok(array) = v8::Local::::try_from(value) + && array + .get_backing_store() + .is_none_or(|store| !store.is_shared() && !store.is_resizable_by_user_javascript()) + { + return Some(array); + } + throw_type_error( + scope, + "BiquadFilterNode requires non-shared, fixed-length Float32Arrays.", + ); + None +} + fn get_frequency_response<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, _rv: v8::ReturnValue<'s, v8::Value>, ) { - if get_private_value(scope, args.this(), BIQUAD_FREQUENCY_SLOT).is_none() { - throw_type_error(scope, "Illegal invocation: expected a BiquadFilterNode."); + let Some(state) = state(scope, args.this()) else { + return; + }; + let Some(frequencies) = float_array(scope, args.get(0)) else { + return; + }; + let Some(magnitudes) = float_array(scope, args.get(1)) else { + return; + }; + let Some(phases) = float_array(scope, args.get(2)) else { + return; + }; + let length = frequencies.length(); + if magnitudes.length() != length || phases.length() != length { + throw_dom_exception( + scope, + "InvalidAccessError", + 15, + "Frequency response arrays must have equal lengths.", + ); return; } - // Node/AudioParam metadata is available independently of the DSP backend. - // Do not silently return fabricated response curves for an unimplemented filter. - throw_dom_exception( - scope, - "NotSupportedError", - 9, - "Biquad frequency response calculation is not implemented.", - ); + let mut frequency_values = Vec::with_capacity(length); + for index in 0..length { + let Some(value) = frequencies + .get_index(scope, index as u32) + .and_then(|value| value.number_value(scope)) + else { + return; + }; + frequency_values.push(value as f32); + } + let mut magnitude_values = vec![0.0; length]; + let mut phase_values = vec![0.0; length]; + if let State::Node(Node::Biquad(node)) = &*state.borrow() { + node.get_frequency_response(&frequency_values, &mut magnitude_values, &mut phase_values); + } + for index in 0..length { + let magnitude = v8::Number::new(scope, f64::from(magnitude_values[index])); + let phase = v8::Number::new(scope, f64::from(phase_values[index])); + if magnitudes + .set_index(scope, index as u32, magnitude.into()) + .is_none() + { + return; + } + if phases + .set_index(scope, index as u32, phase.into()) + .is_none() + { + return; + } + } } diff --git a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/compressor.rs b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/compressor.rs new file mode 100644 index 0000000000..f31f07c81d --- /dev/null +++ b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/compressor.rs @@ -0,0 +1,237 @@ +// Copyright (C) 2011 Google Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Stateful look-ahead compressor, ported from Blink's +//! `third_party/blink/renderer/platform/audio/dynamics_compressor.cc`. +//! +//! Keep the detector, 32-frame envelope updates, stereo link and metering on +//! the same samples that reach the output. No browser fingerprint constants. + +const DELAY_CAPACITY: usize = 1024; + +pub(super) struct Compressor { + sample_rate: f32, + delay: [Vec; 2], + read: usize, + write: usize, + detector: f32, + gain: f32, + meter: f32, + meter_release: f32, + attack_difference: f32, + curve: Curve, +} + +struct Curve { + parameters: [f32; 3], + threshold: f32, + knee_threshold: f32, + knee_output_db: f32, + knee: f32, + slope: f32, +} + +fn to_db(value: f32) -> f32 { + 20.0 * value.log10() +} + +fn from_db(value: f32) -> f32 { + 10.0_f32.powf(0.05 * value) +} + +fn finite(value: f32, fallback: f32) -> f32 { + if value.is_finite() { value } else { fallback } +} + +impl Curve { + fn new(parameters: [f32; 3]) -> Self { + let [threshold_db, knee_db, ratio] = parameters; + let threshold = from_db(threshold_db); + let knee_threshold = from_db(threshold_db + knee_db); + let mut curve = Self { + parameters, + threshold, + knee_threshold, + knee_output_db: 0.0, + knee: 5.0, + slope: 1.0 / ratio, + }; + let mut min = 0.1_f32; + let mut max = 10_000.0_f32; + let x2 = (knee_threshold as f64 * 1.001) as f32; + for _ in 0..15 { + let slope = (to_db(curve.knee_curve(x2)) - to_db(curve.knee_curve(knee_threshold))) + / (to_db(x2) - (threshold_db + knee_db)); + if slope < curve.slope { + max = curve.knee; + } else { + min = curve.knee; + } + curve.knee = (min * max).sqrt(); + } + curve.knee_output_db = to_db(curve.knee_curve(knee_threshold)); + curve + } + + fn knee_curve(&self, input: f32) -> f32 { + if input < self.threshold { + return input; + } + self.threshold + + (1.0 - f64::from(-self.knee * (input - self.threshold)).exp() as f32) / self.knee + } + + fn saturate(&self, input: f32) -> f32 { + if input < self.knee_threshold { + self.knee_curve(input) + } else { + from_db( + self.knee_output_db + + self.slope * (to_db(input) - (self.parameters[0] + self.parameters[1])), + ) + } + } +} + +impl Compressor { + pub(super) fn new(sample_rate: f32) -> Self { + Self { + sample_rate, + delay: std::array::from_fn(|_| vec![0.0; DELAY_CAPACITY]), + read: 0, + write: ((0.006_f32 * sample_rate) as usize).min(DELAY_CAPACITY - 1), + detector: 0.0, + gain: 1.0, + meter: 1.0, + meter_release: (1.0 - (-1.0 / (0.325_f32 as f64 * sample_rate as f64)).exp()) as f32, + attack_difference: -1.0, + curve: Curve::new([-24.0, 30.0, 12.0]), + } + } + + pub(super) fn reduction(&self) -> f32 { + self.meter + } + + /// Process a render quantum (a multiple of 32 samples), in mono or stereo. + /// `parameters` are threshold, knee, ratio, attack and release, sampled at + /// the start of the quantum as required by k-rate AudioParams. + pub(super) fn process( + &mut self, + input: &[&[f32]], + output: &mut [&mut [f32]], + parameters: [f32; 5], + ) { + let [threshold, knee, ratio, attack, release] = parameters; + if self.curve.parameters != [threshold, knee, ratio] { + self.curve = Curve::new([threshold, knee, ratio]); + } + let post_gain = (1.0 / self.curve.saturate(1.0)).powf(0.6); + let attack_frames = attack.max(0.001) * self.sample_rate; + let release_frames = release * self.sample_rate; + let sat_release_frames = 0.0025 * self.sample_rate; + let zones = [0.09_f32, 0.16, 0.42, 0.98]; + let [z1, z2, z3, z4] = zones; + let a = release_frames * z1; + let b = release_frames + * (-1.578_832 * z1 + 2.330_583_8 * z2 - 0.914_119_4 * z3 + 0.162_367_75 * z4); + let c = release_frames + * (0.533_414_3 * z1 - 1.272_736_8 * z2 + 0.925_885_6 * z3 - 0.186_563_1 * z4); + let d = release_frames + * (0.087_834_634 * z1 - 0.169_416_3 * z2 + 0.085_880_58 * z3 - 0.004_298_914 * z4); + let e = release_frames + * (-0.042_416_885 * z1 + 0.111_569_38 * z2 - 0.097_646_765 * z3 + 0.028_494_263 * z4); + let frames = output.first().map_or(0, |channel| channel.len()); + debug_assert_eq!(frames % 32, 0); + debug_assert!(output.len() <= 2); + for base in (0..frames).step_by(32) { + self.detector = finite(self.detector, 1.0); + let desired = self.detector.asin() / std::f32::consts::FRAC_PI_2; + let releasing = desired > self.gain; + let difference = if desired == 0.0 { + if releasing { -1.0 } else { 1.0 } + } else { + to_db(self.gain / desired) + }; + let rate = if releasing { + self.attack_difference = -1.0; + let x = 0.25 * (finite(difference, -1.0).clamp(-12.0, 0.0) + 12.0); + let x2 = x * x; + let x3 = x2 * x; + let x4 = x2 * x2; + from_db(5.0 / (a + b * x + c * x2 + d * x3 + e * x4)) + } else { + self.attack_difference = self.attack_difference.max(finite(difference, 1.0)); + 1.0 - (0.25 / self.attack_difference.max(0.5)).powf(1.0 / attack_frames) + }; + for frame in base..base + 32 { + let mut peak = 0.0_f32; + for channel in 0..output.len() { + let sample = input + .get(channel) + .or_else(|| input.first()) + .map_or(0.0, |samples| samples[frame]); + self.delay[channel][self.write] = sample; + peak = peak.max(sample.abs()); + } + let shaped = self.curve.saturate(peak); + let attenuation = if peak <= 0.0001 { 1.0 } else { shaped / peak }; + let sat_rate = from_db((-to_db(attenuation)).max(2.0) / sat_release_frames) - 1.0; + let detector_rate = if attenuation > self.detector { + sat_rate + } else { + 1.0 + }; + self.detector = finite( + (self.detector + (attenuation - self.detector) * detector_rate).min(1.0), + 1.0, + ); + if rate < 1.0 { + self.gain += (desired - self.gain) * rate; + } else { + self.gain = (self.gain * rate).min(1.0); + } + let warped = f64::from(std::f32::consts::FRAC_PI_2 * self.gain).sin() as f32; + let total_gain = post_gain * warped; + let gain_db = to_db(warped); + if gain_db < self.meter { + self.meter = gain_db; + } else { + self.meter += (gain_db - self.meter) * self.meter_release; + } + for (channel, samples) in output.iter_mut().enumerate() { + samples[frame] = self.delay[channel][self.read] * total_gain; + } + self.read = (self.read + 1) & (DELAY_CAPACITY - 1); + self.write = (self.write + 1) & (DELAY_CAPACITY - 1); + } + if self.detector.is_subnormal() { + self.detector = 0.0; + } + if self.gain.is_subnormal() { + self.gain = 0.0; + } + } + } +} diff --git a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/graph.rs b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/graph.rs index 9093e469bd..4e2fc6625a 100644 --- a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/graph.rs +++ b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/graph.rs @@ -1,18 +1,15 @@ -//! Minimal connection/start bookkeeping for the existing audio backend. -//! -//! Reachability can establish silence without pretending to implement DSP. Keep -//! connections in native slots, handle cycles, and snapshot input availability -//! before completion callbacks can disconnect or reconnect nodes. +//! WebIDL connection validation and GC-traced wrapper relationships. +//! Signal processing and scheduling belong to the native audio graph. use super::*; const CONTEXT: &str = "__moliAudioNodeContext"; -const ANALYSERS: &str = "__moliAudioContextActiveAnalysers"; +const ANALYSERS: &str = "__moliAudioContextPullAnalysers"; +const DEMANDED: &str = "__moliAudioContextDemandedCompressors"; const DESTINATION: &str = "__moliAudioContextDestination"; const INPUTS: &str = "__moliAudioNodeInputs"; const OUTPUTS: &str = "__moliAudioNodeOutputs"; const START_TIME: &str = "__moliAudioSourceStartTime"; -const RENDERED_INPUT: &str = "__moliAudioNodeRenderedInput"; #[derive(WebApiFunctionTemplate)] #[webapi(name = "AudioNode", enumerable)] @@ -102,15 +99,15 @@ fn add_edge<'s>( slot: &'static str, other: v8::Local<'s, v8::Object>, ) { - if objects(scope, node, slot).contains(&other) { + let mut values = objects(scope, node, slot); + if values.contains(&other) { return; } - let array = web_audio_array_slot(scope, node, slot).unwrap_or_else(|| { - let array = v8::Array::new(scope, 0); - set_private_value(scope, node, slot, array.into()); - array - }); - let _ = array.set_index(scope, array.length(), other.into()); + values.push(other); + let values: Vec> = values.into_iter().map(Into::into).collect(); + // Define dense own elements without invoking Array.prototype setters. + let array = v8::Array::new_with_elements(scope, &values); + set_private_value(scope, node, slot, array.into()); } fn remove_edge<'s>( @@ -174,13 +171,17 @@ pub(super) fn connect<'s>( return None; } } + if !with_connection(scope, source, destination, |source, destination| { + source.audio_node().connect(destination.audio_node()); + }) { + return None; + } add_edge(scope, source, OUTPUTS, destination); add_edge(scope, destination, INPUTS, source); - if get_private_value(scope, destination, ANALYSER_FFT_SIZE_SLOT).is_some() { - // Track candidates for offline automatic pull: analysers with input - // can process even without an output connection. + if is_analyser(scope, destination) { add_edge(scope, context, ANALYSERS, destination); } + update_pull_demand(scope, context); Some(destination) } @@ -192,7 +193,6 @@ pub(super) fn disconnect<'s>( let Some(context) = require_node_context(scope, source) else { return; }; - let outputs = objects(scope, source, OUTPUTS); let selected = if args.length() == 0 { None } else if args.get(0).is_object() { @@ -200,15 +200,6 @@ pub(super) fn disconnect<'s>( if require_node_context(scope, destination).is_none() { return; } - if !outputs.contains(&destination) { - throw_dom_exception( - scope, - "InvalidAccessError", - 15, - "The audio nodes are not connected.", - ); - return; - } Some(destination) } else { let Some(port) = args.get(0).uint32_value(scope) else { @@ -239,17 +230,35 @@ pub(super) fn disconnect<'s>( return; } } + // Port conversion can reenter connect/disconnect. Never pass a stale edge + // to the backend, whose targeted-disconnect contract requires it to exist. + let outputs = objects(scope, source, OUTPUTS); + if selected.is_some_and(|destination| !outputs.contains(&destination)) { + throw_dom_exception( + scope, + "InvalidAccessError", + 15, + "The audio nodes are not connected.", + ); + return; + } for destination in outputs { if selected.is_none_or(|selected| selected == destination) { + if !with_connection(scope, source, destination, |source, destination| { + source + .audio_node() + .disconnect_dest(destination.audio_node()); + }) { + return; + } remove_edge(scope, source, OUTPUTS, destination); remove_edge(scope, destination, INPUTS, source); - if get_private_value(scope, destination, ANALYSER_FFT_SIZE_SLOT).is_some() - && objects(scope, destination, INPUTS).is_empty() - { + if is_analyser(scope, destination) && objects(scope, destination, INPUTS).is_empty() { remove_edge(scope, context, ANALYSERS, destination); } } } + update_pull_demand(scope, context); } pub(super) fn start_source<'s>( @@ -257,10 +266,10 @@ pub(super) fn start_source<'s>( args: &v8::FunctionCallbackArguments<'s>, ) { let node = args.this(); - let Some(start) = web_audio_number_slot(scope, node, START_TIME) else { + if web_audio_number_slot(scope, node, START_TIME).is_none() { throw_type_error(scope, "Illegal invocation: expected an OscillatorNode."); return; - }; + } let when = if args.get(0).is_undefined() { 0.0 } else { @@ -273,11 +282,9 @@ pub(super) fn start_source<'s>( throw_type_error(scope, "Audio source start time must be finite."); return; } - if when < 0.0 { - throw_range_error(scope, "Audio source start time must not be negative."); - return; - } - if start.is_finite() { + // WebIDL conversion above may run page code and start this same source. + // Check the live state afterwards, before the algorithm's range check. + if web_audio_number_slot(scope, node, START_TIME).is_some_and(f64::is_finite) { throw_dom_exception( scope, "InvalidStateError", @@ -286,57 +293,104 @@ pub(super) fn start_source<'s>( ); return; } + if when < 0.0 { + throw_range_error(scope, "Audio source start time must not be negative."); + return; + } + let Some(state) = backend::get(scope, node) else { + return; + }; + if let State::Node(Node::Oscillator(native)) = &mut *state.borrow_mut() { + native.start_at(when); + } set_web_audio_number_slot(scope, node, START_TIME, when); } -fn has_started_source<'s>( +fn is_analyser<'s>(scope: &mut v8::PinScope<'s, '_>, node: v8::Local<'s, v8::Object>) -> bool { + backend::get(scope, node) + .is_some_and(|state| matches!(&*state.borrow(), State::Node(Node::Analyser(_)))) +} + +fn set_demand<'s>( scope: &mut v8::PinScope<'s, '_>, node: v8::Local<'s, v8::Object>, - end_time: f64, + active: bool, ) -> bool { - let mut pending = vec![node]; - let mut visited = Vec::new(); - while let Some(node) = pending.pop() { - if visited.contains(&node) { - continue; - } - visited.push(node); - if web_audio_number_slot(scope, node, START_TIME).is_some_and(|start| start < end_time) { - return true; - } - pending.extend(objects(scope, node, INPUTS)); + if let Some(state) = backend::get(scope, node) + && let State::Node(Node::Compressor { demanded, .. }) = &*state.borrow() + { + demanded.store(active, std::sync::atomic::Ordering::Release); + return true; } false } -pub(super) fn prepare_offline_render<'s>( - scope: &mut v8::PinScope<'s, '_>, - context: v8::Local<'s, v8::Object>, - end_time: f64, -) -> bool { - let destination = web_audio_object_slot(scope, context, DESTINATION); - let mut pending: Vec<_> = objects(scope, context, ANALYSERS) - .into_iter() - .filter(|node| objects(scope, *node, OUTPUTS).is_empty()) - .collect(); - pending.extend(destination); +// Demand and input availability are different: a pulled compressor processes +// silent input too. This only selects processors; it never substitutes PCM or +// decides whether the output is silent from graph reachability. +fn update_pull_demand<'s>(scope: &mut v8::PinScope<'s, '_>, context: v8::Local<'s, v8::Object>) { + let previous = objects(scope, context, DEMANDED); + let mut pending = objects(scope, context, ANALYSERS); + pending.extend(web_audio_object_slot(scope, context, DESTINATION)); let mut visited = Vec::new(); + let mut demanded = Vec::new(); while let Some(node) = pending.pop() { if visited.contains(&node) { continue; } visited.push(node); + if backend::get(scope, node) + .is_some_and(|state| matches!(*state.borrow(), State::ModuleWorklet)) + { + // Module-only worklets have no DSP pull edge yet. Do not propagate + // native processor demand across a control-only connection. + continue; + } pending.extend(objects(scope, node, INPUTS)); - let has_input = has_started_source(scope, node, end_time); - let flag = v8::Boolean::new(scope, has_input); - set_private_value(scope, node, RENDERED_INPUT, flag.into()); + if set_demand(scope, node, true) { + demanded.push(node.into()); + } } - destination.is_some_and(|node| rendered_with_input(scope, node)) + // Retained processors must not briefly observe false on the audio thread + // just because an unrelated edge changed in the control thread. + for node in previous { + if !visited.contains(&node) { + set_demand(scope, node, false); + } + } + let nodes = v8::Array::new_with_elements(scope, &demanded); + set_private_value(scope, context, DEMANDED, nodes.into()); } -pub(super) fn rendered_with_input<'s>( +fn with_connection<'s>( scope: &mut v8::PinScope<'s, '_>, - node: v8::Local<'s, v8::Object>, + source: v8::Local<'s, v8::Object>, + destination: v8::Local<'s, v8::Object>, + operation: impl FnOnce(&Node, &Node), ) -> bool { - get_private_value(scope, node, RENDERED_INPUT).is_some_and(|value| value.boolean_value(scope)) + if let Some(source) = backend::get(scope, source) + && let Some(destination) = backend::get(scope, destination) + { + match (&*source.borrow(), &*destination.borrow()) { + (State::Node(source), State::Node(destination)) => { + operation(source, destination); + return true; + } + (State::ModuleWorklet, State::Node(_) | State::ModuleWorklet) + | (State::Node(_), State::ModuleWorklet) => { + // Keep the pre-existing module/MessagePort surface usable. + // The caller records the logical edge; unlike native-to-native + // edges, it does not imply that JS worklet DSP is implemented. + return true; + } + _ => {} + } + } + throw_dom_exception( + scope, + "NotSupportedError", + 9, + "This AudioNode has no native audio processor.", + ); + false } diff --git a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/oscillator.rs b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/oscillator.rs new file mode 100644 index 0000000000..7dd15ac00f --- /dev/null +++ b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/oscillator.rs @@ -0,0 +1,78 @@ +use super::*; +use web_audio_api::node::OscillatorType; + +#[derive(WebApiFunctionTemplate)] +#[webapi(name = "OscillatorNode", enumerable)] +struct OscillatorPrototype { + #[webapi(accessor_property = "type", getter = kind, setter = set_kind)] + kind: (), +} + +pub(super) fn install<'s>( + scope: &mut v8::PinScope<'s, '_, ()>, + template: v8::Local<'s, v8::FunctionTemplate>, +) { + OscillatorPrototype::initialize_prototype_template(scope, template.prototype_template(scope)); +} + +fn kind<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s, v8::Value>, +) { + if let Some(state) = backend::get(scope, args.this()) + && let State::Node(Node::Oscillator(node)) = &*state.borrow() + { + let name = match node.type_() { + OscillatorType::Sine => "sine", + OscillatorType::Square => "square", + OscillatorType::Sawtooth => "sawtooth", + OscillatorType::Triangle => "triangle", + OscillatorType::Custom => "custom", + }; + rv.set(v8str(scope, name).into()); + return; + } + throw_type_error(scope, "Illegal invocation: expected an OscillatorNode."); +} + +fn set_kind<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s, v8::Value>, +) { + let Some(state) = backend::get(scope, args.this()) else { + throw_type_error(scope, "Illegal invocation: expected an OscillatorNode."); + return; + }; + if !matches!(&*state.borrow(), State::Node(Node::Oscillator(_))) { + throw_type_error(scope, "Illegal invocation: expected an OscillatorNode."); + return; + } + let Some(value) = args.get(0).to_string(scope) else { + return; + }; + let value = value.to_rust_string_lossy(scope); + let kind = match value.as_str() { + "sine" => OscillatorType::Sine, + "square" => OscillatorType::Square, + "sawtooth" => OscillatorType::Sawtooth, + "triangle" => OscillatorType::Triangle, + "custom" => { + throw_dom_exception( + scope, + "InvalidStateError", + 11, + "Use setPeriodicWave to select a custom waveform.", + ); + return; + } + _ => { + throw_type_error(scope, "Invalid OscillatorType."); + return; + } + }; + if let State::Node(Node::Oscillator(node)) = &mut *state.borrow_mut() { + node.set_type(kind); + } +} diff --git a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/oscillator_dsp.rs b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/oscillator_dsp.rs new file mode 100644 index 0000000000..69522b5493 --- /dev/null +++ b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/oscillator_dsp.rs @@ -0,0 +1,153 @@ +use super::wavetable::{self, Tables}; +use std::sync::{ + Arc, + atomic::{AtomicU8, AtomicU64, Ordering}, +}; +use web_audio_api::context::BaseAudioContext; +use web_audio_api::node::OscillatorType; +use web_audio_api::worklet::{ + AudioParamValues, AudioWorkletGlobalScope, AudioWorkletNode, AudioWorkletNodeOptions, + AudioWorkletProcessor, +}; +use web_audio_api::{AudioParam, AudioParamDescriptor, AutomationRate}; + +struct Controls { + shape: AtomicU8, + start: AtomicU64, +} + +pub(super) struct Oscillator { + pub(super) node: AudioWorkletNode, + controls: Arc, +} + +impl Oscillator { + pub(super) fn new(context: &C) -> Self { + let controls = Arc::new(Controls { + shape: AtomicU8::new(0), + start: AtomicU64::new(f64::INFINITY.to_bits()), + }); + let tables = wavetable::for_sample_rate(context.sample_rate()); + let node = AudioWorkletNode::new::( + context, + AudioWorkletNodeOptions { + number_of_inputs: 0, + number_of_outputs: 1, + output_channel_count: vec![1], + processor_options: (Arc::clone(&controls), tables), + parameter_data: Default::default(), + audio_node_options: Default::default(), + }, + ); + Self { node, controls } + } + pub(super) fn frequency(&self) -> &AudioParam { + &self.node.parameters()["frequency"] + } + pub(super) fn detune(&self) -> &AudioParam { + &self.node.parameters()["detune"] + } + pub(super) fn start_at(&mut self, when: f64) { + self.controls.start.store( + when.max(self.node.context().current_time()).to_bits(), + Ordering::Release, + ); + } + pub(super) fn type_(&self) -> OscillatorType { + match self.controls.shape.load(Ordering::Relaxed) { + 0 => OscillatorType::Sine, + 1 => OscillatorType::Square, + 2 => OscillatorType::Sawtooth, + 3 => OscillatorType::Triangle, + _ => unreachable!(), + } + } + pub(super) fn set_type(&mut self, kind: OscillatorType) { + let value = match kind { + OscillatorType::Sine => 0, + OscillatorType::Square => 1, + OscillatorType::Sawtooth => 2, + OscillatorType::Triangle => 3, + OscillatorType::Custom => unreachable!("custom waves require their own table"), + }; + self.controls.shape.store(value, Ordering::Release); + } +} + +struct Processor { + controls: Arc, + tables: [Arc; 4], + position: f64, + started: bool, +} + +impl AudioWorkletProcessor for Processor { + type ProcessorOptions = (Arc, [Arc; 4]); + fn constructor((controls, tables): Self::ProcessorOptions) -> Self { + Self { + controls, + tables, + position: 0.0, + started: false, + } + } + fn parameter_descriptors() -> Vec { + [ + ("frequency", 440.0, f32::MIN, f32::MAX), + ("detune", 0.0, -153600.0, 153600.0), + ] + .into_iter() + .map( + |(name, default_value, min_value, max_value)| AudioParamDescriptor { + name: name.into(), + default_value, + min_value, + max_value, + automation_rate: AutomationRate::A, + }, + ) + .collect() + } + fn process<'a, 'b>( + &mut self, + _inputs: &'b [&'a [&'a [f32]]], + outputs: &'b mut [&'a mut [&'a mut [f32]]], + params: AudioParamValues<'b>, + scope: &'b AudioWorkletGlobalScope, + ) -> bool { + let start = f64::from_bits(self.controls.start.load(Ordering::Acquire)); + let output = &mut outputs[0][0]; + if !start.is_finite() { + output.fill(0.0); + return false; + } + let table = &self.tables[self.controls.shape.load(Ordering::Acquire) as usize]; + let frequency = params.get("frequency"); + let detune = params.get("detune"); + let sample_rate = scope.sample_rate; + let nyquist = sample_rate / 2.0; + let rate_scale = table.size as f32 / sample_rate; + for (index, sample) in output.iter_mut().enumerate() { + let time = scope.current_time + index as f64 / f64::from(sample_rate); + if time < start { + *sample = 0.0; + continue; + } + let hz = frequency[index.min(frequency.len() - 1)].clamp(-nyquist, nyquist); + let detune = detune[index.min(detune.len() - 1)]; + let hz = ((f64::from(hz) * 2.0_f64.powf(f64::from(detune) / 1200.0)) as f32) + .clamp(-nyquist, nyquist); + let increment = f64::from(hz * rate_scale); + if !self.started { + self.position = (increment * ((time - start) * f64::from(sample_rate))) + .rem_euclid(table.size as f64); + self.started = true; + } + *sample = table.sample(self.position, hz, sample_rate); + self.position = (self.position + increment).rem_euclid(table.size as f64); + } + true + } +} + +use web_audio_api::node::AudioNode; diff --git a/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/wavetable.rs b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/wavetable.rs new file mode 100644 index 0000000000..53e8fa83e3 --- /dev/null +++ b/moli-renderer-v8/src/context_bootstrap/web_audio_runtime/wavetable.rs @@ -0,0 +1,141 @@ +// Copyright (C) 2012 Google Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Octave-banded Fourier tables, ported from Blink's +//! `third_party/blink/renderer/modules/webaudio/periodic_wave.cc`. +//! Band limiting is essential: a raw +//! triangle or square aliases differently at high frequencies and changes both +//! the compressor's detector input and the output spectrum. + +use realfft::{RealFftPlanner, num_complex::Complex32}; +use std::sync::{Arc, OnceLock}; + +pub(super) struct Tables { + pub(super) size: usize, + bands: Vec>, +} + +pub(super) fn for_sample_rate(sample_rate: f32) -> [Arc; 4] { + static LOW: [OnceLock>; 4] = [const { OnceLock::new() }; 4]; + static MID: [OnceLock>; 4] = [const { OnceLock::new() }; 4]; + static HIGH: [OnceLock>; 4] = [const { OnceLock::new() }; 4]; + let (size, cache) = if sample_rate <= 24000.0 { + (2048, &LOW) + } else if sample_rate <= 88200.0 { + (4096, &MID) + } else { + (16384, &HIGH) + }; + std::array::from_fn(|shape| { + Arc::clone(cache[shape].get_or_init(|| Arc::new(Tables::new(size, shape)))) + }) +} + +impl Tables { + fn new(size: usize, shape: usize) -> Self { + let half = size / 2; + let band_count = 3 * size.ilog2() as usize; + let inverse = RealFftPlanner::::new().plan_fft_inverse(size); + let mut harmonics = inverse.make_input_vec(); + for (n, harmonic) in harmonics.iter_mut().enumerate().take(half).skip(1) { + let pi_factor = 2.0 / (n as f32 * std::f32::consts::PI); + let coefficient = match shape { + 0 => { + if n == 1 { + 1.0 + } else { + 0.0 + } + } + 1 => { + if n & 1 != 0 { + 2.0 * pi_factor + } else { + 0.0 + } + } + 2 => pi_factor * if n & 1 != 0 { 1.0 } else { -1.0 }, + 3 => { + if n & 1 != 0 { + 2.0 * (pi_factor * pi_factor) + * if ((n - 1) >> 1) & 1 != 0 { -1.0 } else { 1.0 } + } else { + 0.0 + } + } + _ => unreachable!(), + }; + *harmonic = Complex32::new(0.0, -coefficient); + } + let mut bands = Vec::with_capacity(band_count); + let mut normalization = 0.5; + let mut scratch = inverse.make_scratch_vec(); + for band in 0..band_count { + let scale = 2.0_f64.powf(f64::from(-(band as f32 * 400.0) / 1200.0)) as f32; + let partials = (scale * half as f32) as usize; + let mut spectrum = harmonics.clone(); + spectrum[half.min(partials + 1)..].fill(Complex32::new(0.0, 0.0)); + let mut samples = inverse.make_output_vec(); + inverse + .process_with_scratch(&mut spectrum, &mut samples, &mut scratch) + .expect("valid Fourier table"); + if band == 0 { + let peak = samples + .iter() + .map(|sample| sample.abs()) + .fold(0.0_f32, f32::max); + if peak != 0.0 { + normalization = 1.0 / peak; + } + } + for sample in &mut samples { + *sample *= normalization; + } + bands.push(samples); + } + Self { size, bands } + } + + pub(super) fn sample(&self, position: f64, frequency: f32, sample_rate: f32) -> f32 { + let ratio = if frequency != 0.0 { + frequency.abs() / (sample_rate / self.size as f32) + } else { + 0.5 + }; + let pitch = (1.0 + ratio.log2() * 1200.0 / 400.0).clamp(0.0, (self.bands.len() - 1) as f32); + let upper = pitch as usize; + let lower = (upper + 1).min(self.bands.len() - 1); + let blend = pitch - upper as f32; + let integer = position as usize; + let index = integer & (self.size - 1); + let next = (index + 1) & (self.size - 1); + let fraction = position as f32 - integer as f32; + let interpolate = |band: usize| { + let first = self.bands[band][index]; + first + fraction * (self.bands[band][next] - first) + }; + let high = interpolate(upper); + high + blend * (interpolate(lower) - high) + } +} diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/media.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/media.rs index 1378297529..4ad16b949c 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/media.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/media.rs @@ -2438,7 +2438,9 @@ fn web_audio_declared_objects_keep_brands_and_hidden_properties() { }; }; const rendering = ctx.startRendering(); - osc.frequency.setValueAtTime(123, 0); + osc.frequency.value = 123; + // Scheduling does not synchronously replace the intrinsic value. + osc.frequency.setValueAtTime(246, 1); Object.assign(globalThis.__webAudioDeclaredProbe, { ctxTag: Object.prototype.toString.call(ctx), ctxCtor: ctx.constructor && ctx.constructor.name, @@ -2478,7 +2480,7 @@ fn web_audio_declared_objects_keep_brands_and_hidden_properties() { assert_eq!( result, - r#"{"completion":{"tag":"[object Object]","ctor":"Object","type":"complete","renderedBufferTag":"[object AudioBuffer]","renderedBufferCtor":"AudioBuffer","renderedBufferLength":32,"renderedBufferSampleRate":8000,"renderedBufferDuration":0.004,"renderedBufferEnumerable":false,"targetIsContext":true,"currentTargetIsContext":true},"ctxTag":"[object OfflineAudioContext]","ctxCtor":"OfflineAudioContext","ctxLength":32,"ctxSampleRate":8000,"ctxState":"closed","lengthEnumerable":false,"destinationTag":"[object AudioDestinationNode]","destinationCtor":"AudioDestinationNode","destinationKeys":"","oscTag":"[object OscillatorNode]","oscCtor":"OscillatorNode","oscKeys":"","oscType":"sine","frequencyTag":"[object AudioParam]","frequencyCtor":"AudioParam","frequencyValue":123,"frequencyEnumerable":false,"setValueAtTimeType":"function","compTag":"[object DynamicsCompressorNode]","compCtor":"DynamicsCompressorNode","compKeys":"","thresholdTag":"[object AudioParam]","reduction":0,"reductionOwn":false,"promiseTag":"[object Promise]"}"# + r#"{"completion":{"tag":"[object Object]","ctor":"Object","type":"complete","renderedBufferTag":"[object AudioBuffer]","renderedBufferCtor":"AudioBuffer","renderedBufferLength":32,"renderedBufferSampleRate":8000,"renderedBufferDuration":0.004,"renderedBufferEnumerable":false,"targetIsContext":true,"currentTargetIsContext":true},"ctxTag":"[object OfflineAudioContext]","ctxCtor":"OfflineAudioContext","ctxLength":32,"ctxSampleRate":8000,"ctxState":"running","lengthEnumerable":false,"destinationTag":"[object AudioDestinationNode]","destinationCtor":"AudioDestinationNode","destinationKeys":"","oscTag":"[object OscillatorNode]","oscCtor":"OscillatorNode","oscKeys":"","oscType":"sine","frequencyTag":"[object AudioParam]","frequencyCtor":"AudioParam","frequencyValue":123,"frequencyEnumerable":false,"setValueAtTimeType":"function","compTag":"[object DynamicsCompressorNode]","compCtor":"DynamicsCompressorNode","compKeys":"","thresholdTag":"[object AudioParam]","reduction":0,"reductionOwn":false,"promiseTag":"[object Promise]"}"# ); } @@ -2853,6 +2855,8 @@ fn web_audio_numeric_entrypoints_parse_webidl_arguments() { record("setSymbol", () => osc.frequency.setValueAtTime(Symbol("value"), 0)); osc.frequency.setValueAtTime("123.5", ctx.currentTime); results.frequency = osc.frequency.value; + osc.connect(ctx.destination); + osc.start(); ctx.oncomplete = (event) => { const buffer = event.renderedBuffer; @@ -2860,6 +2864,7 @@ fn web_audio_numeric_entrypoints_parse_webidl_arguments() { record("channelSymbol", () => buffer.getChannelData(Symbol("channel"))); record("channelRange", () => buffer.getChannelData(1)); results.channel0 = Object.prototype.toString.call(buffer.getChannelData("0.9")); + results.renderedFrequency = osc.frequency.value; }; ctx.startRendering(); globalThis.__audioWebIdlProbe = results; @@ -2875,7 +2880,7 @@ fn web_audio_numeric_entrypoints_parse_webidl_arguments() { assert_eq!( result, - r#"{"ctorMissing":"TypeError","ctorSymbol":"TypeError","length":16,"sampleRate":44100,"setMissingTime":"TypeError","setSymbol":"TypeError","frequency":123.5,"channelMissing":"TypeError","channelSymbol":"TypeError","channelRange":"IndexSizeError","channel0":"[object Float32Array]"}"# + r#"{"ctorMissing":"TypeError","ctorSymbol":"TypeError","length":16,"sampleRate":44100,"setMissingTime":"TypeError","setSymbol":"TypeError","frequency":440,"channelMissing":"TypeError","channelSymbol":"TypeError","channelRange":"IndexSizeError","channel0":"[object Float32Array]","renderedFrequency":123.5}"# ); } #[test] diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/web_audio.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/web_audio.rs index da7e9e7c50..c9c97e4b00 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/web_audio.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/web_audio.rs @@ -1,5 +1,54 @@ use super::*; +#[test] +fn web_audio_connections_revalidate_after_coercion_without_page_hooks() { + let mut vm = new_storage_test_vm("https://audio-connection-reentrancy.test/"); + let source = include_str!("../../../../tests/fixtures/audio-graph-reentrancy-check.js"); + assert_eq!(vm.eval(&format!("JSON.stringify({source})")).unwrap(), "[]"); +} + +#[test] +fn web_audio_native_state_boundaries_match_chromium() { + let mut vm = new_storage_test_vm("https://audio-native-boundaries.test/"); + let source = include_str!("../../../../tests/fixtures/offline-audio-boundaries-check.js"); + vm.exec( + &format!("({source}).then(value => globalThis.__audioBoundaries = value)"), + None, + ) + .expect("audio boundary fixture should execute"); + assert_eq!(vm.eval("JSON.stringify(__audioBoundaries)").unwrap(), "[]"); +} + +#[test] +fn offline_audio_analyser_uses_rendered_pcm_and_a_real_fft() { + let mut vm = new_storage_test_vm("https://audio-rendered-pcm.test/"); + let source = include_str!("../../../../tests/fixtures/offline-audio-analyser-check.js"); + vm.exec( + &format!("({source}).then(value => globalThis.__renderedPcm = value)"), + None, + ) + .expect("offline audio fixture should execute"); + let result = vm + .eval("JSON.stringify(globalThis.__renderedPcm)") + .expect("offline rendering should settle its promise"); + let rows: serde_json::Value = serde_json::from_str(&result).unwrap(); + assert_eq!(rows.as_array().unwrap().len(), 3); + for row in rows.as_array().unwrap() { + for key in [ + "waveformCorrect", + "timeMatchesPcm", + "retainsAfterDisconnect", + "peakCorrect", + "blackmanAmplitudeCorrect", + "frequencyReadStable", + "byteFrequencyCorrect", + "byteTimeCorrect", + ] { + assert_eq!(row[key], true, "{key}: {row}"); + } + } +} + #[test] fn web_audio_factories_are_shared_by_realtime_and_offline_contexts() { let mut vm = new_storage_test_vm("https://audio-factories.test/"); @@ -85,7 +134,10 @@ fn web_audio_factories_reject_forged_contexts_and_keep_nodes_independent() { const a = context.createOscillator(), b = context.createOscillator(); a.type = 'triangle'; if (a.frequency.setValueAtTime(880, 0) !== a.frequency) errors.push('AudioParam return value'); - if (a.frequency.value !== 880 || b.frequency.value !== 440 || b.type !== 'sine') errors.push('shared oscillator state'); + // Scheduling does not synchronously change .value (also true in Chromium). + if (a.frequency.value !== 440 || b.frequency.value !== 440 || b.type !== 'sine') errors.push('premature automation'); + a.frequency.value = 880; + if (a.frequency.value !== 880 || b.frequency.value !== 440) errors.push('shared oscillator state'); const analyser = context.createAnalyser(); if (a.connect(analyser) !== analyser || analyser.connect(context.destination) !== context.destination) errors.push('connect return value'); if (a.start(0) !== undefined || a.disconnect() !== undefined) errors.push('source return value'); @@ -254,8 +306,10 @@ fn web_audio_biquad_and_existing_params_expose_native_readonly_metadata() { } } if (filter.gain.defaultValue !== 0 || filter.gain.minValue !== Math.fround(-3.4028234663852886e38) || !(filter.gain.maxValue > 1541 && filter.gain.maxValue < 1542)) errors.push('gain range'); - try { filter.getFrequencyResponse(new Float32Array(1), new Float32Array(1), new Float32Array(1)); errors.push('fabricated frequency response'); } - catch (e) { if (e.name !== 'NotSupportedError') errors.push('response ' + e.name); } + filter.type = 'allpass'; + const magnitudes = new Float32Array(3), phases = new Float32Array(3); + filter.getFrequencyResponse(new Float32Array([0, 350, rate / 4]), magnitudes, phases); + if (!magnitudes.every(value => Math.abs(value - 1) < 1e-6) || !phases.every(Number.isFinite)) errors.push('allpass response'); } return JSON.stringify(errors); })() @@ -268,8 +322,9 @@ fn offline_audio_silence_depends_on_reachable_started_sources() { let mut vm = new_storage_test_vm("https://audio-graph-silence.test/"); vm.exec(r#" globalThis.__audioSilenceResults = []; +for (const length of [100, 1024]) { for (const mode of ['empty', 'unconnected', 'not-started', 'future', 'disconnected-source', 'disconnected-sink', 'cycle', 'connected', 'duplicate', 'selected-disconnect']) { - const ctx = new OfflineAudioContext(1, 100, 44100); + const ctx = new OfflineAudioContext(1, length, 44100); const osc = ctx.createOscillator(), comp = ctx.createDynamicsCompressor(); if (mode !== 'empty' && mode !== 'unconnected') { osc.connect(comp); @@ -282,14 +337,16 @@ for (const mode of ['empty', 'unconnected', 'not-started', 'future', 'disconnect if (mode === 'duplicate') { osc.connect(comp); osc.connect(comp); } if (mode === 'selected-disconnect') { const a = ctx.createAnalyser(); osc.connect(a); osc.disconnect(a); } ctx.startRendering().then(buffer => { - __audioSilenceResults.push([mode, buffer.getChannelData(0).some(x => x !== 0), comp.reduction < 0]); + const pcm = buffer.getChannelData(0); + __audioSilenceResults.push([length, mode, pcm.some(x => x !== 0), pcm.subarray(0, Math.min(264, length)).every(x => x === 0)]); }); } +} "#, None).expect("offline graph scenarios should render without hanging on a cycle"); let result = vm.eval("JSON.stringify(__audioSilenceResults)").unwrap(); assert_eq!( result, - r#"[["empty",false,false],["unconnected",false,false],["not-started",false,false],["future",false,false],["disconnected-source",false,false],["disconnected-sink",false,false],["cycle",false,false],["connected",true,true],["duplicate",true,true],["selected-disconnect",true,true]]"# + r#"[[100,"empty",false,true],[100,"unconnected",false,true],[100,"not-started",false,true],[100,"future",false,true],[100,"disconnected-source",false,true],[100,"disconnected-sink",false,true],[100,"cycle",false,true],[100,"connected",false,true],[100,"duplicate",false,true],[100,"selected-disconnect",false,true],[1024,"empty",false,true],[1024,"unconnected",false,true],[1024,"not-started",false,true],[1024,"future",false,true],[1024,"disconnected-source",false,true],[1024,"disconnected-sink",false,true],[1024,"cycle",false,true],[1024,"connected",true,true],[1024,"duplicate",true,true],[1024,"selected-disconnect",true,true]]"# ); } @@ -316,6 +373,64 @@ ctx.startRendering(); ); } +#[test] +fn offline_compressor_meter_follows_pull_demand_not_input_reachability() { + let mut vm = new_storage_test_vm("https://audio-compressor-demand.test/"); + vm.exec(r#" +globalThis.__meter = []; +for (const mode of ['orphan', 'silent-destination', 'source-without-destination', 'source-to-destination']) { + const context = new OfflineAudioContext(1, 1024, 44100); + const compressor = context.createDynamicsCompressor(), oscillator = context.createOscillator(); + if (mode.startsWith('source')) { oscillator.connect(compressor); oscillator.start(); } + if (mode.endsWith('destination') && mode !== 'source-without-destination') compressor.connect(context.destination); + const before = compressor.reduction; + context.startRendering().then(buffer => __meter.push([mode, before, compressor.reduction < 0, buffer.getChannelData(0).some(x => x !== 0)])); +} +"#, None).unwrap(); + // Blink can have negative metering while processing silence. The old + // reachability-based "no source => zero reduction" assertion was incorrect. + assert_eq!( + vm.eval("JSON.stringify(__meter)").unwrap(), + r#"[["orphan",0,false,false],["silent-destination",0,true,false],["source-without-destination",0,false,false],["source-to-destination",0,true,true]]"# + ); +} + +#[test] +fn offline_audio_automation_and_delayed_start_use_the_sample_clock() { + let mut vm = new_storage_test_vm("https://audio-automation.test/"); + vm.exec( + r#" +const context = new OfflineAudioContext(1, 4096, 8192), oscillator = context.createOscillator(); +oscillator.frequency.value = 256; +oscillator.frequency.setValueAtTime(512, .25); +oscillator.detune.setValueAtTime(1200, .375); +oscillator.connect(context.destination); +oscillator.start(.125); +const before = oscillator.frequency.value; +context.startRendering().then(buffer => { + const pcm = buffer.getChannelData(0); + const expected = i => i < 1024 ? 0 : + Math.sin(2 * Math.PI * (i < 2048 ? 256 : i < 3072 ? 512 : 1024) * i / 8192); + globalThis.__automation = { + before, + error: Math.max(...pcm.map((value, i) => Math.abs(value - expected(i)))), + silentPrefix: pcm.subarray(0, 1024).every(value => value === 0), + state: context.state, time: context.currentTime + }; +}); +"#, + None, + ) + .unwrap(); + let result: serde_json::Value = + serde_json::from_str(&vm.eval("JSON.stringify(__automation)").unwrap()).unwrap(); + assert_eq!(result["before"], 256); + assert!(result["error"].as_f64().unwrap() < 1e-4, "{result}"); + assert_eq!(result["silentPrefix"], true); + assert_eq!(result["state"], "closed"); + assert_eq!(result["time"], 0.5); +} + #[test] fn web_audio_connections_validate_context_and_source_receivers() { let mut vm = new_storage_test_vm("https://audio-connection-receivers.test/"); diff --git a/moli-renderer-v8/tests/fixtures/audio-graph-reentrancy-check.js b/moli-renderer-v8/tests/fixtures/audio-graph-reentrancy-check.js new file mode 100644 index 0000000000..29bc01e510 --- /dev/null +++ b/moli-renderer-v8/tests/fixtures/audio-graph-reentrancy-check.js @@ -0,0 +1,35 @@ +(() => { + const failures = []; + const context = new OfflineAudioContext(1, 128, 8192); + const source = context.createOscillator(); + const sink = context.createAnalyser(); + source.connect(sink); + try { + source.disconnect(sink, {valueOf() { source.disconnect(sink); return 0; }}); + failures.push('accepted an edge removed during conversion'); + } catch (error) { + if (error.name !== 'InvalidAccessError') failures.push('removed edge: ' + error.name); + } + try { + source.disconnect(sink, {valueOf() { source.connect(sink); return 0; }}); + } catch (error) { + failures.push('new edge during conversion: ' + error.name); + } + let traps = 0; + const previous = Object.getOwnPropertyDescriptor(Array.prototype, '0'); + Object.defineProperty(Array.prototype, '0', { + configurable: true, + set() { traps++; } + }); + try { + source.connect(sink); + sink.connect(context.destination); + source.disconnect(sink); + sink.disconnect(); + } finally { + if (previous) Object.defineProperty(Array.prototype, '0', previous); + else delete Array.prototype[0]; + } + if (traps) failures.push('graph storage called page setters: ' + traps); + return failures; +})() diff --git a/moli-renderer-v8/tests/fixtures/offline-audio-analyser-check.js b/moli-renderer-v8/tests/fixtures/offline-audio-analyser-check.js new file mode 100644 index 0000000000..28c33938b0 --- /dev/null +++ b/moli-renderer-v8/tests/fixtures/offline-audio-analyser-check.js @@ -0,0 +1,51 @@ +(async () => { + const results = []; + for (const frequency of [256, 512, 1024]) { + const rate = 8192, length = 4096, fftSize = 256; + const context = new OfflineAudioContext(1, length, rate); + const oscillator = context.createOscillator(); + oscillator.frequency.value = frequency; + const analyser = context.createAnalyser(); + analyser.fftSize = fftSize; + analyser.smoothingTimeConstant = 0; + analyser.minDecibels = -60; + analyser.maxDecibels = 0; + oscillator.connect(analyser); + analyser.connect(context.destination); + oscillator.start(); + const buffer = await context.startRendering(); + const pcm = buffer.getChannelData(0); + const time = new Float32Array(fftSize); + analyser.getFloatTimeDomainData(time); + const bins = new Float32Array(fftSize / 2); + analyser.getFloatFrequencyData(bins); + const again = new Float32Array(bins.length); + analyser.getFloatFrequencyData(again); + const bytes = new Uint8Array(bins.length); + analyser.getByteFrequencyData(bytes); + const byteTime = new Uint8Array(fftSize); + analyser.getByteTimeDomainData(byteTime); + let peak = 0; + for (let i = 1; i < bins.length; ++i) if (bins[i] > bins[peak]) peak = i; + const waveformError = Math.max(...pcm.map((value, i) => Math.abs(value - Math.sin(2 * Math.PI * frequency * i / rate)))); + const retained = Array.from(time); + oscillator.disconnect(); + analyser.disconnect(); + analyser.getFloatTimeDomainData(time); + results.push({ + frequency, + waveformError, + waveformCorrect: waveformError < 0.0001, + timeMatchesPcm: time.every((value, i) => value === pcm[length - fftSize + i]), + retainsAfterDisconnect: time.every((value, i) => value === retained[i]), + peak, + peakCorrect: peak === frequency * fftSize / rate, + peakDecibels: bins[peak], + blackmanAmplitudeCorrect: Math.abs(bins[peak] - 20 * Math.log10(0.21)) < 0.001, + frequencyReadStable: bins.every((value, i) => value === again[i]), + byteFrequencyCorrect: bytes.every((value, i) => value === Math.max(0, Math.min(255, Math.floor(255 * (bins[i] + 60) / 60)))), + byteTimeCorrect: byteTime.every((value, i) => value === Math.max(0, Math.min(255, Math.floor(128 * (time[i] + 1))))), + }); + } + return results; +})() diff --git a/moli-renderer-v8/tests/fixtures/offline-audio-boundaries-check.js b/moli-renderer-v8/tests/fixtures/offline-audio-boundaries-check.js new file mode 100644 index 0000000000..0593309242 --- /dev/null +++ b/moli-renderer-v8/tests/fixtures/offline-audio-boundaries-check.js @@ -0,0 +1,102 @@ +(async () => { + const failures = []; + const check = (name, value) => { if (!value) failures.push(name); }; + const throws = (name, action, expected) => { + try { action(); failures.push(name + ': did not throw'); } + catch (error) { if (error.name !== expected) failures.push(name + ': ' + error.name); } + }; + for (const [channels, length, rate, error] of [ + [0, 128, 8000, 'NotSupportedError'], [33, 128, 8000, 'NotSupportedError'], + [1, 0, 8000, 'NotSupportedError'], [NaN, 128, 8000, 'NotSupportedError'], + [1, 128, 2999, 'NotSupportedError'], [1, 128, 768001, 'NotSupportedError'], + [1, 128, NaN, 'TypeError'], [1, 128, Infinity, 'TypeError'] + ]) throws('constructor ' + [channels, length, rate], () => new OfflineAudioContext(channels, length, rate), error); + const converted = new OfflineAudioContext(2 ** 32 + 1, 2 ** 32 + 128, 8192); + check('unsigned long conversion', converted.length === 128 && converted.sampleRate === 8192); + const context = new OfflineAudioContext(1, 128, 8192); + const clock = Object.getOwnPropertyDescriptor(BaseAudioContext.prototype, 'currentTime'); + check('currentTime prototype getter', typeof clock?.get === 'function' && !clock.set && clock.enumerable && clock.configurable); + check('currentTime readonly', !Object.hasOwn(context, 'currentTime') && !Reflect.set(context, 'currentTime', 100) && context.currentTime === 0); + if (clock?.get) { + for (const receiver of [{}, BaseAudioContext.prototype, Object.create(context), new Proxy(context, {})]) + throws('currentTime receiver', () => clock.get.call(receiver), 'TypeError'); + } + const oscillator = context.createOscillator(); + for (const parameter of [oscillator.frequency, oscillator.detune, context.createBiquadFilter().frequency]) { + check('default a-rate', parameter.automationRate === 'a-rate'); + parameter.automationRate = 'k-rate'; + check('set k-rate', parameter.automationRate === 'k-rate'); + parameter.automationRate = 'a-rate'; + check('set a-rate', parameter.automationRate === 'a-rate'); + } + const compressor = context.createDynamicsCompressor(); + for (const name of ['threshold', 'knee', 'ratio', 'attack', 'release']) { + const parameter = compressor[name]; + check('fixed k-rate ' + name, parameter.automationRate === 'k-rate'); + for (const rate of ['k-rate', 'a-rate']) + throws('fixed setter ' + name + ' ' + rate, () => { parameter.automationRate = rate; }, 'InvalidStateError'); + check('fixed rate unchanged', parameter.automationRate === 'k-rate'); + } + let completionCount = 0; + context.oncomplete = () => { completionCount++; }; + const rendering = context.startRendering(); + check('render returns promise', rendering instanceof Promise); + check('render immediate state', context.state === 'running'); + await context.startRendering().then(() => failures.push('second rendering resolved'), error => check('second rendering rejects', error.name === 'InvalidStateError')); + const buffer = await rendering; + check('render clock', context.currentTime === 128 / 8192 && context.state === 'closed'); + check('completion once', completionCount === 1 && buffer.length === 128); + await context.startRendering().then(() => failures.push('closed rendering resolved'), error => check('closed rendering rejects', error.name === 'InvalidStateError')); + + const reentrant = new OfflineAudioContext(1, 4096, 8192); + const source = reentrant.createOscillator(); + source.frequency.value = 512; + source.connect(reentrant.destination); + throws('reentrant start', () => source.start({valueOf() { source.start(.25); return 0; }}), 'InvalidStateError'); + throws('repeat start before range validation', () => source.start(-1), 'InvalidStateError'); + throws('restricted time before repeat check', () => source.start(NaN), 'TypeError'); + const samples = (await reentrant.startRendering()).getChannelData(0); + check('reentrant start keeps inner schedule', samples.subarray(0, 2048).every(x => x === 0) && samples.subarray(2048).some(x => x !== 0)); + + for (const frequency of [-512, -1e-20, -Number.MIN_VALUE, 0, 512]) { + const context = new OfflineAudioContext(1, 512, 8192); + const source = context.createOscillator(); + source.frequency.value = frequency; + source.connect(context.destination); + source.start(); + const pcm = (await context.startRendering()).getChannelData(0); + check('signed frequency ' + frequency, pcm.every((x, i) => Number.isFinite(x) && Math.abs(x - Math.sin(2 * Math.PI * frequency * i / 8192)) < .0001)); + } + for (const rate of ['a-rate', 'k-rate']) { + const context = new OfflineAudioContext(1, 128, 8192); + const source = context.createOscillator(); + source.frequency.automationRate = rate; + source.frequency.setValueAtTime(256, 0); + source.connect(context.destination); + source.start(); + check('scheduled intrinsic before rendering ' + rate, source.frequency.value === 440); + const pcm = (await context.startRendering()).getChannelData(0); + check('quantum boundary PCM ' + rate, pcm.every((x, i) => Math.abs(x - Math.sin(2 * Math.PI * 256 * i / 8192)) < .0001)); + check('first quantum intrinsic ' + rate, source.frequency.value === 256); + } + { + const context = new OfflineAudioContext(1, 128, 8192); + const source = context.createOscillator(); + const filter = context.createBiquadFilter(); + const compressor = context.createDynamicsCompressor(); + filter.frequency.setValueAtTime(1024, 0); + compressor.threshold.setValueAtTime(-50, 0); + source.connect(filter).connect(compressor).connect(context.destination); + source.start(); + await context.startRendering(); + check('biquad first quantum intrinsic', filter.frequency.value === 1024); + check('compressor first quantum intrinsic', compressor.threshold.value === -50); + } + const realtime = new AudioContext(); + check('realtime shared clock', !Object.hasOwn(realtime, 'currentTime') && realtime.currentTime >= 0); + await realtime.close(); + const closedClock = realtime.currentTime; + await Promise.resolve(); + check('closed clock stable', realtime.currentTime === closedClock); + return failures; +})() diff --git a/moli/tests/fetch_cli/eval.rs b/moli/tests/fetch_cli/eval.rs index f14289cc3f..686b8b5a52 100644 --- a/moli/tests/fetch_cli/eval.rs +++ b/moli/tests/fetch_cli/eval.rs @@ -66,7 +66,7 @@ fn run_eval_source( } #[test] -fn eval_can_use_realtime_audio_oscillator_and_analyser_shims() -> Result<()> { +fn eval_can_use_realtime_audio_oscillator_and_analyser_nodes() -> Result<()> { let output = run_eval( "data:text/html,Web Audio", r#"(async () => { @@ -74,7 +74,9 @@ fn eval_can_use_realtime_audio_oscillator_and_analyser_shims() -> Result<()> { const oscillator = context.createOscillator(); const analyser = context.createAnalyser(); oscillator.type = 'triangle'; - oscillator.frequency.setValueAtTime(880, context.currentTime); + oscillator.frequency.value = 880; + const frequency = oscillator.frequency.value; + const scheduled = oscillator.frequency.setValueAtTime(880, context.currentTime); analyser.fftSize = 32; const connected = oscillator.connect(analyser).connect(context.destination); // Before starting the source, the analyser must be silent. Reading @@ -89,7 +91,8 @@ fn eval_can_use_realtime_audio_oscillator_and_analyser_shims() -> Result<()> { await context.close(); return { oscillator: oscillator instanceof OscillatorNode, - frequency: oscillator.frequency.value, + frequency, + scheduledSameParameter: scheduled === oscillator.frequency, analyser: analyser instanceof AnalyserNode, connected: connected === context.destination, readReturnedUndefined: readResult === undefined, @@ -110,6 +113,7 @@ fn eval_can_use_realtime_audio_oscillator_and_analyser_shims() -> Result<()> { serde_json::json!({ "oscillator": true, "frequency": 880, + "scheduledSameParameter": true, "analyser": true, "connected": true, "readReturnedUndefined": true, diff --git a/vendor/web-audio-api-1.7.0/Cargo.toml b/vendor/web-audio-api-1.7.0/Cargo.toml new file mode 100644 index 0000000000..84d51c9cd2 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/Cargo.toml @@ -0,0 +1,179 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.86" +name = "web-audio-api" +version = "1.7.0" +authors = ["Otto "] +build = false +include = [ + "/resources", + "/src", + "LICENSE", + "README.md", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "A pure Rust implementation of the Web Audio API, for use in non-browser contexts" +readme = "README.md" +keywords = [ + "web-audio-api", + "audio", + "sound", + "dsp", +] +categories = ["multimedia::audio"] +license = "MIT" +repository = "https://github.com/orottier/web-audio-api-rs" + +[features] +aac = [] +alac = [] +cpal = ["dep:cpal"] +cpal-asio = [ + "cpal", + "cpal/asio", +] +cpal-jack = [ + "cpal", + "cpal/jack", +] +cpal-pipewire = [ + "cpal", + "cpal/pipewire", +] +cubeb = ["dep:cubeb"] +default = ["cpal"] +diagnostics = [] +flac = [] +iai = [] +m4a = [] +mp3 = [] +ogg = [] +spec-compliant-worklet-inputs = [] +wav = [] + +[lib] +name = "web_audio_api" +path = "src/lib.rs" + +[dependencies.almost] +version = "0.2.0" + +[dependencies.arc-swap] +version = "1.6" + +[dependencies.arrayvec] +version = "0.7" + +[dependencies.cpal] +version = "0.18" +optional = true + +[dependencies.creek] +version = "1.2" +features = [ + "decode", + "decode-all", + "encode-wav", +] +default-features = false + +[dependencies.crossbeam-channel] +version = "0.5" + +[dependencies.cubeb] +version = "0.34" +optional = true + +[dependencies.dasp_sample] +version = "0.11" + +[dependencies.fft-convolver] +version = "0.3" + +[dependencies.float_eq] +version = "1.0" + +[dependencies.futures-channel] +version = "0.3.30" +features = ["sink"] + +[dependencies.futures-core] +version = "0.3.30" +default-features = false + +[dependencies.futures-util] +version = "0.3.30" +features = ["sink"] +default-features = false + +[dependencies.hound] +version = "3.5" + +[dependencies.hrtf] +version = "0.9" + +[dependencies.llq] +version = "0.1.1" + +[dependencies.log] +version = "0.4" + +[dependencies.num-complex] +version = "0.4" + +[dependencies.realfft] +version = "3.3" + +[dependencies.rubato] +version = "0.16" + +[dependencies.smallvec] +version = "1.11" + +[dependencies.symphonia] +version = "0.6" +features = [ + "all", + "opt-simd", +] +default-features = false + +[dependencies.vecmath] +version = "1.0" + +[dev-dependencies.alloc_counter] +version = "0.0.4" + +[dev-dependencies.criterion] +version = "0.8" + +[dev-dependencies.env_logger] +version = "0.11" + +[dev-dependencies.futures] +version = "0.3.30" +features = ["executor"] + +[dev-dependencies.paste] +version = "1.0.14" + +[dev-dependencies.rand] +version = "0.10" + +[target.'cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))'.dependencies.no_denormals] +version = "0.3" diff --git a/vendor/web-audio-api-1.7.0/Cargo.toml.orig b/vendor/web-audio-api-1.7.0/Cargo.toml.orig new file mode 100644 index 0000000000..02f07b44e9 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/Cargo.toml.orig @@ -0,0 +1,105 @@ +[package] +name = "web-audio-api" +version = "1.7.0" +authors = ["Otto "] +edition = "2021" +description = "A pure Rust implementation of the Web Audio API, for use in non-browser contexts" +readme = "README.md" +repository = "https://github.com/orottier/web-audio-api-rs" +keywords = ["web-audio-api", "audio", "sound", "dsp"] +license = "MIT" +categories = ["multimedia::audio"] +include = [ + "/resources", + "/src", + "LICENSE", + "README.md", +] +rust-version = "1.86" + +[dependencies] +almost = "0.2.0" +arc-swap = "1.6" +arrayvec = "0.7" +fft-convolver = "0.3" +cpal = { version = "0.18", optional = true } +creek = { version = "1.2", default-features = false, features = [ + "decode", + "decode-all", + "encode-wav", +] } +crossbeam-channel = "0.5" +cubeb = { version = "0.34", optional = true } +dasp_sample = "0.11" +float_eq = "1.0" +futures-channel = { version = "0.3.30", features = ["sink"] } +futures-core = { version = "0.3.30", default-features = false } +futures-util = { version = "0.3.30", default-features = false, features = [ + "sink", +] } +hound = "3.5" +hrtf = "0.9" +llq = "0.1.1" +log = "0.4" +num-complex = "0.4" +realfft = "3.3" +rubato = "0.16" +smallvec = "1.11" +symphonia = { version = "0.6", default-features = false, features = [ + "all", + "opt-simd", +] } +vecmath = "1.0" + +[target.'cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] +no_denormals = "0.3" + +[dev-dependencies] +futures = { version = "0.3.30", features = ["executor"] } +alloc_counter = "0.0.4" +criterion = "0.8" +env_logger = "0.11" +iai = { git = "https://github.com/sigaloid/iai", rev = "d56a597" } +rand = "0.10" +paste = "1.0.14" + +# Uncomment the following lines to enable debug symbols +# during CPU profiling +# [profile.release] +# debug = true + +[[bench]] +name = "my_benchmark" +# The path is required for excluding /benches from the package. +# See also: https://users.rust-lang.org/t/cargo-publish-with-excluded-benchmark-fails-validation/53444/2 +path = "benches/my_benchmark.rs" +harness = false + +[features] +default = ["cpal"] + +# Expose structured runtime diagnostics for AudioContext and the render graph. +diagnostics = [] + +# Make AudioWorklet inputs follow the Web Audio spec for inactive inputs: +# inputs[n] is an empty slice when no connected source is actively processing. +# This is opt-in for v1 because existing processors may assume a stable channel count. +spec-compliant-worklet-inputs = [] + +cpal = ["dep:cpal"] +cubeb = ["dep:cubeb"] +cpal-jack = ["cpal", "cpal/jack"] +cpal-pipewire = ["cpal", "cpal/pipewire"] +cpal-asio = ["cpal", "cpal/asio"] +iai = [] + +# Legacy codec feature names kept for backwards compatibility. +# Decoding support is no longer split by file format; all supported Symphonia +# and Creek decoders are enabled unconditionally. +mp3 = [] +ogg = [] +flac = [] +wav = [] +aac = [] +m4a = [] +alac = [] diff --git a/vendor/web-audio-api-1.7.0/LICENSE b/vendor/web-audio-api-1.7.0/LICENSE new file mode 100644 index 0000000000..616dc850ee --- /dev/null +++ b/vendor/web-audio-api-1.7.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Otto Rottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/web-audio-api-1.7.0/MOLI_PATCHES.md b/vendor/web-audio-api-1.7.0/MOLI_PATCHES.md new file mode 100644 index 0000000000..aacf65ede4 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/MOLI_PATCHES.md @@ -0,0 +1,26 @@ +# Moli's web-audio-api 1.7.0 patch set + +Source: the published `web-audio-api` 1.7.0 crate, upstream +, commit +`2af21af6ff2bbee17201b2dd2a5ebc56f55c3511`. + +Crate archive SHA-256: +`013903031fb735e7fd9aee7c159f3440a19c6969dc81365aec8c64caddcf39bc`. +The upstream MIT license is retained in `LICENSE`. Source, resources, README +and normalized/original manifests are otherwise copied from that archive. + +Local changes: + +1. `src/context/concrete_base.rs`: defer the HRTF database to the existing + control-thread `PannerNode::set_panning_model(HRTF)` path. Context construction + otherwise eagerly resamples the full database even with the `none` backend + and no panner. This can exhaust a renderer script deadline in debug builds. +2. `src/param.rs`: apply automation events at the quantum boundary before + publishing `AudioParam.value` and sampling k-rate. A k-rate boundary step + updates the scalar buffer; a step later in the quantum remains deferred. + The existing scalar fast path and k-rate computation are retained. + A regression exercises a boundary step and another step within the quantum. + +No host-audio backend is enabled by Moli. The native integration tests cover +PCM, FFT, automation, descriptors, JS reentrancy and document/context cleanup. +The upstream parameter unit tests should also be run when editing this patch. diff --git a/vendor/web-audio-api-1.7.0/README.md b/vendor/web-audio-api-1.7.0/README.md new file mode 100644 index 0000000000..c003aea761 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/README.md @@ -0,0 +1,170 @@ +# Rust Web Audio API + +[![crates.io](https://img.shields.io/crates/v/web-audio-api.svg)](https://crates.io/crates/web-audio-api) +[![docs.rs](https://img.shields.io/docsrs/web-audio-api)](https://docs.rs/web-audio-api) + +A pure Rust implementation of the Web Audio API, for use in non-browser contexts + +## About the Web Audio API + +The [Web Audio API](https://www.w3.org/TR/webaudio/) +([MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)) +provides a powerful and versatile system for controlling audio on the Web, +allowing developers to choose audio sources, add effects to audio, create audio +visualizations, apply spatial effects (such as panning) and much more. + +Our Rust implementation decouples the Web Audio API from the Web. You can now +use it in desktop apps, command line utilities, headless execution, etc. + +## Example usage + +```rust,no_run +use web_audio_api::context::{AudioContext, BaseAudioContext}; +use web_audio_api::node::{AudioNode, AudioScheduledSourceNode}; + +// set up the audio context with optimized settings for your hardware +let context = AudioContext::default(); + +// for background music, read from local file +let file = std::fs::File::open("samples/major-scale.ogg").unwrap(); +let buffer = context.decode_audio_data_sync(file).unwrap(); + +// setup an AudioBufferSourceNode +let mut src = context.create_buffer_source(); +src.set_buffer(buffer); +src.set_loop(true); + +// create a biquad filter +let biquad = context.create_biquad_filter(); +biquad.frequency().set_value(125.); + +// connect the audio nodes +src.connect(&biquad); +biquad.connect(&context.destination()); + +// play the buffer +src.start(); + +// enjoy listening +std::thread::sleep(std::time::Duration::from_secs(4)); +``` + +Check out the [docs](https://docs.rs/web-audio-api) for more info. + +## Spec compliance + +We have tried to stick to the official W3C spec as close as possible, but some +deviations could not be avoided: + +- naming: snake_case instead of CamelCase +- getters/setters methods instead of exposed attributes +- introduced some namespacing +- inheritance is modelled with traits + +## Bindings + +We provide NodeJS bindings to this library over at + so you can use this library +by simply writing native NodeJS code. + +This enables us to run the official [WebAudioAPI test +harness](https://github.com/web-platform-tests/wpt/tree/master/webaudio) and +[track our spec compliance +score](https://github.com/ircam-ismm/node-web-audio-api/issues/57). + +## Audio backends + +By default, the [`cpal`](https://github.com/rustaudio/cpal) library is used for +cross platform audio I/O. + +We offer [experimental support](https://github.com/orottier/web-audio-api-rs/issues/187) for the +[`cubeb`](https://github.com/mozilla/cubeb-rs) backend via the `cubeb` feature +flag. Please note that `cmake` must be installed locally in order to run +`cubeb`. + +| Feature flag | Backends | +| -------------- | -------------------------------------------------------------- | +| cpal (default) | ALSA, WASAPI, CoreAudio, Oboe (Android) | +| cpal-jack | JACK | +| cpal-pipewire | PipeWire | +| cpal-asio | ASIO see | +| cubeb | PulseAudio, AudioUnit, WASAPI, OpenSL, AAudio, sndio, Sun, OSS | + +### Notes for Linux users + +Using the library on Linux with the ALSA backend might lead to unexpected +cranky sound with the default render size (i.e. 128 frames). In such cases, a +simple workaround is to pass the `AudioContextLatencyCategory::Playback` +latency hint when creating the audio context, which will increase the render +size to 1024 frames: + +```rs +let audio_context = AudioContext::new(AudioContextOptions { + latency_hint: AudioContextLatencyCategory::Playback, + ..AudioContextOptions::default() +}); +``` + +For real-time and interactive applications where low latency is crucial, you +should instead rely on the JACK backend provided by `cpal`. To that end you +will need a running JACK server and build your application with the `cpal-jack` +feature, e.g. `cargo run --release --features "cpal-jack" --example +microphone`. + +### Targeting the browser + +We can go full circle and pipe the Rust WebAudio output back into the browser +via `cpal`'s `wasm-bindgen` backend. Check out [an example WASM +project](https://github.com/orottier/wasm-web-audio-rs). +Warning: experimental! + +## Audio decoding support + +Audio decoding is powered by Symphonia. The current implementation enables all +Symphonia-supported audio formats and codecs, including AIFF, CAF, ISO/MP4, +MKV/WebM, Ogg, WAV, AAC, ADPCM, ALAC, FLAC, MP1/MP2/MP3, PCM, and Vorbis. + +## Contributing + +web-audio-api-rs welcomes contribution from everyone in the form of +suggestions, bug reports, pull requests, and feedback. 💛 + +To mirror the CI check locally, run: + +```sh +cargo fmt +cargo build --all-targets +cargo test --all-targets +cargo clippy --all-targets +``` + +The formatting and lint checks remain separate and can be run locally with: + +```sh +pre-commit run --all-files +``` + +If you need ideas for contribution, there are several ways to get started: + +- Try out some of our examples (located in the `examples/` directory) and start + building your own audio graphs +- Found a bug or have a feature request? + [Submit an issue](https://github.com/orottier/web-audio-api-rs/issues/new)! +- Issues labeled with + [good first issue](https://github.com/orottier/web-audio-api-rs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) + are relatively easy starter issues. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in web-audio-api-rs by you, shall be licensed as MIT, without any +additional terms or conditions. + +## License + +This project is licensed under the [MIT license]. + +[mit license]: https://github.com/orottier/web-audio-api-rs/blob/main/LICENSE + +## Acknowledgements + +The IR files used for HRTF spatialization are part of the LISTEN database +created by the EAC team from Ircam. diff --git a/vendor/web-audio-api-1.7.0/resources/IRC_1003_C.bin b/vendor/web-audio-api-1.7.0/resources/IRC_1003_C.bin new file mode 100644 index 0000000000000000000000000000000000000000..fd6ceae47731dc33c62b5910290138f22f8da458 GIT binary patch literal 772656 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%0DTWe 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≤&@WFunkbL3r_9 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�jqpRr&a;)s^mt_Z8a8q( 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¨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 impl Iterator { + let alpha = 0.16; + let a0 = (1. - alpha) / 2.; + let a1 = 1. / 2.; + let a2 = alpha / 2.; + + (0..size).map(move |i| { + a0 - a1 * (2. * PI * i as f32 / size as f32).cos() + + a2 * (4. * PI * i as f32 / size as f32).cos() + }) +} + +pub(crate) const DEFAULT_SMOOTHING_TIME_CONSTANT: f64 = 0.8; +pub(crate) const DEFAULT_MIN_DECIBELS: f64 = -100.; +pub(crate) const DEFAULT_MAX_DECIBELS: f64 = -30.; +pub(crate) const DEFAULT_FFT_SIZE: usize = 2048; + +const MIN_FFT_SIZE: usize = 32; +const MAX_FFT_SIZE: usize = 32768; + +// [spec] This MUST be a power of two in the range 32 to 32768, otherwise an +// IndexSizeError exception MUST be thrown. +#[allow(clippy::manual_range_contains)] +fn assert_valid_fft_size(fft_size: usize) { + assert!( + fft_size.is_power_of_two(), + "IndexSizeError - Invalid fft size: {:?} is not a power of two", + fft_size + ); + + assert!( + fft_size >= MIN_FFT_SIZE && fft_size <= MAX_FFT_SIZE, + "IndexSizeError - Invalid fft size: {:?} is outside range [{:?}, {:?}]", + fft_size, + MIN_FFT_SIZE, + MAX_FFT_SIZE + ); +} + +// [spec] If the value of this attribute is set to a value less than 0 or more +// than 1, an IndexSizeError exception MUST be thrown. +#[allow(clippy::manual_range_contains)] +fn assert_valid_smoothing_time_constant(smoothing_time_constant: f64) { + assert!( + smoothing_time_constant >= 0. && smoothing_time_constant <= 1., + "IndexSizeError - Invalid smoothing time constant: {:?} is outside range [0, 1]", + smoothing_time_constant + ); +} + +// [spec] If the value of minDecibels is set to a value more than or equal to maxDecibels, an +// IndexSizeError exception MUST be thrown. +fn assert_valid_decibels(min_decibels: f64, max_decibels: f64) { + assert!( + min_decibels < max_decibels, + "IndexSizeError - Invalid min decibels: {:?} is greater than or equals to max decibels {:?}", + min_decibels, max_decibels + ); +} + +// as the queue is composed of AtomicF32 having only 1 render quantum of extra +// room should be enough +const RING_BUFFER_SIZE: usize = MAX_FFT_SIZE + RENDER_QUANTUM_SIZE; + +// single producer / multiple consumer ring buffer +#[derive(Clone)] +pub(crate) struct AnalyserRingBuffer { + buffer: Arc<[AtomicF32]>, + write_index: Arc, +} + +impl AnalyserRingBuffer { + pub fn new() -> Self { + let mut buffer = Vec::with_capacity(RING_BUFFER_SIZE); + buffer.resize_with(RING_BUFFER_SIZE, || AtomicF32::new(0.)); + + Self { + buffer: buffer.into(), + write_index: Arc::new(AtomicUsize::new(0)), + } + } + + pub fn write(&self, src: &[f32]) { + let mut write_index = self.write_index.load(Ordering::SeqCst); + let len = src.len(); + + src.iter().enumerate().for_each(|(index, value)| { + let position = (write_index + index) % RING_BUFFER_SIZE; + self.buffer[position].store(*value, Ordering::Relaxed); + }); + + write_index += len; + + if write_index >= RING_BUFFER_SIZE { + write_index -= RING_BUFFER_SIZE; + } + + self.write_index.store(write_index, Ordering::SeqCst); + } + + pub fn read(&self, dst: &mut [f32], max_len: usize) { + let write_index = self.write_index.load(Ordering::SeqCst); + // let fft_size = self.fft_size.load(Ordering::SeqCst); + let len = dst.len().min(max_len); + + dst.iter_mut() + .take(len) + .enumerate() + .for_each(|(index, value)| { + // offset calculation by RING_BUFFER_SIZE so we can't negative values + let position = (RING_BUFFER_SIZE + write_index - len + index) % RING_BUFFER_SIZE; + *value = self.buffer[position].load(Ordering::Relaxed); + }); + } + + // to simply share tests with the unsafe version + #[cfg(test)] + fn raw(&self) -> Vec { + let mut slice = vec![0.; RING_BUFFER_SIZE]; + + self.buffer.iter().zip(slice.iter_mut()).for_each(|(a, b)| { + *b = a.load(Ordering::SeqCst); + }); + + slice + } +} + +// As the analyser is wrapped into a Arc> by the analyser node to get interior +// mutability and expose an immutable public API, we should be ok with thread safety. +pub(crate) struct Analyser { + ring_buffer: AnalyserRingBuffer, + fft_size: usize, + smoothing_time_constant: f64, + min_decibels: f64, + max_decibels: f64, + fft_planner: Mutex>, // RealFftPlanner is not `Sync` on all platforms + fft_input: Vec, + fft_scratch: Vec>, + fft_output: Vec>, + last_fft_output: Vec, + last_fft_time: f64, + blackman: Vec, +} + +impl std::fmt::Debug for Analyser { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Analyser") + .field("fft_size", &self.fft_size()) + .field("smoothing_time_constant", &self.smoothing_time_constant()) + .field("min_decibels", &self.min_decibels()) + .field("max_decibels", &self.max_decibels()) + .finish_non_exhaustive() + } +} + +impl Analyser { + pub fn new() -> Self { + let ring_buffer = AnalyserRingBuffer::new(); + // FFT utils + let mut fft_planner = RealFftPlanner::::new(); + let max_fft = fft_planner.plan_fft_forward(MAX_FFT_SIZE); + + let fft_input = max_fft.make_input_vec(); + let fft_scratch = max_fft.make_scratch_vec(); + let fft_output = max_fft.make_output_vec(); + let mut last_fft_output = Vec::with_capacity(fft_output.len()); + last_fft_output.resize_with(fft_output.len(), || 0.); + + // precalculate Blackman window values, reserve enough space for all input sizes + let mut blackman = Vec::with_capacity(fft_input.len()); + generate_blackman(DEFAULT_FFT_SIZE).for_each(|v| blackman.push(v)); + + Self { + ring_buffer, + fft_size: DEFAULT_FFT_SIZE, + smoothing_time_constant: DEFAULT_SMOOTHING_TIME_CONSTANT, + min_decibels: DEFAULT_MIN_DECIBELS, + max_decibels: DEFAULT_MAX_DECIBELS, + fft_planner: Mutex::new(fft_planner), + fft_input, + fft_scratch, + fft_output, + last_fft_output, + last_fft_time: f64::NEG_INFINITY, + blackman, + } + } + + pub fn get_ring_buffer_clone(&self) -> AnalyserRingBuffer { + self.ring_buffer.clone() + } + + pub fn fft_size(&self) -> usize { + self.fft_size + } + + pub fn set_fft_size(&mut self, fft_size: usize) { + assert_valid_fft_size(fft_size); + + let current_fft_size = self.fft_size; + + if current_fft_size != fft_size { + // reset last fft buffer + self.last_fft_output.iter_mut().for_each(|v| *v = 0.); + // generate blackman window + self.blackman.clear(); + generate_blackman(fft_size).for_each(|v| self.blackman.push(v)); + + self.fft_size = fft_size; + } + } + + pub fn smoothing_time_constant(&self) -> f64 { + self.smoothing_time_constant + } + + pub fn set_smoothing_time_constant(&mut self, value: f64) { + assert_valid_smoothing_time_constant(value); + self.smoothing_time_constant = value; + } + + pub fn min_decibels(&self) -> f64 { + self.min_decibels + } + + pub fn max_decibels(&self) -> f64 { + self.max_decibels + } + + pub fn set_decibels(&mut self, min: f64, max: f64) { + // set them together to avoid invalid intermediate min/max combinations + assert_valid_decibels(min, max); + + self.min_decibels = min; + self.max_decibels = max; + } + + pub fn frequency_bin_count(&self) -> usize { + self.fft_size() / 2 + } + + // [spec] Write the current time-domain data (waveform data) into array. + // If array has fewer elements than the value of fftSize, the excess elements + // will be dropped. If array has more elements than the value of fftSize, + // the excess elements will be ignored. The most recent fftSize frames are + // written (after downmixing) + pub fn get_float_time_domain_data(&self, dst: &mut [f32]) { + let fft_size = self.fft_size(); + self.ring_buffer.read(dst, fft_size); + } + + pub fn get_byte_time_domain_data(&self, dst: &mut [u8]) { + let fft_size = self.fft_size(); + let mut tmp = vec![0.; dst.len()]; + self.ring_buffer.read(&mut tmp, fft_size); + + dst.iter_mut().zip(tmp.iter()).for_each(|(o, i)| { + let scaled = 128. * (1. + i); + let clamped = scaled.clamp(0., 255.); + *o = clamped as u8; + }); + } + + fn compute_fft(&mut self) { + let fft_size = self.fft_size(); + let smoothing_time_constant = self.smoothing_time_constant() as f32; + // setup FFT planner and properly sized buffers + let r2c = self.fft_planner.lock().unwrap().plan_fft_forward(fft_size); + let input = &mut self.fft_input[..fft_size]; + let output = &mut self.fft_output[..fft_size / 2 + 1]; + let scratch = &mut self.fft_scratch[..r2c.get_scratch_len()]; + // we ignore the Nyquist bin in output, see comment below + let last_fft_output = &mut self.last_fft_output[..fft_size / 2]; + + // Compute the current time-domain data. + // The most recent fftSize frames are used in computing the frequency data. + self.ring_buffer.read(input, fft_size); + + // Apply a Blackman window to the time domain input data. + input + .iter_mut() + .zip(self.blackman.iter()) + .for_each(|(i, b)| *i *= *b); + + // Apply a Fourier transform to the windowed time domain input data to + // get real and imaginary frequency data. + r2c.process_with_scratch(input, output, scratch).unwrap(); + + // Notes from chromium source code (tbc) + // + // cf. third_party/blink/renderer/platform/audio/fft_frame.h" + // ``` + // Since x[n] is assumed to be real, X[k] has complex conjugate symmetry with + // X[N-k] = conj(X[k]). Thus, we only need to keep X[k] for k = 0 to N/2. + // But since X[0] is purely real and X[N/2] is also purely real, so we could + // place the real part of X[N/2] in the imaginary part of X[0]. Thus + // for k = 1 to N/2: + // + // real_data[k] = Re(X[k]) + // imag_data[k] = Im(X[k]) + // + // and + // + // real_data[0] = Re(X[0]); + // imag_data[0] = Re(X[N/2]) + // ``` + // + // It seems to be why their FFT return only `fft_size / 2` components + // instead `fft_size * 2 + 1`, they pack DC and Nyquist bins together. + // + // However in their `realtime_analyser` they then remove the packed nyquist + // imaginary component: + // cf. third_party/blink/renderer/modules/webaudio/realtime_analyser.h + // ``` + // // Blow away the packed nyquist component. + // imag[0] = 0; + // ``` + // In our case, it seems we can thus just ignore the Nyquist information + // and take the DC bin as it is + + let normalize_factor = 1. / fft_size as f32; + + last_fft_output + .iter_mut() + .zip(output.iter()) + .for_each(|(o, c)| { + let norm = c.norm() * normalize_factor; + let value = smoothing_time_constant * *o + (1. - smoothing_time_constant) * norm; + *o = if value.is_finite() { value } else { 0. }; + }); + } + + pub fn get_float_frequency_data(&mut self, dst: &mut [f32], current_time: f64) { + let frequency_bin_count = self.frequency_bin_count(); + + // [spec] If another call to getByteFrequencyData() or getFloatFrequencyData() + // occurs within the same render quantum as a previous call, the current + // frequency data is not updated with the same data. Instead, the previously + // computed data is returned. + if current_time != self.last_fft_time { + self.compute_fft(); + self.last_fft_time = current_time; + } + + // [spec] Write the current frequency data into array. If array’s byte + // length is less than frequencyBinCount, the excess elements will be + // dropped. If array’s byte length is greater than the frequencyBinCount + let len = dst.len().min(frequency_bin_count); + + // Convert to dB. + dst.iter_mut() + .take(len) + .zip(self.last_fft_output.iter()) + .for_each(|(v, b)| *v = 20. * b.log10()); + } + + pub fn get_byte_frequency_data(&mut self, dst: &mut [u8], current_time: f64) { + let frequency_bin_count = self.frequency_bin_count(); + let min_decibels = self.min_decibels() as f32; + let max_decibels = self.max_decibels() as f32; + + // [spec] If another call to getByteFrequencyData() or getFloatFrequencyData() + // occurs within the same render quantum as a previous call, the current + // frequency data is not updated with the same data. Instead, the previously + // computed data is returned. + if current_time != self.last_fft_time { + self.compute_fft(); + self.last_fft_time = current_time; + } + + // [spec] Write the current frequency data into array. If array’s byte + // length is less than frequencyBinCount, the excess elements will be + // dropped. If array’s byte length is greater than the frequencyBinCount + let len = dst.len().min(frequency_bin_count); + + // Convert to dB and convert / scale to u8 + dst.iter_mut() + .take(len) + .zip(self.last_fft_output.iter()) + .for_each(|(v, b)| { + let db = 20. * b.log10(); + // 𝑏[𝑘] = ⌊255 / dB𝑚𝑎𝑥−dB𝑚𝑖𝑛 * (𝑌[𝑘]−dB𝑚𝑖𝑛)⌋ + let scaled = 255. / (max_decibels - min_decibels) * (db - min_decibels); + let clamped = scaled.clamp(0., 255.); + *v = clamped as u8; + }); + } +} + +#[cfg(test)] +mod tests { + use std::sync::RwLock; + use std::thread; + + use float_eq::{assert_float_eq, float_eq}; + use rand::RngExt; + + use super::*; + + #[test] + fn test_blackman() { + let values: Vec = generate_blackman(2048).collect(); + + let min = values + .iter() + .fold(1000., |min, &val| if val < min { val } else { min }); + let max = values + .iter() + .fold(0., |max, &val| if val > max { val } else { max }); + assert!(min < 0.01 && min > 0.); + assert!(max > 0.99 && max <= 1.); + + let min_pos = values + .iter() + .position(|&v| float_eq!(v, min, abs_all <= 0.)) + .unwrap(); + let max_pos = values + .iter() + .position(|&v| float_eq!(v, max, abs_all <= 0.)) + .unwrap(); + assert_eq!(min_pos, 0); + assert_eq!(max_pos, 1024); + } + + #[test] + fn test_ring_buffer_write_simple() { + let ring_buffer = AnalyserRingBuffer::new(); + + // check index update + { + // fill the buffer twice so we check the buffer wrap + for i in 1..3 { + for j in 0..(RING_BUFFER_SIZE / RENDER_QUANTUM_SIZE) { + let data = [i as f32; RENDER_QUANTUM_SIZE]; + ring_buffer.write(&data); + + // check write index is properly updated + let write_index = ring_buffer.write_index.load(Ordering::SeqCst); + let expected = + (j * RENDER_QUANTUM_SIZE + RENDER_QUANTUM_SIZE) % RING_BUFFER_SIZE; + + assert_eq!(write_index, expected); + } + + // for each loop check the ring buffer is properly filled + let expected = [i as f32; RING_BUFFER_SIZE]; + + assert_float_eq!(&ring_buffer.raw()[..], &expected[..], abs_all <= 1e-12); + } + } + } + + #[test] + fn test_ring_buffer_write_wrap() { + // check values are written in right place + { + let ring_buffer = AnalyserRingBuffer::new(); + + let offset = 10; + ring_buffer + .write_index + .store(RING_BUFFER_SIZE - offset, Ordering::SeqCst); + + let data = [1.; RENDER_QUANTUM_SIZE]; + ring_buffer.write(&data); + + let mut expected = [0.; RING_BUFFER_SIZE]; + + expected.iter_mut().enumerate().for_each(|(index, v)| { + if index < RENDER_QUANTUM_SIZE - offset || index >= RING_BUFFER_SIZE - offset { + *v = 1. + } else { + *v = 0. + } + }); + + assert_float_eq!(&ring_buffer.raw()[..], &expected[..], abs_all <= 1e-12); + } + + // check values are written in right order + { + let ring_buffer = AnalyserRingBuffer::new(); + let offset = 2; + ring_buffer + .write_index + .store(RING_BUFFER_SIZE - offset, Ordering::SeqCst); + + let data = [1., 2., 3., 4.]; + ring_buffer.write(&data); + + let mut expected = [0.; RING_BUFFER_SIZE]; + expected[RING_BUFFER_SIZE - 2] = 1.; + expected[RING_BUFFER_SIZE - 1] = 2.; + expected[0] = 3.; + expected[1] = 4.; + + assert_float_eq!(&ring_buffer.raw()[..], &expected[..], abs_all <= 1e-12); + } + } + + #[test] + fn test_ring_buffer_read_simple() { + let ring_buffer = Arc::new(AnalyserRingBuffer::new()); + + // first pass + let data = [1.; RENDER_QUANTUM_SIZE]; + ring_buffer.write(&data); + + // index is where it should be + let index = ring_buffer.write_index.load(Ordering::SeqCst); + assert_eq!(index, RENDER_QUANTUM_SIZE); + + let mut read_buffer = [0.; RENDER_QUANTUM_SIZE]; + ring_buffer.read(&mut read_buffer, RENDER_QUANTUM_SIZE); + // data is good + let expected = [1.; RENDER_QUANTUM_SIZE]; + assert_float_eq!(&expected, &read_buffer, abs_all <= 1e-12); + + // second pass + let data = [2.; RENDER_QUANTUM_SIZE]; + ring_buffer.write(&data); + + // index is where it should be + let index = ring_buffer.write_index.load(Ordering::SeqCst); + assert_eq!(index, RENDER_QUANTUM_SIZE * 2); + + let mut read_buffer = [0.; RENDER_QUANTUM_SIZE]; + ring_buffer.read(&mut read_buffer, RENDER_QUANTUM_SIZE); + + let expected = [2.; RENDER_QUANTUM_SIZE]; + assert_float_eq!(&expected, &read_buffer, abs_all <= 1e-12); + + let mut full_buffer_expected = [0.; RING_BUFFER_SIZE]; + full_buffer_expected[0..RENDER_QUANTUM_SIZE].copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); + + full_buffer_expected[RENDER_QUANTUM_SIZE..(RENDER_QUANTUM_SIZE * 2)] + .copy_from_slice(&[2.; RENDER_QUANTUM_SIZE]); + + assert_float_eq!( + &ring_buffer.raw()[..], + &full_buffer_expected[..], + abs_all <= 1e-12 + ); + } + + #[test] + fn test_ring_buffer_read_unwrap() { + // check values are read from right place + { + let ring_buffer = AnalyserRingBuffer::new(); + + let offset = 10; + ring_buffer + .write_index + .store(RING_BUFFER_SIZE - offset, Ordering::SeqCst); + + let data = [1.; RENDER_QUANTUM_SIZE]; + ring_buffer.write(&data); + + let mut read_buffer = [0.; RENDER_QUANTUM_SIZE]; + ring_buffer.read(&mut read_buffer, RENDER_QUANTUM_SIZE); + + assert_float_eq!(&read_buffer, &data, abs_all <= 1e-12); + } + + // check values are read from right place and written in right order + { + let ring_buffer = AnalyserRingBuffer::new(); + let offset = 2; + ring_buffer + .write_index + .store(RING_BUFFER_SIZE - offset, Ordering::SeqCst); + + let data = [1., 2., 3., 4.]; + ring_buffer.write(&data); + + let mut read_buffer = [0.; 4]; + ring_buffer.read(&mut read_buffer, RENDER_QUANTUM_SIZE); + + assert_float_eq!(&read_buffer, &[1., 2., 3., 4.], abs_all <= 1e-12); + } + } + + #[test] + fn test_set_decibels() { + let mut analyser = Analyser::new(); + analyser.set_decibels(-20., 10.); + assert_eq!(analyser.min_decibels(), -20.); + assert_eq!(analyser.max_decibels(), 10.); + } + + #[test] + #[should_panic] + fn test_fft_size_constraints_power_of_two() { + let mut analyser = Analyser::new(); + analyser.set_fft_size(13); + } + + #[test] + #[should_panic] + fn test_fft_size_constraints_ge_min_fft_size() { + let mut analyser = Analyser::new(); + analyser.set_fft_size(MIN_FFT_SIZE / 2); + } + + #[test] + #[should_panic] + fn test_fft_size_constraints_le_max_fft_size() { + let mut analyser = Analyser::new(); + analyser.set_fft_size(MAX_FFT_SIZE * 2); + } + + #[test] + #[should_panic] + fn test_smoothing_time_constant_constraints_lt_zero() { + let mut analyser = Analyser::new(); + analyser.set_smoothing_time_constant(-1.); + } + + #[test] + #[should_panic] + fn test_smoothing_time_constant_constraints_gt_one() { + let mut analyser = Analyser::new(); + analyser.set_smoothing_time_constant(2.); + } + + #[test] + #[should_panic] + fn test_min_decibels_constraints_lt_max_decibels() { + let mut analyser = Analyser::new(); + analyser.set_decibels(DEFAULT_MAX_DECIBELS, analyser.max_decibels()); + } + + #[test] + #[should_panic] + fn test_max_decibels_constraints_lt_min_decibels() { + let mut analyser = Analyser::new(); + analyser.set_decibels(analyser.min_decibels(), DEFAULT_MIN_DECIBELS); + } + + #[test] + fn test_get_float_time_domain_data_vs_fft_size() { + // dst is bigger than fft_size + { + let mut analyser = Analyser::new(); + analyser.set_fft_size(32); + + let data = [1.; RENDER_QUANTUM_SIZE]; + let buffer = analyser.get_ring_buffer_clone(); + buffer.write(&data); + + let mut dst = [0.; RENDER_QUANTUM_SIZE]; + analyser.get_float_time_domain_data(&mut dst); + + let mut expected = [0.; RENDER_QUANTUM_SIZE]; + expected.iter_mut().take(32).for_each(|v| *v = 1.); + + assert_float_eq!(&dst, &expected, abs_all <= 0.); + } + + // dst is smaller than fft_size + { + let mut analyser = Analyser::new(); + analyser.set_fft_size(128); + + let data = [1.; RENDER_QUANTUM_SIZE]; + let buffer = analyser.get_ring_buffer_clone(); + buffer.write(&data); + + let mut dst = [0.; 16]; + analyser.get_float_time_domain_data(&mut dst); + + let expected = [1.; 16]; + + assert_float_eq!(&dst, &expected, abs_all <= 0.); + } + } + + #[test] + fn get_byte_time_domain_data() { + let analyser = Analyser::new(); + + let data = [1.; RENDER_QUANTUM_SIZE]; + let buffer = analyser.get_ring_buffer_clone(); + buffer.write(&data); + + let mut dst = [0; RENDER_QUANTUM_SIZE]; + analyser.get_byte_time_domain_data(&mut dst); + + let expected = [255; RENDER_QUANTUM_SIZE]; + + assert_eq!(&dst, &expected); + + let data = [-1.; RENDER_QUANTUM_SIZE]; + let buffer = analyser.get_ring_buffer_clone(); + buffer.write(&data); + + let mut dst = [0; RENDER_QUANTUM_SIZE]; + analyser.get_byte_time_domain_data(&mut dst); + + let expected = [0; RENDER_QUANTUM_SIZE]; + + assert_eq!(&dst, &expected); + } + + #[test] + fn test_get_float_frequency_data() { + // from https://support.ircam.fr/docs/AudioSculpt/3.0/co/Window%20Size.html + // Let's take a 44100 sampling rate. SR=44100 Hz, F(max) = 22050 Hz. + // With a 1024 window size (512 bins), we get . + // Freq Resolution = 44100/1024 = 43.066 + let sample_rate = 44100.; + let fft_size = 1024; + let freq_resolution = 43.066; + + // note: we don't check all the bin range to keep low tests time + for num_bin in 1..(fft_size / 8) { + // create sines whose frequency centered on `num_bin` bin, we should + // the have highest value in `num_bin` bin + // @note (tbc): bin 0 seems to represent freq_resolution / 2 + let freq = freq_resolution * num_bin as f32; + + let mut analyser = Analyser::new(); + analyser.set_fft_size(fft_size); + + let mut signal = Vec::::with_capacity(fft_size); + + for i in 0..fft_size { + let phase = freq * i as f32 / sample_rate; + let sample = (phase * 2. * PI).sin(); + signal.push(sample); + } + + let ring_buffer = analyser.get_ring_buffer_clone(); + ring_buffer.write(&signal); + + let mut bins = vec![0.; analyser.frequency_bin_count()]; + analyser.get_float_frequency_data(&mut bins[..], 0.); + + let highest = bins[num_bin]; + + bins.iter().enumerate().for_each(|(index, db)| { + if index != num_bin { + assert!(db < &highest); + } + }); + } + } + + #[test] + fn test_get_float_frequency_data_vs_frequenc_bin_count() { + let mut analyser = Analyser::new(); + analyser.set_fft_size(RENDER_QUANTUM_SIZE); + + // get data, should be zero (negative infinity decibel) + let mut bins = vec![-1.; RENDER_QUANTUM_SIZE]; + analyser.get_float_frequency_data(&mut bins[..], 0.); + + // only N / 2 values should contain frequency data, rest is unaltered + assert!( + bins[0..(RENDER_QUANTUM_SIZE / 2)] == [f32::NEG_INFINITY; (RENDER_QUANTUM_SIZE / 2)] + ); + assert_float_eq!( + &bins[(RENDER_QUANTUM_SIZE / 2)..], + &[-1.; (RENDER_QUANTUM_SIZE / 2)][..], + abs_all <= 0. + ); + } + + #[test] + fn test_get_byte_frequency_data_vs_frequenc_bin_count() { + let mut analyser = Analyser::new(); + analyser.set_fft_size(RENDER_QUANTUM_SIZE); + + // get data, should be zero (negative infinity decibel) + let mut bins = [255; RENDER_QUANTUM_SIZE]; + analyser.get_byte_frequency_data(&mut bins[..], 0.); + + // only N / 2 values should contain frequency data, rest is unaltered + assert!(bins[0..(RENDER_QUANTUM_SIZE / 2)] == [0; (RENDER_QUANTUM_SIZE / 2)]); + assert!(bins[(RENDER_QUANTUM_SIZE / 2)..] == [255; (RENDER_QUANTUM_SIZE / 2)][..],); + } + + // this mostly tries to show that it works concurrently and we don't fall into + // SEGFAULT traps or something, but this is difficult to really test something + // in an accurante way, other tests are there for such thing + #[test] + fn test_ring_buffer_concurrency() { + let analyser = Arc::new(Analyser::new()); + let ring_buffer = analyser.get_ring_buffer_clone(); + let num_loops = 10_000; + let (sender, receiver) = crossbeam_channel::bounded(1); + + thread::spawn(move || { + let mut rng = rand::rng(); + sender.send(()).unwrap(); // signal ready + + for _ in 0..num_loops { + let rand = rng.random::(); + let data = [rand; RENDER_QUANTUM_SIZE]; + ring_buffer.write(&data); + + std::thread::sleep(std::time::Duration::from_nanos(30)); + } + }); + + // wait for thread to boot + receiver.recv().unwrap(); + + for _ in 0..num_loops { + let mut read_buffer = [0.; RENDER_QUANTUM_SIZE]; + analyser.get_float_time_domain_data(&mut read_buffer); + std::thread::sleep(std::time::Duration::from_nanos(25)); + } + } + + #[test] + fn test_thread_safety() { + let analyser = Arc::new(RwLock::new(Analyser::new())); + + let handle = thread::spawn(move || { + analyser.write().unwrap().set_fft_size(MIN_FFT_SIZE); + assert_eq!(analyser.write().unwrap().fft_size(), MIN_FFT_SIZE); + }); + + handle.join().unwrap(); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/buffer.rs b/vendor/web-audio-api-1.7.0/src/buffer.rs new file mode 100644 index 0000000000..60561b1dde --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/buffer.rs @@ -0,0 +1,817 @@ +//! General purpose audio signal data structures +use std::sync::Arc; + +use crate::{ + assert_valid_buffer_length, assert_valid_channel_number, assert_valid_number_of_channels, + assert_valid_sample_rate, +}; + +/// Options for constructing an [`AudioBuffer`] +// dictionary AudioBufferOptions { +// unsigned long numberOfChannels = 1; +// required unsigned long length; +// required float sampleRate; +// }; +#[derive(Clone, Debug)] +pub struct AudioBufferOptions { + /// The number of channels for the buffer + pub number_of_channels: usize, + /// The length in sample frames of the buffer + pub length: usize, + /// The sample rate in Hz for the buffer + pub sample_rate: f32, +} + +/// Memory-resident audio asset, basically a matrix of channels * samples +/// +/// An AudioBuffer has copy-on-write semantics, so it is cheap to clone. +/// +/// - MDN documentation: +/// - specification: +/// - see also: [`BaseAudioContext::create_buffer`](crate::context::BaseAudioContext::create_buffer) +/// +/// # Usage +/// +/// ```no_run +/// use std::f32::consts::PI; +/// use web_audio_api::context::{AudioContext, BaseAudioContext}; +/// use web_audio_api::node::{AudioNode, AudioScheduledSourceNode}; +/// +/// let context = AudioContext::default(); +/// +/// let length = context.sample_rate() as usize; +/// let sample_rate = context.sample_rate(); +/// let mut buffer = context.create_buffer(1, length, sample_rate); +/// +/// // fill buffer with a sine wave +/// let mut sine = vec![]; +/// +/// for i in 0..length { +/// let phase = i as f32 / length as f32 * 2. * PI * 200.; +/// sine.push(phase.sin()); +/// } +/// +/// buffer.copy_to_channel(&sine, 0); +/// +/// // play the buffer in a loop +/// let mut src = context.create_buffer_source(); +/// src.set_buffer(buffer.clone()); +/// src.set_loop(true); +/// src.connect(&context.destination()); +/// src.start(); +/// ``` +/// +/// # Example +/// +/// - `cargo run --release --example audio_buffer` +/// +#[derive(Clone, Debug)] +pub struct AudioBuffer { + pub(crate) channels: Vec, + pub(crate) sample_rate: f32, +} + +impl AudioBuffer { + /// Allocate a silent audiobuffer with [`AudioBufferOptions`] + /// + /// # Panics + /// + /// This function will panic if: + /// - the given sample rate is zero + /// - the given number of channels is outside the [1, 32] range, + /// 32 being defined by the MAX_CHANNELS constant. + pub fn new(options: AudioBufferOptions) -> Self { + assert_valid_sample_rate(options.sample_rate); + assert_valid_buffer_length(options.length); + assert_valid_number_of_channels(options.number_of_channels); + + let silence = ChannelData::new(options.length); + + Self { + channels: vec![silence; options.number_of_channels], + sample_rate: options.sample_rate, + } + } + + /// Convert raw samples to an AudioBuffer + /// + /// The outer Vec determine the channels. The inner Vecs should have the same length. + /// + /// # Panics + /// + /// This function will panic if: + /// - the given sample rate is zero + /// - the given number of channels defined by `samples.len()`is outside the + /// [1, 32] range, 32 being defined by the MAX_CHANNELS constant. + /// - any of its items have different lengths + pub fn from(samples: Vec>, sample_rate: f32) -> Self { + assert_valid_sample_rate(sample_rate); + assert_valid_number_of_channels(samples.len()); + + let channels: Vec<_> = samples.into_iter().map(ChannelData::from).collect(); + if !channels.iter().all(|c| c.len() == channels[0].len()) { + panic!("Trying to create AudioBuffer from channel data with unequal length"); + } + + Self { + channels, + sample_rate, + } + } + + /// Number of channels in this `AudioBuffer` + pub fn number_of_channels(&self) -> usize { + self.channels.len() + } + + /// Number of samples per channel in this `AudioBuffer` + pub fn length(&self) -> usize { + self.channels.first().map(ChannelData::len).unwrap_or(0) + } + + /// Sample rate of this `AudioBuffer` in Hertz + pub fn sample_rate(&self) -> f32 { + self.sample_rate + } + + /// Duration in seconds of the `AudioBuffer` + pub fn duration(&self) -> f64 { + self.length() as f64 / self.sample_rate as f64 + } + + /// Copy data from a given channel to the given `Vec` + /// + /// # Panics + /// + /// This function will panic if `channel_number` is greater or equal than + /// `AudioBuffer::number_of_channels()` + pub fn copy_from_channel(&self, destination: &mut [f32], channel_number: usize) { + assert_valid_channel_number(channel_number, self.number_of_channels()); + + self.copy_from_channel_with_offset(destination, channel_number, 0); + } + + /// Copy data from a given channel to the given `Vec` starting at `offset` + /// + /// # Panics + /// + /// This function will panic if: + /// - the given channel number is greater than or equal to the given number of channels. + pub fn copy_from_channel_with_offset( + &self, + destination: &mut [f32], + channel_number: usize, + offset: usize, + ) { + assert_valid_channel_number(channel_number, self.number_of_channels()); + let offset = offset.min(self.length()); + // [spec] Let buffer be the AudioBuffer with 𝑁𝑏 frames, let 𝑁𝑓 be the number + // of elements in the destination array, and 𝑘 be the value of bufferOffset. + // Then the number of frames copied from buffer to destination is max(0,min(𝑁𝑏−𝑘,𝑁𝑓)). + // If this is less than 𝑁𝑓, then the remaining elements of destination are not modified. + let dest_length = destination.len(); + let max_frame = (self.length() - offset).clamp(0, dest_length); + let channel = self.channel_data(channel_number).as_slice(); + + destination[..max_frame].copy_from_slice(&channel[offset..(max_frame + offset)]); + } + + /// Copy data from a given source to the given channel. + /// + /// # Panics + /// + /// This function will panic if: + /// - the given channel number is greater than or equal to the given number of channels. + pub fn copy_to_channel(&mut self, source: &[f32], channel_number: usize) { + assert_valid_channel_number(channel_number, self.number_of_channels()); + + self.copy_to_channel_with_offset(source, channel_number, 0); + } + + /// Copy data from a given source to the given channel starting at `offset`. + /// + /// # Panics + /// + /// This function will panic if: + /// - the given channel number is greater than or equal to the given number of channels. + pub fn copy_to_channel_with_offset( + &mut self, + source: &[f32], + channel_number: usize, + offset: usize, + ) { + assert_valid_channel_number(channel_number, self.number_of_channels()); + let offset = offset.min(self.length()); + // [spec] Let buffer be the AudioBuffer with 𝑁𝑏 frames, let 𝑁𝑓 be the number + // of elements in the source array, and 𝑘 be the value of bufferOffset. Then + // the number of frames copied from source to the buffer is max(0,min(𝑁𝑏−𝑘,𝑁𝑓)). + // If this is less than 𝑁𝑓, then the remaining elements of buffer are not modified. + let src_len = source.len(); + let max_frame = (self.length() - offset).clamp(0, src_len); + let channel = self.channel_data_mut(channel_number).as_mut_slice(); + + channel[offset..(max_frame + offset)].copy_from_slice(&source[..max_frame]); + } + + /// Return a read-only copy of the underlying data of the channel + /// + /// # Panics + /// + /// This function will panic if: + /// - the given channel number is greater than or equal to the given number of channels. + pub fn get_channel_data(&self, channel_number: usize) -> &[f32] { + assert_valid_channel_number(channel_number, self.number_of_channels()); + // [spec] According to the rules described in acquire the content either allow writing + // into or getting a copy of the bytes stored in [[internal data]] in a new Float32Array + self.channel_data(channel_number).as_slice() + } + + /// Return a mutable slice of the underlying data of the channel + /// + /// # Panics + /// + /// This function will panic if: + /// - the given channel number is greater than or equal to the given number of channels. + pub fn get_channel_data_mut(&mut self, channel_number: usize) -> &mut [f32] { + assert_valid_channel_number(channel_number, self.number_of_channels()); + self.channel_data_mut(channel_number).as_mut_slice() + } + + /// Create a multi-channel audiobuffer directly from `ChannelData`s. + // @todo - remove in favor of `AudioBuffer::from` + pub(crate) fn from_channels(channels: Vec, sample_rate: f32) -> Self { + Self { + channels, + sample_rate, + } + } + + /// Channel data as slice + pub(crate) fn channels(&self) -> &[ChannelData] { + &self.channels + } + + /// Channel data as slice (mutable) + pub(crate) fn channels_mut(&mut self) -> &mut [ChannelData] { + &mut self.channels + } + + /// Get the samples from this specific channel. + /// + /// Panics if the index is greater than the available number of channels + // @note - this one is used in + pub(crate) fn channel_data(&self, index: usize) -> &ChannelData { + &self.channels[index] + } + + /// Get the samples (mutable) from this specific channel. + /// + /// Panics if the index is greater than the available number of channels + pub(crate) fn channel_data_mut(&mut self, index: usize) -> &mut ChannelData { + &mut self.channels[index] + } + + /// Extends an AudioBuffer with the contents of another. + /// + /// This function will panic if the sample_rate and channel_count are not equal + pub(crate) fn extend(&mut self, other: &Self) { + assert_eq!(self.sample_rate, other.sample_rate); + assert_eq!(self.number_of_channels(), other.number_of_channels()); + + let data = self.channels_mut(); + data.iter_mut() + .zip(other.channels.iter()) + .for_each(|(channel, other_channel)| { + let cur_channel_data = Arc::make_mut(&mut channel.data); + cur_channel_data.extend(other_channel.as_slice()); + }) + } + + /// Split an AudioBuffer in two at the given index. + pub(crate) fn split_off(&mut self, index: usize) -> Self { + let channels: Vec<_> = self + .channels_mut() + .iter_mut() + .map(|channel_data| Arc::make_mut(&mut channel_data.data).split_off(index)) + .map(ChannelData::from) + .collect(); + + AudioBuffer::from_channels(channels, self.sample_rate) + } + + /// Resample to the desired sample rate. The method performs a simple linear + /// interpolation an keep the first and last sample intact. The new number + /// of samples is always ceiled according the ratio defined by old and new + /// sample rates. + /// + /// # Panics + /// + /// This function will panic if: + /// - the given sample rate is zero + pub(crate) fn resample(&mut self, sample_rate: f32) { + assert_valid_sample_rate(sample_rate); + + // if requested sample rate is very similar, do not resample + if float_eq::float_eq!(self.sample_rate, sample_rate, abs <= 0.1) { + self.sample_rate = sample_rate; + return; + } + + // handle zero length case + if self.length() == 0 { + self.sample_rate = sample_rate; + return; + } + + let source_sr = self.sample_rate as f64; + let target_sr = sample_rate as f64; + let ratio = target_sr / source_sr; + let source_length = self.length(); + let target_length = (self.length() as f64 * ratio).ceil() as usize; + + let num_channels = self.number_of_channels(); + let mut resampled = Vec::>::with_capacity(num_channels); + resampled.resize_with(num_channels, || Vec::::with_capacity(target_length)); + + for i in 0..target_length { + let position = i as f64 / (target_length - 1) as f64; // [0., 1.] + let playhead = position * (source_length - 1) as f64; + let playhead_floored = playhead.floor(); + let prev_index = playhead_floored as usize; + let next_index = (prev_index + 1).min(source_length - 1); + + let k = (playhead - playhead_floored) as f32; + let k_inv = 1. - k; + + for (channel, resampled_data) in resampled.iter_mut().enumerate() { + let prev_sample = self.channels[channel].data[prev_index]; + let next_sample = self.channels[channel].data[next_index]; + + let value = k_inv * prev_sample + k * next_sample; + resampled_data.push(value); + } + } + + self.channels + .iter_mut() + .zip(resampled) + .for_each(|(channel_data, resampled_data)| { + channel_data.data = Arc::new(resampled_data); + }); + + self.sample_rate = sample_rate; + } +} + +/// Single channel audio samples, basically wraps a `Arc>` +/// +/// ChannelData has copy-on-write semantics, so it is cheap to clone. +#[derive(Clone, PartialEq)] +pub(crate) struct ChannelData { + data: Arc>, +} + +impl std::fmt::Debug for ChannelData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ChannelData") + .field("len", &self.len()) + .finish_non_exhaustive() + } +} + +impl ChannelData { + pub fn new(length: usize) -> Self { + let buffer = vec![0.; length]; + let data = Arc::new(buffer); + + Self { data } + } + + pub fn from(data: Vec) -> Self { + Self { + data: Arc::new(data), + } + } + + pub fn len(&self) -> usize { + self.data.len() + } + + // clippy wants to keep it, so keep it :) + #[allow(dead_code)] + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + pub fn as_slice(&self) -> &[f32] { + &self.data[..] + } + + pub fn as_mut_slice(&mut self) -> &mut [f32] { + &mut Arc::make_mut(&mut self.data)[..] + } +} + +#[cfg(test)] +mod tests { + use float_eq::assert_float_eq; + use std::f32::consts::PI; + + use super::*; + + #[test] + fn test_constructor() { + let options = AudioBufferOptions { + number_of_channels: 1, + length: 96000, + sample_rate: 48000., + }; + + let audio_buffer = AudioBuffer::new(options); + + assert_eq!(audio_buffer.number_of_channels(), 1); + assert_eq!(audio_buffer.length(), 96000); + assert_float_eq!(audio_buffer.sample_rate(), 48000., abs <= 0.); + assert_float_eq!(audio_buffer.duration(), 2., abs <= 0.); + } + + #[test] + #[should_panic] + fn test_zero_channels() { + let options = AudioBufferOptions { + number_of_channels: 0, + length: 10, + sample_rate: 48000., + }; + + AudioBuffer::new(options); // should panic + } + + #[test] + #[should_panic] + fn test_zero_channels_from() { + let samples = vec![]; + let sample_rate = 48000.; + + AudioBuffer::from(samples, sample_rate); // should panic + } + + #[test] + #[should_panic] + fn test_invalid_sample_rate() { + let options = AudioBufferOptions { + number_of_channels: 1, + length: 10, + sample_rate: 0., + }; + + AudioBuffer::new(options); // should panic + } + + #[test] + #[should_panic] + fn test_invalid_sample_rate_from() { + let samples = vec![vec![0.]]; + let sample_rate = 0.; + + AudioBuffer::from(samples, sample_rate); // should panic + } + + #[test] + #[should_panic] + fn test_invalid_length() { + let options = AudioBufferOptions { + number_of_channels: 1, + length: 0, + sample_rate: 48000., + }; + + AudioBuffer::new(options); // should panic + } + + #[test] + fn test_channel_data_get_set() { + let options = AudioBufferOptions { + number_of_channels: 1, + length: 10, + sample_rate: 48000., + }; + + let mut audio_buffer = AudioBuffer::new(options); + audio_buffer.channel_data_mut(0).as_mut_slice().fill(1.); + assert_float_eq!( + audio_buffer.channel_data(0).as_slice()[..], + [1.; 10][..], + abs_all <= 0. + ); + } + + #[test] + #[should_panic] + fn test_invalid_copy_from_channel() { + let options = AudioBufferOptions { + number_of_channels: 1, + length: 10, + sample_rate: 48000., + }; + + let audio_buffer = AudioBuffer::new(options); + + // same size + let mut dest = vec![1.; 10]; + audio_buffer.copy_from_channel(&mut dest, 1); + } + + #[test] + fn test_copy_from_channel() { + let options = AudioBufferOptions { + number_of_channels: 1, + length: 10, + sample_rate: 48000., + }; + + let audio_buffer = AudioBuffer::new(options); + + // same size + let mut dest = vec![1.; 10]; + audio_buffer.copy_from_channel(&mut dest, 0); + assert_float_eq!(dest[..], vec![0.; 10][..], abs_all <= 0.); + + // smaller destination + let mut dest = vec![1.; 5]; + audio_buffer.copy_from_channel(&mut dest, 0); + assert_float_eq!(dest[..], [0., 0., 0., 0., 0.][..], abs_all <= 0.); + + // larger destination + let mut dest = vec![1.; 11]; + audio_buffer.copy_from_channel(&mut dest, 0); + assert_float_eq!( + dest[..], + [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1.][..], + abs_all <= 0. + ); + + // with offset + let mut dest = vec![1.; 10]; + audio_buffer.copy_from_channel_with_offset(&mut dest, 0, 5); + assert_float_eq!( + dest[..], + [0., 0., 0., 0., 0., 1., 1., 1., 1., 1.][..], + abs_all <= 0. + ); + + // w/ offset outside range + let mut dest = vec![1.; 10]; + audio_buffer.copy_from_channel_with_offset(&mut dest, 0, usize::MAX); + + assert_float_eq!(dest[..], vec![1.; 10][..], abs_all <= 0.); + } + + #[test] + #[should_panic] + fn test_invalid_copy_to_channel() { + let options = AudioBufferOptions { + number_of_channels: 1, + length: 10, + sample_rate: 48000., + }; + + let mut audio_buffer = AudioBuffer::new(options); + + // same size + let src = vec![1.; 10]; + audio_buffer.copy_to_channel(&src, 1); + } + + #[test] + fn test_copy_to_channel() { + let options = AudioBufferOptions { + number_of_channels: 1, + length: 10, + sample_rate: 48000., + }; + + { + // same size + let mut audio_buffer = AudioBuffer::new(options.clone()); + let src = vec![1.; 10]; + audio_buffer.copy_to_channel(&src, 0); + assert_float_eq!( + audio_buffer.channel_data(0).as_slice()[..], + [1.; 10][..], + abs_all <= 0. + ); + } + + { + // smaller source + let mut audio_buffer = AudioBuffer::new(options.clone()); + let src = vec![1.; 5]; + audio_buffer.copy_to_channel(&src, 0); + assert_float_eq!( + audio_buffer.channel_data(0).as_slice()[..], + [1., 1., 1., 1., 1., 0., 0., 0., 0., 0.][..], + abs_all <= 0. + ); + } + + { + // larger source + let mut audio_buffer = AudioBuffer::new(options.clone()); + let src = vec![1.; 12]; + audio_buffer.copy_to_channel(&src, 0); + assert_float_eq!( + audio_buffer.channel_data(0).as_slice()[..], + [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.][..], + abs_all <= 0. + ); + } + + { + // w/ offset + let mut audio_buffer = AudioBuffer::new(options.clone()); + let src = vec![1.; 10]; + audio_buffer.copy_to_channel_with_offset(&src, 0, 5); + assert_float_eq!( + audio_buffer.channel_data(0).as_slice()[..], + [0., 0., 0., 0., 0., 1., 1., 1., 1., 1.][..], + abs_all <= 0. + ); + } + + { + // w/ offset outside range + let mut audio_buffer = AudioBuffer::new(options); + let src = vec![1.; 10]; + audio_buffer.copy_to_channel_with_offset(&src, 0, usize::MAX); + assert_float_eq!( + audio_buffer.channel_data(0).as_slice()[..], + [0.; 10][..], + abs_all <= 0. + ); + } + } + + #[test] + #[should_panic] + fn test_invalid_get_channel_data() { + let options = AudioBufferOptions { + number_of_channels: 1, + length: 10, + sample_rate: 48000., + }; + + let audio_buffer = AudioBuffer::new(options); + + audio_buffer.get_channel_data(1); + } + + // internal API + #[test] + fn test_silent() { + let options = AudioBufferOptions { + number_of_channels: 2, + length: 10, + sample_rate: 44100., + }; + let b = AudioBuffer::new(options); + + assert_eq!(b.length(), 10); + assert_eq!(b.number_of_channels(), 2); + assert_float_eq!(b.sample_rate(), 44100., abs_all <= 0.); + assert_float_eq!(b.channel_data(0).as_slice(), &[0.; 10][..], abs_all <= 0.); + assert_float_eq!(b.channel_data(1).as_slice(), &[0.; 10][..], abs_all <= 0.); + assert_eq!(b.channels().get(2), None); + } + + #[test] + fn test_concat() { + let options = AudioBufferOptions { + number_of_channels: 2, + length: 5, + sample_rate: 44100., + }; + let mut b1 = AudioBuffer::new(options.clone()); + let b2 = AudioBuffer::new(options); + b1.extend(&b2); + + assert_eq!(b1.length(), 10); + assert_eq!(b1.number_of_channels(), 2); + assert_float_eq!(b1.sample_rate(), 44100., abs_all <= 0.); + + let channel_data = ChannelData::from(vec![1.; 5]); + let b3 = AudioBuffer::from_channels(vec![channel_data; 2], 44100.); + + b1.extend(&b3); + + assert_eq!(b1.length(), 15); + assert_eq!(b1.number_of_channels(), 2); + assert_float_eq!(b1.sample_rate(), 44100., abs_all <= 0.); + assert_float_eq!( + b1.channel_data(0).as_slice(), + &[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1.][..], + abs_all <= 0. + ); + } + + #[test] + #[should_panic] + fn test_resample_to_zero_hertz() { + let channel = ChannelData::from(vec![1., 2., 3., 4., 5.]); + let mut buffer = AudioBuffer::from_channels(vec![channel], 48000.); + buffer.resample(0.); + } + + #[test] + fn test_resample_from_empty() { + let channel = ChannelData::from(vec![]); + let mut buffer = AudioBuffer::from_channels(vec![channel], 48000.); + buffer.resample(48000.); + + assert_eq!(buffer.length(), 0); + assert_float_eq!(buffer.sample_rate, 48000., abs_all <= 0.); + } + + #[test] + fn test_upsample() { + let channel = ChannelData::from(vec![1., 2., 3., 4., 5.]); + let mut buffer = AudioBuffer::from_channels(vec![channel], 48000.); + buffer.resample(96000.); // double + + let mut expected = [0.; 10]; + let incr = 4. / 9.; // (5 - 1) / (10 - 1) + + for (i, value) in expected.iter_mut().enumerate() { + *value = 1. + incr * i as f32; + } + + assert_float_eq!( + buffer.channel_data(0).as_slice(), + &expected[..], + abs_all <= 1e-6 + ); + + assert_float_eq!(buffer.sample_rate, 96000., abs_all <= 0.); + } + + #[test] + fn test_downsample() { + let channel = ChannelData::from(vec![1., 2., 3., 4., 5.]); + let mut buffer = AudioBuffer::from_channels(vec![channel], 96000.); + buffer.resample(48000.); // half + + assert_float_eq!( + buffer.channel_data(0).as_slice(), + &[1., 3., 5.][..], + abs_all <= 0. + ); + + assert_float_eq!(buffer.sample_rate, 48000., abs_all <= 0.); + } + + #[test] + fn test_resample_stereo() { + [22500, 38000, 48000, 96000].iter().for_each(|sr| { + let source_sr = *sr; + let target_sr = 44_100; + + let mut left = Vec::::with_capacity(source_sr); + let mut right = Vec::::with_capacity(source_sr); + + for i in 0..source_sr { + let phase = i as f32 / source_sr as f32 * 2. * PI; + left.push(phase.sin()); + right.push(phase.cos()); + } + + let left_chan = ChannelData::from(left); + let right_chan = ChannelData::from(right); + let mut buffer = + AudioBuffer::from_channels(vec![left_chan, right_chan], source_sr as f32); + buffer.resample(target_sr as f32); + + let mut expected_left = vec![]; + let mut expected_right = vec![]; + + for i in 0..target_sr { + let phase = i as f32 / target_sr as f32 * 2. * PI; + expected_left.push(phase.sin()); + expected_right.push(phase.cos()); + } + + assert_float_eq!( + buffer.get_channel_data(0)[..], + &expected_left[..], + abs_all <= 1e-3 + ); + + assert_float_eq!( + buffer.get_channel_data(1)[..], + &expected_right[..], + abs_all <= 1e-3 + ); + + assert_float_eq!(buffer.sample_rate, target_sr as f32, abs_all <= 0.); + }); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/capacity.rs b/vendor/web-audio-api-1.7.0/src/capacity.rs new file mode 100644 index 0000000000..bdc9726955 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/capacity.rs @@ -0,0 +1,283 @@ +use crossbeam_channel::Sender; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use crate::context::{BaseAudioContext, ConcreteBaseAudioContext}; +use crate::events::{EventDispatch, EventHandler, EventPayload, EventType}; +use crate::stats::{AudioStats, AudioStatsSnapshot}; +use crate::Event; + +/// Options for constructing an `AudioRenderCapacity` +#[derive(Clone, Debug)] +pub struct AudioRenderCapacityOptions { + /// An update interval (in seconds) for dispatching [`AudioRenderCapacityEvent`]s + pub update_interval: f64, +} + +impl Default for AudioRenderCapacityOptions { + fn default() -> Self { + Self { + update_interval: 1., + } + } +} + +/// Performance metrics of the rendering thread +#[derive(Clone, Debug)] +pub struct AudioRenderCapacityEvent { + /// The start time of the data collection period in terms of the associated AudioContext's currentTime + pub timestamp: f64, + /// An average of collected load values over the given update interval + pub average_load: f64, + /// A maximum value from collected load values over the given update interval. + pub peak_load: f64, + /// A ratio between the number of buffer underruns and the total number of system-level audio callbacks over the given update interval. + pub underrun_ratio: f64, + /// Inherits from this base Event + pub event: Event, +} + +impl AudioRenderCapacityEvent { + fn new(timestamp: f64, average_load: f64, peak_load: f64, underrun_ratio: f64) -> Self { + // We are limiting the precision here conform + // https://webaudio.github.io/web-audio-api/#dom-audiorendercapacityevent-averageload + Self { + timestamp, + average_load: (average_load * 100.).round() / 100., + peak_load: (peak_load * 100.).round() / 100., + underrun_ratio: (underrun_ratio * 100.).ceil() / 100., + event: Event { + type_: "AudioRenderCapacityEvent", + }, + } + } +} + +/// Provider for rendering performance metrics +/// +/// A load value is computed for each system-level audio callback, by dividing its execution +/// duration by the system-level audio callback buffer size divided by the sample rate. +/// +/// Ideally the load value is below 1.0, meaning that it took less time to render the audio than it +/// took to play it out. An audio buffer underrun happens when this load value is greater than 1.0: the +/// system could not render audio fast enough for real-time. +#[derive(Clone)] +pub struct AudioRenderCapacity { + context: ConcreteBaseAudioContext, + stats: AudioStats, + stop_send: Arc>>>, +} + +impl std::fmt::Debug for AudioRenderCapacity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AudioRenderCapacity") + .field( + "context", + &format!("BaseAudioContext@{}", self.context.address()), + ) + .finish_non_exhaustive() + } +} + +impl AudioRenderCapacity { + pub(crate) fn new(context: ConcreteBaseAudioContext, stats: AudioStats) -> Self { + let stop_send = Arc::new(Mutex::new(None)); + + Self { + context, + stats, + stop_send, + } + } + + /// Start metric collection and analysis + #[allow(clippy::missing_panics_doc)] + pub fn start(&self, options: AudioRenderCapacityOptions) { + // stop current metric collection, if any + self.stop(); + + let (stop_send, stop_recv) = crossbeam_channel::bounded(0); + *self.stop_send.lock().unwrap() = Some(stop_send); + + let mut timestamp: f64 = self.context.current_time(); + let update_interval = Duration::from_secs_f64(options.update_interval.max(0.001)); + let base_context = self.context.clone(); + let stats = self.stats.clone(); + let mut previous = stats.snapshot(); + stats.take_peak_load(); + std::thread::spawn(move || loop { + if stop_recv.recv_timeout(update_interval).is_ok() { + return; + } + + let next = stats.snapshot(); + if next.callback_count == previous.callback_count { + continue; + } + + let peak_load = stats.take_peak_load(); + let event = render_capacity_event(timestamp, previous, next, peak_load); + + let send_result = base_context.send_event(EventDispatch::render_capacity(event)); + if send_result.is_err() { + break; + }; + + previous = next; + timestamp = base_context.current_time(); + }); + } + + /// Stop metric collection and analysis + #[allow(clippy::missing_panics_doc)] + pub fn stop(&self) { + // halt callback thread + if let Some(stop_send) = self.stop_send.lock().unwrap().take() { + let _ = stop_send.send(()); + } + } + + /// The EventHandler for [`AudioRenderCapacityEvent`]. + /// + /// Only a single event handler is active at any time. Calling this method multiple times will + /// override the previous event handler. + pub fn set_onupdate( + &self, + mut callback: F, + ) { + let callback = move |v| match v { + EventPayload::RenderCapacity(v) => callback(v), + _ => unreachable!(), + }; + + self.context.set_event_handler( + EventType::RenderCapacity, + EventHandler::Multiple(Box::new(callback)), + ); + } + + /// Unset the EventHandler for [`AudioRenderCapacityEvent`]. + pub fn clear_onupdate(&self) { + self.context.clear_event_handler(EventType::RenderCapacity); + } +} + +fn render_capacity_event( + timestamp: f64, + previous: AudioStatsSnapshot, + next: AudioStatsSnapshot, + peak_load: f64, +) -> AudioRenderCapacityEvent { + let callback_count = next + .callback_count + .saturating_sub(previous.callback_count) + .max(1); + let render_duration = next + .render_duration_ns_total + .saturating_sub(previous.render_duration_ns_total); + let callback_budget = next + .callback_budget_ns_total + .saturating_sub(previous.callback_budget_ns_total); + let underruns = next.underrun_count.saturating_sub(previous.underrun_count); + + let average_load = if callback_budget == 0 { + 0. + } else { + render_duration as f64 / callback_budget as f64 + }; + + AudioRenderCapacityEvent::new( + timestamp, + average_load, + peak_load, + underruns as f64 / callback_count as f64, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::context::{AudioContext, AudioContextOptions}; + + #[test] + fn test_same_instance() { + let options = AudioContextOptions { + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + let context = AudioContext::new(options); + + let rc1 = context.render_capacity(); + let rc2 = context.render_capacity(); + let rc3 = rc2.clone(); + + // assert all items are actually the same instance + assert!(Arc::ptr_eq(&rc1.stop_send, &rc2.stop_send)); + assert!(Arc::ptr_eq(&rc1.stop_send, &rc3.stop_send)); + } + + #[test] + fn test_stop_when_not_running() { + let options = AudioContextOptions { + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + let context = AudioContext::new(options); + + let rc = context.render_capacity(); + rc.stop(); + } + + #[test] + fn test_render_capacity() { + let options = AudioContextOptions { + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + let context = AudioContext::new(options); + + let rc = context.render_capacity(); + let (send, recv) = crossbeam_channel::bounded(1); + rc.set_onupdate(move |e| send.send(e).unwrap()); + rc.start(AudioRenderCapacityOptions { + update_interval: 0.05, + }); + let event = recv.recv().unwrap(); + + assert!(event.timestamp >= 0.); + assert!(event.average_load >= 0.); + assert!(event.peak_load >= 0.); + assert!(event.underrun_ratio >= 0.); + + assert!(event.timestamp.is_finite()); + assert!(event.average_load.is_finite()); + assert!(event.peak_load.is_finite()); + assert!(event.underrun_ratio.is_finite()); + + assert_eq!(event.event.type_, "AudioRenderCapacityEvent"); + } + + #[test] + fn test_render_capacity_stops_on_close() { + let options = AudioContextOptions { + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + let context = AudioContext::new(options); + + let rc = context.render_capacity(); + let (send, recv) = crossbeam_channel::unbounded(); + rc.set_onupdate(move |e| send.send(e).unwrap()); + rc.start(AudioRenderCapacityOptions { + update_interval: 0.01, + }); + + recv.recv().unwrap(); + while recv.try_recv().is_ok() {} + + context.close_sync(); + std::thread::sleep(std::time::Duration::from_millis(100)); + + assert_eq!(recv.try_iter().count(), 0); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/context/base.rs b/vendor/web-audio-api-1.7.0/src/context/base.rs new file mode 100644 index 0000000000..63875b3bb1 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/context/base.rs @@ -0,0 +1,452 @@ +//! The `BaseAudioContext` interface + +use crate::buffer::{AudioBuffer, AudioBufferOptions}; +use crate::context::{ + AudioContextRegistration, AudioContextState, AudioParamId, ConcreteBaseAudioContext, + DESTINATION_NODE_ID, +}; +use crate::decoding::decode_media_data; +use crate::events::{Event, EventHandler, EventType}; +use crate::node::{AudioNode, AudioNodeOptions}; +use crate::param::AudioParamDescriptor; +use crate::periodic_wave::{PeriodicWave, PeriodicWaveOptions}; +use crate::{node, AudioListener, RENDER_QUANTUM_SIZE}; + +use std::future::Future; + +/// The interface representing an audio-processing graph built from audio modules linked together, +/// each represented by an `AudioNode`. +/// +/// An audio context controls both the creation of the nodes it contains and the execution of the +/// audio processing, or decoding. +#[allow(clippy::module_name_repetitions)] +pub trait BaseAudioContext { + /// Returns the [`BaseAudioContext`] concrete type associated with this `AudioContext` + #[doc(hidden)] // we'd rather not expose the ConcreteBaseAudioContext + fn base(&self) -> &ConcreteBaseAudioContext; + + /// Decode an [`AudioBuffer`] from a given input stream. + /// + /// The current implementation supports Symphonia's audio formats and codecs, + /// including AIFF, CAF, ISO/MP4, MKV/WebM, Ogg, WAV, AAC, ADPCM, ALAC, + /// FLAC, MP1/MP2/MP3, PCM, and Vorbis. + /// + /// In addition to the official spec, the input parameter can be any byte stream (not just an + /// array). This means you can decode audio data from a file, network stream, or in memory + /// buffer, and any other [`std::io::Read`] implementer. The data is buffered internally so you + /// should not wrap the source in a `BufReader`. + /// + /// This function operates synchronously, which may be undesirable on the control thread. The + /// example shows how to avoid this. See also the async method [`Self::decode_audio_data`]. + /// + /// # Errors + /// + /// This method returns an Error in various cases (IO, mime sniffing, decoding). + /// + /// # Usage + /// + /// ```no_run + /// use std::io::Cursor; + /// use web_audio_api::context::{BaseAudioContext, OfflineAudioContext}; + /// + /// let input = Cursor::new(vec![0; 32]); // or a File, TcpStream, ... + /// + /// let context = OfflineAudioContext::new(2, 44_100, 44_100.); + /// let handle = std::thread::spawn(move || context.decode_audio_data_sync(input)); + /// + /// // do other things + /// + /// // await result from the decoder thread + /// let decode_buffer_result = handle.join(); + /// ``` + /// + /// # Examples + /// + /// The following example shows how to use a thread pool for audio buffer decoding: + /// + /// `cargo run --release --example decode_multithreaded` + fn decode_audio_data_sync( + &self, + input: R, + ) -> Result> { + decode_media_data(input, self.sample_rate()) + } + + /// Decode an [`AudioBuffer`] from a given input stream. + /// + /// The current implementation supports Symphonia's audio formats and codecs, + /// including AIFF, CAF, ISO/MP4, MKV/WebM, Ogg, WAV, AAC, ADPCM, ALAC, + /// FLAC, MP1/MP2/MP3, PCM, and Vorbis. + /// + /// In addition to the official spec, the input parameter can be any byte stream (not just an + /// array). This means you can decode audio data from a file, network stream, or in memory + /// buffer, and any other [`std::io::Read`] implementer. The data is buffered internally so you + /// should not wrap the source in a `BufReader`. + /// + /// Warning, the current implementation still uses blocking IO so it's best to use Tokio's + /// `spawn_blocking` to run the decoding on a thread dedicated to blocking operations. See also + /// the async method [`Self::decode_audio_data_sync`]. + /// + /// # Errors + /// + /// This method returns an Error in various cases (IO, mime sniffing, decoding). + // Use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified, + // hence we use `-> impl Future + ..` instead. + fn decode_audio_data( + &self, + input: R, + ) -> impl Future>> + + Send + + 'static { + let sample_rate = self.sample_rate(); + async move { decode_media_data(input, sample_rate) } + } + + /// Create an new "in-memory" `AudioBuffer` with the given number of channels, + /// length (i.e. number of samples per channel) and sample rate. + /// + /// Note: In most cases you will want the sample rate to match the current + /// audio context sample rate. + #[must_use] + fn create_buffer( + &self, + number_of_channels: usize, + length: usize, + sample_rate: f32, + ) -> AudioBuffer { + let options = AudioBufferOptions { + number_of_channels, + length, + sample_rate, + }; + + AudioBuffer::new(options) + } + + /// Creates a `AnalyserNode` + #[must_use] + fn create_analyser(&self) -> node::AnalyserNode { + node::AnalyserNode::new(self.base(), node::AnalyserOptions::default()) + } + + /// Creates an `BiquadFilterNode` which implements a second order filter + #[must_use] + fn create_biquad_filter(&self) -> node::BiquadFilterNode { + node::BiquadFilterNode::new(self.base(), node::BiquadFilterOptions::default()) + } + + /// Creates an `AudioBufferSourceNode` + #[must_use] + fn create_buffer_source(&self) -> node::AudioBufferSourceNode { + node::AudioBufferSourceNode::new(self.base(), node::AudioBufferSourceOptions::default()) + } + + /// Creates an `ConstantSourceNode`, a source representing a constant value + #[must_use] + fn create_constant_source(&self) -> node::ConstantSourceNode { + node::ConstantSourceNode::new(self.base(), node::ConstantSourceOptions::default()) + } + + /// Creates an `ConvolverNode`, a processing node which applies linear convolution + #[must_use] + fn create_convolver(&self) -> node::ConvolverNode { + node::ConvolverNode::new(self.base(), node::ConvolverOptions::default()) + } + + /// Creates a `ChannelMergerNode` + #[must_use] + fn create_channel_merger(&self, number_of_inputs: usize) -> node::ChannelMergerNode { + let opts = node::ChannelMergerOptions { + number_of_inputs, + ..node::ChannelMergerOptions::default() + }; + node::ChannelMergerNode::new(self.base(), opts) + } + + /// Creates a `ChannelSplitterNode` + #[must_use] + fn create_channel_splitter(&self, number_of_outputs: usize) -> node::ChannelSplitterNode { + let opts = node::ChannelSplitterOptions { + number_of_outputs, + ..node::ChannelSplitterOptions::default() + }; + node::ChannelSplitterNode::new(self.base(), opts) + } + + /// Creates a `DelayNode`, delaying the audio signal + #[must_use] + fn create_delay(&self, max_delay_time: f64) -> node::DelayNode { + let opts = node::DelayOptions { + max_delay_time, + ..node::DelayOptions::default() + }; + node::DelayNode::new(self.base(), opts) + } + + /// Creates a `DynamicsCompressorNode`, compressing the audio signal + #[must_use] + fn create_dynamics_compressor(&self) -> node::DynamicsCompressorNode { + node::DynamicsCompressorNode::new(self.base(), node::DynamicsCompressorOptions::default()) + } + + /// Creates an `GainNode`, to control audio volume + #[must_use] + fn create_gain(&self) -> node::GainNode { + node::GainNode::new(self.base(), node::GainOptions::default()) + } + + /// Creates an `IirFilterNode` + /// + /// # Arguments + /// + /// * `feedforward` - An array of the feedforward (numerator) coefficients for the transfer function of the IIR filter. + /// The maximum length of this array is 20 + /// * `feedback` - An array of the feedback (denominator) coefficients for the transfer function of the IIR filter. + /// The maximum length of this array is 20 + #[must_use] + fn create_iir_filter(&self, feedforward: Vec, feedback: Vec) -> node::IIRFilterNode { + let options = node::IIRFilterOptions { + audio_node_options: AudioNodeOptions::default(), + feedforward, + feedback, + }; + node::IIRFilterNode::new(self.base(), options) + } + + /// Creates an `OscillatorNode`, a source representing a periodic waveform. + #[must_use] + fn create_oscillator(&self) -> node::OscillatorNode { + node::OscillatorNode::new(self.base(), node::OscillatorOptions::default()) + } + + /// Creates a `PannerNode` + #[must_use] + fn create_panner(&self) -> node::PannerNode { + node::PannerNode::new(self.base(), node::PannerOptions::default()) + } + + /// Creates a periodic wave + /// + /// Please note that this constructor deviates slightly from the spec by requiring a single + /// argument with the periodic wave options. + #[must_use] + fn create_periodic_wave(&self, options: PeriodicWaveOptions) -> PeriodicWave { + PeriodicWave::new(self.base(), options) + } + + /// Creates an `ScriptProcessorNode` for custom audio processing (deprecated); + /// + /// # Panics + /// + /// This function panics if: + /// - `buffer_size` is not 256, 512, 1024, 2048, 4096, 8192, or 16384 + /// - the number of input and output channels are both zero + /// - either of the channel counts exceed [`crate::MAX_CHANNELS`] + #[must_use] + fn create_script_processor( + &self, + buffer_size: usize, + number_of_input_channels: usize, + number_of_output_channels: usize, + ) -> node::ScriptProcessorNode { + let options = node::ScriptProcessorOptions { + buffer_size, + number_of_input_channels, + number_of_output_channels, + }; + + node::ScriptProcessorNode::new(self.base(), options) + } + + /// Creates an `StereoPannerNode` to pan a stereo output + #[must_use] + fn create_stereo_panner(&self) -> node::StereoPannerNode { + node::StereoPannerNode::new(self.base(), node::StereoPannerOptions::default()) + } + + /// Creates a `WaveShaperNode` + #[must_use] + fn create_wave_shaper(&self) -> node::WaveShaperNode { + node::WaveShaperNode::new(self.base(), node::WaveShaperOptions::default()) + } + + /// Returns an `AudioDestinationNode` representing the final destination of all audio in the + /// context. It can be thought of as the audio-rendering device. + #[must_use] + fn destination(&self) -> node::AudioDestinationNode { + let registration = AudioContextRegistration { + id: DESTINATION_NODE_ID, + context: self.base().clone(), + }; + let channel_config = self.base().destination_channel_config(); + node::AudioDestinationNode::from_raw_parts(registration, channel_config) + } + + /// Returns the `AudioListener` which is used for 3D spatialization + #[must_use] + fn listener(&self) -> AudioListener { + self.base().listener() + } + + /// The sample rate (in sample-frames per second) at which the `AudioContext` handles audio. + #[must_use] + fn sample_rate(&self) -> f32 { + self.base().sample_rate() + } + + /// Returns state of current context + #[must_use] + fn state(&self) -> AudioContextState { + self.base().state() + } + + /// This is the time in seconds of the sample frame immediately following the last sample-frame + /// in the block of audio most recently processed by the context’s rendering graph. + #[must_use] + fn current_time(&self) -> f64 { + self.base().current_time() + } + + // Number of frames computed on each render block + #[must_use] + fn render_quantum_size(&self) -> usize { + RENDER_QUANTUM_SIZE + } + + /// Create an `AudioParam`. + /// + /// Call this inside the `register` closure when setting up your `AudioNode` + #[must_use] + fn create_audio_param( + &self, + opts: AudioParamDescriptor, + dest: &AudioContextRegistration, + ) -> (crate::param::AudioParam, AudioParamId) { + let param = self.base().register(move |registration| { + let (node, proc) = crate::param::audio_param_pair(opts, registration); + + (node, Box::new(proc)) + }); + + // Connect the param to the node, once the node is registered inside the audio graph. + self.base().queue_audio_param_connect(¶m, dest.id()); + + let proc_id = AudioParamId(param.registration().id().0); + (param, proc_id) + } + + /// Register callback to run when the state of the AudioContext has changed + /// + /// Only a single event handler is active at any time. Calling this method multiple times will + /// override the previous event handler. + fn set_onstatechange(&self, mut callback: F) { + let callback = move |_| { + callback(Event { + type_: "statechange", + }) + }; + + self.base().set_event_handler( + EventType::StateChange, + EventHandler::Multiple(Box::new(callback)), + ); + } + + /// Unset the callback to run when the state of the AudioContext has changed + fn clear_onstatechange(&self) { + self.base().clear_event_handler(EventType::StateChange); + } + + #[cfg(test)] + fn mock_registration(&self) -> AudioContextRegistration { + AudioContextRegistration { + id: crate::context::AudioNodeId(0), + context: self.base().clone(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::context::OfflineAudioContext; + + use float_eq::assert_float_eq; + + fn require_send_sync_static(_: T) {} + + #[test] + fn test_decode_audio_data_sync() { + let context = OfflineAudioContext::new(1, 1, 44100.); + let file = std::fs::File::open("samples/sample.wav").unwrap(); + let audio_buffer = context.decode_audio_data_sync(file).unwrap(); + + assert_eq!(audio_buffer.sample_rate(), 44100.); + assert_eq!(audio_buffer.length(), 142_187); + assert_eq!(audio_buffer.number_of_channels(), 2); + assert_float_eq!(audio_buffer.duration(), 3.224, abs_all <= 0.001); + + let left_start = &audio_buffer.get_channel_data(0)[0..100]; + let right_start = &audio_buffer.get_channel_data(1)[0..100]; + // assert distinct two channel data + assert!(left_start != right_start); + } + + #[test] + fn test_decode_audio_data_future_send_static() { + let context = OfflineAudioContext::new(1, 1, 44100.); + let file = std::fs::File::open("samples/sample.wav").unwrap(); + let future = context.decode_audio_data(file); + require_send_sync_static(future); + } + + #[test] + fn test_decode_audio_data_async() { + use futures::executor; + let context = OfflineAudioContext::new(1, 1, 44100.); + let file = std::fs::File::open("samples/sample.wav").unwrap(); + let future = context.decode_audio_data(file); + let audio_buffer = executor::block_on(future).unwrap(); + + assert_eq!(audio_buffer.sample_rate(), 44100.); + assert_eq!(audio_buffer.length(), 142_187); + assert_eq!(audio_buffer.number_of_channels(), 2); + assert_float_eq!(audio_buffer.duration(), 3.224, abs_all <= 0.001); + + let left_start = &audio_buffer.get_channel_data(0)[0..100]; + let right_start = &audio_buffer.get_channel_data(1)[0..100]; + // assert distinct two channel data + assert!(left_start != right_start); + } + + // #[test] + // disabled: symphonia cannot handle empty WAV-files + #[allow(dead_code)] + fn test_decode_audio_data_empty() { + let context = OfflineAudioContext::new(1, 1, 44100.); + let file = std::fs::File::open("samples/empty_2c.wav").unwrap(); + let audio_buffer = context.decode_audio_data_sync(file).unwrap(); + assert_eq!(audio_buffer.length(), 0); + } + + #[test] + fn test_decode_audio_data_decoding_error() { + let context = OfflineAudioContext::new(1, 1, 44100.); + let file = std::fs::File::open("samples/corrupt.wav").unwrap(); + assert!(context.decode_audio_data_sync(file).is_err()); + } + + #[test] + fn test_create_buffer() { + let number_of_channels = 3; + let length = 2000; + let sample_rate = 96_000.; + + let context = OfflineAudioContext::new(1, 1, 44100.); + let buffer = context.create_buffer(number_of_channels, length, sample_rate); + + assert_eq!(buffer.number_of_channels(), 3); + assert_eq!(buffer.length(), 2000); + assert_float_eq!(buffer.sample_rate(), 96000., abs_all <= 0.); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/context/concrete_base.rs b/vendor/web-audio-api-1.7.0/src/context/concrete_base.rs new file mode 100644 index 0000000000..f6d75e202c --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/context/concrete_base.rs @@ -0,0 +1,620 @@ +//! The `ConcreteBaseAudioContext` type + +use crate::context::{ + AudioContextRegistration, AudioContextState, AudioNodeId, BaseAudioContext, + DESTINATION_NODE_ID, LISTENER_NODE_ID, LISTENER_PARAM_IDS, +}; +use crate::events::{EventDispatch, EventHandler, EventLoop, EventType}; +use crate::message::ControlMessage; +use crate::node::{AudioDestinationNode, AudioNode, AudioNodeOptions, ChannelConfig}; +use crate::param::AudioParam; +use crate::render::AudioProcessor; +use crate::spatial::AudioListenerParams; + +use crate::AudioListener; + +use crossbeam_channel::{SendError, Sender}; +use std::collections::HashSet; +use std::sync::atomic::{AtomicU64, AtomicU8, Ordering}; +use std::sync::{Arc, Mutex, RwLock, RwLockWriteGuard}; + +/// This struct assigns new [`AudioNodeId`]s for [`AudioNode`]s +/// +/// It reuses the ids of decommissioned nodes to prevent unbounded growth of the audio graphs node +/// list (which is stored in a Vec indexed by the AudioNodeId). +struct AudioNodeIdProvider { + /// incrementing id + id_inc: AtomicU64, + /// receiver for decommissioned AudioNodeIds, which can be reused + id_consumer: Mutex>, +} + +impl AudioNodeIdProvider { + fn new(id_consumer: llq::Consumer) -> Self { + Self { + id_inc: AtomicU64::new(0), + id_consumer: Mutex::new(id_consumer), + } + } + + fn get(&self) -> AudioNodeId { + if let Some(available_id) = self.id_consumer.lock().unwrap().pop() { + llq::Node::into_inner(available_id) + } else { + AudioNodeId(self.id_inc.fetch_add(1, Ordering::Relaxed)) + } + } +} + +/// The struct that corresponds to the Javascript `BaseAudioContext` object. +/// +/// This object is returned from the `base()` method on +/// [`AudioContext`](crate::context::AudioContext) and +/// [`OfflineAudioContext`](crate::context::OfflineAudioContext), and the `context()` method on +/// `AudioNode`s. +/// +/// The `ConcreteBaseAudioContext` allows for shallow cloning (using an `Arc` internally). +#[allow(clippy::module_name_repetitions)] +#[derive(Clone)] +#[doc(hidden)] +pub struct ConcreteBaseAudioContext { + inner: Arc, +} + +impl PartialEq for ConcreteBaseAudioContext { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.inner, &other.inner) + } +} + +impl std::fmt::Debug for ConcreteBaseAudioContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BaseAudioContext") + .field("id", &self.address()) + .field("state", &self.state()) + .field("sample_rate", &self.sample_rate()) + .field("current_time", &self.current_time()) + .field("max_channel_count", &self.max_channel_count()) + .field("offline", &self.offline()) + .finish_non_exhaustive() + } +} + +/// Inner representation of the `ConcreteBaseAudioContext` +/// +/// These fields are wrapped inside an `Arc` in the actual `ConcreteBaseAudioContext`. +struct ConcreteBaseAudioContextInner { + /// sample rate in Hertz + sample_rate: f32, + /// max number of speaker output channels + max_channel_count: usize, + /// provider for new AudioNodeIds + audio_node_id_provider: AudioNodeIdProvider, + /// destination node's current channel count + destination_channel_config: ChannelConfig, + /// message channel from control to render thread + render_channel: RwLock>, + /// control messages staged while the render thread is suspended + suspended_messages: Mutex>>, + /// control messages that cannot be sent immediately + queued_messages: Mutex>, + /// number of frames played + frames_played: Arc, + /// control msg to add the AudioListener, to be sent when the first panner is created + queued_audio_listener_msgs: Mutex>, + /// AudioListener fields + listener_params: Option, + /// Denotes if this AudioContext is offline or not + offline: bool, + /// Current state of the `ConcreteBaseAudioContext`, shared with the RenderThread + state: Arc, + /// Stores the event handlers + event_loop: EventLoop, + /// Sender for events that will be handled by the EventLoop + event_send: Sender, + /// Current audio graph connections (from node, output port, to node, input port) + connections: Mutex>, +} + +impl BaseAudioContext for ConcreteBaseAudioContext { + fn base(&self) -> &ConcreteBaseAudioContext { + self + } +} + +impl ConcreteBaseAudioContext { + /// Creates a `BaseAudioContext` instance + #[allow(clippy::too_many_arguments)] // TODO refactor with builder pattern + pub(super) fn new( + sample_rate: f32, + max_channel_count: usize, + state: Arc, + frames_played: Arc, + render_channel: Sender, + event_send: Sender, + event_loop: EventLoop, + offline: bool, + node_id_consumer: llq::Consumer, + ) -> Self { + let audio_node_id_provider = AudioNodeIdProvider::new(node_id_consumer); + + let base_inner = ConcreteBaseAudioContextInner { + sample_rate, + max_channel_count, + render_channel: RwLock::new(render_channel), + suspended_messages: Mutex::new(None), + queued_messages: Mutex::new(Vec::new()), + audio_node_id_provider, + destination_channel_config: AudioNodeOptions::default().into(), + frames_played, + queued_audio_listener_msgs: Mutex::new(Vec::new()), + listener_params: None, + offline, + state, + event_loop, + event_send, + connections: Mutex::new(HashSet::new()), + }; + let base = Self { + inner: Arc::new(base_inner), + }; + + // Online AudioContext should start with stereo channels by default + let initial_channel_count = if offline { + max_channel_count + } else { + 2.min(max_channel_count) + }; + + let (listener_params, destination_channel_config) = { + // Register magical nodes. We should not store the nodes inside our context since that + // will create a cyclic reference, but we can reconstruct a new instance on the fly + // when requested + let dest = AudioDestinationNode::new(&base, initial_channel_count); + let destination_channel_config = dest.into_channel_config(); + let listener = crate::spatial::AudioListenerNode::new(&base); + + let listener_params = listener.into_fields(); + let AudioListener { + position_x, + position_y, + position_z, + forward_x, + forward_y, + forward_z, + up_x, + up_y, + up_z, + } = listener_params; + + let listener_params = AudioListenerParams { + position_x: position_x.into_raw_parts(), + position_y: position_y.into_raw_parts(), + position_z: position_z.into_raw_parts(), + forward_x: forward_x.into_raw_parts(), + forward_y: forward_y.into_raw_parts(), + forward_z: forward_z.into_raw_parts(), + up_x: up_x.into_raw_parts(), + up_y: up_y.into_raw_parts(), + up_z: up_z.into_raw_parts(), + }; + + (listener_params, destination_channel_config) + }; // Nodes will drop now, so base.inner has no copies anymore + + let mut base = base; + let inner_mut = Arc::get_mut(&mut base.inner).unwrap(); + inner_mut.listener_params = Some(listener_params); + inner_mut.destination_channel_config = destination_channel_config; + + // Validate if the hardcoded node IDs line up + debug_assert_eq!( + base.inner + .audio_node_id_provider + .id_inc + .load(Ordering::Relaxed), + LISTENER_PARAM_IDS.end, + ); + + // HRTF is loaded on the control thread when a PannerNode selects that + // model. Do not resample its entire database for contexts that never + // create a spatial panner (including headless oscillator/analyser use). + + base + } + + pub(crate) fn address(&self) -> usize { + Arc::as_ptr(&self.inner) as usize + } + + /// Construct a new pair of [`AudioNode`] and [`AudioProcessor`] + pub(crate) fn register< + T: AudioNode, + F: FnOnce(AudioContextRegistration) -> (T, Box), + >( + &self, + f: F, + ) -> T { + // create a unique id for this node + let id = self.inner.audio_node_id_provider.get(); + let registration = AudioContextRegistration { + id, + context: self.clone(), + }; + + // create the node and its renderer + let (node, render) = (f)(registration); + + // pass the renderer to the audio graph + let message = ControlMessage::RegisterNode { + id, + reclaim_id: llq::Node::new(id), + node: render, + inputs: node.number_of_inputs(), + outputs: node.number_of_outputs(), + channel_config: node.channel_config().inner(), + }; + + // if this is the AudioListener or its params, do not add it to the graph just yet + if id == LISTENER_NODE_ID || LISTENER_PARAM_IDS.contains(&id.0) { + let mut queued_audio_listener_msgs = + self.inner.queued_audio_listener_msgs.lock().unwrap(); + queued_audio_listener_msgs.push(message); + } else { + self.send_control_msg(message); + self.resolve_queued_control_msgs(id); + } + + node + } + + /// Send a control message to the render thread + /// + /// When the render thread is closed or crashed, the message is discarded and a log warning is + /// emitted. + pub(crate) fn send_control_msg(&self, msg: ControlMessage) { + if self.state() != AudioContextState::Closed { + let sender = self.inner.render_channel.read().unwrap(); + // if the context is suspended, buffer the message and don't send it + if let Some(queued) = self.inner.suspended_messages.lock().unwrap().as_mut() { + queued.push(msg); + return; + } + + let result = sender.send(msg); + if result.is_err() { + log::warn!("Discarding control message - render thread is closed"); + } + } + } + + pub(crate) fn suspend_control_msgs(&self, msg: ControlMessage) { + let sender = self.inner.render_channel.read().unwrap(); + *self.inner.suspended_messages.lock().unwrap() = Some(Vec::new()); + if sender.send(msg).is_err() { + log::warn!("Discarding control message - render thread is closed"); + } + } + + pub(crate) fn resume_control_msgs(&self, msg: ControlMessage) { + let sender = self.inner.render_channel.read().unwrap(); + let messages = self + .inner + .suspended_messages + .lock() + .unwrap() + .take() + .unwrap_or_default(); + + for msg in messages { + if sender.send(msg).is_err() { + log::warn!("Discarding control message - render thread is closed"); + return; + } + } + + if sender.send(msg).is_err() { + log::warn!("Discarding control message - render thread is closed"); + } + } + + pub(crate) fn send_event(&self, msg: EventDispatch) -> Result<(), SendError> { + self.inner.event_send.send(msg) + } + + pub(crate) fn lock_control_msg_sender(&self) -> RwLockWriteGuard<'_, Sender> { + self.inner.render_channel.write().unwrap() + } + + pub(super) fn mark_node_dropped(&self, id: AudioNodeId) { + // Ignore magic nodes + if id == DESTINATION_NODE_ID || id == LISTENER_NODE_ID || LISTENER_PARAM_IDS.contains(&id.0) + { + return; + } + + // Inform render thread that the control thread AudioNode no longer has any handles + let message = ControlMessage::ControlHandleDropped { id }; + self.send_control_msg(message); + + // Clear the connection administration for this node, the node id may be recycled later + self.inner + .connections + .lock() + .unwrap() + .retain(|&(from, _output, to, _input)| from != id && to != id); + } + + /// Inform render thread that this node can act as a cycle breaker + #[doc(hidden)] + pub fn mark_cycle_breaker(&self, reg: &AudioContextRegistration) { + let id = reg.id(); + let message = ControlMessage::MarkCycleBreaker { id }; + self.send_control_msg(message); + } + + /// `ChannelConfig` of the `AudioDestinationNode` + pub(super) fn destination_channel_config(&self) -> ChannelConfig { + self.inner.destination_channel_config.clone() + } + + /// Returns the `AudioListener` which is used for 3D spatialization + pub(super) fn listener(&self) -> AudioListener { + // instruct to BaseContext to add the AudioListener if it has not already + self.base().ensure_audio_listener_present(); + + let mut ids = LISTENER_PARAM_IDS.map(|i| AudioContextRegistration { + id: AudioNodeId(i), + context: self.clone(), + }); + let params = self.inner.listener_params.as_ref().unwrap(); + + AudioListener { + position_x: AudioParam::from_raw_parts(ids.next().unwrap(), params.position_x.clone()), + position_y: AudioParam::from_raw_parts(ids.next().unwrap(), params.position_y.clone()), + position_z: AudioParam::from_raw_parts(ids.next().unwrap(), params.position_z.clone()), + forward_x: AudioParam::from_raw_parts(ids.next().unwrap(), params.forward_x.clone()), + forward_y: AudioParam::from_raw_parts(ids.next().unwrap(), params.forward_y.clone()), + forward_z: AudioParam::from_raw_parts(ids.next().unwrap(), params.forward_z.clone()), + up_x: AudioParam::from_raw_parts(ids.next().unwrap(), params.up_x.clone()), + up_y: AudioParam::from_raw_parts(ids.next().unwrap(), params.up_y.clone()), + up_z: AudioParam::from_raw_parts(ids.next().unwrap(), params.up_z.clone()), + } + } + + /// Returns state of current context + #[must_use] + pub(super) fn state(&self) -> AudioContextState { + self.inner.state.load(Ordering::Acquire).into() + } + + /// Updates state of current context + pub(super) fn set_state(&self, state: AudioContextState) { + // Only used from OfflineAudioContext or suspended AudioContext, otherwise the state + // changed are spawned from the render thread + let current_state = self.state(); + if current_state != state { + self.inner.state.store(state as u8, Ordering::Release); + let _ = self.send_event(EventDispatch::state_change(state)); + } + } + + /// The sample rate (in sample-frames per second) at which the `AudioContext` handles audio. + #[must_use] + pub(super) fn sample_rate(&self) -> f32 { + self.inner.sample_rate + } + + /// This is the time in seconds of the sample frame immediately following the last sample-frame + /// in the block of audio most recently processed by the context’s rendering graph. + #[must_use] + // web audio api specification requires that `current_time` returns an f64 + // std::sync::AtomicsF64 is not currently implemented in the standard library + // Currently, we have no other choice than casting an u64 into f64, with possible loss of precision + #[allow(clippy::cast_precision_loss)] + pub(super) fn current_time(&self) -> f64 { + self.inner.frames_played.load(Ordering::SeqCst) as f64 / self.inner.sample_rate as f64 + } + + /// Maximum available channels for the audio destination + #[must_use] + pub(crate) fn max_channel_count(&self) -> usize { + self.inner.max_channel_count + } + + /// Release queued control messages to the render thread that were blocking on the availability + /// of the Node with the given `id` + fn resolve_queued_control_msgs(&self, id: AudioNodeId) { + // resolve control messages that depend on this registration + let mut queued = self.inner.queued_messages.lock().unwrap(); + let mut i = 0; // waiting for Vec::drain_filter to stabilize + while i < queued.len() { + if matches!(&queued[i], ControlMessage::ConnectNode {to, ..} if *to == id) { + let m = queued.remove(i); + self.send_control_msg(m); + } else { + i += 1; + } + } + } + + /// Connects the output of the `from` audio node to the input of the `to` audio node + pub(crate) fn connect(&self, from: AudioNodeId, to: AudioNodeId, output: usize, input: usize) { + let inserted = self + .inner + .connections + .lock() + .unwrap() + .insert((from, output, to, input)); + + if !inserted { + return; // do not allow duplicated edges + } + + let message = ControlMessage::ConnectNode { + from, + to, + output, + input, + }; + self.send_control_msg(message); + } + + /// Schedule a connection of an `AudioParam` to the `AudioNode` it belongs to + /// + /// It is not performed immediately as the `AudioNode` is not registered at this point. + pub(super) fn queue_audio_param_connect(&self, param: &AudioParam, audio_node: AudioNodeId) { + // no need to store these type of connections in self.inner.connections + + let message = ControlMessage::ConnectNode { + from: param.registration().id(), + to: audio_node, + output: 0, + input: usize::MAX, // audio params connect to the 'hidden' input port + }; + self.inner.queued_messages.lock().unwrap().push(message); + } + + /// Disconnects outputs of the audio node, possibly filtered by output node, input, output. + pub(crate) fn disconnect( + &self, + from: AudioNodeId, + output: Option, + to: Option, + input: Option, + ) { + // check if the node was connected, otherwise panic + let mut has_disconnected = false; + let mut connections = self.inner.connections.lock().unwrap(); + connections.retain(|&(c_from, c_output, c_to, c_input)| { + let retain = c_from != from + || c_output != output.unwrap_or(c_output) + || c_to != to.unwrap_or(c_to) + || c_input != input.unwrap_or(c_input); + if !retain { + has_disconnected = true; + let message = ControlMessage::DisconnectNode { + from, + to: c_to, + input: c_input, + output: c_output, + }; + self.send_control_msg(message); + } + retain + }); + + // make sure to drop the MutexGuard before the panic to avoid poisoning + drop(connections); + + if !has_disconnected && to.is_some() { + panic!("InvalidAccessError - attempting to disconnect unconnected nodes"); + } + } + + /// Connect the `AudioListener` to a `PannerNode` + pub(crate) fn connect_listener_to_panner(&self, panner: AudioNodeId) { + self.connect(LISTENER_NODE_ID, panner, 0, usize::MAX); + } + + /// Add the [`AudioListener`] to the audio graph (if not already) + pub(crate) fn ensure_audio_listener_present(&self) { + let mut queued_audio_listener_msgs = self.inner.queued_audio_listener_msgs.lock().unwrap(); + let mut released = false; + while let Some(message) = queued_audio_listener_msgs.pop() { + // add the AudioListenerRenderer to the graph + self.send_control_msg(message); + released = true; + } + + if released { + // connect the AudioParamRenderers to the Listener + self.resolve_queued_control_msgs(LISTENER_NODE_ID); + + // hack: Connect the listener to the destination node to force it to render at each + // quantum. Abuse the magical usize::MAX port so it acts as an AudioParam and has no side + // effects + self.connect(LISTENER_NODE_ID, DESTINATION_NODE_ID, 0, usize::MAX); + } + } + + /// Returns true if this is `OfflineAudioContext` (false when it is an `AudioContext`) + pub(crate) fn offline(&self) -> bool { + self.inner.offline + } + + pub(crate) fn set_event_handler(&self, event: EventType, callback: EventHandler) { + self.inner.event_loop.set_handler(event, callback); + } + + pub(crate) fn clear_event_handler(&self, event: EventType) { + self.inner.event_loop.clear_handler(event); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::context::OfflineAudioContext; + + #[test] + fn test_provide_node_id() { + let (mut id_producer, id_consumer) = llq::Queue::new().split(); + let provider = AudioNodeIdProvider::new(id_consumer); + assert_eq!(provider.get().0, 0); // newly assigned + assert_eq!(provider.get().0, 1); // newly assigned + id_producer.push(llq::Node::new(AudioNodeId(0))); + assert_eq!(provider.get().0, 0); // reused + assert_eq!(provider.get().0, 2); // newly assigned + } + + #[test] + fn test_connect_disconnect() { + let context = OfflineAudioContext::new(1, 128, 48000.); + let node1 = context.create_constant_source(); + let node2 = context.create_gain(); + + // connection list starts empty + assert!(context.base().inner.connections.lock().unwrap().is_empty()); + + node1.disconnect(); // never panic for plain disconnect calls + + node1.connect(&node2); + + // connection should be registered + assert_eq!(context.base().inner.connections.lock().unwrap().len(), 1); + + node1.disconnect(); + assert!(context.base().inner.connections.lock().unwrap().is_empty()); + + node1.connect(&node2); + assert_eq!(context.base().inner.connections.lock().unwrap().len(), 1); + + node1.disconnect_dest(&node2); + assert!(context.base().inner.connections.lock().unwrap().is_empty()); + } + + #[test] + #[should_panic] + fn test_disconnect_not_existing() { + let context = OfflineAudioContext::new(1, 128, 48000.); + let node1 = context.create_constant_source(); + let node2 = context.create_gain(); + + node1.disconnect_dest(&node2); + } + + #[test] + fn test_mark_node_dropped() { + let context = OfflineAudioContext::new(1, 128, 48000.); + + let node1 = context.create_constant_source(); + let node2 = context.create_gain(); + + node1.connect(&node2); + context.base().mark_node_dropped(node1.registration().id()); + + // dropping should clear connections administration + assert!(context.base().inner.connections.lock().unwrap().is_empty()); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/context/diagnostics.rs b/vendor/web-audio-api-1.7.0/src/context/diagnostics.rs new file mode 100644 index 0000000000..ad99706d1f --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/context/diagnostics.rs @@ -0,0 +1,104 @@ +//! Structured diagnostics for realtime audio contexts. + +/// Snapshot of an [`AudioContext`](crate::context::AudioContext), its backend, render thread, and +/// audio graph. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct AudioContextDiagnostics { + /// Audio backend details collected on the control thread. + pub backend: AudioBackendDiagnostics, + /// Render thread details collected on the render thread. + pub render_thread: AudioRenderThreadDiagnostics, + /// Audio graph details collected on the render thread. + pub graph: AudioGraphDiagnostics, +} + +/// Snapshot of the active audio backend. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct AudioBackendDiagnostics { + /// Backend implementation name. + pub name: String, + /// Current audio output device id. + pub sink_id: String, + /// Current output latency in seconds, if the backend can report it. + pub output_latency: Option, +} + +/// Snapshot of the active render thread. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct AudioRenderThreadDiagnostics { + /// Render thread sample rate in Hz. + pub sample_rate: f32, + /// Backend callback buffer size in frames. + pub buffer_size: usize, + /// Number of frames played by the backend. + pub frames_played: u64, + /// Number of output channels used by the backend stream. + pub number_of_channels: usize, + /// Whether rendering is currently suspended. + pub suspended: bool, +} + +/// Snapshot of the audio graph. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct AudioGraphDiagnostics { + /// Whether the graph is loaded + pub active: bool, + /// Number of registered nodes. + pub node_count: usize, + /// Number of outgoing graph connections. + pub edge_count: usize, + /// Node ids in the current render ordering. + pub ordered: Vec, + /// Node ids currently excluded from rendering because they are in an unbreakable cycle. + pub in_cycle: Vec, + /// Node ids marked as eligible cycle breakers. + pub cycle_breakers: Vec, + /// Registered audio nodes. + pub nodes: Vec, +} + +/// Snapshot of a single audio graph node. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct AudioNodeDiagnostics { + /// Internal audio node id. + pub id: u64, + /// Renderer type name. + pub processor: String, + /// Number of input ports. + pub inputs: usize, + /// Number of output ports. + pub outputs: usize, + /// Current channel count for each input port. + pub input_channels: Vec, + /// Current channel count for each output port. + pub output_channels: Vec, + /// Channel configuration formatted for inspection. + pub channel_config: String, + /// Outgoing graph connections. + pub outgoing_edges: Vec, + /// Whether the control-side handle for this node has been dropped. + pub control_handle_dropped: bool, + /// Whether this node had incoming connections during the latest render quantum. + pub has_inputs_connected: bool, + /// Whether this node can break render cycles. + pub cycle_breaker: bool, + /// Whether this node reports side effects. + pub has_side_effects: bool, +} + +/// Snapshot of a graph connection from one node output to another node input. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct AudioGraphEdgeDiagnostics { + /// Source output port index. + pub output: usize, + /// Destination node id. + pub destination: u64, + /// Destination input port index, or `None` for hidden internal connections. + pub input: Option, +} diff --git a/vendor/web-audio-api-1.7.0/src/context/mod.rs b/vendor/web-audio-api-1.7.0/src/context/mod.rs new file mode 100644 index 0000000000..c5ccaf160b --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/context/mod.rs @@ -0,0 +1,188 @@ +//! The `BaseAudioContext` interface and the `AudioContext` and `OfflineAudioContext` types + +use std::{any::Any, ops::Range}; + +mod base; +pub use base::*; + +mod concrete_base; +pub use concrete_base::*; + +#[cfg(feature = "diagnostics")] +mod diagnostics; +#[cfg(feature = "diagnostics")] +pub use diagnostics::*; + +mod offline; +pub use offline::*; + +mod online; +pub use online::*; + +// magic node values +/// Destination node id is always at index 0 +pub(crate) const DESTINATION_NODE_ID: AudioNodeId = AudioNodeId(0); +/// listener node id is always at index 1 +const LISTENER_NODE_ID: AudioNodeId = AudioNodeId(1); +/// listener audio parameters ids are always at index 2 through 10 +const LISTENER_PARAM_IDS: Range = 2..11; +/// listener audio parameters ids are always at index 2 through 10 +pub(crate) const LISTENER_AUDIO_PARAM_IDS: [AudioParamId; 9] = [ + AudioParamId(2), + AudioParamId(3), + AudioParamId(4), + AudioParamId(5), + AudioParamId(6), + AudioParamId(7), + AudioParamId(8), + AudioParamId(9), + AudioParamId(10), +]; + +/// Unique identifier for audio nodes. +/// +/// Used for internal bookkeeping. +#[derive(Hash, PartialEq, Eq, Clone, Copy)] +pub(crate) struct AudioNodeId(pub u64); + +impl std::fmt::Debug for AudioNodeId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "AudioNodeId({})", self.0) + } +} + +/// Unique identifier for audio params. +/// +/// Store these in your `AudioProcessor` to get access to `AudioParam` values. +#[derive(Debug)] +pub struct AudioParamId(u64); + +// bit contrived, but for type safety only the context mod can access the inner u64 +impl From<&AudioParamId> for AudioNodeId { + fn from(i: &AudioParamId) -> Self { + Self(i.0) + } +} + +/// Describes the current state of the `AudioContext` +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AudioContextState { + /// This context is currently suspended (context time is not proceeding, + /// audio hardware may be powered down/released). + Suspended, + /// Audio is being processed. + Running, + /// This context has been released, and can no longer be used to process audio. + /// All system audio resources have been released. + Closed, +} + +impl From for AudioContextState { + fn from(value: u8) -> Self { + match value { + 0 => Self::Suspended, + 1 => Self::Running, + 2 => Self::Closed, + _ => unreachable!(), + } + } +} + +/// Handle of the [`AudioNode`](crate::node::AudioNode) to its associated [`BaseAudioContext`]. +/// +/// Only when implementing the AudioNode trait manually, this struct is of any concern. +/// +/// This object allows for communication with the render thread and dynamic lifetime management. +// The only way to construct this object is by calling [`BaseAudioContext::register`]. +// This struct should not derive Clone because of the Drop handler. +pub struct AudioContextRegistration { + /// the audio context in which nodes and connections lives + context: ConcreteBaseAudioContext, + /// identify a specific `AudioNode` + id: AudioNodeId, +} + +impl std::fmt::Debug for AudioContextRegistration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AudioContextRegistration") + .field("id", &self.id) + .field( + "context", + &format!("BaseAudioContext@{}", self.context.address()), + ) + .finish() + } +} + +impl AudioContextRegistration { + /// Get the audio node id of the registration + #[must_use] + pub(crate) fn id(&self) -> AudioNodeId { + self.id + } + + /// Get the [`BaseAudioContext`] concrete type associated with this `AudioContext` + #[must_use] + pub(crate) fn context(&self) -> &ConcreteBaseAudioContext { + &self.context + } + + /// Send a message to the corresponding audio processor of this node + /// + /// The message will be handled by + /// [`AudioProcessor::onmessage`](crate::render::AudioProcessor::onmessage). + pub(crate) fn post_message(&self, msg: M) { + let wrapped = crate::message::ControlMessage::NodeMessage { + id: self.id, + msg: llq::Node::new(Box::new(msg)), + }; + self.context.send_control_msg(wrapped); + } +} + +impl Drop for AudioContextRegistration { + fn drop(&mut self) { + self.context.mark_node_dropped(self.id); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::node::AudioNode; + + fn require_send_sync_static(_: T) {} + + #[test] + fn test_audio_context_registration_traits() { + let context = OfflineAudioContext::new(1, 1, 44100.); + let registration = context.mock_registration(); + + // we want to be able to ship AudioNodes to another thread, so the Registration should be + // Send, Sync and 'static + require_send_sync_static(registration); + } + + #[test] + fn test_offline_audio_context_send_sync() { + let context = OfflineAudioContext::new(1, 1, 44100.); + require_send_sync_static(context); + } + + #[test] + fn test_online_audio_context_send_sync() { + let options = AudioContextOptions { + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + let context = AudioContext::new(options); + require_send_sync_static(context); + } + + #[test] + fn test_context_equals() { + let context = OfflineAudioContext::new(1, 48000, 96000.); + let dest = context.destination(); + assert!(dest.context() == context.base()); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/context/offline.rs b/vendor/web-audio-api-1.7.0/src/context/offline.rs new file mode 100644 index 0000000000..6cc9c0ede2 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/context/offline.rs @@ -0,0 +1,653 @@ +//! The `OfflineAudioContext` type + +use std::sync::atomic::{AtomicU64, AtomicU8}; +use std::sync::{Arc, Mutex}; + +use crate::buffer::AudioBuffer; +use crate::context::{AudioContextState, BaseAudioContext, ConcreteBaseAudioContext}; +use crate::events::{ + Event, EventDispatch, EventHandler, EventPayload, EventType, OfflineAudioCompletionEvent, +}; +use crate::render::RenderThread; +use crate::stats::AudioStats; +use crate::{ + assert_valid_buffer_length, assert_valid_number_of_channels, assert_valid_sample_rate, + RENDER_QUANTUM_SIZE, +}; + +use crate::events::EventLoop; +use futures_channel::{mpsc, oneshot}; +use futures_util::SinkExt as _; + +pub(crate) type OfflineAudioContextCallback = + dyn FnOnce(&mut OfflineAudioContext) + Send + Sync + 'static; + +/// The `OfflineAudioContext` doesn't render the audio to the device hardware; instead, it generates +/// it, as fast as it can, and outputs the result to an `AudioBuffer`. +// the naming comes from the web audio specification +#[allow(clippy::module_name_repetitions)] +pub struct OfflineAudioContext { + /// represents the underlying `BaseAudioContext` + base: ConcreteBaseAudioContext, + /// the size of the buffer in sample-frames + length: usize, + /// actual renderer of the audio graph, can only be called once + renderer: Mutex>, + /// channel to notify resume actions on the rendering + resume_sender: mpsc::Sender<()>, +} + +impl std::fmt::Debug for OfflineAudioContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OfflineAudioContext") + .field("length", &self.length()) + .field("base", &self.base()) + .finish_non_exhaustive() + } +} + +struct OfflineAudioContextRenderer { + /// the rendering 'thread', fully controlled by the offline context + renderer: RenderThread, + /// sorted list of promises to resolve at certain render quanta (via `suspend`) + suspend_promises: Vec<(usize, oneshot::Sender<()>)>, + /// sorted list of callbacks to run at certain render quanta (via `suspend_sync`) + suspend_callbacks: Vec<(usize, Box)>, + /// channel to listen for `resume` calls on a suspended context + resume_receiver: mpsc::Receiver<()>, + /// event loop to run after each render quantum + event_loop: EventLoop, +} + +impl BaseAudioContext for OfflineAudioContext { + fn base(&self) -> &ConcreteBaseAudioContext { + &self.base + } +} + +impl OfflineAudioContext { + /// Creates an `OfflineAudioContext` instance + /// + /// # Arguments + /// + /// * `channels` - number of output channels to render + /// * `length` - length of the rendering audio buffer + /// * `sample_rate` - output sample rate + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn new(number_of_channels: usize, length: usize, sample_rate: f32) -> Self { + assert_valid_number_of_channels(number_of_channels); + assert_valid_buffer_length(length); + assert_valid_sample_rate(sample_rate); + + // communication channel to the render thread, + // unbounded is fine because it does not need to be realtime safe + let (sender, receiver) = crossbeam_channel::unbounded(); + + let (node_id_producer, node_id_consumer) = llq::Queue::new().split(); + let graph = crate::render::graph::Graph::new(node_id_producer); + let message = crate::message::ControlMessage::Startup { graph }; + sender.send(message).unwrap(); + + // track number of frames - synced from render thread to control thread + let frames_played = Arc::new(AtomicU64::new(0)); + let frames_played_clone = Arc::clone(&frames_played); + let state = Arc::new(AtomicU8::new(AudioContextState::Suspended as u8)); + let state_clone = Arc::clone(&state); + + // Communication channel for events from the render thread to the control thread. + // Use an unbounded channel because we do not require real-time safety. + let (event_send, event_recv) = crossbeam_channel::unbounded(); + let event_loop = EventLoop::new(event_recv); + + // setup the render 'thread', which will run inside the control thread + let renderer = RenderThread::new( + sample_rate, + number_of_channels, + receiver, + state_clone, + frames_played_clone, + AudioStats::new(), + event_send.clone(), + ); + + // first, setup the base audio context + let base = ConcreteBaseAudioContext::new( + sample_rate, + number_of_channels, + state, + frames_played, + sender, + event_send, + event_loop.clone(), + true, + node_id_consumer, + ); + + let (resume_sender, resume_receiver) = mpsc::channel(0); + + let renderer = OfflineAudioContextRenderer { + renderer, + suspend_promises: Vec::new(), + suspend_callbacks: Vec::new(), + resume_receiver, + event_loop, + }; + + Self { + base, + length, + renderer: Mutex::new(Some(renderer)), + resume_sender, + } + } + + /// Given the current connections and scheduled changes, starts rendering audio. + /// + /// This function will block the current thread and returns the rendered `AudioBuffer` + /// synchronously. + /// + /// This method will only adhere to scheduled suspensions via [`Self::suspend_sync`] and + /// will ignore those provided via [`Self::suspend`]. + /// + /// # Panics + /// + /// Panics if this method is called multiple times + #[must_use] + pub fn start_rendering_sync(&mut self) -> AudioBuffer { + let renderer = self + .renderer + .lock() + .unwrap() + .take() + .expect("InvalidStateError - Cannot call `startRendering` twice"); + + let OfflineAudioContextRenderer { + renderer, + suspend_callbacks, + event_loop, + .. + } = renderer; + + self.base.set_state(AudioContextState::Running); + + let result = renderer.render_audiobuffer_sync(self, suspend_callbacks, &event_loop); + + self.base.set_state(AudioContextState::Closed); + let _ = self + .base + .send_event(EventDispatch::complete(result.clone())); + + // spin the event loop once more to handle the statechange/complete events + event_loop.handle_pending_events(); + + result + } + + /// Given the current connections and scheduled changes, starts rendering audio. + /// + /// Rendering is purely CPU bound and contains no `await` points, so calling this method will + /// block the executor until completion or until the context is suspended. + /// + /// This method will only adhere to scheduled suspensions via [`Self::suspend`] and will + /// ignore those provided via [`Self::suspend_sync`]. + /// + /// # Panics + /// + /// Panics if this method is called multiple times. + pub async fn start_rendering(&self) -> AudioBuffer { + // We are mixing async with a std Mutex, so be sure not to `await` while the lock is held + let renderer = self + .renderer + .lock() + .unwrap() + .take() + .expect("InvalidStateError - Cannot call `startRendering` twice"); + + let OfflineAudioContextRenderer { + renderer, + suspend_promises, + resume_receiver, + event_loop, + .. + } = renderer; + + self.base.set_state(AudioContextState::Running); + + let result = renderer + .render_audiobuffer(self.length, suspend_promises, resume_receiver, &event_loop) + .await; + + self.base.set_state(AudioContextState::Closed); + let _ = self + .base + .send_event(EventDispatch::complete(result.clone())); + + // spin the event loop once more to handle the statechange/complete events + event_loop.handle_pending_events(); + + result + } + + /// get the length of rendering audio buffer + // false positive: OfflineAudioContext is not const + #[allow(clippy::missing_const_for_fn, clippy::unused_self)] + #[must_use] + pub fn length(&self) -> usize { + self.length + } + + #[track_caller] + fn calculate_suspend_frame(&self, suspend_time: f64) -> usize { + assert!( + suspend_time >= 0., + "InvalidStateError: suspendTime cannot be negative" + ); + assert!( + suspend_time < self.length as f64 / self.sample_rate() as f64, + "InvalidStateError: suspendTime cannot be greater than or equal to the total render duration" + ); + (suspend_time * self.base.sample_rate() as f64 / RENDER_QUANTUM_SIZE as f64).ceil() as usize + } + + /// Schedules a suspension of the time progression in the audio context at the specified time + /// and returns a promise + /// + /// The specified time is quantized and rounded up to the render quantum size. + /// + /// # Panics + /// + /// Panics if the quantized frame number + /// + /// - is negative or + /// - is less than or equal to the current time or + /// - is greater than or equal to the total render duration or + /// - is scheduled by another suspend for the same time + /// + /// # Example usage + /// + /// ```rust + /// use futures::{executor, join}; + /// use futures::FutureExt as _; + /// use std::sync::Arc; + /// + /// use web_audio_api::context::BaseAudioContext; + /// use web_audio_api::context::OfflineAudioContext; + /// use web_audio_api::node::{AudioNode, AudioScheduledSourceNode}; + /// + /// let context = Arc::new(OfflineAudioContext::new(1, 512, 44_100.)); + /// let context_clone = Arc::clone(&context); + /// + /// let suspend_promise = context.suspend(128. / 44_100.).then(|_| async move { + /// let mut src = context_clone.create_constant_source(); + /// src.connect(&context_clone.destination()); + /// src.start(); + /// context_clone.resume().await; + /// }); + /// + /// let render_promise = context.start_rendering(); + /// + /// let buffer = executor::block_on(async move { join!(suspend_promise, render_promise).1 }); + /// assert_eq!(buffer.number_of_channels(), 1); + /// assert_eq!(buffer.length(), 512); + /// ``` + pub async fn suspend(&self, suspend_time: f64) { + let quantum = self.calculate_suspend_frame(suspend_time); + + let (sender, receiver) = oneshot::channel(); + + // We are mixing async with a std Mutex, so be sure not to `await` while the lock is held + { + let mut lock = self.renderer.lock().unwrap(); + let renderer = lock + .as_mut() + .expect("InvalidStateError - cannot suspend when rendering has already started"); + + let insert_pos = renderer + .suspend_promises + .binary_search_by_key(&quantum, |&(q, _)| q) + .expect_err( + "InvalidStateError - cannot suspend multiple times at the same render quantum", + ); + + renderer + .suspend_promises + .insert(insert_pos, (quantum, sender)); + } // lock is dropped + + receiver.await.unwrap(); + self.base().set_state(AudioContextState::Suspended); + } + + /// Schedules a suspension of the time progression in the audio context at the specified time + /// and runs a callback. + /// + /// This is a synchronous version of [`Self::suspend`] that runs the provided callback at + /// the `suspendTime`. The rendering resumes automatically after the callback has run, so there + /// is no `resume_sync` method. + /// + /// The specified time is quantized and rounded up to the render quantum size. + /// + /// # Panics + /// + /// Panics if the quantized frame number + /// + /// - is negative or + /// - is less than or equal to the current time or + /// - is greater than or equal to the total render duration or + /// - is scheduled by another suspend for the same time + /// + /// # Example usage + /// + /// ```rust + /// use web_audio_api::context::BaseAudioContext; + /// use web_audio_api::context::OfflineAudioContext; + /// use web_audio_api::node::{AudioNode, AudioScheduledSourceNode}; + /// + /// let mut context = OfflineAudioContext::new(1, 512, 44_100.); + /// + /// context.suspend_sync(128. / 44_100., |context| { + /// let mut src = context.create_constant_source(); + /// src.connect(&context.destination()); + /// src.start(); + /// }); + /// + /// let buffer = context.start_rendering_sync(); + /// assert_eq!(buffer.number_of_channels(), 1); + /// assert_eq!(buffer.length(), 512); + /// ``` + pub fn suspend_sync( + &mut self, + suspend_time: f64, + callback: F, + ) { + let quantum = self.calculate_suspend_frame(suspend_time); + + let mut lock = self.renderer.lock().unwrap(); + let renderer = lock + .as_mut() + .expect("InvalidStateError - cannot suspend when rendering has already started"); + + let insert_pos = renderer + .suspend_callbacks + .binary_search_by_key(&quantum, |(q, _c)| *q) + .expect_err( + "InvalidStateError - cannot suspend multiple times at the same render quantum", + ); + + let boxed_callback = Box::new(|ctx: &mut OfflineAudioContext| { + ctx.base().set_state(AudioContextState::Suspended); + (callback)(ctx); + ctx.base().set_state(AudioContextState::Running); + }); + + renderer + .suspend_callbacks + .insert(insert_pos, (quantum, boxed_callback)); + } + + /// Resumes the progression of the OfflineAudioContext's currentTime when it has been suspended + /// + /// # Panics + /// + /// Panics when the context is closed or rendering has not started + pub async fn resume(&self) { + self.base().set_state(AudioContextState::Running); + self.resume_sender.clone().send(()).await.unwrap() + } + + /// Register callback to run when the rendering has completed + /// + /// Only a single event handler is active at any time. Calling this method multiple times will + /// override the previous event handler. + #[allow(clippy::missing_panics_doc)] + pub fn set_oncomplete( + &self, + callback: F, + ) { + let callback = move |v| match v { + EventPayload::Complete(v) => { + let event = OfflineAudioCompletionEvent { + rendered_buffer: v, + event: Event { type_: "complete" }, + }; + callback(event) + } + _ => unreachable!(), + }; + + self.base() + .set_event_handler(EventType::Complete, EventHandler::Once(Box::new(callback))); + } + + /// Unset the callback to run when the rendering has completed + pub fn clear_oncomplete(&self) { + self.base().clear_event_handler(EventType::Complete); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use float_eq::assert_float_eq; + use std::sync::atomic::{AtomicBool, Ordering}; + + use crate::node::AudioNode; + use crate::node::AudioScheduledSourceNode; + + #[test] + fn test_sample_rate_length() { + let context = OfflineAudioContext::new(1, 48000, 96000.); + assert_float_eq!(context.sample_rate(), 96000., abs_all <= 0.); + assert_eq!(context.length(), 48000); + } + + #[test] + fn render_empty_graph() { + let mut context = OfflineAudioContext::new(2, 555, 44_100.); + assert_eq!(context.state(), AudioContextState::Suspended); + let buffer = context.start_rendering_sync(); + + assert_eq!(context.length(), 555); + + assert_eq!(buffer.number_of_channels(), 2); + assert_eq!(buffer.length(), 555); + assert_float_eq!(buffer.get_channel_data(0), &[0.; 555][..], abs_all <= 0.); + assert_float_eq!(buffer.get_channel_data(1), &[0.; 555][..], abs_all <= 0.); + + assert_eq!(context.state(), AudioContextState::Closed); + } + + #[test] + #[should_panic] + fn render_twice_panics() { + let mut context = OfflineAudioContext::new(2, 555, 44_100.); + let _ = context.start_rendering_sync(); + let _ = context.start_rendering_sync(); + } + + #[test] + fn test_suspend_sync() { + use crate::node::ConstantSourceNode; + use std::sync::OnceLock; + + let len = RENDER_QUANTUM_SIZE * 4; + let sample_rate = 48000_f64; + + let mut context = OfflineAudioContext::new(1, len, sample_rate as f32); + static SOURCE: OnceLock = OnceLock::new(); + + context.suspend_sync(RENDER_QUANTUM_SIZE as f64 / sample_rate, |context| { + assert_eq!(context.state(), AudioContextState::Suspended); + let mut src = context.create_constant_source(); + src.connect(&context.destination()); + src.start(); + SOURCE.set(src).unwrap(); + }); + + context.suspend_sync((3 * RENDER_QUANTUM_SIZE) as f64 / sample_rate, |context| { + assert_eq!(context.state(), AudioContextState::Suspended); + SOURCE.get().unwrap().disconnect(); + }); + + let output = context.start_rendering_sync(); + + assert_float_eq!( + output.get_channel_data(0)[..RENDER_QUANTUM_SIZE], + &[0.; RENDER_QUANTUM_SIZE][..], + abs_all <= 0. + ); + assert_float_eq!( + output.get_channel_data(0)[RENDER_QUANTUM_SIZE..3 * RENDER_QUANTUM_SIZE], + &[1.; 2 * RENDER_QUANTUM_SIZE][..], + abs_all <= 0. + ); + assert_float_eq!( + output.get_channel_data(0)[3 * RENDER_QUANTUM_SIZE..4 * RENDER_QUANTUM_SIZE], + &[0.; RENDER_QUANTUM_SIZE][..], + abs_all <= 0. + ); + } + + #[test] + fn render_suspend_resume_async() { + use futures::executor; + use futures::join; + use futures::FutureExt as _; + + let context = Arc::new(OfflineAudioContext::new(1, 512, 44_100.)); + let context_clone = Arc::clone(&context); + + let suspend_promise = context.suspend(128. / 44_100.).then(|_| async move { + let mut src = context_clone.create_constant_source(); + src.connect(&context_clone.destination()); + src.start(); + context_clone.resume().await; + }); + + let render_promise = context.start_rendering(); + + let buffer = executor::block_on(async move { join!(suspend_promise, render_promise).1 }); + + assert_eq!(buffer.number_of_channels(), 1); + assert_eq!(buffer.length(), 512); + + assert_float_eq!( + buffer.get_channel_data(0)[..128], + &[0.; 128][..], + abs_all <= 0. + ); + assert_float_eq!( + buffer.get_channel_data(0)[128..], + &[1.; 384][..], + abs_all <= 0. + ); + } + + #[test] + #[should_panic] + fn test_suspend_negative_panics() { + let mut context = OfflineAudioContext::new(2, 128, 44_100.); + context.suspend_sync(-1.0, |_| ()); + } + + #[test] + #[should_panic] + fn test_suspend_after_duration_panics() { + let mut context = OfflineAudioContext::new(2, 128, 44_100.); + context.suspend_sync(1.0, |_| ()); + } + + #[test] + #[should_panic] + fn test_suspend_after_render_panics() { + let mut context = OfflineAudioContext::new(2, 128, 44_100.); + let _ = context.start_rendering_sync(); + context.suspend_sync(0.0, |_| ()); + } + + #[test] + #[should_panic] + fn test_suspend_identical_frame_panics() { + let mut context = OfflineAudioContext::new(2, 128, 44_100.); + context.suspend_sync(0.0, |_| ()); + context.suspend_sync(0.0, |_| ()); + } + + #[test] + fn test_onstatechange() { + let mut context = OfflineAudioContext::new(2, 555, 44_100.); + + let changed = Arc::new(AtomicBool::new(false)); + let changed_clone = Arc::clone(&changed); + context.set_onstatechange(move |_event| { + changed_clone.store(true, Ordering::Relaxed); + }); + + let _ = context.start_rendering_sync(); + + assert!(changed.load(Ordering::Relaxed)); + } + + #[test] + fn test_onstatechange_async() { + use futures::executor; + + let context = OfflineAudioContext::new(2, 555, 44_100.); + + let changed = Arc::new(AtomicBool::new(false)); + let changed_clone = Arc::clone(&changed); + context.set_onstatechange(move |_event| { + changed_clone.store(true, Ordering::Relaxed); + }); + + let _ = executor::block_on(context.start_rendering()); + + assert!(changed.load(Ordering::Relaxed)); + } + + #[test] + fn test_oncomplete() { + let mut context = OfflineAudioContext::new(2, 555, 44_100.); + + let complete = Arc::new(AtomicBool::new(false)); + let complete_clone = Arc::clone(&complete); + context.set_oncomplete(move |event| { + assert_eq!(event.rendered_buffer.length(), 555); + complete_clone.store(true, Ordering::Relaxed); + }); + + let _ = context.start_rendering_sync(); + + assert!(complete.load(Ordering::Relaxed)); + } + + #[test] + fn test_oncomplete_async() { + use futures::executor; + + let context = OfflineAudioContext::new(2, 555, 44_100.); + + let complete = Arc::new(AtomicBool::new(false)); + let complete_clone = Arc::clone(&complete); + context.set_oncomplete(move |event| { + assert_eq!(event.rendered_buffer.length(), 555); + complete_clone.store(true, Ordering::Relaxed); + }); + + let _ = executor::block_on(context.start_rendering()); + + assert!(complete.load(Ordering::Relaxed)); + } + + fn require_send_sync(_: T) {} + + #[test] + fn test_all_futures_thread_safe() { + let context = OfflineAudioContext::new(2, 555, 44_100.); + + require_send_sync(context.start_rendering()); + require_send_sync(context.suspend(1.)); + require_send_sync(context.resume()); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/context/online.rs b/vendor/web-audio-api-1.7.0/src/context/online.rs new file mode 100644 index 0000000000..a461db904b --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/context/online.rs @@ -0,0 +1,1011 @@ +//! The `AudioContext` type and constructor options +use std::error::Error; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; + +#[cfg(feature = "diagnostics")] +use crate::context::{AudioBackendDiagnostics, AudioContextDiagnostics}; +use crate::context::{AudioContextState, BaseAudioContext, ConcreteBaseAudioContext}; +#[cfg(feature = "diagnostics")] +use crate::events::EventPayload; +use crate::events::{EventDispatch, EventHandler, EventLoop, EventType}; +use crate::io::{self, AudioBackendManager, ControlThreadInit, NoneBackend, RenderThreadInit}; +use crate::media_devices::{enumerate_devices_sync, MediaDeviceInfoKind}; +use crate::media_streams::{MediaStream, MediaStreamTrack}; +use crate::message::{ControlMessage, OneshotNotify}; +use crate::node::{self, AudioNodeOptions}; +use crate::render::graph::Graph; +use crate::MediaElement; +use crate::{is_valid_sample_rate, AudioPlaybackStats, AudioRenderCapacity, Event}; + +use futures_channel::oneshot; + +/// Check if the provided sink_id is available for playback +/// +/// It should be "", "none" or a valid output `sinkId` returned from [`enumerate_devices_sync`] +fn is_valid_sink_id(sink_id: &str) -> bool { + if sink_id.is_empty() || sink_id == "none" { + true + } else { + enumerate_devices_sync() + .into_iter() + .filter(|d| d.kind() == MediaDeviceInfoKind::AudioOutput) + .any(|d| d.device_id() == sink_id) + } +} + +#[derive(Debug)] +enum AudioContextError { + SinkNotFound { sink_id: String }, + InvalidSampleRate { sample_rate: f32 }, + Backend { error: io::AudioBackendError }, +} + +impl std::fmt::Display for AudioContextError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SinkNotFound { sink_id } => { + write!(f, "NotFoundError - Invalid sinkId: {sink_id:?}") + } + Self::InvalidSampleRate { sample_rate } => { + write!( + f, + "NotSupportedError - Invalid sample rate: {sample_rate}, should be in the range [3000.0, 768000.0]" + ) + } + Self::Backend { error } => write!(f, "InvalidStateError - {error}"), + } + } +} + +impl Error for AudioContextError {} + +impl From for AudioContextError { + fn from(error: io::AudioBackendError) -> Self { + Self::Backend { error } + } +} + +/// Identify the type of playback, which affects tradeoffs +/// between audio output latency and power consumption +#[derive(Copy, Clone, Debug, Default)] +pub enum AudioContextLatencyCategory { + /// Balance audio output latency and power consumption. + Balanced, + /// Provide the lowest audio output latency possible without glitching. This is the default. + #[default] + Interactive, + /// Prioritize sustained playback without interruption over audio output latency. + /// + /// Lowest power consumption. + Playback, + /// Specify the number of seconds of latency + /// + /// This latency is not guaranteed to be applied, it depends on the audio hardware capabilities + Custom(f64), +} + +#[derive(Copy, Clone, Debug)] +#[non_exhaustive] +/// This allows users to ask for a particular render quantum size. +/// +/// Currently, only the default value is available +#[derive(Default)] +pub enum AudioContextRenderSizeCategory { + /// The default value of 128 frames + #[default] + Default, +} + +/// Specify the playback configuration for the [`AudioContext`] constructor. +/// +/// All fields are optional and will default to the value best suited for interactive playback on +/// your hardware configuration. +/// +/// For future compatibility, it is best to construct a default implementation of this struct and +/// set the fields you would like to override: +/// ``` +/// use web_audio_api::context::AudioContextOptions; +/// +/// // Request a sample rate of 44.1 kHz, leave other fields to their default values +/// let opts = AudioContextOptions { +/// sample_rate: Some(44100.), +/// ..AudioContextOptions::default() +/// }; +#[derive(Clone, Debug, Default)] +pub struct AudioContextOptions { + /// Identify the type of playback, which affects tradeoffs between audio output latency and + /// power consumption. + pub latency_hint: AudioContextLatencyCategory, + + /// Sample rate of the audio context and audio output hardware. Use `None` for a default value. + pub sample_rate: Option, + + /// The audio output device + /// - use `""` for the default audio output device + /// - use `"none"` to process the audio graph without playing through an audio output device. + /// - use `"sinkId"` to use the specified audio sink id, obtained with [`enumerate_devices_sync`] + pub sink_id: String, + + /// Option to request a default, optimized or specific render quantum size. It is a hint that might not be honored. + pub render_size_hint: AudioContextRenderSizeCategory, +} + +/// This interface represents an audio graph whose `AudioDestinationNode` is routed to a real-time +/// output device that produces a signal directed at the user. +// the naming comes from the web audio specification +#[allow(clippy::module_name_repetitions)] +pub struct AudioContext { + /// represents the underlying `BaseAudioContext` + base: ConcreteBaseAudioContext, + /// audio backend (play/pause functionality) + backend_manager: Mutex>, + /// Provider for rendering performance metrics + render_capacity: AudioRenderCapacity, + /// Provider for playback statistics + playback_stats: AudioPlaybackStats, + /// true while the render thread has not yet processed its initial Startup message + startup_pending: std::sync::Arc, + /// Initializer for the render thread (when restart is required) + render_thread_init: RenderThreadInit, +} + +impl std::fmt::Debug for AudioContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AudioContext") + .field("sink_id", &self.sink_id()) + .field("base_latency", &self.base_latency()) + .field("output_latency", &self.output_latency()) + .field("base", &self.base()) + .finish_non_exhaustive() + } +} + +impl Drop for AudioContext { + fn drop(&mut self) { + // Continue playing the stream if the AudioContext goes out of scope + if self.state() == AudioContextState::Running { + let tombstone = Box::new(NoneBackend::void()); + let original = std::mem::replace(self.backend_manager.get_mut().unwrap(), tombstone); + Box::leak(original); + } + } +} + +impl BaseAudioContext for AudioContext { + fn base(&self) -> &ConcreteBaseAudioContext { + &self.base + } +} + +impl Default for AudioContext { + fn default() -> Self { + Self::new(AudioContextOptions::default()) + } +} + +impl AudioContext { + /// Creates and returns a new `AudioContext` object. + /// + /// This will play live audio on the default output device. + /// + /// ```no_run + /// use web_audio_api::context::{AudioContext, AudioContextOptions}; + /// + /// // Request a sample rate of 44.1 kHz and default latency (buffer size 128, if available) + /// let opts = AudioContextOptions { + /// sample_rate: Some(44100.), + /// ..AudioContextOptions::default() + /// }; + /// + /// // Setup the audio context that will emit to your speakers + /// let context = AudioContext::new(opts); + /// + /// // Alternatively, use the default constructor to get the best settings for your hardware + /// // let context = AudioContext::default(); + /// ``` + /// + /// # Panics + /// + /// The `AudioContext` constructor will panic when an invalid `sinkId` is provided in the + /// `AudioContextOptions`, when the sample rate is outside the valid range [3000.0, 768000.0], + /// or when the selected audio backend cannot create or start the output stream. Use + /// [`Self::try_new`] to handle these errors without panicking. + #[must_use] + pub fn new(options: AudioContextOptions) -> Self { + Self::try_new_inner(options).unwrap_or_else(|e| panic!("{e}")) + } + + /// Creates and returns a new `AudioContext` object. + /// + /// This will play live audio on the requested output device and returns backend errors instead + /// of panicking when the stream cannot be created. + /// + /// # Errors + /// + /// Returns an error when the sink id is invalid, the sample rate is outside the valid range + /// [3000.0, 768000.0], or when the selected audio backend cannot create or start the output + /// stream. + pub fn try_new(options: AudioContextOptions) -> Result> { + Self::try_new_inner(options).map_err(Into::into) + } + + fn try_new_inner(options: AudioContextOptions) -> Result { + // https://webaudio.github.io/web-audio-api/#validating-sink-identifier + if !is_valid_sink_id(&options.sink_id) { + return Err(AudioContextError::SinkNotFound { + sink_id: options.sink_id, + }); + } + + // Validate sample_rate if provided + // https://webaudio.github.io/web-audio-api/#sample-rates + if let Some(sample_rate) = options.sample_rate { + if !is_valid_sample_rate(sample_rate) { + return Err(AudioContextError::InvalidSampleRate { sample_rate }); + } + } + + // Set up the audio output thread + let (control_thread_init, render_thread_init) = io::thread_init(); + let startup_pending = Arc::clone(&render_thread_init.startup_pending); + let backend = io::build_output(options, render_thread_init.clone())?; + + let ControlThreadInit { + state, + frames_played, + stats, + ctrl_msg_send, + event_send, + event_recv, + } = control_thread_init; + + // Construct the audio Graph and hand it to the render thread + let (node_id_producer, node_id_consumer) = llq::Queue::new().split(); + let graph = Graph::new(node_id_producer); + let message = ControlMessage::Startup { graph }; + ctrl_msg_send.send(message).unwrap(); + + // Set up the event loop thread that handles the events spawned by the render thread + let event_loop = EventLoop::new(event_recv); + + // Put everything together in the BaseAudioContext (shared with offline context) + let base = ConcreteBaseAudioContext::new( + backend.sample_rate(), + backend.number_of_channels(), + state, + frames_played, + ctrl_msg_send, + event_send, + event_loop.clone(), + false, + node_id_consumer, + ); + + // Setup AudioRenderCapacity for this context + let render_capacity = AudioRenderCapacity::new(base.clone(), stats.clone()); + let playback_stats = AudioPlaybackStats::new(base.clone(), stats); + + // As the final step, spawn a thread for the event loop. If we do this earlier we may miss + // event handling of the initial events that are emitted right after render thread + // construction. + event_loop.run_in_thread(); + + Ok(Self { + base, + backend_manager: Mutex::new(backend), + render_capacity, + playback_stats, + startup_pending, + render_thread_init, + }) + } + + /// This represents the number of seconds of processing latency incurred by + /// the `AudioContext` passing the audio from the `AudioDestinationNode` + /// to the audio subsystem. + // We don't do any buffering between rendering the audio and sending + // it to the audio subsystem, so this value is zero. (see Gecko) + #[allow(clippy::unused_self)] + #[must_use] + pub fn base_latency(&self) -> f64 { + 0. + } + + /// The estimation in seconds of audio output latency, i.e., the interval + /// between the time the UA requests the host system to play a buffer and + /// the time at which the first sample in the buffer is actually processed + /// by the audio output device. + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn output_latency(&self) -> f64 { + self.try_output_latency() + .unwrap_or_else(|e| panic!("InvalidStateError - {e}")) + } + + /// The estimation in seconds of audio output latency. + /// + /// # Errors + /// + /// Returns an error when the selected audio backend cannot query the output latency. + fn try_output_latency(&self) -> Result> { + Ok(self.backend_manager.lock().unwrap().output_latency()?) + } + + /// Identifier or the information of the current audio output device. + /// + /// The initial value is `""`, which means the default audio output device. + #[allow(clippy::missing_panics_doc)] + pub fn sink_id(&self) -> String { + self.backend_manager.lock().unwrap().sink_id().to_owned() + } + + /// Returns an [`AudioRenderCapacity`] instance associated with an AudioContext. + #[must_use] + pub fn render_capacity(&self) -> AudioRenderCapacity { + self.render_capacity.clone() + } + + /// Returns an [`AudioPlaybackStats`] instance associated with this `AudioContext`. + #[must_use] + pub fn playback_stats(&self) -> AudioPlaybackStats { + self.playback_stats.clone() + } + + /// Update the current audio output device. + /// + /// The provided `sink_id` string must match a device name `enumerate_devices_sync`. + /// + /// Supplying `"none"` for the `sink_id` will process the audio graph without playing through an + /// audio output device. + /// + /// This function operates synchronously and might block the current thread. An async version + /// is currently not implemented. + #[allow(clippy::needless_collect, clippy::missing_panics_doc)] + pub fn set_sink_id_sync(&self, sink_id: String) -> Result<(), Box> { + log::debug!("SinkChange requested"); + if self.sink_id() == sink_id { + log::debug!("SinkChange: no-op"); + return Ok(()); // sink is already active + } + + if !is_valid_sink_id(&sink_id) { + Err(format!("NotFoundError: invalid sinkId {sink_id}"))?; + }; + + log::debug!("SinkChange: locking backend manager"); + let mut backend_manager_guard = self.backend_manager.lock().unwrap(); + let original_state = self.state(); + if original_state == AudioContextState::Closed { + log::debug!("SinkChange: context is closed"); + return Ok(()); + } + + // Acquire exclusive lock on ctrl msg sender + log::debug!("SinkChange: locking message channel"); + let ctrl_msg_send = self.base.lock_control_msg_sender(); + + // Flush out the ctrl msg receiver, cache + let mut pending_msgs: Vec<_> = self.render_thread_init.ctrl_msg_recv.try_iter().collect(); + + // Acquire the active audio graph from the current render thread, shutting it down + let graph = if matches!(pending_msgs.first(), Some(ControlMessage::Startup { .. })) { + // Handle the edge case where the previous backend was suspended for its entire lifetime. + // In this case, the `Startup` control message was never processed. + log::debug!("SinkChange: recover unstarted graph"); + + let msg = pending_msgs.remove(0); + match msg { + ControlMessage::Startup { graph } => graph, + _ => unreachable!(), + } + } else { + // Acquire the audio graph from the current render thread, shutting it down + log::debug!("SinkChange: recover graph from render thread"); + + let (graph_send, graph_recv) = crossbeam_channel::bounded(1); + let message = ControlMessage::CloseAndRecycle { sender: graph_send }; + ctrl_msg_send.send(message).unwrap(); + if original_state == AudioContextState::Suspended { + // We must wake up the render thread to be able to handle the shutdown. + // No new audio will be produced because it will receive the shutdown command first. + backend_manager_guard.resume()?; + } + graph_recv.recv().unwrap() + }; + + log::debug!("SinkChange: closing audio stream"); + backend_manager_guard.close()?; + + // hotswap the backend + let options = AudioContextOptions { + sample_rate: Some(self.sample_rate()), + latency_hint: AudioContextLatencyCategory::default(), // todo reuse existing setting + sink_id, + render_size_hint: AudioContextRenderSizeCategory::default(), // todo reuse existing setting + }; + log::debug!("SinkChange: starting audio stream"); + *backend_manager_guard = io::build_output(options, self.render_thread_init.clone())?; + + // if the previous backend state was suspend, suspend the new one before shipping the graph + if original_state == AudioContextState::Suspended { + log::debug!("SinkChange: suspending audio stream"); + backend_manager_guard.suspend()?; + } + + // send the audio graph to the new render thread + let message = ControlMessage::Startup { graph }; + ctrl_msg_send.send(message).unwrap(); + + // flush the cached msgs + pending_msgs + .into_iter() + .for_each(|m| self.base().send_control_msg(m)); + + // explicitly release the lock to prevent concurrent render threads + drop(backend_manager_guard); + + // trigger event when all the work is done + let _ = self.base.send_event(EventDispatch::sink_change()); + + log::debug!("SinkChange: done"); + Ok(()) + } + + /// Register callback to run when the audio sink has changed + /// + /// Only a single event handler is active at any time. Calling this method multiple times will + /// override the previous event handler. + pub fn set_onsinkchange(&self, mut callback: F) { + let callback = move |_| { + callback(Event { + type_: "sinkchange", + }) + }; + + self.base().set_event_handler( + EventType::SinkChange, + EventHandler::Multiple(Box::new(callback)), + ); + } + + /// Unset the callback to run when the audio sink has changed + pub fn clear_onsinkchange(&self) { + self.base().clear_event_handler(EventType::SinkChange); + } + + /// Request a structured diagnostic report of the audio context. + /// + /// The report is collected asynchronously: backend details are captured on the control thread, + /// while render thread and graph details are captured in the realtime render thread. The + /// callback is invoked once on the event loop thread. + /// + /// This API is available with the `diagnostics` crate feature. + #[cfg(feature = "diagnostics")] + #[allow(clippy::missing_panics_doc)] + pub fn run_diagnostics(&self, callback: F) { + let backend = { + let backend = self.backend_manager.lock().unwrap(); + AudioBackendDiagnostics { + name: backend.name().to_string(), + sink_id: backend.sink_id().to_string(), + output_latency: backend.output_latency().ok(), + } + }; + + let callback = move |v| match v { + EventPayload::Diagnostics(v) => { + callback(v); + } + _ => unreachable!(), + }; + + self.base().set_event_handler( + EventType::Diagnostics, + EventHandler::Once(Box::new(callback)), + ); + + self.base() + .send_control_msg(ControlMessage::RunDiagnostics { backend }); + } + + /// Suspends the progression of time in the audio context. + /// + /// This will temporarily halt audio hardware access and reducing CPU/battery usage in the + /// process. + /// + /// # Panics + /// + /// Will panic if: + /// + /// * The audio device is not available + /// * For a `BackendSpecificError` + pub async fn suspend(&self) { + // Don't lock the backend manager because we can't hold is across the await point + log::debug!("Suspend called"); + + let state = self.state(); + if state == AudioContextState::Closed { + log::debug!("Suspend no-op - context is closed"); + return; + } + + if state != AudioContextState::Running && !self.startup_pending.load(Ordering::Acquire) { + log::debug!("Suspend no-op - context is not running"); + return; + } + + // Pause rendering via a control message + let (sender, receiver) = oneshot::channel(); + let notify = OneshotNotify::Async(sender); + self.base + .suspend_control_msgs(ControlMessage::Suspend { notify }); + + // Wait for the render thread to have processed the suspend message. + // The AudioContextState will be updated by the render thread. + log::debug!("Suspending audio graph, waiting for signal.."); + receiver.await.unwrap(); + + // Then ask the audio host to suspend the stream + log::debug!("Suspended audio graph. Suspending audio stream.."); + self.backend_manager + .lock() + .unwrap() + .suspend() + .unwrap_or_else(|e| panic!("InvalidStateError - {e}")); + + log::debug!("Suspended audio stream"); + } + + /// Resumes the progression of time in an audio context that has previously been + /// suspended/paused. + /// + /// # Panics + /// + /// Will panic if: + /// + /// * The audio device is not available + /// * For a `BackendSpecificError` + pub async fn resume(&self) { + let (sender, receiver) = oneshot::channel(); + + { + // Lock the backend manager mutex to avoid concurrent calls + log::debug!("Resume called, locking backend manager"); + let backend_manager_guard = self.backend_manager.lock().unwrap(); + + if self.state() != AudioContextState::Suspended { + log::debug!("Resume no-op - context is not suspended"); + return; + } + + // Ask the audio host to resume the stream + backend_manager_guard + .resume() + .unwrap_or_else(|e| panic!("InvalidStateError - {e}")); + + // Then, ask to resume rendering via a control message + log::debug!("Resumed audio stream, waking audio graph"); + let notify = OneshotNotify::Async(sender); + self.base + .resume_control_msgs(ControlMessage::Resume { notify }); + + // Drop the Mutex guard so we won't hold it across an await point + } + + // Wait for the render thread to have processed the resume message + // The AudioContextState will be updated by the render thread. + receiver.await.unwrap(); + log::debug!("Resumed audio graph"); + } + + /// Closes the `AudioContext`, releasing the system resources being used. + /// + /// This will not automatically release all `AudioContext`-created objects, but will suspend + /// the progression of the currentTime, and stop processing audio data. + /// + /// # Panics + /// + /// Will panic when this function is called multiple times + pub async fn close(&self) { + // Don't lock the backend manager because we can't hold is across the await point + log::debug!("Close called"); + + if self.state() == AudioContextState::Closed { + log::debug!("Close no-op - context is already closed"); + return; + } + + // Stop AudioRenderCapacity before closing so no capacity events are queued during shutdown. + self.render_capacity.stop(); + + if self.state() == AudioContextState::Running { + // First, stop rendering via a control message + let (sender, receiver) = oneshot::channel(); + let notify = OneshotNotify::Async(sender); + self.base.send_control_msg(ControlMessage::Close { notify }); + + // Wait for the render thread to have processed the suspend message. + // The AudioContextState will be updated by the render thread. + log::debug!("Suspending audio graph, waiting for signal.."); + receiver.await.unwrap(); + } else { + // if the context is not running, change the state manually + self.base.set_state(AudioContextState::Closed); + } + + // Then ask the audio host to close the stream + log::debug!("Suspended audio graph. Closing audio stream.."); + self.backend_manager + .lock() + .unwrap() + .close() + .unwrap_or_else(|e| panic!("InvalidStateError - {e}")); + + log::debug!("Closed audio stream"); + } + + /// Suspends the progression of time in the audio context. + /// + /// This will temporarily halt audio hardware access and reducing CPU/battery usage in the + /// process. + /// + /// This function operates synchronously and blocks the current thread until the audio thread + /// has stopped processing. + /// + /// # Panics + /// + /// Will panic if: + /// + /// * The audio device is not available + /// * For a `BackendSpecificError` + pub fn suspend_sync(&self) { + // Lock the backend manager mutex to avoid concurrent calls + log::debug!("Suspend_sync called, locking backend manager"); + let backend_manager_guard = self.backend_manager.lock().unwrap(); + + let state = self.state(); + if state == AudioContextState::Closed { + log::debug!("Suspend_sync no-op - context is closed"); + return; + } + + if state != AudioContextState::Running && !self.startup_pending.load(Ordering::Acquire) { + log::debug!("Suspend_sync no-op - context is not running"); + return; + } + + // Pause rendering via a control message + let (sender, receiver) = crossbeam_channel::bounded(0); + let notify = OneshotNotify::Sync(sender); + self.base + .suspend_control_msgs(ControlMessage::Suspend { notify }); + + // Wait for the render thread to have processed the suspend message. + // The AudioContextState will be updated by the render thread. + log::debug!("Suspending audio graph, waiting for signal.."); + receiver.recv().ok(); + + // Then ask the audio host to suspend the stream + log::debug!("Suspended audio graph. Suspending audio stream.."); + backend_manager_guard + .suspend() + .unwrap_or_else(|e| panic!("InvalidStateError - {e}")); + + log::debug!("Suspended audio stream"); + } + + /// Resumes the progression of time in an audio context that has previously been + /// suspended/paused. + /// + /// This function operates synchronously and blocks the current thread until the audio thread + /// has started processing again. + /// + /// # Panics + /// + /// Will panic if: + /// + /// * The audio device is not available + /// * For a `BackendSpecificError` + pub fn resume_sync(&self) { + // Lock the backend manager mutex to avoid concurrent calls + log::debug!("Resume_sync called, locking backend manager"); + let backend_manager_guard = self.backend_manager.lock().unwrap(); + + if self.state() != AudioContextState::Suspended { + log::debug!("Resume no-op - context is not suspended"); + return; + } + + // Ask the audio host to resume the stream + backend_manager_guard + .resume() + .unwrap_or_else(|e| panic!("InvalidStateError - {e}")); + + // Then, ask to resume rendering via a control message + log::debug!("Resumed audio stream, waking audio graph"); + let (sender, receiver) = crossbeam_channel::bounded(0); + let notify = OneshotNotify::Sync(sender); + self.base + .resume_control_msgs(ControlMessage::Resume { notify }); + + // Wait for the render thread to have processed the resume message + // The AudioContextState will be updated by the render thread. + receiver.recv().ok(); + log::debug!("Resumed audio graph"); + } + + /// Closes the `AudioContext`, releasing the system resources being used. + /// + /// This will not automatically release all `AudioContext`-created objects, but will suspend + /// the progression of the currentTime, and stop processing audio data. + /// + /// This function operates synchronously and blocks the current thread until the audio thread + /// has stopped processing. + /// + /// # Panics + /// + /// Will panic when this function is called multiple times + pub fn close_sync(&self) { + // Lock the backend manager mutex to avoid concurrent calls + log::debug!("Close_sync called, locking backend manager"); + let backend_manager_guard = self.backend_manager.lock().unwrap(); + + if self.state() == AudioContextState::Closed { + log::debug!("Close no-op - context is already closed"); + return; + } + + // Stop AudioRenderCapacity before closing so no capacity events are queued during shutdown. + self.render_capacity.stop(); + + // First, stop rendering via a control message + if self.state() == AudioContextState::Running { + let (sender, receiver) = crossbeam_channel::bounded(0); + let notify = OneshotNotify::Sync(sender); + self.base.send_control_msg(ControlMessage::Close { notify }); + + // Wait for the render thread to have processed the suspend message. + // The AudioContextState will be updated by the render thread. + log::debug!("Suspending audio graph, waiting for signal.."); + receiver.recv().ok(); + } else { + // if the context is not running, change the state manually + self.base.set_state(AudioContextState::Closed); + } + + // Then ask the audio host to close the stream + log::debug!("Suspended audio graph. Closing audio stream.."); + backend_manager_guard + .close() + .unwrap_or_else(|e| panic!("InvalidStateError - {e}")); + + log::debug!("Closed audio stream"); + } + + /// Creates a [`MediaStreamAudioSourceNode`](node::MediaStreamAudioSourceNode) from a + /// [`MediaStream`] + #[must_use] + pub fn create_media_stream_source( + &self, + media: &MediaStream, + ) -> node::MediaStreamAudioSourceNode { + let opts = node::MediaStreamAudioSourceOptions { + media_stream: media, + }; + node::MediaStreamAudioSourceNode::new(self, opts) + } + + /// Creates a [`MediaStreamAudioDestinationNode`](node::MediaStreamAudioDestinationNode) + #[must_use] + pub fn create_media_stream_destination(&self) -> node::MediaStreamAudioDestinationNode { + let opts = AudioNodeOptions::default(); + node::MediaStreamAudioDestinationNode::new(self, opts) + } + + /// Creates a [`MediaStreamTrackAudioSourceNode`](node::MediaStreamTrackAudioSourceNode) from a + /// [`MediaStreamTrack`] + #[must_use] + pub fn create_media_stream_track_source( + &self, + media: &MediaStreamTrack, + ) -> node::MediaStreamTrackAudioSourceNode { + let opts = node::MediaStreamTrackAudioSourceOptions { + media_stream_track: media, + }; + node::MediaStreamTrackAudioSourceNode::new(self, opts) + } + + /// Creates a [`MediaElementAudioSourceNode`](node::MediaElementAudioSourceNode) from a + /// [`MediaElement`] + #[must_use] + pub fn create_media_element_source( + &self, + media_element: &mut MediaElement, + ) -> node::MediaElementAudioSourceNode { + let opts = node::MediaElementAudioSourceOptions { media_element }; + node::MediaElementAudioSourceNode::new(self, opts) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[cfg(feature = "diagnostics")] + use crate::context::DESTINATION_NODE_ID; + use futures::executor; + + #[test] + fn test_suspend_resume_close() { + let options = AudioContextOptions { + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + + // construct with 'none' sink_id + let context = AudioContext::new(options); + + // Ensure startup has been processed before testing suspend/resume transitions. + executor::block_on(context.resume()); + assert_eq!(context.state(), AudioContextState::Running); + + executor::block_on(context.suspend()); + assert_eq!(context.state(), AudioContextState::Suspended); + let time1 = context.current_time(); + assert!(time1 >= 0.); + + // allow some time to progress + std::thread::sleep(std::time::Duration::from_millis(1)); + let time2 = context.current_time(); + assert_eq!(time1, time2); // no progression of time + + executor::block_on(context.resume()); + assert_eq!(context.state(), AudioContextState::Running); + + // allow some time to progress + std::thread::sleep(std::time::Duration::from_millis(1)); + + let time3 = context.current_time(); + assert!(time3 > time2); // time is progressing + + executor::block_on(context.close()); + assert_eq!(context.state(), AudioContextState::Closed); + + let time4 = context.current_time(); + + // allow some time to progress + std::thread::sleep(std::time::Duration::from_millis(1)); + + let time5 = context.current_time(); + assert_eq!(time5, time4); // no progression of time + } + + #[test] + fn test_suspend_during_startup() { + let options = AudioContextOptions { + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + + let context = AudioContext::new(options); + + executor::block_on(context.suspend()); + assert_eq!(context.state(), AudioContextState::Suspended); + + let time1 = context.current_time(); + std::thread::sleep(std::time::Duration::from_millis(5)); + let time2 = context.current_time(); + assert_eq!(time1, time2); + } + + #[test] + fn test_suspend_sync_during_startup() { + let options = AudioContextOptions { + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + + let context = AudioContext::new(options); + + context.suspend_sync(); + assert_eq!(context.state(), AudioContextState::Suspended); + + let time1 = context.current_time(); + std::thread::sleep(std::time::Duration::from_millis(5)); + let time2 = context.current_time(); + assert_eq!(time1, time2); + } + + fn require_send_sync(_: T) {} + + #[test] + fn test_all_futures_thread_safe() { + let options = AudioContextOptions { + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + let context = AudioContext::new(options); + + require_send_sync(context.suspend()); + require_send_sync(context.resume()); + require_send_sync(context.close()); + } + + #[test] + fn test_try_new_invalid_sample_rate() { + let options = AudioContextOptions { + sample_rate: Some(0.), + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + + let result = AudioContext::try_new(options); + assert!(result.is_err()); + let error_msg = result.unwrap_err().to_string(); + assert!(error_msg.contains("Invalid sample rate")); + } + + #[test] + #[should_panic] + fn test_invalid_sink_id() { + let options = AudioContextOptions { + sink_id: "invalid".into(), + ..AudioContextOptions::default() + }; + let _ = AudioContext::new(options); + } + + #[test] + fn test_try_new_invalid_sink_id() { + let options = AudioContextOptions { + sink_id: "invalid".into(), + ..AudioContextOptions::default() + }; + + let error = AudioContext::try_new(options).unwrap_err(); + assert_eq!( + error.to_string(), + "NotFoundError - Invalid sinkId: \"invalid\"" + ); + } + + #[cfg(feature = "diagnostics")] + #[test] + fn test_run_diagnostics_returns_structured_output() { + let options = AudioContextOptions { + sink_id: "none".into(), + ..AudioContextOptions::default() + }; + let context = AudioContext::new(options); + let (sender, receiver) = std::sync::mpsc::channel(); + + context.run_diagnostics(move |diagnostics| { + sender.send(diagnostics).unwrap(); + }); + + let diagnostics = receiver + .recv_timeout(std::time::Duration::from_secs(1)) + .unwrap(); + + assert!(diagnostics.backend.name.contains("NoneBackend")); + assert_eq!(diagnostics.backend.sink_id, "none"); + assert_eq!(diagnostics.backend.output_latency, Some(0.)); + assert_eq!(diagnostics.render_thread.sample_rate, context.sample_rate()); + assert_eq!( + diagnostics.render_thread.number_of_channels, + crate::MAX_CHANNELS + ); + assert!(diagnostics.graph.active); + assert_eq!(diagnostics.graph.node_count, diagnostics.graph.nodes.len()); + assert_eq!(diagnostics.graph.edge_count, 0); + assert!(diagnostics.graph.in_cycle.is_empty()); + assert!(diagnostics.graph.cycle_breakers.is_empty()); + assert!(diagnostics + .graph + .nodes + .iter() + .any(|node| node.id == DESTINATION_NODE_ID.0 + && node.inputs == node.input_channels.len() + && node.outputs == node.output_channels.len())); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/decoding.rs b/vendor/web-audio-api-1.7.0/src/decoding.rs new file mode 100644 index 0000000000..7bc4136bbf --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/decoding.rs @@ -0,0 +1,310 @@ +use std::error::Error; +use std::io::{Read, Seek, SeekFrom}; + +use crate::buffer::{AudioBuffer, ChannelData}; + +use symphonia::core::audio::GenericAudioBufferRef; +use symphonia::core::codecs::audio::{ + AudioCodecId, AudioDecoder, AudioDecoderOptions, FinalizeResult, +}; +use symphonia::core::errors::Error as SymphoniaError; +use symphonia::core::formats::probe::Hint; +use symphonia::core::formats::{FormatOptions, FormatReader, TrackType}; +use symphonia::core::meta::MetadataOptions; + +pub(crate) fn decode_media_data( + input: R, + target_sample_rate: f32, +) -> Result> { + let mut sample_rate = None; + let mut buffer: Option = None; + + for chunk in MediaDecoder::try_new(input)? { + let chunk = chunk?; + + match sample_rate { + Some(rate) if rate != chunk.sample_rate() => { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "decoded audio sample rate changed midstream", + ))); + } + Some(_) => {} + None => sample_rate = Some(chunk.sample_rate()), + } + + match buffer { + Some(ref mut buffer) if buffer.number_of_channels() != chunk.number_of_channels() => { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "decoded audio channel count changed midstream", + ))); + } + Some(ref mut buffer) => buffer.extend(&chunk), + None => buffer = Some(chunk), + } + } + + let mut buffer = buffer.unwrap_or_else(|| AudioBuffer::from(vec![vec![]], target_sample_rate)); + + // Resample to desired rate (no-op if already matching). + buffer.resample(target_sample_rate); + + Ok(buffer) +} + +/// Wrapper for `Read` implementers to be used in Symphonia decoding +/// +/// Symphonia requires its input to impl `Seek` - but allows non-seekable sources. Hence we +/// implement Seek but return false for `is_seekable()`. +struct MediaInput { + input: R, +} + +impl MediaInput { + pub fn new(input: R) -> Self { + Self { input } + } +} + +impl Read for MediaInput { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.input.read(buf) + } +} + +impl Seek for MediaInput { + fn seek(&mut self, _pos: SeekFrom) -> std::io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "MediaInput does not support seeking", + )) + } +} + +impl symphonia::core::io::MediaSource for MediaInput { + fn is_seekable(&self) -> bool { + false + } + fn byte_len(&self) -> Option { + None + } +} + +/// Media stream decoder (OGG, WAV, FLAC, ..) +/// +/// The current implementation supports Symphonia's audio formats and codecs. +pub(crate) struct MediaDecoder { + format: Box, + decoder: Box, + track_index: usize, + packet_count: usize, +} + +impl MediaDecoder { + /// Try to construct a new instance from a `Read` implementer + /// + /// # Errors + /// + /// This method returns an Error in various cases (IO, mime sniffing, decoding). + pub fn try_new( + input: R, + ) -> Result> { + // Symphonia lib needs a Box - use our own MediaInput + let input = Box::new(MediaInput::new(input)); + + // Create the media source stream using the boxed media source from above. + let stream = symphonia::core::io::MediaSourceStream::new(input, Default::default()); + + // Create a hint to help the format registry guess what format reader is appropriate. In this + // function we'll leave it empty. + let hint = Hint::new(); + + // TODO: Allow to customize some options. + let format_opts: FormatOptions = Default::default(); + let metadata_opts: MetadataOptions = Default::default(); + // Opt-in to verify the decoded data against the checksums in the container. + let decoder_opts = AudioDecoderOptions::default().verify(true); + + // Probe the media source stream for a format. + let format = + symphonia::default::get_probe().probe(&hint, stream, format_opts, metadata_opts)?; + + // Get the default audio track. + let track = format + .default_track(TrackType::Audio) + .ok_or(SymphoniaError::Unsupported( + "no default media track available", + ))?; + let track_index = format + .tracks() + .iter() + .position(|t| t.id == track.id) + .unwrap(); + + let codec_params = track + .codec_params + .as_ref() + .and_then(|params| params.audio()) + .ok_or(SymphoniaError::Unsupported( + "default media track is not an audio track", + ))?; + + // Create a (stateful) decoder for the track. + let decoder = symphonia::default::get_codecs() + .make_audio_decoder(codec_params, &decoder_opts) + .map_err(|err| { + if matches!( + err, + SymphoniaError::Unsupported("core (codec): unsupported audio codec") + ) { + Box::new(UnsupportedAudioCodecError { + codec: codec_params.codec, + }) as Box + } else { + Box::new(err) as Box + } + })?; + + Ok(Self { + format, + decoder, + track_index, + packet_count: 0, + }) + } +} + +#[derive(Debug)] +struct UnsupportedAudioCodecError { + codec: AudioCodecId, +} + +impl std::fmt::Display for UnsupportedAudioCodecError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "unsupported audio codec: {}", self.codec) + } +} + +impl std::error::Error for UnsupportedAudioCodecError {} + +impl Iterator for MediaDecoder { + type Item = Result>; + + fn next(&mut self) -> Option { + let Self { + format, + decoder, + track_index, + packet_count, + } = self; + + // Get the track. + let track = format.tracks().get(*track_index)?; + let track_id = track.id; + + loop { + // Get the next packet from the format reader. + let packet = match format.next_packet() { + Ok(None) => { + log::debug!("Decoding finished after {packet_count} packet(s)"); + let FinalizeResult { verify_ok } = decoder.finalize(); + if verify_ok == Some(false) { + log::warn!("Verification of decoded data failed"); + } + return None; + } + Err(err) => { + if let SymphoniaError::IoError(err) = &err { + if err.kind() == std::io::ErrorKind::UnexpectedEof { + log::debug!( + "Decoding finished after {packet_count} packet(s) at unexpected EOF" + ); + let FinalizeResult { verify_ok } = decoder.finalize(); + if verify_ok == Some(false) { + log::warn!("Verification of decoded data failed"); + } + return None; + } + } + + log::warn!( + "Failed to fetch next packet following packet #{packet_count}: {err}" + ); + return Some(Err(Box::new(err))); + } + Ok(Some(packet)) => { + *packet_count += 1; + packet + } + }; + + // If the packet does not belong to the selected track, skip it. + let packet_track_id = packet.track_id; + if packet_track_id != track_id { + log::debug!( + "Skipping packet from other track {packet_track_id} while decoding track {track_id}" + ); + continue; + } + + // Decode the packet into audio samples. + match decoder.decode(&packet) { + Ok(input) => { + let output = input.into(); + return Some(Ok(output)); + } + Err(SymphoniaError::DecodeError(err)) => { + // Recoverable error, continue with the next packet. + log::warn!("Failed to decode packet #{packet_count}: {err}"); + } + Err(SymphoniaError::IoError(err)) => { + // Recoverable error, continue with the next packet. + log::warn!("I/O error while decoding packet #{packet_count}: {err}"); + } + Err(err) => { + // All other errors are considered fatal and decoding must be aborted. + return Some(Err(Box::new(err))); + } + }; + } + } +} + +impl From> for AudioBuffer { + fn from(input: GenericAudioBufferRef<'_>) -> Self { + let sample_rate = input.spec().rate() as f32; + + let mut data = Vec::new(); + input.copy_to_vecs_planar::(&mut data); + + let channels = data.into_iter().map(ChannelData::from).collect(); + AudioBuffer::from_channels(channels, sample_rate) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + #[test] + fn test_media_decoder() { + let input = Cursor::new(vec![0; 32]); + let media = MediaDecoder::try_new(input); + + assert!(media.is_err()); // the input was not a valid MIME type + } + + #[test] + fn test_unsupported_audio_codec_error_includes_codec_id() { + let input = std::fs::File::open("samples/sample.webm").unwrap(); + let media = MediaDecoder::try_new(input); + + let err = match media { + Ok(_) => panic!("expected unsupported codec error"), + Err(err) => err.to_string(), + }; + assert_eq!(err, "unsupported audio codec: 0x1001"); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/events.rs b/vendor/web-audio-api-1.7.0/src/events.rs new file mode 100644 index 0000000000..069e9f36af --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/events.rs @@ -0,0 +1,264 @@ +#[cfg(feature = "diagnostics")] +use crate::context::AudioContextDiagnostics; +use crate::context::ConcreteBaseAudioContext; +use crate::context::{AudioContextState, AudioNodeId}; +use crate::{AudioBuffer, AudioRenderCapacityEvent}; + +use std::any::Any; +use std::collections::HashMap; +use std::hash::Hash; +use std::ops::ControlFlow; +use std::sync::{Arc, Mutex}; + +use crossbeam_channel::Receiver; + +/// The Event interface +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Event { + pub type_: &'static str, +} + +#[derive(Hash, Eq, PartialEq, Debug)] +pub(crate) enum EventType { + Ended(AudioNodeId), + SinkChange, + StateChange, + RenderCapacity, + ProcessorError(AudioNodeId), + #[cfg(feature = "diagnostics")] + Diagnostics, + Message(AudioNodeId), + Complete, + AudioProcessing(AudioNodeId), +} + +/// The Error Event interface +#[non_exhaustive] +#[derive(Debug)] +pub struct ErrorEvent { + /// The error message + pub message: String, + /// The object with which panic was originally invoked. + pub error: Box, + /// Inherits from this base Event + pub event: Event, +} + +/// The AudioProcessingEvent interface +#[non_exhaustive] +#[derive(Debug)] +pub struct AudioProcessingEvent { + /// The input buffer + pub input_buffer: AudioBuffer, + /// The output buffer + pub output_buffer: AudioBuffer, + /// The time when the audio will be played in the same time coordinate system as the + /// AudioContext's currentTime. + pub playback_time: f64, + pub(crate) registration: Option<(ConcreteBaseAudioContext, AudioNodeId)>, +} + +impl Drop for AudioProcessingEvent { + fn drop(&mut self) { + if let Some((context, id)) = self.registration.take() { + let wrapped = crate::message::ControlMessage::NodeMessage { + id, + msg: llq::Node::new(Box::new(self.output_buffer.clone())), + }; + context.send_control_msg(wrapped); + } + } +} + +/// The OfflineAudioCompletionEvent Event interface +#[non_exhaustive] +#[derive(Debug)] +pub struct OfflineAudioCompletionEvent { + /// The rendered AudioBuffer + pub rendered_buffer: AudioBuffer, + /// Inherits from this base Event + pub event: Event, +} + +#[derive(Debug)] +pub(crate) enum EventPayload { + None, + RenderCapacity(AudioRenderCapacityEvent), + ProcessorError(ErrorEvent), + #[cfg(feature = "diagnostics")] + Diagnostics(AudioContextDiagnostics), + Message(Box), + AudioContextState(AudioContextState), + Complete(AudioBuffer), + AudioProcessing(AudioProcessingEvent), +} + +#[derive(Debug)] +pub(crate) struct EventDispatch { + type_: EventType, + payload: EventPayload, +} + +impl EventDispatch { + pub fn ended(id: AudioNodeId) -> Self { + EventDispatch { + type_: EventType::Ended(id), + payload: EventPayload::None, + } + } + + pub fn sink_change() -> Self { + EventDispatch { + type_: EventType::SinkChange, + payload: EventPayload::None, + } + } + + pub fn state_change(state: AudioContextState) -> Self { + EventDispatch { + type_: EventType::StateChange, + payload: EventPayload::AudioContextState(state), + } + } + + pub fn render_capacity(value: AudioRenderCapacityEvent) -> Self { + EventDispatch { + type_: EventType::RenderCapacity, + payload: EventPayload::RenderCapacity(value), + } + } + + pub fn processor_error(id: AudioNodeId, value: ErrorEvent) -> Self { + EventDispatch { + type_: EventType::ProcessorError(id), + payload: EventPayload::ProcessorError(value), + } + } + + #[cfg(feature = "diagnostics")] + pub fn diagnostics(value: AudioContextDiagnostics) -> Self { + EventDispatch { + type_: EventType::Diagnostics, + payload: EventPayload::Diagnostics(value), + } + } + + pub fn message(id: AudioNodeId, value: Box) -> Self { + EventDispatch { + type_: EventType::Message(id), + payload: EventPayload::Message(value), + } + } + + pub fn complete(buffer: AudioBuffer) -> Self { + EventDispatch { + type_: EventType::Complete, + payload: EventPayload::Complete(buffer), + } + } + + pub fn audio_processing(id: AudioNodeId, value: AudioProcessingEvent) -> Self { + EventDispatch { + type_: EventType::AudioProcessing(id), + payload: EventPayload::AudioProcessing(value), + } + } +} + +pub(crate) enum EventHandler { + Once(Box), + Multiple(Box), +} + +#[derive(Clone)] +pub(crate) struct EventLoop { + event_recv: Receiver, + event_handlers: Arc>>, +} + +impl EventLoop { + pub fn new(event_recv: Receiver) -> Self { + Self { + event_recv, + event_handlers: Default::default(), + } + } + + fn handle_event(&self, mut event: EventDispatch) -> ControlFlow<()> { + // Terminate the event loop when the audio context is closing + let mut result = ControlFlow::Continue(()); + if matches!( + event.payload, + EventPayload::AudioContextState(AudioContextState::Closed) + ) { + event.payload = EventPayload::None; // the statechange handler takes no argument + result = ControlFlow::Break(()); + } + + // In unit tests, we rethrow panics to avoid missing critical node exceptions + // https://github.com/orottier/web-audio-api-rs/issues/522 + #[cfg(test)] + if let EventPayload::ProcessorError(e) = event.payload { + panic!("Rethrowing exception during tests: {:?}", e); + } + + let mut event_handler_lock = self.event_handlers.lock().unwrap(); + let callback_option = event_handler_lock.remove(&event.type_); + drop(event_handler_lock); // release Mutex while running callback + + if let Some(callback) = callback_option { + match callback { + EventHandler::Once(f) => (f)(event.payload), + EventHandler::Multiple(mut f) => { + (f)(event.payload); + self.event_handlers + .lock() + .unwrap() + .insert(event.type_, EventHandler::Multiple(f)); + } + }; + } + + result + } + + #[inline(always)] + pub fn handle_pending_events(&self) -> bool { + let mut events_were_handled = false; + // try_iter will yield all pending events, but does not block + for event in self.event_recv.try_iter() { + // we can ignore the return value, it is only useful in the event thread + let _ = self.handle_event(event); + events_were_handled = true; + } + events_were_handled + } + + pub fn run_in_thread(&self) { + log::debug!("Entering event thread"); + + // split borrows to help compiler + let self_clone = self.clone(); + + std::thread::spawn(move || { + // This thread is dedicated to event handling, so we can block + for event in self_clone.event_recv.iter() { + let result = self_clone.handle_event(event); + if result.is_break() { + break; + } + } + + log::debug!("Event loop has terminated"); + }); + } + + pub fn set_handler(&self, event: EventType, callback: EventHandler) { + self.event_handlers.lock().unwrap().insert(event, callback); + } + + pub fn clear_handler(&self, event: EventType) { + self.event_handlers.lock().unwrap().remove(&event); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/io/cpal.rs b/vendor/web-audio-api-1.7.0/src/io/cpal.rs new file mode 100644 index 0000000000..c3089ba193 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/io/cpal.rs @@ -0,0 +1,831 @@ +//! Audio IO management API +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::sync::Mutex; + +use cpal::{ + traits::{DeviceTrait, HostTrait, StreamTrait}, + Device, Error as CpalError, ErrorKind as CpalErrorKind, OutputCallbackInfo, SampleFormat, + Stream, StreamConfig, SupportedBufferSize, +}; +use crossbeam_channel::Receiver; + +use super::{ + AudioBackendError, AudioBackendErrorKind, AudioBackendManager, BackendResult, RenderThreadInit, +}; + +use crate::buffer::AudioBuffer; +use crate::context::AudioContextLatencyCategory; +use crate::context::AudioContextOptions; +use crate::io::microphone::MicrophoneRender; +use crate::media_devices::{MediaDeviceInfo, MediaDeviceInfoKind}; +use crate::render::RenderThread; +use crate::stats::AudioStats; +use crate::{AtomicF64, MAX_CHANNELS}; + +fn get_host() -> BackendResult { + #[cfg(all(feature = "cpal-pipewire", target_os = "linux"))] + { + if let Some(host) = preferred_host(cpal::HostId::PipeWire, "PipeWire") { + return Ok(host); + } + } + + #[cfg(feature = "cpal-jack")] + { + if let Some(host) = preferred_host(cpal::HostId::Jack, "JACK") { + return Ok(host); + } + } + + Ok(cpal::default_host()) +} + +#[cfg(any( + feature = "cpal-jack", + all(feature = "cpal-pipewire", target_os = "linux") +))] +fn preferred_host(host_id: cpal::HostId, host_name: &str) -> Option { + let host_id = cpal::available_hosts() + .into_iter() + .find(|id| *id == host_id)?; + + let host = match cpal::host_from_id(host_id) { + Ok(host) => host, + Err(err) => { + log::warn!("{host_name} host is unavailable, falling back to default host: {err}"); + return None; + } + }; + + match host.devices() { + Ok(devices) => { + if devices.count() > 0 { + Some(host) + } else { + log::warn!("No {host_name} devices found, falling back to default host"); + None + } + } + Err(err) => { + log::warn!( + "Could not enumerate {host_name} devices, falling back to default host: {err}" + ); + None + } + } +} + +fn map_cpal_backend_error( + kind: AudioBackendErrorKind, + operation: &'static str, + err: impl std::fmt::Display, +) -> AudioBackendError { + AudioBackendError::new(kind, "cpal", operation, err.to_string()) +} + +fn map_cpal_error(operation: &'static str, err: CpalError) -> AudioBackendError { + let kind = match err.kind() { + CpalErrorKind::DeviceNotAvailable => AudioBackendErrorKind::DeviceUnavailable, + CpalErrorKind::UnsupportedConfig | CpalErrorKind::UnsupportedOperation => { + AudioBackendErrorKind::NotSupported + } + CpalErrorKind::InvalidInput => AudioBackendErrorKind::InvalidArgument, + CpalErrorKind::DeviceBusy + | CpalErrorKind::DeviceChanged + | CpalErrorKind::HostUnavailable + | CpalErrorKind::PermissionDenied + | CpalErrorKind::RealtimeDenied + | CpalErrorKind::ResourceExhausted + | CpalErrorKind::StreamInvalidated + | CpalErrorKind::Xrun + | CpalErrorKind::BackendError + | CpalErrorKind::Other + | _ => AudioBackendErrorKind::BackendSpecific, + }; + map_cpal_backend_error(kind, operation, err) +} + +fn cpal_device_for_id( + host: &cpal::Host, + kind: MediaDeviceInfoKind, + device_id: &str, +) -> BackendResult> { + let devices: Vec = match kind { + MediaDeviceInfoKind::AudioInput => host + .input_devices() + .map_err(|e| map_cpal_error("enumerate_input_devices", e))? + .collect(), + MediaDeviceInfoKind::AudioOutput => host + .output_devices() + .map_err(|e| map_cpal_error("enumerate_output_devices", e))? + .collect(), + MediaDeviceInfoKind::VideoInput => Vec::new(), + }; + let mut seen = Vec::::new(); + + for device in devices { + let Some(num_channels) = cpal_device_channels(&device, kind) else { + continue; + }; + let stable_id = cpal_stable_device_id(&device, kind, num_channels, &seen)?; + if stable_id == device_id { + return Ok(Some(device)); + } + seen.push(stable_id); + } + + Ok(None) +} + +fn cpal_device_channels(device: &Device, kind: MediaDeviceInfoKind) -> Option { + Some(match kind { + MediaDeviceInfoKind::AudioInput => device.default_input_config().ok()?.channels(), + MediaDeviceInfoKind::AudioOutput => device.default_output_config().ok()?.channels(), + MediaDeviceInfoKind::VideoInput => return None, + }) +} + +fn cpal_stable_device_id( + device: &Device, + kind: MediaDeviceInfoKind, + num_channels: u16, + seen: &[String], +) -> BackendResult { + let name = device + .description() + .map_err(|e| map_cpal_error("device_name", e))? + .to_string(); + + Ok(stable_device_id("cpal", kind, name, num_channels, seen)) +} + +fn stable_device_id( + host: &str, + kind: MediaDeviceInfoKind, + friendly_name: String, + num_channels: u16, + seen: &[String], +) -> String { + let mut index = 0; + loop { + let device_id = crate::media_devices::DeviceId::as_string( + kind, + host.to_string(), + friendly_name.clone(), + num_channels, + index, + ); + + if !seen.iter().any(|id| id == &device_id) { + return device_id; + } + + index += 1; + } +} + +/// Audio backend using the `cpal` library +#[derive(Clone)] +#[allow(unused)] +pub(crate) struct CpalBackend { + stream: Arc>>, + output_latency: Arc, + sample_rate: f32, + number_of_channels: usize, + sink_id: String, +} + +impl AudioBackendManager for CpalBackend { + fn build_output( + options: AudioContextOptions, + render_thread_init: RenderThreadInit, + ) -> BackendResult + where + Self: Sized, + { + let host = get_host()?; + + log::info!("Audio Output Host: cpal {:?}", host.id()); + + let RenderThreadInit { + state, + startup_pending, + frames_played, + stats, + ctrl_msg_recv, + event_send, + } = render_thread_init; + + let device = if options.sink_id.is_empty() { + host.default_output_device().ok_or_else(|| { + AudioBackendError::new( + AudioBackendErrorKind::DeviceUnavailable, + "cpal", + "default_output_device", + "No output device available", + ) + })? + } else { + cpal_device_for_id(&host, MediaDeviceInfoKind::AudioOutput, &options.sink_id)? + .or_else(|| host.default_output_device()) + .ok_or_else(|| { + AudioBackendError::new( + AudioBackendErrorKind::DeviceUnavailable, + "cpal", + "select_output_device", + "No output device available", + ) + })? + }; + + log::info!( + "Output device: {:?}", + device + .description() + .map_err(|e| map_cpal_error("output_device_name", e))? + .to_string() + ); + + let default_device_config = device + .default_output_config() + .map_err(|e| map_cpal_error("default_output_config", e))?; + + // we grab the largest number of channels provided by the soundcard + // clamped to MAX_CHANNELS, this value cannot be changed by the user + let number_of_channels = usize::from(default_device_config.channels()).min(MAX_CHANNELS); + + // override default device configuration with the options provided by + // the user when creating the `AudioContext` + let mut preferred_config: StreamConfig = default_device_config.into(); + // make sure the number of channels is clamped to MAX_CHANNELS + preferred_config.channels = number_of_channels as u16; + + // set specific sample rate if requested + if let Some(sample_rate) = options.sample_rate { + crate::assert_valid_sample_rate(sample_rate); + preferred_config.sample_rate = sample_rate as u32; + } + + // always try to set a decent buffer size + let buffer_size = super::buffer_size_for_latency_category( + options.latency_hint, + preferred_config.sample_rate as f32, + ) as u32; + + let clamped_buffer_size: u32 = match default_device_config.buffer_size() { + SupportedBufferSize::Unknown => buffer_size, + SupportedBufferSize::Range { min, max } => buffer_size.clamp(*min, *max), + }; + + preferred_config.buffer_size = cpal::BufferSize::Fixed(clamped_buffer_size); + + // On android detected range for the buffer size seems to be too big, use default buffer size instead + // See https://github.com/orottier/web-audio-api-rs/issues/515 + if cfg!(target_os = "android") { + if let AudioContextLatencyCategory::Balanced + | AudioContextLatencyCategory::Interactive = options.latency_hint + { + preferred_config.buffer_size = cpal::BufferSize::Default; + } + } + + // report the picked sample rate to the render thread, i.e. if the requested + // sample rate is not supported by the hardware, it will fallback to the + // default device sample rate + let mut sample_rate = preferred_config.sample_rate as f32; + + // shared atomic to report output latency to the control thread + let output_latency = Arc::new(AtomicF64::new(0.)); + + let mut renderer = RenderThread::new( + sample_rate, + preferred_config.channels as usize, + ctrl_msg_recv.clone(), + Arc::clone(&state), + Arc::clone(&frames_played), + stats.clone(), + event_send.clone(), + ); + renderer.set_startup_pending(Arc::clone(&startup_pending)); + renderer.spawn_garbage_collector_thread(); + + log::debug!( + "Attempt output stream with preferred config: {:?}", + preferred_config + ); + + let spawned = spawn_output_stream( + &device, + default_device_config.sample_format(), + preferred_config, + renderer, + Arc::clone(&output_latency), + stats.clone(), + ); + + let stream = match spawned { + Ok(stream) => { + log::debug!("Output stream set up successfully"); + stream + } + Err(e) => { + log::warn!("Output stream build failed with preferred config: {}", e); + + let mut supported_config: StreamConfig = default_device_config.into(); + // make sure number of channels is clamped to MAX_CHANNELS + supported_config.channels = number_of_channels as u16; + // fallback to device default sample rate + sample_rate = supported_config.sample_rate as f32; + + log::debug!( + "Attempt output stream with fallback config: {:?}", + supported_config + ); + + let mut renderer = RenderThread::new( + sample_rate, + supported_config.channels as usize, + ctrl_msg_recv, + state, + frames_played, + stats.clone(), + event_send, + ); + renderer.set_startup_pending(startup_pending); + renderer.spawn_garbage_collector_thread(); + + let spawned = spawn_output_stream( + &device, + default_device_config.sample_format(), + supported_config, + renderer, + Arc::clone(&output_latency), + stats.clone(), + ); + + spawned.map_err(|e| map_cpal_error("build_fallback_output_stream", e))? + } + }; + + // Required because some hosts don't play the stream automatically + stream + .play() + .map_err(|e| map_cpal_error("play_output_stream", e))?; + + Ok(CpalBackend { + stream: Arc::new(Mutex::new(Some(stream))), + output_latency, + sample_rate, + number_of_channels, + sink_id: options.sink_id, + }) + } + + fn build_input( + options: AudioContextOptions, + number_of_channels: Option, + ) -> BackendResult<(Self, Receiver)> + where + Self: Sized, + { + let host = get_host()?; + + log::info!("Audio Input Host: cpal {:?}", host.id()); + + let device = if options.sink_id.is_empty() { + host.default_input_device().ok_or_else(|| { + AudioBackendError::new( + AudioBackendErrorKind::DeviceUnavailable, + "cpal", + "default_input_device", + "No input device available", + ) + })? + } else { + cpal_device_for_id(&host, MediaDeviceInfoKind::AudioInput, &options.sink_id)? + .or_else(|| host.default_input_device()) + .ok_or_else(|| { + AudioBackendError::new( + AudioBackendErrorKind::DeviceUnavailable, + "cpal", + "select_input_device", + "No input device available", + ) + })? + }; + + log::info!( + "Input device: {:?}", + device + .description() + .map_err(|e| map_cpal_error("input_device_name", e))? + .to_string() + ); + + let supported = device + .default_input_config() + .map_err(|e| map_cpal_error("default_input_config", e))?; + + // clone the config, we may need to fall back on it later + let mut preferred: StreamConfig = supported.into(); + + if let Some(number_of_channels) = number_of_channels { + // do not ask for more channels than available + preferred.channels = preferred.channels.min(number_of_channels as u16); + } + + // make sure we don't exceed MAX_CHANNELS + preferred.channels = preferred.channels.min(MAX_CHANNELS as u16); + + // set specific sample rate if requested + if let Some(sample_rate) = options.sample_rate { + crate::assert_valid_sample_rate(sample_rate); + preferred.sample_rate = sample_rate as u32; + } + + // always try to set a decent buffer size + let buffer_size = super::buffer_size_for_latency_category( + options.latency_hint, + preferred.sample_rate as f32, + ) as u32; + + let clamped_buffer_size: u32 = match supported.buffer_size() { + SupportedBufferSize::Unknown => buffer_size, + SupportedBufferSize::Range { min, max } => buffer_size.clamp(*min, *max), + }; + + preferred.buffer_size = cpal::BufferSize::Fixed(clamped_buffer_size); + let mut sample_rate = preferred.sample_rate as f32; + let mut number_of_channels = preferred.channels as usize; + + let smoothing = 3; // todo, use buffering to smooth frame drops + let (sender, mut receiver) = crossbeam_channel::bounded(smoothing); + let renderer = MicrophoneRender::new(number_of_channels, sample_rate, sender); + + log::debug!( + "Attempt input stream with preferred config: {:?}", + preferred + ); + + let spawned = spawn_input_stream(&device, supported.sample_format(), preferred, renderer); + + // the required block size preferred config may not be supported, in that + // case, fallback the supported config + let stream = match spawned { + Ok(stream) => { + log::debug!("Input stream set up successfully"); + stream + } + Err(e) => { + log::warn!("Input stream build failed with preferred config: {}", e); + + let supported_config: StreamConfig = supported.into(); + if usize::from(supported_config.channels) > MAX_CHANNELS { + return Err(AudioBackendError::new( + AudioBackendErrorKind::NotSupported, + "cpal", + "build_input_stream", + format!( + "Input device requires {} channels, exceeding the supported maximum of {MAX_CHANNELS}; building a stream with {MAX_CHANNELS} channels failed: {e}", + supported_config.channels + ), + )); + } + + // fallback to device default sample rate and channel count + number_of_channels = usize::from(supported_config.channels); + sample_rate = supported_config.sample_rate as f32; + + log::debug!( + "Attempt input stream with fallback config: {:?}", + supported_config + ); + + // setup a new comms channel + let (sender, receiver2) = crossbeam_channel::bounded(smoothing); + receiver = receiver2; // overwrite earlier + + let renderer = MicrophoneRender::new(number_of_channels, sample_rate, sender); + + let spawned = spawn_input_stream( + &device, + supported.sample_format(), + supported_config, + renderer, + ); + spawned.map_err(|e| map_cpal_error("build_fallback_input_stream", e))? + } + }; + + // Required because some hosts don't play the stream automatically + stream + .play() + .map_err(|e| map_cpal_error("play_input_stream", e))?; + + let backend = CpalBackend { + stream: Arc::new(Mutex::new(Some(stream))), + output_latency: Arc::new(AtomicF64::new(0.)), + sample_rate, + number_of_channels, + sink_id: options.sink_id, + }; + + Ok((backend, receiver)) + } + + fn resume(&self) -> BackendResult { + if let Some(s) = self.stream.lock().unwrap().as_ref() { + s.play() + .map(|_| true) + .map_err(|e| map_cpal_error("resume", e))?; + return Ok(true); + } + + Ok(false) + } + + fn suspend(&self) -> BackendResult { + if let Some(s) = self.stream.lock().unwrap().as_ref() { + s.pause() + .map(|_| true) + .map_err(|e| map_cpal_error("suspend", e))?; + return Ok(true); + } + + Ok(false) + } + + fn close(&self) -> BackendResult<()> { + self.stream.lock().unwrap().take(); // will Drop + Ok(()) + } + + fn sample_rate(&self) -> f32 { + self.sample_rate + } + + fn number_of_channels(&self) -> usize { + self.number_of_channels + } + + fn output_latency(&self) -> BackendResult { + Ok(self.output_latency.load(Ordering::Relaxed)) + } + + fn sink_id(&self) -> &str { + self.sink_id.as_str() + } + + fn enumerate_devices_sync() -> BackendResult> + where + Self: Sized, + { + let host = get_host()?; + + let input_devices = host + .input_devices() + .map_err(|e| map_cpal_error("enumerate_input_devices", e))? + .filter_map(|d| { + let num_channels = d.default_input_config().ok()?.channels(); + Some((d, MediaDeviceInfoKind::AudioInput, num_channels)) + }); + + let output_devices = host + .output_devices() + .map_err(|e| map_cpal_error("enumerate_output_devices", e))? + .filter_map(|d| { + let num_channels = d.default_output_config().ok()?.channels(); + Some((d, MediaDeviceInfoKind::AudioOutput, num_channels)) + }); + + // cf. https://github.com/orottier/web-audio-api-rs/issues/356 + let mut list = Vec::::new(); + + for (device, kind, num_channels) in input_devices.chain(output_devices) { + let mut index = 0; + + loop { + let device_id = crate::media_devices::DeviceId::as_string( + kind, + "cpal".to_string(), + device + .description() + .map_err(|e| map_cpal_error("device_name", e))? + .to_string(), + num_channels, + index, + ); + + if !list.iter().any(|d| d.device_id() == device_id) { + let device = MediaDeviceInfo::new( + device_id, + None, + kind, + device + .description() + .map_err(|e| map_cpal_error("device_name", e))? + .to_string(), + ); + + list.push(device); + break; + } else { + index += 1; + } + } + } + + Ok(list) + } +} + +fn latency_in_seconds(infos: &OutputCallbackInfo) -> f64 { + let timestamp = infos.timestamp(); + let delta = timestamp.playback.duration_since(timestamp.callback); + delta.as_secs() as f64 + delta.subsec_nanos() as f64 * 1e-9 +} + +/// Creates an output stream +/// +/// # Arguments: +/// +/// * `device` - the output audio device on which the stream is created +/// * `sample_format` - audio sample format of the stream +/// * `config` - stream configuration +/// * `render` - the render thread which process the audio data +fn spawn_output_stream( + device: &Device, + sample_format: SampleFormat, + config: StreamConfig, + mut render: RenderThread, + output_latency: Arc, + stats: AudioStats, +) -> Result { + let err_fn = |err| log::error!("an error occurred on the output audio stream: {}", err); + + match sample_format { + SampleFormat::F32 => device.build_output_stream( + config, + move |d: &mut [f32], i: &OutputCallbackInfo| { + render.render(d); + let latency = latency_in_seconds(i); + output_latency.store(latency, Ordering::Relaxed); + stats.record_latency_seconds(latency); + }, + err_fn, + None, + ), + SampleFormat::F64 => device.build_output_stream( + config, + move |d: &mut [f64], i: &OutputCallbackInfo| { + render.render(d); + let latency = latency_in_seconds(i); + output_latency.store(latency, Ordering::Relaxed); + stats.record_latency_seconds(latency); + }, + err_fn, + None, + ), + SampleFormat::U8 => device.build_output_stream( + config, + move |d: &mut [u8], i: &OutputCallbackInfo| { + render.render(d); + let latency = latency_in_seconds(i); + output_latency.store(latency, Ordering::Relaxed); + stats.record_latency_seconds(latency); + }, + err_fn, + None, + ), + SampleFormat::U16 => device.build_output_stream( + config, + move |d: &mut [u16], i: &OutputCallbackInfo| { + render.render(d); + let latency = latency_in_seconds(i); + output_latency.store(latency, Ordering::Relaxed); + stats.record_latency_seconds(latency); + }, + err_fn, + None, + ), + SampleFormat::U32 => device.build_output_stream( + config, + move |d: &mut [u32], i: &OutputCallbackInfo| { + render.render(d); + let latency = latency_in_seconds(i); + output_latency.store(latency, Ordering::Relaxed); + stats.record_latency_seconds(latency); + }, + err_fn, + None, + ), + SampleFormat::U64 => device.build_output_stream( + config, + move |d: &mut [u64], i: &OutputCallbackInfo| { + render.render(d); + let latency = latency_in_seconds(i); + output_latency.store(latency, Ordering::Relaxed); + stats.record_latency_seconds(latency); + }, + err_fn, + None, + ), + SampleFormat::I8 => device.build_output_stream( + config, + move |d: &mut [i8], i: &OutputCallbackInfo| { + render.render(d); + let latency = latency_in_seconds(i); + output_latency.store(latency, Ordering::Relaxed); + stats.record_latency_seconds(latency); + }, + err_fn, + None, + ), + SampleFormat::I16 => device.build_output_stream( + config, + move |d: &mut [i16], i: &OutputCallbackInfo| { + render.render(d); + let latency = latency_in_seconds(i); + output_latency.store(latency, Ordering::Relaxed); + stats.record_latency_seconds(latency); + }, + err_fn, + None, + ), + SampleFormat::I32 => device.build_output_stream( + config, + move |d: &mut [i32], i: &OutputCallbackInfo| { + render.render(d); + let latency = latency_in_seconds(i); + output_latency.store(latency, Ordering::Relaxed); + stats.record_latency_seconds(latency); + }, + err_fn, + None, + ), + SampleFormat::I64 => device.build_output_stream( + config, + move |d: &mut [i64], i: &OutputCallbackInfo| { + render.render(d); + let latency = latency_in_seconds(i); + output_latency.store(latency, Ordering::Relaxed); + stats.record_latency_seconds(latency); + }, + err_fn, + None, + ), + _ => Err(CpalErrorKind::UnsupportedConfig.into()), + } +} + +/// Creates an input stream +/// +/// # Arguments: +/// +/// * `device` - the input audio device on which the stream is created +/// * `sample_format` - audio sample format of the stream +/// * `config` - stream configuration +/// * `render` - the render thread which process the audio data +fn spawn_input_stream( + device: &Device, + sample_format: SampleFormat, + config: StreamConfig, + render: MicrophoneRender, +) -> Result { + let err_fn = |err| log::error!("an error occurred on the input audio stream: {}", err); + + match sample_format { + SampleFormat::F32 => { + device.build_input_stream(config, move |d: &[f32], _c| render.render(d), err_fn, None) + } + SampleFormat::F64 => { + device.build_input_stream(config, move |d: &[f64], _c| render.render(d), err_fn, None) + } + SampleFormat::U8 => { + device.build_input_stream(config, move |d: &[u8], _c| render.render(d), err_fn, None) + } + SampleFormat::U16 => { + device.build_input_stream(config, move |d: &[u16], _c| render.render(d), err_fn, None) + } + SampleFormat::U32 => { + device.build_input_stream(config, move |d: &[u32], _c| render.render(d), err_fn, None) + } + SampleFormat::U64 => { + device.build_input_stream(config, move |d: &[u64], _c| render.render(d), err_fn, None) + } + SampleFormat::I8 => { + device.build_input_stream(config, move |d: &[i8], _c| render.render(d), err_fn, None) + } + SampleFormat::I16 => { + device.build_input_stream(config, move |d: &[i16], _c| render.render(d), err_fn, None) + } + SampleFormat::I32 => { + device.build_input_stream(config, move |d: &[i32], _c| render.render(d), err_fn, None) + } + SampleFormat::I64 => { + device.build_input_stream(config, move |d: &[i64], _c| render.render(d), err_fn, None) + } + _ => Err(CpalErrorKind::UnsupportedConfig.into()), + } +} diff --git a/vendor/web-audio-api-1.7.0/src/io/cubeb.rs b/vendor/web-audio-api-1.7.0/src/io/cubeb.rs new file mode 100644 index 0000000000..fa473213a1 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/io/cubeb.rs @@ -0,0 +1,701 @@ +use std::thread; + +use super::{ + AudioBackendError, AudioBackendErrorKind, AudioBackendManager, BackendResult, RenderThreadInit, +}; + +use crate::buffer::AudioBuffer; +use crate::context::AudioContextOptions; +use crate::io::microphone::MicrophoneRender; +use crate::media_devices::{MediaDeviceInfo, MediaDeviceInfoKind}; +use crate::render::RenderThread; +use crate::{MAX_CHANNELS, RENDER_QUANTUM_SIZE}; + +use cubeb::{Context, DeviceId, DeviceType, StereoFrame, Stream, StreamParams}; + +use crossbeam_channel::{Receiver, Sender}; + +// erase type of `Frame` in cubeb `Stream` +struct BoxedStream(Box); + +impl BoxedStream { + fn new(stream: Stream) -> Self { + Self(Box::new(stream)) + } +} + +trait CubebStream { + fn delegate_start(&self) -> Result<(), cubeb::Error>; + fn delegate_stop(&self) -> Result<(), cubeb::Error>; + fn delegate_latency(&self) -> Result; +} + +impl CubebStream for Stream { + fn delegate_start(&self) -> Result<(), cubeb::Error> { + self.start() + } + fn delegate_stop(&self) -> Result<(), cubeb::Error> { + self.stop() + } + fn delegate_latency(&self) -> Result { + self.latency() + } +} + +mod owner_thread { + use super::*; + + pub struct Responder(Sender>); + + impl Responder { + pub fn send(self, result: BackendResult) { + let _ = self.0.send(result); + } + } + + pub enum CubebCommand { + Resume(Responder), + Suspend(Responder), + Close(Responder<()>), + OutputLatency { + sample_rate: f32, + response: Responder, + }, + } + + pub struct CubebStreamInfo { + pub sample_rate: f32, + pub number_of_channels: usize, + } + + pub struct CubebOwner { + stream: BoxedStream, + _ctx: Context, + } + + impl CubebOwner { + pub fn new(ctx: Context, stream: BoxedStream) -> Self { + Self { stream, _ctx: ctx } + } + + pub fn run(self, commands: Receiver) { + while let Ok(command) = commands.recv() { + match command { + CubebCommand::Resume(response) => { + response.send(self.resume()); + } + CubebCommand::Suspend(response) => { + response.send(self.suspend()); + } + CubebCommand::Close(response) => { + let result = self.close(); + response.send(result); + break; + } + CubebCommand::OutputLatency { + sample_rate, + response, + } => { + response.send(self.output_latency(sample_rate)); + } + } + } + } + + pub fn resume(&self) -> BackendResult { + self.stream + .0 + .delegate_start() + .map(|_| true) + .map_err(|e| map_cubeb_error("resume", e)) + } + + fn suspend(&self) -> BackendResult { + self.stream + .0 + .delegate_stop() + .map(|_| true) + .map_err(|e| map_cubeb_error("suspend", e)) + } + + fn close(self) -> BackendResult<()> { + let _ = self.stream.0.delegate_stop(); + drop(self); + Ok(()) + } + + fn output_latency(&self, sample_rate: f32) -> BackendResult { + self.stream + .0 + .delegate_latency() + .map(|frames| frames as f64 / sample_rate as f64) + .map_err(|e| map_cubeb_error("output_latency", e)) + } + } + + #[derive(Clone)] + pub struct CubebThreadHandle { + commands: Sender, + } + + impl CubebThreadHandle { + pub fn new(commands: Sender) -> Self { + Self { commands } + } + + fn request( + &self, + operation: &'static str, + make_command: impl FnOnce(Responder) -> CubebCommand, + ) -> BackendResult { + let (response_send, response_recv) = crossbeam_channel::bounded(1); + self.commands + .send(make_command(Responder(response_send))) + .map_err(|_| cubeb_backend_error(operation, "Cubeb owner thread has stopped"))?; + response_recv + .recv() + .map_err(|_| cubeb_backend_error(operation, "Cubeb owner thread has stopped"))? + } + + pub fn close(&self) -> BackendResult<()> { + self.request("close", CubebCommand::Close) + } + + pub fn resume(&self) -> BackendResult { + self.request("resume", CubebCommand::Resume) + } + + pub fn suspend(&self) -> BackendResult { + self.request("suspend", CubebCommand::Suspend) + } + + pub fn output_latency(&self, sample_rate: f32) -> BackendResult { + self.request("output_latency", |response| CubebCommand::OutputLatency { + sample_rate, + response, + }) + } + } +} +use owner_thread::{CubebOwner, CubebStreamInfo, CubebThreadHandle}; + +fn spawn_cubeb_owner( + name: &'static str, + build: impl FnOnce() -> BackendResult<(CubebOwner, CubebStreamInfo)> + Send + 'static, +) -> BackendResult<(CubebThreadHandle, CubebStreamInfo)> { + let (commands_send, commands_recv) = crossbeam_channel::unbounded(); + let (ready_send, ready_recv) = crossbeam_channel::bounded(1); + thread::Builder::new() + .name(name.to_string()) + .spawn(move || match build() { + Ok((owner, info)) => { + let _ = ready_send.send(Ok(info)); + owner.run(commands_recv); + } + Err(e) => { + let _ = ready_send.send(Err(e)); + } + }) + .map_err(|e| cubeb_backend_error("spawn_thread", e.to_string()))?; + + let info = ready_recv + .recv() + .map_err(|_| cubeb_backend_error("spawn_thread", "Cubeb owner thread stopped"))??; + Ok((CubebThreadHandle::new(commands_send), info)) +} + +fn map_cubeb_error(operation: &'static str, err: cubeb::Error) -> AudioBackendError { + let kind = match err { + cubeb::Error::DeviceUnavailable => AudioBackendErrorKind::DeviceUnavailable, + cubeb::Error::InvalidFormat | cubeb::Error::NotSupported => { + AudioBackendErrorKind::NotSupported + } + cubeb::Error::InvalidParameter => AudioBackendErrorKind::InvalidArgument, + cubeb::Error::Error => AudioBackendErrorKind::BackendSpecific, + }; + + AudioBackendError::new(kind, "cubeb", operation, err.to_string()) +} + +fn cubeb_backend_error(operation: &'static str, message: impl Into) -> AudioBackendError { + AudioBackendError::new( + AudioBackendErrorKind::BackendSpecific, + "cubeb", + operation, + message, + ) +} + +fn cubeb_device_for_id( + context: &Context, + device_type: DeviceType, + kind: MediaDeviceInfoKind, + device_id: &str, +) -> BackendResult> { + let devices = context + .enumerate_devices(device_type) + .map_err(|e| map_cubeb_error(enumerate_operation(kind), e))?; + let mut seen = Vec::::new(); + + for device in devices.iter() { + let stable_id = cubeb_stable_device_id(device, kind, &seen)?; + if stable_id == device_id { + return Ok(Some(device.devid())); + } + seen.push(stable_id); + } + + Ok(None) +} + +fn init_output_backend( + ctx: &Context, + params: StreamParams, + buffer_size: u32, + device: Option, + mut renderer: RenderThread, +) -> BackendResult { + let mut builder = cubeb::StreamBuilder::<[f32; N]>::new(); + + match device { + None => builder.default_output(¶ms), + Some(devid) => builder.output(devid, ¶ms), + }; + + builder + .name("Cubeb web_audio_api") + .latency(buffer_size) + .data_callback(move |_input, output| { + // `output` is `&mut [[f32; N]]`, a slice of slices. + // The renderer just wants a single slice, flatten it. + // Inspired by the unstable feature + { + let output: &mut [f32] = + // SAFETY: `[T]` is layout-identical to `[T; N]` + unsafe { std::slice::from_raw_parts_mut(output.as_mut_ptr().cast(), output.len() * N) }; + renderer.render(output); + } + + output.len() as isize + }) + .state_callback(|state| { + log::debug!("stream state changed: {state:?}"); + }); + + let stream = builder + .init(ctx) + .map_err(|e| map_cubeb_error("init_output_stream", e))?; + Ok(BoxedStream::new(stream)) +} + +/// Audio backend using the `cubeb` library +#[derive(Clone)] +pub(crate) struct CubebBackend { + handle: CubebThreadHandle, + sample_rate: f32, + number_of_channels: usize, + sink_id: String, +} + +impl AudioBackendManager for CubebBackend { + fn build_output( + options: AudioContextOptions, + render_thread_init: RenderThreadInit, + ) -> BackendResult + where + Self: Sized, + { + let RenderThreadInit { + state, + startup_pending, + frames_played, + stats, + ctrl_msg_recv, + event_send, + } = render_thread_init; + + let sink_id = options.sink_id.clone(); + let (handle, info) = spawn_cubeb_owner("web-audio-api cubeb output", move || { + let ctx = Context::init(None, None).map_err(|e| map_cubeb_error("init_context", e))?; + log::info!("Audio Output Host: cubeb {:?}", ctx.backend_id()); + + // Use user requested sample rate, or else the device preferred one + let device_sample_rate = ctx.preferred_sample_rate().map(|v| v as f32).ok(); + let sample_rate = options.sample_rate.or(device_sample_rate).unwrap_or(48000.); + + let number_of_channels = ctx + .max_channel_count() + .map(|v| v as usize) + .ok() + .unwrap_or(2); + + // clamp the requested stream number of channels to MAX_CHANNELS even if + // the soundcard can provide more channels + let number_of_channels = number_of_channels.min(MAX_CHANNELS); + + let layout = match number_of_channels { + 1 => cubeb::ChannelLayout::MONO, + 2 => cubeb::ChannelLayout::STEREO, + 4 => cubeb::ChannelLayout::QUAD, + _ => cubeb::ChannelLayout::UNDEFINED, // TODO, does this work? + }; + + let mut renderer = RenderThread::new( + sample_rate, + number_of_channels, + ctrl_msg_recv, + state, + frames_played, + stats, + event_send, + ); + renderer.set_startup_pending(startup_pending); + renderer.spawn_garbage_collector_thread(); + + let params = cubeb::StreamParamsBuilder::new() + .format(cubeb::SampleFormat::Float32NE) // use float (native endian) + .rate(sample_rate as u32) + .channels(number_of_channels as u32) + .layout(layout) + .take(); + + // Calculate ideal latency + let buffer_size_req = + super::buffer_size_for_latency_category(options.latency_hint, sample_rate) as u32; + let min_latency = ctx + .min_latency(¶ms) + .ok() + .unwrap_or(RENDER_QUANTUM_SIZE as u32); + let buffer_size = buffer_size_req.max(min_latency); + + let device = if options.sink_id.is_empty() { + None + } else { + cubeb_device_for_id( + &ctx, + DeviceType::OUTPUT, + MediaDeviceInfoKind::AudioOutput, + &options.sink_id, + )? + }; + + let stream = match number_of_channels { + // so sorry, but I need to constify the non-const `number_of_channels` + 1 => init_output_backend::<1>(&ctx, params, buffer_size, device, renderer), + 2 => init_output_backend::<2>(&ctx, params, buffer_size, device, renderer), + 3 => init_output_backend::<3>(&ctx, params, buffer_size, device, renderer), + 4 => init_output_backend::<4>(&ctx, params, buffer_size, device, renderer), + 5 => init_output_backend::<5>(&ctx, params, buffer_size, device, renderer), + 6 => init_output_backend::<6>(&ctx, params, buffer_size, device, renderer), + 7 => init_output_backend::<7>(&ctx, params, buffer_size, device, renderer), + 8 => init_output_backend::<8>(&ctx, params, buffer_size, device, renderer), + 9 => init_output_backend::<9>(&ctx, params, buffer_size, device, renderer), + 10 => init_output_backend::<10>(&ctx, params, buffer_size, device, renderer), + 11 => init_output_backend::<11>(&ctx, params, buffer_size, device, renderer), + 12 => init_output_backend::<12>(&ctx, params, buffer_size, device, renderer), + 13 => init_output_backend::<13>(&ctx, params, buffer_size, device, renderer), + 14 => init_output_backend::<14>(&ctx, params, buffer_size, device, renderer), + 15 => init_output_backend::<15>(&ctx, params, buffer_size, device, renderer), + 16 => init_output_backend::<16>(&ctx, params, buffer_size, device, renderer), + 17 => init_output_backend::<17>(&ctx, params, buffer_size, device, renderer), + 18 => init_output_backend::<18>(&ctx, params, buffer_size, device, renderer), + 19 => init_output_backend::<19>(&ctx, params, buffer_size, device, renderer), + 20 => init_output_backend::<20>(&ctx, params, buffer_size, device, renderer), + 21 => init_output_backend::<21>(&ctx, params, buffer_size, device, renderer), + 22 => init_output_backend::<22>(&ctx, params, buffer_size, device, renderer), + 23 => init_output_backend::<23>(&ctx, params, buffer_size, device, renderer), + 24 => init_output_backend::<24>(&ctx, params, buffer_size, device, renderer), + 25 => init_output_backend::<25>(&ctx, params, buffer_size, device, renderer), + 26 => init_output_backend::<26>(&ctx, params, buffer_size, device, renderer), + 27 => init_output_backend::<27>(&ctx, params, buffer_size, device, renderer), + 28 => init_output_backend::<28>(&ctx, params, buffer_size, device, renderer), + 29 => init_output_backend::<29>(&ctx, params, buffer_size, device, renderer), + 30 => init_output_backend::<30>(&ctx, params, buffer_size, device, renderer), + 31 => init_output_backend::<31>(&ctx, params, buffer_size, device, renderer), + 32 => init_output_backend::<32>(&ctx, params, buffer_size, device, renderer), + _ => Err(cubeb_backend_error( + "init_output_stream", + "Unexpected channel count", + )), + }?; + + let owner = CubebOwner::new(ctx, stream); + owner.resume()?; + Ok(( + owner, + CubebStreamInfo { + sample_rate, + number_of_channels, + }, + )) + })?; + + let backend = CubebBackend { + handle, + number_of_channels: info.number_of_channels, + sample_rate: info.sample_rate, + sink_id, + }; + + Ok(backend) + } + + fn build_input( + options: AudioContextOptions, + _number_of_channels: Option, + ) -> BackendResult<(Self, Receiver)> + where + Self: Sized, + { + /* Set up a dedicated stream for input capturing + * + * This is not how it should be, we should link the input stream together + * with the output stream in one go. However, this means that we always + * capture mic input, even if the user does not want it - TODO + */ + + let smoothing = 3; // todo, use buffering to smooth frame drops + let (sender, receiver) = crossbeam_channel::bounded(smoothing); + let sink_id = options.sink_id.clone(); + let (handle, info) = spawn_cubeb_owner("web-audio-api cubeb input", move || { + let ctx = Context::init(None, None).map_err(|e| map_cubeb_error("init_context", e))?; + log::info!("Audio Input Host: cubeb {:?}", ctx.backend_id()); + + // Use user requested sample rate, or else the device preferred one + let device_sample_rate = ctx.preferred_sample_rate().map(|v| v as f32).ok(); + let sample_rate = options.sample_rate.or(device_sample_rate).unwrap_or(48000.); + + // TODO support all channel configs + let _max_channel_count = ctx.max_channel_count().map(|v| v as usize).ok(); + const NUMBER_OF_INPUT_CHANNELS: usize = 2; + let layout = cubeb::ChannelLayout::STEREO; + + let params = cubeb::StreamParamsBuilder::new() + .format(cubeb::SampleFormat::Float32NE) // use float (native endian) + .rate(sample_rate as u32) + .channels(NUMBER_OF_INPUT_CHANNELS as u32) + .layout(layout) + .take(); + + // Calculate ideal latency + let buffer_size_req = + super::buffer_size_for_latency_category(options.latency_hint, sample_rate) as u32; + let min_latency = ctx + .min_latency(¶ms) + .ok() + .unwrap_or(RENDER_QUANTUM_SIZE as u32); + let buffer_size = buffer_size_req.max(min_latency); + + let device = if options.sink_id.is_empty() { + None + } else { + cubeb_device_for_id( + &ctx, + DeviceType::INPUT, + MediaDeviceInfoKind::AudioInput, + &options.sink_id, + )? + }; + + let renderer = MicrophoneRender::new(NUMBER_OF_INPUT_CHANNELS, sample_rate, sender); + + // Microphone input is always assumed STEREO (TODO) + let mut builder = cubeb::StreamBuilder::>::new(); + + match device { + None => builder.default_input(¶ms), + Some(devid) => builder.input(devid, ¶ms), + }; + + builder + .name("Cubeb web_audio_api (input)") + .latency(buffer_size) + .data_callback(move |input, _output| { + let mut tmp = vec![0.; input.len() * NUMBER_OF_INPUT_CHANNELS]; + tmp.chunks_mut(NUMBER_OF_INPUT_CHANNELS) + .zip(input) + .for_each(|(t, i)| { + t[0] = i.l; + t[1] = i.r; + }); + renderer.render(&tmp); + input.len() as isize + }) + .state_callback(|state| { + log::debug!("stream state changed: {state:?}"); + }); + + let stream = builder + .init(&ctx) + .map_err(|e| map_cubeb_error("init_input_stream", e))?; + let owner = CubebOwner::new(ctx, BoxedStream::new(stream)); + owner.resume()?; + Ok(( + owner, + CubebStreamInfo { + sample_rate, + number_of_channels: NUMBER_OF_INPUT_CHANNELS, + }, + )) + })?; + + let backend = CubebBackend { + handle, + number_of_channels: info.number_of_channels, + sample_rate: info.sample_rate, + sink_id, + }; + + Ok((backend, receiver)) + } + + fn resume(&self) -> BackendResult { + self.handle.resume() + } + + fn suspend(&self) -> BackendResult { + self.handle.suspend() + } + + fn close(&self) -> BackendResult<()> { + self.handle.close() + } + + fn sample_rate(&self) -> f32 { + self.sample_rate + } + + fn number_of_channels(&self) -> usize { + self.number_of_channels + } + + fn output_latency(&self) -> BackendResult { + self.handle.output_latency(self.sample_rate) + } + + fn sink_id(&self) -> &str { + self.sink_id.as_str() + } + + fn enumerate_devices_sync() -> BackendResult> + where + Self: Sized, + { + let context = Context::init(None, None).map_err(|e| map_cubeb_error("init_context", e))?; + + let mut list = + enumerate_cubeb_devices(&context, DeviceType::INPUT, MediaDeviceInfoKind::AudioInput)?; + list.extend(enumerate_cubeb_devices( + &context, + DeviceType::OUTPUT, + MediaDeviceInfoKind::AudioOutput, + )?); + + Ok(list) + } +} + +fn enumerate_cubeb_devices( + context: &Context, + device_type: DeviceType, + kind: MediaDeviceInfoKind, +) -> BackendResult> { + let devices = context + .enumerate_devices(device_type) + .map_err(|e| map_cubeb_error(enumerate_operation(kind), e))?; + let mut list = Vec::::new(); + + for device in devices.iter() { + let friendly_name: String = device + .friendly_name() + .ok_or_else(|| { + cubeb_backend_error("device_friendly_name", "Device has no friendly name") + })? + .into(); + let max_channels = device.max_channels().try_into().map_err(|_| { + cubeb_backend_error("device_max_channels", "Device channel count overflows u16") + })?; + let mut index = 0; + + loop { + let device_id = crate::media_devices::DeviceId::as_string( + kind, + "cubeb".to_string(), + friendly_name.clone(), + max_channels, + index, + ); + + if !list.iter().any(|d| d.device_id() == device_id) { + let device = MediaDeviceInfo::new( + device_id, + device.group_id().map(str::to_string), + kind, + friendly_name, + ); + + list.push(device); + break; + } else { + index += 1; + } + } + } + + Ok(list) +} + +fn enumerate_operation(kind: MediaDeviceInfoKind) -> &'static str { + match kind { + MediaDeviceInfoKind::AudioInput => "enumerate_input_devices", + MediaDeviceInfoKind::AudioOutput => "enumerate_output_devices", + MediaDeviceInfoKind::VideoInput => "enumerate_video_devices", + } +} + +fn cubeb_stable_device_id( + device: &cubeb::DeviceInfo, + kind: MediaDeviceInfoKind, + seen: &[String], +) -> BackendResult { + let friendly_name = device + .friendly_name() + .ok_or_else(|| cubeb_backend_error("device_friendly_name", "Device has no friendly name"))? + .into(); + let max_channels = device.max_channels().try_into().map_err(|_| { + cubeb_backend_error("device_max_channels", "Device channel count overflows u16") + })?; + + Ok(stable_device_id( + "cubeb", + kind, + friendly_name, + max_channels, + seen, + )) +} + +fn stable_device_id( + host: &str, + kind: MediaDeviceInfoKind, + friendly_name: String, + max_channels: u16, + seen: &[String], +) -> String { + let mut index = 0; + loop { + let device_id = crate::media_devices::DeviceId::as_string( + kind, + host.to_string(), + friendly_name.clone(), + max_channels, + index, + ); + + if !seen.iter().any(|id| id == &device_id) { + return device_id; + } + + index += 1; + } +} diff --git a/vendor/web-audio-api-1.7.0/src/io/microphone.rs b/vendor/web-audio-api-1.7.0/src/io/microphone.rs new file mode 100644 index 0000000000..2935c42bc3 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/io/microphone.rs @@ -0,0 +1,109 @@ +use std::error::Error; + +use crate::buffer::{AudioBuffer, AudioBufferOptions}; +use crate::io::AudioBackendManager; +use crate::RENDER_QUANTUM_SIZE; + +use crossbeam_channel::{Receiver, Sender, TryRecvError}; + +pub(crate) struct MicrophoneStream { + receiver: Receiver, + number_of_channels: usize, + sample_rate: f32, + stream: Box, +} + +impl MicrophoneStream { + pub(crate) fn new( + receiver: Receiver, + backend: Box, + ) -> Self { + Self { + receiver, + number_of_channels: backend.number_of_channels(), + sample_rate: backend.sample_rate(), + stream: backend, + } + } +} + +impl Drop for MicrophoneStream { + fn drop(&mut self) { + log::debug!("Microphone stream has been dropped"); + let _ = self.stream.close(); + } +} + +impl Iterator for MicrophoneStream { + type Item = Result>; + + fn next(&mut self) -> Option { + let next = match self.receiver.try_recv() { + Ok(buffer) => { + // new frame was ready + buffer + } + Err(TryRecvError::Empty) => { + // frame not received in time, emit silence + log::debug!("empty channel: input frame delayed"); + + let options = AudioBufferOptions { + number_of_channels: self.number_of_channels, + length: RENDER_QUANTUM_SIZE, + sample_rate: self.sample_rate, + }; + + AudioBuffer::new(options) + } + Err(TryRecvError::Disconnected) => { + // MicrophoneRender has stopped, close stream + return None; + } + }; + + Some(Ok(next)) + } +} + +pub(crate) struct MicrophoneRender { + number_of_channels: usize, + sample_rate: f32, + sender: Sender, +} + +impl MicrophoneRender { + pub fn new(number_of_channels: usize, sample_rate: f32, sender: Sender) -> Self { + Self { + number_of_channels, + sample_rate, + sender, + } + } + + pub fn render + Copy>(&self, data: &[S]) { + let mut channels = Vec::with_capacity(self.number_of_channels); + // copy rendered audio into output slice + for i in 0..self.number_of_channels { + channels.push( + data.iter() + .skip(i) + .step_by(self.number_of_channels) + .map(|v| v.to_sample_()) + .collect(), + ); + } + + let buffer = AudioBuffer::from(channels, self.sample_rate); + let result = self.sender.try_send(buffer); // can fail (frame dropped) + + if result.is_err() { + log::debug!("input frame dropped"); + } + } +} + +impl Drop for MicrophoneRender { + fn drop(&mut self) { + log::debug!("Microphone input has been dropped"); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/io/mod.rs b/vendor/web-audio-api-1.7.0/src/io/mod.rs new file mode 100644 index 0000000000..a132c48798 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/io/mod.rs @@ -0,0 +1,301 @@ +//! Audio input/output interfaces + +use std::error::Error; +use std::fmt; +use std::sync::atomic::{AtomicBool, AtomicU64, AtomicU8}; +use std::sync::Arc; + +use crossbeam_channel::{Receiver, Sender}; + +use crate::buffer::AudioBuffer; +use crate::context::{AudioContextLatencyCategory, AudioContextOptions, AudioContextState}; +use crate::events::EventDispatch; +use crate::media_devices::MediaDeviceInfo; +use crate::media_streams::{MediaStream, MediaStreamTrack}; +use crate::message::ControlMessage; +use crate::stats::AudioStats; +use crate::RENDER_QUANTUM_SIZE; + +mod none; +pub(crate) use none::NoneBackend; + +#[cfg(feature = "cpal")] +mod cpal; + +#[cfg(feature = "cubeb")] +mod cubeb; + +#[cfg(any(feature = "cubeb", feature = "cpal"))] +mod microphone; + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum AudioBackendErrorKind { + DeviceUnavailable, + NotSupported, + InvalidArgument, + BackendSpecific, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct AudioBackendError { + pub(crate) kind: AudioBackendErrorKind, + pub(crate) backend: &'static str, + pub(crate) operation: &'static str, + pub(crate) message: String, +} + +pub(crate) type BackendResult = Result; + +impl AudioBackendError { + pub(crate) fn new( + kind: AudioBackendErrorKind, + backend: &'static str, + operation: &'static str, + message: impl Into, + ) -> Self { + Self { + kind, + backend, + operation, + message: message.into(), + } + } + + #[cfg(all(not(feature = "cubeb"), not(feature = "cpal")))] + pub(crate) fn no_backend(operation: &'static str) -> Self { + Self::new( + AudioBackendErrorKind::NotSupported, + "none", + operation, + "No audio backend available, enable the 'cpal' or 'cubeb' feature", + ) + } +} + +impl fmt::Display for AudioBackendError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} backend error during {}: {:?}: {}", + self.backend, self.operation, self.kind, self.message + ) + } +} + +impl Error for AudioBackendError {} + +#[derive(Debug)] +pub(crate) struct ControlThreadInit { + pub state: Arc, + pub frames_played: Arc, + pub stats: AudioStats, + pub ctrl_msg_send: Sender, + pub event_send: Sender, + pub event_recv: Receiver, +} + +#[derive(Clone, Debug)] +pub(crate) struct RenderThreadInit { + pub state: Arc, + pub startup_pending: Arc, + pub frames_played: Arc, + pub stats: AudioStats, + pub ctrl_msg_recv: Receiver, + pub event_send: Sender, +} + +pub(crate) fn thread_init() -> (ControlThreadInit, RenderThreadInit) { + // Track audio context state - synced from render thread to control thread + let state = Arc::new(AtomicU8::new(AudioContextState::Suspended as u8)); + let startup_pending = Arc::new(AtomicBool::new(true)); + + // Track number of frames - synced from render thread to control thread + let frames_played = Arc::new(AtomicU64::new(0)); + + let stats = AudioStats::new(); + + // Communication channel for ctrl msgs from the control thread to the render thread. + // Use a bounded channel for real-time safety. A maximum of 256 control messages (add/remove + // node, settings, ..) will be handled per render quantum. The control thread will block when + // the capacity is reached. + let (ctrl_msg_send, ctrl_msg_recv) = crossbeam_channel::bounded(256); + + // Communication channel for events from the render thread to the control thread. + // Use a bounded channel for real-time safety. A maximum of 256 events (node ended, error, ..) + // will be sent per render quantum. Excess events are dropped when the capacity is reached. + let (event_send, event_recv) = crossbeam_channel::bounded(256); + + let control_thread_init = ControlThreadInit { + state: Arc::clone(&state), + frames_played: Arc::clone(&frames_played), + stats: stats.clone(), + ctrl_msg_send, + event_send: event_send.clone(), + event_recv, + }; + + let render_thread_init = RenderThreadInit { + state, + startup_pending, + frames_played, + stats, + ctrl_msg_recv, + event_send, + }; + + (control_thread_init, render_thread_init) +} + +/// Set up an output stream (speakers) bases on the selected features (cubeb/cpal/none) +pub(crate) fn build_output( + options: AudioContextOptions, + render_thread_init: RenderThreadInit, +) -> BackendResult> { + if options.sink_id == "none" { + let backend = NoneBackend::build_output(options, render_thread_init)?; + return Ok(Box::new(backend)); + } + + #[cfg(feature = "cubeb")] + { + let backend = cubeb::CubebBackend::build_output(options, render_thread_init)?; + Ok(Box::new(backend)) + } + #[cfg(all(not(feature = "cubeb"), feature = "cpal"))] + { + let backend = cpal::CpalBackend::build_output(options, render_thread_init)?; + Ok(Box::new(backend)) + } + #[cfg(all(not(feature = "cubeb"), not(feature = "cpal")))] + { + Err(AudioBackendError::no_backend("build_output")) + } +} + +/// Set up an input stream (microphone) bases on the selected features (cubeb/cpal/none) +pub(crate) fn build_input( + options: AudioContextOptions, + number_of_channels: Option, +) -> BackendResult { + #[cfg(all(not(feature = "cubeb"), not(feature = "cpal")))] + { + Err(AudioBackendError::no_backend("build_input")) + } + + #[cfg(any(feature = "cubeb", feature = "cpal"))] + { + let (backend, receiver) = { + #[cfg(feature = "cubeb")] + { + cubeb::CubebBackend::build_input(options, number_of_channels)? + } + + #[cfg(all(not(feature = "cubeb"), feature = "cpal"))] + { + cpal::CpalBackend::build_input(options, number_of_channels)? + } + }; + + let media_iter = microphone::MicrophoneStream::new(receiver, Box::new(backend)); + let track = MediaStreamTrack::from_iter(media_iter); + Ok(MediaStream::from_tracks(vec![track])) + } +} + +/// Interface for audio backends +pub(crate) trait AudioBackendManager: Send + Sync + 'static { + /// Name of the concrete implementation - for debug purposes + #[cfg_attr(not(feature = "diagnostics"), allow(dead_code))] + fn name(&self) -> &'static str { + std::any::type_name::() + } + + /// Setup a new output stream (speakers) + fn build_output( + options: AudioContextOptions, + render_thread_init: RenderThreadInit, + ) -> BackendResult + where + Self: Sized; + + /// Setup a new input stream (microphone capture) + fn build_input( + options: AudioContextOptions, + number_of_channels: Option, + ) -> BackendResult<(Self, Receiver)> + where + Self: Sized; + + /// Resume or start the stream + fn resume(&self) -> BackendResult; + + /// Suspend the stream + fn suspend(&self) -> BackendResult; + + /// Close the stream, freeing all resources. It cannot be started again after closing. + fn close(&self) -> BackendResult<()>; + + /// Sample rate of the stream + fn sample_rate(&self) -> f32; + + /// Number of channels of the stream + fn number_of_channels(&self) -> usize; + + /// Output latency of the stream in seconds + /// + /// This is the difference between the time the backend acquires the data in the callback and + /// the listener can hear the sound. + fn output_latency(&self) -> BackendResult; + + /// The audio output device - `""` means the default device + fn sink_id(&self) -> &str; + + fn enumerate_devices_sync() -> BackendResult> + where + Self: Sized; +} + +/// Calculate buffer size in frames for a given latency category +fn buffer_size_for_latency_category( + latency_cat: AudioContextLatencyCategory, + sample_rate: f32, +) -> usize { + // at 44100Hz sample rate (this could be even more relaxed): + // Interactive: 128 samples is 2,9ms + // Balanced: 512 samples is 11,6ms + // Playback: 1024 samples is 23,2ms + match latency_cat { + AudioContextLatencyCategory::Interactive => RENDER_QUANTUM_SIZE, + AudioContextLatencyCategory::Balanced => RENDER_QUANTUM_SIZE * 4, + AudioContextLatencyCategory::Playback => RENDER_QUANTUM_SIZE * 8, + // buffer_size is always positive and truncation is the desired behavior + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_possible_truncation)] + AudioContextLatencyCategory::Custom(latency) => { + assert!( + latency > 0., + "RangeError - Invalid custom latency: {:?}, should be strictly positive", + latency + ); + + let buffer_size = (latency * sample_rate as f64) as usize; + buffer_size.next_power_of_two() + } + } +} + +pub(crate) fn enumerate_devices_sync() -> BackendResult> { + #[cfg(feature = "cubeb")] + { + cubeb::CubebBackend::enumerate_devices_sync() + } + + #[cfg(all(not(feature = "cubeb"), feature = "cpal"))] + { + cpal::CpalBackend::enumerate_devices_sync() + } + + #[cfg(all(not(feature = "cubeb"), not(feature = "cpal")))] + Err(AudioBackendError::no_backend("enumerate_devices_sync")) +} diff --git a/vendor/web-audio-api-1.7.0/src/io/none.rs b/vendor/web-audio-api-1.7.0/src/io/none.rs new file mode 100644 index 0000000000..2cda2e8cb6 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/io/none.rs @@ -0,0 +1,217 @@ +use std::thread; +use std::time::{Duration, Instant}; + +use super::{ + AudioBackendError, AudioBackendErrorKind, AudioBackendManager, BackendResult, RenderThreadInit, +}; + +use crate::buffer::AudioBuffer; +use crate::context::AudioContextOptions; +use crate::media_devices::MediaDeviceInfo; +use crate::render::RenderThread; +use crate::{MAX_CHANNELS, RENDER_QUANTUM_SIZE}; + +use crossbeam_channel::{Receiver, Sender}; + +enum NoneBackendMessage { + Resume, + Suspend, + Close, +} + +#[derive(Clone)] +pub(crate) struct NoneBackend { + sender: Sender, + sample_rate: f32, +} + +impl NoneBackend { + /// Creates a mock backend to be used as tombstones + pub(crate) fn void() -> Self { + Self { + sample_rate: 0., + sender: crossbeam_channel::bounded(0).0, + } + } +} + +struct Callback { + receiver: Receiver, + render_thread: RenderThread, + sample_rate: f32, + running: bool, +} + +impl Callback { + fn run(mut self) { + let buffer_size = RENDER_QUANTUM_SIZE; // TODO Latency Category + let mut buffer = vec![0.; buffer_size * MAX_CHANNELS]; + let interval = Duration::from_secs_f32(buffer_size as f32 / self.sample_rate); + + // For an isochronous callback we must calculate the deadline every render quantum + let mut deadline = Instant::now().checked_add(interval).unwrap(); + + loop { + // poll the receiver as long as the deadline is in the future + while let Ok(msg) = self.receiver.recv_deadline(deadline) { + match msg { + NoneBackendMessage::Close => return, + NoneBackendMessage::Resume => { + self.running = true; + deadline = Instant::now().checked_add(interval).unwrap(); + break; // start processing right away + } + NoneBackendMessage::Suspend => self.running = false, + } + } + + if self.running { + self.render_thread.render(&mut buffer[..]); + } + + deadline = deadline.checked_add(interval).unwrap(); + } + } +} + +impl AudioBackendManager for NoneBackend { + /// Setup a new output stream (speakers) + fn build_output( + options: AudioContextOptions, + render_thread_init: RenderThreadInit, + ) -> BackendResult + where + Self: Sized, + { + let sample_rate = options.sample_rate.unwrap_or(48000.); + + let RenderThreadInit { + state, + startup_pending, + frames_played, + stats, + ctrl_msg_recv, + event_send, + } = render_thread_init; + + let mut render_thread = RenderThread::new( + sample_rate, + MAX_CHANNELS, + ctrl_msg_recv, + state, + frames_played, + stats, + event_send, + ); + render_thread.set_startup_pending(startup_pending); + render_thread.spawn_garbage_collector_thread(); + + // Use a bounded channel for real-time safety. A maximum of 32 control messages (resume, + // suspend, ..) will be handled per render quantum. The control thread will block when the + // capacity is reached. + let (sender, receiver) = crossbeam_channel::bounded(32); + + // todo: pass buffer size and sample rate + let callback = Callback { + render_thread, + receiver, + sample_rate, + running: true, + }; + + thread::spawn(move || callback.run()); + + Ok(Self { + sender, + sample_rate, + }) + } + + /// Setup a new input stream (microphone capture) + fn build_input( + _options: AudioContextOptions, + _number_of_channels: Option, + ) -> BackendResult<(Self, Receiver)> + where + Self: Sized, + { + Err(AudioBackendError::new( + AudioBackendErrorKind::NotSupported, + "none", + "build_input", + "The none backend does not support audio input", + )) + } + + /// Resume or start the stream + fn resume(&self) -> BackendResult { + self.sender + .send(NoneBackendMessage::Resume) + .map(|_| true) + .map_err(|_| { + AudioBackendError::new( + AudioBackendErrorKind::BackendSpecific, + "none", + "resume", + "The render thread is no longer available", + ) + }) + } + + /// Suspend the stream + fn suspend(&self) -> BackendResult { + self.sender + .send(NoneBackendMessage::Suspend) + .map(|_| true) + .map_err(|_| { + AudioBackendError::new( + AudioBackendErrorKind::BackendSpecific, + "none", + "suspend", + "The render thread is no longer available", + ) + }) + } + + /// Close the stream, freeing all resources. It cannot be started again after closing. + fn close(&self) -> BackendResult<()> { + self.sender.send(NoneBackendMessage::Close).map_err(|_| { + AudioBackendError::new( + AudioBackendErrorKind::BackendSpecific, + "none", + "close", + "The render thread is no longer available", + ) + }) + } + + /// Sample rate of the stream + fn sample_rate(&self) -> f32 { + self.sample_rate + } + + /// Number of channels of the stream + fn number_of_channels(&self) -> usize { + MAX_CHANNELS + } + + /// Output latency of the stream in seconds + /// + /// This is the difference between the time the backend acquires the data in the callback and + /// the listener can hear the sound. + fn output_latency(&self) -> BackendResult { + Ok(0.) + } + + /// The audio output device + fn sink_id(&self) -> &str { + "none" + } + + fn enumerate_devices_sync() -> BackendResult> + where + Self: Sized, + { + Ok(vec![]) + } +} diff --git a/vendor/web-audio-api-1.7.0/src/lib.rs b/vendor/web-audio-api-1.7.0/src/lib.rs new file mode 100644 index 0000000000..70d9a77fc8 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/lib.rs @@ -0,0 +1,328 @@ +#![doc = include_str!("../README.md")] +#![warn(rust_2018_idioms)] +#![warn(rust_2021_compatibility)] +#![warn(clippy::missing_panics_doc)] +#![warn(clippy::clone_on_ref_ptr)] +#![warn(clippy::todo)] +#![warn(clippy::dbg_macro)] +#![warn(clippy::unimplemented)] +#![deny(trivial_numeric_casts)] +#![deny(missing_debug_implementations)] +#![deny(unsafe_op_in_unsafe_fn)] + +use std::error::Error; +use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; + +/// Render quantum size, the audio graph is rendered in blocks of RENDER_QUANTUM_SIZE samples +/// see. +pub(crate) const RENDER_QUANTUM_SIZE: usize = 128; + +/// Maximum number of channels for audio processing +pub const MAX_CHANNELS: usize = 32; + +mod buffer; +pub use buffer::*; + +mod capacity; +pub use capacity::*; + +mod playback_stats; +pub use playback_stats::*; + +pub mod context; + +pub mod media_devices; +pub mod media_recorder; +pub mod media_streams; + +pub mod node; + +mod events; +pub use events::*; + +mod message_port; +pub use message_port::MessagePort; + +mod param; +pub use param::*; + +mod periodic_wave; +pub use periodic_wave::*; + +mod render; + +mod stats; + +mod spatial; +pub use spatial::AudioListener; + +mod io; + +mod analysis; +mod message; + +mod decoding; + +mod media_element; +pub use media_element::MediaElement; + +mod resampling; +pub mod worklet; + +#[repr(transparent)] +pub(crate) struct AtomicF32 { + bits: AtomicU32, +} + +impl std::fmt::Debug for AtomicF32 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", self.load(Ordering::Relaxed))) + } +} + +impl AtomicF32 { + #[must_use] + pub fn new(value: f32) -> Self { + Self { + bits: AtomicU32::new(value.to_bits()), + } + } + + #[must_use] + pub fn load(&self, ordering: Ordering) -> f32 { + f32::from_bits(self.bits.load(ordering)) + } + + pub fn store(&self, value: f32, ordering: Ordering) { + self.bits.store(value.to_bits(), ordering); + } +} + +/// Atomic float 64, only `load` and `store` are supported, no arithmetic +#[repr(transparent)] +pub(crate) struct AtomicF64 { + bits: AtomicU64, +} + +impl std::fmt::Debug for AtomicF64 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", self.load(Ordering::Relaxed))) + } +} + +impl AtomicF64 { + #[must_use] + pub fn new(value: f64) -> Self { + Self { + bits: AtomicU64::new(value.to_bits()), + } + } + + #[must_use] + pub fn load(&self, ordering: Ordering) -> f64 { + f64::from_bits(self.bits.load(ordering)) + } + + pub fn store(&self, value: f64, ordering: Ordering) { + self.bits.store(value.to_bits(), ordering); + } +} + +/// Assert that the given sample rate is valid. +/// +/// Note that in practice sample rates should stand between 8000Hz (lower bound for +/// voice based applications, e.g. see phone bandwidth) and 96000Hz (for very high +/// quality audio applications and spectrum manipulation). +/// Most common sample rates for musical applications are 44100 and 48000. +/// +/// The Web Audio API specification requires implementations to support every +/// sample rate in the inclusive range `[3000, 768000]`. +/// +/// - see +/// +/// # Panics +/// +/// This function will panic if the given sample rate is outside the inclusive +/// range `[3000, 768000]`. +/// +/// The minimum sample rate supported by the Web Audio API specification, in Hz. +pub(crate) const MIN_SAMPLE_RATE: f32 = 3_000.; + +/// The maximum sample rate supported by the Web Audio API specification, in Hz. +pub(crate) const MAX_SAMPLE_RATE: f32 = 768_000.; + +/// Returns whether the given sample rate falls in the inclusive range +/// `[MIN_SAMPLE_RATE, MAX_SAMPLE_RATE]` defined by the Web Audio API specification. +/// +/// - see +#[inline(always)] +pub(crate) fn is_valid_sample_rate(sample_rate: f32) -> bool { + (MIN_SAMPLE_RATE..=MAX_SAMPLE_RATE).contains(&sample_rate) +} + +#[track_caller] +#[inline(always)] +pub(crate) fn assert_valid_sample_rate(sample_rate: f32) { + assert!( + is_valid_sample_rate(sample_rate), + "NotSupportedError - Invalid sample rate: {:?}, should be in the range [{:?}, {:?}]", + sample_rate, + MIN_SAMPLE_RATE, + MAX_SAMPLE_RATE, + ); +} + +/// Assert that the given number of channels is valid. +/// +/// # Panics +/// +/// This function will panic if: +/// - the given number of channels is outside the [1, 32] range, +/// 32 being defined by the MAX_CHANNELS constant. +/// +#[track_caller] +#[inline(always)] +pub(crate) fn assert_valid_number_of_channels(number_of_channels: usize) { + assert!( + number_of_channels > 0 && number_of_channels <= MAX_CHANNELS, + "NotSupportedError - Invalid number of channels: {:?} is outside range [1, {:?}]", + number_of_channels, + MAX_CHANNELS + ); +} + +/// Assert that the given channel number is valid according to the number of channels +/// of an Audio asset (e.g. [`AudioBuffer`]). +/// +/// # Panics +/// +/// This function will panic if: +/// - the given channel number is greater than or equal to the given number of channels. +/// +#[track_caller] +#[inline(always)] +pub(crate) fn assert_valid_channel_number(channel_number: usize, number_of_channels: usize) { + assert!( + channel_number < number_of_channels, + "IndexSizeError - Invalid channel number {:?} (number of channels: {:?})", + channel_number, + number_of_channels + ); +} + +/// Assert that the given value number is a valid buffer length, i.e. greater than zero +/// +/// # Panics +/// +/// This function will panic if: +/// - the given value is not lower than or equal to zero +/// +#[track_caller] +#[inline(always)] +pub(crate) fn assert_valid_buffer_length(length: usize) { + assert!( + length > 0, + "NotSupportedError - Invalid length: {:?} is less than or equal to minimum bound (0)", + length, + ); +} + +/// Assert that the given value number is a valid time information, i.e. greater +/// than or equal to zero and finite. +/// +/// # Panics +/// +/// This function will panic if: +/// - the given value is not finite and lower than zero +/// +#[track_caller] +#[inline(always)] +pub(crate) fn assert_valid_time_value(value: f64) { + assert!( + value.is_finite(), + "TypeError - The provided time value is non-finite.", + ); + + assert!( + value >= 0., + "RangeError - The provided time value ({:?}) cannot be negative", + value + ); +} + +pub(crate) trait AudioBufferIter: Iterator + Send + 'static {} + +impl + Send + 'static> AudioBufferIter for M {} + +type FallibleBuffer = Result>; + +#[cfg(test)] +mod tests { + use float_eq::assert_float_eq; + + use super::*; + + #[test] + fn test_atomic_f64() { + let f = AtomicF64::new(2.0); + assert_float_eq!(f.load(Ordering::SeqCst), 2.0, abs <= 0.); + + f.store(3.0, Ordering::SeqCst); + assert_float_eq!(f.load(Ordering::SeqCst), 3.0, abs <= 0.); + } + + #[test] + fn test_valid_sample_rate() { + assert_valid_sample_rate(48000.); + assert_valid_sample_rate(3_000.); + assert_valid_sample_rate(768_000.); + } + + #[test] + #[should_panic] + fn test_invalid_sample_rate_too_small() { + assert_valid_sample_rate(2_999.); + } + + #[test] + #[should_panic] + fn test_invalid_sample_rate_too_big() { + assert_valid_sample_rate(768_001.); + } + + #[test] + #[should_panic] + fn test_invalid_number_of_channels_min() { + assert_valid_number_of_channels(0); + } + + #[test] + #[should_panic] + fn test_invalid_number_of_channels_max() { + assert_valid_number_of_channels(33); + } + + #[test] + fn test_valid_number_of_channels() { + assert_valid_number_of_channels(1); + assert_valid_number_of_channels(32); + } + + #[test] + #[should_panic] + fn test_invalid_time_value_non_finite() { + assert_valid_time_value(f64::NAN); + } + + #[test] + #[should_panic] + fn test_invalid_time_value_negative() { + assert_valid_time_value(-1.); + } + + #[test] + fn test_valid_time_value() { + assert_valid_time_value(0.); + assert_valid_time_value(1.); + } +} diff --git a/vendor/web-audio-api-1.7.0/src/media_devices/mod.rs b/vendor/web-audio-api-1.7.0/src/media_devices/mod.rs new file mode 100644 index 0000000000..c369c6f589 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/media_devices/mod.rs @@ -0,0 +1,250 @@ +//! Primitives of the MediaDevices API +//! +//! The MediaDevices interface provides access to connected media input devices like microphones. +//! +//! + +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +use crate::context::{AudioContextLatencyCategory, AudioContextOptions}; +use crate::io::BackendResult; +use crate::media_streams::MediaStream; + +/// List the available media output devices, such as speakers, headsets, loopbacks, etc +/// +/// The media device_id can be used to specify the [`sink_id` of the `AudioContext`](crate::context::AudioContextOptions::sink_id) +/// +/// ```no_run +/// use web_audio_api::media_devices::{enumerate_devices_sync, MediaDeviceInfoKind}; +/// +/// let devices = enumerate_devices_sync(); +/// assert_eq!(devices[0].device_id(), "1"); +/// assert_eq!(devices[0].group_id(), None); +/// assert_eq!(devices[0].kind(), MediaDeviceInfoKind::AudioOutput); +/// assert_eq!(devices[0].label(), "Macbook Pro Builtin Speakers"); +/// ``` +pub fn enumerate_devices_sync() -> Vec { + try_enumerate_devices_sync().unwrap_or_else(|e| { + log::error!("Unable to enumerate media devices: {e}"); + vec![] + }) +} + +pub(crate) fn try_enumerate_devices_sync() -> BackendResult> { + crate::io::enumerate_devices_sync() +} + +// Internal struct to derive a stable id for a given input / output device +// cf. https://github.com/orottier/web-audio-api-rs/issues/356 +#[derive(Hash)] +pub(crate) struct DeviceId { + kind: MediaDeviceInfoKind, + host: String, + device_name: String, + num_channels: u16, + index: u8, +} + +impl DeviceId { + pub(crate) fn as_string( + kind: MediaDeviceInfoKind, + host: String, + device_name: String, + num_channels: u16, + index: u8, + ) -> String { + let device_info = Self { + kind, + host, + device_name, + num_channels, + index, + }; + + let mut hasher = DefaultHasher::new(); + device_info.hash(&mut hasher); + format!("{}", hasher.finish()) + } +} + +/// Describes input/output type of a media device +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum MediaDeviceInfoKind { + VideoInput, + AudioInput, + AudioOutput, +} + +/// Describes a single media input or output device +/// +/// Call [`enumerate_devices_sync`] to obtain a list of devices for your hardware. +#[derive(Debug)] +pub struct MediaDeviceInfo { + device_id: String, + group_id: Option, + kind: MediaDeviceInfoKind, + label: String, +} + +impl MediaDeviceInfo { + pub(crate) fn new( + device_id: String, + group_id: Option, + kind: MediaDeviceInfoKind, + label: String, + ) -> Self { + Self { + device_id, + group_id, + kind, + label, + } + } + + /// Identifier for the represented device + /// + /// The current implementation is not stable across sessions so you should not persist this + /// value + pub fn device_id(&self) -> &str { + &self.device_id + } + + /// Two devices have the same group identifier if they belong to the same physical device + pub fn group_id(&self) -> Option<&str> { + self.group_id.as_deref() + } + + /// Enumerated value that is either "videoinput", "audioinput" or "audiooutput". + pub fn kind(&self) -> MediaDeviceInfoKind { + self.kind + } + + /// Friendly label describing this device + pub fn label(&self) -> &str { + &self.label + } +} + +/// Dictionary used to instruct what sort of tracks to include in the [`MediaStream`] returned by +/// [`get_user_media_sync`] +#[derive(Clone, Debug)] +pub enum MediaStreamConstraints { + Audio, + AudioWithConstraints(MediaTrackConstraints), +} + +/// Desired media stream track settings for [`MediaTrackConstraints`] +#[derive(Default, Debug, Clone)] +#[non_exhaustive] +pub struct MediaTrackConstraints { + // ConstrainULong width; + // ConstrainULong height; + // ConstrainDouble aspectRatio; + // ConstrainDouble frameRate; + // ConstrainDOMString facingMode; + // ConstrainDOMString resizeMode; + pub sample_rate: Option, + // ConstrainULong sampleSize; + // ConstrainBoolean echoCancellation; + // ConstrainBoolean autoGainControl; + // ConstrainBoolean noiseSuppression; + pub latency: Option, + pub channel_count: Option, // TODO model as ConstrainULong; + pub device_id: Option, + // ConstrainDOMString groupId; +} + +impl From for AudioContextOptions { + fn from(value: MediaTrackConstraints) -> Self { + let latency_hint = match value.latency { + Some(v) => AudioContextLatencyCategory::Custom(v), + None => AudioContextLatencyCategory::Interactive, + }; + let sink_id = value.device_id.unwrap_or(String::from("")); + + AudioContextOptions { + latency_hint, + sample_rate: value.sample_rate, + sink_id, + render_size_hint: Default::default(), + } + } +} + +/// Check if the provided device_id is available for playback +/// +/// It should be "" or a valid input `deviceId` returned from [`enumerate_devices_sync`] +fn is_valid_device_id(device_id: &str) -> bool { + if device_id.is_empty() { + true + } else { + enumerate_devices_sync() + .into_iter() + .filter(|d| d.kind == MediaDeviceInfoKind::AudioInput) + .any(|d| d.device_id() == device_id) + } +} + +/// Prompt for permission to use a media input (audio only) +/// +/// This produces a [`MediaStream`] with tracks containing the requested types of media, which can +/// be used inside a [`MediaStreamAudioSourceNode`](crate::node::MediaStreamAudioSourceNode). +/// +/// It is okay for the `MediaStream` struct to go out of scope, any corresponding stream will still be +/// kept alive and emit audio buffers. Call the `close()` method if you want to stop the media +/// input and release all system resources. +/// +/// This function operates synchronously, which may be undesirable on the control thread. An async +/// version is currently not implemented. +/// +/// # Panics +/// +/// This function will panic when the selected audio backend cannot create or start the input +/// stream. A public `try_get_user_media_sync` could be added in the future to handle these errors +/// without panicking, similar to [`AudioContext::try_new`](crate::context::AudioContext::new). +/// +/// Input streams with more than [`MAX_CHANNELS`](crate::MAX_CHANNELS) are not +/// supported. A panic will occur when the hardware is unable to serve a stream +/// with lower channel count. +/// +/// # Example +/// +/// ```no_run +/// use web_audio_api::context::{BaseAudioContext, AudioContext}; +/// use web_audio_api::context::{AudioContextLatencyCategory, AudioContextOptions}; +/// use web_audio_api::media_devices; +/// use web_audio_api::media_devices::MediaStreamConstraints; +/// use web_audio_api::node::AudioNode; +/// +/// let context = AudioContext::default(); +/// let mic = media_devices::get_user_media_sync(MediaStreamConstraints::Audio); +/// +/// // register as media element in the audio context +/// let background = context.create_media_stream_source(&mic); +/// +/// // connect the node directly to the destination node (speakers) +/// background.connect(&context.destination()); +/// +/// // enjoy listening +/// std::thread::sleep(std::time::Duration::from_secs(4)); +/// ``` +pub fn get_user_media_sync(constraints: MediaStreamConstraints) -> MediaStream { + try_get_user_media_sync(constraints).unwrap_or_else(|e| panic!("InvalidStateError - {e}")) +} + +pub(crate) fn try_get_user_media_sync( + constraints: MediaStreamConstraints, +) -> BackendResult { + let (channel_count, mut options) = match constraints { + MediaStreamConstraints::Audio => (None, AudioContextOptions::default()), + MediaStreamConstraints::AudioWithConstraints(cs) => (cs.channel_count, cs.into()), + }; + + if !is_valid_device_id(&options.sink_id) { + log::error!("NotFoundError: invalid deviceId {:?}", options.sink_id); + options.sink_id = String::from(""); + } + + crate::io::build_input(options, channel_count) +} diff --git a/vendor/web-audio-api-1.7.0/src/media_element.rs b/vendor/web-audio-api-1.7.0/src/media_element.rs new file mode 100644 index 0000000000..d78afb98a8 --- /dev/null +++ b/vendor/web-audio-api-1.7.0/src/media_element.rs @@ -0,0 +1,223 @@ +use std::error::Error; +use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use creek::{ReadDiskStream, SeekMode, SymphoniaDecoder}; +use crossbeam_channel::{Receiver, Sender}; + +use crate::{AtomicF64, AudioBuffer, RENDER_QUANTUM_SIZE}; + +/// Real time safe audio stream +pub(crate) struct RTSStream { + stream: ReadDiskStream, + number_of_channels: usize, + current_time: Arc, + receiver: Receiver, + loop_: Arc, + paused: Arc, + playback_rate: Arc, +} + +impl std::fmt::Debug for RTSStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RTSStream") + .field("number_of_channels", &self.number_of_channels) + .finish_non_exhaustive() + } +} + +/// Controller actions for a media element +pub(crate) enum MediaElementAction { + /// Seek to the given timestamp + Seek(f64), + /// Enable/disable looping + SetLoop(bool), + /// Start or restart the stream + Play, + /// Pause the stream + Pause, + /// Update the playback rate + SetPlaybackRate(f64), +} + +/// Shim of the `

= AudioWorkletRenderer { + processor: Processor::new(processor_options), + audio_param_map: processor_param_map, + output_channel_count, + inputs_flat: Vec::with_capacity(number_of_inputs * MAX_CHANNELS), + inputs_grouped: Vec::with_capacity(number_of_inputs), + outputs_flat: Vec::with_capacity(number_of_output_channels), + outputs_grouped: Vec::with_capacity(number_of_outputs), + }; + + (node, Box::new(render)) + }); + + node + } + + /// Collection of AudioParam objects with associated names of this node + /// + /// This map is populated from a list of [`AudioParamDescriptor`]s in the + /// [`AudioWorkletProcessor`] class constructor at the instantiation. + pub fn parameters(&self) -> &HashMap { + &self.audio_param_map + } + + /// Message port to the processor in the render thread + /// + /// Every AudioWorkletNode has an associated port which is the [`MessagePort`]. It is connected + /// to the port on the corresponding [`AudioWorkletProcessor`] object allowing bidirectional + /// communication between the AudioWorkletNode and its AudioWorkletProcessor. + pub fn port(&self) -> MessagePort<'_> { + MessagePort::from_node(self) + } +} + +enum Processor { + Uninit(Option), + Init(P), +} + +impl Processor

{ + fn new(opts: P::ProcessorOptions) -> Self { + Self::Uninit(Some(opts)) + } + + fn load(&mut self) -> &mut dyn AudioWorkletProcessor { + if let Processor::Uninit(opts) = self { + *self = Self::Init(P::constructor(opts.take().unwrap())); + } + + match self { + Self::Init(p) => p, + Self::Uninit(_) => unreachable!(), + } + } +} + +struct AudioWorkletRenderer { + processor: Processor

, + audio_param_map: HashMap, + output_channel_count: Vec, + + // Preallocated, reusable containers for channel data + inputs_flat: Vec<&'static [f32]>, + inputs_grouped: Vec<&'static [&'static [f32]]>, + outputs_flat: Vec<&'static mut [f32]>, + outputs_grouped: Vec<&'static mut [&'static mut [f32]]>, +} + +// SAFETY: +// The concrete AudioWorkletProcessor is instantiated inside the render thread and won't be +// sent elsewhere. +unsafe impl Send for AudioWorkletRenderer

{} + +impl AudioProcessor for AudioWorkletRenderer

{ + fn process( + &mut self, + inputs: &[AudioRenderQuantum], + outputs: &mut [AudioRenderQuantum], + params: crate::render::AudioParamValues<'_>, + scope: &AudioWorkletGlobalScope, + ) -> bool { + let processor = self.processor.load(); + + // Bear with me, to construct a &[&[&[f32]]] we first build a backing vector of all the + // individual sample slices. Then we chop it up to get to the right sub-slice structure. + inputs + .iter() + .flat_map(|input| input.channels()) + .map(|input_channel| input_channel.as_ref()) + // SAFETY + // We're upgrading the lifetime of the channel data to `static`. This is okay because + // `self.processor` is a HRTB (for <'a> Fn (&'a) -> ..) so the references cannot + // escape. The channel containers are cleared at the end of the `process` method. + .map(|input_channel| unsafe { std::mem::transmute(input_channel) }) + .for_each(|c| self.inputs_flat.push(c)); + + let mut inputs_flat = &self.inputs_flat[..]; + for input in inputs { + let c = input.number_of_channels(); + let (left, right) = inputs_flat.split_at(c); + #[cfg(feature = "spec-compliant-worklet-inputs")] + let left_static = { + // [spec] If there are no actively processing AudioNodes connected to + // the 𝑛th input of the AudioWorkletNode for the current render quantum, + // then the content of inputs[n] is an empty array, indicating that + // zero channels of input are available. This is the only circumstance + // under which the number of elements of inputs[n] can be zero. + // cf. https://webaudio.github.io/web-audio-api/#audioworkletprocess-callback-parameters + if input.is_silent() { + &[] + } else { + // SAFETY - see comments above + unsafe { std::mem::transmute::<&[&[f32]], &[&[f32]]>(left) } + } + }; + + #[cfg(not(feature = "spec-compliant-worklet-inputs"))] + // SAFETY - see comments above + let left_static = unsafe { std::mem::transmute::<&[&[f32]], &[&[f32]]>(left) }; + + self.inputs_grouped.push(left_static); + inputs_flat = right; + } + + // Set the proper channel count for the outputs + if !outputs.is_empty() && self.output_channel_count.is_empty() { + // special case - single input/output - inherit channel count from input + outputs[0].set_number_of_channels(inputs[0].number_of_channels()); + } else { + outputs + .iter_mut() + .zip(self.output_channel_count.iter()) + .for_each(|(output, &channel_count)| output.set_number_of_channels(channel_count)); + } + + // Create an iterator for the output channel counts without allocating, handling also the + // case where self.output_channel_count is empty. + let single_case = [inputs + .first() + .map(|i| i.number_of_channels()) + .unwrap_or_default()]; + let output_channel_count = if self.output_channel_count.is_empty() { + &single_case[..] + } else { + &self.output_channel_count[..] + }; + + outputs + .iter_mut() + .flat_map(|output| output.channels_mut()) + .map(|output_channel| output_channel.deref_mut()) + // SAFETY + // We're upgrading the lifetime of the channel data to `static`. This is okay because + // `self.processor` is a HRTB (for <'a> Fn (&'a) -> ..) so the references cannot + // escape. The channel containers are cleared at the end of the `process` method. + .map(|output_channel| unsafe { std::mem::transmute(output_channel) }) + .for_each(|c| self.outputs_flat.push(c)); + + if !outputs.is_empty() { + let mut outputs_flat = &mut self.outputs_flat[..]; + for c in output_channel_count { + let (left, right) = outputs_flat.split_at_mut(*c); + // SAFETY - see comments above + let left_static = + unsafe { std::mem::transmute::<&mut [&mut [f32]], &mut [&mut [f32]]>(left) }; + self.outputs_grouped.push(left_static); + outputs_flat = right; + } + } + + let param_getter = AudioParamValues { + values: params, + map: &self.audio_param_map, + }; + + let tail_time = processor.process( + &self.inputs_grouped[..], + &mut self.outputs_grouped[..], + param_getter, + scope, + ); + + self.inputs_grouped.clear(); + self.inputs_flat.clear(); + self.outputs_grouped.clear(); + self.outputs_flat.clear(); + + tail_time + } + + fn onmessage(&mut self, msg: &mut dyn Any) { + self.processor.load().onmessage(msg) + } + + fn has_side_effects(&self) -> bool { + true // could be IO, message passing, .. + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::context::OfflineAudioContext; + use float_eq::assert_float_eq; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; + + struct TestProcessor; + + impl AudioWorkletProcessor for TestProcessor { + type ProcessorOptions = (); + + fn constructor(_opts: Self::ProcessorOptions) -> Self { + TestProcessor {} + } + + fn process<'a, 'b>( + &mut self, + _inputs: &'b [&'a [&'a [f32]]], + _outputs: &'b mut [&'a mut [&'a mut [f32]]], + _params: AudioParamValues<'b>, + _scope: &'b AudioWorkletGlobalScope, + ) -> bool { + true + } + } + + #[test] + fn test_worklet_render() { + let mut context = OfflineAudioContext::new(1, 128, 48000.); + let options = AudioWorkletNodeOptions::default(); + let worklet = AudioWorkletNode::new::(&context, options); + worklet.connect(&context.destination()); + let buffer = context.start_rendering_sync(); + assert_float_eq!( + buffer.get_channel_data(0)[..], + &[0.; 128][..], + abs_all <= 0. + ); + } + + #[test] + fn test_worklet_inputs_outputs() { + let matrix = [0, 1, 2]; + let mut context = OfflineAudioContext::new(1, 128, 48000.); + for inputs in matrix { + for outputs in matrix { + if inputs == 0 && outputs == 0 { + continue; // this case is not allowed + } + let options = AudioWorkletNodeOptions { + number_of_inputs: inputs, + number_of_outputs: outputs, + ..AudioWorkletNodeOptions::default() + }; + let worklet = AudioWorkletNode::new::(&context, options); + + if outputs > 0 { + worklet.connect(&context.destination()); + } + } + } + let buffer = context.start_rendering_sync(); + assert_float_eq!( + buffer.get_channel_data(0)[..], + &[0.; 128][..], + abs_all <= 0. + ); + } + + #[test] + fn test_worklet_only_input() { + struct SetBoolWhenRunProcessor(Arc); + + impl AudioWorkletProcessor for SetBoolWhenRunProcessor { + type ProcessorOptions = Arc; + + fn constructor(opts: Self::ProcessorOptions) -> Self { + Self(opts) + } + + fn process<'a, 'b>( + &mut self, + _inputs: &'b [&'a [&'a [f32]]], + _outputs: &'b mut [&'a mut [&'a mut [f32]]], + _params: AudioParamValues<'b>, + _scope: &'b AudioWorkletGlobalScope, + ) -> bool { + self.0.store(true, Ordering::Relaxed); + false + } + } + + let has_run = Arc::new(AtomicBool::new(false)); + + let mut context = OfflineAudioContext::new(1, 128, 48000.); + let options = AudioWorkletNodeOptions { + number_of_inputs: 1, + number_of_outputs: 0, + processor_options: Arc::clone(&has_run), + ..AudioWorkletNodeOptions::default() + }; + let _ = AudioWorkletNode::new::(&context, options); + + let _ = context.start_rendering_sync(); + assert!(has_run.load(Ordering::Relaxed)); + } + + #[cfg(feature = "spec-compliant-worklet-inputs")] + #[test] + fn test_worklet_input_not_actively_processing() { + use crate::node::AudioScheduledSourceNode; + use crate::node::OscillatorNode; + + struct SetBoolWhenInputNotActivelyProcessing(Arc); + + impl AudioWorkletProcessor for SetBoolWhenInputNotActivelyProcessing { + type ProcessorOptions = Arc; + + fn constructor(opts: Self::ProcessorOptions) -> Self { + Self(opts) + } + + fn process<'a, 'b>( + &mut self, + inputs: &'b [&'a [&'a [f32]]], + _outputs: &'b mut [&'a mut [&'a mut [f32]]], + _params: AudioParamValues<'b>, + _scope: &'b AudioWorkletGlobalScope, + ) -> bool { + if inputs[0].is_empty() { + self.0.store(true, Ordering::Relaxed); + } + false + } + } + + let mark_empty_input = Arc::new(AtomicBool::new(false)); + + let mut context = OfflineAudioContext::new(1, 48000, 48000.); + let options = AudioWorkletNodeOptions { + number_of_inputs: 1, + number_of_outputs: 0, + processor_options: Arc::clone(&mark_empty_input), + ..AudioWorkletNodeOptions::default() + }; + let worklet = + AudioWorkletNode::new::(&context, options); + let mut osc = OscillatorNode::new(&context, Default::default()); + osc.connect(&worklet); + osc.start(); + // when stopped the osc should output one channel of silence, which indicates + // that it is not actively processing. Hence the inputs[0].len() of the + // the worklet should be zero + osc.stop_at(0.1); + + let _ = context.start_rendering_sync(); + assert!(mark_empty_input.load(Ordering::Relaxed)); + } + + #[cfg(feature = "spec-compliant-worklet-inputs")] + #[test] + fn test_worklet_no_input() { + struct SetBoolWhenInputNotActivelyProcessing(Arc); + + impl AudioWorkletProcessor for SetBoolWhenInputNotActivelyProcessing { + type ProcessorOptions = Arc; + + fn constructor(opts: Self::ProcessorOptions) -> Self { + Self(opts) + } + + fn process<'a, 'b>( + &mut self, + inputs: &'b [&'a [&'a [f32]]], + _outputs: &'b mut [&'a mut [&'a mut [f32]]], + _params: AudioParamValues<'b>, + _scope: &'b AudioWorkletGlobalScope, + ) -> bool { + if inputs[0].is_empty() { + self.0.store(true, Ordering::Relaxed); + } + false + } + } + + let mark_empty_input = Arc::new(AtomicBool::new(false)); + + let mut context = OfflineAudioContext::new(1, 128, 48000.); + let options = AudioWorkletNodeOptions { + number_of_inputs: 1, + number_of_outputs: 0, + processor_options: Arc::clone(&mark_empty_input), + ..AudioWorkletNodeOptions::default() + }; + let _ = AudioWorkletNode::new::(&context, options); + + let _ = context.start_rendering_sync(); + assert!(mark_empty_input.load(Ordering::Relaxed)); + } + + #[test] + fn test_worklet_output_channel_count() { + let mut context = OfflineAudioContext::new(1, 128, 48000.); + + let options1 = AudioWorkletNodeOptions { + output_channel_count: vec![], + ..AudioWorkletNodeOptions::default() + }; + let worklet1 = AudioWorkletNode::new::(&context, options1); + worklet1.connect(&context.destination()); + + let options2 = AudioWorkletNodeOptions { + output_channel_count: vec![1], + ..AudioWorkletNodeOptions::default() + }; + let worklet2 = AudioWorkletNode::new::(&context, options2); + worklet2.connect(&context.destination()); + + let options3 = AudioWorkletNodeOptions { + number_of_outputs: 2, + output_channel_count: vec![1, 2], + ..AudioWorkletNodeOptions::default() + }; + let worklet3 = AudioWorkletNode::new::(&context, options3); + worklet3.connect(&context.destination()); + + let buffer = context.start_rendering_sync(); + assert_float_eq!( + buffer.get_channel_data(0)[..], + &[0.; 128][..], + abs_all <= 0. + ); + } + + #[test] + fn send_bound() { + #[derive(Default)] + struct RcProcessor { + _rc: std::rc::Rc<()>, // not send + } + + impl AudioWorkletProcessor for RcProcessor { + type ProcessorOptions = (); + + fn constructor(_opts: Self::ProcessorOptions) -> Self { + Self::default() + } + + fn process<'a, 'b>( + &mut self, + _inputs: &'b [&'a [&'a [f32]]], + _outputs: &'b mut [&'a mut [&'a mut [f32]]], + _params: AudioParamValues<'b>, + _scope: &'b AudioWorkletGlobalScope, + ) -> bool { + true + } + } + + let context = OfflineAudioContext::new(1, 128, 48000.); + let options = AudioWorkletNodeOptions::default(); + let _worklet = AudioWorkletNode::new::(&context, options); + } +}