From f534ca7e5d019755f8d670a4bc9b2b9d2a308f08 Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Fri, 24 Jan 2025 16:27:19 -0500 Subject: [PATCH 1/7] Start work on audio handler in Rust --- lightningbeam-core/Cargo.lock | 1054 +++++++++++++++++++++++++++++++ lightningbeam-core/Cargo.toml | 28 + lightningbeam-core/src/audio.rs | 105 +++ lightningbeam-core/src/lib.rs | 111 ++++ lightningbeam-core/src/time.rs | 136 ++++ 5 files changed, 1434 insertions(+) create mode 100644 lightningbeam-core/Cargo.lock create mode 100644 lightningbeam-core/Cargo.toml create mode 100644 lightningbeam-core/src/audio.rs create mode 100644 lightningbeam-core/src/lib.rs create mode 100644 lightningbeam-core/src/time.rs diff --git a/lightningbeam-core/Cargo.lock b/lightningbeam-core/Cargo.lock new file mode 100644 index 0000000..9db978d --- /dev/null +++ b/lightningbeam-core/Cargo.lock @@ -0,0 +1,1054 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5915f52fe2cf65e83924d037b6c5290b7cee097c6b5c8700746e6168a343fd6b" +dependencies = [ + "alsa-sys", + "bitflags 1.3.2", + "libc", + "nix", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.8.0", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.96", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cc" +version = "1.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "coreaudio-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88" +dependencies = [ + "bitflags 1.3.2", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f342c1b63e185e9953584ff2199726bf53850d96610a310e3aca09e9405a2d0b" +dependencies = [ + "alsa", + "core-foundation-sys", + "coreaudio-rs", + "jni", + "js-sys", + "libc", + "mach", + "ndk 0.7.0", + "ndk-context", + "oboe", + "once_cell", + "parking_lot", + "stdweb", + "thiserror", + "web-sys", + "windows", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "lightningbeam-core" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "cpal", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys 0.3.0", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys 0.4.1+23.1.7779620", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "oboe" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f63c358b4fa0fbcfefd7c8be5cfc39c08ce2389f5325687e7762a48d30a5c1" +dependencies = [ + "jni", + "ndk 0.6.0", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd" +dependencies = [ + "cc", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde_json" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +dependencies = [ + "sha1_smol", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn 1.0.109", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn 1.0.109", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] diff --git a/lightningbeam-core/Cargo.toml b/lightningbeam-core/Cargo.toml new file mode 100644 index 0000000..d34a84e --- /dev/null +++ b/lightningbeam-core/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "lightningbeam-core" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +wasm-bindgen = "0.2" +cpal = "0.14" + +[dependencies.web-sys] +version = "0.3.22" +features = ["console"] + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so it's only enabled +# in debug mode. +[target."cfg(debug_assertions)".dependencies] +console_error_panic_hook = "0.1.5" + +[features] +default = ["native"] +native = [] +wasm = [] diff --git a/lightningbeam-core/src/audio.rs b/lightningbeam-core/src/audio.rs new file mode 100644 index 0000000..16a7dba --- /dev/null +++ b/lightningbeam-core/src/audio.rs @@ -0,0 +1,105 @@ +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::{SampleFormat, SampleRate, Stream, StreamConfig}; +use cpal::{BufferSize, SupportedBufferSize}; +use std::sync::{Arc, Mutex}; +use crate::AudioOutput; + +pub struct CpalAudioOutput { + stream: Option, + buffer: Arc>>, // Shared buffer for audio chunks + sample_rate: u32, + channels: u16, +} + +impl CpalAudioOutput { + pub fn new(sample_rate: u32, channels: u16) -> Self { + Self { + stream: None, + buffer: Arc::new(Mutex::new(Vec::new())), + sample_rate, + channels, + } + } +} + +impl AudioOutput for CpalAudioOutput { + fn start(&mut self) -> Result<(), Box> { + let host = cpal::default_host(); + let device = host + .default_output_device() + .ok_or("No output device available")?; + let supported_config = device + .default_output_config().unwrap(); + // .with_sample_rate(SampleRate(self.sample_rate)); + let config = StreamConfig { + channels: self.channels, + sample_rate: SampleRate(self.sample_rate), + buffer_size: match supported_config.buffer_size() { + SupportedBufferSize::Range { min, max: _ } => BufferSize::Fixed(*min), + SupportedBufferSize::Unknown => BufferSize::Default, + }, + }; + + let buffer = self.buffer.clone(); + let sample_format = supported_config.sample_format(); + + let stream = match sample_format { + SampleFormat::F32 => device.build_output_stream( + &config, + move |data: &mut [f32], _| { + let mut buffer = buffer.lock().unwrap(); + for (out_sample, buffer_sample) in data.iter_mut().zip(buffer.iter()) { + *out_sample = *buffer_sample; + } + buffer.clear(); // Clear buffer after playback + }, + move |err| { + eprintln!("Audio stream error: {:?}", err); + }, + ), + SampleFormat::I16 => device.build_output_stream( + &config, + move |data: &mut [i16], _| { + let mut buffer = buffer.lock().unwrap(); + for (out_sample, buffer_sample) in data.iter_mut().zip(buffer.iter()) { + *out_sample = (*buffer_sample * i16::MAX as f32) as i16; + } + buffer.clear(); + }, + move |err| { + eprintln!("Audio stream error: {:?}", err); + }, + ), + SampleFormat::U16 => device.build_output_stream( + &config, + move |data: &mut [u16], _| { + let mut buffer = buffer.lock().unwrap(); + for (out_sample, buffer_sample) in data.iter_mut().zip(buffer.iter()) { + *out_sample = ((*buffer_sample + 1.0) * 0.5 * u16::MAX as f32) as u16; + } + buffer.clear(); + }, + move |err| { + eprintln!("Audio stream error: {:?}", err); + }, + ), + }; + + // If the stream creation failed, return the error + let stream = stream.map_err(|e| { + format!( + "Failed to build output stream for sample format {:?}: {:?}", + sample_format, e + ) + })?; + + stream.play()?; + self.stream = Some(stream); + Ok(()) + } + + fn play_chunk(&mut self, chunk: Vec) { + let mut buffer = self.buffer.lock().unwrap(); + buffer.extend(chunk); + } +} \ No newline at end of file diff --git a/lightningbeam-core/src/lib.rs b/lightningbeam-core/src/lib.rs new file mode 100644 index 0000000..a3d36e2 --- /dev/null +++ b/lightningbeam-core/src/lib.rs @@ -0,0 +1,111 @@ +mod time; +use time::{Timestamp, Duration, Frame}; +mod audio; +use audio::{CpalAudioOutput}; + +#[cfg(feature = "wasm")] +use wasm_bindgen::prelude::*; + +pub trait AudioTrack { + /// Render a chunk of audio for the given timestamp and duration. + fn render_chunk(&self, timestamp: Timestamp, duration: Duration) -> Vec; + + /// Get the sample rate of the audio track. + fn sample_rate(&self) -> u32; +} + +pub trait VideoTrack { + /// Render a frame for the given timestamp. + fn render_frame(&self, timestamp: Timestamp) -> Frame; + + /// Get the frame rate of the video track. + fn frame_rate(&self) -> f64; +} + +pub struct TrackManager { + audio_tracks: Vec>, + video_tracks: Vec>, + sample_rate: u32, + frame_duration: Duration, // Duration of each frame in seconds (e.g., 1/60 for 60 FPS) +} + +impl TrackManager { + pub fn new(sample_rate: u32, frame_duration: f64) -> Self { + Self { + audio_tracks: Vec::new(), + video_tracks: Vec::new(), + sample_rate, + frame_duration: Duration::new(frame_duration), + } + } + + pub fn add_audio_track(&mut self, track: Box) { + self.audio_tracks.push(track); + } + + pub fn add_video_track(&mut self, track: Box) { + self.video_tracks.push(track); + } + + pub fn play(&mut self, timestamp: Timestamp, audio_output: &mut dyn AudioOutput, video_output: &mut dyn FrameTarget) { + let mut timestamp = timestamp.clone(); + + // Main playback loop + loop { + // Render and play audio chunks + let mut audio_mix: Vec = vec![0.0; self.frame_duration.to_samples(self.sample_rate) as usize]; + for track in &mut self.audio_tracks { + let chunk = track.render_chunk(timestamp, self.frame_duration); + for (i, sample) in chunk.iter().enumerate() { + audio_mix[i] += sample; // Simple mixing (sum of samples) + } + } + audio_output.play_chunk(audio_mix); + + // Render video frames + for track in &self.video_tracks { + let track_frame = track.render_frame(timestamp); + } + + // Update timestamp + timestamp += self.frame_duration; + + // Break condition (e.g., end of tracks) + if self.audio_tracks.iter().all(|t| t.render_chunk(timestamp, self.frame_duration).is_empty()) { + break; + } + } + } +} + +pub trait AudioOutput { + fn start(&mut self) -> Result<(), Box>; + fn play_chunk(&mut self, chunk: Vec); +} + +pub trait FrameTarget { + fn draw(&mut self, frame: &[u8], width: u32, height: u32); +} + +#[cfg(test)] +mod tests { + use super::*; + + // #[test] + // fn it_works() { + // let result = add(2, 2); + // assert_eq!(result, 4); + // } +} + +// This is like the `main` function, except for JavaScript. +#[cfg(feature="wasm")] +#[wasm_bindgen(start)] +pub fn main_js() -> Result<(), JsValue> { + // This provides better error messages in debug mode. + // It's disabled in release mode so it doesn't bloat up the file size. + #[cfg(debug_assertions)] + console_error_panic_hook::set_once(); + + Ok(()) +} \ No newline at end of file diff --git a/lightningbeam-core/src/time.rs b/lightningbeam-core/src/time.rs new file mode 100644 index 0000000..4f21a13 --- /dev/null +++ b/lightningbeam-core/src/time.rs @@ -0,0 +1,136 @@ +/// A strongly-typed representation of a timestamp (seconds). +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +pub struct Timestamp(f64); + +/// A strongly-typed representation of a duration (seconds). +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +pub struct Duration(f64); + +impl Timestamp { + /// Create a new timestamp in seconds. + pub fn new(seconds: f64) -> Self { + Timestamp(seconds) + } + + /// Create a new timestamp from milliseconds. + pub fn from_millis(milliseconds: u64) -> Self { + Timestamp(milliseconds as f64 / 1000.0) + } + + /// Get the value in seconds. + pub fn as_seconds(&self) -> f64 { + self.0 + } + + /// Get the value in milliseconds. + pub fn as_millis(&self) -> u64 { + (self.0 * 1000.0).round() as u64 + } + + /// Add a duration to a timestamp, producing a new timestamp. + pub fn add_duration(&self, duration: Duration) -> Timestamp { + Timestamp(self.0 + duration.0) + } + + /// Subtract a duration from a timestamp, producing a new timestamp. + pub fn subtract_duration(&self, duration: Duration) -> Timestamp { + Timestamp(self.0 - duration.0) + } + + /// Subtract another timestamp, producing a duration. + pub fn subtract_timestamp(&self, other: Timestamp) -> Duration { + Duration(self.0 - other.0) + } +} + +impl Duration { + /// Create a new duration in seconds. + pub fn new(seconds: f64) -> Self { + Duration(seconds) + } + + /// Create a new duration from milliseconds. + pub fn from_millis(milliseconds: u64) -> Self { + Duration(milliseconds as f64 / 1000.0) + } + + /// Create a new duration from frames, given a frame rate. + pub fn from_frames(frames: u64, frame_rate: f64) -> Self { + Duration(frames as f64 / frame_rate) + } + + /// Get the value in seconds. + pub fn as_seconds(&self) -> f64 { + self.0 + } + + /// Get the value in milliseconds. + pub fn as_millis(&self) -> u64 { + (self.0 * 1000.0).round() as u64 + } + + /// Get the number of frames for this duration, given a frame rate. + pub fn to_frames(&self, frame_rate: f64) -> u64 { + (self.0 * frame_rate).round() as u64 + } + + /// Get the number of samples in this duration at a given sample rate + pub fn to_samples(&self, sample_rate: u32) -> u64 { + (self.0 * sample_rate as f64).round() as u64 + } + + /// Add two durations together. + pub fn add(&self, other: Duration) -> Duration { + Duration(self.0 + other.0) + } + + /// Subtract one duration from another. + pub fn subtract(&self, other: Duration) -> Duration { + Duration(self.0 - other.0) + } +} + +// Overloading operators for more natural usage +use std::ops::{Add, Sub, AddAssign, SubAssign}; + +impl Add for Timestamp { + type Output = Timestamp; + + fn add(self, duration: Duration) -> Timestamp { + self.add_duration(duration) + } +} + +impl Sub for Timestamp { + type Output = Timestamp; + + fn sub(self, duration: Duration) -> Timestamp { + self.subtract_duration(duration) + } +} + +impl AddAssign for Timestamp { + fn add_assign(&mut self, duration: Duration) { + self.0 += duration.0; + } +} + +impl SubAssign for Timestamp { + fn sub_assign(&mut self, duration: Duration) { + self.0 -= duration.0; + } +} + +/// Represents a video frame. +#[derive(Debug, Clone)] +pub struct Frame { + pub width: u32, + pub height: u32, + pub data: Vec, // RGBA pixel data +} + +impl Frame { + pub fn new(width: u32, height: u32, data: Vec) -> Self { + Frame { width, height, data } + } +} From 98d6e82397d8a7cfd0e05f19acf10b160695bdd6 Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Sat, 25 Jan 2025 04:10:04 -0500 Subject: [PATCH 2/7] More work on Rust --- lightningbeam-core/Cargo.lock | 458 +++++++++++--------------------- lightningbeam-core/Cargo.toml | 7 +- lightningbeam-core/build.sh | 3 + lightningbeam-core/src/audio.rs | 191 +++++++------ lightningbeam-core/src/lib.rs | 215 ++++++++++++--- lightningbeam-core/src/time.rs | 29 ++ 6 files changed, 483 insertions(+), 420 deletions(-) create mode 100755 lightningbeam-core/build.sh diff --git a/lightningbeam-core/Cargo.lock b/lightningbeam-core/Cargo.lock index 9db978d..d3c70be 100644 --- a/lightningbeam-core/Cargo.lock +++ b/lightningbeam-core/Cargo.lock @@ -13,14 +13,14 @@ dependencies = [ [[package]] name = "alsa" -version = "0.6.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5915f52fe2cf65e83924d037b6c5290b7cee097c6b5c8700746e6168a343fd6b" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", - "bitflags 1.3.2", + "bitflags 2.8.0", + "cfg-if", "libc", - "nix", ] [[package]] @@ -33,18 +33,18 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - [[package]] name = "bindgen" version = "0.70.1" @@ -60,7 +60,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.96", + "syn", ] [[package]] @@ -158,11 +158,12 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "coreaudio-rs" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" dependencies = [ "bitflags 1.3.2", + "core-foundation-sys", "coreaudio-sys", ] @@ -177,33 +178,32 @@ dependencies = [ [[package]] name = "cpal" -version = "0.14.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f342c1b63e185e9953584ff2199726bf53850d96610a310e3aca09e9405a2d0b" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" dependencies = [ "alsa", "core-foundation-sys", "coreaudio-rs", + "dasp_sample", "jni", "js-sys", "libc", - "mach", - "ndk 0.7.0", + "mach2", + "ndk", "ndk-context", "oboe", - "once_cell", - "parking_lot", - "stdweb", - "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", "windows", ] [[package]] -name = "discard" -version = "1.0.4" +name = "dasp_sample" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" [[package]] name = "either" @@ -248,24 +248,20 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - [[package]] name = "jni" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", "jni-sys", "log", "thiserror", "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -306,30 +302,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] name = "lightningbeam-core" version = "0.1.0" dependencies = [ + "anyhow", "console_error_panic_hook", "cpal", + "log", "serde", "wasm-bindgen", + "wasm-logger", "web-sys", ] -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.25" @@ -337,10 +326,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] -name = "mach" -version = "0.3.2" +name = "mach2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" dependencies = [ "libc", ] @@ -351,15 +340,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -368,31 +348,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "ndk" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.8.0", "jni-sys", - "ndk-sys 0.3.0", + "log", + "ndk-sys", "num_enum", "thiserror", ] -[[package]] -name = "ndk" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" -dependencies = [ - "bitflags 1.3.2", - "jni-sys", - "ndk-sys 0.4.1+23.1.7779620", - "num_enum", - "raw-window-handle", - "thiserror", -] - [[package]] name = "ndk-context" version = "0.1.1" @@ -401,35 +368,13 @@ checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] name = "ndk-sys" -version = "0.3.0" +version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ "jni-sys", ] -[[package]] -name = "ndk-sys" -version = "0.4.1+23.1.7779620" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "nix" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" -dependencies = [ - "bitflags 1.3.2", - "cc", - "cfg-if", - "libc", - "memoffset", -] - [[package]] name = "nom" version = "7.1.3" @@ -442,13 +387,13 @@ dependencies = [ [[package]] name = "num-derive" -version = "0.3.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -462,33 +407,33 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.11" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.11" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "oboe" -version = "0.4.6" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f63c358b4fa0fbcfefd7c8be5cfc39c08ce2389f5325687e7762a48d30a5c1" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" dependencies = [ "jni", - "ndk 0.6.0", + "ndk", "ndk-context", "num-derive", "num-traits", @@ -497,9 +442,9 @@ dependencies = [ [[package]] name = "oboe-sys" -version = "0.4.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" dependencies = [ "cc", ] @@ -510,29 +455,6 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - [[package]] name = "pkg-config" version = "0.3.31" @@ -567,21 +489,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags 2.8.0", -] - [[package]] name = "regex" version = "1.11.1" @@ -617,27 +524,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - [[package]] name = "rustversion" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - [[package]] name = "same-file" version = "1.0.6" @@ -647,27 +539,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.217" @@ -685,108 +556,15 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] -[[package]] -name = "serde_json" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.96" @@ -815,7 +593,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -873,10 +651,23 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.96", + "syn", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -895,7 +686,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -909,6 +700,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-logger" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -925,20 +727,45 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "windows" -version = "0.37.0" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" dependencies = [ - "windows_aarch64_msvc 0.37.0", - "windows_i686_gnu 0.37.0", - "windows_i686_msvc 0.37.0", - "windows_x86_64_gnu 0.37.0", - "windows_x86_64_msvc 0.37.0", + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -947,7 +774,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -956,16 +798,22 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -974,9 +822,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.37.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -986,9 +834,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.37.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -1004,9 +852,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.37.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -1016,9 +864,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.37.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -1026,6 +874,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -1034,9 +888,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.37.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" diff --git a/lightningbeam-core/Cargo.toml b/lightningbeam-core/Cargo.toml index d34a84e..a6badae 100644 --- a/lightningbeam-core/Cargo.toml +++ b/lightningbeam-core/Cargo.toml @@ -8,8 +8,11 @@ crate-type = ["cdylib", "rlib"] [dependencies] serde = { version = "1.0", features = ["derive"] } -wasm-bindgen = "0.2" -cpal = "0.14" +wasm-bindgen = "0.2.45" +cpal = { version = "0.15", features = ["wasm-bindgen"] } +anyhow = "1.0" +wasm-logger = "0.2" +log = "0.4" [dependencies.web-sys] version = "0.3.22" diff --git a/lightningbeam-core/build.sh b/lightningbeam-core/build.sh new file mode 100755 index 0000000..efac65a --- /dev/null +++ b/lightningbeam-core/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd core +wasm-pack build --target web --out-dir ../src/pkg --features wasm diff --git a/lightningbeam-core/src/audio.rs b/lightningbeam-core/src/audio.rs index 16a7dba..4166a88 100644 --- a/lightningbeam-core/src/audio.rs +++ b/lightningbeam-core/src/audio.rs @@ -1,25 +1,93 @@ use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use cpal::{SampleFormat, SampleRate, Stream, StreamConfig}; -use cpal::{BufferSize, SupportedBufferSize}; +use cpal::{Sample, SampleFormat, StreamConfig, SupportedBufferSize, SampleRate, BufferSize}; use std::sync::{Arc, Mutex}; -use crate::AudioOutput; +use crate::{TrackManager, Timestamp, Duration, SampleCount, AudioOutput, PlaybackState}; + +// #[cfg(feature = "wasm")] +// use wasm_bindgen::prelude::*; + +// #[cfg(feature="wasm")] +// #[wasm_bindgen] pub struct CpalAudioOutput { - stream: Option, - buffer: Arc>>, // Shared buffer for audio chunks + track_manager: Option>>, + _stream: Option, + playback_state: PlaybackState, + timestamp: Arc>, + chunk_size: usize, sample_rate: u32, - channels: u16, } +// #[cfg(feature="wasm")] +// #[wasm_bindgen] impl CpalAudioOutput { - pub fn new(sample_rate: u32, channels: u16) -> Self { + pub fn new() -> Self { Self { - stream: None, - buffer: Arc::new(Mutex::new(Vec::new())), - sample_rate, - channels, + track_manager: None, + _stream: None, + playback_state: PlaybackState::Stopped, + timestamp: Arc::new(Mutex::new(Timestamp::from_seconds(0.0))), + chunk_size: 0, + sample_rate: 44100, // Default sample rate, updated later } } + + fn build_stream( + &mut self, + device: &cpal::Device, + config: cpal::SupportedStreamConfig, +) -> Result +where + T: Sample + From + cpal::SizedSample, +{ + let supported_config = config.config(); + self.sample_rate = supported_config.sample_rate.0; + let num_channels = supported_config.channels as usize; // Get channel count + + let track_manager = self.track_manager.clone(); + let playback_state = self.playback_state.clone(); + let timestamp = self.timestamp.clone(); + let sample_rate = self.sample_rate; + + let err_fn = |err| eprintln!("Audio stream error: {:?}", err); + + let stream = device.build_output_stream( + &supported_config, + move |data: &mut [T], _: &cpal::OutputCallbackInfo| { + if let Some(track_manager) = &track_manager { + let num_frames = data.len() / num_channels; // Stereo: divide by 2 + let sample_count = SampleCount::new(num_frames); + let chunk_duration = Duration::new(num_frames as f64 / sample_rate as f64); + + let mut track_manager = track_manager.lock().unwrap(); + let playing = matches!(playback_state, PlaybackState::Playing); + + let mut timestamp_guard = timestamp.lock().unwrap(); + let timestamp = &mut *timestamp_guard; + + let chunk = track_manager.update_audio( + timestamp.clone(), + playing, + sample_count, + sample_rate, + ); + + // Write samples (interleaved stereo) + for (i, frame) in chunk.iter().enumerate() { + let sample = T::from(*frame); + data[i * num_channels] = sample; // Left channel + data[i * num_channels + 1] = sample; // Right channel (or process separately) + } + + *timestamp_guard += chunk_duration; + } + }, + err_fn, + None, + )?; + + Ok(stream) + } } impl AudioOutput for CpalAudioOutput { @@ -27,79 +95,34 @@ impl AudioOutput for CpalAudioOutput { let host = cpal::default_host(); let device = host .default_output_device() - .ok_or("No output device available")?; - let supported_config = device - .default_output_config().unwrap(); - // .with_sample_rate(SampleRate(self.sample_rate)); - let config = StreamConfig { - channels: self.channels, - sample_rate: SampleRate(self.sample_rate), - buffer_size: match supported_config.buffer_size() { - SupportedBufferSize::Range { min, max: _ } => BufferSize::Fixed(*min), - SupportedBufferSize::Unknown => BufferSize::Default, - }, - }; - - let buffer = self.buffer.clone(); - let sample_format = supported_config.sample_format(); - - let stream = match sample_format { - SampleFormat::F32 => device.build_output_stream( - &config, - move |data: &mut [f32], _| { - let mut buffer = buffer.lock().unwrap(); - for (out_sample, buffer_sample) in data.iter_mut().zip(buffer.iter()) { - *out_sample = *buffer_sample; - } - buffer.clear(); // Clear buffer after playback - }, - move |err| { - eprintln!("Audio stream error: {:?}", err); - }, - ), - SampleFormat::I16 => device.build_output_stream( - &config, - move |data: &mut [i16], _| { - let mut buffer = buffer.lock().unwrap(); - for (out_sample, buffer_sample) in data.iter_mut().zip(buffer.iter()) { - *out_sample = (*buffer_sample * i16::MAX as f32) as i16; - } - buffer.clear(); - }, - move |err| { - eprintln!("Audio stream error: {:?}", err); - }, - ), - SampleFormat::U16 => device.build_output_stream( - &config, - move |data: &mut [u16], _| { - let mut buffer = buffer.lock().unwrap(); - for (out_sample, buffer_sample) in data.iter_mut().zip(buffer.iter()) { - *out_sample = ((*buffer_sample + 1.0) * 0.5 * u16::MAX as f32) as u16; - } - buffer.clear(); - }, - move |err| { - eprintln!("Audio stream error: {:?}", err); - }, - ), - }; - - // If the stream creation failed, return the error - let stream = stream.map_err(|e| { - format!( - "Failed to build output stream for sample format {:?}: {:?}", - sample_format, e - ) - })?; - - stream.play()?; - self.stream = Some(stream); + .ok_or_else(|| "No output device available")?; + let supported_config = device.default_output_config()?; + self._stream = Some(self.build_stream::(&device, supported_config)?); + if let Some(stream) = self._stream.as_ref() { + stream.play().unwrap(); + } else { + eprintln!("Stream is not initialized!"); + } Ok(()) } - - fn play_chunk(&mut self, chunk: Vec) { - let mut buffer = self.buffer.lock().unwrap(); - buffer.extend(chunk); + + fn play(&mut self, start_timestamp: Timestamp) { + self.timestamp.lock().unwrap().set(start_timestamp); + self.playback_state = PlaybackState::Playing; } -} \ No newline at end of file + + fn stop(&mut self) { + self.playback_state = PlaybackState::Stopped; + } + + fn register_track_manager(&mut self, track_manager: Arc>) { + self.track_manager = Some(track_manager); + } + + fn get_timestamp(&mut self) -> Timestamp { + *self.timestamp.lock().unwrap() + } + fn set_chunk_size(&mut self, chunk_size: usize) { + self.chunk_size = chunk_size + } +} diff --git a/lightningbeam-core/src/lib.rs b/lightningbeam-core/src/lib.rs index a3d36e2..0912adc 100644 --- a/lightningbeam-core/src/lib.rs +++ b/lightningbeam-core/src/lib.rs @@ -1,20 +1,30 @@ mod time; -use time::{Timestamp, Duration, Frame}; +use time::{Timestamp, Duration, Frame, SampleCount}; mod audio; use audio::{CpalAudioOutput}; +use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError}; + +use std::sync::{Arc, Mutex}; #[cfg(feature = "wasm")] -use wasm_bindgen::prelude::*; +mod wasm_imports { + pub use wasm_bindgen::prelude::*; + pub use web_sys::console; + use wasm_logger; +} +#[cfg(feature = "wasm")] +use wasm_imports::*; -pub trait AudioTrack { + +pub trait AudioTrack: Send { /// Render a chunk of audio for the given timestamp and duration. - fn render_chunk(&self, timestamp: Timestamp, duration: Duration) -> Vec; + fn render_chunk(&mut self, timestamp: Timestamp, duration: SampleCount) -> Vec; /// Get the sample rate of the audio track. fn sample_rate(&self) -> u32; } -pub trait VideoTrack { +pub trait VideoTrack: Send { /// Render a frame for the given timestamp. fn render_frame(&self, timestamp: Timestamp) -> Frame; @@ -25,8 +35,10 @@ pub trait VideoTrack { pub struct TrackManager { audio_tracks: Vec>, video_tracks: Vec>, - sample_rate: u32, - frame_duration: Duration, // Duration of each frame in seconds (e.g., 1/60 for 60 FPS) + // sample_rate: u32, + // frame_duration: Duration, // Duration of each frame in seconds (e.g., 1/60 for 60 FPS) + timestamp: Timestamp, + playback_state: PlaybackState, } impl TrackManager { @@ -34,8 +46,10 @@ impl TrackManager { Self { audio_tracks: Vec::new(), video_tracks: Vec::new(), - sample_rate, - frame_duration: Duration::new(frame_duration), + // sample_rate, + // frame_duration: Duration::new(frame_duration), + playback_state: PlaybackState::Stopped, + timestamp: Timestamp::from_seconds(0.0), } } @@ -46,47 +60,180 @@ impl TrackManager { pub fn add_video_track(&mut self, track: Box) { self.video_tracks.push(track); } + + pub fn play(&mut self, start_timestamp: Timestamp) { + self.timestamp = start_timestamp; + self.playback_state = PlaybackState::Playing; + } + + pub fn stop(&mut self) { + self.playback_state = PlaybackState::Stopped; + } - pub fn play(&mut self, timestamp: Timestamp, audio_output: &mut dyn AudioOutput, video_output: &mut dyn FrameTarget) { - let mut timestamp = timestamp.clone(); + // pub fn play(&mut self, timestamp: Timestamp, audio_output: &mut dyn AudioOutput, video_output: &mut dyn FrameTarget) { + // let mut timestamp = timestamp.clone(); - // Main playback loop - loop { - // Render and play audio chunks - let mut audio_mix: Vec = vec![0.0; self.frame_duration.to_samples(self.sample_rate) as usize]; - for track in &mut self.audio_tracks { - let chunk = track.render_chunk(timestamp, self.frame_duration); - for (i, sample) in chunk.iter().enumerate() { - audio_mix[i] += sample; // Simple mixing (sum of samples) - } - } - audio_output.play_chunk(audio_mix); + // // Main playback loop + // loop { + // // Render and play audio chunks + // let mut audio_mix: Vec = vec![0.0; self.frame_duration.to_samples(self.sample_rate) as usize]; + // for track in &mut self.audio_tracks { + // let chunk = track.render_chunk(timestamp, self.frame_duration); + // for (i, sample) in chunk.iter().enumerate() { + // audio_mix[i] += sample; // Simple mixing (sum of samples) + // } + // } + // audio_output.play_chunk(audio_mix); - // Render video frames - for track in &self.video_tracks { - let track_frame = track.render_frame(timestamp); - } + // // Render video frames + // for track in &self.video_tracks { + // let track_frame = track.render_frame(timestamp); + // } - // Update timestamp - timestamp += self.frame_duration; + // // Update timestamp + // timestamp += self.frame_duration; - // Break condition (e.g., end of tracks) - if self.audio_tracks.iter().all(|t| t.render_chunk(timestamp, self.frame_duration).is_empty()) { - break; + // // Break condition (e.g., end of tracks) + // if self.audio_tracks.iter().all(|t| t.render_chunk(timestamp, self.frame_duration).is_empty()) { + // break; + // } + // } + // } + pub fn update_audio(&mut self, timestamp: Timestamp, playing: bool, chunk_size: SampleCount, sample_rate: u32) -> Vec { + let mut mixed_audio = vec![0.0; chunk_size.as_usize()]; + + // TODO: render video + for track in &mut self.audio_tracks { + let track_audio = track.render_chunk(timestamp, chunk_size); + for (i, sample) in track_audio.iter().enumerate() { + mixed_audio[i] += *sample; // Simple mixing, add samples together } } + + mixed_audio } } pub trait AudioOutput { fn start(&mut self) -> Result<(), Box>; - fn play_chunk(&mut self, chunk: Vec); + fn play(&mut self, start_timestamp: Timestamp); + fn stop(&mut self); + fn register_track_manager(&mut self, track_manager: Arc>); + fn get_timestamp(&mut self) -> Timestamp; + fn set_chunk_size(&mut self, chunk_size: usize); } pub trait FrameTarget { fn draw(&mut self, frame: &[u8], width: u32, height: u32); } +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +enum PlaybackState { + Playing, + Stopped, +} + +pub struct SineWaveTrack { + frequency: f32, + phase: f32, + sample_rate: u32, +} + +impl SineWaveTrack { + pub fn new(frequency: f32, sample_rate: u32) -> Self { + Self { + frequency, + phase: 0.0, + sample_rate, + } + } +} + +impl AudioTrack for SineWaveTrack { + fn render_chunk(&mut self, timestamp: Timestamp, chunk_size: SampleCount) -> Vec { + let mut chunk = Vec::with_capacity(chunk_size.as_usize()); + let phase_increment = (2.0 * std::f32::consts::PI * self.frequency) / self.sample_rate as f32; + + for _ in 0..chunk_size.as_usize() { + chunk.push((self.phase).sin()); + self.phase += phase_increment; + if self.phase > 2.0 * std::f32::consts::PI { + self.phase -= 2.0 * std::f32::consts::PI; + } + } + + chunk + } + fn sample_rate(&self) -> u32 { + self.sample_rate + } +} + + +#[cfg(feature="wasm")] +#[wasm_bindgen] +pub struct CoreInterface { + #[wasm_bindgen(skip)] + track_manager: Arc>, + #[wasm_bindgen(skip)] + cpal_audio_output: Box, +} + +#[cfg(feature="wasm")] +#[wasm_bindgen] +impl CoreInterface { + #[wasm_bindgen(constructor)] + pub fn new(sample_rate: u32, frame_duration: f64) -> Self { + Self { + track_manager: Arc::new(Mutex::new(TrackManager::new(sample_rate, frame_duration))), + cpal_audio_output: Box::new(CpalAudioOutput::new()) + } + } + pub fn init(&mut self) { + println!("Init CoreInterface"); + let track_manager_clone = self.track_manager.clone(); + self.cpal_audio_output.register_track_manager(track_manager_clone); + self.cpal_audio_output.start(); + } + pub fn play(&mut self, timestamp: f64) { + // Lock the Mutex to get access to TrackManager + let mut track_manager = self.track_manager.lock().unwrap(); + track_manager.play(Timestamp::new(timestamp)); + } + pub fn get_timestamp(&mut self) -> f64 { + self.cpal_audio_output.get_timestamp().as_seconds() + } +} + + +struct PlainTextLogger; + +impl Log for PlainTextLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Info + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + console::log_1(&format!( + "{} [{}:{}] {}", + record.level(), + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ).into()); + } + } + + fn flush(&self) {} +} + +pub fn init_plain_text_logger() -> Result<(), SetLoggerError> { + log::set_boxed_logger(Box::new(PlainTextLogger))?; + log::set_max_level(LevelFilter::Info); + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -106,6 +253,10 @@ pub fn main_js() -> Result<(), JsValue> { // It's disabled in release mode so it doesn't bloat up the file size. #[cfg(debug_assertions)] console_error_panic_hook::set_once(); + init_plain_text_logger().expect("Failed to initialize plain text logger"); + + + log::info!("Logger initialized!"); Ok(()) } \ No newline at end of file diff --git a/lightningbeam-core/src/time.rs b/lightningbeam-core/src/time.rs index 4f21a13..c014ced 100644 --- a/lightningbeam-core/src/time.rs +++ b/lightningbeam-core/src/time.rs @@ -6,12 +6,21 @@ pub struct Timestamp(f64); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] pub struct Duration(f64); +/// A strongly-typed representation of a number of samples. +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +pub struct SampleCount(usize); + impl Timestamp { /// Create a new timestamp in seconds. pub fn new(seconds: f64) -> Self { Timestamp(seconds) } + /// Create a new timestamp from seconds. (dummy method) + pub fn from_seconds(seconds: f64) -> Self { + Timestamp(seconds) + } + /// Create a new timestamp from milliseconds. pub fn from_millis(milliseconds: u64) -> Self { Timestamp(milliseconds as f64 / 1000.0) @@ -41,6 +50,10 @@ impl Timestamp { pub fn subtract_timestamp(&self, other: Timestamp) -> Duration { Duration(self.0 - other.0) } + + pub fn set(&mut self, other: Timestamp) { + self.0 = other.as_seconds(); + } } impl Duration { @@ -79,6 +92,10 @@ impl Duration { (self.0 * sample_rate as f64).round() as u64 } + pub fn to_std(&self) -> std::time::Duration { + std::time::Duration::from_nanos((self.0/1_000_000_000.0).round() as u64) + } + /// Add two durations together. pub fn add(&self, other: Duration) -> Duration { Duration(self.0 + other.0) @@ -90,6 +107,18 @@ impl Duration { } } + +impl SampleCount { + /// Create a new count of samples. + pub fn new(samples: usize) -> Self { + SampleCount(samples) + } + + pub fn as_usize(&self) -> usize { + self.0 + } +} + // Overloading operators for more natural usage use std::ops::{Add, Sub, AddAssign, SubAssign}; From cdd1ff2cbf2f4d9aa2927e40324da96817cca247 Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Sat, 25 Jan 2025 05:39:57 -0500 Subject: [PATCH 3/7] sound is playing! --- lightningbeam-core/src/audio.rs | 22 +- lightningbeam-core/src/lib.rs | 288 ++++++----- src/main.js | 13 + src/pkg/lightningbeam_core.d.ts | 66 +++ src/pkg/lightningbeam_core.js | 655 ++++++++++++++++++++++++ src/pkg/lightningbeam_core_bg.wasm | Bin 0 -> 135753 bytes src/pkg/lightningbeam_core_bg.wasm.d.ts | 23 + src/pkg/package.json | 14 + 8 files changed, 952 insertions(+), 129 deletions(-) create mode 100644 src/pkg/lightningbeam_core.d.ts create mode 100644 src/pkg/lightningbeam_core.js create mode 100644 src/pkg/lightningbeam_core_bg.wasm create mode 100644 src/pkg/lightningbeam_core_bg.wasm.d.ts create mode 100644 src/pkg/package.json diff --git a/lightningbeam-core/src/audio.rs b/lightningbeam-core/src/audio.rs index 4166a88..9dc806f 100644 --- a/lightningbeam-core/src/audio.rs +++ b/lightningbeam-core/src/audio.rs @@ -1,5 +1,5 @@ use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use cpal::{Sample, SampleFormat, StreamConfig, SupportedBufferSize, SampleRate, BufferSize}; +use cpal::{Sample}; use std::sync::{Arc, Mutex}; use crate::{TrackManager, Timestamp, Duration, SampleCount, AudioOutput, PlaybackState}; @@ -44,15 +44,29 @@ where self.sample_rate = supported_config.sample_rate.0; let num_channels = supported_config.channels as usize; // Get channel count + let buffer_size_range = match config.buffer_size() { + cpal::SupportedBufferSize::Range { min, max } => (*min, *max), + cpal::SupportedBufferSize::Unknown => { + // Use a reasonable default range if the device doesn't specify + (256, 4096) + } + }; + + // Define the desired buffer size and clamp it to the supported range + let desired_buffer_size = 2048; + let clamped_buffer_size = desired_buffer_size.clamp(buffer_size_range.0, buffer_size_range.1); + + let mut stream_config = supported_config.clone(); + stream_config.buffer_size = cpal::BufferSize::Fixed(clamped_buffer_size); + let track_manager = self.track_manager.clone(); - let playback_state = self.playback_state.clone(); let timestamp = self.timestamp.clone(); let sample_rate = self.sample_rate; let err_fn = |err| eprintln!("Audio stream error: {:?}", err); let stream = device.build_output_stream( - &supported_config, + &stream_config, move |data: &mut [T], _: &cpal::OutputCallbackInfo| { if let Some(track_manager) = &track_manager { let num_frames = data.len() / num_channels; // Stereo: divide by 2 @@ -60,14 +74,12 @@ where let chunk_duration = Duration::new(num_frames as f64 / sample_rate as f64); let mut track_manager = track_manager.lock().unwrap(); - let playing = matches!(playback_state, PlaybackState::Playing); let mut timestamp_guard = timestamp.lock().unwrap(); let timestamp = &mut *timestamp_guard; let chunk = track_manager.update_audio( timestamp.clone(), - playing, sample_count, sample_rate, ); diff --git a/lightningbeam-core/src/lib.rs b/lightningbeam-core/src/lib.rs index 0912adc..b99a592 100644 --- a/lightningbeam-core/src/lib.rs +++ b/lightningbeam-core/src/lib.rs @@ -5,112 +5,89 @@ use audio::{CpalAudioOutput}; use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError}; use std::sync::{Arc, Mutex}; +use std::fmt; #[cfg(feature = "wasm")] mod wasm_imports { - pub use wasm_bindgen::prelude::*; - pub use web_sys::console; - use wasm_logger; + pub use wasm_bindgen::prelude::*; + pub use web_sys::console; } #[cfg(feature = "wasm")] use wasm_imports::*; -pub trait AudioTrack: Send { - /// Render a chunk of audio for the given timestamp and duration. - fn render_chunk(&mut self, timestamp: Timestamp, duration: SampleCount) -> Vec; +pub trait Track: Send { + fn get_name(&self) -> &str { + "Unnamed Track" + } - /// Get the sample rate of the audio track. - fn sample_rate(&self) -> u32; -} - -pub trait VideoTrack: Send { - /// Render a frame for the given timestamp. - fn render_frame(&self, timestamp: Timestamp) -> Frame; - - /// Get the frame rate of the video track. - fn frame_rate(&self) -> f64; + fn set_name(&mut self, _name: String) { + } + /// Render audio for the given timestamp and duration. + /// Returns `None` if this track doesn't produce audio. + fn render_audio(&mut self, _timestamp: Timestamp, _duration: SampleCount, _sample_rate: u32, _playing: bool) -> Option> { + None + } + + /// Render a video frame for the given timestamp. + /// Returns `None` if this track doesn't produce video. + fn render_video(&self, _timestamp: Timestamp, _playing: bool) -> Option { + None + } } pub struct TrackManager { - audio_tracks: Vec>, - video_tracks: Vec>, - // sample_rate: u32, - // frame_duration: Duration, // Duration of each frame in seconds (e.g., 1/60 for 60 FPS) + tracks: Vec>, timestamp: Timestamp, playback_state: PlaybackState, } impl TrackManager { - pub fn new(sample_rate: u32, frame_duration: f64) -> Self { + pub fn new() -> Self { Self { - audio_tracks: Vec::new(), - video_tracks: Vec::new(), - // sample_rate, - // frame_duration: Duration::new(frame_duration), - playback_state: PlaybackState::Stopped, + tracks: Vec::new(), timestamp: Timestamp::from_seconds(0.0), + playback_state: PlaybackState::Stopped, } } - pub fn add_audio_track(&mut self, track: Box) { - self.audio_tracks.push(track); + pub fn add_track(&mut self, track: Box) { + self.tracks.push(track); } - pub fn add_video_track(&mut self, track: Box) { - self.video_tracks.push(track); + pub fn update_audio(&mut self, timestamp: Timestamp, chunk_size: SampleCount, sample_rate: u32) -> Vec { + + let mut mixed = vec![0.0; chunk_size.as_usize()]; + let playing = matches!(self.playback_state, PlaybackState::Playing); + + for track in &mut self.tracks { + if let Some(samples) = track.render_audio(timestamp, chunk_size, sample_rate, playing) { + for (i, sample) in samples.iter().enumerate() { + mixed[i] += *sample; + } + } + } + + mixed } - + + pub fn update_video(&self, timestamp: Timestamp) -> Vec { + let playing = matches!(self.playback_state, PlaybackState::Playing); + self.tracks + .iter() + .filter_map(|track| track.render_video(timestamp, playing)) + .collect() + } + pub fn play(&mut self, start_timestamp: Timestamp) { self.timestamp = start_timestamp; self.playback_state = PlaybackState::Playing; } - pub fn stop(&mut self) { - self.playback_state = PlaybackState::Stopped; + self.playback_state = PlaybackState::Stopped; } - - // pub fn play(&mut self, timestamp: Timestamp, audio_output: &mut dyn AudioOutput, video_output: &mut dyn FrameTarget) { - // let mut timestamp = timestamp.clone(); - - // // Main playback loop - // loop { - // // Render and play audio chunks - // let mut audio_mix: Vec = vec![0.0; self.frame_duration.to_samples(self.sample_rate) as usize]; - // for track in &mut self.audio_tracks { - // let chunk = track.render_chunk(timestamp, self.frame_duration); - // for (i, sample) in chunk.iter().enumerate() { - // audio_mix[i] += sample; // Simple mixing (sum of samples) - // } - // } - // audio_output.play_chunk(audio_mix); - - // // Render video frames - // for track in &self.video_tracks { - // let track_frame = track.render_frame(timestamp); - // } - - // // Update timestamp - // timestamp += self.frame_duration; - - // // Break condition (e.g., end of tracks) - // if self.audio_tracks.iter().all(|t| t.render_chunk(timestamp, self.frame_duration).is_empty()) { - // break; - // } - // } - // } - pub fn update_audio(&mut self, timestamp: Timestamp, playing: bool, chunk_size: SampleCount, sample_rate: u32) -> Vec { - let mut mixed_audio = vec![0.0; chunk_size.as_usize()]; - - // TODO: render video - for track in &mut self.audio_tracks { - let track_audio = track.render_chunk(timestamp, chunk_size); - for (i, sample) in track_audio.iter().enumerate() { - mixed_audio[i] += *sample; // Simple mixing, add samples together - } - } - - mixed_audio + pub fn get_tracks(&self) -> &Vec> { + &self.tracks } } @@ -136,39 +113,75 @@ enum PlaybackState { pub struct SineWaveTrack { frequency: f32, phase: f32, - sample_rate: u32, + name: String, } impl SineWaveTrack { - pub fn new(frequency: f32, sample_rate: u32) -> Self { + pub fn new(frequency: f32) -> Self { Self { frequency, phase: 0.0, - sample_rate, + name: "Sine Wave Track".to_string(), } } } -impl AudioTrack for SineWaveTrack { - fn render_chunk(&mut self, timestamp: Timestamp, chunk_size: SampleCount) -> Vec { +impl Track for SineWaveTrack { + fn get_name(&self) -> &str { + &self.name + } + fn set_name(&mut self, name: String) { + self.name = name; + } + fn render_audio(&mut self, _timestamp: Timestamp, chunk_size: SampleCount, sample_rate: u32, playing: bool) -> Option> { let mut chunk = Vec::with_capacity(chunk_size.as_usize()); - let phase_increment = (2.0 * std::f32::consts::PI * self.frequency) / self.sample_rate as f32; - + let phase_increment = (2.0 * std::f32::consts::PI * self.frequency) / sample_rate as f32; + for _ in 0..chunk_size.as_usize() { - chunk.push((self.phase).sin()); - self.phase += phase_increment; - if self.phase > 2.0 * std::f32::consts::PI { - self.phase -= 2.0 * std::f32::consts::PI; - } + if playing { + chunk.push((self.phase).sin()*0.25); + } else { + chunk.push(0.0); + } + self.phase += phase_increment; + if self.phase > 2.0 * std::f32::consts::PI { + self.phase -= 2.0 * std::f32::consts::PI; + } } - - chunk - } - fn sample_rate(&self) -> u32 { - self.sample_rate + + Some(chunk) } } +#[cfg(feature="wasm")] +#[wasm_bindgen] +pub struct JsTrack { + name: String, +} + +#[cfg(feature="wasm")] +#[wasm_bindgen] +impl JsTrack { + #[wasm_bindgen(getter)] + pub fn name(&self) -> String { + self.name.clone() + } +} +#[cfg(feature="wasm")] +impl fmt::Display for JsTrack { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "JsTrack {{ name: {} }}", self.name) + } +} + +#[cfg(feature="wasm")] +#[wasm_bindgen] +impl JsTrack { + #[wasm_bindgen(js_name = toString)] + pub fn to_string(&self) -> String { + format!("{}", self) // Calls the Display implementation + } +} #[cfg(feature="wasm")] #[wasm_bindgen] @@ -183,9 +196,9 @@ pub struct CoreInterface { #[wasm_bindgen] impl CoreInterface { #[wasm_bindgen(constructor)] - pub fn new(sample_rate: u32, frame_duration: f64) -> Self { + pub fn new() -> Self { Self { - track_manager: Arc::new(Mutex::new(TrackManager::new(sample_rate, frame_duration))), + track_manager: Arc::new(Mutex::new(TrackManager::new())), cpal_audio_output: Box::new(CpalAudioOutput::new()) } } @@ -193,45 +206,72 @@ impl CoreInterface { println!("Init CoreInterface"); let track_manager_clone = self.track_manager.clone(); self.cpal_audio_output.register_track_manager(track_manager_clone); - self.cpal_audio_output.start(); + let _ = self.cpal_audio_output.start(); } pub fn play(&mut self, timestamp: f64) { // Lock the Mutex to get access to TrackManager let mut track_manager = self.track_manager.lock().unwrap(); track_manager.play(Timestamp::new(timestamp)); } + pub fn stop(&mut self) { + // Lock the Mutex to get access to TrackManager + let mut track_manager = self.track_manager.lock().unwrap(); + track_manager.stop(); + } + pub fn add_sine_track(&mut self, frequency: f32) -> Result<(), String> { + if frequency.is_nan() || frequency.is_infinite() || frequency <= 0.0 { + return Err(format!("Invalid frequency: {}", frequency)); + } + log::info!("Freq: {}", frequency); + let mut track_manager = self.track_manager.lock().unwrap(); + let sine_track = SineWaveTrack::new(frequency); + track_manager.add_track(Box::new(sine_track)); + + Ok(()) + } + pub fn get_timestamp(&mut self) -> f64 { self.cpal_audio_output.get_timestamp().as_seconds() } + pub fn get_tracks(&mut self) -> Vec { + let track_manager = self.track_manager.lock().unwrap(); + let tracks = track_manager.get_tracks(); + tracks + .iter() + .map(|track| JsTrack { + name: track.get_name().to_string(), + }) + .collect() + } } struct PlainTextLogger; impl Log for PlainTextLogger { - fn enabled(&self, metadata: &Metadata) -> bool { - metadata.level() <= Level::Info + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Info + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + console::log_1(&format!( + "{} [{}:{}] {}", + record.level(), + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ).into()); } - - fn log(&self, record: &Record) { - if self.enabled(record.metadata()) { - console::log_1(&format!( - "{} [{}:{}] {}", - record.level(), - record.file().unwrap_or("unknown"), - record.line().unwrap_or(0), - record.args() - ).into()); - } - } - - fn flush(&self) {} + } + + fn flush(&self) {} } pub fn init_plain_text_logger() -> Result<(), SetLoggerError> { - log::set_boxed_logger(Box::new(PlainTextLogger))?; - log::set_max_level(LevelFilter::Info); - Ok(()) + log::set_boxed_logger(Box::new(PlainTextLogger))?; + log::set_max_level(LevelFilter::Info); + Ok(()) } #[cfg(test)] @@ -249,14 +289,14 @@ mod tests { #[cfg(feature="wasm")] #[wasm_bindgen(start)] pub fn main_js() -> Result<(), JsValue> { - // This provides better error messages in debug mode. - // It's disabled in release mode so it doesn't bloat up the file size. - #[cfg(debug_assertions)] - console_error_panic_hook::set_once(); - init_plain_text_logger().expect("Failed to initialize plain text logger"); - - - log::info!("Logger initialized!"); - - Ok(()) + // This provides better error messages in debug mode. + // It's disabled in release mode so it doesn't bloat up the file size. + #[cfg(debug_assertions)] + console_error_panic_hook::set_once(); + init_plain_text_logger().expect("Failed to initialize plain text logger"); + + + log::info!("Logger initialized!"); + + Ok(()) } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 475b157..bb0bd79 100644 --- a/src/main.js +++ b/src/main.js @@ -78,6 +78,8 @@ const { Menu, MenuItem, PredefinedMenuItem, Submenu } = window.__TAURI__.menu; const { getCurrentWindow } = window.__TAURI__.window; const { getVersion } = window.__TAURI__.app; +import init, { CoreInterface } from './pkg/lightningbeam_core.js'; + window.onerror = (message, source, lineno, colno, error) => { invoke("error", { msg: `${message} at ${source}:${lineno}:${colno}\n${error?.stack || ''}` }); }; @@ -8408,3 +8410,14 @@ if (window.openedFiles?.length>0) { newWindow(window.openedFiles[i]) } } + +async function testAudio() { + console.log("Starting rust") + await init(); + console.log("Rust started") + const coreInterface = new CoreInterface(100, 100) + coreInterface.init() + coreInterface.play(0.0) + console.log(coreInterface) +} +testAudio() \ No newline at end of file diff --git a/src/pkg/lightningbeam_core.d.ts b/src/pkg/lightningbeam_core.d.ts new file mode 100644 index 0000000..87d6b82 --- /dev/null +++ b/src/pkg/lightningbeam_core.d.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +/* eslint-disable */ +export function main_js(): void; +export class CoreInterface { + free(): void; + constructor(); + init(): void; + play(timestamp: number): void; + stop(): void; + add_sine_track(frequency: number): void; + get_timestamp(): number; + get_tracks(): JsTrack[]; +} +export class JsTrack { + private constructor(); + free(): void; + toString(): string; + readonly name: string; +} + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly __wbg_jstrack_free: (a: number, b: number) => void; + readonly jstrack_name: (a: number, b: number) => void; + readonly jstrack_toString: (a: number, b: number) => void; + readonly __wbg_coreinterface_free: (a: number, b: number) => void; + readonly coreinterface_new: () => number; + readonly coreinterface_init: (a: number) => void; + readonly coreinterface_play: (a: number, b: number) => void; + readonly coreinterface_stop: (a: number) => void; + readonly coreinterface_add_sine_track: (a: number, b: number, c: number) => void; + readonly coreinterface_get_timestamp: (a: number) => number; + readonly coreinterface_get_tracks: (a: number, b: number) => void; + readonly main_js: () => void; + readonly __wbindgen_exn_store: (a: number) => void; + readonly __wbindgen_export_1: WebAssembly.Table; + readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; + readonly __wbindgen_add_to_stack_pointer: (a: number) => number; + readonly __wbindgen_free: (a: number, b: number, c: number) => void; + readonly _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h03a328ab39659ec3: (a: number, b: number) => void; + readonly __wbindgen_start: () => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; +/** +* Instantiates the given `module`, which can either be bytes or +* a precompiled `WebAssembly.Module`. +* +* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. +* +* @returns {InitOutput} +*/ +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. +* +* @returns {Promise} +*/ +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/src/pkg/lightningbeam_core.js b/src/pkg/lightningbeam_core.js new file mode 100644 index 0000000..0ef6268 --- /dev/null +++ b/src/pkg/lightningbeam_core.js @@ -0,0 +1,655 @@ +const lAudioContext = (typeof AudioContext !== 'undefined' ? AudioContext : (typeof webkitAudioContext !== 'undefined' ? webkitAudioContext : undefined)); +let wasm; + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} + +let cachedFloat32ArrayMemory0 = null; + +function getFloat32ArrayMemory0() { + if (cachedFloat32ArrayMemory0 === null || cachedFloat32ArrayMemory0.byteLength === 0) { + cachedFloat32ArrayMemory0 = new Float32Array(wasm.memory.buffer); + } + return cachedFloat32ArrayMemory0; +} + +function getArrayF32FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getFloat32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len); +} + +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(state => { + wasm.__wbindgen_export_1.get(state.dtor)(state.a, state.b) +}); + +function makeMutClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_1.get(state.dtor)(a, state.b); + CLOSURE_DTORS.unregister(state); + } else { + state.a = a; + } + } + }; + real.original = state; + CLOSURE_DTORS.register(real, state, state); + return real; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +let WASM_VECTOR_LEN = 0; + +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +let cachedDataViewMemory0 = null; + +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +function getArrayJsValueFromWasm0(ptr, len) { + ptr = ptr >>> 0; + const mem = getDataViewMemory0(); + const result = []; + for (let i = ptr; i < ptr + 4 * len; i += 4) { + result.push(takeObject(mem.getUint32(i, true))); + } + return result; +} + +export function main_js() { + wasm.main_js(); +} + +function __wbg_adapter_18(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h03a328ab39659ec3(arg0, arg1); +} + +const CoreInterfaceFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_coreinterface_free(ptr >>> 0, 1)); + +export class CoreInterface { + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + CoreInterfaceFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_coreinterface_free(ptr, 0); + } + constructor() { + const ret = wasm.coreinterface_new(); + this.__wbg_ptr = ret >>> 0; + CoreInterfaceFinalization.register(this, this.__wbg_ptr, this); + return this; + } + init() { + wasm.coreinterface_init(this.__wbg_ptr); + } + /** + * @param {number} timestamp + */ + play(timestamp) { + wasm.coreinterface_play(this.__wbg_ptr, timestamp); + } + stop() { + wasm.coreinterface_stop(this.__wbg_ptr); + } + /** + * @param {number} frequency + */ + add_sine_track(frequency) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.coreinterface_add_sine_track(retptr, this.__wbg_ptr, frequency); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {number} + */ + get_timestamp() { + const ret = wasm.coreinterface_get_timestamp(this.__wbg_ptr); + return ret; + } + /** + * @returns {JsTrack[]} + */ + get_tracks() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.coreinterface_get_tracks(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var v1 = getArrayJsValueFromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 4, 4); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } +} + +const JsTrackFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_jstrack_free(ptr >>> 0, 1)); + +export class JsTrack { + + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(JsTrack.prototype); + obj.__wbg_ptr = ptr; + JsTrackFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + JsTrackFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_jstrack_free(ptr, 0); + } + /** + * @returns {string} + */ + get name() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.jstrack_name(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } + } + /** + * @returns {string} + */ + toString() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.jstrack_toString(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } + } +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_call_672a4d21634d4a24 = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_close_5a97ef05b337f8ce = function() { return handleError(function (arg0) { + const ret = getObject(arg0).close(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_connect_b22945d106632a36 = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).connect(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_copyToChannel_b81ecf19fd54e146 = function() { return handleError(function (arg0, arg1, arg2, arg3) { + getObject(arg0).copyToChannel(getArrayF32FromWasm0(arg1, arg2), arg3); + }, arguments) }; + imports.wbg.__wbg_createBufferSource_f7860a96f709acbd = function() { return handleError(function (arg0) { + const ret = getObject(arg0).createBufferSource(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_createBuffer_926beeec3ff39b5a = function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = getObject(arg0).createBuffer(arg1 >>> 0, arg2 >>> 0, arg3); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_currentTime_adef4d803f58eb66 = function(arg0) { + const ret = getObject(arg0).currentTime; + return ret; + }; + imports.wbg.__wbg_destination_6400091abd6f01b3 = function(arg0) { + const ret = getObject(arg0).destination; + return addHeapObject(ret); + }; + imports.wbg.__wbg_eval_e10dc02e9547f640 = function() { return handleError(function (arg0, arg1) { + const ret = eval(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_instanceof_Window_def73ea0955fc569 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Window; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_jstrack_new = function(arg0) { + const ret = JsTrack.__wrap(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_log_c222819a41e063d3 = function(arg0) { + console.log(getObject(arg0)); + }; + imports.wbg.__wbg_maxChannelCount_a06f8ca4190698ed = function(arg0) { + const ret = getObject(arg0).maxChannelCount; + return ret; + }; + imports.wbg.__wbg_new_405e22f390576ce2 = function() { + const ret = new Object(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_newwithcontextoptions_b62c06fed7900366 = function() { return handleError(function (arg0) { + const ret = new lAudioContext(getObject(arg0)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_resume_35efdc4ffe13bf18 = function() { return handleError(function (arg0) { + const ret = getObject(arg0).resume(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_setTimeout_f2fe5af8e3debeb3 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).setTimeout(getObject(arg1), arg2); + return ret; + }, arguments) }; + imports.wbg.__wbg_setbuffer_10a9ee2a05c73896 = function(arg0, arg1) { + getObject(arg0).buffer = getObject(arg1); + }; + imports.wbg.__wbg_setchannelCount_876fcf5798895180 = function(arg0, arg1) { + getObject(arg0).channelCount = arg1 >>> 0; + }; + imports.wbg.__wbg_setonended_00ff85c70a4f819f = function(arg0, arg1) { + getObject(arg0).onended = getObject(arg1); + }; + imports.wbg.__wbg_setsamplerate_8bc3fd769a6db02b = function(arg0, arg1) { + getObject(arg0).sampleRate = arg1; + }; + imports.wbg.__wbg_start_e81f89e130c3c86e = function() { return handleError(function (arg0, arg1) { + getObject(arg0).start(arg1); + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + const ret = typeof global === 'undefined' ? null : global; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + const ret = typeof globalThis === 'undefined' ? null : globalThis; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + const ret = typeof self === 'undefined' ? null : self; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + const ret = typeof window === 'undefined' ? null : window; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbindgen_boolean_get = function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; + return ret; + }; + imports.wbg.__wbindgen_cb_drop = function(arg0) { + const obj = takeObject(arg0).original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + const ret = false; + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper99 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 40, __wbg_adapter_18); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + + return imports; +} + +function __wbg_init_memory(imports, memory) { + +} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedDataViewMemory0 = null; + cachedFloat32ArrayMemory0 = null; + cachedUint8ArrayMemory0 = null; + + + wasm.__wbindgen_start(); + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (typeof module !== 'undefined') { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (typeof module_or_path !== 'undefined') { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (typeof module_or_path === 'undefined') { + module_or_path = new URL('lightningbeam_core_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync }; +export default __wbg_init; diff --git a/src/pkg/lightningbeam_core_bg.wasm b/src/pkg/lightningbeam_core_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..720d4342d669742e7235e3d6708f3ce00d567c41 GIT binary patch literal 135753 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 void; +export const jstrack_name: (a: number, b: number) => void; +export const jstrack_toString: (a: number, b: number) => void; +export const __wbg_coreinterface_free: (a: number, b: number) => void; +export const coreinterface_new: () => number; +export const coreinterface_init: (a: number) => void; +export const coreinterface_play: (a: number, b: number) => void; +export const coreinterface_stop: (a: number) => void; +export const coreinterface_add_sine_track: (a: number, b: number, c: number) => void; +export const coreinterface_get_timestamp: (a: number) => number; +export const coreinterface_get_tracks: (a: number, b: number) => void; +export const main_js: () => void; +export const __wbindgen_exn_store: (a: number) => void; +export const __wbindgen_export_1: WebAssembly.Table; +export const __wbindgen_malloc: (a: number, b: number) => number; +export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; +export const __wbindgen_add_to_stack_pointer: (a: number) => number; +export const __wbindgen_free: (a: number, b: number, c: number) => void; +export const _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h03a328ab39659ec3: (a: number, b: number) => void; +export const __wbindgen_start: () => void; diff --git a/src/pkg/package.json b/src/pkg/package.json new file mode 100644 index 0000000..b23063e --- /dev/null +++ b/src/pkg/package.json @@ -0,0 +1,14 @@ +{ + "name": "lightningbeam-core", + "version": "0.1.0", + "files": [ + "lightningbeam_core_bg.wasm", + "lightningbeam_core.js", + "lightningbeam_core.d.ts" + ], + "module": "lightningbeam_core.js", + "types": "lightningbeam_core.d.ts", + "sideEffects": [ + "./snippets/*" + ] +} \ No newline at end of file From e12c2e8877db8a049e48be2bde4c59dcfa25fc4d Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Sat, 25 Jan 2025 18:48:34 -0500 Subject: [PATCH 4/7] Require user interaction to start audio --- lightningbeam-core/src/audio.rs | 23 ++++++++++++++++++----- lightningbeam-core/src/lib.rs | 6 ++++++ src/main.js | 21 +++++++++++++++++++++ src/pkg/lightningbeam_core.d.ts | 2 ++ src/pkg/lightningbeam_core.js | 17 +++++++++++++++-- src/pkg/lightningbeam_core_bg.wasm | Bin 135753 -> 137627 bytes src/pkg/lightningbeam_core_bg.wasm.d.ts | 1 + 7 files changed, 63 insertions(+), 7 deletions(-) diff --git a/lightningbeam-core/src/audio.rs b/lightningbeam-core/src/audio.rs index 9dc806f..3904192 100644 --- a/lightningbeam-core/src/audio.rs +++ b/lightningbeam-core/src/audio.rs @@ -3,6 +3,11 @@ use cpal::{Sample}; use std::sync::{Arc, Mutex}; use crate::{TrackManager, Timestamp, Duration, SampleCount, AudioOutput, PlaybackState}; +#[derive(PartialEq)] +enum AudioState { + Suspended, + Running, +} // #[cfg(feature = "wasm")] // use wasm_bindgen::prelude::*; @@ -13,6 +18,7 @@ pub struct CpalAudioOutput { track_manager: Option>>, _stream: Option, playback_state: PlaybackState, + audio_state: AudioState, timestamp: Arc>, chunk_size: usize, sample_rate: u32, @@ -26,6 +32,7 @@ impl CpalAudioOutput { track_manager: None, _stream: None, playback_state: PlaybackState::Stopped, + audio_state: AudioState::Suspended, timestamp: Arc::new(Mutex::new(Timestamp::from_seconds(0.0))), chunk_size: 0, sample_rate: 44100, // Default sample rate, updated later @@ -110,11 +117,6 @@ impl AudioOutput for CpalAudioOutput { .ok_or_else(|| "No output device available")?; let supported_config = device.default_output_config()?; self._stream = Some(self.build_stream::(&device, supported_config)?); - if let Some(stream) = self._stream.as_ref() { - stream.play().unwrap(); - } else { - eprintln!("Stream is not initialized!"); - } Ok(()) } @@ -126,6 +128,17 @@ impl AudioOutput for CpalAudioOutput { fn stop(&mut self) { self.playback_state = PlaybackState::Stopped; } + + fn resume(&mut self) -> Result<(), anyhow::Error> { + if self.audio_state == AudioState::Suspended { + if let Some(stream) = &self._stream { + stream.play()?; + self.audio_state = AudioState::Running; + log::info!("Audio resumed"); + } + } + Ok(()) + } fn register_track_manager(&mut self, track_manager: Arc>) { self.track_manager = Some(track_manager); diff --git a/lightningbeam-core/src/lib.rs b/lightningbeam-core/src/lib.rs index b99a592..85d5020 100644 --- a/lightningbeam-core/src/lib.rs +++ b/lightningbeam-core/src/lib.rs @@ -95,6 +95,7 @@ pub trait AudioOutput { fn start(&mut self) -> Result<(), Box>; fn play(&mut self, start_timestamp: Timestamp); fn stop(&mut self); + fn resume(&mut self) -> Result<(), anyhow::Error>; fn register_track_manager(&mut self, track_manager: Arc>); fn get_timestamp(&mut self) -> Timestamp; fn set_chunk_size(&mut self, chunk_size: usize); @@ -218,6 +219,11 @@ impl CoreInterface { let mut track_manager = self.track_manager.lock().unwrap(); track_manager.stop(); } + pub fn resume_audio(&mut self) -> Result<(), JsValue> { + // Call this on user gestures if audio gets suspended + self.cpal_audio_output.resume() + .map_err(|e| JsValue::from_str(&format!("Failed to resume audio: {}", e))) + } pub fn add_sine_track(&mut self, frequency: f32) -> Result<(), String> { if frequency.is_nan() || frequency.is_infinite() || frequency <= 0.0 { return Err(format!("Invalid frequency: {}", frequency)); diff --git a/src/main.js b/src/main.js index bb0bd79..d27025f 100644 --- a/src/main.js +++ b/src/main.js @@ -8419,5 +8419,26 @@ async function testAudio() { coreInterface.init() coreInterface.play(0.0) console.log(coreInterface) + + let audioStarted = false; + const startCoreInterfaceAudio = () => { + if (!audioStarted) { + try { + coreInterface.resume_audio(); + audioStarted = true; + console.log("Started CoreInterface Audio!") + } catch (err) { + console.error("Audio resume failed:", err); + } + } + + // Remove the event listeners to prevent them from firing again + document.removeEventListener("click", startCoreInterfaceAudio); + document.removeEventListener("keydown", startCoreInterfaceAudio); + }; + + // Add event listeners for mouse click and key press + document.addEventListener("click", startCoreInterfaceAudio); + document.addEventListener("keydown", startCoreInterfaceAudio); } testAudio() \ No newline at end of file diff --git a/src/pkg/lightningbeam_core.d.ts b/src/pkg/lightningbeam_core.d.ts index 87d6b82..34d7254 100644 --- a/src/pkg/lightningbeam_core.d.ts +++ b/src/pkg/lightningbeam_core.d.ts @@ -7,6 +7,7 @@ export class CoreInterface { init(): void; play(timestamp: number): void; stop(): void; + resume_audio(): void; add_sine_track(frequency: number): void; get_timestamp(): number; get_tracks(): JsTrack[]; @@ -30,6 +31,7 @@ export interface InitOutput { readonly coreinterface_init: (a: number) => void; readonly coreinterface_play: (a: number, b: number) => void; readonly coreinterface_stop: (a: number) => void; + readonly coreinterface_resume_audio: (a: number, b: number) => void; readonly coreinterface_add_sine_track: (a: number, b: number, c: number) => void; readonly coreinterface_get_timestamp: (a: number) => number; readonly coreinterface_get_tracks: (a: number, b: number) => void; diff --git a/src/pkg/lightningbeam_core.js b/src/pkg/lightningbeam_core.js index 0ef6268..cc982c5 100644 --- a/src/pkg/lightningbeam_core.js +++ b/src/pkg/lightningbeam_core.js @@ -288,6 +288,19 @@ export class CoreInterface { stop() { wasm.coreinterface_stop(this.__wbg_ptr); } + resume_audio() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.coreinterface_resume_audio(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } /** * @param {number} frequency */ @@ -549,8 +562,8 @@ function __wbg_get_imports() { const ret = false; return ret; }; - imports.wbg.__wbindgen_closure_wrapper99 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 40, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper87 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 31, __wbg_adapter_18); return addHeapObject(ret); }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/src/pkg/lightningbeam_core_bg.wasm b/src/pkg/lightningbeam_core_bg.wasm index 720d4342d669742e7235e3d6708f3ce00d567c41..1a0b9fad1e0042ae6a68d1084b2083f7d7b332d8 100644 GIT binary patch delta 31387 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 delta 30120 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 number; export const coreinterface_init: (a: number) => void; export const coreinterface_play: (a: number, b: number) => void; export const coreinterface_stop: (a: number) => void; +export const coreinterface_resume_audio: (a: number, b: number) => void; export const coreinterface_add_sine_track: (a: number, b: number, c: number) => void; export const coreinterface_get_timestamp: (a: number) => number; export const coreinterface_get_tracks: (a: number, b: number) => void; From 749caa14a5ca393336d9370fdd97518f860718cc Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Wed, 29 Jan 2025 04:41:59 -0500 Subject: [PATCH 5/7] Add RecordedAudioTrack --- lightningbeam-core/Cargo.lock | 387 ++++++++++++++++++++++++ lightningbeam-core/Cargo.toml | 12 +- lightningbeam-core/build.sh | 5 +- lightningbeam-core/src/lib.rs | 350 +++++++++++++++++++-- lightningbeam-core/src/time.rs | 66 ++++ src/pkg/lightningbeam_core.d.ts | 2 +- src/pkg/lightningbeam_core.js | 6 +- src/pkg/lightningbeam_core_bg.wasm | Bin 137627 -> 137320 bytes src/pkg/lightningbeam_core_bg.wasm.d.ts | 2 +- 9 files changed, 800 insertions(+), 30 deletions(-) diff --git a/lightningbeam-core/Cargo.lock b/lightningbeam-core/Cargo.lock index d3c70be..c2984ce 100644 --- a/lightningbeam-core/Cargo.lock +++ b/lightningbeam-core/Cargo.lock @@ -39,6 +39,12 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "autocfg" version = "1.4.0" @@ -81,6 +87,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" + [[package]] name = "bytes" version = "1.9.0" @@ -211,12 +223,27 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + [[package]] name = "glob" version = "0.3.2" @@ -229,6 +256,12 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + [[package]] name = "indexmap" version = "2.7.1" @@ -289,6 +322,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.169" @@ -312,9 +351,15 @@ dependencies = [ "anyhow", "console_error_panic_hook", "cpal", + "hound", + "js-sys", "log", + "minimp3", + "rubato", "serde", + "symphonia", "wasm-bindgen", + "wasm-bindgen-futures", "wasm-logger", "web-sys", ] @@ -325,6 +370,15 @@ version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "mach2" version = "0.4.2" @@ -346,6 +400,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minimp3" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372" +dependencies = [ + "minimp3-sys", + "slice-deque", + "thiserror", +] + +[[package]] +name = "minimp3-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" +dependencies = [ + "cc", +] + [[package]] name = "ndk" version = "0.8.0" @@ -385,6 +459,15 @@ dependencies = [ "minimal-lexical", ] +[[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-derive" version = "0.4.2" @@ -396,6 +479,15 @@ dependencies = [ "syn", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -461,6 +553,15 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[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-macro-crate" version = "1.3.1" @@ -489,6 +590,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "realfft" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390252372b7f2aac8360fc5e72eba10136b166d6faeed97e6d0c8324eb99b2b1" +dependencies = [ + "rustfft", +] + [[package]] name = "regex" version = "1.11.1" @@ -518,12 +628,39 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rubato" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6dd52e80cfc21894deadf554a5673002938ae4625f7a283e536f9cf7c17b0d5" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "realfft", +] + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustfft" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43806561bc506d0c5d160643ad742e3161049ac01027b5e6d7524091fd401d86" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", + "version_check", +] + [[package]] name = "rustversion" version = "1.0.19" @@ -565,6 +702,218 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "slice-deque" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" +dependencies = [ + "libc", + "mach", + "winapi", +] + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "symphonia" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-adpcm", + "symphonia-codec-alac", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-caf", + "symphonia-format-isomp4", + "symphonia-format-mkv", + "symphonia-format-ogg", + "symphonia-format-riff", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e34f34298a7308d4397a6c7fbf5b84c5d491231ce3dd379707ba673ab3bd97" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbf25b545ad0d3ee3e891ea643ad115aff4ca92f6aec472086b957a58522f70" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-adpcm" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94e1feac3327cd616e973d5be69ad36b3945f16b06f19c6773fc3ac0b426a0f" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-alac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d8a6666649a08412906476a8b0efd9b9733e241180189e9f92b09c08d0e38f3" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-caf" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e43c99c696a388295a29fe71b133079f5d8b18041cf734c5459c35ad9097af50" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfdf178d697e50ce1e5d9b982ba1b94c47218e03ec35022d9f0e071a16dc844" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-mkv" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb43471a100f7882dc9937395bd5ebee8329298e766250b15b3875652fe3d6f" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada3505789516bcf00fc1157c67729eded428b455c27ca370e41f4d785bfa931" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50" +dependencies = [ + "extended", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "484472580fa49991afda5f6550ece662237b00c6f562c7d9638d1b086ed010fe" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" version = "2.0.96" @@ -613,12 +962,28 @@ dependencies = [ "winnow", ] +[[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 = "unicode-ident" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -721,6 +1086,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -730,6 +1111,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" version = "0.54.0" diff --git a/lightningbeam-core/Cargo.toml b/lightningbeam-core/Cargo.toml index a6badae..ca67190 100644 --- a/lightningbeam-core/Cargo.toml +++ b/lightningbeam-core/Cargo.toml @@ -13,10 +13,20 @@ cpal = { version = "0.15", features = ["wasm-bindgen"] } anyhow = "1.0" wasm-logger = "0.2" log = "0.4" +rubato = "0.14.0" +symphonia = { version = "0.5", features = ["all"] } [dependencies.web-sys] version = "0.3.22" -features = ["console"] +features = ["console", "AudioContext"] + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +minimp3 = "0.5.1" # Only include minimp3 for native platforms +hound = "3.5.1" # Only include hound for native platforms + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen-futures = "0.4" +js-sys = "0.3" # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires diff --git a/lightningbeam-core/build.sh b/lightningbeam-core/build.sh index efac65a..4992bb3 100755 --- a/lightningbeam-core/build.sh +++ b/lightningbeam-core/build.sh @@ -1,3 +1,6 @@ #!/bin/bash -cd core +echo "Building native..." +cargo build +echo +echo "Building wasm..." wasm-pack build --target web --out-dir ../src/pkg --features wasm diff --git a/lightningbeam-core/src/lib.rs b/lightningbeam-core/src/lib.rs index 85d5020..227d597 100644 --- a/lightningbeam-core/src/lib.rs +++ b/lightningbeam-core/src/lib.rs @@ -3,8 +3,29 @@ use time::{Timestamp, Duration, Frame, SampleCount}; mod audio; use audio::{CpalAudioOutput}; use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError}; +use rubato::{FftFixedIn, Resampler}; +use std::io::Cursor; use std::sync::{Arc, Mutex}; +use std::error::Error; + +use symphonia::core::{ + audio::AudioBufferRef, + audio::Signal, + codecs::{DecoderOptions}, + formats::{FormatOptions}, + io::MediaSourceStream, + meta::MetadataOptions, + probe::Hint, +}; + +#[cfg(not(target_arch = "wasm32"))] +use std::io; +#[cfg(not(target_arch = "wasm32"))] +use std::io::Write; + + +#[cfg(target_arch = "wasm32")] use std::fmt; #[cfg(feature = "wasm")] @@ -20,7 +41,7 @@ pub trait Track: Send { fn get_name(&self) -> &str { "Unnamed Track" } - + fn set_name(&mut self, _name: String) { } /// Render audio for the given timestamp and duration. @@ -34,6 +55,10 @@ pub trait Track: Send { fn render_video(&self, _timestamp: Timestamp, _playing: bool) -> Option { None } + + // Set the sample rate of any audio this track might contain + fn set_sample_rate(&mut self, _sample_rate: u32) { + } } pub struct TrackManager { @@ -154,34 +179,288 @@ impl Track for SineWaveTrack { } } +#[derive(Debug, Clone)] +struct AudioBuffer { + original_data: Vec, + original_sample_rate: u32, + resampled_data: Vec, + start_time: Timestamp, +} + +impl AudioBuffer { + fn duration(&self) -> Duration { + if self.resampled_data.is_empty() { + Duration::from_seconds(0.0) + } else { + Duration::from_seconds( + self.resampled_data.len() as f64 / + self.original_sample_rate as f64 + ) + } + } +} + + +pub struct RecordedAudioTrack { + name: String, + buffers: Vec, + target_sample_rate: Option, +} + + +impl RecordedAudioTrack { + pub fn new(name: &str) -> Self { + Self { + name: name.to_string(), + buffers: Vec::new(), + target_sample_rate: None, + } + } + + pub fn add_buffer(&mut self, start_time: Timestamp, sample_rate: u32, data: Vec) { + let resampled_data = match self.target_sample_rate { + Some(target_rate) if sample_rate != target_rate => + self::resample(&data, sample_rate, target_rate), + Some(_target_rate) => + data.clone(), // Already at target rate + None => + Vec::new(), // Will be resampled later + }; + + self.buffers.push(AudioBuffer { + original_data: data, + original_sample_rate: sample_rate, + resampled_data, + start_time, + }); + + // Keep buffers sorted by start time + self.buffers.sort_by(|a, b| a.start_time.partial_cmp(&b.start_time).unwrap()); + } +} + +impl Track for RecordedAudioTrack { + fn get_name(&self) -> &str { + &self.name + } + fn set_sample_rate(&mut self, target_rate: u32) { + self.target_sample_rate = Some(target_rate); + + for buffer in &mut self.buffers { + if buffer.original_sample_rate == target_rate { + buffer.resampled_data = buffer.original_data.clone(); + } else { + buffer.resampled_data = self::resample( + &buffer.original_data, + buffer.original_sample_rate, + target_rate + ); + } + } + } + + fn render_audio( + &mut self, + timestamp: Timestamp, + duration: SampleCount, + sample_rate: u32, + playing: bool, + ) -> Option> { + if !playing || self.target_sample_rate != Some(sample_rate) { + return Some(vec![0.0; duration.as_usize()]); + } + + // let chunk_samples = duration.as_usize(); + let mut output = vec![0.0; duration.as_usize()]; + let mut remaining_samples = duration; + let mut current_time = timestamp; + + // Find the first buffer that overlaps with the requested time + let mut buffer_index = match self.buffers.binary_search_by(|b| { + b.start_time.partial_cmp(¤t_time).unwrap() + }) { + Ok(i) => i, + Err(i) if i > 0 => i - 1, // Check previous buffer if timestamp is between buffers + _ => 0, + }; + + while remaining_samples.as_usize() > 0 && buffer_index < self.buffers.len() { + let buffer = &self.buffers[buffer_index]; + + // Calculate overlap with current buffer + let buffer_start = buffer.start_time; + let buffer_end = buffer_start + buffer.duration(); + + if current_time >= buffer_end { + // Move to next buffer + buffer_index += 1; + continue; + } + + // Calculate how many samples we can take from this buffer + let buffer_offset = ((current_time - buffer_start).as_seconds() * sample_rate as f64) as usize; + let available_samples = SampleCount::new(buffer.resampled_data.len().saturating_sub(buffer_offset)); + let samples_to_take = remaining_samples.min(available_samples); + + if samples_to_take == 0 { + // No more samples in this buffer + buffer_index += 1; + continue; + } + + // Copy samples from buffer to output + let output_offset = duration - remaining_samples; + output[output_offset.as_usize()..(output_offset + samples_to_take).as_usize()] + .copy_from_slice(&buffer.resampled_data[buffer_offset..buffer_offset + samples_to_take.as_usize()]); + + // Update state + remaining_samples -= samples_to_take; + current_time += samples_to_take.to_duration(sample_rate); + } + + Some(output) + } +} + +fn resample(input: &[f32], input_rate: u32, output_rate: u32) -> Vec { + if input_rate == output_rate { + return input.to_vec(); + } + + let input_rate = input_rate.try_into().unwrap(); + let output_rate = output_rate.try_into().unwrap(); + let chunk_size = input.len(); + + let mut resampler = FftFixedIn::new( + output_rate, + input_rate, + chunk_size, + 1, // channel count + 2, // fft size + ).unwrap(); + + let output = resampler.process(&[input], None).unwrap(); + output[0].clone() +} + +pub trait AudioLoader { + fn load_audio( + &self, + track: &mut RecordedAudioTrack, + start_time: Timestamp, + audio_data: &[u8], + ) -> Result<(), Box>; +} + +pub struct GenericAudioLoader; + +impl AudioLoader for GenericAudioLoader { + fn load_audio( + &self, + track: &mut RecordedAudioTrack, + start_time: Timestamp, + audio_data: &[u8], + ) -> Result<(), Box> { + decode_audio(track, start_time, audio_data) + } +} + +fn decode_audio( + track: &mut RecordedAudioTrack, + start_time: Timestamp, + audio_data: &[u8], +) -> Result<(), Box> { + // Create a media source from the byte slice + let mss = MediaSourceStream::new( + Box::new(Cursor::new(audio_data.to_vec())), + Default::default(), + ); + + // Use a fresh hint (no extension specified) for format detection + let hint = Hint::new(); + + // Probe the media source for a supported format + let probed = symphonia::default::get_probe() + .format(&hint, mss, &FormatOptions::default(), &MetadataOptions::default())?; + + // Get the format reader + let mut format = probed.format; + + // Find the first supported audio track + let default_track = format + .tracks() + .iter() + .find(|t| t.codec_params.codec != symphonia::core::codecs::CODEC_TYPE_NULL) + .ok_or("No supported audio track found")?; + + // Create a decoder for the track + let mut decoder = symphonia::default::get_codecs() + .make(&default_track.codec_params, &DecoderOptions::default())?; + + // Get the sample rate from the track + let sample_rate = default_track.codec_params.sample_rate.ok_or("Unknown sample rate")?; + let mut decoded_samples = Vec::new(); + + // Decode loop + loop { + let packet = match format.next_packet() { + Ok(packet) => packet, + Err(_) => break, // End of stream + }; + + match decoder.decode(&packet)? { + AudioBufferRef::F32(buf) => { + for i in 0..buf.frames() { + for c in 0..buf.spec().channels.count() { + decoded_samples.push(buf.chan(c)[i]); + } + } + } + AudioBufferRef::S16(buf) => { + for i in 0..buf.frames() { + for c in 0..buf.spec().channels.count() { + decoded_samples.push(buf.chan(c)[i] as f32 / 32768.0); + } + } + } + _ => return Err("Unsupported audio format".into()), + } + } + + // Add the decoded audio to the track + track.add_buffer(start_time, sample_rate, decoded_samples); + + Ok(()) +} + #[cfg(feature="wasm")] #[wasm_bindgen] pub struct JsTrack { - name: String, + name: String, } #[cfg(feature="wasm")] #[wasm_bindgen] impl JsTrack { - #[wasm_bindgen(getter)] - pub fn name(&self) -> String { - self.name.clone() - } + #[wasm_bindgen(getter)] + pub fn name(&self) -> String { + self.name.clone() + } } #[cfg(feature="wasm")] impl fmt::Display for JsTrack { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "JsTrack {{ name: {} }}", self.name) + write!(f, "JsTrack {{ name: {} }}", self.name) } } #[cfg(feature="wasm")] #[wasm_bindgen] impl JsTrack { - #[wasm_bindgen(js_name = toString)] - pub fn to_string(&self) -> String { - format!("{}", self) // Calls the Display implementation - } + #[wasm_bindgen(js_name = toString)] + pub fn to_string(&self) -> String { + format!("{}", self) // Calls the Display implementation + } } #[cfg(feature="wasm")] @@ -222,7 +501,7 @@ impl CoreInterface { pub fn resume_audio(&mut self) -> Result<(), JsValue> { // Call this on user gestures if audio gets suspended self.cpal_audio_output.resume() - .map_err(|e| JsValue::from_str(&format!("Failed to resume audio: {}", e))) + .map_err(|e| JsValue::from_str(&format!("Failed to resume audio: {}", e))) } pub fn add_sine_track(&mut self, frequency: f32) -> Result<(), String> { if frequency.is_nan() || frequency.is_infinite() || frequency <= 0.0 { @@ -232,10 +511,10 @@ impl CoreInterface { let mut track_manager = self.track_manager.lock().unwrap(); let sine_track = SineWaveTrack::new(frequency); track_manager.add_track(Box::new(sine_track)); - + Ok(()) } - + pub fn get_timestamp(&mut self) -> f64 { self.cpal_audio_output.get_timestamp().as_seconds() } @@ -245,7 +524,7 @@ impl CoreInterface { tracks .iter() .map(|track| JsTrack { - name: track.get_name().to_string(), + name: track.get_name().to_string(), }) .collect() } @@ -261,17 +540,42 @@ impl Log for PlainTextLogger { fn log(&self, record: &Record) { if self.enabled(record.metadata()) { - console::log_1(&format!( - "{} [{}:{}] {}", - record.level(), - record.file().unwrap_or("unknown"), - record.line().unwrap_or(0), - record.args() - ).into()); + #[cfg(target_arch = "wasm32")] + { + // WASM: Log to the JS console + console::log_1( + &format!( + "{} [{}:{}] {}", + record.level(), + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ) + .into(), + ); + } + + #[cfg(not(target_arch = "wasm32"))] + { + // Native: Log to stderr + let _ = writeln!( + io::stderr(), + "{} [{}:{}] {}", + record.level(), + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ); + } } } - fn flush(&self) {} + fn flush(&self) { + #[cfg(not(target_arch = "wasm32"))] + { + let _ = io::stderr().flush(); + } + } } pub fn init_plain_text_logger() -> Result<(), SetLoggerError> { diff --git a/lightningbeam-core/src/time.rs b/lightningbeam-core/src/time.rs index c014ced..7458686 100644 --- a/lightningbeam-core/src/time.rs +++ b/lightningbeam-core/src/time.rs @@ -54,6 +54,14 @@ impl Timestamp { pub fn set(&mut self, other: Timestamp) { self.0 = other.as_seconds(); } + + pub fn max(&self, other: Timestamp) -> Timestamp { + Timestamp(self.0.max(other.0)) + } + + pub fn min(&self, other: Timestamp) -> Timestamp { + Timestamp(self.0.min(other.0)) + } } impl Duration { @@ -62,6 +70,11 @@ impl Duration { Duration(seconds) } + /// Create a new duration from seconds. (dummy method) + pub fn from_seconds(seconds: f64) -> Self { + Duration(seconds) + } + /// Create a new duration from milliseconds. pub fn from_millis(milliseconds: u64) -> Self { Duration(milliseconds as f64 / 1000.0) @@ -117,6 +130,24 @@ impl SampleCount { pub fn as_usize(&self) -> usize { self.0 } + + pub fn to_duration(&self, sample_rate: u32) -> Duration { + Duration((self.0 as f64) / (sample_rate as f64)) + } + + pub fn max(&self, other: SampleCount) -> SampleCount { + SampleCount(self.0.max(other.0)) + } + + pub fn min(&self, other: SampleCount) -> SampleCount { + SampleCount(self.0.min(other.0)) + } +} + +impl PartialEq for SampleCount{ + fn eq(&self, other: &usize) -> bool { + self.0 == *other + } } // Overloading operators for more natural usage @@ -138,6 +169,14 @@ impl Sub for Timestamp { } } +impl Sub for Timestamp { + type Output = Duration; + + fn sub(self, other: Timestamp) -> Duration { + self.subtract_timestamp(other) + } +} + impl AddAssign for Timestamp { fn add_assign(&mut self, duration: Duration) { self.0 += duration.0; @@ -150,6 +189,33 @@ impl SubAssign for Timestamp { } } + +impl Add for SampleCount { + type Output = SampleCount; + fn add(self, other: SampleCount) -> SampleCount { + SampleCount(self.0 + other.0) + } +} + +impl Sub for SampleCount { + type Output = SampleCount; + fn sub(self, other: SampleCount) -> SampleCount { + SampleCount(self.0 - other.0) + } +} + +impl AddAssign for SampleCount { + fn add_assign(&mut self, other: SampleCount) { + self.0 += other.0; + } +} + +impl SubAssign for SampleCount { + fn sub_assign(&mut self, other: SampleCount) { + self.0 -= other.0; + } +} + /// Represents a video frame. #[derive(Debug, Clone)] pub struct Frame { diff --git a/src/pkg/lightningbeam_core.d.ts b/src/pkg/lightningbeam_core.d.ts index 34d7254..3b8c0e8 100644 --- a/src/pkg/lightningbeam_core.d.ts +++ b/src/pkg/lightningbeam_core.d.ts @@ -42,7 +42,7 @@ export interface InitOutput { readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; readonly __wbindgen_free: (a: number, b: number, c: number) => void; - readonly _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h03a328ab39659ec3: (a: number, b: number) => void; + readonly _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hfe92e343adbc229b: (a: number, b: number) => void; readonly __wbindgen_start: () => void; } diff --git a/src/pkg/lightningbeam_core.js b/src/pkg/lightningbeam_core.js index cc982c5..1df0b89 100644 --- a/src/pkg/lightningbeam_core.js +++ b/src/pkg/lightningbeam_core.js @@ -250,7 +250,7 @@ export function main_js() { } function __wbg_adapter_18(arg0, arg1) { - wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h03a328ab39659ec3(arg0, arg1); + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hfe92e343adbc229b(arg0, arg1); } const CoreInterfaceFinalization = (typeof FinalizationRegistry === 'undefined') @@ -562,8 +562,8 @@ function __wbg_get_imports() { const ret = false; return ret; }; - imports.wbg.__wbindgen_closure_wrapper87 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 31, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper151 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 73, __wbg_adapter_18); return addHeapObject(ret); }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/src/pkg/lightningbeam_core_bg.wasm b/src/pkg/lightningbeam_core_bg.wasm index 1a0b9fad1e0042ae6a68d1084b2083f7d7b332d8..2d2ab1d3a5a35baef4c0bff45a64f17356355b42 100644 GIT binary patch delta 20790 zcmch9dt6o3691lc&f#?qdQep4A^QL#iVsv&R8+R5y?myom0FrsqWMa#ZV%9Wkg1Uy zU07IJR+MDep^hfqFwwYIEG;TCD=RC#KEk3xqx^km@3W6b>)zk*uOEJfwPx0=$DTE7 z*39gUPkX~ol!t91I;j&~rB9@``qdQUT}YqUDM23=5^L+;tGj-8h{sMs5&G(oL`OK; z6h+@2(lK4J+3X6n4G)V@ln@}oL5gDIiek6fY$Qks#Su%*NYNdk{Zq;M0T)*lumc

