seq
This commit is contained in:
621
Cargo.lock
generated
621
Cargo.lock
generated
@@ -2,6 +2,12 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.12"
|
version = "0.8.12"
|
||||||
@@ -25,6 +31,12 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alsa"
|
name = "alsa"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@@ -97,6 +109,26 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arboard"
|
||||||
|
version = "3.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
|
||||||
|
dependencies = [
|
||||||
|
"clipboard-win",
|
||||||
|
"image",
|
||||||
|
"log",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-foundation",
|
||||||
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"x11rb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -222,12 +254,33 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cassowary"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "castaway"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.50"
|
version = "1.2.50"
|
||||||
@@ -352,6 +405,20 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compact_str"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
|
||||||
|
dependencies = [
|
||||||
|
"castaway",
|
||||||
|
"cfg-if",
|
||||||
|
"itoa",
|
||||||
|
"rustversion",
|
||||||
|
"ryu",
|
||||||
|
"static_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-random"
|
name = "const-random"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
@@ -492,6 +559,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.15"
|
version = "0.5.15"
|
||||||
@@ -507,6 +583,31 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.28.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"rustix 0.38.44",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crunchy"
|
name = "crunchy"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@@ -523,6 +624,40 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
||||||
|
dependencies = [
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dasp_sample"
|
name = "dasp_sample"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -570,6 +705,16 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dispatch2"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"objc2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "doux"
|
name = "doux"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -657,6 +802,26 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365"
|
checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fax"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
|
||||||
|
dependencies = [
|
||||||
|
"fax_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fax_derive"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fd-lock"
|
name = "fd-lock"
|
||||||
version = "4.0.4"
|
version = "4.0.4"
|
||||||
@@ -664,10 +829,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
|
checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"rustix",
|
"rustix 1.1.3",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -680,6 +854,22 @@ version = "0.5.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
@@ -690,6 +880,16 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gethostname"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||||
|
dependencies = [
|
||||||
|
"rustix 1.1.3",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -719,6 +919,28 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
@@ -740,6 +962,26 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.25.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder-lite",
|
||||||
|
"moxcms",
|
||||||
|
"num-traits",
|
||||||
|
"png",
|
||||||
|
"tiff",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.12.1"
|
version = "2.12.1"
|
||||||
@@ -747,16 +989,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown 0.16.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "indoc"
|
||||||
version = "0.1.13"
|
version = "2.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instability"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"indoc",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -928,6 +1183,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -949,6 +1210,15 @@ version = "0.4.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "luau0-src"
|
name = "luau0-src"
|
||||||
version = "0.17.1+luau702"
|
version = "0.17.1+luau702"
|
||||||
@@ -1015,6 +1285,16 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -1022,6 +1302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
@@ -1054,6 +1335,16 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moxcms"
|
||||||
|
version = "0.7.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"pxfm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndk"
|
name = "ndk"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -1163,6 +1454,79 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
|
||||||
|
dependencies = [
|
||||||
|
"objc2-encode",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-app-kit"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-foundation"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"dispatch2",
|
||||||
|
"objc2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-graphics"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"dispatch2",
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-io-surface",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-encode"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-foundation"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-io-surface"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oboe"
|
name = "oboe"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@@ -1230,6 +1594,18 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.8.5"
|
version = "2.8.5"
|
||||||
@@ -1310,6 +1686,19 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
@@ -1359,6 +1748,21 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pxfm"
|
||||||
|
version = "0.1.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.42"
|
version = "1.0.42"
|
||||||
@@ -1413,6 +1817,27 @@ dependencies = [
|
|||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ratatui"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"cassowary",
|
||||||
|
"compact_str",
|
||||||
|
"crossterm",
|
||||||
|
"indoc",
|
||||||
|
"instability",
|
||||||
|
"itertools 0.13.0",
|
||||||
|
"lru",
|
||||||
|
"paste",
|
||||||
|
"strum",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-truncate",
|
||||||
|
"unicode-width 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
@@ -1464,19 +1889,19 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rhai"
|
name = "rhai"
|
||||||
version = "1.23.6"
|
version = "1.24.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f4e35aaaa439a5bda2f8d15251bc375e4edfac75f9865734644782c9701b5709"
|
checksum = "1f9ef5dabe4c0b43d8f1187dc6beb67b53fe607fff7e30c5eb7f71b814b8c2c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"instant",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rhai_codegen",
|
"rhai_codegen",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smartstring",
|
"smartstring",
|
||||||
"thin-vec",
|
"thin-vec",
|
||||||
|
"web-time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1525,6 +1950,19 @@ version = "2.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys 0.4.15",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
@@ -1534,7 +1972,7 @@ dependencies = [
|
|||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys 0.11.0",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1571,11 +2009,17 @@ dependencies = [
|
|||||||
"nix",
|
"nix",
|
||||||
"radix_trie",
|
"radix_trie",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width 0.1.14",
|
||||||
"utf8parse",
|
"utf8parse",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -1591,6 +2035,22 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "seq"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"arboard",
|
||||||
|
"cpal",
|
||||||
|
"crossterm",
|
||||||
|
"doux",
|
||||||
|
"ratatui",
|
||||||
|
"rhai",
|
||||||
|
"rusty_link",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tui-textarea",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -1670,6 +2130,27 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.8"
|
version = "1.4.8"
|
||||||
@@ -1680,6 +2161,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simple-easing"
|
name = "simple-easing"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -1752,6 +2239,28 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.26.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.26.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "symphonia"
|
name = "symphonia"
|
||||||
version = "0.5.5"
|
version = "0.5.5"
|
||||||
@@ -1933,6 +2442,20 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiff"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
|
||||||
|
dependencies = [
|
||||||
|
"fax",
|
||||||
|
"flate2",
|
||||||
|
"half",
|
||||||
|
"quick-error",
|
||||||
|
"weezl",
|
||||||
|
"zune-jpeg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-keccak"
|
name = "tiny-keccak"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
@@ -2041,6 +2564,17 @@ version = "0.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tui-textarea"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
"ratatui",
|
||||||
|
"unicode-width 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.19.0"
|
version = "1.19.0"
|
||||||
@@ -2065,12 +2599,29 @@ version = "1.12.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-truncate"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||||
|
dependencies = [
|
||||||
|
"itertools 0.13.0",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width 0.1.14",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
@@ -2194,6 +2745,22 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-time"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "weezl"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -2616,6 +3183,23 @@ version = "0.46.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||||
|
dependencies = [
|
||||||
|
"gethostname",
|
||||||
|
"rustix 1.1.3",
|
||||||
|
"x11rb-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb-protocol"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.33"
|
version = "0.8.33"
|
||||||
@@ -2669,3 +3253,18 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-jpeg"
|
||||||
|
version = "0.4.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
|
||||||
|
dependencies = [
|
||||||
|
"zune-core",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [".", "doux-sova"]
|
members = [".", "doux-sova", "seq"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "doux"
|
name = "doux"
|
||||||
|
|||||||
20
seq/Cargo.toml
Normal file
20
seq/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "seq"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seq"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
doux = { path = "..", features = ["native"] }
|
||||||
|
rusty_link = "0.4"
|
||||||
|
ratatui = "0.29"
|
||||||
|
crossterm = "0.28"
|
||||||
|
cpal = "0.15"
|
||||||
|
rhai = "1.24"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
tui-textarea = "0.7"
|
||||||
|
arboard = "3"
|
||||||
375
seq/src/app.rs
Normal file
375
seq/src/app.rs
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use tui_textarea::TextArea;
|
||||||
|
|
||||||
|
use crate::file;
|
||||||
|
use crate::link::LinkState;
|
||||||
|
use crate::model::{Pattern, Project};
|
||||||
|
use crate::page::Page;
|
||||||
|
use crate::script::{ScriptEngine, StepContext};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Focus {
|
||||||
|
Sequencer,
|
||||||
|
Editor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub enum Modal {
|
||||||
|
None,
|
||||||
|
ConfirmQuit,
|
||||||
|
SaveAs(String),
|
||||||
|
LoadFrom(String),
|
||||||
|
PatternPicker { cursor: usize },
|
||||||
|
BankPicker { cursor: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
pub tempo: f64,
|
||||||
|
pub beat: f64,
|
||||||
|
pub phase: f64,
|
||||||
|
pub peers: u64,
|
||||||
|
pub playing: bool,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub quantum: f64,
|
||||||
|
|
||||||
|
pub project: Project,
|
||||||
|
pub focus: Focus,
|
||||||
|
pub page: Page,
|
||||||
|
pub current_step: usize,
|
||||||
|
pub playback_step: usize,
|
||||||
|
|
||||||
|
pub edit_bank: usize,
|
||||||
|
pub edit_pattern: usize,
|
||||||
|
pub playback_bank: usize,
|
||||||
|
pub playback_pattern: usize,
|
||||||
|
pub queued_bank: Option<usize>,
|
||||||
|
pub queued_pattern: Option<usize>,
|
||||||
|
pub event_count: usize,
|
||||||
|
pub active_voices: usize,
|
||||||
|
pub peak_voices: usize,
|
||||||
|
pub cpu_load: f32,
|
||||||
|
pub schedule_depth: usize,
|
||||||
|
pub sample_pool_mb: f32,
|
||||||
|
pub scope: [f32; 64],
|
||||||
|
pub script_engine: ScriptEngine,
|
||||||
|
pub file_path: Option<PathBuf>,
|
||||||
|
pub status_message: Option<String>,
|
||||||
|
pub editor: TextArea<'static>,
|
||||||
|
pub flash_until: Option<Instant>,
|
||||||
|
pub modal: Modal,
|
||||||
|
pub clipboard: Option<arboard::Clipboard>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new(tempo: f64, quantum: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
tempo,
|
||||||
|
beat: 0.0,
|
||||||
|
phase: 0.0,
|
||||||
|
peers: 0,
|
||||||
|
playing: true,
|
||||||
|
quantum,
|
||||||
|
|
||||||
|
project: Project::default(),
|
||||||
|
focus: Focus::Sequencer,
|
||||||
|
page: Page::default(),
|
||||||
|
current_step: 0,
|
||||||
|
playback_step: 0,
|
||||||
|
|
||||||
|
edit_bank: 0,
|
||||||
|
edit_pattern: 0,
|
||||||
|
playback_bank: 0,
|
||||||
|
playback_pattern: 0,
|
||||||
|
queued_bank: None,
|
||||||
|
queued_pattern: None,
|
||||||
|
event_count: 0,
|
||||||
|
active_voices: 0,
|
||||||
|
peak_voices: 0,
|
||||||
|
cpu_load: 0.0,
|
||||||
|
schedule_depth: 0,
|
||||||
|
sample_pool_mb: 0.0,
|
||||||
|
scope: [0.0; 64],
|
||||||
|
script_engine: ScriptEngine::new(),
|
||||||
|
file_path: None,
|
||||||
|
status_message: None,
|
||||||
|
editor: TextArea::default(),
|
||||||
|
flash_until: None,
|
||||||
|
modal: Modal::None,
|
||||||
|
clipboard: arboard::Clipboard::new().ok(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_from_link(&mut self, link: &LinkState) {
|
||||||
|
let (tempo, beat, phase, peers) = link.query();
|
||||||
|
self.tempo = tempo;
|
||||||
|
self.beat = beat;
|
||||||
|
self.phase = phase;
|
||||||
|
self.peers = peers;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_playing(&mut self) {
|
||||||
|
self.playing = !self.playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tempo_up(&mut self, link: &LinkState) {
|
||||||
|
self.tempo = (self.tempo + 1.0).min(300.0);
|
||||||
|
link.set_tempo(self.tempo);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tempo_down(&mut self, link: &LinkState) {
|
||||||
|
self.tempo = (self.tempo - 1.0).max(20.0);
|
||||||
|
link.set_tempo(self.tempo);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_focus(&mut self) {
|
||||||
|
match self.focus {
|
||||||
|
Focus::Sequencer => {
|
||||||
|
self.focus = Focus::Editor;
|
||||||
|
self.load_step_to_editor();
|
||||||
|
}
|
||||||
|
Focus::Editor => {
|
||||||
|
self.save_editor_to_step();
|
||||||
|
self.compile_current_step();
|
||||||
|
self.focus = Focus::Sequencer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_edit_pattern(&self) -> &Pattern {
|
||||||
|
self.project.pattern_at(self.edit_bank, self.edit_pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_step(&mut self) {
|
||||||
|
let len = self.current_edit_pattern().length;
|
||||||
|
self.current_step = (self.current_step + 1) % len;
|
||||||
|
self.load_step_to_editor();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev_step(&mut self) {
|
||||||
|
let len = self.current_edit_pattern().length;
|
||||||
|
self.current_step = (self.current_step + len - 1) % len;
|
||||||
|
self.load_step_to_editor();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step_up(&mut self) {
|
||||||
|
let len = self.current_edit_pattern().length;
|
||||||
|
if self.current_step >= 8 {
|
||||||
|
self.current_step -= 8;
|
||||||
|
} else {
|
||||||
|
self.current_step = (self.current_step + len - 8) % len;
|
||||||
|
}
|
||||||
|
self.load_step_to_editor();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step_down(&mut self) {
|
||||||
|
let len = self.current_edit_pattern().length;
|
||||||
|
self.current_step = (self.current_step + 8) % len;
|
||||||
|
self.load_step_to_editor();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_step(&mut self) {
|
||||||
|
let step_idx = self.current_step;
|
||||||
|
let (bank, pattern) = (self.edit_bank, self.edit_pattern);
|
||||||
|
if let Some(step) = self.project.pattern_at_mut(bank, pattern).step_mut(step_idx) {
|
||||||
|
step.active = !step.active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_step_to_editor(&mut self) {
|
||||||
|
let step_idx = self.current_step;
|
||||||
|
if let Some(step) = self.current_edit_pattern().step(step_idx) {
|
||||||
|
let lines: Vec<String> = if step.script.is_empty() {
|
||||||
|
vec![String::new()]
|
||||||
|
} else {
|
||||||
|
step.script.lines().map(String::from).collect()
|
||||||
|
};
|
||||||
|
self.editor = TextArea::new(lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_editor_to_step(&mut self) {
|
||||||
|
let text = self.editor.lines().join("\n");
|
||||||
|
let step_idx = self.current_step;
|
||||||
|
let (bank, pattern) = (self.edit_bank, self.edit_pattern);
|
||||||
|
if let Some(step) = self.project.pattern_at_mut(bank, pattern).step_mut(step_idx) {
|
||||||
|
step.script = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile_current_step(&mut self) {
|
||||||
|
let step_idx = self.current_step;
|
||||||
|
let (bank, pattern) = (self.edit_bank, self.edit_pattern);
|
||||||
|
|
||||||
|
let script = self
|
||||||
|
.project
|
||||||
|
.pattern_at(bank, pattern)
|
||||||
|
.step(step_idx)
|
||||||
|
.map(|s| s.script.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if script.trim().is_empty() {
|
||||||
|
if let Some(step) = self.project.pattern_at_mut(bank, pattern).step_mut(step_idx) {
|
||||||
|
step.command = None;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx = StepContext {
|
||||||
|
step: step_idx,
|
||||||
|
beat: self.beat,
|
||||||
|
bank,
|
||||||
|
pattern,
|
||||||
|
tempo: self.tempo,
|
||||||
|
phase: self.phase,
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.script_engine.evaluate(&script, &ctx) {
|
||||||
|
Ok(cmd) => {
|
||||||
|
if let Some(step) = self.project.pattern_at_mut(bank, pattern).step_mut(step_idx) {
|
||||||
|
step.command = Some(cmd);
|
||||||
|
}
|
||||||
|
self.status_message = Some("Script compiled".to_string());
|
||||||
|
self.flash_until = Some(Instant::now() + std::time::Duration::from_millis(150));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if let Some(step) = self.project.pattern_at_mut(bank, pattern).step_mut(step_idx) {
|
||||||
|
step.command = None;
|
||||||
|
}
|
||||||
|
self.status_message = Some(format!("Script error: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile_all_steps(&mut self) {
|
||||||
|
let pattern_len = self.current_edit_pattern().length;
|
||||||
|
let (bank, pattern) = (self.edit_bank, self.edit_pattern);
|
||||||
|
|
||||||
|
for step_idx in 0..pattern_len {
|
||||||
|
let script = self
|
||||||
|
.project
|
||||||
|
.pattern_at(bank, pattern)
|
||||||
|
.step(step_idx)
|
||||||
|
.map(|s| s.script.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if script.trim().is_empty() {
|
||||||
|
if let Some(step) = self.project.pattern_at_mut(bank, pattern).step_mut(step_idx) {
|
||||||
|
step.command = None;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx = StepContext {
|
||||||
|
step: step_idx,
|
||||||
|
beat: 0.0,
|
||||||
|
bank,
|
||||||
|
pattern,
|
||||||
|
tempo: self.tempo,
|
||||||
|
phase: 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(cmd) = self.script_engine.evaluate(&script, &ctx) {
|
||||||
|
if let Some(step) = self.project.pattern_at_mut(bank, pattern).step_mut(step_idx) {
|
||||||
|
step.command = Some(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_current_for_playback(&mut self) {
|
||||||
|
self.queued_bank = Some(self.edit_bank);
|
||||||
|
self.queued_pattern = Some(self.edit_pattern);
|
||||||
|
self.status_message = Some(format!(
|
||||||
|
"Queued B{:02} P{:02} (next loop)",
|
||||||
|
self.edit_bank + 1,
|
||||||
|
self.edit_pattern + 1
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_edit_pattern(&mut self, pattern: usize) {
|
||||||
|
self.edit_pattern = pattern;
|
||||||
|
self.current_step = 0;
|
||||||
|
self.load_step_to_editor();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_edit_bank(&mut self, bank: usize) {
|
||||||
|
self.edit_bank = bank;
|
||||||
|
self.edit_pattern = 0;
|
||||||
|
self.current_step = 0;
|
||||||
|
self.load_step_to_editor();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&mut self, path: PathBuf) {
|
||||||
|
self.save_editor_to_step();
|
||||||
|
match file::save(&self.project, &path) {
|
||||||
|
Ok(()) => {
|
||||||
|
self.status_message = Some(format!("Saved: {}", path.display()));
|
||||||
|
self.file_path = Some(path);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.status_message = Some(format!("Save error: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(&mut self, path: PathBuf) {
|
||||||
|
match file::load(&path) {
|
||||||
|
Ok(project) => {
|
||||||
|
self.project = project;
|
||||||
|
self.current_step = 0;
|
||||||
|
self.load_step_to_editor();
|
||||||
|
self.compile_all_steps();
|
||||||
|
self.status_message = Some(format!("Loaded: {}", path.display()));
|
||||||
|
self.file_path = Some(path);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.status_message = Some(format!("Load error: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_status(&mut self) {
|
||||||
|
self.status_message = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_flashing(&self) -> bool {
|
||||||
|
self.flash_until
|
||||||
|
.map(|t| Instant::now() < t)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_step(&mut self) {
|
||||||
|
let step_idx = self.current_step;
|
||||||
|
let script = self
|
||||||
|
.current_edit_pattern()
|
||||||
|
.step(step_idx)
|
||||||
|
.map(|s| s.script.clone());
|
||||||
|
|
||||||
|
if let Some(script) = script {
|
||||||
|
if let Some(clip) = &mut self.clipboard {
|
||||||
|
if clip.set_text(&script).is_ok() {
|
||||||
|
self.status_message = Some("Copied".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paste_step(&mut self) {
|
||||||
|
let text = self
|
||||||
|
.clipboard
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|clip| clip.get_text().ok());
|
||||||
|
|
||||||
|
if let Some(text) = text {
|
||||||
|
let step_idx = self.current_step;
|
||||||
|
let (bank, pattern) = (self.edit_bank, self.edit_pattern);
|
||||||
|
if let Some(step) = self.project.pattern_at_mut(bank, pattern).step_mut(step_idx) {
|
||||||
|
step.script = text;
|
||||||
|
}
|
||||||
|
self.load_step_to_editor();
|
||||||
|
self.compile_current_step();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
seq/src/audio.rs
Normal file
120
seq/src/audio.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
|
use cpal::Stream;
|
||||||
|
use doux::Engine;
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::link::LinkState;
|
||||||
|
use crate::model::Project;
|
||||||
|
|
||||||
|
pub struct AudioState {
|
||||||
|
prev_beat: f64,
|
||||||
|
step_index: usize,
|
||||||
|
bank: usize,
|
||||||
|
pattern: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioState {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
prev_beat: -1.0,
|
||||||
|
step_index: 0,
|
||||||
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_stream(
|
||||||
|
engine: Arc<Mutex<Engine>>,
|
||||||
|
link: Arc<LinkState>,
|
||||||
|
playing: Arc<AtomicBool>,
|
||||||
|
project: Arc<Mutex<Project>>,
|
||||||
|
playback_step: Arc<AtomicUsize>,
|
||||||
|
event_count: Arc<AtomicUsize>,
|
||||||
|
playback_bank: Arc<AtomicUsize>,
|
||||||
|
playback_pattern: Arc<AtomicUsize>,
|
||||||
|
queued_bank: Arc<AtomicUsize>,
|
||||||
|
queued_pattern: Arc<AtomicUsize>,
|
||||||
|
) -> (Stream, f32) {
|
||||||
|
let host = cpal::default_host();
|
||||||
|
let device = host.default_output_device().expect("no output device");
|
||||||
|
let config = device.default_output_config().expect("no default config");
|
||||||
|
let sample_rate = config.sample_rate().0 as f32;
|
||||||
|
|
||||||
|
let stream_config = cpal::StreamConfig {
|
||||||
|
channels: 2,
|
||||||
|
sample_rate: config.sample_rate(),
|
||||||
|
buffer_size: cpal::BufferSize::Default,
|
||||||
|
};
|
||||||
|
|
||||||
|
let quantum = 4.0;
|
||||||
|
let audio_state = Arc::new(Mutex::new(AudioState::new()));
|
||||||
|
|
||||||
|
let sr = sample_rate;
|
||||||
|
let stream = device
|
||||||
|
.build_output_stream(
|
||||||
|
&stream_config,
|
||||||
|
move |data: &mut [f32], _| {
|
||||||
|
let buffer_samples = data.len() / 2;
|
||||||
|
let buffer_time_ns = (buffer_samples as f64 / sr as f64 * 1e9) as u64;
|
||||||
|
|
||||||
|
let is_playing = playing.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
if is_playing {
|
||||||
|
let state = link.capture_audio_state();
|
||||||
|
let time = link.clock_micros();
|
||||||
|
let beat = state.beat_at_time(time, quantum);
|
||||||
|
|
||||||
|
let mut audio = audio_state.lock().unwrap();
|
||||||
|
let beat_int = (beat * 4.0).floor() as i64;
|
||||||
|
let prev_beat_int = (audio.prev_beat * 4.0).floor() as i64;
|
||||||
|
|
||||||
|
if beat_int != prev_beat_int && audio.prev_beat >= 0.0 {
|
||||||
|
let proj = project.lock().unwrap();
|
||||||
|
let pattern = proj.pattern_at(audio.bank, audio.pattern);
|
||||||
|
let step_idx = audio.step_index % pattern.length;
|
||||||
|
|
||||||
|
playback_step.store(step_idx, Ordering::Relaxed);
|
||||||
|
playback_bank.store(audio.bank, Ordering::Relaxed);
|
||||||
|
playback_pattern.store(audio.pattern, Ordering::Relaxed);
|
||||||
|
|
||||||
|
if let Some(step) = pattern.step(step_idx) {
|
||||||
|
if step.active {
|
||||||
|
if let Some(ref cmd) = step.command {
|
||||||
|
engine.lock().unwrap().evaluate(cmd);
|
||||||
|
event_count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_step = (audio.step_index + 1) % pattern.length;
|
||||||
|
audio.step_index = next_step;
|
||||||
|
|
||||||
|
if next_step == 0 {
|
||||||
|
let qb = queued_bank.load(Ordering::Relaxed);
|
||||||
|
let qp = queued_pattern.load(Ordering::Relaxed);
|
||||||
|
if qb != usize::MAX && qp != usize::MAX {
|
||||||
|
audio.bank = qb;
|
||||||
|
audio.pattern = qp;
|
||||||
|
audio.step_index = 0;
|
||||||
|
queued_bank.store(usize::MAX, Ordering::Relaxed);
|
||||||
|
queued_pattern.store(usize::MAX, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
audio.prev_beat = beat;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut eng = engine.lock().unwrap();
|
||||||
|
eng.metrics.load.set_buffer_time(buffer_time_ns);
|
||||||
|
eng.process_block(data, &[], &[]);
|
||||||
|
},
|
||||||
|
|err| eprintln!("stream error: {err}"),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("failed to build stream");
|
||||||
|
|
||||||
|
stream.play().expect("failed to play stream");
|
||||||
|
(stream, sample_rate)
|
||||||
|
}
|
||||||
75
seq/src/file.rs
Normal file
75
seq/src/file.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::model::{Bank, Project};
|
||||||
|
|
||||||
|
const VERSION: u8 = 1;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct ProjectFile {
|
||||||
|
version: u8,
|
||||||
|
banks: Vec<Bank>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Project> for ProjectFile {
|
||||||
|
fn from(project: &Project) -> Self {
|
||||||
|
Self {
|
||||||
|
version: VERSION,
|
||||||
|
banks: project.banks.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProjectFile> for Project {
|
||||||
|
fn from(file: ProjectFile) -> Self {
|
||||||
|
Self { banks: file.banks }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FileError {
|
||||||
|
Io(io::Error),
|
||||||
|
Json(serde_json::Error),
|
||||||
|
Version(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for FileError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
FileError::Io(e) => write!(f, "IO error: {e}"),
|
||||||
|
FileError::Json(e) => write!(f, "JSON error: {e}"),
|
||||||
|
FileError::Version(v) => write!(f, "Unsupported version: {v}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for FileError {
|
||||||
|
fn from(e: io::Error) -> Self {
|
||||||
|
FileError::Io(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for FileError {
|
||||||
|
fn from(e: serde_json::Error) -> Self {
|
||||||
|
FileError::Json(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(project: &Project, path: &Path) -> Result<(), FileError> {
|
||||||
|
let file = ProjectFile::from(project);
|
||||||
|
let json = serde_json::to_string_pretty(&file)?;
|
||||||
|
fs::write(path, json)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(path: &Path) -> Result<Project, FileError> {
|
||||||
|
let json = fs::read_to_string(path)?;
|
||||||
|
let file: ProjectFile = serde_json::from_str(&json)?;
|
||||||
|
if file.version > VERSION {
|
||||||
|
return Err(FileError::Version(file.version));
|
||||||
|
}
|
||||||
|
Ok(Project::from(file))
|
||||||
|
}
|
||||||
46
seq/src/link.rs
Normal file
46
seq/src/link.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use rusty_link::{AblLink, SessionState};
|
||||||
|
|
||||||
|
pub struct LinkState {
|
||||||
|
link: AblLink,
|
||||||
|
quantum: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LinkState {
|
||||||
|
pub fn new(tempo: f64, quantum: f64) -> Self {
|
||||||
|
let link = AblLink::new(tempo);
|
||||||
|
Self { link, quantum }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable(&self) {
|
||||||
|
self.link.enable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clock_micros(&self) -> i64 {
|
||||||
|
self.link.clock_micros()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query(&self) -> (f64, f64, f64, u64) {
|
||||||
|
let mut state = SessionState::new();
|
||||||
|
self.link.capture_app_session_state(&mut state);
|
||||||
|
let time = self.link.clock_micros();
|
||||||
|
let tempo = state.tempo();
|
||||||
|
let beat = state.beat_at_time(time, self.quantum);
|
||||||
|
let phase = state.phase_at_time(time, self.quantum);
|
||||||
|
let peers = self.link.num_peers();
|
||||||
|
(tempo, beat, phase, peers)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_tempo(&self, tempo: f64) {
|
||||||
|
let mut state = SessionState::new();
|
||||||
|
self.link.capture_app_session_state(&mut state);
|
||||||
|
let time = self.link.clock_micros();
|
||||||
|
state.set_tempo(tempo, time);
|
||||||
|
self.link.commit_app_session_state(&state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capture_audio_state(&self) -> SessionState {
|
||||||
|
let mut state = SessionState::new();
|
||||||
|
self.link.capture_audio_session_state(&mut state);
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
304
seq/src/main.rs
Normal file
304
seq/src/main.rs
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
mod app;
|
||||||
|
mod audio;
|
||||||
|
mod file;
|
||||||
|
mod link;
|
||||||
|
mod model;
|
||||||
|
mod page;
|
||||||
|
mod script;
|
||||||
|
mod ui;
|
||||||
|
mod views;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
|
||||||
|
use crossterm::terminal::{
|
||||||
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
|
};
|
||||||
|
use crossterm::ExecutableCommand;
|
||||||
|
use doux::Engine;
|
||||||
|
use ratatui::prelude::CrosstermBackend;
|
||||||
|
use ratatui::Terminal;
|
||||||
|
|
||||||
|
use app::{App, Focus, Modal};
|
||||||
|
use link::LinkState;
|
||||||
|
use model::Project;
|
||||||
|
use page::Page;
|
||||||
|
|
||||||
|
const TEMPO: f64 = 120.0;
|
||||||
|
const QUANTUM: f64 = 4.0;
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
let link = Arc::new(LinkState::new(TEMPO, QUANTUM));
|
||||||
|
link.enable();
|
||||||
|
|
||||||
|
let playing = Arc::new(AtomicBool::new(true));
|
||||||
|
let playback_step = Arc::new(AtomicUsize::new(0));
|
||||||
|
let event_count = Arc::new(AtomicUsize::new(0));
|
||||||
|
let playback_bank = Arc::new(AtomicUsize::new(0));
|
||||||
|
let playback_pattern = Arc::new(AtomicUsize::new(0));
|
||||||
|
let queued_bank = Arc::new(AtomicUsize::new(usize::MAX));
|
||||||
|
let queued_pattern = Arc::new(AtomicUsize::new(usize::MAX));
|
||||||
|
let mut app = App::new(TEMPO, QUANTUM);
|
||||||
|
|
||||||
|
let engine = Arc::new(Mutex::new(Engine::new(44100.0)));
|
||||||
|
let project = Arc::new(Mutex::new(Project::default()));
|
||||||
|
|
||||||
|
let (_stream, sample_rate) = audio::build_stream(
|
||||||
|
Arc::clone(&engine),
|
||||||
|
Arc::clone(&link),
|
||||||
|
Arc::clone(&playing),
|
||||||
|
Arc::clone(&project),
|
||||||
|
Arc::clone(&playback_step),
|
||||||
|
Arc::clone(&event_count),
|
||||||
|
Arc::clone(&playback_bank),
|
||||||
|
Arc::clone(&playback_pattern),
|
||||||
|
Arc::clone(&queued_bank),
|
||||||
|
Arc::clone(&queued_pattern),
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut eng = engine.lock().unwrap();
|
||||||
|
eng.sr = sample_rate;
|
||||||
|
eng.isr = 1.0 / sample_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_raw_mode()?;
|
||||||
|
io::stdout().execute(EnterAlternateScreen)?;
|
||||||
|
|
||||||
|
let backend = CrosstermBackend::new(io::stdout());
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
app.update_from_link(&link);
|
||||||
|
app.playing = playing.load(Ordering::Relaxed);
|
||||||
|
app.playback_step = playback_step.load(Ordering::Relaxed);
|
||||||
|
app.event_count = event_count.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
{
|
||||||
|
let eng = engine.lock().unwrap();
|
||||||
|
app.active_voices = eng.active_voices;
|
||||||
|
app.peak_voices = app.peak_voices.max(eng.active_voices);
|
||||||
|
app.cpu_load = eng.metrics.load.get_load();
|
||||||
|
app.schedule_depth = eng.schedule.len();
|
||||||
|
for (i, s) in app.scope.iter_mut().enumerate() {
|
||||||
|
*s = eng.output.get(i * 2).copied().unwrap_or(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.playback_bank = playback_bank.load(Ordering::Relaxed);
|
||||||
|
app.playback_pattern = playback_pattern.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
if app.queued_bank.is_some() {
|
||||||
|
queued_bank.store(app.queued_bank.unwrap(), Ordering::Relaxed);
|
||||||
|
queued_pattern.store(app.queued_pattern.unwrap(), Ordering::Relaxed);
|
||||||
|
app.queued_bank = None;
|
||||||
|
app.queued_pattern = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut proj = project.lock().unwrap();
|
||||||
|
proj.banks = app.project.banks.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.draw(|frame| ui::render(frame, &mut app))?;
|
||||||
|
|
||||||
|
if event::poll(Duration::from_millis(16))? {
|
||||||
|
if let Event::Key(key) = event::read()? {
|
||||||
|
app.clear_status();
|
||||||
|
|
||||||
|
match &mut app.modal {
|
||||||
|
Modal::ConfirmQuit => match key.code {
|
||||||
|
KeyCode::Char('y') | KeyCode::Char('Y') => break,
|
||||||
|
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
|
||||||
|
app.modal = Modal::None;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
Modal::SaveAs(path) => match key.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
|
let save_path = PathBuf::from(path.as_str());
|
||||||
|
app.modal = Modal::None;
|
||||||
|
app.save(save_path);
|
||||||
|
}
|
||||||
|
KeyCode::Esc => {
|
||||||
|
app.modal = Modal::None;
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
path.push(c);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
Modal::LoadFrom(path) => match key.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
|
let load_path = PathBuf::from(path.as_str());
|
||||||
|
app.modal = Modal::None;
|
||||||
|
app.load(load_path);
|
||||||
|
}
|
||||||
|
KeyCode::Esc => {
|
||||||
|
app.modal = Modal::None;
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
path.push(c);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
Modal::PatternPicker { ref mut cursor } => {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
|
let selected = *cursor;
|
||||||
|
app.modal = Modal::None;
|
||||||
|
app.select_edit_pattern(selected);
|
||||||
|
}
|
||||||
|
KeyCode::Esc => {
|
||||||
|
app.modal = Modal::None;
|
||||||
|
}
|
||||||
|
KeyCode::Left => {
|
||||||
|
*cursor = (*cursor + 15) % 16;
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
*cursor = (*cursor + 1) % 16;
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
*cursor = (*cursor + 12) % 16;
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
*cursor = (*cursor + 4) % 16;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Modal::BankPicker { ref mut cursor } => {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
|
let selected = *cursor;
|
||||||
|
app.modal = Modal::None;
|
||||||
|
app.select_edit_bank(selected);
|
||||||
|
}
|
||||||
|
KeyCode::Esc => {
|
||||||
|
app.modal = Modal::None;
|
||||||
|
}
|
||||||
|
KeyCode::Left => {
|
||||||
|
*cursor = (*cursor + 15) % 16;
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
*cursor = (*cursor + 1) % 16;
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
*cursor = (*cursor + 12) % 16;
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
*cursor = (*cursor + 4) % 16;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Modal::None => {
|
||||||
|
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
||||||
|
|
||||||
|
if ctrl && key.code == KeyCode::Left {
|
||||||
|
app.page.left();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ctrl && key.code == KeyCode::Right {
|
||||||
|
app.page.right();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match app.page {
|
||||||
|
Page::Main => match app.focus {
|
||||||
|
Focus::Sequencer => match key.code {
|
||||||
|
KeyCode::Char('q') => {
|
||||||
|
app.modal = Modal::ConfirmQuit;
|
||||||
|
}
|
||||||
|
KeyCode::Char(' ') => {
|
||||||
|
app.toggle_playing();
|
||||||
|
playing.store(app.playing, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
KeyCode::Tab => app.toggle_focus(),
|
||||||
|
KeyCode::Left => app.prev_step(),
|
||||||
|
KeyCode::Right => app.next_step(),
|
||||||
|
KeyCode::Up => app.step_up(),
|
||||||
|
KeyCode::Down => app.step_down(),
|
||||||
|
KeyCode::Enter => app.toggle_step(),
|
||||||
|
KeyCode::Char('p') => {
|
||||||
|
app.modal =
|
||||||
|
Modal::PatternPicker { cursor: app.edit_pattern };
|
||||||
|
}
|
||||||
|
KeyCode::Char('b') => {
|
||||||
|
app.modal = Modal::BankPicker { cursor: app.edit_bank };
|
||||||
|
}
|
||||||
|
KeyCode::Char('g') => {
|
||||||
|
app.queue_current_for_playback();
|
||||||
|
}
|
||||||
|
KeyCode::Char('s') => {
|
||||||
|
let default = app
|
||||||
|
.file_path
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.display().to_string())
|
||||||
|
.unwrap_or_else(|| "project.buboseq".to_string());
|
||||||
|
app.modal = Modal::SaveAs(default);
|
||||||
|
}
|
||||||
|
KeyCode::Char('l') => {
|
||||||
|
app.modal = Modal::LoadFrom(String::new());
|
||||||
|
}
|
||||||
|
KeyCode::Char('+') | KeyCode::Char('=') => app.tempo_up(&link),
|
||||||
|
KeyCode::Char('-') => app.tempo_down(&link),
|
||||||
|
KeyCode::Char('c') if ctrl => app.copy_step(),
|
||||||
|
KeyCode::Char('v') if ctrl => app.paste_step(),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
Focus::Editor => match key.code {
|
||||||
|
KeyCode::Tab | KeyCode::Esc => app.toggle_focus(),
|
||||||
|
KeyCode::Char('e') if ctrl => {
|
||||||
|
app.save_editor_to_step();
|
||||||
|
app.compile_current_step();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
app.editor.input(Event::Key(key));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Page::Audio => match key.code {
|
||||||
|
KeyCode::Char('q') => {
|
||||||
|
app.modal = Modal::ConfirmQuit;
|
||||||
|
}
|
||||||
|
KeyCode::Char('h') => {
|
||||||
|
engine.lock().unwrap().hush();
|
||||||
|
}
|
||||||
|
KeyCode::Char('p') => {
|
||||||
|
engine.lock().unwrap().panic();
|
||||||
|
}
|
||||||
|
KeyCode::Char('r') => {
|
||||||
|
app.peak_voices = 0;
|
||||||
|
}
|
||||||
|
KeyCode::Char('t') => {
|
||||||
|
engine.lock().unwrap().evaluate("sin 440 * 0.3");
|
||||||
|
}
|
||||||
|
KeyCode::Char(' ') => {
|
||||||
|
app.toggle_playing();
|
||||||
|
playing.store(app.playing, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_raw_mode()?;
|
||||||
|
io::stdout().execute(LeaveAlternateScreen)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
89
seq/src/model.rs
Normal file
89
seq/src/model.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Step {
|
||||||
|
pub active: bool,
|
||||||
|
pub script: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub command: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Step {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
active: true,
|
||||||
|
script: String::new(),
|
||||||
|
command: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Pattern {
|
||||||
|
pub steps: Vec<Step>,
|
||||||
|
pub length: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Pattern {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
steps: (0..16).map(|_| Step::default()).collect(),
|
||||||
|
length: 16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pattern {
|
||||||
|
pub fn step(&self, index: usize) -> Option<&Step> {
|
||||||
|
self.steps.get(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step_mut(&mut self, index: usize) -> Option<&mut Step> {
|
||||||
|
self.steps.get_mut(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn set_length(&mut self, length: usize) {
|
||||||
|
let length = length.clamp(1, 64);
|
||||||
|
while self.steps.len() < length {
|
||||||
|
self.steps.push(Step::default());
|
||||||
|
}
|
||||||
|
self.length = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Bank {
|
||||||
|
pub patterns: Vec<Pattern>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Bank {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
patterns: (0..16).map(|_| Pattern::default()).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Project {
|
||||||
|
pub banks: Vec<Bank>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Project {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
banks: (0..16).map(|_| Bank::default()).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Project {
|
||||||
|
pub fn pattern_at(&self, bank: usize, pattern: usize) -> &Pattern {
|
||||||
|
&self.banks[bank].patterns[pattern]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pattern_at_mut(&mut self, bank: usize, pattern: usize) -> &mut Pattern {
|
||||||
|
&mut self.banks[bank].patterns[pattern]
|
||||||
|
}
|
||||||
|
}
|
||||||
22
seq/src/page.rs
Normal file
22
seq/src/page.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Page {
|
||||||
|
#[default]
|
||||||
|
Main,
|
||||||
|
Audio,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Page {
|
||||||
|
pub fn left(&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Page::Main => Page::Audio,
|
||||||
|
Page::Audio => Page::Audio,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn right(&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Page::Main => Page::Main,
|
||||||
|
Page::Audio => Page::Main,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
seq/src/script.rs
Normal file
120
seq/src/script.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use rhai::{Engine, Scope};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Cmd {
|
||||||
|
pairs: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cmd {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { pairs: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with(sound: &str) -> Self {
|
||||||
|
let mut cmd = Self::new();
|
||||||
|
cmd.pairs.push(("sound".into(), sound.into()));
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, key: &str, val: &str) -> Self {
|
||||||
|
self.pairs.push((key.into(), val.into()));
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Cmd {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let parts: Vec<String> = self
|
||||||
|
.pairs
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| format!("{k}/{v}"))
|
||||||
|
.collect();
|
||||||
|
write!(f, "/{}", parts.join("/"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StepContext {
|
||||||
|
pub step: usize,
|
||||||
|
pub beat: f64,
|
||||||
|
pub bank: usize,
|
||||||
|
pub pattern: usize,
|
||||||
|
pub tempo: f64,
|
||||||
|
pub phase: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScriptEngine {
|
||||||
|
engine: Engine,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScriptEngine {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
engine.set_max_expr_depths(64, 32);
|
||||||
|
register_cmd(&mut engine);
|
||||||
|
Self { engine }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result<String, String> {
|
||||||
|
if script.trim().is_empty() {
|
||||||
|
return Err("empty script".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
scope.push("step", ctx.step as i64);
|
||||||
|
scope.push("beat", ctx.beat);
|
||||||
|
scope.push("bank", ctx.bank as i64);
|
||||||
|
scope.push("pattern", ctx.pattern as i64);
|
||||||
|
scope.push("tempo", ctx.tempo);
|
||||||
|
scope.push("phase", ctx.phase);
|
||||||
|
|
||||||
|
if let Ok(cmd) = self.engine.eval_with_scope::<Cmd>(&mut scope, script) {
|
||||||
|
return Ok(cmd.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.engine
|
||||||
|
.eval_with_scope::<String>(&mut scope, script)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_cmd(engine: &mut Engine) {
|
||||||
|
engine.register_type_with_name::<Cmd>("Cmd");
|
||||||
|
engine.register_fn("sound", Cmd::with);
|
||||||
|
|
||||||
|
macro_rules! reg_both {
|
||||||
|
($($name:expr),*) => {
|
||||||
|
$(
|
||||||
|
engine.register_fn($name, |c: &mut Cmd, v: f64| c.set($name, &v.to_string()));
|
||||||
|
engine.register_fn($name, |c: &mut Cmd, v: i64| c.set($name, &v.to_string()));
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
reg_both!(
|
||||||
|
"time", "repeat", "dur", "gate",
|
||||||
|
"freq", "detune", "speed", "glide",
|
||||||
|
"pw", "spread", "mult", "warp", "mirror", "harmonics", "timbre", "morph", "begin", "end",
|
||||||
|
"gain", "postgain", "velocity", "pan",
|
||||||
|
"attack", "decay", "sustain", "release",
|
||||||
|
"lpf", "lpq", "lpe", "lpa", "lpd", "lps", "lpr",
|
||||||
|
"hpf", "hpq", "hpe", "hpa", "hpd", "hps", "hpr",
|
||||||
|
"bpf", "bpq", "bpe", "bpa", "bpd", "bps", "bpr",
|
||||||
|
"penv", "patt", "pdec", "psus", "prel",
|
||||||
|
"vib", "vibmod",
|
||||||
|
"fm", "fmh", "fme", "fma", "fmd", "fms", "fmr",
|
||||||
|
"am", "amdepth",
|
||||||
|
"rm", "rmdepth",
|
||||||
|
"phaser", "phaserdepth", "phasersweep", "phasercenter",
|
||||||
|
"flanger", "flangerdepth", "flangerfeedback",
|
||||||
|
"chorus", "chorusdepth", "chorusdelay",
|
||||||
|
"comb", "combfreq", "combfeedback", "combdamp",
|
||||||
|
"coarse", "crush", "fold", "wrap", "distort", "distortvol",
|
||||||
|
"delay", "delaytime", "delayfeedback",
|
||||||
|
"verb", "verbdecay", "verbdamp", "verbpredelay", "verbdiff",
|
||||||
|
"voice", "orbit", "note", "size", "n", "cut"
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn("reset", |c: &mut Cmd, v: bool| {
|
||||||
|
c.set("reset", if v { "1" } else { "0" })
|
||||||
|
});
|
||||||
|
}
|
||||||
312
seq/src/ui.rs
Normal file
312
seq/src/ui.rs
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||||
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
|
use ratatui::text::{Line, Span};
|
||||||
|
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
|
||||||
|
use ratatui::Frame;
|
||||||
|
|
||||||
|
use crate::app::{App, Modal};
|
||||||
|
use crate::page::Page;
|
||||||
|
use crate::views::{audio_view, main_view};
|
||||||
|
|
||||||
|
pub fn render(frame: &mut Frame, app: &mut App) {
|
||||||
|
let [header_area, scope_area, body_area, footer_area] = Layout::vertical([
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(2),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(3),
|
||||||
|
])
|
||||||
|
.areas(frame.area());
|
||||||
|
|
||||||
|
render_header(frame, app, header_area);
|
||||||
|
render_scope(frame, app, scope_area);
|
||||||
|
|
||||||
|
match app.page {
|
||||||
|
Page::Main => main_view::render(frame, app, body_area),
|
||||||
|
Page::Audio => audio_view::render(frame, app, body_area),
|
||||||
|
}
|
||||||
|
|
||||||
|
render_footer(frame, app, footer_area);
|
||||||
|
render_modal(frame, app);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_scope(frame: &mut Frame, app: &App, area: Rect) {
|
||||||
|
let scope_chars: String = app
|
||||||
|
.scope
|
||||||
|
.iter()
|
||||||
|
.map(|&s| {
|
||||||
|
let level = (s.abs() * 8.0).min(7.0) as usize;
|
||||||
|
['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'][level]
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let scope = Paragraph::new(scope_chars).style(Style::new().fg(Color::Green));
|
||||||
|
|
||||||
|
frame.render_widget(scope, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_header(frame: &mut Frame, app: &App, area: Rect) {
|
||||||
|
let play_symbol = if app.playing { "▶" } else { "■" };
|
||||||
|
let play_color = if app.playing {
|
||||||
|
Color::Green
|
||||||
|
} else {
|
||||||
|
Color::Red
|
||||||
|
};
|
||||||
|
|
||||||
|
let cpu_pct = (app.cpu_load * 100.0).min(100.0);
|
||||||
|
let cpu_color = if cpu_pct > 80.0 {
|
||||||
|
Color::Red
|
||||||
|
} else if cpu_pct > 50.0 {
|
||||||
|
Color::Yellow
|
||||||
|
} else {
|
||||||
|
Color::Green
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut spans = vec![
|
||||||
|
Span::styled("EDIT ", Style::new().fg(Color::Cyan)),
|
||||||
|
Span::styled(
|
||||||
|
format!("B{:02}:P{:02}", app.edit_bank + 1, app.edit_pattern + 1),
|
||||||
|
Style::new().fg(Color::Cyan).add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled("PLAY ", Style::new().fg(play_color)),
|
||||||
|
Span::styled(
|
||||||
|
format!(
|
||||||
|
"B{:02}:P{:02} {}",
|
||||||
|
app.playback_bank + 1,
|
||||||
|
app.playback_pattern + 1,
|
||||||
|
play_symbol
|
||||||
|
),
|
||||||
|
Style::new().fg(play_color).add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
if app.queued_bank.is_some() {
|
||||||
|
spans.push(Span::raw(" "));
|
||||||
|
spans.push(Span::styled("QUEUE ", Style::new().fg(Color::Yellow)));
|
||||||
|
spans.push(Span::styled(
|
||||||
|
format!(
|
||||||
|
"B{:02}:P{:02}",
|
||||||
|
app.queued_bank.unwrap() + 1,
|
||||||
|
app.queued_pattern.unwrap() + 1
|
||||||
|
),
|
||||||
|
Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
spans.extend([
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(format!("{:.1} BPM", app.tempo), Style::new().fg(Color::Magenta)),
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(format!("CPU:{cpu_pct:.0}%"), Style::new().fg(cpu_color)),
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(format!("V:{}", app.active_voices), Style::new().fg(Color::Cyan)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let header = Paragraph::new(Line::from(spans))
|
||||||
|
.block(Block::default().borders(Borders::ALL).title("seq"));
|
||||||
|
|
||||||
|
frame.render_widget(header, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
|
||||||
|
let page_indicator = match app.page {
|
||||||
|
Page::Main => "[MAIN] ",
|
||||||
|
Page::Audio => "[AUDIO] ",
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = if let Some(ref msg) = app.status_message {
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(page_indicator, Style::new().fg(Color::White).add_modifier(Modifier::DIM)),
|
||||||
|
Span::styled(msg.clone(), Style::new().fg(Color::Yellow)),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
match app.page {
|
||||||
|
Page::Main => Line::from(vec![
|
||||||
|
Span::styled(page_indicator, Style::new().fg(Color::White).add_modifier(Modifier::DIM)),
|
||||||
|
Span::styled("←→↑↓", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":nav "),
|
||||||
|
Span::styled("p", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":pat "),
|
||||||
|
Span::styled("b", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":bank "),
|
||||||
|
Span::styled("g", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":go "),
|
||||||
|
Span::styled("Enter", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":toggle "),
|
||||||
|
Span::styled("Tab", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":focus "),
|
||||||
|
Span::styled("s/l", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":save/load"),
|
||||||
|
]),
|
||||||
|
Page::Audio => Line::from(vec![
|
||||||
|
Span::styled(page_indicator, Style::new().fg(Color::White).add_modifier(Modifier::DIM)),
|
||||||
|
Span::styled("q", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":quit "),
|
||||||
|
Span::styled("h", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":hush "),
|
||||||
|
Span::styled("p", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":panic "),
|
||||||
|
Span::styled("r", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":reset "),
|
||||||
|
Span::styled("t", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":test "),
|
||||||
|
Span::styled("C-←→", Style::new().fg(Color::Yellow)),
|
||||||
|
Span::raw(":page"),
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let footer = Paragraph::new(content).block(Block::default().borders(Borders::ALL));
|
||||||
|
frame.render_widget(footer, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {
|
||||||
|
let x = area.x + area.width.saturating_sub(width) / 2;
|
||||||
|
let y = area.y + area.height.saturating_sub(height) / 2;
|
||||||
|
Rect::new(x, y, width.min(area.width), height.min(area.height))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_modal(frame: &mut Frame, app: &App) {
|
||||||
|
let term = frame.area();
|
||||||
|
match &app.modal {
|
||||||
|
Modal::None => {}
|
||||||
|
Modal::ConfirmQuit => {
|
||||||
|
let width = 30.min(term.width.saturating_sub(4));
|
||||||
|
let height = 5.min(term.height.saturating_sub(4));
|
||||||
|
let area = centered_rect(width, height, term);
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
let modal = Paragraph::new(Line::from("Quit? (y/n)"))
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Confirm")
|
||||||
|
.border_style(Style::new().fg(Color::Yellow)),
|
||||||
|
);
|
||||||
|
frame.render_widget(modal, area);
|
||||||
|
}
|
||||||
|
Modal::SaveAs(path) => {
|
||||||
|
let width = (term.width * 60 / 100).clamp(40, 70).min(term.width.saturating_sub(4));
|
||||||
|
let height = 5.min(term.height.saturating_sub(4));
|
||||||
|
let area = centered_rect(width, height, term);
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
let modal = Paragraph::new(Line::from(vec![
|
||||||
|
Span::raw("> "),
|
||||||
|
Span::styled(path, Style::new().fg(Color::Cyan)),
|
||||||
|
Span::styled("█", Style::new().fg(Color::White)),
|
||||||
|
]))
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Save As (Enter to confirm, Esc to cancel)")
|
||||||
|
.border_style(Style::new().fg(Color::Green)),
|
||||||
|
);
|
||||||
|
frame.render_widget(modal, area);
|
||||||
|
}
|
||||||
|
Modal::LoadFrom(path) => {
|
||||||
|
let width = (term.width * 60 / 100).clamp(40, 70).min(term.width.saturating_sub(4));
|
||||||
|
let height = 5.min(term.height.saturating_sub(4));
|
||||||
|
let area = centered_rect(width, height, term);
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
let modal = Paragraph::new(Line::from(vec![
|
||||||
|
Span::raw("> "),
|
||||||
|
Span::styled(path, Style::new().fg(Color::Cyan)),
|
||||||
|
Span::styled("█", Style::new().fg(Color::White)),
|
||||||
|
]))
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Load From (Enter to confirm, Esc to cancel)")
|
||||||
|
.border_style(Style::new().fg(Color::Blue)),
|
||||||
|
);
|
||||||
|
frame.render_widget(modal, area);
|
||||||
|
}
|
||||||
|
Modal::PatternPicker { cursor } => {
|
||||||
|
render_picker_modal(
|
||||||
|
frame,
|
||||||
|
&format!("Select Pattern (Bank {:02})", app.edit_bank + 1),
|
||||||
|
*cursor,
|
||||||
|
app.edit_pattern,
|
||||||
|
app.playback_pattern,
|
||||||
|
app.edit_bank == app.playback_bank,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Modal::BankPicker { cursor } => {
|
||||||
|
render_picker_modal(
|
||||||
|
frame,
|
||||||
|
"Select Bank",
|
||||||
|
*cursor,
|
||||||
|
app.edit_bank,
|
||||||
|
app.playback_bank,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_picker_modal(
|
||||||
|
frame: &mut Frame,
|
||||||
|
title: &str,
|
||||||
|
cursor: usize,
|
||||||
|
edit_pos: usize,
|
||||||
|
play_pos: usize,
|
||||||
|
show_play: bool,
|
||||||
|
) {
|
||||||
|
let term = frame.area();
|
||||||
|
let width = 30.min(term.width.saturating_sub(4));
|
||||||
|
let height = 10.min(term.height.saturating_sub(4));
|
||||||
|
let area = centered_rect(width, height, term);
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(title)
|
||||||
|
.border_style(Style::new().fg(Color::Rgb(100, 160, 180)));
|
||||||
|
|
||||||
|
let inner = block.inner(area);
|
||||||
|
frame.render_widget(block, area);
|
||||||
|
|
||||||
|
let rows = Layout::vertical([
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
])
|
||||||
|
.split(inner);
|
||||||
|
|
||||||
|
for row in 0..4 {
|
||||||
|
let mut spans = Vec::new();
|
||||||
|
for col in 0..4 {
|
||||||
|
let idx = row * 4 + col;
|
||||||
|
let num = format!(" {:02} ", idx + 1);
|
||||||
|
|
||||||
|
let style = if idx == cursor {
|
||||||
|
Style::new().bg(Color::Cyan).fg(Color::Black)
|
||||||
|
} else if idx == edit_pos {
|
||||||
|
Style::new().fg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||||
|
} else if show_play && idx == play_pos {
|
||||||
|
Style::new().fg(Color::Green).add_modifier(Modifier::BOLD)
|
||||||
|
} else {
|
||||||
|
Style::new().fg(Color::White)
|
||||||
|
};
|
||||||
|
|
||||||
|
spans.push(Span::styled(num, style));
|
||||||
|
if col < 3 {
|
||||||
|
spans.push(Span::raw(" "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame.render_widget(Paragraph::new(Line::from(spans)), rows[row]);
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.render_widget(
|
||||||
|
Paragraph::new(Line::from(vec![
|
||||||
|
Span::styled("[E]", Style::new().fg(Color::Cyan)),
|
||||||
|
Span::raw("=edit "),
|
||||||
|
Span::styled("[P]", Style::new().fg(Color::Green)),
|
||||||
|
Span::raw("=play"),
|
||||||
|
])),
|
||||||
|
rows[5],
|
||||||
|
);
|
||||||
|
}
|
||||||
81
seq/src/views/audio_view.rs
Normal file
81
seq/src/views/audio_view.rs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
use ratatui::layout::{Constraint, Layout, Rect};
|
||||||
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
|
use ratatui::text::{Line, Span};
|
||||||
|
use ratatui::widgets::{Block, Borders, Gauge, Paragraph};
|
||||||
|
use ratatui::Frame;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
|
||||||
|
pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
||||||
|
render_stats(frame, app, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_stats(frame: &mut Frame, app: &App, area: Rect) {
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Engine Stats")
|
||||||
|
.border_style(Style::new().fg(Color::Cyan));
|
||||||
|
|
||||||
|
let inner = block.inner(area);
|
||||||
|
frame.render_widget(block, area);
|
||||||
|
|
||||||
|
let [cpu_area, voices_area, extra_area] = Layout::vertical([
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(2),
|
||||||
|
Constraint::Min(1),
|
||||||
|
])
|
||||||
|
.areas(inner);
|
||||||
|
|
||||||
|
let cpu_pct = (app.cpu_load * 100.0).min(100.0);
|
||||||
|
let cpu_color = if cpu_pct > 80.0 {
|
||||||
|
Color::Red
|
||||||
|
} else if cpu_pct > 50.0 {
|
||||||
|
Color::Yellow
|
||||||
|
} else {
|
||||||
|
Color::Green
|
||||||
|
};
|
||||||
|
|
||||||
|
let gauge = Gauge::default()
|
||||||
|
.block(Block::default().title("CPU"))
|
||||||
|
.gauge_style(Style::new().fg(cpu_color).bg(Color::DarkGray))
|
||||||
|
.percent(cpu_pct as u16)
|
||||||
|
.label(format!("{cpu_pct:.1}%"));
|
||||||
|
|
||||||
|
frame.render_widget(gauge, cpu_area);
|
||||||
|
|
||||||
|
let voice_color = if app.active_voices > 24 {
|
||||||
|
Color::Red
|
||||||
|
} else if app.active_voices > 16 {
|
||||||
|
Color::Yellow
|
||||||
|
} else {
|
||||||
|
Color::Cyan
|
||||||
|
};
|
||||||
|
|
||||||
|
let voices = Paragraph::new(Line::from(vec![
|
||||||
|
Span::raw("Active: "),
|
||||||
|
Span::styled(
|
||||||
|
format!("{:3}", app.active_voices),
|
||||||
|
Style::new().fg(voice_color).add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
|
Span::raw(" Peak: "),
|
||||||
|
Span::styled(
|
||||||
|
format!("{:3}", app.peak_voices),
|
||||||
|
Style::new().fg(Color::Yellow),
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
|
||||||
|
frame.render_widget(voices, voices_area);
|
||||||
|
|
||||||
|
let extra = Paragraph::new(vec![
|
||||||
|
Line::from(vec![
|
||||||
|
Span::raw("Schedule: "),
|
||||||
|
Span::styled(format!("{}", app.schedule_depth), Style::new().fg(Color::White)),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::raw("Pool: "),
|
||||||
|
Span::styled(format!("{:.1} MB", app.sample_pool_mb), Style::new().fg(Color::White)),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
frame.render_widget(extra, extra_area);
|
||||||
|
}
|
||||||
146
seq/src/views/main_view.rs
Normal file
146
seq/src/views/main_view.rs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||||
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
|
use ratatui::widgets::{Block, Borders, Paragraph};
|
||||||
|
use ratatui::Frame;
|
||||||
|
|
||||||
|
use crate::app::{App, Focus};
|
||||||
|
|
||||||
|
pub fn render(frame: &mut Frame, app: &mut App, area: Rect) {
|
||||||
|
let [seq_area, editor_area] =
|
||||||
|
Layout::vertical([Constraint::Length(9), Constraint::Fill(1)]).areas(area);
|
||||||
|
|
||||||
|
render_sequencer(frame, app, seq_area);
|
||||||
|
render_editor(frame, app, editor_area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_sequencer(frame: &mut Frame, app: &App, area: Rect) {
|
||||||
|
let focus_indicator = if app.focus == Focus::Sequencer {
|
||||||
|
"*"
|
||||||
|
} else {
|
||||||
|
" "
|
||||||
|
};
|
||||||
|
|
||||||
|
let border_style = if app.focus == Focus::Sequencer {
|
||||||
|
Style::new().fg(Color::Rgb(100, 160, 180))
|
||||||
|
} else {
|
||||||
|
Style::new().fg(Color::Rgb(70, 75, 85))
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(border_style)
|
||||||
|
.title(format!("Sequencer{focus_indicator}"));
|
||||||
|
|
||||||
|
let inner = block.inner(area);
|
||||||
|
frame.render_widget(block, area);
|
||||||
|
|
||||||
|
if inner.width < 50 {
|
||||||
|
let msg = Paragraph::new("Terminal too narrow")
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.style(Style::new().fg(Color::Rgb(120, 125, 135)));
|
||||||
|
frame.render_widget(msg, inner);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rows = Layout::vertical([
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
])
|
||||||
|
.split(inner);
|
||||||
|
|
||||||
|
let row_areas = [rows[0], rows[2]];
|
||||||
|
|
||||||
|
for (row_idx, row_area) in row_areas.iter().enumerate() {
|
||||||
|
let col_constraints = [
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(2),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
];
|
||||||
|
let cols = Layout::horizontal(col_constraints).split(*row_area);
|
||||||
|
|
||||||
|
let tile_indices = [0, 2, 4, 6, 8, 10, 12, 14];
|
||||||
|
for (col_idx, &col_layout_idx) in tile_indices.iter().enumerate() {
|
||||||
|
let step_idx = row_idx * 8 + col_idx;
|
||||||
|
render_tile(frame, cols[col_layout_idx], app, step_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_tile(frame: &mut Frame, area: Rect, app: &App, step_idx: usize) {
|
||||||
|
let pattern = app.current_edit_pattern();
|
||||||
|
let step = pattern.step(step_idx);
|
||||||
|
let is_active = step.map(|s| s.active).unwrap_or(false);
|
||||||
|
let is_selected = step_idx == app.current_step;
|
||||||
|
|
||||||
|
let same_pattern =
|
||||||
|
app.edit_bank == app.playback_bank && app.edit_pattern == app.playback_pattern;
|
||||||
|
let is_playing = app.playing && same_pattern && step_idx == app.playback_step;
|
||||||
|
|
||||||
|
let (bg, fg) = match (is_playing, is_active, is_selected) {
|
||||||
|
(true, true, _) => (Color::Rgb(195, 85, 65), Color::White),
|
||||||
|
(true, false, _) => (Color::Rgb(180, 120, 45), Color::Black),
|
||||||
|
(false, true, true) => (Color::Rgb(55, 128, 115), Color::White),
|
||||||
|
(false, true, false) => (Color::Rgb(45, 106, 95), Color::White),
|
||||||
|
(false, false, true) => (Color::Rgb(59, 91, 138), Color::White),
|
||||||
|
(false, false, false) => (Color::Rgb(45, 48, 55), Color::Rgb(120, 125, 135)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let symbol = if is_playing {
|
||||||
|
"▶".to_string()
|
||||||
|
} else {
|
||||||
|
format!("{:02}", step_idx + 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
let tile = Paragraph::new(symbol)
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.style(Style::new().bg(bg).fg(fg).add_modifier(Modifier::BOLD));
|
||||||
|
|
||||||
|
frame.render_widget(tile, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_editor(frame: &mut Frame, app: &mut App, area: Rect) {
|
||||||
|
let focus_indicator = if app.focus == Focus::Editor {
|
||||||
|
"*"
|
||||||
|
} else {
|
||||||
|
" "
|
||||||
|
};
|
||||||
|
|
||||||
|
let border_style = if app.is_flashing() {
|
||||||
|
Style::new().fg(Color::Green)
|
||||||
|
} else if app.focus == Focus::Editor {
|
||||||
|
Style::new().fg(Color::Rgb(100, 160, 180))
|
||||||
|
} else {
|
||||||
|
Style::new().fg(Color::Rgb(70, 75, 85))
|
||||||
|
};
|
||||||
|
|
||||||
|
let step_num = app.current_step + 1;
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(border_style)
|
||||||
|
.title(format!("Step {step_num:02} Script{focus_indicator}"));
|
||||||
|
|
||||||
|
let inner = block.inner(area);
|
||||||
|
frame.render_widget(block, area);
|
||||||
|
|
||||||
|
let cursor_style = if app.focus == Focus::Editor {
|
||||||
|
Style::new().bg(Color::White).fg(Color::Black)
|
||||||
|
} else {
|
||||||
|
Style::default()
|
||||||
|
};
|
||||||
|
app.editor.set_cursor_style(cursor_style);
|
||||||
|
|
||||||
|
frame.render_widget(&app.editor, inner);
|
||||||
|
}
|
||||||
2
seq/src/views/mod.rs
Normal file
2
seq/src/views/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod audio_view;
|
||||||
|
pub mod main_view;
|
||||||
@@ -646,6 +646,9 @@ impl Engine {
|
|||||||
.schedule_depth
|
.schedule_depth
|
||||||
.store(self.schedule.len() as u32, Ordering::Relaxed);
|
.store(self.schedule.len() as u32, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let copy_len = output.len().min(self.output.len());
|
||||||
|
self.output[..copy_len].copy_from_slice(&output[..copy_len]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dsp(&mut self) {
|
pub fn dsp(&mut self) {
|
||||||
|
|||||||
17733
test.buboseq
Normal file
17733
test.buboseq
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user