diff --git a/lightningbeam-core/Cargo.lock b/lightningbeam-core/Cargo.lock new file mode 100644 index 0000000..8740cb0 --- /dev/null +++ b/lightningbeam-core/Cargo.lock @@ -0,0 +1,1342 @@ +# 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.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.8.0", + "cfg-if", + "libc", +] + +[[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 = "anyhow" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[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", +] + +[[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 = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" + +[[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.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys", + "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.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa", + "core-foundation-sys", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "either" +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 = "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 = "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 = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[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 = "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" +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 0.52.6", +] + +[[package]] +name = "lightningbeam-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "atomic_refcell", + "console_error_panic_hook", + "cpal", + "crossbeam-channel", + "gloo-timers", + "js-sys", + "log", + "parking_lot", + "rubato", + "serde", + "symphonia", + "wasm-bindgen", + "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]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.8.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "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.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[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-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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +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 0.52.6", +] + +[[package]] +name = "pkg-config" +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" +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 = "realfft" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390252372b7f2aac8360fc5e72eba10136b166d6faeed97e6d0c8324eb99b2b1" +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" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[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 = "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", +] + +[[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 = "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" +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", +] + +[[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 = "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" +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", + "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" +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", + "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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +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 0.59.0", +] + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "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]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "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]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[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..3eb80d5 --- /dev/null +++ b/lightningbeam-core/Cargo.toml @@ -0,0 +1,42 @@ +[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.45" +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"] } +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", "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 +# 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/build.sh b/lightningbeam-core/build.sh new file mode 100755 index 0000000..4992bb3 --- /dev/null +++ b/lightningbeam-core/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +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/audio.rs b/lightningbeam-core/src/audio.rs new file mode 100644 index 0000000..a81867a --- /dev/null +++ b/lightningbeam-core/src/audio.rs @@ -0,0 +1,380 @@ +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, Clone)] +enum AudioState { + Suspended, + Running, +} + +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, +} + +pub struct CpalAudioOutput { + track_manager: Option>>, + _stream: Option, + playback_state: PlaybackState, + audio_state: AudioState, + 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 + } + } + pub fn reset(&mut self) { + *self = Self::new(); + } + fn get_scheduling_threshold(&self) -> f32 { + self.scheduling_threshold.load(Ordering::Relaxed) as f32 / 1000.0 + } +} + +impl CpalAudioOutput { + pub fn new() -> Self { + let (tx, rx) = crossbeam_channel::bounded(1); + Self { + 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, + stutter_detector: Arc::new(Mutex::new(StutterDetector::new())), + resize_sender: tx, + resize_receiver: rx, + } + } + + 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; + + 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 => (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 err_fn = |err| log::error!("Audio stream error: {:?}", err); + + let stream = device.build_output_stream( + &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; + + // 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()?; + + { + 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()?; + } + + Ok(()) + } +} + +impl AudioOutput for CpalAudioOutput { + fn start(&mut self) -> Result<(), Box> { + 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)?); + Ok(()) + } + + fn play(&mut self, start_timestamp: Timestamp) { + self.timestamp.lock().unwrap().set(start_timestamp); + self.playback_state = PlaybackState::Playing; + } + + 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); + } + + fn get_timestamp(&mut self) -> Timestamp { + *self.timestamp.lock().unwrap() + } + 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 new file mode 100644 index 0000000..de745fd --- /dev/null +++ b/lightningbeam-core/src/lib.rs @@ -0,0 +1,679 @@ +mod time; +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")] +mod wasm_imports { + pub use wasm_bindgen::prelude::*; + pub use web_sys::console; +} +#[cfg(feature = "wasm")] +use wasm_imports::*; + + +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. + /// 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 + } + + // Set the sample rate of any audio this track might contain + fn set_sample_rate(&mut self, _sample_rate: u32) { + } +} + +pub struct TrackManager { + tracks: Vec>, + timestamp: Timestamp, + playback_state: PlaybackState, +} + +impl TrackManager { + pub fn new() -> Self { + Self { + tracks: Vec::new(), + timestamp: Timestamp::from_seconds(0.0), + playback_state: PlaybackState::Stopped, + } + } + + pub fn add_track(&mut self, track: Box) { + self.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; + } + pub fn get_tracks(&self) -> &Vec> { + &self.tracks + } +} + +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); + fn check_resize(&mut self) -> Result<(), Box>; +} + +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, + name: String, +} + +impl SineWaveTrack { + pub fn new(frequency: f32) -> Self { + Self { + frequency, + phase: 0.0, + name: "Sine Wave Track".to_string(), + } + } +} + +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) / sample_rate as f32; + + for _ in 0..chunk_size.as_usize() { + 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; + } + } + + Some(chunk) + } +} + +#[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, +} + +#[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] +pub struct CoreInterface { + #[wasm_bindgen(skip)] + track_manager: Arc>, + #[wasm_bindgen(skip)] + cpal_audio_output: Arc>, + #[wasm_bindgen(skip)] + resize_interval_id: Option, + #[wasm_bindgen(skip)] + resize_closure: Option>, +} + +#[cfg(feature="wasm")] +#[wasm_bindgen] +impl CoreInterface { + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self { + track_manager: Arc::new(Mutex::new(TrackManager::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(); + 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 + 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 resume_audio(&mut self) -> Result<(), JsValue> { + // Call this on user gestures if audio gets suspended + 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)); + } + 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.lock().unwrap().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() + } + +} + +// 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; + +impl Log for PlainTextLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Info + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + #[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) { + #[cfg(not(target_arch = "wasm32"))] + { + let _ = io::stderr().flush(); + } + } +} + +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::*; + + // #[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(); + 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 new file mode 100644 index 0000000..7458686 --- /dev/null +++ b/lightningbeam-core/src/time.rs @@ -0,0 +1,231 @@ +/// 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); + +/// 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) + } + + /// 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) + } + + 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 { + /// Create a new duration in seconds. + pub fn new(seconds: f64) -> Self { + 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) + } + + /// 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 + } + + 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) + } + + /// Subtract one duration from another. + pub fn subtract(&self, other: Duration) -> Duration { + Duration(self.0 - other.0) + } +} + + +impl SampleCount { + /// Create a new count of samples. + pub fn new(samples: usize) -> Self { + SampleCount(samples) + } + + 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 +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 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; + } +} + +impl SubAssign for Timestamp { + fn sub_assign(&mut self, duration: Duration) { + self.0 -= duration.0; + } +} + + +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 { + 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 } + } +} diff --git a/src/main.js b/src/main.js index 475b157..d27025f 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,35 @@ 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) + + 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 new file mode 100644 index 0000000..91bd9d3 --- /dev/null +++ b/src/pkg/lightningbeam_core.d.ts @@ -0,0 +1,70 @@ +/* tslint:disable */ +/* eslint-disable */ +export function main_js(): void; +export class CoreInterface { + free(): void; + constructor(); + init(): void; + play(timestamp: number): void; + stop(): void; + resume_audio(): 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_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; + 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__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; +} + +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..d18ce67 --- /dev/null +++ b/src/pkg/lightningbeam_core.js @@ -0,0 +1,742 @@ +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)); + } +} + +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() { + 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; +} + +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_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') + ? { 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); + } + 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 + */ + 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_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); + }, 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_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); + }; + imports.wbg.__wbg_newwithcontextoptions_b62c06fed7900366 = function() { return handleError(function (arg0) { + 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; + }, 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.__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; + 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_wrapper208 = 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) { + 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_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; + }; + 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 0000000..dc07e5d Binary files /dev/null and b/src/pkg/lightningbeam_core_bg.wasm differ diff --git a/src/pkg/lightningbeam_core_bg.wasm.d.ts b/src/pkg/lightningbeam_core_bg.wasm.d.ts new file mode 100644 index 0000000..882e2b7 --- /dev/null +++ b/src/pkg/lightningbeam_core_bg.wasm.d.ts @@ -0,0 +1,26 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const __wbg_jstrack_free: (a: number, b: number) => 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_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; +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__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; 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