diff --git a/daw-backend/src/lib.rs b/daw-backend/src/lib.rs index 5d09468..a17fbeb 100644 --- a/daw-backend/src/lib.rs +++ b/daw-backend/src/lib.rs @@ -37,6 +37,8 @@ pub struct AudioSystem { pub stream: cpal::Stream, pub sample_rate: u32, pub channels: u32, + /// Event receiver for polling audio events (only present when no EventEmitter is provided) + pub event_rx: Option>, } impl AudioSystem { @@ -152,6 +154,7 @@ impl AudioSystem { stream: output_stream, sample_rate, channels, + event_rx: None, // No event receiver when audio device unavailable }); } }; @@ -179,6 +182,7 @@ impl AudioSystem { stream: output_stream, sample_rate, channels, + event_rx: None, // No event receiver when audio device unavailable }); } }; @@ -205,16 +209,20 @@ impl AudioSystem { // Leak the input stream to keep it alive Box::leak(Box::new(input_stream)); - // Spawn emitter thread if provided - if let Some(emitter) = event_emitter { + // Spawn emitter thread if provided, or store event_rx for manual polling + let event_rx_option = if let Some(emitter) = event_emitter { Self::spawn_emitter_thread(event_rx, emitter); - } + None + } else { + Some(event_rx) + }; Ok(Self { controller, stream: output_stream, sample_rate, channels, + event_rx: event_rx_option, }) } diff --git a/lightningbeam-ui/Cargo.lock b/lightningbeam-ui/Cargo.lock index 6e2d885..04cf011 100644 --- a/lightningbeam-ui/Cargo.lock +++ b/lightningbeam-ui/Cargo.lock @@ -138,6 +138,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "aligned-vec" version = "0.6.4" @@ -147,6 +156,46 @@ dependencies = [ "equator", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alsa" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2562ad8dcf0f789f65c6fdaad8a8a9708ed6b488e649da28c01656ad66b8b47" +dependencies = [ + "alsa-sys", + "bitflags 1.3.2", + "libc", + "nix 0.24.3", +] + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.10.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 = "android-activity" version = "0.6.0" @@ -161,7 +210,7 @@ dependencies = [ "jni-sys", "libc", "log", - "ndk", + "ndk 0.9.0", "ndk-context", "ndk-sys 0.6.0+11769913", "num_enum", @@ -537,7 +586,7 @@ dependencies = [ "anyhow", "arrayvec", "log", - "nom", + "nom 8.0.0", "num-rational", "v_frame", ] @@ -572,6 +621,24 @@ dependencies = [ "simd-abstraction", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.1", + "shlex", + "syn 2.0.110", +] + [[package]] name = "bit-set" version = "0.6.0" @@ -805,6 +872,21 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.2.45" @@ -823,6 +905,15 @@ 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 7.1.3", +] + [[package]] name = "cfg-expr" version = "0.15.8" @@ -860,6 +951,17 @@ dependencies = [ "libc", ] +[[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 = "clap" version = "4.5.51" @@ -978,6 +1080,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1066,6 +1181,69 @@ dependencies = [ "libc", ] +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +dependencies = [ + "bindgen", +] + +[[package]] +name = "coremidi" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7847ca018a67204508b77cb9e6de670125075f7464fff5f673023378fa34f5" +dependencies = [ + "core-foundation 0.9.4", + "core-foundation-sys", + "coremidi-sys", +] + +[[package]] +name = "coremidi-sys" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9504310988d938e49fff1b5f1e56e3dafe39bb1bae580c19660b58b83a191e" +dependencies = [ + "core-foundation-sys", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa 0.9.1", + "core-foundation-sys", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk 0.8.0", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.54.0", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1118,6 +1296,31 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.10.0", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -1196,6 +1399,120 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "dasp_envelope" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec617ce7016f101a87fe85ed44180839744265fae73bb4aa43e7ece1b7668b6" +dependencies = [ + "dasp_frame", + "dasp_peak", + "dasp_ring_buffer", + "dasp_rms", + "dasp_sample", +] + +[[package]] +name = "dasp_frame" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a3937f5fe2135702897535c8d4a5553f8b116f76c1529088797f2eee7c5cd6" +dependencies = [ + "dasp_sample", +] + +[[package]] +name = "dasp_graph" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39b17b071a1fa4c78054730085620c3bb22dc5fded00483312557a3fdf26d7c4" +dependencies = [ + "dasp_frame", + "dasp_ring_buffer", + "dasp_signal", + "dasp_slice", + "petgraph 0.5.1", +] + +[[package]] +name = "dasp_interpolate" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc975a6563bb7ca7ec0a6c784ead49983a21c24835b0bc96eea11ee407c7486" +dependencies = [ + "dasp_frame", + "dasp_ring_buffer", + "dasp_sample", +] + +[[package]] +name = "dasp_peak" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf88559d79c21f3d8523d91250c397f9a15b5fc72fbb3f87fdb0a37b79915bf" +dependencies = [ + "dasp_frame", + "dasp_sample", +] + +[[package]] +name = "dasp_ring_buffer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d79e19b89618a543c4adec9c5a347fe378a19041699b3278e616e387511ea1" + +[[package]] +name = "dasp_rms" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c5dcb30b7e5014486e2822537ea2beae50b19722ffe2ed7549ab03774575aa" +dependencies = [ + "dasp_frame", + "dasp_ring_buffer", + "dasp_sample", +] + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "dasp_signal" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa1ab7d01689c6ed4eae3d38fe1cea08cba761573fbd2d592528d55b421077e7" +dependencies = [ + "dasp_envelope", + "dasp_frame", + "dasp_interpolate", + "dasp_peak", + "dasp_ring_buffer", + "dasp_rms", + "dasp_sample", + "dasp_window", +] + +[[package]] +name = "dasp_slice" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e1c7335d58e7baedafa516cb361360ff38d6f4d3f9d9d5ee2a2fc8e27178fa1" +dependencies = [ + "dasp_frame", + "dasp_sample", +] + +[[package]] +name = "dasp_window" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ded7b88821d2ce4e8b842c9f1c86ac911891ab89443cc1de750cae764c5076" +dependencies = [ + "dasp_sample", +] + [[package]] name = "data-encoding" version = "2.9.0" @@ -1217,6 +1534,33 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +[[package]] +name = "daw-backend" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "cpal", + "crossterm", + "dasp_envelope", + "dasp_graph", + "dasp_interpolate", + "dasp_peak", + "dasp_ring_buffer", + "dasp_rms", + "dasp_sample", + "dasp_signal", + "midir", + "midly", + "pathdiff", + "petgraph 0.6.5", + "rand", + "ratatui", + "rtrb", + "serde", + "serde_json", + "symphonia", +] + [[package]] name = "digest" version = "0.10.7" @@ -1444,6 +1788,15 @@ dependencies = [ "bytemuck", ] +[[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 = "endi" version = "1.1.0" @@ -1602,6 +1955,12 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + [[package]] name = "fastrand" version = "2.3.0" @@ -1653,6 +2012,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.1.5" @@ -2046,6 +2417,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "glow" version = "0.13.1" @@ -2293,6 +2670,8 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", ] @@ -2504,6 +2883,16 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.12.0" @@ -2551,6 +2940,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -2750,6 +3148,7 @@ name = "lightningbeam-editor" version = "0.1.0" dependencies = [ "clap", + "daw-backend", "eframe", "egui-wgpu", "egui_extras", @@ -2761,6 +3160,7 @@ dependencies = [ "peniko 0.5.0", "pollster", "resvg 0.42.0", + "rtrb", "serde", "serde_json", "uuid", @@ -2783,7 +3183,7 @@ dependencies = [ "dashmap", "data-encoding", "getrandom 0.3.4", - "indexmap", + "indexmap 2.12.0", "itertools 0.10.5", "lazy_static", "lightningcss-derive", @@ -2863,6 +3263,24 @@ dependencies = [ "imgref", ] +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2927,6 +3345,31 @@ dependencies = [ "paste", ] +[[package]] +name = "midir" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a456444d83e7ead06ae6a5c0a215ed70282947ff3897fb45fcb052b757284731" +dependencies = [ + "alsa 0.7.1", + "bitflags 1.3.2", + "coremidi", + "js-sys", + "libc", + "wasm-bindgen", + "web-sys", + "windows 0.43.0", +] + +[[package]] +name = "midly" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207d755f4cb882d20c4da58d707ca9130a0c9bc5061f657a4f299b8e36362b7a" +dependencies = [ + "rayon", +] + [[package]] name = "mime" version = "0.3.17" @@ -2945,6 +3388,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2955,6 +3404,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "moxcms" version = "0.7.9" @@ -2997,7 +3458,7 @@ dependencies = [ "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap", + "indexmap 2.12.0", "log", "rustc-hash 1.1.0", "spirv", @@ -3006,6 +3467,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum", + "thiserror 1.0.69", +] + [[package]] name = "ndk" version = "0.9.0" @@ -3051,6 +3526,17 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + [[package]] name = "nix" version = "0.29.0" @@ -3070,6 +3556,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[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 = "nom" version = "8.0.0" @@ -3437,6 +3933,29 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni", + "ndk 0.8.0", + "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.21.3" @@ -3613,6 +4132,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset 0.2.0", + "indexmap 1.9.3", +] + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset 0.4.2", + "indexmap 2.12.0", +] + [[package]] name = "phf" version = "0.11.3" @@ -3999,6 +4538,26 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" +[[package]] +name = "ratatui" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +dependencies = [ + "bitflags 2.10.0", + "cassowary", + "compact_str", + "crossterm", + "itertools 0.12.1", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + [[package]] name = "rav1e" version = "0.7.1" @@ -4109,6 +4668,35 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + [[package]] name = "rend" version = "0.4.2" @@ -4204,6 +4792,12 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rtrb" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8388ea1a9e0ea807e442e8263a699e7edcb320ecbcd21b4fa8ff859acce3ba" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -4414,6 +5008,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.6" @@ -4589,6 +5204,16 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.110", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4616,6 +5241,28 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.110", +] + [[package]] name = "svg_fmt" version = "0.4.5" @@ -4642,6 +5289,201 @@ dependencies = [ "siphasher 1.0.1", ] +[[package]] +name = "symphonia" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac", + "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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-adpcm" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dddc50e2bbea4cfe027441eece77c46b9f319748605ab8f3443350129ddd07f" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-alac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8413fa754942ac16a73634c9dfd1500ed5c61430956b33728567f667fdd393ab" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-caf" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8faf379316b6b6e6bbc274d00e7a592e0d63ff1a7e182ce8ba25e24edd3d096" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-mkv" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122d786d2c43a49beb6f397551b4a050d8229eaa54c7ddf9ee4b98899b8742d0" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f" +dependencies = [ + "extended", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" version = "1.0.109" @@ -4863,7 +5705,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.12.0", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -4874,7 +5716,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap", + "indexmap 2.12.0", "serde", "serde_spanned", "toml_datetime 0.6.3", @@ -4887,7 +5729,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap", + "indexmap 2.12.0", "toml_datetime 0.7.3", "toml_parser", "winnow 0.7.13", @@ -5020,6 +5862,17 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "unicode-vo" version = "0.1.0" @@ -5515,7 +6368,7 @@ dependencies = [ "bitflags 2.10.0", "cfg_aliases 0.1.1", "document-features", - "indexmap", + "indexmap 2.12.0", "log", "naga", "once_cell", @@ -5622,6 +6475,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244" +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" version = "0.52.0" @@ -5632,6 +6500,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.58.0" @@ -5651,6 +6529,16 @@ dependencies = [ "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 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.58.0" @@ -5659,7 +6547,7 @@ checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ "windows-implement", "windows-interface", - "windows-result", + "windows-result 0.2.0", "windows-strings", "windows-targets 0.52.6", ] @@ -5692,6 +6580,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[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-result" version = "0.2.0" @@ -5707,7 +6604,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] @@ -5720,6 +6617,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -5771,6 +6677,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -5810,6 +6731,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -5828,6 +6755,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -5846,6 +6779,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5876,6 +6815,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -5894,6 +6839,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -5912,6 +6863,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -5930,6 +6887,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -5964,7 +6927,7 @@ dependencies = [ "js-sys", "libc", "memmap2", - "ndk", + "ndk 0.9.0", "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", @@ -6167,7 +7130,7 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix", + "nix 0.29.0", "ordered-stream", "rand", "serde", diff --git a/lightningbeam-ui/lightningbeam-editor/Cargo.toml b/lightningbeam-ui/lightningbeam-editor/Cargo.toml index e6a245f..61b1113 100644 --- a/lightningbeam-ui/lightningbeam-editor/Cargo.toml +++ b/lightningbeam-ui/lightningbeam-editor/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] lightningbeam-core = { path = "../lightningbeam-core" } daw-backend = { path = "../../daw-backend" } +rtrb = "0.3" # UI Framework eframe = { workspace = true } diff --git a/lightningbeam-ui/lightningbeam-editor/src/main.rs b/lightningbeam-ui/lightningbeam-editor/src/main.rs index b8dac2e..1bc8334 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/main.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/main.rs @@ -267,8 +267,10 @@ struct EditorApp { rdp_tolerance: f64, // RDP simplification tolerance (default: 10.0) schneider_max_error: f64, // Schneider curve fitting max error (default: 30.0) // Audio engine integration - audio_controller: Option, // Audio engine controller for playback - audio_event_rx: Option>, // Audio event receiver + audio_system: Option, // Audio system (must be kept alive for stream) + // Playback state (global for all panes) + playback_time: f64, // Current playback position in seconds (persistent - save with document) + is_playing: bool, // Whether playback is currently active (transient - don't save) } impl EditorApp { @@ -302,6 +304,19 @@ impl EditorApp { // Wrap document in ActionExecutor let action_executor = lightningbeam_core::action::ActionExecutor::new(document); + // Initialize audio system (keep the whole system to maintain the audio stream) + let audio_system = match daw_backend::AudioSystem::new(None, 256) { + Ok(audio_system) => { + println!("✅ Audio engine initialized successfully"); + Some(audio_system) + } + Err(e) => { + eprintln!("❌ Failed to initialize audio engine: {}", e); + eprintln!(" Playback will be disabled"); + None + } + }; + Self { layouts, current_layout_index: 0, @@ -327,6 +342,9 @@ impl EditorApp { draw_simplify_mode: lightningbeam_core::tool::SimplifyMode::Smooth, // Default to smooth curves rdp_tolerance: 10.0, // Default RDP tolerance schneider_max_error: 30.0, // Default Schneider max error + audio_system, + playback_time: 0.0, // Start at beginning + is_playing: false, // Start paused } } @@ -665,6 +683,29 @@ impl eframe::App for EditorApp { } } + // Poll audio events from the audio engine + if let Some(audio_system) = &mut self.audio_system { + if let Some(event_rx) = &mut audio_system.event_rx { + while let Ok(event) = event_rx.pop() { + use daw_backend::AudioEvent; + match event { + AudioEvent::PlaybackPosition(time) => { + self.playback_time = time; + } + AudioEvent::PlaybackStopped => { + self.is_playing = false; + } + _ => {} // Ignore other events for now + } + } + } + } + + // Request continuous repaints when playing to update time display + if self.is_playing { + ctx.request_repaint(); + } + // Check keyboard shortcuts (works on all platforms) ctx.input(|i| { // Check menu shortcuts @@ -726,6 +767,32 @@ impl eframe::App for EditorApp { // Registry for actions to execute after rendering (two-phase dispatch) let mut pending_actions: Vec> = Vec::new(); + // Create render context + let mut ctx = RenderContext { + tool_icon_cache: &mut self.tool_icon_cache, + icon_cache: &mut self.icon_cache, + selected_tool: &mut self.selected_tool, + fill_color: &mut self.fill_color, + stroke_color: &mut self.stroke_color, + active_color_mode: &mut self.active_color_mode, + pane_instances: &mut self.pane_instances, + pending_view_action: &mut self.pending_view_action, + fallback_pane_priority: &mut fallback_pane_priority, + pending_handlers: &mut pending_handlers, + theme: &self.theme, + action_executor: &mut self.action_executor, + selection: &mut self.selection, + active_layer_id: &mut self.active_layer_id, + tool_state: &mut self.tool_state, + pending_actions: &mut pending_actions, + draw_simplify_mode: &mut self.draw_simplify_mode, + rdp_tolerance: &mut self.rdp_tolerance, + schneider_max_error: &mut self.schneider_max_error, + audio_controller: self.audio_system.as_mut().map(|sys| &mut sys.controller), + playback_time: &mut self.playback_time, + is_playing: &mut self.is_playing, + }; + render_layout_node( ui, &mut self.current_layout, @@ -735,26 +802,8 @@ impl eframe::App for EditorApp { &mut self.selected_pane, &mut layout_action, &mut self.split_preview_mode, - &mut self.icon_cache, - &mut self.tool_icon_cache, - &mut self.selected_tool, - &mut self.fill_color, - &mut self.stroke_color, - &mut self.active_color_mode, - &mut self.pane_instances, &Vec::new(), // Root path - &mut self.pending_view_action, - &mut fallback_pane_priority, - &mut pending_handlers, - &self.theme, - &mut self.action_executor, - &mut self.selection, - &mut self.active_layer_id, - &mut self.tool_state, - &mut pending_actions, - &mut self.draw_simplify_mode, - &mut self.rdp_tolerance, - &mut self.schneider_max_error, + &mut ctx, ); // Execute action on the best handler (two-phase dispatch) @@ -782,9 +831,9 @@ impl eframe::App for EditorApp { // Set cursor based on hover state if let Some((_, is_horizontal)) = self.hovered_divider { if is_horizontal { - ctx.set_cursor_icon(egui::CursorIcon::ResizeHorizontal); + ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeHorizontal); } else { - ctx.set_cursor_icon(egui::CursorIcon::ResizeVertical); + ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeVertical); } } }); @@ -816,6 +865,33 @@ impl eframe::App for EditorApp { } +/// Context for rendering operations - bundles all mutable state needed during rendering +/// This avoids having 25+ individual parameters in rendering functions +struct RenderContext<'a> { + tool_icon_cache: &'a mut ToolIconCache, + icon_cache: &'a mut IconCache, + selected_tool: &'a mut Tool, + fill_color: &'a mut egui::Color32, + stroke_color: &'a mut egui::Color32, + active_color_mode: &'a mut panes::ColorMode, + pane_instances: &'a mut HashMap, + pending_view_action: &'a mut Option, + fallback_pane_priority: &'a mut Option, + pending_handlers: &'a mut Vec, + theme: &'a Theme, + action_executor: &'a mut lightningbeam_core::action::ActionExecutor, + selection: &'a mut lightningbeam_core::selection::Selection, + active_layer_id: &'a mut Option, + tool_state: &'a mut lightningbeam_core::tool::ToolState, + pending_actions: &'a mut Vec>, + draw_simplify_mode: &'a mut lightningbeam_core::tool::SimplifyMode, + rdp_tolerance: &'a mut f64, + schneider_max_error: &'a mut f64, + audio_controller: Option<&'a mut daw_backend::EngineController>, + playback_time: &'a mut f64, + is_playing: &'a mut bool, +} + /// Recursively render a layout node with drag support fn render_layout_node( ui: &mut egui::Ui, @@ -826,30 +902,12 @@ fn render_layout_node( selected_pane: &mut Option, layout_action: &mut Option, split_preview_mode: &mut SplitPreviewMode, - icon_cache: &mut IconCache, - tool_icon_cache: &mut ToolIconCache, - selected_tool: &mut Tool, - fill_color: &mut egui::Color32, - stroke_color: &mut egui::Color32, - active_color_mode: &mut panes::ColorMode, - pane_instances: &mut HashMap, path: &NodePath, - pending_view_action: &mut Option, - fallback_pane_priority: &mut Option, - pending_handlers: &mut Vec, - theme: &Theme, - action_executor: &mut lightningbeam_core::action::ActionExecutor, - selection: &mut lightningbeam_core::selection::Selection, - active_layer_id: &mut Option, - tool_state: &mut lightningbeam_core::tool::ToolState, - pending_actions: &mut Vec>, - draw_simplify_mode: &mut lightningbeam_core::tool::SimplifyMode, - rdp_tolerance: &mut f64, - schneider_max_error: &mut f64, + ctx: &mut RenderContext, ) { match node { LayoutNode::Pane { name } => { - render_pane(ui, name, rect, selected_pane, layout_action, split_preview_mode, icon_cache, tool_icon_cache, selected_tool, fill_color, stroke_color, active_color_mode, pane_instances, path, pending_view_action, fallback_pane_priority, pending_handlers, theme, action_executor, selection, active_layer_id, tool_state, pending_actions, draw_simplify_mode, rdp_tolerance, schneider_max_error); + render_pane(ui, name, rect, selected_pane, layout_action, split_preview_mode, path, ctx); } LayoutNode::HorizontalGrid { percent, children } => { // Handle dragging @@ -882,26 +940,8 @@ fn render_layout_node( selected_pane, layout_action, split_preview_mode, - icon_cache, - tool_icon_cache, - selected_tool, - fill_color, - stroke_color, - active_color_mode, - pane_instances, &left_path, - pending_view_action, - fallback_pane_priority, - pending_handlers, - theme, - action_executor, - selection, - active_layer_id, - tool_state, - pending_actions, - draw_simplify_mode, - rdp_tolerance, - schneider_max_error, + ctx, ); let mut right_path = path.clone(); @@ -915,26 +955,8 @@ fn render_layout_node( selected_pane, layout_action, split_preview_mode, - icon_cache, - tool_icon_cache, - selected_tool, - fill_color, - stroke_color, - active_color_mode, - pane_instances, &right_path, - pending_view_action, - fallback_pane_priority, - pending_handlers, - theme, - action_executor, - selection, - active_layer_id, - tool_state, - pending_actions, - draw_simplify_mode, - rdp_tolerance, - schneider_max_error, + ctx, ); // Draw divider with interaction @@ -1040,26 +1062,8 @@ fn render_layout_node( selected_pane, layout_action, split_preview_mode, - icon_cache, - tool_icon_cache, - selected_tool, - fill_color, - stroke_color, - active_color_mode, - pane_instances, &top_path, - pending_view_action, - fallback_pane_priority, - pending_handlers, - theme, - action_executor, - selection, - active_layer_id, - tool_state, - pending_actions, - draw_simplify_mode, - rdp_tolerance, - schneider_max_error, + ctx, ); let mut bottom_path = path.clone(); @@ -1073,26 +1077,8 @@ fn render_layout_node( selected_pane, layout_action, split_preview_mode, - icon_cache, - tool_icon_cache, - selected_tool, - fill_color, - stroke_color, - active_color_mode, - pane_instances, &bottom_path, - pending_view_action, - fallback_pane_priority, - pending_handlers, - theme, - action_executor, - selection, - active_layer_id, - tool_state, - pending_actions, - draw_simplify_mode, - rdp_tolerance, - schneider_max_error, + ctx, ); // Draw divider with interaction @@ -1178,26 +1164,8 @@ fn render_pane( selected_pane: &mut Option, layout_action: &mut Option, split_preview_mode: &mut SplitPreviewMode, - icon_cache: &mut IconCache, - tool_icon_cache: &mut ToolIconCache, - selected_tool: &mut Tool, - fill_color: &mut egui::Color32, - stroke_color: &mut egui::Color32, - active_color_mode: &mut panes::ColorMode, - pane_instances: &mut HashMap, path: &NodePath, - pending_view_action: &mut Option, - fallback_pane_priority: &mut Option, - pending_handlers: &mut Vec, - theme: &Theme, - action_executor: &mut lightningbeam_core::action::ActionExecutor, - selection: &mut lightningbeam_core::selection::Selection, - active_layer_id: &mut Option, - tool_state: &mut lightningbeam_core::tool::ToolState, - pending_actions: &mut Vec>, - draw_simplify_mode: &mut lightningbeam_core::tool::SimplifyMode, - rdp_tolerance: &mut f64, - schneider_max_error: &mut f64, + ctx: &mut RenderContext, ) { let pane_type = PaneType::from_name(pane_name); @@ -1260,7 +1228,7 @@ fn render_pane( // Load and render icon if available if let Some(pane_type) = pane_type { - if let Some(icon) = icon_cache.get_or_load(pane_type) { + if let Some(icon) = ctx.icon_cache.get_or_load(pane_type) { let icon_texture_id = icon.texture_id(ui.ctx()); let icon_rect = icon_button_rect.shrink(2.0); // Small padding inside button ui.painter().image( @@ -1297,7 +1265,7 @@ fn render_pane( for pane_type_option in PaneType::all() { // Load icon for this pane type - if let Some(icon) = icon_cache.get_or_load(*pane_type_option) { + if let Some(icon) = ctx.icon_cache.get_or_load(*pane_type_option) { ui.horizontal(|ui| { // Show icon let icon_texture_id = icon.texture_id(ui.ctx()); @@ -1351,80 +1319,86 @@ fn render_pane( // Render pane-specific header controls (if pane has them) if let Some(pane_type) = pane_type { // Get or create pane instance for header rendering - let needs_new_instance = pane_instances + let needs_new_instance = ctx.pane_instances .get(path) .map(|instance| instance.pane_type() != pane_type) .unwrap_or(true); if needs_new_instance { - pane_instances.insert(path.clone(), panes::PaneInstance::new(pane_type)); + ctx.pane_instances.insert(path.clone(), panes::PaneInstance::new(pane_type)); } - if let Some(pane_instance) = pane_instances.get_mut(path) { + if let Some(pane_instance) = ctx.pane_instances.get_mut(path) { let mut header_ui = ui.new_child(egui::UiBuilder::new().max_rect(header_controls_rect).layout(egui::Layout::left_to_right(egui::Align::Center))); let mut shared = panes::SharedPaneState { - tool_icon_cache, - icon_cache, - selected_tool, - fill_color, - stroke_color, - active_color_mode, - pending_view_action, - fallback_pane_priority, - theme, - pending_handlers, - action_executor, - selection, - active_layer_id, - tool_state, - pending_actions, - draw_simplify_mode, - rdp_tolerance, - schneider_max_error, + tool_icon_cache: ctx.tool_icon_cache, + icon_cache: ctx.icon_cache, + selected_tool: ctx.selected_tool, + fill_color: ctx.fill_color, + stroke_color: ctx.stroke_color, + active_color_mode: ctx.active_color_mode, + pending_view_action: ctx.pending_view_action, + fallback_pane_priority: ctx.fallback_pane_priority, + theme: ctx.theme, + pending_handlers: ctx.pending_handlers, + action_executor: ctx.action_executor, + selection: ctx.selection, + active_layer_id: ctx.active_layer_id, + tool_state: ctx.tool_state, + pending_actions: ctx.pending_actions, + draw_simplify_mode: ctx.draw_simplify_mode, + rdp_tolerance: ctx.rdp_tolerance, + schneider_max_error: ctx.schneider_max_error, + audio_controller: ctx.audio_controller.as_mut().map(|c| &mut **c), + playback_time: ctx.playback_time, + is_playing: ctx.is_playing, }; pane_instance.render_header(&mut header_ui, &mut shared); } } - // Make pane content clickable (use full rect for split preview interaction) + // Make pane content clickable (use content rect, not header, for split preview interaction) let pane_id = ui.id().with(("pane", path)); - let response = ui.interact(rect, pane_id, egui::Sense::click()); + let response = ui.interact(content_rect, pane_id, egui::Sense::click()); // Render pane-specific content using trait-based system if let Some(pane_type) = pane_type { // Get or create pane instance for this path // Check if we need a new instance (either doesn't exist or type changed) - let needs_new_instance = pane_instances + let needs_new_instance = ctx.pane_instances .get(path) .map(|instance| instance.pane_type() != pane_type) .unwrap_or(true); if needs_new_instance { - pane_instances.insert(path.clone(), PaneInstance::new(pane_type)); + ctx.pane_instances.insert(path.clone(), PaneInstance::new(pane_type)); } // Get the pane instance and render its content - if let Some(pane_instance) = pane_instances.get_mut(path) { + if let Some(pane_instance) = ctx.pane_instances.get_mut(path) { // Create shared state let mut shared = SharedPaneState { - tool_icon_cache, - icon_cache, - selected_tool, - fill_color, - stroke_color, - active_color_mode, - pending_view_action, - fallback_pane_priority, - theme, - pending_handlers, - action_executor, - selection, - active_layer_id, - tool_state, - pending_actions, - draw_simplify_mode, - rdp_tolerance, - schneider_max_error, + tool_icon_cache: ctx.tool_icon_cache, + icon_cache: ctx.icon_cache, + selected_tool: ctx.selected_tool, + fill_color: ctx.fill_color, + stroke_color: ctx.stroke_color, + active_color_mode: ctx.active_color_mode, + pending_view_action: ctx.pending_view_action, + fallback_pane_priority: ctx.fallback_pane_priority, + theme: ctx.theme, + pending_handlers: ctx.pending_handlers, + action_executor: ctx.action_executor, + selection: ctx.selection, + active_layer_id: ctx.active_layer_id, + tool_state: ctx.tool_state, + pending_actions: ctx.pending_actions, + draw_simplify_mode: ctx.draw_simplify_mode, + rdp_tolerance: ctx.rdp_tolerance, + schneider_max_error: ctx.schneider_max_error, + audio_controller: ctx.audio_controller.as_mut().map(|c| &mut **c), + playback_time: ctx.playback_time, + is_playing: ctx.is_playing, }; // Render pane content (header was already rendered above) diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/mod.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/mod.rs index 7f0d0fc..9ab8427 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/mod.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/mod.rs @@ -73,6 +73,11 @@ pub struct SharedPaneState<'a> { pub draw_simplify_mode: &'a mut lightningbeam_core::tool::SimplifyMode, pub rdp_tolerance: &'a mut f64, pub schneider_max_error: &'a mut f64, + /// Audio engine controller for playback control + pub audio_controller: Option<&'a mut daw_backend::EngineController>, + /// Global playback state + pub playback_time: &'a mut f64, // Current playback position in seconds + pub is_playing: &'a mut bool, // Whether playback is currently active } /// Trait for pane rendering diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs index 866bba0..e4adccd 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs @@ -25,9 +25,6 @@ enum ClipDragType { } pub struct TimelinePane { - /// Current playback time in seconds - current_time: f64, - /// Horizontal zoom level (pixels per second) pixels_per_second: f32, @@ -53,15 +50,11 @@ pub struct TimelinePane { /// Cached mouse position from mousedown (used for edge detection when drag starts) mousedown_pos: Option, - - /// Is playback currently active? - is_playing: bool, } impl TimelinePane { pub fn new() -> Self { Self { - current_time: 0.0, pixels_per_second: 100.0, viewport_start_time: 0.0, viewport_scroll_y: 0.0, @@ -72,7 +65,6 @@ impl TimelinePane { clip_drag_state: None, drag_offset: 0.0, mousedown_pos: None, - is_playing: false, } } @@ -302,8 +294,8 @@ impl TimelinePane { } /// Render the playhead (current time indicator) - fn render_playhead(&self, ui: &mut egui::Ui, rect: egui::Rect, theme: &crate::theme::Theme) { - let x = self.time_to_x(self.current_time); + fn render_playhead(&self, ui: &mut egui::Ui, rect: egui::Rect, theme: &crate::theme::Theme, playback_time: f64) { + let x = self.time_to_x(playback_time); if x >= 0.0 && x <= rect.width() { let painter = ui.painter(); @@ -684,6 +676,9 @@ impl TimelinePane { active_layer_id: &mut Option, selection: &mut lightningbeam_core::selection::Selection, pending_actions: &mut Vec>, + playback_time: &mut f64, + is_playing: &mut bool, + audio_controller: Option<&mut daw_backend::EngineController>, ) { let response = ui.allocate_rect(full_timeline_rect, egui::Sense::click_and_drag()); @@ -1034,7 +1029,8 @@ impl TimelinePane { if cursor_over_ruler && !alt_held && (response.clicked() || (response.dragged() && !self.is_panning)) { if let Some(pos) = response.interact_pointer_pos() { let x = (pos.x - content_rect.min.x).max(0.0); - self.current_time = self.x_to_time(x).max(0.0); + let new_time = self.x_to_time(x).max(0.0); + *playback_time = new_time; self.is_scrubbing = true; } } @@ -1042,12 +1038,17 @@ impl TimelinePane { else if self.is_scrubbing && response.dragged() && !self.is_panning { if let Some(pos) = response.interact_pointer_pos() { let x = (pos.x - content_rect.min.x).max(0.0); - self.current_time = self.x_to_time(x).max(0.0); + let new_time = self.x_to_time(x).max(0.0); + *playback_time = new_time; } } - // Stop scrubbing when drag ends - else if !response.dragged() { + // Stop scrubbing when drag ends - seek the audio engine + else if !response.dragged() && self.is_scrubbing { self.is_scrubbing = false; + // Seek the audio engine to the new position + if let Some(controller) = audio_controller { + controller.seek(*playback_time); + } } // Distinguish between mouse wheel (discrete) and trackpad (smooth) @@ -1154,29 +1155,54 @@ impl PaneRenderer for TimelinePane { // Go to start if ui.add_sized(button_size, egui::Button::new("|◀")).clicked() { - self.current_time = 0.0; + *shared.playback_time = 0.0; + if let Some(controller) = shared.audio_controller.as_mut() { + controller.seek(0.0); + } } // Rewind (step backward) if ui.add_sized(button_size, egui::Button::new("◀◀")).clicked() { - self.current_time = (self.current_time - 0.1).max(0.0); + *shared.playback_time = (*shared.playback_time - 0.1).max(0.0); + if let Some(controller) = shared.audio_controller.as_mut() { + controller.seek(*shared.playback_time); + } } // Play/Pause toggle - let play_pause_text = if self.is_playing { "⏸" } else { "▶" }; + let play_pause_text = if *shared.is_playing { "⏸" } else { "▶" }; if ui.add_sized(button_size, egui::Button::new(play_pause_text)).clicked() { - self.is_playing = !self.is_playing; - // TODO: Actually start/stop playback + *shared.is_playing = !*shared.is_playing; + println!("🔘 Play/Pause button clicked! is_playing = {}", *shared.is_playing); + + // Send play/pause command to audio engine + if let Some(controller) = shared.audio_controller.as_mut() { + if *shared.is_playing { + controller.play(); + println!("▶ Started playback"); + } else { + controller.pause(); + println!("⏸ Paused playback"); + } + } else { + println!("⚠️ No audio controller available (audio system failed to initialize)"); + } } // Fast forward (step forward) if ui.add_sized(button_size, egui::Button::new("▶▶")).clicked() { - self.current_time = (self.current_time + 0.1).min(self.duration); + *shared.playback_time = (*shared.playback_time + 0.1).min(self.duration); + if let Some(controller) = shared.audio_controller.as_mut() { + controller.seek(*shared.playback_time); + } } // Go to end if ui.add_sized(button_size, egui::Button::new("▶|")).clicked() { - self.current_time = self.duration; + *shared.playback_time = self.duration; + if let Some(controller) = shared.audio_controller.as_mut() { + controller.seek(self.duration); + } } }); }); @@ -1188,7 +1214,7 @@ impl PaneRenderer for TimelinePane { let text_color = text_style.text_color.unwrap_or(egui::Color32::from_gray(200)); // Time display - ui.colored_label(text_color, format!("Time: {:.2}s / {:.2}s", self.current_time, self.duration)); + ui.colored_label(text_color, format!("Time: {:.2}s / {:.2}s", *shared.playback_time, self.duration)); ui.separator(); @@ -1205,8 +1231,8 @@ impl PaneRenderer for TimelinePane { _path: &NodePath, shared: &mut SharedPaneState, ) { - // Sync timeline's current_time to document - shared.action_executor.document_mut().current_time = self.current_time; + // Sync playback_time to document + shared.action_executor.document_mut().current_time = *shared.playback_time; // Get document from action executor let document = shared.action_executor.document(); @@ -1306,7 +1332,7 @@ impl PaneRenderer for TimelinePane { // Render playhead on top (clip to timeline area) ui.set_clip_rect(timeline_rect.intersect(original_clip_rect)); - self.render_playhead(ui, timeline_rect, shared.theme); + self.render_playhead(ui, timeline_rect, shared.theme, *shared.playback_time); // Restore original clip rect ui.set_clip_rect(original_clip_rect); @@ -1323,6 +1349,9 @@ impl PaneRenderer for TimelinePane { shared.active_layer_id, shared.selection, shared.pending_actions, + shared.playback_time, + shared.is_playing, + shared.audio_controller.as_mut().map(|c| &mut **c), ); // Register handler for pending view actions (two-phase dispatch) diff --git a/src/main.js b/src/main.js index 181832a..a104856 100644 --- a/src/main.js +++ b/src/main.js @@ -3,6 +3,7 @@ const { listen } = window.__TAURI__.event; import * as fitCurve from "/fit-curve.js"; import { Bezier } from "/bezier.js"; import { Quadtree } from "./quadtree.js"; +import { initRenderWindow, updateGradient, syncPosition, closeRenderWindow } from "./render-window-integration.js"; import { createNewFileDialog, showNewFileDialog, @@ -822,6 +823,30 @@ window.addEventListener("DOMContentLoaded", () => { } } })(); + + // Initialize native render window after layout is ready + (async () => { + try { + // Wait for layout to be fully positioned + await new Promise(resolve => setTimeout(resolve, 200)); + + // Find the stage canvas (should be first in canvases array) + const stageCanvas = canvases[0]; + if (stageCanvas) { + console.log('Initializing native render window...'); + await initRenderWindow(stageCanvas); + console.log('Native render window initialized successfully'); + + // Make functions available globally for testing + window.updateRenderGradient = updateGradient; + window.syncRenderWindow = syncPosition; + } else { + console.warn('Stage canvas not found, skipping render window initialization'); + } + } catch (error) { + console.error('Failed to initialize native render window:', error); + } + })(); }); window.addEventListener("resize", () => {