diff --git a/Cargo.lock b/Cargo.lock index 3f2d7093..f49c67e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,9 +307,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.25.1" +version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" [[package]] name = "bytes" @@ -385,9 +385,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -419,9 +419,9 @@ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "clap" -version = "4.6.2" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" +checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" dependencies = [ "clap_builder", "clap_derive", @@ -429,9 +429,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.2" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" dependencies = [ "anstream", "anstyle", @@ -441,14 +441,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -655,9 +655,9 @@ checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "darling" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23" dependencies = [ "darling_core", "darling_macro", @@ -665,26 +665,26 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e" dependencies = [ "darling_core", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -768,9 +768,9 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "encode_unicode" @@ -809,17 +809,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" -[[package]] -name = "fast-srgb8" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" - [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "field-offset" @@ -831,6 +825,15 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -861,13 +864,13 @@ dependencies = [ [[package]] name = "foreign-types-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -1315,15 +1318,15 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" +checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8" dependencies = [ "darling", "indoc", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -1496,9 +1499,9 @@ checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07" [[package]] name = "kqueue" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" +checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea" dependencies = [ "kqueue-sys", "libc", @@ -1522,9 +1525,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libloading" @@ -1538,14 +1541,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" dependencies = [ "bitflags 2.13.1", "libc", "plain", - "redox_syscall 0.9.0", + "redox_syscall 0.9.1", ] [[package]] @@ -1742,15 +1745,16 @@ dependencies = [ ] [[package]] -name = "notify-debouncer-mini" +name = "notify-debouncer-full" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17849edfaabd9a5fef1c606d99cfc615a8e99f7ac4366406d86c7942a3184cf2" +checksum = "c02b49179cfebc9932238d04d6079912d26de0379328872846118a0fa0dbb302" dependencies = [ + "file-id", "log", "notify", "notify-types", - "tempfile", + "walkdir", ] [[package]] @@ -2038,22 +2042,22 @@ dependencies = [ [[package]] name = "palette" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +checksum = "ddeed8580d347d2abf3dcf06a5f0b3dc020258338526b277847cd4248a70fc64" dependencies = [ "approx", - "fast-srgb8", "palette_derive", + "palette_math", "phf", "rand 0.8.7", ] [[package]] name = "palette_derive" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +checksum = "88537020289b719d81be994ccf1bbf4990f477e2f69ee52fe3e45f43a02e56be" dependencies = [ "by_address", "proc-macro2", @@ -2061,6 +2065,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "palette_math" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6eb142958d64335fb0e345c5b9ead2ecd6fc438c307e9d7d3c4fd428dbaf12" + [[package]] name = "pango" version = "0.18.3" @@ -2175,42 +2185,18 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_shared", - "rand 0.8.7", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.119", ] [[package]] name = "phf_shared" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ "siphasher", ] @@ -2391,9 +2377,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-xml" -version = "0.39.4" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", ] @@ -2572,9 +2558,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" +checksum = "07507be7b4a5f9f26eeb41eeaebb1f5a7ff29dfb29739facc21d35bf8b11c21e" dependencies = [ "bitflags 2.13.1", ] @@ -2725,7 +2711,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.0", + "syn 3.0.3", ] [[package]] @@ -3110,9 +3096,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.0" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fac314a64dc9a36e61a9eb4261a5e9bbfbc922b27e518af97bc32b926cf967" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -3151,8 +3137,7 @@ dependencies = [ "gtk", "konst", "livi", - "notify", - "notify-debouncer-mini", + "notify-debouncer-full", "palette", "proptest", "proptest-derive", @@ -3236,7 +3221,7 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.0", + "syn 3.0.3", ] [[package]] @@ -3356,9 +3341,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow 1.0.4", ] @@ -3586,9 +3571,9 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +checksum = "016ccf01d1c58b6f8999612813e17c9b2390f7d70671428869913310f83f54b8" dependencies = [ "cc", "downcast-rs", @@ -3600,9 +3585,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.14" +version = "0.31.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" dependencies = [ "bitflags 2.13.1", "rustix 1.1.4", @@ -3672,9 +3657,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" dependencies = [ "proc-macro2", "quick-xml", @@ -4031,9 +4016,9 @@ checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "xcursor" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" [[package]] name = "xdg" @@ -4062,18 +4047,18 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0e1ba1bf..b398b732 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,8 +35,8 @@ winit = { optional = true, version = "0.30.4", features = [ "x11" ]} wavers = { optional = true, version = "1.4.3" } uuid = { optional = true, version = "1.10.0", features = [ "v4" ] } gtk = { optional = true, version = "0.18.1" } -notify = "8.2.0" -notify-debouncer-mini = "0.7.0" + +notify-debouncer-full = "0.7.0" #once_cell = "1.19.0" #no_deadlocks = "1.3.2" diff --git a/src/device/arrange.rs b/src/device/arrange.rs index 5cbc57c9..5f871a38 100644 --- a/src/device/arrange.rs +++ b/src/device/arrange.rs @@ -786,12 +786,10 @@ pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll { for (index, track, _x1, _x2) in self.tracks_with_sizes() { let f = Rgb(255, 255, 255); let b = track.color.dark.term; - let iter = ||track.sequencer.midi_outs.iter(); - let draw = |port: &MidiOutput, _|fg(f, bg(b, - format!("·o{index:02} {}", port.port_name()).full_w().align_w()).exact_h(1)); - iter_south(iter, draw).full_h().align_nw() - .exact_w(track_width(index, track)) - .draw(to)?; + iter_south(||track.sequencer.midi_outs.iter().map(|port: &MidiOutput|{ + fg(f, bg(b, format!("·o{index:02} {}", port.port_name()).full_w().align_w()).exact_h(1)) + })) + .full_h().align_nw().exact_w(track_width(index, track)).draw(to)?; } Ok(Some(XYWH(0, 0, 0, 0))) }).align_w())) @@ -812,9 +810,8 @@ pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll { either(track.sequencer.recording, fg(Red, "●rec "), "·rec "), either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "), ).align_w().full_w()), - iter_south(||track.sequencer.midi_ins.iter(), - |port, _|fg_bg(Rgb(255, 255, 255), track.color.dark.term, - format!("·i{index:02} {}", port.port_name()).align_w().full_w())) + iter_south(||track.sequencer.midi_ins.iter().map(|port|fg_bg(Rgb(255, 255, 255), track.color.dark.term, + format!("·i{index:02} {}", port.port_name()).align_w().full_w()))) ).align_nw().exact_wh(track_width(index, track), height + 1).draw(to)?; } Ok(Some(XYWH(0, 0, 0, 0))) @@ -956,13 +953,14 @@ impl Arrangement { button_2("D", "+", false), iter_once(self.tracks_with_sizes(), move|(_, track, _x1, _x2), index|bg( track.color.dark.term, - iter_south(move||0..height, - |_, _index|fg_bg( - ItemTheme::G[32].lightest.term, - ItemTheme::G[32].dark.term, - format!(" · {}", "--").align_nw() - ).exact_wh(track.width as u16, 2) - ).align_nw()).exact_wh( + iter_south(move||(0..height).map(|_|fg_bg( + ItemTheme::G[32].lightest.term, + ItemTheme::G[32].dark.term, + format!(" · {}", "--").align_nw() + ).exact_wh(track.width as u16, 2))) + .align_nw() + ) + .exact_wh( Some(track_width(index, track)), Some(height + 1), ))) diff --git a/src/device/dialog.rs b/src/device/dialog.rs index 169acb48..66f51abb 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -60,7 +60,7 @@ impl Dialog { MenuItem("New session".into(), Arc::new(Box::new(|app|Ok({ app.dialog = Dialog::None; - app.mode = app.config.modes.get(":arranger"); + app.mode = Some(":arranger".into()); })))), MenuItem("Load session".into(), Arc::new(Box::new(|_|Ok(())))), diff --git a/src/tek.edn b/src/tek.edn index 8fe0cd13..c14689f7 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -13,11 +13,25 @@ :transport) (mode :menu (name Menu) (info Mode selector.) (keys :axis/y :confirm) - (view (bg (g 16) - (bsp/s (fill/x (bg (g 32) (max/xy 80 2 :transport))) - (bsp/s (max/y 3 (bg (g 80) :ports/out)) - (bsp/n (max/y 3 (bg (g 80) :ports/in)) - (bg (g 30) (bsp/s (max/y 6 :logo) :dialog/menu)))))))) + (view (bg (g 16) (bsp/n + (bg (g 32) (max/xy 80 2 :transport)) + (bsp/s + (max/y 3 (bg (g 80) :ports/out)) + (bsp/n + (max/y 3 (bg (g 80) :ports/in)) + (bg (g 30) (bsp/s + (max/y 6 :logo) + :dialog/menu)))))))) + +(view :ports/out + (bsp/s (align/w (text L-AUDIO-OUT)) + (bsp/e (text MIDI-OUT) + (align/e (text AUDIO-OUT-R))))) + +(view :ports/in + (bsp/s (fill/x (align/w (text L-AUDIO-IN))) + (bsp/e (text MIDI-IN) + (align/e (text AUDIO-IN-R))))) (mode :sequencer (name Sequencer) (info MIDI sequencer.) (keys :editor :clock :global) @@ -28,20 +42,12 @@ (bsp/n (fixed/y 1 :status) (fill (bsp/a (fill/xy (align/e :pool)) :editor))))) -(mode :sampler (name Sampler) (info Sample player.) +(mode :sampler (name Sampler) (info Sample player.) (keys :sampler/directions :sampler/record :sampler/play) (bsp/s (fixed/y 1 :transport) (bsp/n (fixed/y 1 :status) (fill :samples/grid)))) -(view :ports/out - (fill/x (bsp/s (fill/x (align/w (text L-AUDIO-OUT))) - (bsp/e (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R))))))) - -(view :ports/in - (fill/x (bsp/s (fill/x (align/w (text L-AUDIO-IN))) - (bsp/e (text MIDI-IN) (fill/x (align/e (text AUDIO-IN-R))))))) - (mode :groovebox (name Groovebox) (info Sequencer with sampler.) (keys :clock :editor :sampler :global) (mode browse (keys :browse)) @@ -49,11 +55,11 @@ (mode length (keys :pool-length)) (bsp/w :meters/output (bsp/e :meters/input (bsp/w :groove/meta :groove/editor)))) -(view :groove/meta (fill/y (align/n (stack/s :midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool)))) +(view :groove/meta (fill/y (align/n (stack/s :midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool)))) -(view :groove/editor (bsp/n :groove/sample :groove/sequence)) +(view :groove/editor (bsp/n :groove/sample :groove/sequence)) -(view :groove/sample (fixed/y :h-sample-detail (bsp/e (fill/y (fixed/x 20 (align/nw :sample-status))) :sample-viewer))) +(view :groove/sample (fixed/y :h-sample-detail (bsp/e (fill/y (fixed/x 20 (align/nw :sample-status))) :sample-viewer))) (view :groove/sequence (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) (bsp/e :samples/keys :editor))) diff --git a/src/tek.rs b/src/tek.rs index 5fd9b660..6445815c 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -16,7 +16,7 @@ pub(crate) use ::{ fs::File, ops::{Add, Sub, Mul, Div, Rem}, path::{Path, PathBuf}, - sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}}, + sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, AtomicU64, Ordering::Relaxed}}, time::Duration, thread::{spawn, JoinHandle}, }, @@ -40,15 +40,26 @@ pub(crate) use ::{ }, }; +/// Banner. +pub(crate) const HEADER: &'static str = r#" +~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ + █ █▀ █▀▀▄ ~ heatwave is the new darkwave ~ + ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; + +pub fn show_version () { + println!("versions aint real man"); +} + /// Command-line entrypoint. #[allow(unused)] fn main () -> Usually<()> { tengri::Tui::setup_panic(); #[cfg(feature = "cli")] { - Config::watched(crate::cli::run_with_config).map(|_|()) + Config::watched(crate::cli::run_with_config)?; } #[cfg(not(feature = "cli"))] { - Config::watched(run_new_plain).map(|_|()) + Config::watched(run_new_plain)?; } + Ok(()) } #[cfg(not(feature = "cli"))] @@ -77,28 +88,23 @@ fn run_new_plain (config: Config) -> Usually<()> { })?) } -/// CLI banner. -pub(crate) const HEADER: &'static str = r#" -~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ - █ █▀ █▀▀▄ ~ heatwave is the new darkwave ~ - ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; - #[cfg(feature = "cli")] pub mod cli { use crate::*; - pub fn run_with_config (config: Config) -> Usually<()> { + pub fn run_with_config (config: Arc) -> Usually<()> { Cli::parse().run(Some(config)) } /// Command-line configuration. impl Cli { - pub fn run (&self, mut config: Option) -> Usually<()> { + pub fn run (&self, mut config: Option>) -> Usually<()> { if config.is_none() { config = Some(Config::init_new(None)?); } self.action.run(config.unwrap()) } } + /// The command-line interface descriptor. /// /// ``` @@ -116,7 +122,7 @@ pub(crate) const HEADER: &'static str = r#" #[command(subcommand)] pub action: Action, } impl Action { - fn run (&self, config: Config) -> Usually<()> { + fn run (&self, config: Arc) -> Usually<()> { use Action::*; match self { Version => show_version(), @@ -228,6 +234,399 @@ pub(crate) const HEADER: &'static str = r#" } } +pub use self::config::*; +mod config { + use crate::*; + use std::path::PathBuf; + use notify_debouncer_full::{ + new_debouncer_opt, Debouncer, DebounceEventResult, NoCache, + notify::{RecursiveMode, RecommendedWatcher, PollWatcher, Config as NotifyConfig, Watcher} + }; + + /// Configuration: mode, view, and bind definitions. + /// + /// ``` + /// let config = tek::Config::default(); + /// ``` + /// + /// ``` + /// // Some dizzle. + /// // What indentation to use here lol? + /// let source = stringify!((mode :menu (name Menu) + /// (info Mode selector.) (keys :axis/y :confirm) + /// (view (bg (g 0) (bsp/s :ports/out + /// (bsp/n :ports/in + /// (bg (g 30) (bsp/s (fixed/y 7 :logo) + /// (fill :dialog/menu))))))))); + /// // Add this definition to the config and try to load it. + /// // A "mode" is basically a state machine + /// // with associated input and output definitions. + /// tek::Config::default().add(&source).unwrap().get_mode(":menu").unwrap(); + /// ``` + #[derive(Default, Debug)] + pub struct Config { + /// XDG base directories of running user. + pub dirs: BaseDirectories, + /// Active collection of interaction modes. + pub modes: Modes, + /// Active collection of event bindings. + pub binds: Binds, + /// Active collection of view definitions. + pub views: Views, + /// Error caught during reloading + pub error: RwLock>>, + /// Timestamp + pub stamp: AtomicU64, + /// Watcher + pub watch: RwLock> + } + + /// Collection of custom view definitions. + pub type Views = Arc, Arc>>>; + + /// Collection of input bindings. + pub type Binds = Arc, Bind>>>>; + + /// An map of input events (e.g. [TuiEvent]) to [Binding]s. + /// + /// ``` + /// let lang = "(@x (nop)) (@y (nop) (nop))"; + /// let bind = tek::Bind::>::load(&lang).unwrap(); + /// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1)); + /// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2)); + /// ``` + #[derive(Debug)] + pub struct Bind( + /// Map of each event (e.g. key combination) to + /// all command expressions bound to it by + /// all loaded input layers. + pub BTreeMap>> + ); + + /// A sequence of zero or more commands (e.g. [AppCommand]), + /// optionally filtered by [Condition] to form layers. + /// + /// ``` + /// //FIXME: Why does it overflow? + /// //let binding: Binding<()> = tek::Binding { ..Default::default() }; + /// ``` + #[derive(Debug, Clone)] pub struct Binding { + pub commands: Arc<[C]>, + pub condition: Option, + pub description: Option>, + pub source: Option>, + } + + /// Condition that must evaluate to true in order to enable an input layer. + /// + /// ``` + /// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true}))); + /// ``` + #[derive(Clone)] + pub struct Condition( + pub Arcbool + Send + Sync>> + ); + + /// Collection of UI modes. + /// + /// ``` + /// let mut modes = tek::Modes::default(); + /// assert_eq!(modes.len(), 0); + /// let _ = modes.add(&":foo", &"(mode (name Foo) (info Bar))"); + /// assert_eq!(modes.len(), 1); + /// ``` + #[derive(Default, Debug, Clone)] + pub struct Modes(Arc, Arc>>>>>); + + /// Group of view and keys definitions. + /// + /// ``` + /// let mode = tek::Mode::>::default(); + /// ``` + #[derive(Default, Debug)] + pub struct Mode { + pub path: PathBuf, + pub name: Vec, + pub info: Vec, + pub view: Vec, + pub keys: Vec, + pub modes: Modes, + } + + impl Config { + const CONFIG_DIR: &'static str = "tek"; + const CONFIG_SUB: &'static str = "v0"; + const CONFIG: &'static str = "tek.edn"; + const DEFAULTS: &'static str = include_str!("tek.edn"); + + /// Create a new app configuration from a set of XDG base directories, + pub fn new (dirs: Option) -> Self { + Self { + dirs: dirs.unwrap_or_else(||{ + BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB) + }), + stamp: quanta::Clock::new().raw().into(), + ..Default::default() + } + } + + pub fn watched (callback: impl FnOnce(Arc)->T) -> Usually { + let config = Self::init_new(None)?; + Self::watch(config.clone(), None)?; + let result = callback(config); + Ok(result) + } + + pub fn init_new (_dirs: Option) -> Usually> { + let config = Arc::new(Self::new(None)); + config.init()?; + Ok(config) + } + + pub fn watch (config: Arc, bounce: Option) -> Usually<()> { + let handler = { + let config = config.clone(); + move |result|match result { + Ok(events) => if let Err(e) = config.init() { + *config.error.write().unwrap() = Some(format!("{e:?}").into()); + panic!("{e:?}"); + } else { + println!("config updated"); + }, + Err(errors) => { + panic!("{errors:?}"); + } + } + }; + let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new( + handler, + NotifyConfig::default().with_poll_interval(Duration::from_millis(250)) + )?; + if let Some(mut path) = config.get_file() { + println!("watching: {path:?}"); + watcher.watch(&path, RecursiveMode::NonRecursive)?; + *config.watch.write().unwrap() = Some(watcher); + Ok(()) + } else { + Err(format!("no config path").into()) + } + } + + fn find_file (&self) -> Option { + self.dirs.find_config_file(Self::CONFIG) + } + + fn place_file (&self) -> Result { + self.dirs.place_config_file(Self::CONFIG) + } + + fn get_file (&self) -> Option { + self.dirs.get_config_file(Self::CONFIG) + } + + /// Write initial contents of configuration. + pub fn init (&self) -> Usually<()> { + println!("\r\ninit {}", quanta::Clock::new().raw()); + self.clear(); + self.load(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|{ + cfgs.add(&dsl)?; + Ok(()) + }) + } + + /// Write initial contents of a configuration file. + pub fn load ( + &self, + path: &str, + defaults: &str, + mut each: impl FnMut(&Self, &str)->Usually<()> + ) -> Usually<()> { + self.stamp.store(quanta::Clock::new().raw(), Relaxed); + if self.find_file().is_none() { + //println!("Creating {path:?}"); + std::fs::write(self.place_file()?, defaults)?; + } + Ok(if let Some(path) = self.find_file() { + //println!("Loading {path:?}"); + let src = std::fs::read_to_string(&path)?; + src.as_str().each(move|item|each(self, item))?; + } else { + return Err(format!("{path}: not found").into()) + }) + } + + /// Add statements to configuration from [Dsl] source. + pub fn add (&self, dsl: impl Language) -> Usually<&Self> { + dsl.each(|item|self.add_one(item))?; + Ok(self) + } + + fn clear (&self) { + *self.modes.0.write().unwrap() = Default::default(); + *self.views.write().unwrap() = Default::default(); + *self.binds.write().unwrap() = Default::default(); + } + + fn add_one (&self, item: impl Language) -> Usually<()> { + if let Some(expr) = item.expr()? { + let head = expr.head()?; + let tail = expr.tail()?; + let name = tail.head()?; + let body = tail.tail()?; + //println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); + match head { + Some("mode") if let Some(name) = name => self.modes.add(&name, &body)?, + Some("keys") if let Some(name) = name => load_bind(&self.binds, &name, &body)?, + Some("view") if let Some(name) = name => load_view(&self.views, &name, &body)?, + _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) + } + Ok(()) + } else { + return Err(format!("Config::load: expected expr, got: {item:?}").into()) + } + } + + pub fn get_mode (&self, mode: impl AsRef) -> Option>>> { + self.modes.get(mode) + } + + pub fn print (&self) { + use ::ansi_term::Color::*; + println!("{:?}", self.dirs); + for (k, v) in self.views.read().unwrap().iter() { + println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}"))); + } + for (k, v) in self.binds.read().unwrap().iter() { + println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}"))); + for (k, v) in v.0.iter() { + print!("{} ", &Yellow.paint(match &k.0 { + Event::Key(KeyEvent { modifiers, .. }) => + format!("{:>16}", format!("{modifiers}")), + _ => unimplemented!() + })); + print!("{}", &Yellow.bold().paint(match &k.0 { + Event::Key(KeyEvent { code, .. }) => + format!("{:<10}", format!("{code}")), + _ => unimplemented!() + })); + for v in v.iter() { + print!(" => {:?}", v.commands); + print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); + println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); + //println!(" {:?}", v.source); + } + } + } + self.modes.for_each(|k, v|{ + println!(); + for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } + for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } + print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); + print!("\n{}", Blue.paint("KEYS")); + for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } + println!(); + v.modes.for_each(|k, v|{ + print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); + print!( " INFO={:?}", v.info); + print!( " VIEW={:?}", v.view); + println!(" KEYS={:?}", v.keys); + }); + print!("{}", Blue.paint("VIEW")); + for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } + println!(); + }); + } + } + + impl Modes { + pub fn add (&self, name: &impl AsRef, body: &impl Language) -> Usually<()> { + let mut mode = Mode::default(); + body.each(|item|mode.add(item))?; + self.0.write().unwrap().insert(name.as_ref().into(), Arc::new(mode)); + Ok(()) + } + pub fn get (&self, name: impl AsRef) -> Option>>> { + self.0.read().unwrap().get(name.as_ref()).cloned() + } + pub fn for_each (&self, mut ator: impl FnMut(&str, &Mode>)->T) { + for (k, v) in self.0.read().unwrap().iter() { + let _ = ator(k.as_ref(), v.as_ref()); + } + } + pub fn len (&self) -> usize { + self.0.read().unwrap().len() + } + } + + impl Mode> { + /// Add a definition to the mode. + /// + /// Supported definitions: + /// + /// - (name ...) -> name + /// - (info ...) -> description + /// - (keys ...) -> key bindings + /// - (mode ...) -> submode + /// - ... -> view + /// + /// ``` + /// let mut mode: tek::Mode> = Default::default(); + /// mode.add("(name hello)").unwrap(); + /// ``` + pub fn add (&mut self, dsl: impl Language) -> Usually<()> { + Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() { + //println!("Mode::add: {head} {:?}", expr.tail()); + let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); + match head { + "name" => self.add_name(tail)?, + "info" => self.add_info(tail)?, + "keys" => self.add_keys(tail)?, + "mode" => self.add_mode(tail)?, + _ => self.add_view(tail)?, + }; + } else if let Ok(Some(word)) = dsl.word() { + self.add_view(word)?; + } else { + return Err(format!("Mode::add: unexpected: {dsl:?}").into()); + }) + + //DslParse(dsl, ||Err(format!("Mode::add: unexpected: {dsl:?}").into())) + //.word(|word|self.add_view(word)) + //.expr(|expr|expr.head(|head|{ + ////println!("Mode::add: {head} {:?}", expr.tail()); + //let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); + //match head { + //"name" => self.add_name(tail), + //"info" => self.add_info(tail), + //"keys" => self.add_keys(tail)?, + //"mode" => self.add_mode(tail)?, + //_ => self.add_view(tail), + //}; + //})) + } + + fn add_name (&mut self, dsl: impl Language) -> Perhaps<()> { + Ok(dsl.src()?.map(|src|self.name.push(src.into()))) + } + fn add_info (&mut self, dsl: impl Language) -> Perhaps<()> { + Ok(dsl.src()?.map(|src|self.info.push(src.into()))) + } + fn add_view (&mut self, dsl: impl Language) -> Perhaps<()> { + Ok(dsl.src()?.map(|src|self.view.push(src.into()))) + } + fn add_keys (&mut self, dsl: impl Language) -> Perhaps<()> { + Ok(Some(dsl.each(|expr|{ self.keys.push(expr.trim().into()); Ok(()) })?)) + } + fn add_mode (&mut self, dsl: impl Language) -> Perhaps<()> { + Ok(Some(if let Some(id) = dsl.head()? { + self.modes.add(&id, &dsl.tail())?; + } else { + return Err(format!("Mode::add: self: incomplete: {dsl:?}").into()); + })) + } + } +} + pub use self::app::*; mod app { use crate::*; @@ -294,9 +693,9 @@ mod app { /// Performance counter pub perf: PerfModel, /// Available view modes and input bindings - pub config: Config, + pub config: Arc, /// Currently selected mode - pub mode: Option>>>, + pub mode: Option>, /// Undo history pub history: Vec<(AppCommand, Option)>, /// Dialog overlay @@ -314,144 +713,22 @@ mod app { /// ``` /// let mut proj = tek::Arrangement::default(); /// proj.jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack"); - /// let mut conf = tek::Config::default(); + /// let mut conf = std::sync::Arc::new(tek::Config::default()); /// conf.add("(mode hello)"); /// let tek = tek::App::new(proj, conf, "hello"); /// ``` - pub fn new (project: Arrangement, config: Config, mode: impl AsRef) -> Self { - let mode: &str = mode.as_ref(); + pub fn new (project: Arrangement, config: Arc, mode: impl AsRef) -> Self { App { jack: project.jack.clone(), color: ItemTheme::random(), dialog: Dialog::welcome(), - mode: config.get_mode(mode), + mode: Some(mode.as_ref().into()), config, project, ..Default::default() } } - /// Update memoized render of clock values. - /// ``` - /// tek::App::default().update_clock(); - /// ``` - pub fn update_clock (&self) { - ClockView::update_clock( - &self.project.clock.view_cache, self.clock(), self.size.w() > 80 - ) - } - - /// Set modal dialog. - /// - /// ``` - /// let previous: tek::Dialog = tek::App::default().set_dialog(tek::Dialog::welcome()); - /// ``` - pub fn set_dialog (&mut self, mut dialog: Dialog) -> Dialog { - std::mem::swap(&mut self.dialog, &mut dialog); - dialog - } - - /// FIXME: generalize. Set picked device in device pick dialog. - /// - /// ``` - /// tek::App::default().device_pick(0); - /// ``` - pub fn device_pick (&mut self, index: usize) { - self.dialog = Dialog::Device(index); - } - - /// FIXME: generalize. Add device to current track. - pub fn add_device (&mut self, index: usize) -> Usually<()> { - match index { - 0 => { - let name = self.jack.with_client(|c|c.name().to_string()); - let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name(); - let track = self.track().expect("no active track"); - let port = format!("{}/Sampler", &track.name); - let connect = Connect::exact(format!("{name}:{midi}")); - let sampler = if let Ok(sampler) = Sampler::new( - &self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]] - ) { - self.dialog = Dialog::None; - Device::Sampler(sampler) - } else { - self.dialog = Dialog::Message("Failed to add device.".into()); - return Err("failed to add device".into()) - }; - let track = self.track_mut().expect("no active track"); - track.devices.push(sampler); - Ok(()) - }, - 1 => { - todo!(); - //Ok(()) - }, - _ => unreachable!(), - } - } - - /// Return reference to content browser if open. - /// - /// ``` - /// assert_eq!(tek::App::default().browser(), None); - /// ``` - pub fn browser (&self) -> Option<&Browse> { - if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } - } - - /// Is a MIDI editor currently focused? - /// - /// ``` - /// tek::App::default().editor_focused(); - /// ``` - pub fn editor_focused (&self) -> bool { - false - } - - /// Toggle MIDI editor. - /// - /// ``` - /// tek::App::default().toggle_editor(None); - /// ``` - pub fn toggle_editor (&mut self, value: Option) { - //FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); - let value = value.unwrap_or_else(||!self.editor().is_some()); - if value { - // Create new clip in pool when entering empty cell - if let Selection::TrackClip { track, scene } = *self.selection() - && let Some(scene) = self.project.scenes.get_mut(scene) - && let Some(slot) = scene.clips.get_mut(track) - && slot.is_none() - && let Some(track) = self.project.tracks.get_mut(track) - { - let (_index, clip) = self.pool.add_new_clip(); - // autocolor: new clip colors from scene and track color - let color = track.color.base.mix(scene.color.base, 0.5); - clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into(); - if let Some(editor) = &mut self.project.editor { - editor.set_clip(Some(&clip)); - } - *slot = Some(clip.clone()); - //Some(clip) - } else { - //None - } - } else if let Selection::TrackClip { track, scene } = *self.selection() - && let Some(scene) = self.project.scenes.get_mut(scene) - && let Some(slot) = scene.clips.get_mut(track) - && let Some(clip) = slot.as_mut() - { - // Remove clip from arrangement when exiting empty clip editor - let mut swapped = None; - if clip.read().unwrap().count_midi_messages() == 0 { - std::mem::swap(&mut swapped, slot); - } - if let Some(clip) = swapped { - self.pool.delete_clip(&clip.read().unwrap()); - } - } - } - fn get_arc_str (&self, src: impl Language) -> Perhaps> { Ok(src.src()?.map(|x|x.into())) } @@ -596,6 +873,127 @@ mod app { }) } + /// Update memoized render of clock values. + /// ``` + /// tek::App::default().update_clock(); + /// ``` + pub fn update_clock (&self) { + ClockView::update_clock( + &self.project.clock.view_cache, self.clock(), self.size.w() > 80 + ) + } + + /// Set modal dialog. + /// + /// ``` + /// let previous: tek::Dialog = tek::App::default().set_dialog(tek::Dialog::welcome()); + /// ``` + pub fn set_dialog (&mut self, mut dialog: Dialog) -> Dialog { + std::mem::swap(&mut self.dialog, &mut dialog); + dialog + } + + /// FIXME: generalize. Set picked device in device pick dialog. + /// + /// ``` + /// tek::App::default().device_pick(0); + /// ``` + pub fn device_pick (&mut self, index: usize) { + self.dialog = Dialog::Device(index); + } + + /// FIXME: generalize. Add device to current track. + pub fn add_device (&mut self, index: usize) -> Usually<()> { + match index { + 0 => { + let name = self.jack.with_client(|c|c.name().to_string()); + let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name(); + let track = self.track().expect("no active track"); + let port = format!("{}/Sampler", &track.name); + let connect = Connect::exact(format!("{name}:{midi}")); + let sampler = if let Ok(sampler) = Sampler::new( + &self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]] + ) { + self.dialog = Dialog::None; + Device::Sampler(sampler) + } else { + self.dialog = Dialog::Message("Failed to add device.".into()); + return Err("failed to add device".into()) + }; + let track = self.track_mut().expect("no active track"); + track.devices.push(sampler); + Ok(()) + }, + 1 => { + todo!(); + //Ok(()) + }, + _ => unreachable!(), + } + } + + /// Return reference to content browser if open. + /// + /// ``` + /// assert_eq!(tek::App::default().browser(), None); + /// ``` + pub fn browser (&self) -> Option<&Browse> { + if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } + } + + /// Is a MIDI editor currently focused? + /// + /// ``` + /// tek::App::default().editor_focused(); + /// ``` + pub fn editor_focused (&self) -> bool { + false + } + + /// Toggle MIDI editor. + /// + /// ``` + /// tek::App::default().toggle_editor(None); + /// ``` + pub fn toggle_editor (&mut self, value: Option) { + //FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); + let value = value.unwrap_or_else(||!self.editor().is_some()); + if value { + // Create new clip in pool when entering empty cell + if let Selection::TrackClip { track, scene } = *self.selection() + && let Some(scene) = self.project.scenes.get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) + && slot.is_none() + && let Some(track) = self.project.tracks.get_mut(track) + { + let (_index, clip) = self.pool.add_new_clip(); + // autocolor: new clip colors from scene and track color + let color = track.color.base.mix(scene.color.base, 0.5); + clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into(); + if let Some(editor) = &mut self.project.editor { + editor.set_clip(Some(&clip)); + } + *slot = Some(clip.clone()); + //Some(clip) + } else { + //None + } + } else if let Selection::TrackClip { track, scene } = *self.selection() + && let Some(scene) = self.project.scenes.get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) + && let Some(clip) = slot.as_mut() + { + // Remove clip from arrangement when exiting empty clip editor + let mut swapped = None; + if clip.read().unwrap().count_midi_messages() == 0 { + std::mem::swap(&mut swapped, slot); + } + if let Some(clip) = swapped { + self.pool.delete_clip(&clip.read().unwrap()); + } + } + } + } pub fn swap_value ( @@ -650,20 +1048,24 @@ mod app { -> Usually> { let mut commands = vec![]; - if let Some(ref mode) = app.mode { - for id in mode.keys.iter() { - if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref()) - && let Some(bindings) = event_map.query(input) { - for binding in bindings { - for command in binding.commands.iter() { - if let Some(command) = app.namespace(command)? as Option { - commands.push(command) + app.mode + .as_ref() + .and_then(|m|app.config.get_mode(m)) + .map(|mode|{ + for id in mode.keys.iter() { + if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref()) + && let Some(bindings) = event_map.query(input) { + for binding in bindings { + for command in binding.commands.iter() { + if let Some(command) = app.namespace(command)? as Option { + commands.push(command) + } } } } } - } - } + Ok::<_, Box>(()) + }).transpose()?; Ok(commands) } @@ -1022,10 +1424,6 @@ pub fn print_status (project: &Arrangement) { // TODO dawvert integration } -pub fn show_version () { - println!("versions aint real man"); -} - //pub fn tui ( //app: Arc>, //jack: Jack, @@ -1067,367 +1465,6 @@ pub fn show_version () { } }); -pub use self::config::*; -mod config { - use crate::*; - use std::path::PathBuf; - use notify_debouncer_mini::{ - new_debouncer, Debouncer, DebounceEventResult, - notify::{RecursiveMode, RecommendedWatcher} - }; - - /// Configuration: mode, view, and bind definitions. - /// - /// ``` - /// let config = tek::Config::default(); - /// ``` - /// - /// ``` - /// // Some dizzle. - /// // What indentation to use here lol? - /// let source = stringify!((mode :menu (name Menu) - /// (info Mode selector.) (keys :axis/y :confirm) - /// (view (bg (g 0) (bsp/s :ports/out - /// (bsp/n :ports/in - /// (bg (g 30) (bsp/s (fixed/y 7 :logo) - /// (fill :dialog/menu))))))))); - /// // Add this definition to the config and try to load it. - /// // A "mode" is basically a state machine - /// // with associated input and output definitions. - /// tek::Config::default().add(&source).unwrap().get_mode(":menu").unwrap(); - /// ``` - #[derive(Default, Debug)] - pub struct Config { - /// XDG base directories of running user. - pub dirs: BaseDirectories, - /// Active collection of interaction modes. - pub modes: Modes, - /// Active collection of event bindings. - pub binds: Binds, - /// Active collection of view definitions. - pub views: Views, - } - - /// Collection of custom view definitions. - pub type Views = Arc, Arc>>>; - - /// Collection of input bindings. - pub type Binds = Arc, Bind>>>>; - - /// An map of input events (e.g. [TuiEvent]) to [Binding]s. - /// - /// ``` - /// let lang = "(@x (nop)) (@y (nop) (nop))"; - /// let bind = tek::Bind::>::load(&lang).unwrap(); - /// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1)); - /// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2)); - /// ``` - #[derive(Debug)] - pub struct Bind( - /// Map of each event (e.g. key combination) to - /// all command expressions bound to it by - /// all loaded input layers. - pub BTreeMap>> - ); - - /// A sequence of zero or more commands (e.g. [AppCommand]), - /// optionally filtered by [Condition] to form layers. - /// - /// ``` - /// //FIXME: Why does it overflow? - /// //let binding: Binding<()> = tek::Binding { ..Default::default() }; - /// ``` - #[derive(Debug, Clone)] pub struct Binding { - pub commands: Arc<[C]>, - pub condition: Option, - pub description: Option>, - pub source: Option>, - } - - /// Condition that must evaluate to true in order to enable an input layer. - /// - /// ``` - /// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true}))); - /// ``` - #[derive(Clone)] - pub struct Condition( - pub Arcbool + Send + Sync>> - ); - - /// Collection of UI modes. - /// - /// ``` - /// let mut modes = tek::Modes::default(); - /// assert_eq!(modes.len(), 0); - /// let _ = modes.add(&":foo", &"(mode (name Foo) (info Bar))"); - /// assert_eq!(modes.len(), 1); - /// ``` - #[derive(Default, Debug, Clone)] - pub struct Modes(Arc, Arc>>>>>); - - /// Group of view and keys definitions. - /// - /// ``` - /// let mode = tek::Mode::>::default(); - /// ``` - #[derive(Default, Debug)] - pub struct Mode { - pub path: PathBuf, - pub name: Vec, - pub info: Vec, - pub view: Vec, - pub keys: Vec, - pub modes: Modes, - } - - impl Config { - const CONFIG_DIR: &'static str = "tek"; - const CONFIG_SUB: &'static str = "v0"; - const CONFIG: &'static str = "tek.edn"; - const DEFAULTS: &'static str = include_str!("tek.edn"); - - pub fn watched (callback: impl FnOnce(Self)->T) -> Usually { - let config = Self::init_new(None)?; - let _watch = config.watch(None)?; - let result = callback(config); - Ok(result) - } - - pub fn init_new (_dirs: Option) -> Usually { - let mut config = Self::new(None); - config.init()?; - Ok(config) - } - - /// Create a new app configuration from a set of XDG base directories, - pub fn new (dirs: Option) -> Self { - let default = ||BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB); - let dirs = dirs.unwrap_or_else(default); - Self { dirs, ..Default::default() } - } - - pub fn watch (&self, bounce: Option) -> Usually> { - let mut debouncer = new_debouncer( - bounce.unwrap_or(Duration::from_millis(500)), - |events: DebounceEventResult| { - panic!("updated: {events:?}"); - } - )?; - if let Some(path) = self.get_file() { - debouncer.watcher().watch(&path, RecursiveMode::NonRecursive)?; - Ok(debouncer) - } else { - Err(format!("no config path").into()) - } - } - - fn find_file (&self) -> Option { - self.dirs.find_config_file(Self::CONFIG) - } - - fn place_file (&self) -> Result { - self.dirs.place_config_file(Self::CONFIG) - } - - fn get_file (&self) -> Option { - self.dirs.get_config_file(Self::CONFIG) - } - - /// Write initial contents of configuration. - pub fn init (&mut self) -> Usually<()> { - self.init_one(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|{ - cfgs.add(&dsl)?; - Ok(()) - })?; - Ok(()) - } - - /// Write initial contents of a configuration file. - pub fn init_one ( - &mut self, - path: &str, - defaults: &str, - mut each: impl FnMut(&mut Self, &str)->Usually<()> - ) -> Usually<()> { - if self.find_file().is_none() { - //println!("Creating {path:?}"); - std::fs::write(self.place_file()?, defaults)?; - } - Ok(if let Some(path) = self.find_file() { - //println!("Loading {path:?}"); - let src = std::fs::read_to_string(&path)?; - src.as_str().each(move|item|each(self, item))?; - } else { - return Err(format!("{path}: not found").into()) - }) - } - - /// Add statements to configuration from [Dsl] source. - pub fn add (&mut self, dsl: impl Language) -> Usually<&mut Self> { - dsl.each(|item|self.add_one(item))?; - Ok(self) - } - - fn add_one (&self, item: impl Language) -> Usually<()> { - if let Some(expr) = item.expr()? { - let head = expr.head()?; - let tail = expr.tail()?; - let name = tail.head()?; - let body = tail.tail()?; - //println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); - match head { - Some("mode") if let Some(name) = name => self.modes.add(&name, &body)?, - Some("keys") if let Some(name) = name => load_bind(&self.binds, &name, &body)?, - Some("view") if let Some(name) = name => load_view(&self.views, &name, &body)?, - _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) - } - Ok(()) - } else { - return Err(format!("Config::load: expected expr, got: {item:?}").into()) - } - } - - pub fn get_mode (&self, mode: impl AsRef) -> Option>>> { - self.modes.get(mode) - } - - pub fn print (&self) { - use ::ansi_term::Color::*; - println!("{:?}", self.dirs); - for (k, v) in self.views.read().unwrap().iter() { - println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}"))); - } - for (k, v) in self.binds.read().unwrap().iter() { - println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}"))); - for (k, v) in v.0.iter() { - print!("{} ", &Yellow.paint(match &k.0 { - Event::Key(KeyEvent { modifiers, .. }) => - format!("{:>16}", format!("{modifiers}")), - _ => unimplemented!() - })); - print!("{}", &Yellow.bold().paint(match &k.0 { - Event::Key(KeyEvent { code, .. }) => - format!("{:<10}", format!("{code}")), - _ => unimplemented!() - })); - for v in v.iter() { - print!(" => {:?}", v.commands); - print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); - println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); - //println!(" {:?}", v.source); - } - } - } - self.modes.for_each(|k, v|{ - println!(); - for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } - for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } - print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); - print!("\n{}", Blue.paint("KEYS")); - for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } - println!(); - v.modes.for_each(|k, v|{ - print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); - print!( " INFO={:?}", v.info); - print!( " VIEW={:?}", v.view); - println!(" KEYS={:?}", v.keys); - }); - print!("{}", Blue.paint("VIEW")); - for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } - println!(); - }); - } - } - - impl Modes { - pub fn add (&self, name: &impl AsRef, body: &impl Language) -> Usually<()> { - let mut mode = Mode::default(); - body.each(|item|mode.add(item))?; - self.0.write().unwrap().insert(name.as_ref().into(), Arc::new(mode)); - Ok(()) - } - pub fn get (&self, name: impl AsRef) -> Option>>> { - self.0.read().unwrap().get(name.as_ref()).cloned() - } - pub fn for_each (&self, mut ator: impl FnMut(&str, &Mode>)->T) { - for (k, v) in self.0.read().unwrap().iter() { - let _ = ator(k.as_ref(), v.as_ref()); - } - } - pub fn len (&self) -> usize { - self.0.read().unwrap().len() - } - } - - impl Mode> { - /// Add a definition to the mode. - /// - /// Supported definitions: - /// - /// - (name ...) -> name - /// - (info ...) -> description - /// - (keys ...) -> key bindings - /// - (mode ...) -> submode - /// - ... -> view - /// - /// ``` - /// let mut mode: tek::Mode> = Default::default(); - /// mode.add("(name hello)").unwrap(); - /// ``` - pub fn add (&mut self, dsl: impl Language) -> Usually<()> { - Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() { - //println!("Mode::add: {head} {:?}", expr.tail()); - let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); - match head { - "name" => self.add_name(tail)?, - "info" => self.add_info(tail)?, - "keys" => self.add_keys(tail)?, - "mode" => self.add_mode(tail)?, - _ => self.add_view(tail)?, - }; - } else if let Ok(Some(word)) = dsl.word() { - self.add_view(word)?; - } else { - return Err(format!("Mode::add: unexpected: {dsl:?}").into()); - }) - - //DslParse(dsl, ||Err(format!("Mode::add: unexpected: {dsl:?}").into())) - //.word(|word|self.add_view(word)) - //.expr(|expr|expr.head(|head|{ - ////println!("Mode::add: {head} {:?}", expr.tail()); - //let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); - //match head { - //"name" => self.add_name(tail), - //"info" => self.add_info(tail), - //"keys" => self.add_keys(tail)?, - //"mode" => self.add_mode(tail)?, - //_ => self.add_view(tail), - //}; - //})) - } - - fn add_name (&mut self, dsl: impl Language) -> Perhaps<()> { - Ok(dsl.src()?.map(|src|self.name.push(src.into()))) - } - fn add_info (&mut self, dsl: impl Language) -> Perhaps<()> { - Ok(dsl.src()?.map(|src|self.info.push(src.into()))) - } - fn add_view (&mut self, dsl: impl Language) -> Perhaps<()> { - Ok(dsl.src()?.map(|src|self.view.push(src.into()))) - } - fn add_keys (&mut self, dsl: impl Language) -> Perhaps<()> { - Ok(Some(dsl.each(|expr|{ self.keys.push(expr.trim().into()); Ok(()) })?)) - } - fn add_mode (&mut self, dsl: impl Language) -> Perhaps<()> { - Ok(Some(if let Some(id) = dsl.head()? { - self.modes.add(&id, &dsl.tail())?; - } else { - return Err(format!("Mode::add: self: incomplete: {dsl:?}").into()); - })) - } - } -} - pub use self::draw::*; mod draw { use crate::*; @@ -1459,18 +1496,37 @@ mod draw { e.as_ref().align_c().draw(to)?; } - if let Some(ref mode) = self.mode { - for (index, dsl) in mode.view.iter().enumerate() { - if let Err(e) = self.interpret(to, dsl) { - let src = &dsl.src().unwrap_or(Some("")).unwrap_or(""); - let message = format!("Mode: {:?}\nLayer: #{index}\nError: {e}\nSource:\n{}", &mode.name, &src); - *self.error.write().unwrap() = Some(message.into()); - break; - } - } - } + self.mode + .as_ref() + .and_then(|m|self.config.get_mode(m)) + .map(|mode|{ - ShowSize.align_se().draw(to)?; + south!( + format!("Mode: {:?}", self.mode.as_ref()), + format!("Time: {}", self.config.stamp.load(Relaxed)), + iter_south(||mode.view.iter().enumerate().map(|(index, line)|{ + format!("View #{index} {line}") + })) + ).align_nw().draw(to)?; + + for (index, dsl) in mode.view.iter().enumerate() { + if let Err(e) = self.interpret(to, dsl) { + let src = &dsl.src().unwrap_or(Some("")).unwrap_or(""); + let message = format!("Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", &mode.name, &src); + *self.error.write().unwrap() = Some(message.into()); + return Err(e); + } + } + + *self.error.write().unwrap() = None; + + Ok(()) + }); + + east( + format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)), + format!("{} ", self.perf.clock.raw() / 1000000000), + ).align_se().draw(to)?; Ok(Some(xywh)) }) @@ -1661,14 +1717,14 @@ mod draw { pub fn view_device (state: &App) -> impl Draw { let selected = state.dialog.device_kind().unwrap(); - south(bold(true, "Add device"), iter_south( - move||device_kinds().iter(), - move|_label: &&'static str, i|{ + south( + bold(true, "Add device"), + iter_south(move||device_kinds().iter().enumerate().map(move|(i, _label)|{ let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) }; let l = if i == selected { "[ " } else { " " }; let r = if i == selected { " ]" } else { " " }; bg(b, east(l, west(r, "FIXME device name"))).full_w() - })) + }))) } /// ``` @@ -1839,8 +1895,9 @@ mod draw { { let ins = ports.len() as u16; let frame = Outer(true, Style::default().fg(g(96))); - let iter = move||ports.iter(); - let names = iter_south(iter, move|port, index|format!(" {index} {}", port.port_name()).align_w().full_h()); + let names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!( + " {index} {}", port.port_name() + ).align_w().full_h())); let field = field_v(theme, title, names); border(true, frame, field.exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins) } diff --git a/tengri b/tengri index ac9fd7df..b0971999 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit ac9fd7dfba24335d09793a55d94ea54039253f62 +Subproject commit b09719993af0b9150b8b79c3ec08601795c5cd4e