diff --git a/Cargo.lock b/Cargo.lock index 3f2d7093..a1080f3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,21 +54,23 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-activity" -version = "0.6.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.13.1", + "bitflags 2.11.0", "cc", + "cesu8", "jni", + "jni-sys", "libc", "log", "ndk", "ndk-context", "ndk-sys", "num_enum", - "thiserror 2.0.19", + "thiserror 1.0.69", ] [[package]] @@ -88,9 +90,9 @@ dependencies = [ [[package]] name = "anstream" -version = "1.0.0" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -103,15 +105,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.14" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "1.0.0" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] @@ -138,9 +140,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.104" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "approx" @@ -159,9 +161,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "as-raw-xcb-connection" @@ -206,9 +208,9 @@ checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" [[package]] name = "autocfg" -version = "1.5.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -258,9 +260,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block2" @@ -295,9 +297,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.3" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "by_address" @@ -307,15 +309,15 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.25.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "bytes" -version = "1.12.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cairo-rs" @@ -323,7 +325,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "cairo-sys-rs", "glib", "libc", @@ -348,7 +350,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "log", "polling", "rustix 0.38.44", @@ -385,9 +387,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.3.0" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -395,6 +397,12 @@ 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" @@ -413,15 +421,15 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.6.2" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -429,9 +437,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.2" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -441,27 +449,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "1.1.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "colorchoice" -version = "1.0.5" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "combine" @@ -475,9 +483,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd622ebbb56a5b2ccb651b32b911cdeb2a9b4b11776b2473bf26a26a286244e" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ "castaway", "cfg-if", @@ -526,7 +534,7 @@ checksum = "eb0240417fe20ccf13397fa25e6f0a987dbbfaf27d9e13532419df7f593e65e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", "unicode-xid", ] @@ -581,9 +589,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.7" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -591,18 +599,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.20" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" @@ -610,7 +618,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "crossterm_winapi", "mio", "parking_lot 0.12.5", @@ -626,13 +634,13 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "crossterm_winapi", "derive_more", "document-features", "mio", "parking_lot 0.12.5", - "rustix 1.1.4", + "rustix 1.1.3", "signal-hook", "signal-hook-mio", "winapi", @@ -673,7 +681,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -684,7 +692,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -706,7 +714,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -720,27 +728,16 @@ name = "dizzle" version = "0.1.0" dependencies = [ "const_panic", - "dizzle_proc", "itertools 0.14.0", "konst", - "peg", - "thiserror 2.0.19", -] - -[[package]] -name = "dizzle_proc" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror 2.0.18", ] [[package]] name = "dlib" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ "libloading", ] @@ -768,9 +765,9 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] name = "either" -version = "1.16.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encode_unicode" @@ -817,9 +814,9 @@ checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "fastrand" -version = "2.4.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "field-offset" @@ -867,7 +864,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -876,20 +873,11 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "futures" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -902,9 +890,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -912,15 +900,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -929,38 +917,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1037,7 +1025,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.4", + "rustix 1.1.3", "windows-link", ] @@ -1060,19 +1048,21 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi 5.3.0", + "r-efi", "wasip2", ] [[package]] name = "getrandom" -version = "0.4.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" dependencies = [ "cfg-if", "libc", - "r-efi 6.0.0", + "r-efi", + "wasip2", + "wasip3", ] [[package]] @@ -1119,7 +1109,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "futures-channel", "futures-core", "futures-executor", @@ -1147,7 +1137,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -1220,7 +1210,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -1236,9 +1226,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -1268,6 +1258,12 @@ 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" @@ -1276,12 +1272,14 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.14.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1293,37 +1291,17 @@ dependencies = [ "rustversion", ] -[[package]] -name = "inotify" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -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" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" +checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" dependencies = [ "darling", "indoc", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -1361,15 +1339,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.18" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jack" version = "0.13.0" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "jack-sys", "lazy_static", "libc", @@ -1380,7 +1358,7 @@ dependencies = [ name = "jack-sys" version = "0.5.1" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "lazy_static", "libc", "libloading", @@ -1390,88 +1368,51 @@ dependencies = [ [[package]] name = "jni" -version = "0.22.4" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ + "cesu8", "cfg-if", "combine", - "jni-macros", - "jni-sys 0.4.1", + "jni-sys", "log", - "simd_cesu8", - "thiserror 2.0.19", + "thiserror 1.0.69", "walkdir", - "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", + "windows-sys 0.45.0", ] [[package]] name = "jni-sys" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -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", -] +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.35" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.4.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "93f0862381daaec758576dcc22eb7bbf4d7efd67328553f3b45a412a51a3fb21" dependencies = [ - "cfg-if", - "futures-util", + "once_cell", "wasm-bindgen", ] [[package]] name = "konst" -version = "0.3.17" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97feab15b395d1860944abe6a8dd8ed9f8eadfae01750fada8427abda531d887" +checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" dependencies = [ "const_panic", "konst_kernel", @@ -1494,26 +1435,6 @@ 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" @@ -1521,10 +1442,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "libc" -version = "0.2.186" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libloading" @@ -1538,14 +1465,13 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "libc", - "plain", - "redox_syscall 0.9.0", + "redox_syscall 0.7.1", ] [[package]] @@ -1577,9 +1503,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.12.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litrs" @@ -1611,9 +1537,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" @@ -1641,15 +1567,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.3" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" -version = "0.9.11" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] @@ -1683,9 +1609,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", @@ -1699,8 +1625,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.13.1", - "jni-sys 0.3.1", + "bitflags 2.11.0", + "jni-sys", "log", "ndk-sys", "num_enum", @@ -1720,46 +1646,7 @@ version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "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", + "jni-sys", ] [[package]] @@ -1773,9 +1660,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.6" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -1783,14 +1670,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.6" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -1815,7 +1702,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "libc", "objc2", @@ -1831,7 +1718,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "objc2", "objc2-core-location", @@ -1855,7 +1742,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "objc2", "objc2-foundation", @@ -1897,7 +1784,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "dispatch", "libc", @@ -1922,7 +1809,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "objc2", "objc2-foundation", @@ -1934,7 +1821,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "objc2", "objc2-foundation", @@ -1957,7 +1844,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "objc2", "objc2-cloud-kit", @@ -1989,7 +1876,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "objc2", "objc2-core-location", @@ -2007,9 +1894,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.4" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" @@ -2019,9 +1906,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "orbclient" -version = "0.3.55" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +checksum = "52ad2c6bae700b7aa5d1cc30c59bdd3a1c180b09dbaea51e2ae2b8e1cf211fdd" dependencies = [ "libc", "libredox", @@ -2046,7 +1933,7 @@ dependencies = [ "fast-srgb8", "palette_derive", "phf", - "rand 0.8.7", + "rand 0.8.5", ] [[package]] @@ -2058,7 +1945,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -2140,33 +2027,6 @@ 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" @@ -2190,7 +2050,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.7", + "rand 0.8.5", ] [[package]] @@ -2203,7 +2063,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -2217,41 +2077,35 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.13" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.13" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polling" @@ -2263,7 +2117,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.4", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -2276,6 +2130,16 @@ 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" @@ -2298,11 +2162,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.5.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -2331,24 +2195,24 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.107" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.11.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.13.1", + "bitflags 2.11.0", "num-traits", - "rand 0.9.5", + "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -2365,7 +2229,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -2391,18 +2255,18 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-xml" -version = "0.39.4" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.47" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -2413,17 +2277,11 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - [[package]] name = "rand" -version = "0.8.7" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2432,9 +2290,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -2493,7 +2351,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "cassowary", "compact_str", "crossterm 0.28.1", @@ -2514,7 +2372,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", ] [[package]] @@ -2525,9 +2383,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -2567,23 +2425,23 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", ] [[package]] name = "redox_syscall" -version = "0.9.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" +checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", ] [[package]] name = "regex-syntax" -version = "0.8.11" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "ringbuf" @@ -2596,9 +2454,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.28" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc_version" @@ -2615,7 +2473,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2624,22 +2482,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys 0.11.0", "windows-sys 0.61.2", ] [[package]] name = "rustversion" -version = "1.0.23" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" @@ -2695,37 +2553,50 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.28" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", ] [[package]] name = "serde_core" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 3.0.0", + "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", ] [[package]] @@ -2739,18 +2610,18 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] [[package]] name = "shlex" -version = "2.0.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" @@ -2783,27 +2654,11 @@ dependencies = [ "libc", ] -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -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" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" @@ -2813,9 +2668,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" @@ -2823,7 +2678,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "calloop", "calloop-wayland-source", "cursor-icon", @@ -2888,7 +2743,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -3099,20 +2954,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.119" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -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" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3151,13 +2995,12 @@ dependencies = [ "gtk", "konst", "livi", - "notify", - "notify-debouncer-mini", + "midly", "palette", "proptest", "proptest-derive", "quanta", - "rand 0.8.7", + "rand 0.8.5", "symphonia", "tek", "tengri", @@ -3170,14 +3013,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.27.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.4.1", "once_cell", - "rustix 1.1.4", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -3191,10 +3034,9 @@ dependencies = [ "crossterm 0.29.0", "dizzle", "jack", - "midly", "palette", "quanta", - "rand 0.8.7", + "rand 0.8.5", "ratatui", "unicode-width 0.2.0", ] @@ -3210,11 +3052,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.18", ] [[package]] @@ -3225,18 +3067,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 3.0.0", + "syn 2.0.117", ] [[package]] @@ -3284,11 +3126,11 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", - "serde_spanned 1.1.1", + "serde_spanned 1.0.4", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 0.7.15", + "winnow 0.7.14", ] [[package]] @@ -3309,15 +3151,6 @@ 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" @@ -3344,30 +3177,30 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.13+spec-1.1.0" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow 1.0.4", + "winnow 0.7.14", ] [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ - "winnow 1.0.4", + "winnow 0.7.14", ] [[package]] name = "toml_writer" -version = "1.1.2+spec-1.1.0" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tracing" @@ -3393,9 +3226,9 @@ checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" [[package]] name = "typewit" -version = "1.15.2" +version = "1.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6" +checksum = "f8c1ae7cc0fdb8b842d65d127cb981574b0d2b249b74d1c7a2986863dc134f71" dependencies = [ "typewit_proc_macros", ] @@ -3420,9 +3253,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" @@ -3461,11 +3294,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.24.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.4.3", + "getrandom 0.4.1", "js-sys", "wasm-bindgen", ] @@ -3509,18 +3342,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.4+wasi-0.2.12" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +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" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1de241cdc66a9d91bd84f097039eb140cdc6eec47e0cdbaf9d932a1dd6c35866" dependencies = [ "cfg-if", "once_cell", @@ -3531,19 +3373,23 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "a42e96ea38f49b191e08a1bab66c7ffdba24b06f9995b39a9dd60222e5b6f1da" dependencies = [ + "cfg-if", + "futures-util", "js-sys", + "once_cell", "wasm-bindgen", + "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "e12fdf6649048f2e3de6d7d5ff3ced779cdedee0e0baffd7dff5cdfa3abc8a52" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3551,26 +3397,60 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "0e63d1795c565ac3462334c1e396fd46dbf481c40f51f5072c310717bc4fb309" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "e9f9cdac23a5ce71f6bf9f8824898a501e511892791ea2a0c6b8568c68b9cb53" 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" @@ -3586,13 +3466,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.15" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" dependencies = [ "cc", "downcast-rs", - "rustix 1.1.4", + "rustix 1.1.3", "scoped-tls", "smallvec", "wayland-sys", @@ -3600,12 +3480,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.14" +version = "0.31.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" dependencies = [ - "bitflags 2.13.1", - "rustix 1.1.4", + "bitflags 2.11.0", + "rustix 1.1.3", "wayland-backend", "wayland-scanner", ] @@ -3616,29 +3496,29 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.14" +version = "0.31.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +checksum = "5864c4b5b6064b06b1e8b74ead4a98a6c45a285fe7a0e784d24735f011fdb078" dependencies = [ - "rustix 1.1.4", + "rustix 1.1.3", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.13" +version = "0.32.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -3646,11 +3526,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.12" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +checksum = "aa98634619300a535a9a97f338aed9a5ff1e01a461943e8346ff4ae26007306b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -3659,11 +3539,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.12" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -3672,9 +3552,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.10" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" dependencies = [ "proc-macro2", "quick-xml", @@ -3683,9 +3563,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.11" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" dependencies = [ "dlib", "log", @@ -3695,9 +3575,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "f2c7c5718134e770ee62af3b6b4a84518ec10101aad610c024b64d6ff29bb1ff" dependencies = [ "js-sys", "wasm-bindgen", @@ -3750,6 +3630,15 @@ 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" @@ -3768,15 +3657,6 @@ 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" @@ -3786,6 +3666,21 @@ 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" @@ -3795,7 +3690,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -3803,21 +3698,10 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.53.5" +name = "windows_aarch64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -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", -] +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" @@ -3826,10 +3710,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" +name = "windows_aarch64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -3838,10 +3722,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -3849,12 +3733,6 @@ 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" @@ -3862,10 +3740,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" +name = "windows_i686_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -3874,10 +3752,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_i686_msvc" -version = "0.53.1" +name = "windows_x86_64_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -3886,10 +3764,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" +name = "windows_x86_64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -3898,10 +3776,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -3909,22 +3787,16 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winit" -version = "0.30.13" +version = "0.30.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "bytemuck", "calloop", @@ -3978,24 +3850,100 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.15" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - -[[package]] -name = "winnow" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.57.1" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +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", +] [[package]] name = "x11-dl" @@ -4019,7 +3967,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix 1.1.4", + "rustix 1.1.3", "x11rb-protocol", ] @@ -4047,7 +3995,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "dlib", "log", "once_cell", @@ -4062,20 +4010,26 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] + +[[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 0e1ba1bf..8453c345 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ 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" } @@ -35,8 +36,6 @@ 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 33f077c9..21326baa 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 30030e02..46ef565e 100644 --- a/bacon.toml +++ b/bacon.toml @@ -1,14 +1,11 @@ # 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"] @@ -50,9 +47,9 @@ command = [ "cargo", "run", # put launch parameters for your program behind a `--` separator ] -need_stdout = false +need_stdout = true allow_warnings = true -background = false +background = true [jobs.run-long] watch = ["dizzle", "tengri", "app"] command = [ "cargo", "run", ] diff --git a/shell.nix b/shell.nix index 78a0b912..a449cf33 100755 --- a/shell.nix +++ b/shell.nix @@ -9,7 +9,6 @@ pkgs.libclang pkgs.mold pkgs.cloc - pkgs.watchexec ]; buildInputs = [ pkgs.libclang diff --git a/src/app.rs b/src/app.rs index a583e394..93c3d74a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,25 +1,11 @@ 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. /// /// ``` @@ -43,21 +29,7 @@ impl_has_clips!( |self: App|self.pool.clips); /// //let _ = app.project.h_outputs(); /// let _ = app.project.h_scenes(); /// ``` -#[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 { +#[derive(Default, Debug)] pub struct App { /// Base color. pub color: ItemTheme, /// Must not be dropped for the duration of the process @@ -81,7 +53,9 @@ pub struct App { /// Error, if any pub error: Arc>>> } + impl App { + /// Create a new application instance from a backend, project, config, and mode /// /// ``` @@ -105,6 +79,36 @@ 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(); @@ -223,139 +227,99 @@ impl App { } } } - 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!(), - }) - } } +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 + } +}; }); + pub fn swap_value ( target: &mut T, value: &T, returned: impl Fn(T)->U ) -> Perhaps { @@ -396,3 +360,4 @@ 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 c1a514f8..01628e03 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 new file mode 100644 index 00000000..61250d14 --- /dev/null +++ b/src/app/cli.rs @@ -0,0 +1,259 @@ +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 ff935638..fab190fa 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -38,11 +38,8 @@ 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 watcher = notify_debouncer_mini::new_debouncer(Duration::from_millis(500), |res| { - println!("{res:?}"); - })?; - let result = callback(config); + let config = Self::init_new(None)?; + let result = callback(config); Ok(result) } diff --git a/src/app/draw.rs b/src/app/draw.rs index 3f439d12..3c534514 100644 --- a/src/app/draw.rs +++ b/src/app/draw.rs @@ -14,9 +14,7 @@ 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!( - "mode {:?} view #{index}: {e}", &self.mode.name, - ).into()); + *self.error.write().unwrap() = Some(format!("view #{index}: {e}").into()); break; } } @@ -47,7 +45,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), @@ -58,7 +56,6 @@ 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(); @@ -112,7 +109,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((4 * index) as u16).draw(to)?; + .push_y((2 * index) as u16).draw(to)?; } Ok(Some(to.area().into())) }).full_wh()) @@ -144,6 +141,19 @@ 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()); @@ -152,7 +162,7 @@ pub fn draw_templates (to: &mut Tui, frags: std::str::Split<&str>, state: &App) 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(), + button_play_pause(play, false).align_w().full_wh(), east!( field_h(theme, "BPM", bpm), field_h(theme, "Beat", beat), @@ -455,7 +465,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 { - let _ = port.port_name().align_w().full_w().draw(to)?; + port.port_name().align_w().full_w().draw(to); } Ok(Some(XYWH(0, 0, 0, 0))) })), @@ -574,7 +584,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 { - let _ = south( + south( bg(track.color.dark.term, east!( either(track.sequencer.monitoring, fg(Green, "mon "), "mon "), either(track.sequencer.recording, fg(Red, "rec "), "rec "), @@ -582,24 +592,26 @@ 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() { - let _ = east( + east( east( " ● ", bold(true, fg(Rgb(255,255,255), port.port_name())) ).align_w().exact_w(20), west( ().exact_w(4), - 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)?; + 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); } todo!() }) - ).draw(to)?; + ).draw(to); } todo!() }))) @@ -634,7 +646,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 { - let _ = thunk(|to: &mut Tui|{ + thunk(|to: &mut Tui|{ east( either(true, fg(Green, "play "), "play "), either(false, fg(Yellow, "solo "), "solo "), @@ -649,7 +661,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) @@ -699,14 +711,10 @@ 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 2f9683a8..f599cc16 100644 --- a/src/deps.rs +++ b/src/deps.rs @@ -1,3 +1,20 @@ +#[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::{ @@ -13,20 +30,4 @@ 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 8e842c52..614a8735 100644 --- a/src/device.rs +++ b/src/device.rs @@ -100,7 +100,6 @@ 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::*; @@ -109,36 +108,9 @@ 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 d54a8b2e..e3836693 100644 --- a/src/device/arrange.rs +++ b/src/device/arrange.rs @@ -5,8 +5,7 @@ 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. @@ -20,8 +19,6 @@ pub struct Arrangement { 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 @@ -34,6 +31,8 @@ pub struct Arrangement { #[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 @@ -46,58 +45,34 @@ pub struct Arrangement { #[cfg(feature = "scene")] pub scene_scroll: usize, } -/// 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 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()); impl Arrangement { /// Create a new arrangement. pub fn new ( - 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, + jack: &Jack<'static>, + name: Option>, + clock: Clock, + tracks: Vec, + scenes: Vec, + midi_ins: Vec, + midi_outs: Vec, ) -> Self { Self { - 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, + 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 }, ..Default::default() } } @@ -142,759 +117,7 @@ impl Arrangement { } -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 -} +#[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::*; diff --git a/src/device/arrange/clip.rs b/src/device/arrange/clip.rs new file mode 100644 index 00000000..e572a9da --- /dev/null +++ b/src/device/arrange/clip.rs @@ -0,0 +1,95 @@ +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 new file mode 100644 index 00000000..d7c57966 --- /dev/null +++ b/src/device/arrange/scene.rs @@ -0,0 +1,174 @@ +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 new file mode 100644 index 00000000..ae748e5d --- /dev/null +++ b/src/device/arrange/select.rs @@ -0,0 +1,161 @@ +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 new file mode 100644 index 00000000..c480f694 --- /dev/null +++ b/src/device/arrange/track.rs @@ -0,0 +1,350 @@ +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 b007ffa2..25aa4fc5 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -14,56 +14,38 @@ use crate::{*, browse::*, device::*, menu::*}; Browse(BrowseTarget, Arc), Options, } - -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() - } -} - +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 Dialog { /// ``` /// let _ = tek::Dialog::welcome(); /// ``` pub fn welcome () -> Self { Self::Menu(1, MenuItems([ - MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))), - MenuItem("New session".into(), Arc::new(Box::new(|app|Ok({ + MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))), + MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({ app.dialog = Dialog::None; app.mode = app.config.modes.get(":arranger").unwrap(); })))), - MenuItem("Load session".into(), Arc::new(Box::new(|_|Ok(())))), - MenuItem("Exit".into(), Arc::new(Box::new(|_|Ok({ - () - })))), + MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))), ].into())) } /// FIXME: generalize diff --git a/src/device/midi.rs b/src/device/midi.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/device/port.rs b/src/device/port.rs new file mode 100644 index 00000000..eb8cf92a --- /dev/null +++ b/src/device/port.rs @@ -0,0 +1,136 @@ +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 new file mode 100644 index 00000000..bb1db676 --- /dev/null +++ b/src/device/port/audio.rs @@ -0,0 +1,103 @@ +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 new file mode 100644 index 00000000..8571fa2e --- /dev/null +++ b/src/device/port/connect.rs @@ -0,0 +1,83 @@ +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 new file mode 100644 index 00000000..eb9160cc --- /dev/null +++ b/src/device/port/midi.rs @@ -0,0 +1,256 @@ +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 new file mode 100644 index 00000000..0702a21a --- /dev/null +++ b/src/device/port/register.rs @@ -0,0 +1,27 @@ +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 8088a404..3e280965 100644 --- a/src/device/sequence.rs +++ b/src/device/sequence.rs @@ -1,6 +1,5 @@ use crate::{*, clock::*, device::*}; - impl +AsMut> HasSequencer for T {} /// A MIDI sequence. @@ -230,6 +229,7 @@ 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,6 +592,35 @@ 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 d14123c0..9020ef91 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -1,76 +1,3 @@ -(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)) @@ -83,67 +10,113 @@ (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 :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)) +(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))) diff --git a/src/tek.rs b/src/tek.rs index 1f291ac9..26391210 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -6,295 +6,33 @@ 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<()> { - 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(|_|()) + 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(|_|()) } -pub mod deps; -pub(crate) use self::deps::*; -pub mod device; -pub use self::device::*; -pub mod app; -pub use self::app::*; + +pub mod deps; pub 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 c0d6d017..482b6241 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit c0d6d0174e8858108ec053f4ac486cf6229980e1 +Subproject commit 482b6241690f7b116b07d06fb4950754f6fdf584