diff --git a/Cargo.lock b/Cargo.lock index a1080f3f..3f2d7093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,23 +54,21 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-activity" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" dependencies = [ "android-properties", - "bitflags 2.11.0", + "bitflags 2.13.1", "cc", - "cesu8", "jni", - "jni-sys", "libc", "log", "ndk", "ndk-context", "ndk-sys", "num_enum", - "thiserror 1.0.69", + "thiserror 2.0.19", ] [[package]] @@ -90,9 +88,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -105,15 +103,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -140,9 +138,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "approx" @@ -161,9 +159,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "as-raw-xcb-connection" @@ -208,9 +206,9 @@ checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "backtrace" @@ -260,9 +258,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "block2" @@ -297,9 +295,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "by_address" @@ -309,15 +307,15 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.25.0" +version = "1.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cairo-rs" @@ -325,7 +323,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "cairo-sys-rs", "glib", "libc", @@ -350,7 +348,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "log", "polling", "rustix 0.38.44", @@ -387,9 +385,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" dependencies = [ "find-msvc-tools", "jobserver", @@ -397,12 +395,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-expr" version = "0.15.8" @@ -421,15 +413,15 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "clap" -version = "4.5.60" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" dependencies = [ "clap_builder", "clap_derive", @@ -437,9 +429,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" dependencies = [ "anstream", "anstyle", @@ -449,27 +441,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" @@ -483,9 +475,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "7fd622ebbb56a5b2ccb651b32b911cdeb2a9b4b11776b2473bf26a26a286244e" dependencies = [ "castaway", "cfg-if", @@ -534,7 +526,7 @@ checksum = "eb0240417fe20ccf13397fa25e6f0a987dbbfaf27d9e13532419df7f593e65e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "unicode-xid", ] @@ -589,9 +581,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -599,18 +591,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crossterm" @@ -618,7 +610,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "crossterm_winapi", "mio", "parking_lot 0.12.5", @@ -634,13 +626,13 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "crossterm_winapi", "derive_more", "document-features", "mio", "parking_lot 0.12.5", - "rustix 1.1.3", + "rustix 1.1.4", "signal-hook", "signal-hook-mio", "winapi", @@ -681,7 +673,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -692,7 +684,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -714,7 +706,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -728,16 +720,27 @@ name = "dizzle" version = "0.1.0" dependencies = [ "const_panic", + "dizzle_proc", "itertools 0.14.0", "konst", - "thiserror 2.0.18", + "peg", + "thiserror 2.0.19", +] + +[[package]] +name = "dizzle_proc" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] name = "dlib" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ "libloading", ] @@ -765,9 +768,9 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "encode_unicode" @@ -814,9 +817,9 @@ checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "field-offset" @@ -864,7 +867,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -874,10 +877,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] -name = "futures" -version = "0.3.32" +name = "fsevent-sys" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -890,9 +902,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -900,15 +912,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -917,38 +929,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-channel", "futures-core", @@ -1025,7 +1037,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.3", + "rustix 1.1.4", "windows-link", ] @@ -1048,21 +1060,19 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi", - "wasip2", - "wasip3", + "r-efi 6.0.0", ] [[package]] @@ -1109,7 +1119,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "futures-channel", "futures-core", "futures-executor", @@ -1137,7 +1147,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1210,7 +1220,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1226,9 +1236,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -1258,12 +1268,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -1272,14 +1276,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", + "hashbrown 0.17.1", ] [[package]] @@ -1292,16 +1294,36 @@ dependencies = [ ] [[package]] -name = "instability" -version = "0.3.11" +name = "inotify" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" +dependencies = [ + "bitflags 2.13.1", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[package]] +name = "instability" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ "darling", "indoc", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1339,15 +1361,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jack" version = "0.13.0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "jack-sys", "lazy_static", "libc", @@ -1358,7 +1380,7 @@ dependencies = [ name = "jack-sys" version = "0.5.1" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "lazy_static", "libc", "libloading", @@ -1368,51 +1390,88 @@ dependencies = [ [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-macros", + "jni-sys 0.4.1", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror 2.0.19", "walkdir", - "windows-sys 0.45.0", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.87" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f0862381daaec758576dcc22eb7bbf4d7efd67328553f3b45a412a51a3fb21" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] [[package]] name = "konst" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" +checksum = "97feab15b395d1860944abe6a8dd8ed9f8eadfae01750fada8427abda531d887" dependencies = [ "const_panic", "konst_kernel", @@ -1435,23 +1494,37 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07" +[[package]] +name = "kqueue" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.1", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" -version = "0.2.182" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -1465,13 +1538,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "libc", - "redox_syscall 0.7.1", + "plain", + "redox_syscall 0.9.0", ] [[package]] @@ -1503,9 +1577,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litrs" @@ -1537,9 +1611,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" @@ -1567,15 +1641,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -1609,9 +1683,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "log", @@ -1625,8 +1699,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.0", - "jni-sys", + "bitflags 2.13.1", + "jni-sys 0.3.1", "log", "ndk-sys", "num_enum", @@ -1646,7 +1720,46 @@ version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "jni-sys", + "jni-sys 0.3.1", +] + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.13.1", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-mini" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17849edfaabd9a5fef1c606d99cfc615a8e99f7ac4366406d86c7942a3184cf2" +dependencies = [ + "log", + "notify", + "notify-types", + "tempfile", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.13.1", ] [[package]] @@ -1660,9 +1773,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -1670,14 +1783,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1702,7 +1815,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "block2", "libc", "objc2", @@ -1718,7 +1831,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "block2", "objc2", "objc2-core-location", @@ -1742,7 +1855,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "block2", "objc2", "objc2-foundation", @@ -1784,7 +1897,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "block2", "dispatch", "libc", @@ -1809,7 +1922,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "block2", "objc2", "objc2-foundation", @@ -1821,7 +1934,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "block2", "objc2", "objc2-foundation", @@ -1844,7 +1957,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "block2", "objc2", "objc2-cloud-kit", @@ -1876,7 +1989,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "block2", "objc2", "objc2-core-location", @@ -1894,9 +2007,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1906,9 +2019,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "orbclient" -version = "0.3.50" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ad2c6bae700b7aa5d1cc30c59bdd3a1c180b09dbaea51e2ae2b8e1cf211fdd" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" dependencies = [ "libc", "libredox", @@ -1933,7 +2046,7 @@ dependencies = [ "fast-srgb8", "palette_derive", "phf", - "rand 0.8.5", + "rand 0.8.7", ] [[package]] @@ -1945,7 +2058,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2027,6 +2140,33 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "peg" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aad070be5b63aa72103f2fcdd70a83adbd5e90112ce5b574171ff1c65501773" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd8ef6825cae95355031ae26a99b616a2a21f22ba2de0197c43dfb05acbe7ee" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7011d97b484a5ebdc4b1fdb3b12d5e4bbbea56e9d22b688f2e79e04b65a7d8a6" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2050,7 +2190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand 0.8.7", ] [[package]] @@ -2063,7 +2203,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2077,35 +2217,41 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "polling" @@ -2117,7 +2263,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -2130,16 +2276,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -2162,11 +2298,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit 0.25.13+spec-1.1.0", ] [[package]] @@ -2195,24 +2331,24 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.11.0", + "bitflags 2.13.1", "num-traits", - "rand 0.9.2", + "rand 0.9.5", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -2229,7 +2365,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2255,18 +2391,18 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-xml" -version = "0.38.4" +version = "0.39.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -2278,10 +2414,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "rand" -version = "0.8.5" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2290,9 +2432,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -2351,7 +2493,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "cassowary", "compact_str", "crossterm 0.28.1", @@ -2372,7 +2514,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", ] [[package]] @@ -2383,9 +2525,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -2425,23 +2567,23 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", ] [[package]] name = "redox_syscall" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", ] [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "ringbuf" @@ -2454,9 +2596,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" [[package]] name = "rustc_version" @@ -2473,7 +2615,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2482,22 +2624,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "rusty-fork" @@ -2553,50 +2695,37 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", ] [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "syn 3.0.0", ] [[package]] @@ -2610,18 +2739,18 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook" @@ -2655,10 +2784,26 @@ dependencies = [ ] [[package]] -name = "siphasher" -version = "1.0.2" +name = "simd_cesu8" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -2668,9 +2813,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "smithay-client-toolkit" @@ -2678,7 +2823,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "calloop", "calloop-wayland-source", "cursor-icon", @@ -2743,7 +2888,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2954,9 +3099,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fac314a64dc9a36e61a9eb4261a5e9bbfbc922b27e518af97bc32b926cf967" dependencies = [ "proc-macro2", "quote", @@ -2995,12 +3151,13 @@ dependencies = [ "gtk", "konst", "livi", - "midly", + "notify", + "notify-debouncer-mini", "palette", "proptest", "proptest-derive", "quanta", - "rand 0.8.5", + "rand 0.8.7", "symphonia", "tek", "tengri", @@ -3013,14 +3170,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.25.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.3", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -3034,9 +3191,10 @@ dependencies = [ "crossterm 0.29.0", "dizzle", "jack", + "midly", "palette", "quanta", - "rand 0.8.5", + "rand 0.8.7", "ratatui", "unicode-width 0.2.0", ] @@ -3052,11 +3210,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -3067,18 +3225,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.0", ] [[package]] @@ -3126,11 +3284,11 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", - "serde_spanned 1.0.4", + "serde_spanned 1.1.1", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -3151,6 +3309,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.19.15" @@ -3177,30 +3344,30 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ "indexmap", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 0.7.14", + "winnow 1.0.4", ] [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 0.7.14", + "winnow 1.0.4", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tracing" @@ -3226,9 +3393,9 @@ checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" [[package]] name = "typewit" -version = "1.14.2" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c1ae7cc0fdb8b842d65d127cb981574b0d2b249b74d1c7a2986863dc134f71" +checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6" dependencies = [ "typewit_proc_macros", ] @@ -3253,9 +3420,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-truncate" @@ -3294,11 +3461,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.21.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -3342,27 +3509,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.110" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de241cdc66a9d91bd84f097039eb140cdc6eec47e0cdbaf9d932a1dd6c35866" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -3373,23 +3531,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.60" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a42e96ea38f49b191e08a1bab66c7ffdba24b06f9995b39a9dd60222e5b6f1da" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.110" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12fdf6649048f2e3de6d7d5ff3ced779cdedee0e0baffd7dff5cdfa3abc8a52" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3397,60 +3551,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.110" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e63d1795c565ac3462334c1e396fd46dbf481c40f51f5072c310717bc4fb309" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.110" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f9cdac23a5ce71f6bf9f8824898a501e511892791ea2a0c6b8568c68b9cb53" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "wavers" version = "1.5.1" @@ -3466,13 +3586,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.12" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ "cc", "downcast-rs", - "rustix 1.1.3", + "rustix 1.1.4", "scoped-tls", "smallvec", "wayland-sys", @@ -3480,12 +3600,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.12" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "bitflags 2.11.0", - "rustix 1.1.3", + "bitflags 2.13.1", + "rustix 1.1.4", "wayland-backend", "wayland-scanner", ] @@ -3496,29 +3616,29 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.12" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5864c4b5b6064b06b1e8b74ead4a98a6c45a285fe7a0e784d24735f011fdb078" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" dependencies = [ - "rustix 1.1.3", + "rustix 1.1.4", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.10" +version = "0.32.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -3526,11 +3646,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.10" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa98634619300a535a9a97f338aed9a5ff1e01a461943e8346ff4ae26007306b" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -3539,11 +3659,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.10" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -3552,9 +3672,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" dependencies = [ "proc-macro2", "quick-xml", @@ -3563,9 +3683,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.8" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ "dlib", "log", @@ -3575,9 +3695,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.87" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c7c5718134e770ee62af3b6b4a84518ec10101aad610c024b64d6ff29bb1ff" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -3630,15 +3750,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -3657,6 +3768,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -3666,21 +3786,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -3690,7 +3795,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -3698,10 +3803,21 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" +name = "windows-targets" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] [[package]] name = "windows_aarch64_gnullvm" @@ -3710,10 +3826,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -3722,10 +3838,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.42.2" +name = "windows_aarch64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -3733,6 +3849,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" @@ -3740,10 +3862,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.42.2" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -3752,10 +3874,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" +name = "windows_i686_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -3764,10 +3886,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +name = "windows_x86_64_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -3776,10 +3898,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +name = "windows_x86_64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -3788,15 +3910,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winit" -version = "0.30.12" +name = "windows_x86_64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.11.0", + "bitflags 2.13.1", "block2", "bytemuck", "calloop", @@ -3850,100 +3978,24 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.51.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "x11-dl" @@ -3967,7 +4019,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "x11rb-protocol", ] @@ -3995,7 +4047,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "dlib", "log", "once_cell", @@ -4010,26 +4062,20 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 8453c345..0e1ba1bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ backtrace = { version = "0.3.72" } builder-pattern = { version = "0.4.2" } bumpalo = { version = "3.19.0" } konst = { version = "0.3.16", features = [ "rust_1_83" ] } -midly = { version = "0.5" } palette = { version = "0.7.6", features = [ "random" ] } quanta = { version = "0.12.3" } rand = { version = "0.8.5" } @@ -36,6 +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" #once_cell = "1.19.0" #no_deadlocks = "1.3.2" diff --git a/Justfile b/Justfile index 21326baa..33f077c9 100644 --- a/Justfile +++ b/Justfile @@ -1,4 +1,4 @@ -export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold" +#export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold" export RUST_BACKTRACE := "1" default +ARGS="new": diff --git a/bacon.toml b/bacon.toml index 46ef565e..30030e02 100644 --- a/bacon.toml +++ b/bacon.toml @@ -1,11 +1,14 @@ # https://dystroy.org/bacon/config/ default_job = "test" env.CARGO_TERM_COLOR = "always" + [keybindings] c = "job:check" t = "job:test" n = "job:nextest" l = "job:clippy" +r = "job:run" + [jobs] [jobs.check] command = ["cargo", "check"] @@ -47,9 +50,9 @@ command = [ "cargo", "run", # put launch parameters for your program behind a `--` separator ] -need_stdout = true +need_stdout = false allow_warnings = true -background = true +background = false [jobs.run-long] watch = ["dizzle", "tengri", "app"] command = [ "cargo", "run", ] diff --git a/shell.nix b/shell.nix index a449cf33..78a0b912 100755 --- a/shell.nix +++ b/shell.nix @@ -9,6 +9,7 @@ pkgs.libclang pkgs.mold pkgs.cloc + pkgs.watchexec ]; buildInputs = [ pkgs.libclang diff --git a/src/app.rs b/src/app.rs index 93c3d74a..a583e394 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,11 +1,25 @@ use crate::*; pub mod audio; #[allow(unused)] pub use self::audio::*; pub mod bind; pub use self::bind::*; -pub mod cli; pub use self::cli::*; pub mod config; pub use self::config::*; pub mod draw; pub use self::draw::*; pub mod size; pub use self::size::*; - +primitive!(u8: try_to_u8); +primitive!(u16: try_to_u16); +primitive!(usize: try_to_usize); +primitive!(isize: try_to_isize); +impl_has!(Clock: |self: App|self.project.clock); +impl_has!(Vec: |self: App|self.project.midi_ins); +impl_has!(Vec: |self: App|self.project.midi_outs); +impl_has!(Dialog: |self: App|self.dialog); +impl_has!(Jack<'static>: |self: App|self.jack); +impl_has!(Pool: |self: App|self.pool); +impl_has!(Selection: |self: App|self.project.selection); +impl_as_ref!(Vec: |self: App|self.project.as_ref()); +impl_as_mut!(Vec: |self: App|self.project.as_mut()); +impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt()); +impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt()); +impl_has_clips!( |self: App|self.pool.clips); /// Total application state. /// /// ``` @@ -29,7 +43,21 @@ pub mod size; pub use self::size::*; /// //let _ = app.project.h_outputs(); /// let _ = app.project.h_scenes(); /// ``` -#[derive(Default, Debug)] pub struct App { +#[derive(Default, Debug)] +#[namespace(u8)] +#[namespace(isize)] +#[namespace(ItemTheme)] +#[namespace(Arc App::get_arc_str)] +#[namespace(u16 App::get_u16)] +#[namespace(usize App::get_usize)] +#[namespace(bool App::get_bool)] +#[namespace(Selection App::get_selection)] +#[namespace(Color App::get_color)] +#[namespace(Option App::get_opt_u7)] +#[namespace(Option App::get_opt_u16)] +#[namespace(Option App::get_opt_usize)] +#[namespace(Option>> App::get_clip)] +pub struct App { /// Base color. pub color: ItemTheme, /// Must not be dropped for the duration of the process @@ -53,9 +81,7 @@ pub mod size; pub use self::size::*; /// Error, if any pub error: Arc>>> } - impl App { - /// Create a new application instance from a backend, project, config, and mode /// /// ``` @@ -79,36 +105,6 @@ impl App { ..Default::default() } } - - pub fn confirm (&mut self) -> Perhaps { - Ok(match &self.dialog { - Dialog::Menu(index, items) => { - let callback = items.0[*index].1.clone(); - callback(self)?; - None - }, - _ => todo!(), - }) - } - - pub fn inc (&mut self, axis: &ControlAxis) -> Perhaps { - Ok(match (&self.dialog, axis) { - (Dialog::None, _) => todo!(), - (Dialog::Menu(_, _), ControlAxis::Y) => - AppCommand::SetDialog { dialog: self.dialog.menu_next() }.act(self)?, - _ => todo!() - }) - } - - pub fn dec (&mut self, axis: &ControlAxis) -> Perhaps { - Ok(match (&self.dialog, axis) { - (Dialog::None, _) => None, - (Dialog::Menu(_, _), ControlAxis::Y) => - AppCommand::SetDialog { dialog: self.dialog.menu_prev() }.act(self)?, - _ => todo!() - }) - } - /// Update memoized render of clock values. /// ``` /// tek::App::default().update_clock(); @@ -227,98 +223,138 @@ impl App { } } } -} - -impl_has!(Clock: |self: App|self.project.clock); -impl_has!(Vec: |self: App|self.project.midi_ins); -impl_has!(Vec: |self: App|self.project.midi_outs); -impl_has!(Dialog: |self: App|self.dialog); -impl_has!(Jack<'static>: |self: App|self.jack); -impl_has!(Pool: |self: App|self.pool); -impl_has!(Selection: |self: App|self.project.selection); -impl_as_ref!(Vec: |self: App|self.project.as_ref()); -impl_as_mut!(Vec: |self: App|self.project.as_mut()); -impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt()); -impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt()); -impl_has_clips!( |self: App|self.pool.clips); - -primitive!(u8: try_to_u8); -primitive!(u16: try_to_u16); -primitive!(usize: try_to_usize); -primitive!(isize: try_to_isize); - -namespace!(App: Arc { literal = |dsl|Ok(dsl.src()?.map(|x|x.into())); }); - -namespace!(App: u8 { literal = |dsl|try_to_u8(dsl); }); - -namespace!(App: u16 { literal = |dsl|try_to_u16(dsl); symbol = |app| { - ":w/sidebar" => app.project.w_sidebar(app.editor().is_some()), - ":h/sample-detail" => 6.max(app.size.h() as u16 * 3 / 9), }; }); - -namespace!(App: isize { literal = |dsl|try_to_isize(dsl); }); - -namespace!(App: usize { literal = |dsl|try_to_usize(dsl); symbol = |app| { - ":scene-count" => app.scenes().len(), - ":track-count" => app.tracks().len(), - ":device-kind" => app.dialog.device_kind().unwrap_or(0), - ":device-kind/next" => app.dialog.device_kind_next().unwrap_or(0), - ":device-kind/prev" => app.dialog.device_kind_prev().unwrap_or(0), }; }); - -namespace!(App: bool { symbol = |app| { // Provide boolean values. - ":mode/editor" => app.project.editor.is_some(), - ":focused/dialog" => !matches!(app.dialog, Dialog::None), - ":focused/message" => matches!(app.dialog, Dialog::Message(..)), - ":focused/add_device" => matches!(app.dialog, Dialog::Device(..)), - ":focused/browser" => app.dialog.browser().is_some(), - ":focused/pool/import" => matches!(app.pool.mode, Some(PoolMode::Import(..))), - ":focused/pool/export" => matches!(app.pool.mode, Some(PoolMode::Export(..))), - ":focused/pool/rename" => matches!(app.pool.mode, Some(PoolMode::Rename(..))), - ":focused/pool/length" => matches!(app.pool.mode, Some(PoolMode::Length(..))), - ":focused/clip" => !app.editor_focused() && matches!(app.selection(), Selection::TrackClip{..}), - ":focused/track" => !app.editor_focused() && matches!(app.selection(), Selection::Track(..)), - ":focused/scene" => !app.editor_focused() && matches!(app.selection(), Selection::Scene(..)), - ":focused/mix" => !app.editor_focused() && matches!(app.selection(), Selection::Mix), -}; }); - -namespace!(App: ItemTheme {}); // TODO: provide colors here - // -namespace!(App: Selection { symbol = |app| { - ":select/scene" => app.selection().select_scene(app.tracks().len()), - ":select/scene/next" => app.selection().select_scene_next(app.scenes().len()), - ":select/scene/prev" => app.selection().select_scene_prev(), - ":select/track" => app.selection().select_track(app.tracks().len()), - ":select/track/next" => app.selection().select_track_next(app.tracks().len()), - ":select/track/prev" => app.selection().select_track_prev(), -}; }); - -namespace!(App: Color { - symbol = |app| { - ":color/bg" => Color::Rgb(28, 32, 36), - }; - expression = |app| { - "g" (n: u8) => Color::Rgb(n, n, n), - "rgb" (r: u8, g: u8, b: u8) => Color::Rgb(r, g, b), - }; -}); - -namespace!(App: Option { symbol = |app| { - ":editor/pitch" => Some((app.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8).into()) -}; }); - -namespace!(App: Option { symbol = |app| {}; }); - -namespace!(App: Option { symbol = |app| { - ":selected/scene" => app.selection().scene(), - ":selected/track" => app.selection().track(), -}; }); - -namespace!(App: Option>> { symbol = |app| { - ":selected/clip" => if let Selection::TrackClip { track, scene } = app.selection() { - app.scenes()[*scene].clips[*track].clone() - } else { - None + fn get_arc_str (&self, src: impl Language) -> Perhaps> { + Ok(src.src()?.map(|x|x.into())) } -}; }); + fn get_u16 (&self, src: impl Language) -> Perhaps { + Ok(Some(match src.word()? { + Some(":w/sidebar") => self.project.w_sidebar(self.editor().is_some()), + Some(":h/sample-detail") => 6.max(self.size.h() as u16 * 3 / 9), + _ => return try_to_u16(src) + })) + } + fn get_usize (&self, src: impl Language) -> Perhaps { + Ok(Some(match src.word()? { + Some(":scene-count") => self.scenes().len(), + Some(":track-count") => self.tracks().len(), + Some(":device-kind") => self.dialog.device_kind().unwrap_or(0), + Some(":device-kind/next") => self.dialog.device_kind_next().unwrap_or(0), + Some(":device-kind/prev") => self.dialog.device_kind_prev().unwrap_or(0), + _ => return try_to_usize(src) + })) + } + fn get_bool (&self, src: impl Language) -> Perhaps { + src.word()?.map(|word|Ok(match word { + "Y" => true, + "N" => false, + ":mode/editor" => self.project.editor.is_some(), + ":focused/dialog" => !matches!(self.dialog, Dialog::None), + ":focused/message" => matches!(self.dialog, Dialog::Message(..)), + ":focused/add_device" => matches!(self.dialog, Dialog::Device(..)), + ":focused/browser" => self.dialog.browser().is_some(), + ":focused/pool/import" => matches!(self.pool.mode, Some(PoolMode::Import(..))), + ":focused/pool/export" => matches!(self.pool.mode, Some(PoolMode::Export(..))), + ":focused/pool/rename" => matches!(self.pool.mode, Some(PoolMode::Rename(..))), + ":focused/pool/length" => matches!(self.pool.mode, Some(PoolMode::Length(..))), + ":focused/clip" => !self.editor_focused() && matches!(self.selection(), Selection::TrackClip{..}), + ":focused/track" => !self.editor_focused() && matches!(self.selection(), Selection::Track(..)), + ":focused/scene" => !self.editor_focused() && matches!(self.selection(), Selection::Scene(..)), + ":focused/mix" => !self.editor_focused() && matches!(self.selection(), Selection::Mix), + _ => return Err(format!("not bool: {word}").into()) + })).transpose() + } + fn get_selection (&self, src: impl Language) -> Perhaps { + src.word()?.map(|word|Ok(match word { + ":select/scene" => self.selection().select_scene(self.tracks().len()), + ":select/scene/next" => self.selection().select_scene_next(self.scenes().len()), + ":select/scene/prev" => self.selection().select_scene_prev(), + ":select/track" => self.selection().select_track(self.tracks().len()), + ":select/track/next" => self.selection().select_track_next(self.tracks().len()), + ":select/track/prev" => self.selection().select_track_prev(), + _ => return Err(format!("not selection: {word}").into()) + })).transpose() + } + fn get_color (&self, src: impl Language) -> Perhaps { + if let Some(expr) = src.expr()? { + match (expr.head()?, expr.tail()?) { + (Some("g"), Some(tail)) => { + let n = try_to_u8(expr.tail().map_err(Into::into))?.ok_or(LanguageError::Domain("not gray"))?; + Ok(Some(Color::Rgb(n, n, n))) + }, + (Some("rgb"), Some(tail)) => { + let r = try_to_u8(expr.tail().map_err(Into::into))? + .ok_or(LanguageError::Domain("not red"))?; + let g = try_to_u8(expr.tail().tail().head().map_err(Into::into))? + .ok_or(LanguageError::Domain("not green"))?; + let b = try_to_u8(expr.tail().tail().tail().head().map_err(Into::into))? + .ok_or(LanguageError::Domain("not blue"))?; + Ok(Some(Color::Rgb(r, g, b))) + }, + (Some(_), _) => return Err(format!("not a color expression: {expr}").into()), + (None, _) => return Err(format!("not a color expression: {expr}").into()), + } + } else if let Ok(Some(sym)) = src.word() { + Ok(match sym { + ":color/bg" => Some(Color::Rgb(28, 32, 36)), + ":color/fg" => Some(Color::Rgb(98, 92, 96)), + _ => return Err(format!("not a color: {sym}").into()) + }) + } else { + return Err(format!("not a color: {:?}", src.src()?).into()) + } + } + fn get_opt_u7 (&self, src: impl Language) -> Perhaps> { + src.word()?.map(|word|Ok(match word { + ":editor/pitch" => Some(( + self.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8 + ).into()), + _ => return Err(format!("unknown midi note: {word}").into()) + })).transpose() + } + fn get_opt_u16 (&self, _src: impl Language) -> Perhaps> { + Ok(None) + } + fn get_opt_usize (&self, src: impl Language) -> Perhaps> { + src.word()?.map(|word|Ok(match word { + ":selected/scene" => self.selection().scene(), + ":selected/track" => self.selection().track(), + _ => return Err(format!("unknown opt: {word}").into()) + })).transpose() + } + fn get_clip (&self, src: impl Language) -> Perhaps>>> { + src.word()?.map(|word|Ok(match word { + ":selected/clip" if let Selection::TrackClip { track, scene } = self.selection() => + self.scenes()[*scene].clips[*track].clone(), + _ => return Err(format!("not a clip: {word}").into()) + })).transpose() + } + pub fn inc (&mut self, axis: &ControlAxis) -> Perhaps { + Ok(match (&self.dialog, axis) { + (Dialog::None, _) => todo!(), + (Dialog::Menu(_, _), ControlAxis::Y) => + AppCommand::SetDialog { dialog: self.dialog.menu_next() }.act(self)?, + _ => todo!() + }) + } + pub fn dec (&mut self, axis: &ControlAxis) -> Perhaps { + Ok(match (&self.dialog, axis) { + (Dialog::None, _) => None, + (Dialog::Menu(_, _), ControlAxis::Y) => + AppCommand::SetDialog { dialog: self.dialog.menu_prev() }.act(self)?, + _ => todo!() + }) + } + pub fn confirm (&mut self) -> Perhaps { + Ok(match &self.dialog { + Dialog::Menu(index, items) => { + let callback = items.0[*index].1.clone(); + callback(self)?; + None + }, + _ => todo!(), + }) + } +} pub fn swap_value ( target: &mut T, value: &T, returned: impl Fn(T)->U @@ -360,4 +396,3 @@ pub fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { files.sort(); Ok((subdirs, files)) } - diff --git a/src/app/bind.rs b/src/app/bind.rs index 01628e03..c1a514f8 100644 --- a/src/app/bind.rs +++ b/src/app/bind.rs @@ -1,6 +1,6 @@ use crate::*; -tui_keys!(|self: App, input| { +tui_keys!(self: App, input { let commands = tek_commands_collect(self, input)?; let results = tek_commands_execute(self, commands)?; self.history.extend(results.into_iter()); diff --git a/src/app/cli.rs b/src/app/cli.rs deleted file mode 100644 index 61250d14..00000000 --- a/src/app/cli.rs +++ /dev/null @@ -1,259 +0,0 @@ -use crate::*; - -/// The command-line interface descriptor. -/// -/// ``` -/// let cli: tek::Cli = Default::default(); -/// -/// use clap::CommandFactory; -/// tek::Cli::command().debug_assert(); -/// ``` -#[derive(Parser)] -#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))] -#[derive(Debug, Default)] pub struct Cli { - /// Pre-defined configuration modes. - /// - /// TODO: Replace these with scripted configurations. - #[command(subcommand)] pub action: Action, -} - -/// Application modes that can be passed to the mommand line interface. -/// -/// ``` -/// let action: tek::Action = Default::default(); -/// ``` -#[derive(Debug, Clone, Subcommand, Default)] pub enum Action { - /// Continue where you left off - #[default] Resume, - /// Run headlessly in current session. - Headless, - /// Show status of current session. - Status, - /// List known sessions. - List, - /// Continue work in a copy of the current session. - Fork, - /// Create a new empty session. - New { - /// Name of JACK client - #[arg(short='n', long)] name: Option, - /// Whether to attempt to become transport master - #[arg(short='Y', long, default_value_t = false)] sync_lead: bool, - /// Whether to sync to external transport master - #[arg(short='y', long, default_value_t = true)] sync_follow: bool, - /// Initial tempo in beats per minute - #[arg(short='b', long, default_value = None)] bpm: Option, - /// Whether to include a transport toolbar (default: true) - #[arg(short='c', long, default_value_t = true)] show_clock: bool, - /// MIDI outs to connect to (multiple instances accepted) - #[arg(short='I', long)] midi_from: Vec, - /// MIDI outs to connect to (multiple instances accepted) - #[arg(short='i', long)] midi_from_re: Vec, - /// MIDI ins to connect to (multiple instances accepted) - #[arg(short='O', long)] midi_to: Vec, - /// MIDI ins to connect to (multiple instances accepted) - #[arg(short='o', long)] midi_to_re: Vec, - /// Audio outs to connect to left input - #[arg(short='l', long)] left_from: Vec, - /// Audio outs to connect to right input - #[arg(short='r', long)] right_from: Vec, - /// Audio ins to connect from left output - #[arg(short='L', long)] left_to: Vec, - /// Audio ins to connect from right output - #[arg(short='R', long)] right_to: Vec, - /// Tracks to creat - #[arg(short='t', long)] tracks: Option, - /// Scenes to create - #[arg(short='s', long)] scenes: Option, - }, - /// Import media as new session. - Import, - /// Show configuration. - Config, - /// Show version. - Version, -} - -/// Command-line configuration. -#[cfg(feature = "cli")] -impl Cli { - pub fn run (&self) -> Usually<()> { - self.action.run(Config::init_new(None)?) - } -} - -#[cfg(feature = "cli")] -impl Action { - fn run (&self, config: Config) -> Usually<()> { - use Action::*; - match self { - Version => tek_show_version(), - Config => tek_print_config(&config), - List => todo!("list sessions"), - Resume => todo!("resume session"), - New { - name, bpm, tracks, scenes, sync_lead, sync_follow, - midi_from: mf, midi_from_re: mfr, midi_to: mt, midi_to_re: mtr, - left_from: lf, right_from: rf, left_to: lt, right_to: rt, .. - } => { - let name = name.as_ref().map_or("tek", |x|x.as_str()); - let jack = Jack::new(&name)?; - let mut proj = tek_project_new( - &jack, Clock::new(&jack, *bpm)?, &lf, <, &rf, &rt, &mf, &mt, &mfr, &mtr - )?; - proj.tracks_add(tracks.unwrap_or(0), None, &[], &[])?; - proj.scenes_add(scenes.unwrap_or(0))?; - //if matches!(self, Action::Status) { - //// Show status and exit - //tek_print_status(&proj); - //return Ok(()) - //} - // Initialize the app state - let app = Arc::new(RwLock::new(App::new(&jack, proj, config, ":menu"))); - //if matches!(self, Action::Headless) { - //// TODO: Headless mode (daemon + client over IPC, then over network...) - //println!("todo headless"); - //return Ok(()) - //} - let (_keyboard, _terminal) = Exit::run(|exited|tui_io( - exited.as_ref(), - &app, - Duration::from_millis(100), - Duration::from_millis(10), - std::io::stdout() - ))?; - // Run the [Tui] and [Jack] threads with the [App] state. - //Tui::new(Box::new(std::io::stdout()))?.run(true, &jack.run(move|jack|{ - //// Between jack init and app's first cycle: - //jack.sync_lead(*sync_lead, |mut state|{ - //let clock = app.clock(); - //clock.playhead.update_from_sample(state.position.frame() as f64); - //state.position.bbt = Some(clock.bbt()); - //state.position - //})?; - //jack.sync_follow(*sync_follow)?; - //// FIXME: They don't work properly. - //Ok(app) - //})?)?; - }, - _ => todo!() - } - Ok(()) - } -} - -pub fn tek_project_new ( - jack: &Jack<'static>, - clock: Clock, - left_from: &[impl AsRef], - left_to: &[impl AsRef], - right_from: &[impl AsRef], - right_to: &[impl AsRef], - midi_from: &[impl AsRef], - midi_to: &[impl AsRef], - midi_from_re: &[impl AsRef], - midi_to_re: &[impl AsRef], -) -> Usually { - // TODO: Collect audio IO: - let empty = &[] as &[&str]; - let left_froms = Connect::collect(left_from, empty, empty); - let left_tos = Connect::collect(left_to, empty, empty); - let right_froms = Connect::collect(right_from, empty, empty); - let right_tos = Connect::collect(right_to, empty, empty); - let _audio_froms = &[left_froms.as_slice(), right_froms.as_slice()]; - let _audio_tos = &[left_tos.as_slice(), right_tos.as_slice()]; - Ok(Arrangement::new( - jack, - None, - clock, - vec![], - vec![], - Connect::collect(&midi_from, &[] as &[&str], &midi_from_re).iter().enumerate() - .map(|(index, connect)|jack.midi_in(&format!("M/{index}"), &[connect.clone()])) - .collect::, _>>()?, - Connect::collect(&midi_to, &[] as &[&str], &midi_to_re).iter().enumerate() - .map(|(index, connect)|jack.midi_out(&format!("{index}/M"), &[connect.clone()])) - .collect::, _>>()? - )) -} - -pub fn tek_show_version () { - println!("todo version"); -} - -pub fn tek_print_config (config: &Config) { - use ::ansi_term::Color::*; - println!("{:?}", config.dirs); - for (k, v) in config.views.read().unwrap().iter() { - println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}"))); - } - for (k, v) in config.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); - } - } - } - config.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!(); - }); -} - -pub fn tek_print_status (project: &Arrangement) { - println!("Name: {:?}", &project.name); - println!("JACK: {:?}", &project.jack); - println!("Buffer: {:?}", &project.clock.chunk); - println!("Sample rate: {:?}", &project.clock.timebase.sr); - println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); - println!("Tempo: {:?}", &project.clock.timebase.bpm); - println!("Quantize: {:?}", &project.clock.quant); - println!("Launch: {:?}", &project.clock.sync); - println!("Playhead: {:?}us", &project.clock.playhead.usec); - println!("Playhead: {:?}s", &project.clock.playhead.sample); - println!("Playhead: {:?}p", &project.clock.playhead.pulse); - println!("Started: {:?}", &project.clock.started); - println!("Tracks:"); - for (i, t) in project.tracks.iter().enumerate() { - println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, - &t.sequencer.play_clip, &t.sequencer.next_clip); - } - println!("Scenes:"); - for (i, t) in project.scenes.iter().enumerate() { - println!(" Scene {i}: {} {:?}", &t.name, &t.clips); - } - println!("MIDI Ins: {:?}", &project.midi_ins); - println!("MIDI Outs: {:?}", &project.midi_outs); - println!("Audio Ins: {:?}", &project.audio_ins); - println!("Audio Outs: {:?}", &project.audio_outs); - // TODO git integration - // TODO dawvert integration -} diff --git a/src/app/config.rs b/src/app/config.rs index fab190fa..ff935638 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -38,8 +38,11 @@ impl Config { const DEFAULTS: &'static str = include_str!("../tek.edn"); pub fn watch (callback: impl FnOnce(Self)->T) -> Usually { - let config = Self::init_new(None)?; - let result = callback(config); + let config = Self::init_new(None)?; + let watcher = notify_debouncer_mini::new_debouncer(Duration::from_millis(500), |res| { + println!("{res:?}"); + })?; + let result = callback(config); Ok(result) } diff --git a/src/app/draw.rs b/src/app/draw.rs index 3c534514..3f439d12 100644 --- a/src/app/draw.rs +++ b/src/app/draw.rs @@ -14,7 +14,9 @@ impl View for App { } for (index, dsl) in self.mode.view.iter().enumerate() { if let Err(e) = self.interpret(to, dsl) { - *self.error.write().unwrap() = Some(format!("view #{index}: {e}").into()); + *self.error.write().unwrap() = Some(format!( + "mode {:?} view #{index}: {e}", &self.mode.name, + ).into()); break; } } @@ -45,7 +47,7 @@ fn tek_draw_expr (state: &App, to: &mut Tui, lang: &impl Expression) -> Drawn Drawn { let mut frags = dsl.src()?.unwrap().split("/"); match frags.next() { - Some(":logo") => view_logo().draw(to), + //Some(":logo") => view_logo().draw(to), Some(":meters") => draw_meter_section(to, frags), Some(":tracks") => draw_tracks(to, frags, state), Some(":scenes") => draw_scenes(to, frags), @@ -56,6 +58,7 @@ fn tek_draw_word (state: &App, to: &mut Tui, dsl: &impl Expression) -> Drawn view_device(state).draw(to), Some(":status") => "TODO: Status Bar".exact_h(1).draw(to), Some(":editor") => "TODO Editor".draw(to), + Some(":transport") => view_transport(true, "", "", "").draw(to), Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).draw(to), Some(_) => { let views = state.config.views.read().unwrap(); @@ -109,7 +112,7 @@ pub fn draw_dialog ( let f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) }; let b = if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) }; fg_bg(f, b, item.full_w().align_w().exact_h(2)) - .push_y((2 * index) as u16).draw(to)?; + .push_y((4 * index) as u16).draw(to)?; } Ok(Some(to.area().into())) }).full_wh()) @@ -141,19 +144,6 @@ pub fn draw_templates (to: &mut Tui, frags: std::str::Split<&str>, state: &App) }).min_w(30).exact_h(height).draw(to) } -/// ``` -/// let _ = tek::view_logo(); -/// ``` -pub fn view_logo () -> impl Draw { - bold(true, fg(Rgb(240, 200, 180), south!{ - "".exact_h(1), - "".exact_h(1), - "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~".exact_h(1), - east("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", east(fg(Rgb(230,100,40), "v0.3.0"), " ~~")).exact_h(1), - "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~".exact_h(1), - })).exact_wh(32, 7) -} - /// ``` /// let x = ""; /// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref()); @@ -162,7 +152,7 @@ pub fn view_logo () -> impl Draw { pub fn view_transport (play: bool, bpm: &str, beat: &str, time: &str) -> impl Draw { let theme = ItemTheme::G[96]; bg(Black, east!(above( - button_play_pause(play, false).align_w().full_wh(), + button_play_pause(play, false).align_w(), east!( field_h(theme, "BPM", bpm), field_h(theme, "Beat", beat), @@ -465,7 +455,7 @@ pub fn view_track_outputs ( south(button_2("o", "utput", false).align_w().full_w(), thunk(|to: &mut Tui|{ for port in midi_outs { - port.port_name().align_w().full_w().draw(to); + let _ = port.port_name().align_w().full_w().draw(to)?; } Ok(Some(XYWH(0, 0, 0, 0))) })), @@ -584,7 +574,7 @@ pub fn view_inputs (tracks: impl TracksSizes<'_>, midi_ins: &[MidiInput]) -> imp let title_2 = button_2("I", "+", false).exact_wh(4, 1); east(title_1, west(title_2, thunk(move|to: &mut Tui|{ for (_index, track, x1, _x2) in tracks { - south( + let _ = south( bg(track.color.dark.term, east!( either(track.sequencer.monitoring, fg(Green, "mon "), "mon "), either(track.sequencer.recording, fg(Red, "rec "), "rec "), @@ -592,26 +582,24 @@ pub fn view_inputs (tracks: impl TracksSizes<'_>, midi_ins: &[MidiInput]) -> imp ).exact_w(track.width as u16)).align_w().push_x(x1 as u16), thunk(move |to: &mut Tui|{ for (index, port) in midi_ins.iter().enumerate() { - east( + let _ = east( east( " ● ", bold(true, fg(Rgb(255,255,255), port.port_name())) ).align_w().exact_w(20), west( ().exact_w(4), - thunk(move|to: &mut Tui|{ - bg(track.color.darker.term, east!( - either(track.sequencer.monitoring, fg(Green, " ● "), " · "), - either(track.sequencer.recording, fg(Red, " ● "), " · "), - either(track.sequencer.overdub, fg(Yellow, " ● "), " · "), - ).exact_w(track.width as u16).align_w()).draw(to); - todo!() - })) - ).push_x(index as u16 * 10).exact_h(1).draw(to); + bg(track.color.darker.term, east!( + either(track.sequencer.monitoring, fg(Green, " ● "), " · "), + either(track.sequencer.recording, fg(Red, " ● "), " · "), + either(track.sequencer.overdub, fg(Yellow, " ● "), " · "), + ).exact_w(track.width as u16).align_w()) + ) + ).push_x(index as u16 * 10).exact_h(1).draw(to)?; } todo!() }) - ).draw(to); + ).draw(to)?; } todo!() }))) @@ -646,7 +634,7 @@ pub fn view_outputs ( view_track_row_section(theme, list, button_2("O", "+", false), bg(theme.darker.term, thunk(|to: &mut Tui|{ for (index, track, _x1, _x2) in tracks { - thunk(|to: &mut Tui|{ + let _ = thunk(|to: &mut Tui|{ east( either(true, fg(Green, "play "), "play "), either(false, fg(Yellow, "solo "), "solo "), @@ -661,7 +649,7 @@ pub fn view_outputs ( } } todo!() - }).exact_w(track_width(index, track)).draw(to); + }).exact_w(track_width(index, track)).draw(to)?; } todo!() }).align_w().full_w())).exact_h(height) @@ -711,10 +699,14 @@ pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( }).align_x()) } -pub fn field_h (theme: ItemTheme, head: impl Draw, body: impl Draw) -> impl Draw { +pub fn field_h ( + _theme: ItemTheme, _head: impl Draw, _body: impl Draw +) -> impl Draw { } -pub fn field_v (theme: ItemTheme, head: impl Draw, body: impl Draw) -> impl Draw { +pub fn field_v ( + _theme: ItemTheme, _head: impl Draw, _body: impl Draw +) -> impl Draw { } pub fn view_sessions () -> impl Draw { diff --git a/src/deps.rs b/src/deps.rs index f599cc16..2f9683a8 100644 --- a/src/deps.rs +++ b/src/deps.rs @@ -1,20 +1,3 @@ -#[cfg(feature = "cli")] -pub(crate) use ::clap::{self, Parser, Subcommand}; - -pub(crate) use ::xdg::BaseDirectories; - -pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*}; - -pub(crate) use tengri::{ - *, lang::*, - crossterm::event::{Event, KeyEvent}, - ratatui::{ - self, - prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}}, - widgets::{Widget, canvas::{Canvas, Line}}, - }, -}; - #[allow(unused)] pub(crate) use ::{ std::{ @@ -30,4 +13,20 @@ pub(crate) use ::{ time::Duration, thread::{spawn, JoinHandle}, }, + xdg::{ + BaseDirectories, + }, + tengri::{ + *, + lang::*, + midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*}, + crossterm::event::{Event, KeyEvent}, + ratatui::{ + self, + prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}}, + widgets::{Widget, canvas::{Canvas, Line}}, + }, + }, }; +#[cfg(feature = "cli")] +pub(crate) use ::clap::{self, Parser, Subcommand}; diff --git a/src/device.rs b/src/device.rs index 614a8735..8e842c52 100644 --- a/src/device.rs +++ b/src/device.rs @@ -100,6 +100,7 @@ pub trait HasDevices: AsRef> + AsMut> { } } +pub use ::tengri::sing::*; pub mod arrange; pub use self::arrange::*; pub mod browse; pub use self::browse::*; pub mod clock; pub use self::clock::*; @@ -108,9 +109,36 @@ pub mod editor; pub use self::editor::*; pub mod menu; pub use self::menu::*; pub mod meter; pub use self::meter::*; pub mod mix; pub use self::mix::*; -pub mod port; pub use self::port::*; pub mod sampler; pub use self::sampler::*; pub mod sequence; pub use self::sequence::*; #[cfg(feature = "plugin")] pub mod plugin; #[cfg(feature = "plugin")] pub use self::plugin::*; + +def_command!(AudioInputCommand: |port: AudioInput| { + Close => todo!(), + Connect { audio_out: Arc } => todo!(), +}); + +def_command!(AudioOutputCommand: |port: AudioOutput| { + Close => todo!(), + Connect { audio_in: Arc } => todo!(), +}); + +def_command!(MidiInputCommand: |port: MidiInput| { + Close => todo!(), + Connect { midi_out: Arc } => todo!(), +}); + +def_command!(MidiOutputCommand: |port: MidiOutput| { + Close => todo!(), + Connect { midi_in: Arc } => todo!(), +}); + +pub struct Junction(T); + +impl View for Junction { + fn view (&self) -> impl Draw { + T::KIND + } +} diff --git a/src/device/arrange.rs b/src/device/arrange.rs index e3836693..d54a8b2e 100644 --- a/src/device/arrange.rs +++ b/src/device/arrange.rs @@ -5,7 +5,8 @@ use crate::*; /// ``` /// let arranger = tek::Arrangement::default(); /// ``` -#[derive(Default, Debug)] pub struct Arrangement { +#[derive(Default, Debug)] +pub struct Arrangement { /// Project name. pub name: Arc, /// Base color. @@ -19,6 +20,8 @@ use crate::*; pub size: Sizer, /// Display size of clips area pub size_inner: Sizer, + /// Selected UI element + pub selection: Selection, /// Source of time #[cfg(feature = "clock")] pub clock: Clock, /// Allows one MIDI clip to be edited @@ -31,8 +34,6 @@ use crate::*; #[cfg(feature = "port")] pub audio_ins: Vec, /// List of global audio outputs #[cfg(feature = "port")] pub audio_outs: Vec, - /// Selected UI element - #[cfg(feature = "select")] pub selection: Selection, /// Last track number (to avoid duplicate port names) #[cfg(feature = "track")] pub track_last: usize, /// List of tracks @@ -45,34 +46,58 @@ use crate::*; #[cfg(feature = "scene")] pub scene_scroll: usize, } -impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &self.jack } } -impl_has!(Jack<'static>: |self: Arrangement| self.jack); -impl_has!(Sizer: |self: Arrangement| self.size); -impl_has!(Vec: |self: Arrangement| self.midi_ins); -impl_has!(Vec: |self: Arrangement| self.midi_outs); -impl_has!(Clock: |self: Arrangement| self.clock); -impl_has!(Selection: |self: Arrangement| self.selection); -impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref()); -impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut()); +/// Represents the current user selection in the arranger +#[derive(PartialEq, Clone, Copy, Debug, Default)] +pub enum Selection { + #[default] + /// Nothing is selected + Nothing, + /// The whole mix is selected + Mix, + /// A MIDI input is selected. + Input(usize), + /// A MIDI output is selected. + Output(usize), + /// A scene is selected. + #[cfg(feature = "scene")] Scene(usize), + /// A track is selected. + #[cfg(feature = "track")] Track(usize), + /// A clip (track × scene) is selected. + #[cfg(feature = "track")] TrackClip { track: usize, scene: usize }, + /// A track's MIDI input connection is selected. + #[cfg(feature = "track")] TrackInput { track: usize, port: usize }, + /// A track's MIDI output connection is selected. + #[cfg(feature = "track")] TrackOutput { track: usize, port: usize }, + /// A track device slot is selected. + #[cfg(feature = "track")] TrackDevice { track: usize, device: usize }, +} impl Arrangement { /// Create a new arrangement. pub fn new ( - jack: &Jack<'static>, - name: Option>, - clock: Clock, - tracks: Vec, - scenes: Vec, - midi_ins: Vec, - midi_outs: Vec, + jack: &Jack<'static>, + name: Arc, + clock: Clock, + tracks: impl Iterator, + scenes: impl Iterator, + midi_ins: impl Iterator, + midi_outs: impl Iterator, + audio_ins: impl Iterator, + audio_outs: impl Iterator, ) -> Self { Self { - clock, tracks, scenes, midi_ins, midi_outs, - jack: jack.clone(), - name: name.unwrap_or_default(), - color: ItemTheme::random(), - selection: Selection::TrackClip { track: 0, scene: 0 }, + jack: jack.clone(), + color: ItemTheme::random(), + selection: Selection::TrackClip { track: 0, scene: 0 }, + tracks: tracks.collect(), + scenes: scenes.collect(), + midi_ins: midi_ins.collect(), + midi_outs: midi_outs.collect(), + audio_ins: audio_ins.collect(), + audio_outs: audio_outs.collect(), + clock, + name, ..Default::default() } } @@ -117,7 +142,759 @@ impl Arrangement { } -#[cfg(feature = "clip")] mod clip; #[cfg(feature = "clip")] pub use self::clip::*; -#[cfg(feature = "scene")] mod scene; #[cfg(feature = "scene")] pub use self::scene::*; -#[cfg(feature = "track")] mod track; #[cfg(feature = "track")] pub use self::track::*; -#[cfg(feature = "select")] mod select; #[cfg(feature = "select")] pub use self::select::*; +impl Selection { + pub fn describe ( + &self, + #[cfg(feature = "track")] tracks: &[Track], + #[cfg(feature = "scene")] scenes: &[Scene], + ) -> Arc { + use Selection::*; + format!("{}", match self { + Mix => "Everything".to_string(), + #[cfg(feature = "scene")] Scene(s) => + scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()), + #[cfg(feature = "track")] Track(t) => + tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()), + TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) { + (Some(_), Some(s)) => match s.clip(*track) { + Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name), + None => format!("T{track} S{scene}: Empty") + }, + _ => format!("T{track} S{scene}: Empty"), + }, + _ => todo!() + }).into() + } + #[cfg(feature = "scene")] pub fn scene (&self) -> Option { + use Selection::*; + match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None } + } + #[cfg(feature = "scene")] pub fn select_scene (&self, scene_count: usize) -> Self { + use Selection::*; + match self { + Mix | Track(_) => Scene(0), + Scene(s) => Scene((s + 1) % scene_count), + TrackClip { scene, .. } => Track(*scene), + _ => todo!(), + } + } + #[cfg(feature = "scene")] pub fn select_scene_next (&self, len: usize) -> Self { + use Selection::*; + match self { + Mix => Scene(0), + Track(t) => TrackClip { track: *t, scene: 0 }, + Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix }, + TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) }, + _ => todo!() + } + } + #[cfg(feature = "scene")] pub fn select_scene_prev (&self) -> Self { + use Selection::*; + match self { + Mix | Scene(0) => Mix, + Scene(s) => Scene(s - 1), + Track(t) => Track(*t), + TrackClip { track, scene: 0 } => Track(*track), + TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 }, + _ => todo!() + } + } + #[cfg(feature = "track")] pub fn track (&self) -> Option { + use Selection::*; + if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self { + Some(*track) + } else { + None + } + } + #[cfg(feature = "track")] pub fn select_track (&self, track_count: usize) -> Self { + use Selection::*; + match self { + Mix => Track(0), + Scene(_) => Mix, + Track(t) => Track((t + 1) % track_count), + TrackClip { track, .. } => Track(*track), + _ => todo!(), + } + } + #[cfg(feature = "track")] pub fn select_track_next (&self, len: usize) -> Self { + use Selection::*; + match self { + Mix => Track(0), + Scene(s) => TrackClip { track: 0, scene: *s }, + Track(t) => if t + 1 < len { Track(t + 1) } else { Mix }, + TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) }, + _ => todo!() + } + } + #[cfg(feature = "track")] pub fn select_track_prev (&self) -> Self { + use Selection::*; + match self { + Mix => Mix, + Scene(s) => Scene(*s), + Track(0) => Mix, + Track(t) => Track(t - 1), + TrackClip { track: 0, scene } => Scene(*scene), + TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene }, + _ => todo!() + } + } +} + +impl +AsMut> HasSelection for T {} + +pub trait HasSelection: AsRef + AsMut { + fn selection (&self) -> &Selection { self.as_ref() } + fn selection_mut (&mut self) -> &mut Selection { self.as_mut() } + /// Get the active track + #[cfg(feature = "track")] + fn selected_track (&self) -> Option<&Track> where Self: HasTracks { + let index = self.selection().track()?; + self.tracks().get(index) + } + /// Get a mutable reference to the active track + #[cfg(feature = "track")] + fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks { + let index = self.selection().track()?; + self.tracks_mut().get_mut(index) + } + /// Get the active scene + #[cfg(feature = "scene")] + fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes { + let index = self.selection().scene()?; + self.scenes().get(index) + } + /// Get a mutable reference to the active scene + #[cfg(feature = "scene")] + fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes { + let index = self.selection().scene()?; + self.scenes_mut().get_mut(index) + } + /// Get the active clip + #[cfg(feature = "clip")] + fn selected_clip (&self) -> Option>> where Self: HasScenes + HasTracks { + self.selected_scene()?.clips.get(self.selection().track()?)?.clone() + } +} + +impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &self.jack } } +impl_has!(Jack<'static>: |self: Arrangement| self.jack); +impl_has!(Sizer: |self: Arrangement| self.size); +impl_has!(Vec: |self: Arrangement| self.midi_ins); +impl_has!(Vec: |self: Arrangement| self.midi_outs); +impl_has!(Clock: |self: Arrangement| self.clock); +impl_has!(Selection: |self: Arrangement| self.selection); +impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref()); +impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut()); + +pub trait HasClipsSize { + fn clips_size (&self) -> &Sizer; +} +pub trait ClipsView: TracksView + ScenesView { + /// Draw clips per scene + fn view_scenes_clips <'a> (&'a self) -> impl Draw + 'a { + view_scenes_clips( + ||self.scenes_with_sizes(), + self.tracks_with_sizes(), + self.selection(), + self.editor(), + self.clips_size(), + self.is_editing(), + ) + } +} + +impl HasClipsSize for App { + fn clips_size (&self) -> &Sizer { &self.project.size_inner } +} + +impl HasClipsSize for Arrangement { + fn clips_size (&self) -> &Sizer { &self.size_inner } +} + +/// TODO: Preserve the generic passthru syntax; +/// remove this macro (only used twice) and potentially the trait. +#[macro_export] macro_rules! impl_has_clips { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? { + fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> { + $cb.read().unwrap() + } + fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> { + $cb.write().unwrap() + } + } + } +} +#[macro_export] macro_rules! has_clip { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? { + fn clip (&$self) -> Option>> { $cb } + } + } +} +def_command!(ClipCommand: |clip: MidiClip| { + SetColor { color: Option } => { + //(SetColor [t: usize, s: usize, c: ItemTheme] + //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); + //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) + todo!() + }, + SetLoop { looping: Option } => { + //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) + //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) + todo!() + } +}); +impl Arrangement { + /// Put a clip in a slot + pub fn clip_put ( + &mut self, track: usize, scene: usize, clip: Option>> + ) -> Option>> { + let old = self.scenes[scene].clips[track].clone(); + self.scenes[scene].clips[track] = clip; + old + } + + /// Change the color of a clip, returning the previous one + pub fn clip_set_color ( + &self, track: usize, scene: usize, color: ItemTheme + ) -> Option { + self.scenes[scene].clips[track].as_ref().map(|clip|{ + let mut clip = clip.write().unwrap(); + let old = clip.color.clone(); + clip.color = color.clone(); + panic!("{color:?} {old:?}"); + //old + }) + } + + /// Toggle looping for the active clip + pub fn toggle_loop (&mut self) { + if let Some(clip) = self.selected_clip() { + clip.write().unwrap().toggle_loop() + } + } +} + +/// Default scene height. +pub const H_SCENE: usize = 2; + +/// Default editor height. +pub const H_EDITOR: usize = 15; + +/// A scene consists of a set of clips to play together. +/// +/// ``` +/// let scene: tek::Scene = Default::default(); +/// let _ = scene.pulses(); +/// let _ = scene.is_playing(&[]); +/// ``` +#[derive(Debug, Default)] pub struct Scene { + /// Name of scene + pub name: Arc, + /// Identifying color of scene + pub color: ItemTheme, + /// Clips in scene, one per track + pub clips: Vec>>>, +} + +impl Scene { + /// Returns the pulse length of the longest clip in the scene + pub fn pulses (&self) -> usize { + self.clips.iter().fold(0, |a, p|{ + a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) + }) + } + /// Returns true if all clips in the scene are + /// currently playing on the given collection of tracks. + pub fn is_playing (&self, tracks: &[Track]) -> bool { + self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() + .all(|(track_index, clip)|match clip { + Some(c) => tracks + .get(track_index) + .map(|track|{ + if let Some((_, Some(clip))) = track.sequencer().play_clip() { + *clip.read().unwrap() == *c.read().unwrap() + } else { + false + } + }) + .unwrap_or(false), + None => true + }) + } + pub fn clip (&self, index: usize) -> Option<&Arc>> { + match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None } + } + fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } + fn _todo_usize_stub_ (&self) -> usize { todo!() } + fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } + fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } +} + +pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync { + fn h_scenes (&self) -> u16; + fn w_side (&self) -> u16; + fn w_mid (&self) -> u16; + + fn view_scenes_names (&self) -> impl Draw { + view_scenes_names( + self.scenes_with_sizes(), self.selection(), self.editor(), self.is_editing() + ) + } + fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { + let mut y = 0; + self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ + let height = if self.selection().scene() == Some(s) && self.editor().is_some() { + 8 + } else { + H_SCENE + }; + if y + height <= self.clips_size().h() as usize { + let data = (s, scene, y, y + height); + y += height; + Some(data) + } else { + None + } + }) + } +} + +pub trait HasSceneScroll: HasScenes { + fn scene_scroll (&self) -> usize; +} + +pub trait HasScene: AsRefOpt + AsMutOpt { + fn scene_mut (&mut self) -> Option<&mut Scene> { self.as_mut_opt() } + fn scene (&self) -> Option<&Scene> { self.as_ref_opt() } +} + +pub trait HasScenes: AsRef> + AsMut> { + fn scenes (&self) -> &Vec { self.as_ref() } + fn scenes_mut (&mut self) -> &mut Vec { self.as_mut() } + /// Generate the default name for a new scene + fn scene_default_name (&self) -> Arc { format!("s{:3>}", self.scenes().len() + 1).into() } + fn scene_longest_name (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) } + /// Add multiple scenes + fn scenes_add (&mut self, n: usize) -> Usually<()> where Self: HasTracks { + let scene_color_1 = ItemColor::random(); + let scene_color_2 = ItemColor::random(); + for i in 0..n { + let _ = self.scene_add(None, Some( + scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() + ))?; + } + Ok(()) + } + /// Add a scene + fn scene_add (&mut self, name: Option<&str>, color: Option) + -> Usually<(usize, &mut Scene)> where Self: HasTracks + { + let scene = Scene { + name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()), + clips: vec![None;self.tracks().len()], + color: color.unwrap_or_else(ItemTheme::random), + }; + self.scenes_mut().push(scene); + let index = self.scenes().len() - 1; + Ok((index, &mut self.scenes_mut()[index])) + } +} + +impl HasSceneScroll for Arrangement { + fn scene_scroll (&self) -> usize { self.scene_scroll } +} + +impl HasSceneScroll for App { + fn scene_scroll (&self) -> usize { self.project.scene_scroll() } +} + +impl ScenesView for Arrangement { + fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) } + fn w_side (&self) -> u16 { (self.size.w() as u16 * 2 / 10).max(20) } + fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) } +} + +pub type SceneWith<'a, T> = + (usize, &'a Scene, usize, usize, T); + +def_command!(SceneCommand: |scene: Scene| { + SetSize { size: usize } => { todo!() }, + SetZoom { size: usize } => { todo!() }, + SetName { name: Arc } => + swap_value(&mut scene.name, name, |name|Self::SetName{name}), + SetColor { color: ItemTheme } => + swap_value(&mut scene.color, color, |color|Self::SetColor{color}), +}); + +#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt()); +#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); +#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene()); +#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut()); +impl_has!(Vec: |self: Arrangement| self.scenes); +impl ClipsView for T {} +impl>+AsMut>> HasScenes for T {} +impl+AsMutOpt+Send+Sync> HasScene for T {} + +impl ScenesView for App { + fn w_mid (&self) -> u16 { + (self.size.w() as u16).saturating_sub(self.w_side()) + } + fn w_side (&self) -> u16 { + 20 + } + fn h_scenes (&self) -> u16 { + (self.size.h() as u16).saturating_sub(20) + } +} + +/// A track consists of a sequencer and zero or more devices chained after it. +/// +/// ``` +/// let track: tek::Track = Default::default(); +/// ``` +#[derive(Debug, Default)] pub struct Track { + /// Name of track + pub name: Arc, + /// Identifying color of track + pub color: ItemTheme, + /// Preferred width of track column + pub width: usize, + /// MIDI sequencer state + pub sequencer: Sequencer, + /// Device chain + pub devices: Vec, +} + +impl Track { + /// Create a new track with only the default [Sequencer]. + pub fn new ( + name: &impl AsRef, + color: Option, + jack: &Jack<'static>, + clock: Option<&Clock>, + clip: Option<&Arc>>, + midi_from: &[Connect], + midi_to: &[Connect], + ) -> Usually { + Ok(Self { + name: name.as_ref().into(), + color: color.unwrap_or_default(), + sequencer: Sequencer::new( + format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to + )?, + ..Default::default() + }) + } + pub fn audio_ins (&self) -> &[AudioInput] { + self.devices.first().map(|x|x.audio_ins()).unwrap_or_default() + } + pub fn audio_outs (&self) -> &[AudioOutput] { + self.devices.last().map(|x|x.audio_outs()).unwrap_or_default() + } + fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } + fn _todo_usize_stub_ (&self) -> usize { todo!() } + fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } + fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } + + pub fn per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + tracks: impl Fn() -> U + Send + Sync + 'a, + callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + ) -> impl Draw + 'a { + view_track_per(tracks, callback) + } +} + +#[cfg(feature = "sampler")] +impl Track { + + /// Create a new track connecting the [Sequencer] to a [Sampler]. + pub fn new_with_sampler ( + name: &impl AsRef, + color: Option, + jack: &Jack<'static>, + clock: Option<&Clock>, + clip: Option<&Arc>>, + midi_from: &[Connect], + midi_to: &[Connect], + audio_from: &[&[Connect];2], + audio_to: &[&[Connect];2], + ) -> Usually { + let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?; + let client_name = jack.with_client(|c|c.name().to_string()); + let port_name = track.sequencer.midi_outs[0].port_name(); + let connect = [Connect::exact(format!("{client_name}:{}", port_name))]; + track.devices.push(Device::Sampler(Sampler::new( + jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to + )?)); + Ok(track) + } + + pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> { + for device in self.devices.iter() { + match device { + Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, + _ => {} + } + } + None + } + + pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> { + for device in self.devices.iter_mut() { + match device { + Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, + _ => {} + } + } + None + } +} + +impl HasWidth for Track { + const MIN_WIDTH: usize = 9; + fn width_inc (&mut self) { self.width += 1; } + fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } } +} + +pub trait HasTracks: AsRef> + AsMut> { + fn tracks (&self) -> &Vec { self.as_ref() } + fn tracks_mut (&mut self) -> &mut Vec { self.as_mut() } + /// Run audio callbacks for every track and every device + fn process_tracks (&mut self, client: &Client, scope: &ProcessScope) -> Control { + for track in self.tracks_mut().iter_mut() { + if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { + return Control::Quit + } + for device in track.devices.iter_mut() { + if Control::Quit == DeviceAudio(device).process(client, scope) { + return Control::Quit + } + } + } + Control::Continue + } + fn track_longest_name (&self) -> usize { + self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) + } + /// Stop all playing clips + fn tracks_stop_all (&mut self) { + for track in self.tracks_mut().iter_mut() { + track.sequencer.enqueue_next(None); + } + } + /// Stop all playing clips + fn tracks_launch (&mut self, clips: Option>>>>) { + if let Some(clips) = clips { + for (clip, track) in clips.iter().zip(self.tracks_mut()) { + track.sequencer.enqueue_next(clip.as_ref()); + } + } else { + for track in self.tracks_mut().iter_mut() { + track.sequencer.enqueue_next(None); + } + } + } + /// Spacing between tracks. + const TRACK_SPACING: usize = 0; +} + +pub trait HasTrack: AsRefOpt + AsMutOpt { + fn track (&self) -> Option<&Track> { self.as_ref_opt() } + fn track_mut (&mut self) -> Option<&mut Track> { self.as_mut_opt() } + + #[cfg(feature = "port")] + fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw + 'a { + view_midi_ins_status(theme, self.track()) + } + + #[cfg(feature = "port")] + fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw + '_ { + view_midi_outs_status(theme, self.track()) + } + + #[cfg(feature = "port")] + fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw { + view_audio_ins_status(theme, self.track()) + } + + #[cfg(feature = "port")] + fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw { + view_audio_outs_status(theme, self.track()) + } +} + +pub trait HasTrackScroll: HasTracks { + fn track_scroll (&self) -> usize; +} + +pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll { + /// Draw name of each track + fn view_track_names (&self, theme: ItemTheme) -> impl Draw { + view_track_names( + theme, self.tracks_with_sizes(), self.tracks().len(), self.scenes().len(), self.selection() + ) + } + /// Draw outputs per track + fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw { + view_track_outputs( + theme, self.tracks_with_sizes(), self.midi_outs().iter() + ) + } + /// Draw inputs per track + fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw { + let mut h = 0u16; + for track in self.tracks().iter() { + h = h.max(track.sequencer.midi_ins.len() as u16); + } + view_track_inputs(theme, self.tracks_with_sizes(), h) + } + /// Iterate over tracks with their corresponding sizes. + fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { + let _editor_width = self.editor().map(|e|e.size.w()); + let _active_track = self.selection().track(); + let mut x = 0; + let w = self.clips_size().w() as usize; + self.tracks().iter().enumerate().map_while(move |(index, track)|{ + let width = track.width.max(8); + if x + width < w { + let data = (index, track, x, x + width); + x += width + Self::TRACK_SPACING; + Some(data) + } else { + None + } + }) + } + +} + +impl HasTrackScroll for Arrangement { + fn track_scroll (&self) -> usize { self.track_scroll } +} + +def_command!(TrackCommand: |track: Track| { + Stop => { track.sequencer.enqueue_next(None); Ok(None) }, + SetRec { rec: Option } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }), + SetMon { mon: Option } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }), + SetMute { mute: Option } => todo!(), + SetSolo { solo: Option } => todo!(), + SetSize { size: usize } => todo!(), + SetZoom { zoom: usize } => todo!(), + SetName { name: Arc } => swap_value(&mut track.name, name, |name|Self::SetName { name }), + SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }), +}); + +impl>+AsMut>> HasTracks for T {} +impl+AsMutOpt+Send+Sync> HasTrack for T {} +impl TracksView for T {} +impl_has!(Vec: |self: Arrangement| self.tracks); +impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track()); +impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut()); +impl_as_ref!(Vec: |self: App| self.project.as_ref()); +impl_as_mut!(Vec: |self: App| self.project.as_mut()); +#[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt()); +#[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt()); + +impl Arrangement { + + pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw + '_ { + view_inputs( + self.tracks_with_sizes(), self.midi_ins().as_slice() + ) + } + + pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw { + view_outputs( + theme, self.tracks_with_sizes(), self.midi_outs(), self.outputs_height() + ) + } + + pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { + view_track_devices( + theme, self.tracks_with_sizes(), self.track(), self.devices_height() + ) + } + + fn devices_height (&self) -> u16 { + let mut h = 2; + for track in self.tracks().iter() { + h = h.max(track.devices.len() * 2); + } + h as u16 + } + + fn outputs_height (&self) -> u16 { + let mut h = 1; + for output in self.midi_outs().iter() { + h += 1 + output.connections.len(); + } + h as u16 + } + + /// Add multiple tracks + pub fn tracks_add ( + &mut self, count: usize, width: Option, mins: &[Connect], mouts: &[Connect], + ) -> Usually<()> { + let track_color_1 = ItemColor::random(); + let track_color_2 = ItemColor::random(); + for i in 0..count { + let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); + let track = self.track_add(None, Some(color), mins, mouts)?.1; + if let Some(width) = width { + track.width = width; + } + } + Ok(()) + } + + /// Add a track + pub fn track_add ( + &mut self, + name: Option<&str>, + color: Option, + mins: &[Connect], + mouts: &[Connect], + ) -> Usually<(usize, &mut Track)> { + let name: Arc = name.map_or_else( + ||format!("trk{:02}", self.track_last).into(), + |x|x.to_string().into() + ); + self.track_last += 1; + let track = Track { + width: (name.len() + 2).max(12), + color: color.unwrap_or_else(ItemTheme::random), + sequencer: Sequencer::new( + &format!("{name}"), + self.jack(), + Some(self.clock()), + None, + mins, + mouts + )?, + name, + ..Default::default() + }; + self.tracks_mut().push(track); + let len = self.tracks().len(); + let index = len - 1; + for scene in self.scenes_mut().iter_mut() { + while scene.clips.len() < len { + scene.clips.push(None); + } + } + Ok((index, &mut self.tracks_mut()[index])) + } +} + +impl HasTrackScroll for App { + fn track_scroll (&self) -> usize { + self.project.track_scroll() + } +} + +pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { + track.width as u16 +} diff --git a/src/device/arrange/clip.rs b/src/device/arrange/clip.rs deleted file mode 100644 index e572a9da..00000000 --- a/src/device/arrange/clip.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::*; - -/// TODO: Preserve the generic passthru syntax; -/// remove this macro (only used twice) and potentially the trait. -#[macro_export] macro_rules! impl_has_clips { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? { - fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> { - $cb.read().unwrap() - } - fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> { - $cb.write().unwrap() - } - } - } -} - -#[macro_export] macro_rules! has_clip { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? { - fn clip (&$self) -> Option>> { $cb } - } - } -} - -pub trait ClipsView: TracksView + ScenesView { - /// Draw clips per scene - fn view_scenes_clips <'a> (&'a self) -> impl Draw + 'a { - view_scenes_clips( - ||self.scenes_with_sizes(), - self.tracks_with_sizes(), - self.selection(), - self.editor(), - self.clips_size(), - self.is_editing(), - ) - } -} - -def_command!(ClipCommand: |clip: MidiClip| { - SetColor { color: Option } => { - //(SetColor [t: usize, s: usize, c: ItemTheme] - //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); - //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) - todo!() - }, - SetLoop { looping: Option } => { - //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) - //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) - todo!() - } -}); - -impl Arrangement { - /// Put a clip in a slot - pub fn clip_put ( - &mut self, track: usize, scene: usize, clip: Option>> - ) -> Option>> { - let old = self.scenes[scene].clips[track].clone(); - self.scenes[scene].clips[track] = clip; - old - } - - /// Change the color of a clip, returning the previous one - pub fn clip_set_color ( - &self, track: usize, scene: usize, color: ItemTheme - ) -> Option { - self.scenes[scene].clips[track].as_ref().map(|clip|{ - let mut clip = clip.write().unwrap(); - let old = clip.color.clone(); - clip.color = color.clone(); - panic!("{color:?} {old:?}"); - //old - }) - } - - /// Toggle looping for the active clip - pub fn toggle_loop (&mut self) { - if let Some(clip) = self.selected_clip() { - clip.write().unwrap().toggle_loop() - } - } -} - -pub trait HasClipsSize { - fn clips_size (&self) -> &Sizer; -} - -impl HasClipsSize for App { - fn clips_size (&self) -> &Sizer { &self.project.size_inner } -} - -impl HasClipsSize for Arrangement { - fn clips_size (&self) -> &Sizer { &self.size_inner } -} diff --git a/src/device/arrange/scene.rs b/src/device/arrange/scene.rs deleted file mode 100644 index d7c57966..00000000 --- a/src/device/arrange/scene.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::*; - -/// Default scene height. -pub const H_SCENE: usize = 2; - -/// Default editor height. -pub const H_EDITOR: usize = 15; - -/// A scene consists of a set of clips to play together. -/// -/// ``` -/// let scene: tek::Scene = Default::default(); -/// let _ = scene.pulses(); -/// let _ = scene.is_playing(&[]); -/// ``` -#[derive(Debug, Default)] pub struct Scene { - /// Name of scene - pub name: Arc, - /// Identifying color of scene - pub color: ItemTheme, - /// Clips in scene, one per track - pub clips: Vec>>>, -} - -impl Scene { - /// Returns the pulse length of the longest clip in the scene - pub fn pulses (&self) -> usize { - self.clips.iter().fold(0, |a, p|{ - a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) - }) - } - /// Returns true if all clips in the scene are - /// currently playing on the given collection of tracks. - pub fn is_playing (&self, tracks: &[Track]) -> bool { - self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() - .all(|(track_index, clip)|match clip { - Some(c) => tracks - .get(track_index) - .map(|track|{ - if let Some((_, Some(clip))) = track.sequencer().play_clip() { - *clip.read().unwrap() == *c.read().unwrap() - } else { - false - } - }) - .unwrap_or(false), - None => true - }) - } - pub fn clip (&self, index: usize) -> Option<&Arc>> { - match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None } - } - fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } - fn _todo_usize_stub_ (&self) -> usize { todo!() } - fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } - fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } -} - -pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync { - fn h_scenes (&self) -> u16; - fn w_side (&self) -> u16; - fn w_mid (&self) -> u16; - - fn view_scenes_names (&self) -> impl Draw { - view_scenes_names( - self.scenes_with_sizes(), self.selection(), self.editor(), self.is_editing() - ) - } - fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { - let mut y = 0; - self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ - let height = if self.selection().scene() == Some(s) && self.editor().is_some() { - 8 - } else { - H_SCENE - }; - if y + height <= self.clips_size().h() as usize { - let data = (s, scene, y, y + height); - y += height; - Some(data) - } else { - None - } - }) - } -} - -pub trait HasSceneScroll: HasScenes { - fn scene_scroll (&self) -> usize; -} - -pub trait HasScene: AsRefOpt + AsMutOpt { - fn scene_mut (&mut self) -> Option<&mut Scene> { self.as_mut_opt() } - fn scene (&self) -> Option<&Scene> { self.as_ref_opt() } -} - -pub trait HasScenes: AsRef> + AsMut> { - fn scenes (&self) -> &Vec { self.as_ref() } - fn scenes_mut (&mut self) -> &mut Vec { self.as_mut() } - /// Generate the default name for a new scene - fn scene_default_name (&self) -> Arc { format!("s{:3>}", self.scenes().len() + 1).into() } - fn scene_longest_name (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) } - /// Add multiple scenes - fn scenes_add (&mut self, n: usize) -> Usually<()> where Self: HasTracks { - let scene_color_1 = ItemColor::random(); - let scene_color_2 = ItemColor::random(); - for i in 0..n { - let _ = self.scene_add(None, Some( - scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() - ))?; - } - Ok(()) - } - /// Add a scene - fn scene_add (&mut self, name: Option<&str>, color: Option) - -> Usually<(usize, &mut Scene)> where Self: HasTracks - { - let scene = Scene { - name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()), - clips: vec![None;self.tracks().len()], - color: color.unwrap_or_else(ItemTheme::random), - }; - self.scenes_mut().push(scene); - let index = self.scenes().len() - 1; - Ok((index, &mut self.scenes_mut()[index])) - } -} - -impl HasSceneScroll for Arrangement { - fn scene_scroll (&self) -> usize { self.scene_scroll } -} - -impl HasSceneScroll for App { - fn scene_scroll (&self) -> usize { self.project.scene_scroll() } -} - -impl ScenesView for Arrangement { - fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) } - fn w_side (&self) -> u16 { (self.size.w() as u16 * 2 / 10).max(20) } - fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) } -} - -pub type SceneWith<'a, T> = - (usize, &'a Scene, usize, usize, T); - -def_command!(SceneCommand: |scene: Scene| { - SetSize { size: usize } => { todo!() }, - SetZoom { size: usize } => { todo!() }, - SetName { name: Arc } => - swap_value(&mut scene.name, name, |name|Self::SetName{name}), - SetColor { color: ItemTheme } => - swap_value(&mut scene.color, color, |color|Self::SetColor{color}), -}); - -#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt()); -#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); -#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene()); -#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut()); -impl_has!(Vec: |self: Arrangement| self.scenes); -impl ClipsView for T {} -impl>+AsMut>> HasScenes for T {} -impl+AsMutOpt+Send+Sync> HasScene for T {} - -impl ScenesView for App { - fn w_mid (&self) -> u16 { - (self.size.w() as u16).saturating_sub(self.w_side()) - } - fn w_side (&self) -> u16 { - 20 - } - fn h_scenes (&self) -> u16 { - (self.size.h() as u16).saturating_sub(20) - } -} diff --git a/src/device/arrange/select.rs b/src/device/arrange/select.rs deleted file mode 100644 index ae748e5d..00000000 --- a/src/device/arrange/select.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::*; - -/// Represents the current user selection in the arranger -#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection { - #[default] - /// Nothing is selected - Nothing, - /// The whole mix is selected - Mix, - /// A MIDI input is selected. - Input(usize), - /// A MIDI output is selected. - Output(usize), - /// A scene is selected. - #[cfg(feature = "scene")] Scene(usize), - /// A track is selected. - #[cfg(feature = "track")] Track(usize), - /// A clip (track × scene) is selected. - #[cfg(feature = "track")] TrackClip { track: usize, scene: usize }, - /// A track's MIDI input connection is selected. - #[cfg(feature = "track")] TrackInput { track: usize, port: usize }, - /// A track's MIDI output connection is selected. - #[cfg(feature = "track")] TrackOutput { track: usize, port: usize }, - /// A track device slot is selected. - #[cfg(feature = "track")] TrackDevice { track: usize, device: usize }, -} - -impl Selection { - pub fn describe ( - &self, - #[cfg(feature = "track")] tracks: &[Track], - #[cfg(feature = "scene")] scenes: &[Scene], - ) -> Arc { - use Selection::*; - format!("{}", match self { - Mix => "Everything".to_string(), - #[cfg(feature = "scene")] Scene(s) => - scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()), - #[cfg(feature = "track")] Track(t) => - tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()), - TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) { - (Some(_), Some(s)) => match s.clip(*track) { - Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name), - None => format!("T{track} S{scene}: Empty") - }, - _ => format!("T{track} S{scene}: Empty"), - }, - _ => todo!() - }).into() - } - #[cfg(feature = "scene")] pub fn scene (&self) -> Option { - use Selection::*; - match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None } - } - #[cfg(feature = "scene")] pub fn select_scene (&self, scene_count: usize) -> Self { - use Selection::*; - match self { - Mix | Track(_) => Scene(0), - Scene(s) => Scene((s + 1) % scene_count), - TrackClip { scene, .. } => Track(*scene), - _ => todo!(), - } - } - #[cfg(feature = "scene")] pub fn select_scene_next (&self, len: usize) -> Self { - use Selection::*; - match self { - Mix => Scene(0), - Track(t) => TrackClip { track: *t, scene: 0 }, - Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix }, - TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) }, - _ => todo!() - } - } - #[cfg(feature = "scene")] pub fn select_scene_prev (&self) -> Self { - use Selection::*; - match self { - Mix | Scene(0) => Mix, - Scene(s) => Scene(s - 1), - Track(t) => Track(*t), - TrackClip { track, scene: 0 } => Track(*track), - TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 }, - _ => todo!() - } - } - #[cfg(feature = "track")] pub fn track (&self) -> Option { - use Selection::*; - if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self { - Some(*track) - } else { - None - } - } - #[cfg(feature = "track")] pub fn select_track (&self, track_count: usize) -> Self { - use Selection::*; - match self { - Mix => Track(0), - Scene(_) => Mix, - Track(t) => Track((t + 1) % track_count), - TrackClip { track, .. } => Track(*track), - _ => todo!(), - } - } - #[cfg(feature = "track")] pub fn select_track_next (&self, len: usize) -> Self { - use Selection::*; - match self { - Mix => Track(0), - Scene(s) => TrackClip { track: 0, scene: *s }, - Track(t) => if t + 1 < len { Track(t + 1) } else { Mix }, - TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) }, - _ => todo!() - } - } - #[cfg(feature = "track")] pub fn select_track_prev (&self) -> Self { - use Selection::*; - match self { - Mix => Mix, - Scene(s) => Scene(*s), - Track(0) => Mix, - Track(t) => Track(t - 1), - TrackClip { track: 0, scene } => Scene(*scene), - TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene }, - _ => todo!() - } - } -} - -impl +AsMut> HasSelection for T {} - -pub trait HasSelection: AsRef + AsMut { - fn selection (&self) -> &Selection { self.as_ref() } - fn selection_mut (&mut self) -> &mut Selection { self.as_mut() } - /// Get the active track - #[cfg(feature = "track")] - fn selected_track (&self) -> Option<&Track> where Self: HasTracks { - let index = self.selection().track()?; - self.tracks().get(index) - } - /// Get a mutable reference to the active track - #[cfg(feature = "track")] - fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks { - let index = self.selection().track()?; - self.tracks_mut().get_mut(index) - } - /// Get the active scene - #[cfg(feature = "scene")] - fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes { - let index = self.selection().scene()?; - self.scenes().get(index) - } - /// Get a mutable reference to the active scene - #[cfg(feature = "scene")] - fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes { - let index = self.selection().scene()?; - self.scenes_mut().get_mut(index) - } - /// Get the active clip - #[cfg(feature = "clip")] - fn selected_clip (&self) -> Option>> where Self: HasScenes + HasTracks { - self.selected_scene()?.clips.get(self.selection().track()?)?.clone() - } -} diff --git a/src/device/arrange/track.rs b/src/device/arrange/track.rs deleted file mode 100644 index c480f694..00000000 --- a/src/device/arrange/track.rs +++ /dev/null @@ -1,350 +0,0 @@ -use crate::*; - -/// A track consists of a sequencer and zero or more devices chained after it. -/// -/// ``` -/// let track: tek::Track = Default::default(); -/// ``` -#[derive(Debug, Default)] pub struct Track { - /// Name of track - pub name: Arc, - /// Identifying color of track - pub color: ItemTheme, - /// Preferred width of track column - pub width: usize, - /// MIDI sequencer state - pub sequencer: Sequencer, - /// Device chain - pub devices: Vec, -} - -impl Track { - /// Create a new track with only the default [Sequencer]. - pub fn new ( - name: &impl AsRef, - color: Option, - jack: &Jack<'static>, - clock: Option<&Clock>, - clip: Option<&Arc>>, - midi_from: &[Connect], - midi_to: &[Connect], - ) -> Usually { - Ok(Self { - name: name.as_ref().into(), - color: color.unwrap_or_default(), - sequencer: Sequencer::new( - format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to - )?, - ..Default::default() - }) - } - pub fn audio_ins (&self) -> &[AudioInput] { - self.devices.first().map(|x|x.audio_ins()).unwrap_or_default() - } - pub fn audio_outs (&self) -> &[AudioOutput] { - self.devices.last().map(|x|x.audio_outs()).unwrap_or_default() - } - fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } - fn _todo_usize_stub_ (&self) -> usize { todo!() } - fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } - fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } - - pub fn per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - tracks: impl Fn() -> U + Send + Sync + 'a, - callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - ) -> impl Draw + 'a { - view_track_per(tracks, callback) - } -} - -#[cfg(feature = "sampler")] -impl Track { - - /// Create a new track connecting the [Sequencer] to a [Sampler]. - pub fn new_with_sampler ( - name: &impl AsRef, - color: Option, - jack: &Jack<'static>, - clock: Option<&Clock>, - clip: Option<&Arc>>, - midi_from: &[Connect], - midi_to: &[Connect], - audio_from: &[&[Connect];2], - audio_to: &[&[Connect];2], - ) -> Usually { - let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?; - let client_name = jack.with_client(|c|c.name().to_string()); - let port_name = track.sequencer.midi_outs[0].port_name(); - let connect = [Connect::exact(format!("{client_name}:{}", port_name))]; - track.devices.push(Device::Sampler(Sampler::new( - jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to - )?)); - Ok(track) - } - - pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> { - for device in self.devices.iter() { - match device { - Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, - _ => {} - } - } - None - } - - pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> { - for device in self.devices.iter_mut() { - match device { - Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, - _ => {} - } - } - None - } -} - -impl HasWidth for Track { - const MIN_WIDTH: usize = 9; - fn width_inc (&mut self) { self.width += 1; } - fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } } -} - -pub trait HasTracks: AsRef> + AsMut> { - fn tracks (&self) -> &Vec { self.as_ref() } - fn tracks_mut (&mut self) -> &mut Vec { self.as_mut() } - /// Run audio callbacks for every track and every device - fn process_tracks (&mut self, client: &Client, scope: &ProcessScope) -> Control { - for track in self.tracks_mut().iter_mut() { - if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { - return Control::Quit - } - for device in track.devices.iter_mut() { - if Control::Quit == DeviceAudio(device).process(client, scope) { - return Control::Quit - } - } - } - Control::Continue - } - fn track_longest_name (&self) -> usize { - self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) - } - /// Stop all playing clips - fn tracks_stop_all (&mut self) { - for track in self.tracks_mut().iter_mut() { - track.sequencer.enqueue_next(None); - } - } - /// Stop all playing clips - fn tracks_launch (&mut self, clips: Option>>>>) { - if let Some(clips) = clips { - for (clip, track) in clips.iter().zip(self.tracks_mut()) { - track.sequencer.enqueue_next(clip.as_ref()); - } - } else { - for track in self.tracks_mut().iter_mut() { - track.sequencer.enqueue_next(None); - } - } - } - /// Spacing between tracks. - const TRACK_SPACING: usize = 0; -} - -pub trait HasTrack: AsRefOpt + AsMutOpt { - fn track (&self) -> Option<&Track> { self.as_ref_opt() } - fn track_mut (&mut self) -> Option<&mut Track> { self.as_mut_opt() } - - #[cfg(feature = "port")] - fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw + 'a { - view_midi_ins_status(theme, self.track()) - } - - #[cfg(feature = "port")] - fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw + '_ { - view_midi_outs_status(theme, self.track()) - } - - #[cfg(feature = "port")] - fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw { - view_audio_ins_status(theme, self.track()) - } - - #[cfg(feature = "port")] - fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw { - view_audio_outs_status(theme, self.track()) - } -} - -pub trait HasTrackScroll: HasTracks { - fn track_scroll (&self) -> usize; -} - -pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll { - /// Draw name of each track - fn view_track_names (&self, theme: ItemTheme) -> impl Draw { - view_track_names( - theme, self.tracks_with_sizes(), self.tracks().len(), self.scenes().len(), self.selection() - ) - } - /// Draw outputs per track - fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw { - view_track_outputs( - theme, self.tracks_with_sizes(), self.midi_outs().iter() - ) - } - /// Draw inputs per track - fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw { - let mut h = 0u16; - for track in self.tracks().iter() { - h = h.max(track.sequencer.midi_ins.len() as u16); - } - view_track_inputs(theme, self.tracks_with_sizes(), h) - } - /// Iterate over tracks with their corresponding sizes. - fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { - let _editor_width = self.editor().map(|e|e.size.w()); - let _active_track = self.selection().track(); - let mut x = 0; - let w = self.clips_size().w() as usize; - self.tracks().iter().enumerate().map_while(move |(index, track)|{ - let width = track.width.max(8); - if x + width < w { - let data = (index, track, x, x + width); - x += width + Self::TRACK_SPACING; - Some(data) - } else { - None - } - }) - } - -} - -impl HasTrackScroll for Arrangement { - fn track_scroll (&self) -> usize { self.track_scroll } -} - -def_command!(TrackCommand: |track: Track| { - Stop => { track.sequencer.enqueue_next(None); Ok(None) }, - SetRec { rec: Option } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }), - SetMon { mon: Option } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }), - SetMute { mute: Option } => todo!(), - SetSolo { solo: Option } => todo!(), - SetSize { size: usize } => todo!(), - SetZoom { zoom: usize } => todo!(), - SetName { name: Arc } => swap_value(&mut track.name, name, |name|Self::SetName { name }), - SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }), -}); - -impl>+AsMut>> HasTracks for T {} -impl+AsMutOpt+Send+Sync> HasTrack for T {} -impl TracksView for T {} -impl_has!(Vec: |self: Arrangement| self.tracks); -impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track()); -impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut()); -impl_as_ref!(Vec: |self: App| self.project.as_ref()); -impl_as_mut!(Vec: |self: App| self.project.as_mut()); -#[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt()); -#[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt()); - -impl Arrangement { - - pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw + '_ { - view_inputs( - self.tracks_with_sizes(), self.midi_ins().as_slice() - ) - } - - pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw { - view_outputs( - theme, self.tracks_with_sizes(), self.midi_outs(), self.outputs_height() - ) - } - - pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { - view_track_devices( - theme, self.tracks_with_sizes(), self.track(), self.devices_height() - ) - } - - fn devices_height (&self) -> u16 { - let mut h = 2; - for track in self.tracks().iter() { - h = h.max(track.devices.len() * 2); - } - h as u16 - } - - fn outputs_height (&self) -> u16 { - let mut h = 1; - for output in self.midi_outs().iter() { - h += 1 + output.connections.len(); - } - h as u16 - } - - /// Add multiple tracks - pub fn tracks_add ( - &mut self, count: usize, width: Option, mins: &[Connect], mouts: &[Connect], - ) -> Usually<()> { - let track_color_1 = ItemColor::random(); - let track_color_2 = ItemColor::random(); - for i in 0..count { - let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); - let track = self.track_add(None, Some(color), mins, mouts)?.1; - if let Some(width) = width { - track.width = width; - } - } - Ok(()) - } - - /// Add a track - pub fn track_add ( - &mut self, - name: Option<&str>, - color: Option, - mins: &[Connect], - mouts: &[Connect], - ) -> Usually<(usize, &mut Track)> { - let name: Arc = name.map_or_else( - ||format!("trk{:02}", self.track_last).into(), - |x|x.to_string().into() - ); - self.track_last += 1; - let track = Track { - width: (name.len() + 2).max(12), - color: color.unwrap_or_else(ItemTheme::random), - sequencer: Sequencer::new( - &format!("{name}"), - self.jack(), - Some(self.clock()), - None, - mins, - mouts - )?, - name, - ..Default::default() - }; - self.tracks_mut().push(track); - let len = self.tracks().len(); - let index = len - 1; - for scene in self.scenes_mut().iter_mut() { - while scene.clips.len() < len { - scene.clips.push(None); - } - } - Ok((index, &mut self.tracks_mut()[index])) - } -} - -impl HasTrackScroll for App { - fn track_scroll (&self) -> usize { - self.project.track_scroll() - } -} - -pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { - track.width as u16 -} diff --git a/src/device/dialog.rs b/src/device/dialog.rs index 25aa4fc5..b007ffa2 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -14,38 +14,56 @@ use crate::{*, browse::*, device::*, menu::*}; Browse(BrowseTarget, Arc), Options, } -namespace!(App: Dialog { symbol = |app| { - ":dialog/none" => Dialog::None, - ":dialog/options" => Dialog::Options, - ":dialog/device" => Dialog::Device(0), - ":dialog/device/prev" => Dialog::Device(0), - ":dialog/device/next" => Dialog::Device(0), - ":dialog/help" => Dialog::Help(0), - ":dialog/save" => Dialog::Browse(BrowseTarget::SaveProject, - Browse::new(None).unwrap().into()), - ":dialog/load" => Dialog::Browse(BrowseTarget::LoadProject, - Browse::new(None).unwrap().into()), - ":dialog/import/clip" => Dialog::Browse(BrowseTarget::ImportClip(Default::default()), - Browse::new(None).unwrap().into()), - ":dialog/export/clip" => Dialog::Browse(BrowseTarget::ExportClip(Default::default()), - Browse::new(None).unwrap().into()), - ":dialog/import/sample" => Dialog::Browse(BrowseTarget::ImportSample(Default::default()), - Browse::new(None).unwrap().into()), - ":dialog/export/sample" => Dialog::Browse(BrowseTarget::ExportSample(Default::default()), - Browse::new(None).unwrap().into()), -}; }); + +impl<'n> Namespace<'n, Dialog> for App { + fn namespace (&self, src: impl Language) -> Perhaps { + src.word()?.map(|word|Ok(match word { + ":dialog/none" => Dialog::None, + ":dialog/options" => Dialog::Options, + ":dialog/device" => Dialog::Device(0), + ":dialog/device/prev" => Dialog::Device(0), + ":dialog/device/next" => Dialog::Device(0), + ":dialog/help" => Dialog::Help(0), + ":dialog/save" => Dialog::Browse( + BrowseTarget::SaveProject, + Browse::new(None).unwrap().into()), + ":dialog/load" => Dialog::Browse( + BrowseTarget::LoadProject, + Browse::new(None).unwrap().into()), + ":dialog/import/clip" => Dialog::Browse( + BrowseTarget::ImportClip(Default::default()), + Browse::new(None).unwrap().into()), + ":dialog/export/clip" => Dialog::Browse( + BrowseTarget::ExportClip(Default::default()), + Browse::new(None).unwrap().into()), + ":dialog/import/sample" => Dialog::Browse( + BrowseTarget::ImportSample(Default::default()), + Browse::new(None).unwrap().into()), + ":dialog/export/sample" => Dialog::Browse( + BrowseTarget::ExportSample(Default::default()), + Browse::new(None).unwrap().into()), + _ => return Err( + format!("not a dialog: {}", src.src()?.unwrap_or("")).into() + ) + })).transpose() + } +} + impl Dialog { /// ``` /// let _ = tek::Dialog::welcome(); /// ``` pub fn welcome () -> Self { Self::Menu(1, MenuItems([ - MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))), - MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({ + MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))), + MenuItem("New session".into(), Arc::new(Box::new(|app|Ok({ app.dialog = Dialog::None; app.mode = app.config.modes.get(":arranger").unwrap(); })))), - MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))), + MenuItem("Load session".into(), Arc::new(Box::new(|_|Ok(())))), + MenuItem("Exit".into(), Arc::new(Box::new(|_|Ok({ + () + })))), ].into())) } /// FIXME: generalize diff --git a/src/device/midi.rs b/src/device/midi.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/device/port.rs b/src/device/port.rs deleted file mode 100644 index eb8cf92a..00000000 --- a/src/device/port.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::*; -use ConnectName::*; -use ConnectScope::*; -use ConnectStatus::*; - -mod audio; pub use self::audio::*; -mod midi; pub use self::midi::*; -mod connect; pub use self::connect::*; -mod register; pub use self::register::*; - -pub trait JackPort: HasJack<'static> { - type Port: PortSpec + Default; - type Pair: PortSpec + Default; - - fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) - -> Usually where Self: Sized; - - fn register (jack: &Jack<'static>, name: &impl AsRef) -> Usually> { - jack.with_client(|c|c.register_port::(name.as_ref(), Default::default())) - .map_err(|e|e.into()) - } - - fn port_name (&self) -> &Arc; - - fn connections (&self) -> &[Connect]; - - fn port (&self) -> &Port; - - fn port_mut (&mut self) -> &mut Port; - - fn into_port (self) -> Port where Self: Sized; - - fn close (self) -> Usually<()> where Self: Sized { - let jack = self.jack().clone(); - Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?) - } - - fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { - self.with_client(|c|c.ports(re_name, re_type, flags)) - } - - fn port_by_id (&self, id: u32) -> Option> { - self.with_client(|c|c.port_by_id(id)) - } - - fn port_by_name (&self, name: impl AsRef) -> Option> { - self.with_client(|c|c.port_by_name(name.as_ref())) - } - - fn connect_to_matching <'k> (&'k self) -> Usually<()> { - for connect in self.connections().iter() { - match &connect.name { - Some(Exact(name)) => { - *connect.status.write().unwrap() = self.connect_exact(name)?; - }, - Some(RegExp(re)) => { - *connect.status.write().unwrap() = self.connect_regexp(re, connect.scope)?; - }, - _ => {}, - }; - } - Ok(()) - } - - fn connect_exact <'k> (&'k self, name: &str) -> - Usually, Arc, ConnectStatus)>> - { - self.with_client(move|c|{ - let mut status = vec![]; - for port in c.ports(None, None, PortFlags::empty()).iter() { - if port.as_str() == &*name { - if let Some(port) = c.port_by_name(port.as_str()) { - let port_status = self.connect_to_unowned(&port)?; - let name = port.name()?.into(); - status.push((port, name, port_status)); - if port_status == Connected { - break - } - } - } - } - Ok(status) - }) - } - - fn connect_regexp <'k> ( - &'k self, re: &str, scope: Option - ) -> Usually, Arc, ConnectStatus)>> { - self.with_client(move|c|{ - let mut status = vec![]; - let ports = c.ports(Some(&re), None, PortFlags::empty()); - for port in ports.iter() { - if let Some(port) = c.port_by_name(port.as_str()) { - let port_status = self.connect_to_unowned(&port)?; - let name = port.name()?.into(); - status.push((port, name, port_status)); - if port_status == Connected && scope == Some(One) { - break - } - } - } - Ok(status) - }) - } - - /** Connect to a matching port by name. */ - fn connect_to_name (&self, name: impl AsRef) -> Usually { - self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) { - self.connect_to_unowned(port) - } else { - Ok(Missing) - }) - } - - /** Connect to a matching port by reference. */ - fn connect_to_unowned (&self, port: &Port) -> Usually { - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { - Connected - } else if let Ok(_) = c.connect_ports(port, self.port()) { - Connected - } else { - Mismatch - })) - } - - /** Connect to an owned matching port by reference. */ - fn connect_to_owned (&self, port: &Port) -> Usually { - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { - Connected - } else if let Ok(_) = c.connect_ports(port, self.port()) { - Connected - } else { - Mismatch - })) - } -} diff --git a/src/device/port/audio.rs b/src/device/port/audio.rs deleted file mode 100644 index bb1db676..00000000 --- a/src/device/port/audio.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::*; - -def_command!(AudioInputCommand: |port: AudioInput| { - Close => todo!(), - Connect { audio_out: Arc } => todo!(), -}); - -def_command!(AudioOutputCommand: |port: AudioOutput| { - Close => todo!(), - Connect { audio_in: Arc } => todo!(), -}); - -impl HasJack<'static> for AudioInput { fn jack (&self) -> &Jack<'static> { &self.jack } } -impl HasJack<'static> for AudioOutput { fn jack (&self) -> &Jack<'static> { &self.jack } } - -/// Audio input port. -#[derive(Debug)] pub struct AudioInput { - /// Handle to JACK client, for receiving reconnect events. - pub jack: Jack<'static>, - /// Port name - pub name: Arc, - /// Port handle. - pub port: Port, - /// List of ports to connect to. - pub connections: Vec, -} - -/// Audio output port. -#[derive(Debug)] pub struct AudioOutput { - /// Handle to JACK client, for receiving reconnect events. - pub jack: Jack<'static>, - /// Port name - pub name: Arc, - /// Port handle. - pub port: Port, - /// List of ports to connect to. - pub connections: Vec, -} - - -impl JackPort for AudioInput { - type Port = AudioIn; - type Pair = AudioOut; - fn port_name (&self) -> &Arc { - &self.name - } - fn port (&self) -> &Port { - &self.port - } - fn port_mut (&mut self) -> &mut Port { - &mut self.port - } - fn into_port (self) -> Port { - self.port - } - fn connections (&self) -> &[Connect] { - self.connections.as_slice() - } - fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) - -> Usually where Self: Sized - { - let port = Self { - port: Self::register(jack, name)?, - jack: jack.clone(), - name: name.as_ref().into(), - connections: connect.to_vec() - }; - port.connect_to_matching()?; - Ok(port) - } -} - -impl JackPort for AudioOutput { - type Port = AudioOut; - type Pair = AudioIn; - fn port_name (&self) -> &Arc { - &self.name - } - fn port (&self) -> &Port { - &self.port - } - fn port_mut (&mut self) -> &mut Port { - &mut self.port - } - fn into_port (self) -> Port { - self.port - } - fn connections (&self) -> &[Connect] { - self.connections.as_slice() - } - fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) - -> Usually where Self: Sized - { - let port = Self { - port: Self::register(jack, name)?, - jack: jack.clone(), - name: name.as_ref().into(), - connections: connect.to_vec() - }; - port.connect_to_matching()?; - Ok(port) - } -} diff --git a/src/device/port/connect.rs b/src/device/port/connect.rs deleted file mode 100644 index 8571fa2e..00000000 --- a/src/device/port/connect.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::*; -use ConnectName::*; -use ConnectScope::*; -use ConnectStatus::*; - -#[derive(Clone, Debug, PartialEq)] pub enum ConnectName { - /** Exact match */ - Exact(Arc), - /** Match regular expression */ - RegExp(Arc), -} - -#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope { - One, - All -} - -#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus { - Missing, - Disconnected, - Connected, - Mismatch, -} - -/// Port connection manager. -/// -/// ``` -/// let connect = tek::Connect::default(); -/// ``` -#[derive(Clone, Debug, Default)] pub struct Connect { - pub name: Option, - pub scope: Option, - pub status: Arc, Arc, ConnectStatus)>>>, - pub info: Arc, -} - -impl Connect { - pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) - -> Vec - { - let mut connections = vec![]; - for port in exact.iter() { connections.push(Self::exact(port)) } - for port in re.iter() { connections.push(Self::regexp(port)) } - for port in re_all.iter() { connections.push(Self::regexp_all(port)) } - connections - } - /// Connect to this exact port - pub fn exact (name: impl AsRef) -> Self { - let info = format!("=:{}", name.as_ref()).into(); - let name = Some(Exact(name.as_ref().into())); - Self { name, scope: Some(One), status: Arc::new(RwLock::new(vec![])), info } - } - pub fn regexp (name: impl AsRef) -> Self { - let info = format!("~:{}", name.as_ref()).into(); - let name = Some(RegExp(name.as_ref().into())); - Self { name, scope: Some(One), status: Arc::new(RwLock::new(vec![])), info } - } - pub fn regexp_all (name: impl AsRef) -> Self { - let info = format!("+:{}", name.as_ref()).into(); - let name = Some(RegExp(name.as_ref().into())); - Self { name, scope: Some(All), status: Arc::new(RwLock::new(vec![])), info } - } - pub fn info (&self) -> Arc { - format!(" ({}) {} {}", { - let status = self.status.read().unwrap(); - let mut ok = 0; - for (_, _, state) in status.iter() { - if *state == Connected { - ok += 1 - } - } - format!("{ok}/{}", status.len()) - }, match self.scope { - None => "x", - Some(One) => " ", - Some(All) => "*", - }, match &self.name { - None => format!("x"), - Some(Exact(name)) => format!("= {name}"), - Some(RegExp(name)) => format!("~ {name}"), - }).into() - } -} diff --git a/src/device/port/midi.rs b/src/device/port/midi.rs deleted file mode 100644 index eb9160cc..00000000 --- a/src/device/port/midi.rs +++ /dev/null @@ -1,256 +0,0 @@ -use crate::*; - -impl HasJack<'static> for MidiInput { fn jack (&self) -> &Jack<'static> { &self.jack } } - -impl HasJack<'static> for MidiOutput { fn jack (&self) -> &Jack<'static> { &self.jack } } - -/// MIDI input port. -#[derive(Debug)] pub struct MidiInput { - /// Handle to JACK client, for receiving reconnect events. - pub jack: Jack<'static>, - /// Port name - pub name: Arc, - /// Port handle. - pub port: Port, - /// List of currently held notes. - pub held: Arc>, - /// List of ports to connect to. - pub connections: Vec, -} - -/// MIDI output port. -#[derive(Debug)] pub struct MidiOutput { - /// Handle to JACK client, for receiving reconnect events. - pub jack: Jack<'static>, - /// Port name - pub name: Arc, - /// Port handle. - pub port: Port, - /// List of ports to connect to. - pub connections: Vec, - /// List of currently held notes. - pub held: Arc>, - /// Buffer - pub note_buffer: Vec, - /// Buffer - pub output_buffer: Vec>>, -} - -def_command!(MidiInputCommand: |port: MidiInput| { - Close => todo!(), - Connect { midi_out: Arc } => todo!(), -}); - -def_command!(MidiOutputCommand: |port: MidiOutput| { - Close => todo!(), - Connect { midi_in: Arc } => todo!(), -}); -/// Trait for thing that may receive MIDI. -pub trait HasMidiIns { - fn midi_ins (&self) -> &Vec; - fn midi_ins_mut (&mut self) -> &mut Vec; - /// Collect MIDI input from app ports (TODO preallocate large buffers) - fn midi_input_collect <'a> (&'a self, scope: &'a ProcessScope) -> CollectedMidiInput<'a> { - self.midi_ins().iter() - .map(|port|port.port().iter(scope) - .map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes))) - .collect::>()) - .collect::>() - } - fn midi_ins_with_sizes <'a> (&'a self) -> - impl Iterator, &'a [Connect], usize, usize)> + Send + Sync + 'a - { - let mut y = 0; - self.midi_ins().iter().enumerate().map(move|(i, input)|{ - let height = 1 + input.connections().len(); - let data = (i, input.port_name(), input.connections(), y, y + height); - y += height; - data - }) - } -} -/// Trait for thing that may output MIDI. -pub trait HasMidiOuts { - fn midi_outs (&self) -> &Vec; - fn midi_outs_mut (&mut self) -> &mut Vec; - fn midi_outs_with_sizes <'a> (&'a self) -> - impl Iterator, &'a [Connect], usize, usize)> + Send + Sync + 'a - { - let mut y = 0; - self.midi_outs().iter().enumerate().map(move|(i, output)|{ - let height = 1 + output.connections().len(); - let data = (i, output.port_name(), output.connections(), y, y + height); - y += height; - data - }) - } - fn midi_outs_emit (&mut self, scope: &ProcessScope) { - for port in self.midi_outs_mut().iter_mut() { - port.buffer_emit(scope) - } - } -} - -impl JackPort for MidiInput { - type Port = MidiIn; - type Pair = MidiOut; - fn port_name (&self) -> &Arc { - &self.name - } - fn port (&self) -> &Port { - &self.port - } - fn port_mut (&mut self) -> &mut Port { - &mut self.port - } - fn into_port (self) -> Port { - self.port - } - fn connections (&self) -> &[Connect] { - self.connections.as_slice() - } - fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) - -> Usually where Self: Sized - { - let port = Self { - port: Self::register(jack, name)?, - jack: jack.clone(), - name: name.as_ref().into(), - connections: connect.to_vec(), - held: Arc::new(RwLock::new([false;128])) - }; - port.connect_to_matching()?; - Ok(port) - } -} - -impl JackPort for MidiOutput { - type Port = MidiOut; - type Pair = MidiIn; - fn port_name (&self) -> &Arc { - &self.name - } - fn port (&self) -> &Port { - &self.port - } - fn port_mut (&mut self) -> &mut Port { - &mut self.port - } - fn into_port (self) -> Port { - self.port - } - fn connections (&self) -> &[Connect] { - self.connections.as_slice() - } - fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) - -> Usually where Self: Sized - { - let port = Self::register(jack, name)?; - let jack = jack.clone(); - let name = name.as_ref().into(); - let connections = connect.to_vec(); - let port = Self { - jack, - port, - name, - connections, - held: Arc::new([false;128].into()), - note_buffer: vec![0;8], - output_buffer: vec![vec![];65536], - }; - port.connect_to_matching()?; - Ok(port) - } -} - -impl View for MidiInput { - fn view (&self) -> impl Draw { - "TODO: MIDI IN" - } -} - -impl View for MidiOutput { - fn view (&self) -> impl Draw { - "TODO: MIDI OUT" - } -} - -impl MidiOutput { - /// Clear the section of the output buffer that we will be using, - /// emitting "all notes off" at start of buffer if requested. - pub fn buffer_clear (&mut self, scope: &ProcessScope, reset: bool) { - let n_frames = (scope.n_frames() as usize).min(self.output_buffer.len()); - for frame in &mut self.output_buffer[0..n_frames] { - frame.clear(); - } - if reset { - all_notes_off(&mut self.output_buffer); - } - } - /// Write a note to the output buffer - pub fn buffer_write <'a> ( - &'a mut self, - sample: usize, - event: LiveEvent, - ) { - self.note_buffer.fill(0); - event.write(&mut self.note_buffer).expect("failed to serialize MIDI event"); - self.output_buffer[sample].push(self.note_buffer.clone()); - // Update the list of currently held notes. - if let LiveEvent::Midi { ref message, .. } = event { - update_keys(&mut*self.held.write().unwrap(), message); - } - } - /// Write a chunk of MIDI data from the output buffer to the output port. - pub fn buffer_emit (&mut self, scope: &ProcessScope) { - let samples = scope.n_frames() as usize; - let mut writer = self.port.writer(scope); - for (time, events) in self.output_buffer.iter().enumerate().take(samples) { - for bytes in events.iter() { - writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{ - panic!("Failed to write MIDI data: {bytes:?}"); - }); - } - } - } -} -impl MidiInput { - pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator, &'a [u8])> { - parse_midi_input(self.port().iter(scope)) - } -} -impl> + AsMut>> HasMidiIns for T { - fn midi_ins (&self) -> &Vec { self.as_ref() } - fn midi_ins_mut (&mut self) -> &mut Vec { self.as_mut() } -} -impl> + AsMut>> HasMidiOuts for T { - fn midi_outs (&self) -> &Vec { self.as_ref() } - fn midi_outs_mut (&mut self) -> &mut Vec { self.as_mut() } -} -impl> AddMidiIn for T { - fn midi_in_add (&mut self) -> Usually<()> { - let index = self.midi_ins().len(); - let port = MidiInput::new(self.jack(), &format!("M/{index}"), &[])?; - self.midi_ins_mut().push(port); - Ok(()) - } -} -/// Trail for thing that may gain new MIDI ports. -impl> AddMidiOut for T { - fn midi_out_add (&mut self) -> Usually<()> { - let index = self.midi_outs().len(); - let port = MidiOutput::new(self.jack(), &format!("{index}/M"), &[])?; - self.midi_outs_mut().push(port); - Ok(()) - } -} - -/// May create new MIDI input ports. -pub trait AddMidiIn { - fn midi_in_add (&mut self) -> Usually<()>; -} - -/// May create new MIDI output ports. -pub trait AddMidiOut { - fn midi_out_add (&mut self) -> Usually<()>; -} diff --git a/src/device/port/register.rs b/src/device/port/register.rs deleted file mode 100644 index 0702a21a..00000000 --- a/src/device/port/register.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::*; - -pub trait RegisterPorts: HasJack<'static> { - /// Register a MIDI input port. - fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; - /// Register a MIDI output port. - fn midi_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; - /// Register an audio input port. - fn audio_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; - /// Register an audio output port. - fn audio_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; -} - -impl> RegisterPorts for J { - fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { - MidiInput::new(self.jack(), name, connect) - } - fn midi_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { - MidiOutput::new(self.jack(), name, connect) - } - fn audio_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { - AudioInput::new(self.jack(), name, connect) - } - fn audio_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { - AudioOutput::new(self.jack(), name, connect) - } -} diff --git a/src/device/sequence.rs b/src/device/sequence.rs index 3e280965..8088a404 100644 --- a/src/device/sequence.rs +++ b/src/device/sequence.rs @@ -1,5 +1,6 @@ use crate::{*, clock::*, device::*}; + impl +AsMut> HasSequencer for T {} /// A MIDI sequence. @@ -229,7 +230,6 @@ pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip { pub type MidiData = Vec>; pub type ClipPool = Vec>>; -pub type CollectedMidiInput<'a> = Vec, MidiError>)>>; pub trait HasClips { fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; @@ -592,35 +592,6 @@ pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) } -/// Return boxed iterator of MIDI events -pub fn parse_midi_input <'a> (input: ::tengri::jack::MidiIter<'a>) - -> Box, &'a [u8])> + 'a> -{ - Box::new(input.map(|::tengri::jack::RawMidi { time, bytes }|( - time as usize, - LiveEvent::parse(bytes).unwrap(), - bytes - ))) -} - -/// Add "all notes off" to the start of a buffer. -pub fn all_notes_off (output: &mut [Vec>]) { - let mut buf = vec![]; - let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() }; - let evt = LiveEvent::Midi { channel: 0.into(), message: msg }; - evt.write(&mut buf).unwrap(); - output[0].push(buf); -} - -/// Update notes_in array -pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { - match message { - MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; } - MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; }, - _ => {} - } -} - /// Returns the next shorter length pub fn note_duration_prev (pulses: usize) -> usize { for (length, _) in NOTE_DURATIONS.iter().rev() { if *length < pulses { return *length } } diff --git a/src/tek.edn b/src/tek.edn index 9020ef91..d14123c0 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -1,3 +1,76 @@ +(view :logo (bsp/s (exact/y 1 (text ~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~)) + (bsp/s (exact/y 1 (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ v0.3.0 ~~)) + (bsp/s (exact/y 1 (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~)) (text dig?))))) + +(view :browse (bsp/s (padding 3 1 :browse-title) (enclose (fg (g 96)) browser))) + +(mode :transport (name Transport) (info JACK transport controller.) (keys :clock :global) + :transport) + +(mode :menu (name Menu) (info Mode selector.) (keys :axis/y :confirm) + (view (bg (g 0) + (bsp/s (max/y 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/h 6 (bg (g 70) :logo) :dialog/menu)))))))))) + +(mode :sequencer (name Sequencer) (info MIDI sequencer.) + (keys :editor :clock :global) + (mode browse (keys :browse)) + (mode rename (keys :pool/rename)) + (mode length (keys :pool/length)) + (bsp/s (fixed/y 1 :transport) + (bsp/n (fixed/y 1 :status) + (fill (bsp/a (fill/xy (align/e :pool)) :editor))))) + +(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)) + (mode rename (keys :pool-rename)) + (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/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/sequence (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) (bsp/e :samples/keys :editor))) + +(mode :arranger (name Arranger) (info Launch grid.) + (keys (see :clock :color :launch :scenes :tracks :global) + (@tab project/edit) (@shift/I project/input/add) (@shift/O project/output/add) + (@shift/D dialog/show :dialog/device)) + (mode :editor (keys :editor)) + (mode :dialog (keys :dialog)) + (mode :message (keys :message)) + (mode :add-device (keys :add-device)) + (mode :browse (keys :browse)) + (mode :rename (keys :input)) + (mode :length (keys :rename)) + (mode :clip (keys :clip)) + (mode :track (keys :track)) + (mode :scene (keys :scene)) + (mode :mix (keys :mix)) + (bsp/n :status (bsp/w :meters/output (bsp/e :meters/input (bsp/n :tracks/inputs + (bsp/s :tracks/outputs (bsp/s :tracks/names (bsp/s :tracks/devices + (fill (either :mode/editor (bsp/e :scenes/names :editor) :scenes)))))))))) + (keys :back (@escape back)) (keys :confirm (@enter confirm)) (keys :axis/x (@left x/dec) (@right x/inc)) @@ -10,113 +83,67 @@ (keys :axis/i2 (@lt i2/dec) (@gt z2/inc)) (keys :axis/w (@openbracket w/dec) (@closebracket w/inc)) (keys :axis/w2 (@openbrace w2/dec) (@closebrace w2/inc)) - -(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)))))))) - -(view :ports/out (fill/x (fixed/y 3 (bsp/a - (fill/x (align/w (text L-AUDIO-OUT))) - (bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R)))))))) -(view :ports/in (fill/x (fixed/y 3 (bsp/a - (fill/x (align/w (text L-AUDIO-IN))) - (bsp/a (text MIDI-IN) (fill/x (align/e (text AUDIO-IN-R)))))))) -(mode :transport (name Transport) (info JACK transport controller.) - (keys :clock :global) - :transport) -(mode :sequencer (name Sequencer) (info MIDI sequencer.) - (mode browse (keys :browse)) - (mode rename (keys :pool/rename)) - (mode length (keys :pool/length)) - (keys :editor :clock :global) - (bsp/s (fixed/y 1 :transport) - (bsp/n (fixed/y 1 :status) - (fill (bsp/a (fill/xy (align/e :pool)) :editor))))) -(keys :editor (see :axis/i :axis/i2 :axis/y :page :editor/view :editor/add :editor/del)) -(keys :editor/view (see :axis/x :axis/x2 :axis/z :axis/z2) (@z toggle :lock)) -(keys :editor/add (@a editor/append :true) (@enter editor/append :false)) -(keys :editor/del (@del editor/delete/note) (@shift/del editor/delete/note)) -(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)))) -(keys :sampler/record - (@r sampler/record/toggle :sample/selected) - (@shift/R sampler/record/back)) -(keys :sampler/play - (@p sampler/play/sample :sample/selected) - (@P sampler/stop/sample :sample/selected)) -(keys :sampler/import-export - (@shift/f6 dialog :dialog/export/sample) - (@shift/f9 dialog :dialog/import/sample)) -(keys :sampler/directions - (@up sampler/select :sample/above) - (@down sampler/select :sample/below) - (@left sampler/select :sample/to/left) - (@right sampler/select :sample/to/right)) -(mode :groovebox (name Groovebox) (info Sequencer with sampler.) - (mode browse (keys :browse)) - (mode rename (keys :pool-rename)) - (mode length (keys :pool-length)) - (keys :clock :editor :sampler :global) - (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/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/sequence (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) (bsp/e :samples/keys :editor))) -(mode :arranger (name Arranger) (info Launch grid.) - (mode :editor (keys :editor)) - (mode :dialog (keys :dialog)) - (mode :message (keys :message)) - (mode :add-device (keys :add-device)) - (mode :browse (keys :browse)) - (mode :rename (keys :input)) - (mode :length (keys :rename)) - (mode :clip (keys :clip)) - (mode :track (keys :track)) - (mode :scene (keys :scene)) - (mode :mix (keys :mix)) - (keys (see :clock :color :launch :scenes :tracks :global) - (@tab project/edit) - (@shift/I project/input/add) (@shift/O project/output/add) - (@shift/D dialog/show :dialog/device)) - (bsp/n :status (bsp/w :meters/output (bsp/e :meters/input (bsp/n :tracks/inputs - (bsp/s :tracks/outputs (bsp/s :tracks/names (bsp/s :tracks/devices - (fill (either :mode/editor (bsp/e :scenes/names :editor) :scenes)))))))))) -(keys :tracks (@t select :select/track) (@shift/T project/track/add) - (@left select :select/track/dec) (@right select :select/track/inc)) -(keys :track (see :color :launch :axis/z :axis/z2 :delete) - (@r toggle :rec) (@m toggle :mon) (@p toggle :play) (@P toggle :solo)) -(keys :scenes (@s select :select/scene) (@shift/S project/scene/add) - (@up select :select/scene/dec) (@down select :select/scene/inc)) -(keys :scene (see :color :launch :axis/z :axis/z2 :delete)) - (keys :focus) -(keys :help (@f1 dialog :help)) -(keys :page (@pgup page/up) (@pgdn page/down)) -(keys :delete (@delete delete) (@backspace delete/back)) -(keys :input (see :axis/x :delete) (:char input)) -(keys :list (see :axis/y :confirm)) -(keys :length (see :axis/x :axis/y :confirm)) -(keys :browse (see :list :input :focus)) -(keys :history (@u undo 1) (@r redo 1)) -(keys :saveload (@f6 dialog :save) (@f9 dialog :load)) -(keys :color (@c color)) -(keys :launch (@q launch)) -(keys :clock (@space clock/toggle 0) (@shift/space clock/toggle 0)) -(keys :global (see :history :saveload) (@f8 dialog :options) (@f10 dialog :quit)) -(keys :clip (see :color :launch :axis/z :axis/z2 :delete) (@l toggle :loop)) -(keys :sequencer (see :color :launch) (@shift/I input/add) (@shift/O output/add)) -(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete) - (@n rename/begin) (@t length/begin) (@m import/begin) (@x export/begin) - (@shift/A clip/add :after :new/clip) (@shift/D clip/add :after :cloned/clip)) -(view :browse (bsp/s - (padding 3 1 :browse-title) - (enclose (fg (g 96)) browser))) +(keys :editor (see :axis/i :axis/i2 :axis/y :page :editor/view :editor/add :editor/del)) +(keys :editor/view (see :axis/x :axis/x2 :axis/z :axis/z2) + (@z toggle :lock)) +(keys :editor/add (@a editor/append :true) + (@enter editor/append :false)) +(keys :editor/del (@del editor/delete/note) + (@shift/del editor/delete/note)) +(keys :sampler/record (@r sampler/record/toggle :sample/selected) + (@shift/R sampler/record/back)) +(keys :sampler/play (@p sampler/play/sample :sample/selected) + (@P sampler/stop/sample :sample/selected)) +(keys :sampler/import-export (@shift/f6 dialog :dialog/export/sample) + (@shift/f9 dialog :dialog/import/sample)) +(keys :sampler/directions (@up sampler/select :sample/above) + (@down sampler/select :sample/below) + (@left sampler/select :sample/to/left) + (@right sampler/select :sample/to/right)) +(keys :tracks (@t select :select/track) + (@shift/T project/track/add) + (@left select :select/track/dec) + (@right select :select/track/inc)) +(keys :track (see :color :launch :axis/z :axis/z2 :delete) + (@r toggle :rec) + (@m toggle :mon) + (@p toggle :play) + (@P toggle :solo)) +(keys :scenes (@s select :select/scene) + (@shift/S project/scene/add) + (@up select :select/scene/dec) + (@down select :select/scene/inc)) +(keys :scene (see :color :launch :axis/z :axis/z2 :delete)) +(keys :help (@f1 dialog :help)) +(keys :page (@pgup page/up) + (@pgdn page/down)) +(keys :delete (@delete delete) + (@backspace delete/back)) +(keys :input (see :axis/x :delete) (:char input)) +(keys :list (see :axis/y :confirm)) +(keys :length (see :axis/x :axis/y :confirm)) +(keys :browse (see :list :input :focus)) +(keys :history (@u undo 1) + (@r redo 1)) +(keys :saveload (@f6 dialog :save) + (@f9 dialog :load)) +(keys :color (@c color)) +(keys :launch (@q launch)) +(keys :clock (@space clock/toggle 0) + (@shift/space clock/toggle 0)) +(keys :global (see :history :saveload) + (@f8 dialog :options) + (@f10 dialog :quit)) +(keys :clip (see :color :launch :axis/z :axis/z2 :delete) + (@l toggle :loop)) +(keys :sequencer (see :color :launch) + (@shift/I input/add) + (@shift/O output/add)) +(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete) + (@n rename/begin) + (@t length/begin) + (@m import/begin) + (@x export/begin) + (@shift/A clip/add :after :new/clip) + (@shift/D clip/add :after :cloned/clip)) diff --git a/src/tek.rs b/src/tek.rs index 26391210..1f291ac9 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -6,33 +6,295 @@ trait_alias, type_changing_struct_update )] - pub extern crate atomic_float; pub extern crate xdg; -pub extern crate midly; pub extern crate tengri; - /// Command-line entrypoint. #[cfg(feature = "cli")] pub fn main () -> Usually<()> { - tui_setup_panic(); - Config::watch(|config|{ - tui_run_main(Jack::new_run("tek", move|jack|{ - let clock = Clock::new(&jack, Some(51.))?; - let project = Arrangement::new(&jack, None, clock, vec![], vec![], vec![], vec![]); - let app = App::new(&jack, project, config, ":menu"); - Ok(app) - })?) - }).map(|_|()) + tengri::Tui::setup_panic(); + let name = "tek"; + let mode = ":menu"; + let title = "untitled!"; + let bpm = 74.; + Config::watch(|config|tengri::Tui::run_main(Jack::new_run(name, move|jack|{ + let clock = Clock::new(&jack, Some(bpm))?; + let tracks = []; + let scenes = []; + let project = Arrangement::new( + &jack, title.into(), clock, + tracks.into_iter(), + scenes.into_iter(), + Connect::midi_ins(&jack, &"M", &[], None)?.into_iter(), + Connect::midi_outs(&jack, &"M", &[], None)?.into_iter(), + [].into_iter() + .chain(Connect::audio_ins(&jack, &"L", &[], None)?.into_iter()) + .chain(Connect::audio_ins(&jack, &"R", &[], None)?.into_iter()), + [].into_iter() + .chain(Connect::audio_outs(&jack, &"L", &[], None)?.into_iter()) + .chain(Connect::audio_outs(&jack, &"R", &[], None)?.into_iter()), + ); + Ok(App::new(&jack, project, config, mode)) + })?)).map(|_|()) } - -pub mod deps; pub use self::deps::*; -pub mod device; pub use self::device::*; -pub mod app; pub use self::app::*; - - +pub mod deps; +pub(crate) use self::deps::*; +pub mod device; +pub use self::device::*; +pub mod app; +pub use self::app::*; /// CLI banner. pub(crate) const HEADER: &'static str = r#" - ~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ █ █▀ █▀▀▄ ~ v0.4.0, 2026 heatwave edition ~ ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; +#[cfg(feature = "cli")] +mod cli { + + use crate::*; + + /// The command-line interface descriptor. + /// + /// ``` + /// let cli: tek::Cli = Default::default(); + /// + /// use clap::CommandFactory; + /// tek::Cli::command().debug_assert(); + /// ``` + #[derive(Parser)] + #[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))] + #[derive(Debug, Default)] pub struct Cli { + /// Pre-defined configuration modes. + /// + /// TODO: Replace these with scripted configurations. + #[command(subcommand)] pub action: Action, + } + + /// Application modes that can be passed to the mommand line interface. + /// + /// ``` + /// let action: tek::Action = Default::default(); + /// ``` + #[derive(Debug, Clone, Subcommand, Default)] pub enum Action { + /// Continue where you left off + #[default] Resume, + /// Run headlessly in current session. + Headless, + /// Show status of current session. + Status, + /// List known sessions. + List, + /// Continue work in a copy of the current session. + Fork, + /// Create a new empty session. + New { + /// Name of JACK client + #[arg(short='n', long)] name: Option, + /// Whether to attempt to become transport master + #[arg(short='Y', long, default_value_t = false)] sync_lead: bool, + /// Whether to sync to external transport master + #[arg(short='y', long, default_value_t = true)] sync_follow: bool, + /// Initial tempo in beats per minute + #[arg(short='b', long, default_value = None)] bpm: Option, + /// Whether to include a transport toolbar (default: true) + #[arg(short='c', long, default_value_t = true)] show_clock: bool, + /// MIDI outs to connect to (multiple instances accepted) + #[arg(short='I', long)] midi_from: Vec, + /// MIDI outs to connect to (multiple instances accepted) + #[arg(short='i', long)] midi_from_re: Vec, + /// MIDI ins to connect to (multiple instances accepted) + #[arg(short='O', long)] midi_to: Vec, + /// MIDI ins to connect to (multiple instances accepted) + #[arg(short='o', long)] midi_to_re: Vec, + /// Audio outs to connect to left input + #[arg(short='l', long)] left_from: Vec, + /// Audio outs to connect to right input + #[arg(short='r', long)] right_from: Vec, + /// Audio ins to connect from left output + #[arg(short='L', long)] left_to: Vec, + /// Audio ins to connect from right output + #[arg(short='R', long)] right_to: Vec, + /// Tracks to creat + #[arg(short='t', long)] tracks: Option, + /// Scenes to create + #[arg(short='s', long)] scenes: Option, + }, + /// Import media as new session. + Import, + /// Show configuration. + Config, + /// Show version. + Version, + } + + /// Command-line configuration. + #[cfg(feature = "cli")] + impl Cli { + pub fn run (&self) -> Usually<()> { + self.action.run(Config::init_new(None)?) + } + } + + #[cfg(feature = "cli")] + impl Action { + fn run (&self, config: Config) -> Usually<()> { + use Action::*; + match self { + Version => show_version(), + Config => print_config(&config), + List => todo!("list sessions"), + Resume => todo!("resume session"), + New { + name, bpm, tracks, scenes, + sync_lead, sync_follow, + midi_from: mf, midi_from_re: mfr, midi_to: mt, midi_to_re: mtr, + left_from: lf, right_from: rf, left_to: lt, right_to: rt, .. + } => { + let name = name.as_ref().map_or("tek", |x|x.as_str()); + let jack = Jack::new(&name)?; + let mut proj = Arrangement::new( + &jack, + name.into(), + Clock::new(&jack, None)?, + [].into_iter(), + [].into_iter(), + Connect::midi_ins(&jack, &"M", &[], None)?.into_iter(), + Connect::midi_outs(&jack, &"M", &[], None)?.into_iter(), + [].into_iter() + .chain(Connect::audio_ins(&jack, &"L", &[], None)?.into_iter()) + .chain(Connect::audio_ins(&jack, &"R", &[], None)?.into_iter()), + [].into_iter() + .chain(Connect::audio_outs(&jack, &"L", &[], None)?.into_iter()) + .chain(Connect::audio_outs(&jack, &"R", &[], None)?.into_iter()), + ); + //&jack, Clock::new(&jack, *bpm)?, &lf, <, &rf, &rt, &mf, &mt, &mfr, &mtr)?; + proj.tracks_add(tracks.unwrap_or(0), None, &[], &[])?; + proj.scenes_add(scenes.unwrap_or(0))?; + //if matches!(self, Action::Status) { + //// Show status and exit + //tek_print_status(&proj); + //return Ok(()) + //} + // Initialize the app state + let app = Arc::new(RwLock::new(App::new(&jack, proj, config, ":menu"))); + //if matches!(self, Action::Headless) { + //// TODO: Headless mode (daemon + client over IPC, then over network...) + //println!("todo headless"); + //return Ok(()) + //} + let (_keyboard, _terminal) = Exit::run(|exited|Tui::io( + exited.as_ref(), + &app, + Duration::from_millis(100), + Duration::from_millis(10), + std::io::stdout() + ))?; + }, + _ => todo!() + } + Ok(()) + } + } + + //pub fn tui ( + //app: Arc>, + //jack: Jack, + //sync_lead: &bool, + //sync_follow: &bool, + //) -> Usually<()> { + //// Run the [Tui] and [Jack] threads with the [App] state. + //Tui::run_main(&jack.run(move|jack|{ + //// Between jack init and app's first cycle: + ////jack.sync_lead(*sync_lead, |mut state|{ + ////let clock = app.write().unwrap().clock(); + ////clock.playhead.update_from_sample(state.position.frame() as f64); + ////state.position.bbt = Some(clock.bbt()); + ////state.position + ////})?; + ////jack.sync_follow(*sync_follow)?; + //// FIXME: They don't work properly. + //Ok(app) + //})?)? + //} + + pub fn show_version () { + println!("todo version"); + } + + pub fn print_config (config: &Config) { + use ::ansi_term::Color::*; + println!("{:?}", config.dirs); + for (k, v) in config.views.read().unwrap().iter() { + println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}"))); + } + for (k, v) in config.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); + } + } + } + config.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!(); + }); + } + + pub fn print_status (project: &Arrangement) { + println!("Name: {:?}", &project.name); + println!("JACK: {:?}", &project.jack); + println!("Buffer: {:?}", &project.clock.chunk); + println!("Sample rate: {:?}", &project.clock.timebase.sr); + println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); + println!("Tempo: {:?}", &project.clock.timebase.bpm); + println!("Quantize: {:?}", &project.clock.quant); + println!("Launch: {:?}", &project.clock.sync); + println!("Playhead: {:?}us", &project.clock.playhead.usec); + println!("Playhead: {:?}s", &project.clock.playhead.sample); + println!("Playhead: {:?}p", &project.clock.playhead.pulse); + println!("Started: {:?}", &project.clock.started); + println!("Tracks:"); + for (i, t) in project.tracks.iter().enumerate() { + println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, + &t.sequencer.play_clip, &t.sequencer.next_clip); + } + println!("Scenes:"); + for (i, t) in project.scenes.iter().enumerate() { + println!(" Scene {i}: {} {:?}", &t.name, &t.clips); + } + println!("MIDI Ins: {:?}", &project.midi_ins); + println!("MIDI Outs: {:?}", &project.midi_outs); + println!("Audio Ins: {:?}", &project.audio_ins); + println!("Audio Outs: {:?}", &project.audio_outs); + // TODO git integration + // TODO dawvert integration + } + +} diff --git a/tengri b/tengri index 482b6241..c0d6d017 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 482b6241690f7b116b07d06fb4950754f6fdf584 +Subproject commit c0d6d0174e8858108ec053f4ac486cf6229980e1