BJ42Hq$YBqqxl}_n`r6P}X^Nf`7EO2PZSBX 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 nXXo#iokOZ{3u9P@7Fj9qCs%Y5J%Y3 number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_add_to_stack_pointer: (a: number) => number; export const __wbindgen_free: (a: number, b: number, c: number) => void; -export const _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h03a328ab39659ec3: (a: number, b: number) => void; +export const _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hfe92e343adbc229b: (a: number, b: number) => void; export const __wbindgen_start: () => void; From 18fad499c574ca970f3e2003edabf2e64e2592b2 Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Fri, 31 Jan 2025 06:44:03 -0500 Subject: [PATCH 6/7] Automatically resize audio buffer to prevent overruns --- lightningbeam-core/Cargo.lock | 171 +++++++----- lightningbeam-core/Cargo.toml | 11 +- lightningbeam-core/src/audio.rs | 336 +++++++++++++++++++----- lightningbeam-core/src/lib.rs | 155 +++++++---- src/pkg/lightningbeam_core.d.ts | 4 +- src/pkg/lightningbeam_core.js | 106 ++++++-- src/pkg/lightningbeam_core_bg.wasm | Bin 137320 -> 168521 bytes src/pkg/lightningbeam_core_bg.wasm.d.ts | 4 +- 8 files changed, 598 insertions(+), 189 deletions(-) diff --git a/lightningbeam-core/Cargo.lock b/lightningbeam-core/Cargo.lock index c2984ce..8740cb0 100644 --- a/lightningbeam-core/Cargo.lock +++ b/lightningbeam-core/Cargo.lock @@ -45,6 +45,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "atomic_refcell" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" + [[package]] name = "autocfg" version = "1.4.0" @@ -211,6 +217,21 @@ dependencies = [ "windows", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "dasp_sample" version = "0.11.0" @@ -244,24 +265,45 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -[[package]] -name = "hound" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" - [[package]] name = "indexmap" version = "2.7.1" @@ -349,12 +391,14 @@ name = "lightningbeam-core" version = "0.1.0" dependencies = [ "anyhow", + "atomic_refcell", "console_error_panic_hook", "cpal", - "hound", + "crossbeam-channel", + "gloo-timers", "js-sys", "log", - "minimp3", + "parking_lot", "rubato", "serde", "symphonia", @@ -362,6 +406,17 @@ dependencies = [ "wasm-bindgen-futures", "wasm-logger", "web-sys", + "web-time", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", ] [[package]] @@ -370,15 +425,6 @@ version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - [[package]] name = "mach2" version = "0.4.2" @@ -400,26 +446,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "minimp3" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372" -dependencies = [ - "minimp3-sys", - "slice-deque", - "thiserror", -] - -[[package]] -name = "minimp3-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" -dependencies = [ - "cc", -] - [[package]] name = "ndk" version = "0.8.0" @@ -547,6 +573,29 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -599,6 +648,15 @@ dependencies = [ "rustfft", ] +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "regex" version = "1.11.1" @@ -676,6 +734,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.217" @@ -703,15 +767,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "slice-deque" -version = "0.3.0" +name = "smallvec" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" -dependencies = [ - "libc", - "mach", - "winapi", -] +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "strength_reduce" @@ -1087,21 +1146,15 @@ dependencies = [ ] [[package]] -name = "winapi" -version = "0.3.9" +name = "web-time" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "js-sys", + "wasm-bindgen", ] -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.9" @@ -1111,12 +1164,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows" version = "0.54.0" diff --git a/lightningbeam-core/Cargo.toml b/lightningbeam-core/Cargo.toml index ca67190..3eb80d5 100644 --- a/lightningbeam-core/Cargo.toml +++ b/lightningbeam-core/Cargo.toml @@ -15,18 +15,19 @@ wasm-logger = "0.2" log = "0.4" rubato = "0.14.0" symphonia = { version = "0.5", features = ["all"] } +crossbeam-channel = "0.5.4" +atomic_refcell = "0.1.13" # WASM-compatible atomic refcell +parking_lot = "0.12" [dependencies.web-sys] version = "0.3.22" -features = ["console", "AudioContext"] - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -minimp3 = "0.5.1" # Only include minimp3 for native platforms -hound = "3.5.1" # Only include hound for native platforms +features = ["console", "AudioContext", "Window", "Performance", "PerformanceTiming"] [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" js-sys = "0.3" +web-time = "0.2" # WASM-compatible timing +gloo-timers = { version = "0.2", features = ["futures"] } # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires diff --git a/lightningbeam-core/src/audio.rs b/lightningbeam-core/src/audio.rs index 3904192..92d157d 100644 --- a/lightningbeam-core/src/audio.rs +++ b/lightningbeam-core/src/audio.rs @@ -2,18 +2,35 @@ use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::{Sample}; use std::sync::{Arc, Mutex}; use crate::{TrackManager, Timestamp, Duration, SampleCount, AudioOutput, PlaybackState}; +#[cfg(target_arch = "wasm32")] +use web_time::{Instant, Duration as StdDuration}; +#[cfg(not(target_arch = "wasm32"))] +use std::time::{Instant, Duration as StdDuration}; +use std::sync::atomic::Ordering; +use std::sync::atomic::AtomicU32; +use std::cell::Cell; +use std::collections::VecDeque; -#[derive(PartialEq)] +#[derive(PartialEq, Clone)] enum AudioState { - Suspended, - Running, + Suspended, + Running, } -// #[cfg(feature = "wasm")] -// use wasm_bindgen::prelude::*; +const DELAY_HISTORY_SIZE: usize = 5; + +#[derive(Default)] +struct StutterDetector { + delay_history: Mutex>, + desired_buffer_size: AtomicU32, + current_buffer_size: AtomicU32, + max_buffer_size: AtomicU32, + stutter_count: AtomicU32, + max_stutter_count: AtomicU32, + last_callback_time: Cell>, + scheduling_threshold: AtomicU32, +} -// #[cfg(feature="wasm")] -// #[wasm_bindgen] pub struct CpalAudioOutput { track_manager: Option>>, _stream: Option, @@ -22,12 +39,32 @@ pub struct CpalAudioOutput { timestamp: Arc>, chunk_size: usize, sample_rate: u32, + stutter_detector: Arc>, + resize_sender: crossbeam_channel::Sender<()>, // Or other channel implementation + resize_receiver: crossbeam_channel::Receiver<()>, +} + +impl StutterDetector { + fn new() -> Self { + Self { + delay_history: Mutex::new(VecDeque::with_capacity(DELAY_HISTORY_SIZE)), + desired_buffer_size: AtomicU32::new(256), + current_buffer_size: AtomicU32::new(256), + max_buffer_size: AtomicU32::new(8192), + stutter_count: AtomicU32::new(0), + max_stutter_count: AtomicU32::new(3), + last_callback_time: Cell::new(None), + scheduling_threshold: AtomicU32::new(1200), // 1.2 stored in fixed point + } + } + fn get_scheduling_threshold(&self) -> f32 { + self.scheduling_threshold.load(Ordering::Relaxed) as f32 / 1000.0 + } } -// #[cfg(feature="wasm")] -// #[wasm_bindgen] impl CpalAudioOutput { pub fn new() -> Self { + let (tx, rx) = crossbeam_channel::bounded(1); Self { track_manager: None, _stream: None, @@ -35,7 +72,10 @@ impl CpalAudioOutput { audio_state: AudioState::Suspended, timestamp: Arc::new(Mutex::new(Timestamp::from_seconds(0.0))), chunk_size: 0, - sample_rate: 44100, // Default sample rate, updated later + sample_rate: 44100, + stutter_detector: Arc::new(Mutex::new(StutterDetector::new())), + resize_sender: tx, + resize_receiver: rx, } } @@ -43,70 +83,213 @@ impl CpalAudioOutput { &mut self, device: &cpal::Device, config: cpal::SupportedStreamConfig, -) -> Result -where - T: Sample + From + cpal::SizedSample, -{ + ) -> Result + where + T: Sample + From + cpal::SizedSample, + { let supported_config = config.config(); self.sample_rate = supported_config.sample_rate.0; - let num_channels = supported_config.channels as usize; // Get channel count - + let num_channels = supported_config.channels as usize; + + let stutter_detector = self.stutter_detector.clone(); + let resize_sender = self.resize_sender.clone(); + let sample_rate = self.sample_rate; + let buffer_size_range = match config.buffer_size() { cpal::SupportedBufferSize::Range { min, max } => (*min, *max), - cpal::SupportedBufferSize::Unknown => { - // Use a reasonable default range if the device doesn't specify - (256, 4096) - } - }; - - // Define the desired buffer size and clamp it to the supported range - let desired_buffer_size = 2048; + cpal::SupportedBufferSize::Unknown => (256, 4096), + }; + + let detector_guard = self.stutter_detector.lock().unwrap(); + let desired_buffer_size = detector_guard.desired_buffer_size.load(Ordering::Relaxed); + drop(detector_guard); + let clamped_buffer_size = desired_buffer_size.clamp(buffer_size_range.0, buffer_size_range.1); - let mut stream_config = supported_config.clone(); stream_config.buffer_size = cpal::BufferSize::Fixed(clamped_buffer_size); - + + log::info!("Starting stream with buffer size {}", clamped_buffer_size); + let track_manager = self.track_manager.clone(); let timestamp = self.timestamp.clone(); - let sample_rate = self.sample_rate; - - let err_fn = |err| eprintln!("Audio stream error: {:?}", err); - + + let err_fn = |err| log::error!("Audio stream error: {:?}", err); + let stream = device.build_output_stream( - &stream_config, - move |data: &mut [T], _: &cpal::OutputCallbackInfo| { - if let Some(track_manager) = &track_manager { - let num_frames = data.len() / num_channels; // Stereo: divide by 2 - let sample_count = SampleCount::new(num_frames); - let chunk_duration = Duration::new(num_frames as f64 / sample_rate as f64); - - let mut track_manager = track_manager.lock().unwrap(); - - let mut timestamp_guard = timestamp.lock().unwrap(); - let timestamp = &mut *timestamp_guard; - - let chunk = track_manager.update_audio( - timestamp.clone(), - sample_count, - sample_rate, - ); - - // Write samples (interleaved stereo) - for (i, frame) in chunk.iter().enumerate() { - let sample = T::from(*frame); - data[i * num_channels] = sample; // Left channel - data[i * num_channels + 1] = sample; // Right channel (or process separately) + &stream_config, + move |data: &mut [T], _: &cpal::OutputCallbackInfo| { + // Timing measurement + let processing_start = if cfg!(target_arch = "wasm32") { + let perf = web_sys::window() + .and_then(|w| w.performance()) + .expect("performance should be available"); + let now_ms = perf.now(); + Instant::now() + StdDuration::from_secs_f64(now_ms / 1000.0) + } else { + Instant::now() + }; + + // Initialize resize flag outside of lock scope + let mut should_resize = false; + let current_size; + let buffer_duration; + let scheduling_threshold; + + { + let detector = stutter_detector.lock().unwrap(); + + // Update detector state + current_size = detector.current_buffer_size.load(Ordering::Relaxed); + buffer_duration = StdDuration::from_secs_f64(current_size as f64 / sample_rate as f64); + scheduling_threshold = detector.get_scheduling_threshold(); + + // Calculate scheduling delay + let last_time = detector.last_callback_time.get(); + // log::info!("Current size: {}", current_size); + + // Audio processing + if let Some(track_manager) = &track_manager { + let num_frames = data.len() / num_channels; // Stereo: divide by 2 + let sample_count = SampleCount::new(num_frames); + let chunk_duration = Duration::new(num_frames as f64 / sample_rate as f64); + + let mut track_manager = track_manager.lock().unwrap(); + + let mut timestamp_guard = timestamp.lock().unwrap(); + let timestamp = &mut *timestamp_guard; + + let chunk = track_manager.update_audio( + timestamp.clone(), + sample_count, + sample_rate, + ); + + // Write samples (interleaved stereo) + for (i, frame) in chunk.iter().enumerate() { + let sample = T::from(*frame); + for channel in 0..num_channels { + let index = i * num_channels + channel; + if index < data.len() { + data[index] = sample; } - - *timestamp_guard += chunk_duration; + } } - }, - err_fn, - None, + + *timestamp_guard += chunk_duration; + + // Stutter detection logic + let processing_time = processing_start.elapsed(); + let processing_overrun = processing_time > buffer_duration; + + // Update delay history + if let Some(last) = last_time { + let interval = processing_start.duration_since(last); + let mut history = detector.delay_history.lock().unwrap(); + if history.len() >= 5 { + history.pop_front(); + } + history.push_back(interval); + // log::info!("Interval: {:?}", interval); + } + + // Calculate average delay + let avg_delay = { + let history = detector.delay_history.lock().unwrap(); + if history.is_empty() { + StdDuration::ZERO + } else { + history.iter().sum::() / history.len() as u32 + } + }; + + // log::info!("Average delay: {:?}", avg_delay); + + // Determine stutter + let stutter_detected = avg_delay > buffer_duration.mul_f32(scheduling_threshold) + || processing_overrun; + + // Update stutter count with hysteresis + let current_count = detector.stutter_count.load(Ordering::Relaxed); + if stutter_detected { + detector.stutter_count.store( + (current_count + 1).min(detector.max_stutter_count.load(Ordering::Relaxed)), + Ordering::Relaxed + ); + } else { + detector.stutter_count.store( + current_count.saturating_sub(1), + Ordering::Relaxed + ); + } + + // Check for resize + if detector.stutter_count.load(Ordering::Relaxed) >= detector.max_stutter_count.load(Ordering::Relaxed) { + let desired_size = detector.desired_buffer_size.load(Ordering::Relaxed); + let new_size = (desired_size * 2).min(detector.max_buffer_size.load(Ordering::Relaxed)); + + if new_size != desired_size { + detector.desired_buffer_size.store(new_size, Ordering::Relaxed); + detector.stutter_count.store(0, Ordering::Relaxed); + should_resize = true; + } + } + } + + detector.last_callback_time.set(Some(processing_start)); + } + + // Send resize request outside of lock + if should_resize { + let _ = resize_sender.try_send(()); + } + }, + err_fn, + None, )?; + // Update current buffer size after stream creation + let detector = self.stutter_detector.lock().unwrap(); + detector.current_buffer_size.store(clamped_buffer_size, Ordering::Relaxed); + Ok(stream) } + + fn recreate_stream(&mut self) -> Result<(), Box> { + // Stop and destroy old stream first + if let Some(old_stream) = self._stream.take() { + old_stream.pause()?; + // Explicitly drop the stream + drop(old_stream); + } + + // Add a small delay to ensure resources are freed (especially important in WASM) + #[cfg(not(target_arch = "wasm32"))] + std::thread::sleep(std::time::Duration::from_millis(50)); + + #[cfg(target_arch = "wasm32")] + { + use wasm_bindgen_futures::spawn_local; + use gloo_timers::future::sleep; + spawn_local(async { + sleep(std::time::Duration::from_millis(50)).await; + }); + } + + // Recreate stream with current configuration + let host = cpal::default_host(); + let device = host.default_output_device() + .ok_or_else(|| "No output device available")?; + let supported_config = device.default_output_config()?; + + self._stream = Some(self.build_stream::(&device, supported_config)?); + + // Restart playback if needed + if self.audio_state == AudioState::Running { + self._stream.as_ref().unwrap().play()?; + } + + Ok(()) +} } impl AudioOutput for CpalAudioOutput { @@ -128,7 +311,7 @@ impl AudioOutput for CpalAudioOutput { fn stop(&mut self) { self.playback_state = PlaybackState::Stopped; } - + fn resume(&mut self) -> Result<(), anyhow::Error> { if self.audio_state == AudioState::Suspended { if let Some(stream) = &self._stream { @@ -150,4 +333,37 @@ impl AudioOutput for CpalAudioOutput { fn set_chunk_size(&mut self, chunk_size: usize) { self.chunk_size = chunk_size } -} + fn check_resize(&mut self) -> Result<(), Box> { + // Process resize requests with timeout + let timeout = StdDuration::from_millis(10); + while let Ok(()) = self.resize_receiver.try_recv() { + let start = Instant::now(); + + // Try to lock, non-blocking + { + let detector = match self.stutter_detector.try_lock() { + Ok(d) => d, + Err(_) => { + // Couldn't acquire lock immediately, skip this iteration + return Ok(()); + } + }; + + // Quick check before heavy operation + if detector.desired_buffer_size.load(Ordering::Relaxed) == detector.current_buffer_size.load(Ordering::Relaxed) { + continue; + } + detector.current_buffer_size.store(detector.desired_buffer_size.load(Ordering::Relaxed), Ordering::Relaxed); + } + + // Actual stream recreation + log::info!("Restarting stream"); + let _ = self.recreate_stream()?; + + if Instant::now().duration_since(start) > timeout { + break; + } + } + Ok(()) + } +} \ No newline at end of file diff --git a/lightningbeam-core/src/lib.rs b/lightningbeam-core/src/lib.rs index 227d597..de745fd 100644 --- a/lightningbeam-core/src/lib.rs +++ b/lightningbeam-core/src/lib.rs @@ -124,6 +124,7 @@ pub trait AudioOutput { fn register_track_manager(&mut self, track_manager: Arc>); fn get_timestamp(&mut self) -> Timestamp; fn set_chunk_size(&mut self, chunk_size: usize); + fn check_resize(&mut self) -> Result<(), Box>; } pub trait FrameTarget { @@ -372,64 +373,64 @@ fn decode_audio( ) -> Result<(), Box> { // Create a media source from the byte slice let mss = MediaSourceStream::new( - Box::new(Cursor::new(audio_data.to_vec())), - Default::default(), + Box::new(Cursor::new(audio_data.to_vec())), + Default::default(), ); - + // Use a fresh hint (no extension specified) for format detection let hint = Hint::new(); - + // Probe the media source for a supported format let probed = symphonia::default::get_probe() - .format(&hint, mss, &FormatOptions::default(), &MetadataOptions::default())?; - + .format(&hint, mss, &FormatOptions::default(), &MetadataOptions::default())?; + // Get the format reader let mut format = probed.format; - + // Find the first supported audio track let default_track = format - .tracks() - .iter() - .find(|t| t.codec_params.codec != symphonia::core::codecs::CODEC_TYPE_NULL) - .ok_or("No supported audio track found")?; - + .tracks() + .iter() + .find(|t| t.codec_params.codec != symphonia::core::codecs::CODEC_TYPE_NULL) + .ok_or("No supported audio track found")?; + // Create a decoder for the track let mut decoder = symphonia::default::get_codecs() - .make(&default_track.codec_params, &DecoderOptions::default())?; - + .make(&default_track.codec_params, &DecoderOptions::default())?; + // Get the sample rate from the track let sample_rate = default_track.codec_params.sample_rate.ok_or("Unknown sample rate")?; let mut decoded_samples = Vec::new(); - + // Decode loop loop { - let packet = match format.next_packet() { - Ok(packet) => packet, - Err(_) => break, // End of stream - }; - - match decoder.decode(&packet)? { - AudioBufferRef::F32(buf) => { - for i in 0..buf.frames() { - for c in 0..buf.spec().channels.count() { - decoded_samples.push(buf.chan(c)[i]); - } - } + let packet = match format.next_packet() { + Ok(packet) => packet, + Err(_) => break, // End of stream + }; + + match decoder.decode(&packet)? { + AudioBufferRef::F32(buf) => { + for i in 0..buf.frames() { + for c in 0..buf.spec().channels.count() { + decoded_samples.push(buf.chan(c)[i]); } - AudioBufferRef::S16(buf) => { - for i in 0..buf.frames() { - for c in 0..buf.spec().channels.count() { - decoded_samples.push(buf.chan(c)[i] as f32 / 32768.0); - } - } - } - _ => return Err("Unsupported audio format".into()), + } } + AudioBufferRef::S16(buf) => { + for i in 0..buf.frames() { + for c in 0..buf.spec().channels.count() { + decoded_samples.push(buf.chan(c)[i] as f32 / 32768.0); + } + } + } + _ => return Err("Unsupported audio format".into()), + } } - + // Add the decoded audio to the track track.add_buffer(start_time, sample_rate, decoded_samples); - + Ok(()) } @@ -469,7 +470,11 @@ pub struct CoreInterface { #[wasm_bindgen(skip)] track_manager: Arc>, #[wasm_bindgen(skip)] - cpal_audio_output: Box, + cpal_audio_output: Arc>, + #[wasm_bindgen(skip)] + resize_interval_id: Option, + #[wasm_bindgen(skip)] + resize_closure: Option>, } #[cfg(feature="wasm")] @@ -479,14 +484,21 @@ impl CoreInterface { pub fn new() -> Self { Self { track_manager: Arc::new(Mutex::new(TrackManager::new())), - cpal_audio_output: Box::new(CpalAudioOutput::new()) + cpal_audio_output: Arc::new(Mutex::new(CpalAudioOutput::new())), + resize_interval_id: None, + resize_closure: None, } } pub fn init(&mut self) { println!("Init CoreInterface"); - let track_manager_clone = self.track_manager.clone(); - self.cpal_audio_output.register_track_manager(track_manager_clone); - let _ = self.cpal_audio_output.start(); + { + let track_manager_clone = self.track_manager.clone(); + let mut cpal_audio_output = self.cpal_audio_output.lock().unwrap(); + cpal_audio_output.register_track_manager(track_manager_clone); + let _ = cpal_audio_output.start(); + } + + self.start_resize_polling(); } pub fn play(&mut self, timestamp: f64) { // Lock the Mutex to get access to TrackManager @@ -500,9 +512,52 @@ impl CoreInterface { } pub fn resume_audio(&mut self) -> Result<(), JsValue> { // Call this on user gestures if audio gets suspended - self.cpal_audio_output.resume() + self.cpal_audio_output.lock().unwrap().resume() .map_err(|e| JsValue::from_str(&format!("Failed to resume audio: {}", e))) } + // In CoreInterface + fn start_resize_polling(&mut self) { + #[cfg(target_arch = "wasm32")] + { + use wasm_bindgen::{prelude::*, JsCast}; + use js_sys::Array; + + let window = web_sys::window().unwrap(); + let audio_output = Arc::clone(&self.cpal_audio_output); + + // Use weak reference to break cycle + let weak_audio = Arc::downgrade(&audio_output); + let closure = Closure::::new(move || { + if let Some(audio) = weak_audio.upgrade() { + // NON-BLOCKING lock attempt + if let Ok(mut audio) = audio.try_lock() { + let _ = audio.check_resize(); + } + } + }); + + let args = Array::new(); + let interval_id = window + .set_interval_with_callback_and_timeout_and_arguments( + closure.as_ref().unchecked_ref(), + 50, + &args + ) + .unwrap(); + + self.resize_interval_id = Some(interval_id); + self.resize_closure = Some(closure); + } + + #[cfg(not(target_arch = "wasm32"))] + { + let mut audio = self.cpal_audio_output.clone(); + std::thread::spawn(move || loop { + let _ = audio.check_resize(); + std::thread::sleep(std::time::Duration::from_millis(50)); + }); + } + } pub fn add_sine_track(&mut self, frequency: f32) -> Result<(), String> { if frequency.is_nan() || frequency.is_infinite() || frequency <= 0.0 { return Err(format!("Invalid frequency: {}", frequency)); @@ -516,7 +571,7 @@ impl CoreInterface { } pub fn get_timestamp(&mut self) -> f64 { - self.cpal_audio_output.get_timestamp().as_seconds() + self.cpal_audio_output.lock().unwrap().get_timestamp().as_seconds() } pub fn get_tracks(&mut self) -> Vec { let track_manager = self.track_manager.lock().unwrap(); @@ -528,8 +583,20 @@ impl CoreInterface { }) .collect() } + } +// Cleanup implementation +#[cfg(feature = "wasm")] +impl Drop for CoreInterface { + fn drop(&mut self) { + if let Some(interval_id) = self.resize_interval_id { + web_sys::window() + .unwrap() + .clear_interval_with_handle(interval_id); + } + } +} struct PlainTextLogger; diff --git a/src/pkg/lightningbeam_core.d.ts b/src/pkg/lightningbeam_core.d.ts index 3b8c0e8..91bd9d3 100644 --- a/src/pkg/lightningbeam_core.d.ts +++ b/src/pkg/lightningbeam_core.d.ts @@ -42,7 +42,9 @@ export interface InitOutput { readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; readonly __wbindgen_free: (a: number, b: number, c: number) => void; - readonly _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hfe92e343adbc229b: (a: number, b: number) => void; + readonly _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h078d17eff0b70a99: (a: number, b: number) => void; + readonly _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h26b4819ece79f796: (a: number, b: number) => void; + readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hb1f2a4138d993283: (a: number, b: number, c: number) => void; readonly __wbindgen_start: () => void; } diff --git a/src/pkg/lightningbeam_core.js b/src/pkg/lightningbeam_core.js index 1df0b89..42bafa0 100644 --- a/src/pkg/lightningbeam_core.js +++ b/src/pkg/lightningbeam_core.js @@ -26,6 +26,18 @@ function handleError(f, args) { } } +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + let cachedFloat32ArrayMemory0 = null; function getFloat32ArrayMemory0() { @@ -62,18 +74,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function dropObject(idx) { - if (idx < 132) return; - heap[idx] = heap_next; - heap_next = idx; -} - -function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -249,8 +249,16 @@ export function main_js() { wasm.main_js(); } -function __wbg_adapter_18(arg0, arg1) { - wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hfe92e343adbc229b(arg0, arg1); +function __wbg_adapter_20(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h078d17eff0b70a99(arg0, arg1); +} + +function __wbg_adapter_23(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h26b4819ece79f796(arg0, arg1); +} + +function __wbg_adapter_26(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hb1f2a4138d993283(arg0, arg1, addHeapObject(arg2)); } const CoreInterfaceFinalization = (typeof FinalizationRegistry === 'undefined') @@ -445,6 +453,13 @@ function __wbg_get_imports() { const ret = getObject(arg0).call(getObject(arg1)); return addHeapObject(ret); }, arguments) }; + imports.wbg.__wbg_clearInterval_ad2594253cc39c4b = function(arg0, arg1) { + getObject(arg0).clearInterval(arg1); + }; + imports.wbg.__wbg_clearTimeout_96804de0ab838f26 = function(arg0) { + const ret = clearTimeout(takeObject(arg0)); + return addHeapObject(ret); + }; imports.wbg.__wbg_close_5a97ef05b337f8ce = function() { return handleError(function (arg0) { const ret = getObject(arg0).close(); return addHeapObject(ret); @@ -501,6 +516,10 @@ function __wbg_get_imports() { const ret = new Object(); return addHeapObject(ret); }; + imports.wbg.__wbg_new_78feb108b6472713 = function() { + const ret = new Array(); + return addHeapObject(ret); + }; imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return addHeapObject(ret); @@ -509,10 +528,45 @@ function __wbg_get_imports() { const ret = new lAudioContext(getObject(arg0)); return addHeapObject(ret); }, arguments) }; + imports.wbg.__wbg_now_6f91d421b96ea22a = function(arg0) { + const ret = getObject(arg0).now(); + return ret; + }; + imports.wbg.__wbg_now_d18023d54d4e5500 = function(arg0) { + const ret = getObject(arg0).now(); + return ret; + }; + imports.wbg.__wbg_performance_c185c0cdc2766575 = function(arg0) { + const ret = getObject(arg0).performance; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_performance_f71bd4abe0370171 = function(arg0) { + const ret = getObject(arg0).performance; + return addHeapObject(ret); + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + queueMicrotask(getObject(arg0)); + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + const ret = getObject(arg0).queueMicrotask; + return addHeapObject(ret); + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + const ret = Promise.resolve(getObject(arg0)); + return addHeapObject(ret); + }; imports.wbg.__wbg_resume_35efdc4ffe13bf18 = function() { return handleError(function (arg0) { const ret = getObject(arg0).resume(); return addHeapObject(ret); }, arguments) }; + imports.wbg.__wbg_setInterval_83d54331ceeda644 = function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = getObject(arg0).setInterval(getObject(arg1), arg2, ...getObject(arg3)); + return ret; + }, arguments) }; + imports.wbg.__wbg_setTimeout_eefe7f4c234b0c6b = function() { return handleError(function (arg0, arg1) { + const ret = setTimeout(getObject(arg0), arg1); + return addHeapObject(ret); + }, arguments) }; imports.wbg.__wbg_setTimeout_f2fe5af8e3debeb3 = function() { return handleError(function (arg0, arg1, arg2) { const ret = getObject(arg0).setTimeout(getObject(arg1), arg2); return ret; @@ -548,6 +602,14 @@ function __wbg_get_imports() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addHeapObject(ret); }; + imports.wbg.__wbg_suspend_6011a41599f07de4 = function() { return handleError(function (arg0) { + const ret = getObject(arg0).suspend(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + const ret = getObject(arg0).then(getObject(arg1)); + return addHeapObject(ret); + }; imports.wbg.__wbindgen_boolean_get = function(arg0) { const v = getObject(arg0); const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; @@ -562,8 +624,16 @@ function __wbg_get_imports() { const ret = false; return ret; }; - imports.wbg.__wbindgen_closure_wrapper151 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 73, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper207 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 82, __wbg_adapter_20); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper265 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 109, __wbg_adapter_23); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper283 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 116, __wbg_adapter_26); return addHeapObject(ret); }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { @@ -573,6 +643,10 @@ function __wbg_get_imports() { getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; + imports.wbg.__wbindgen_is_function = function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; + }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = getObject(arg0) === undefined; return ret; diff --git a/src/pkg/lightningbeam_core_bg.wasm b/src/pkg/lightningbeam_core_bg.wasm index 2d2ab1d3a5a35baef4c0bff45a64f17356355b42..e80499dbcd8a5f2cdbc91ca3eebdcc41165815b9 100644 GIT binary patch delta 73360 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 C1LEKS diff --git a/src/pkg/lightningbeam_core_bg.wasm.d.ts b/src/pkg/lightningbeam_core_bg.wasm.d.ts index 7a3debd..882e2b7 100644 --- a/src/pkg/lightningbeam_core_bg.wasm.d.ts +++ b/src/pkg/lightningbeam_core_bg.wasm.d.ts @@ -20,5 +20,7 @@ export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_add_to_stack_pointer: (a: number) => number; export const __wbindgen_free: (a: number, b: number, c: number) => void; -export const _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hfe92e343adbc229b: (a: number, b: number) => void; +export const _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h078d17eff0b70a99: (a: number, b: number) => void; +export const _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h26b4819ece79f796: (a: number, b: number) => void; +export const _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hb1f2a4138d993283: (a: number, b: number, c: number) => void; export const __wbindgen_start: () => void; From ff76b93b9d89178e7d83ce3a144fa262ed2200af Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Fri, 31 Jan 2025 07:12:12 -0500 Subject: [PATCH 7/7] improve detection --- lightningbeam-core/src/audio.rs | 45 ++++++++++++++++++----------- src/pkg/lightningbeam_core.js | 2 +- src/pkg/lightningbeam_core_bg.wasm | Bin 168521 -> 168773 bytes 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lightningbeam-core/src/audio.rs b/lightningbeam-core/src/audio.rs index 92d157d..a81867a 100644 --- a/lightningbeam-core/src/audio.rs +++ b/lightningbeam-core/src/audio.rs @@ -57,6 +57,9 @@ impl StutterDetector { scheduling_threshold: AtomicU32::new(1200), // 1.2 stored in fixed point } } + pub fn reset(&mut self) { + *self = Self::new(); + } fn get_scheduling_threshold(&self) -> f32 { self.scheduling_threshold.load(Ordering::Relaxed) as f32 / 1000.0 } @@ -201,7 +204,7 @@ impl CpalAudioOutput { history.iter().sum::() / history.len() as u32 } }; - + // log::info!("Average delay: {:?}", avg_delay); // Determine stutter @@ -246,50 +249,58 @@ impl CpalAudioOutput { err_fn, None, )?; - + // Update current buffer size after stream creation let detector = self.stutter_detector.lock().unwrap(); detector.current_buffer_size.store(clamped_buffer_size, Ordering::Relaxed); Ok(stream) } - + fn recreate_stream(&mut self) -> Result<(), Box> { // Stop and destroy old stream first if let Some(old_stream) = self._stream.take() { - old_stream.pause()?; - // Explicitly drop the stream - drop(old_stream); + old_stream.pause()?; + // Explicitly drop the stream + drop(old_stream); } - + // Add a small delay to ensure resources are freed (especially important in WASM) #[cfg(not(target_arch = "wasm32"))] std::thread::sleep(std::time::Duration::from_millis(50)); #[cfg(target_arch = "wasm32")] { - use wasm_bindgen_futures::spawn_local; - use gloo_timers::future::sleep; - spawn_local(async { - sleep(std::time::Duration::from_millis(50)).await; - }); + use wasm_bindgen_futures::spawn_local; + use gloo_timers::future::sleep; + spawn_local(async { + sleep(std::time::Duration::from_millis(50)).await; + }); } - + // Recreate stream with current configuration let host = cpal::default_host(); let device = host.default_output_device() - .ok_or_else(|| "No output device available")?; + .ok_or_else(|| "No output device available")?; let supported_config = device.default_output_config()?; + { + let mut detector = self.stutter_detector.lock().unwrap(); + let desired_buffer_size = detector.desired_buffer_size.load(Ordering::Relaxed); + detector.reset(); + detector.desired_buffer_size.store(desired_buffer_size, Ordering::Relaxed); + } + // let mut history = detector.delay_history.lock().unwrap(); + self._stream = Some(self.build_stream::(&device, supported_config)?); // Restart playback if needed if self.audio_state == AudioState::Running { - self._stream.as_ref().unwrap().play()?; + self._stream.as_ref().unwrap().play()?; } - + Ok(()) -} + } } impl AudioOutput for CpalAudioOutput { diff --git a/src/pkg/lightningbeam_core.js b/src/pkg/lightningbeam_core.js index 42bafa0..d18ce67 100644 --- a/src/pkg/lightningbeam_core.js +++ b/src/pkg/lightningbeam_core.js @@ -624,7 +624,7 @@ function __wbg_get_imports() { const ret = false; return ret; }; - imports.wbg.__wbindgen_closure_wrapper207 = function(arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper208 = function(arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 82, __wbg_adapter_20); return addHeapObject(ret); }; diff --git a/src/pkg/lightningbeam_core_bg.wasm b/src/pkg/lightningbeam_core_bg.wasm index e80499dbcd8a5f2cdbc91ca3eebdcc41165815b9..dc07e5d5c195929c53305ba401a6bc45e7559205 100644 GIT binary patch delta 21602 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 delta 21229 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