mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-08-07 14:06:57 +02:00
move port and midi to tengri
This commit is contained in:
parent
711e4fe6a9
commit
8144d75abe
26 changed files with 1520 additions and 1977 deletions
214
Cargo.lock
generated
214
Cargo.lock
generated
|
|
@ -723,6 +723,7 @@ dependencies = [
|
||||||
"dizzle_proc",
|
"dizzle_proc",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"konst",
|
"konst",
|
||||||
|
"peg",
|
||||||
"thiserror 2.0.19",
|
"thiserror 2.0.19",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -875,6 +876,15 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent-sys"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.33"
|
version = "0.3.33"
|
||||||
|
|
@ -1283,6 +1293,26 @@ dependencies = [
|
||||||
"rustversion",
|
"rustversion",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"inotify-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify-sys"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instability"
|
name = "instability"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
|
|
@ -1464,6 +1494,26 @@ version = "0.3.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07"
|
checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5"
|
||||||
|
dependencies = [
|
||||||
|
"kqueue-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue-sys"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
@ -1673,6 +1723,45 @@ dependencies = [
|
||||||
"jni-sys 0.3.1",
|
"jni-sys 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify"
|
||||||
|
version = "8.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"fsevent-sys",
|
||||||
|
"inotify",
|
||||||
|
"kqueue",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"mio",
|
||||||
|
"notify-types",
|
||||||
|
"walkdir",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-debouncer-mini"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17849edfaabd9a5fef1c606d99cfc615a8e99f7ac4366406d86c7942a3184cf2"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"notify",
|
||||||
|
"notify-types",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-types"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
|
@ -2051,6 +2140,33 @@ version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "peg"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0aad070be5b63aa72103f2fcdd70a83adbd5e90112ce5b574171ff1c65501773"
|
||||||
|
dependencies = [
|
||||||
|
"peg-macros",
|
||||||
|
"peg-runtime",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "peg-macros"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddd8ef6825cae95355031ae26a99b616a2a21f22ba2de0197c43dfb05acbe7ee"
|
||||||
|
dependencies = [
|
||||||
|
"peg-runtime",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "peg-runtime"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7011d97b484a5ebdc4b1fdb3b12d5e4bbbea56e9d22b688f2e79e04b65a7d8a6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
@ -3035,7 +3151,8 @@ dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"konst",
|
"konst",
|
||||||
"livi",
|
"livi",
|
||||||
"midly",
|
"notify",
|
||||||
|
"notify-debouncer-mini",
|
||||||
"palette",
|
"palette",
|
||||||
"proptest",
|
"proptest",
|
||||||
"proptest-derive",
|
"proptest-derive",
|
||||||
|
|
@ -3074,6 +3191,7 @@ dependencies = [
|
||||||
"crossterm 0.29.0",
|
"crossterm 0.29.0",
|
||||||
"dizzle",
|
"dizzle",
|
||||||
"jack",
|
"jack",
|
||||||
|
"midly",
|
||||||
"palette",
|
"palette",
|
||||||
"quanta",
|
"quanta",
|
||||||
"rand 0.8.7",
|
"rand 0.8.7",
|
||||||
|
|
@ -3638,7 +3756,7 @@ version = "0.52.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3647,7 +3765,16 @@ version = "0.59.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets",
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.60.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.53.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3665,14 +3792,31 @@ version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm 0.52.6",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc 0.52.6",
|
||||||
"windows_i686_gnu",
|
"windows_i686_gnu 0.52.6",
|
||||||
"windows_i686_gnullvm",
|
"windows_i686_gnullvm 0.52.6",
|
||||||
"windows_i686_msvc",
|
"windows_i686_msvc 0.52.6",
|
||||||
"windows_x86_64_gnu",
|
"windows_x86_64_gnu 0.52.6",
|
||||||
"windows_x86_64_gnullvm",
|
"windows_x86_64_gnullvm 0.52.6",
|
||||||
"windows_x86_64_msvc",
|
"windows_x86_64_msvc 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.53.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
"windows_aarch64_gnullvm 0.53.1",
|
||||||
|
"windows_aarch64_msvc 0.53.1",
|
||||||
|
"windows_i686_gnu 0.53.1",
|
||||||
|
"windows_i686_gnullvm 0.53.1",
|
||||||
|
"windows_i686_msvc 0.53.1",
|
||||||
|
"windows_x86_64_gnu 0.53.1",
|
||||||
|
"windows_x86_64_gnullvm 0.53.1",
|
||||||
|
"windows_x86_64_msvc 0.53.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3681,48 +3825,96 @@ version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnullvm"
|
name = "windows_i686_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winit"
|
name = "winit"
|
||||||
version = "0.30.13"
|
version = "0.30.13"
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ backtrace = { version = "0.3.72" }
|
||||||
builder-pattern = { version = "0.4.2" }
|
builder-pattern = { version = "0.4.2" }
|
||||||
bumpalo = { version = "3.19.0" }
|
bumpalo = { version = "3.19.0" }
|
||||||
konst = { version = "0.3.16", features = [ "rust_1_83" ] }
|
konst = { version = "0.3.16", features = [ "rust_1_83" ] }
|
||||||
midly = { version = "0.5" }
|
|
||||||
palette = { version = "0.7.6", features = [ "random" ] }
|
palette = { version = "0.7.6", features = [ "random" ] }
|
||||||
quanta = { version = "0.12.3" }
|
quanta = { version = "0.12.3" }
|
||||||
rand = { version = "0.8.5" }
|
rand = { version = "0.8.5" }
|
||||||
|
|
@ -36,6 +35,8 @@ winit = { optional = true, version = "0.30.4", features = [ "x11" ]}
|
||||||
wavers = { optional = true, version = "1.4.3" }
|
wavers = { optional = true, version = "1.4.3" }
|
||||||
uuid = { optional = true, version = "1.10.0", features = [ "v4" ] }
|
uuid = { optional = true, version = "1.10.0", features = [ "v4" ] }
|
||||||
gtk = { optional = true, version = "0.18.1" }
|
gtk = { optional = true, version = "0.18.1" }
|
||||||
|
notify = "8.2.0"
|
||||||
|
notify-debouncer-mini = "0.7.0"
|
||||||
|
|
||||||
#once_cell = "1.19.0"
|
#once_cell = "1.19.0"
|
||||||
#no_deadlocks = "1.3.2"
|
#no_deadlocks = "1.3.2"
|
||||||
|
|
|
||||||
2
Justfile
2
Justfile
|
|
@ -1,4 +1,4 @@
|
||||||
export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold"
|
#export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold"
|
||||||
export RUST_BACKTRACE := "1"
|
export RUST_BACKTRACE := "1"
|
||||||
|
|
||||||
default +ARGS="new":
|
default +ARGS="new":
|
||||||
|
|
|
||||||
340
src/app.rs
340
src/app.rs
|
|
@ -1,11 +1,25 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
pub mod audio; #[allow(unused)] pub use self::audio::*;
|
pub mod audio; #[allow(unused)] pub use self::audio::*;
|
||||||
pub mod bind; pub use self::bind::*;
|
pub mod bind; pub use self::bind::*;
|
||||||
pub mod cli; pub use self::cli::*;
|
|
||||||
pub mod config; pub use self::config::*;
|
pub mod config; pub use self::config::*;
|
||||||
pub mod draw; pub use self::draw::*;
|
pub mod draw; pub use self::draw::*;
|
||||||
pub mod size; pub use self::size::*;
|
pub mod size; pub use self::size::*;
|
||||||
|
primitive!(u8: try_to_u8);
|
||||||
|
primitive!(u16: try_to_u16);
|
||||||
|
primitive!(usize: try_to_usize);
|
||||||
|
primitive!(isize: try_to_isize);
|
||||||
|
impl_has!(Clock: |self: App|self.project.clock);
|
||||||
|
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||||
|
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||||
|
impl_has!(Dialog: |self: App|self.dialog);
|
||||||
|
impl_has!(Jack<'static>: |self: App|self.jack);
|
||||||
|
impl_has!(Pool: |self: App|self.pool);
|
||||||
|
impl_has!(Selection: |self: App|self.project.selection);
|
||||||
|
impl_as_ref!(Vec<Scene>: |self: App|self.project.as_ref());
|
||||||
|
impl_as_mut!(Vec<Scene>: |self: App|self.project.as_mut());
|
||||||
|
impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt());
|
||||||
|
impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt());
|
||||||
|
impl_has_clips!( |self: App|self.pool.clips);
|
||||||
/// Total application state.
|
/// Total application state.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -33,16 +47,16 @@ pub mod size; pub use self::size::*;
|
||||||
#[namespace(u8)]
|
#[namespace(u8)]
|
||||||
#[namespace(isize)]
|
#[namespace(isize)]
|
||||||
#[namespace(ItemTheme)]
|
#[namespace(ItemTheme)]
|
||||||
#[namespace(Arc<str> get_arc_str)]
|
#[namespace(Arc<str> App::get_arc_str)]
|
||||||
#[namespace(u16 get_u16)]
|
#[namespace(u16 App::get_u16)]
|
||||||
#[namespace(usize get_usize)]
|
#[namespace(usize App::get_usize)]
|
||||||
#[namespace(bool get_bool)]
|
#[namespace(bool App::get_bool)]
|
||||||
#[namespace(Selection get_selection)]
|
#[namespace(Selection App::get_selection)]
|
||||||
#[namespace(Color get_color)]
|
#[namespace(Color App::get_color)]
|
||||||
#[namespace(Option<u7> get_opt_u7)]
|
#[namespace(Option<u7> App::get_opt_u7)]
|
||||||
#[namespace(Option<u16> get_opt_u16)]
|
#[namespace(Option<u16> App::get_opt_u16)]
|
||||||
#[namespace(Option<usize> get_opt_usize)]
|
#[namespace(Option<usize> App::get_opt_usize)]
|
||||||
#[namespace(Option<Arc<RwLock<MidiClip>>> get_clip)]
|
#[namespace(Option<Arc<RwLock<MidiClip>>> App::get_clip)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
/// Base color.
|
/// Base color.
|
||||||
pub color: ItemTheme,
|
pub color: ItemTheme,
|
||||||
|
|
@ -67,128 +81,7 @@ pub struct App {
|
||||||
/// Error, if any
|
/// Error, if any
|
||||||
pub error: Arc<RwLock<Option<Arc<str>>>>
|
pub error: Arc<RwLock<Option<Arc<str>>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_arc_str (_state: &App, src: impl Language) -> Perhaps<Arc<str>> {
|
|
||||||
Ok(src.src()?.map(|x|x.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_u16 (state: &App, src: impl Language) -> Perhaps<u16> {
|
|
||||||
Ok(Some(match src.word()? {
|
|
||||||
Some(":w/sidebar") => state.project.w_sidebar(state.editor().is_some()),
|
|
||||||
Some(":h/sample-detail") => 6.max(state.size.h() as u16 * 3 / 9),
|
|
||||||
_ => return try_to_u16(src)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_usize (state: &App, src: impl Language) -> Perhaps<usize> {
|
|
||||||
Ok(Some(match src.word()? {
|
|
||||||
Some(":scene-count") => state.scenes().len(),
|
|
||||||
Some(":track-count") => state.tracks().len(),
|
|
||||||
Some(":device-kind") => state.dialog.device_kind().unwrap_or(0),
|
|
||||||
Some(":device-kind/next") => state.dialog.device_kind_next().unwrap_or(0),
|
|
||||||
Some(":device-kind/prev") => state.dialog.device_kind_prev().unwrap_or(0),
|
|
||||||
_ => return try_to_usize(src)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_bool (state: &App, src: impl Language) -> Perhaps<bool> {
|
|
||||||
src.word()?.map(|word|Ok(match word {
|
|
||||||
"Y" => true,
|
|
||||||
"N" => false,
|
|
||||||
":mode/editor" => state.project.editor.is_some(),
|
|
||||||
":focused/dialog" => !matches!(state.dialog, Dialog::None),
|
|
||||||
":focused/message" => matches!(state.dialog, Dialog::Message(..)),
|
|
||||||
":focused/add_device" => matches!(state.dialog, Dialog::Device(..)),
|
|
||||||
":focused/browser" => state.dialog.browser().is_some(),
|
|
||||||
":focused/pool/import" => matches!(state.pool.mode, Some(PoolMode::Import(..))),
|
|
||||||
":focused/pool/export" => matches!(state.pool.mode, Some(PoolMode::Export(..))),
|
|
||||||
":focused/pool/rename" => matches!(state.pool.mode, Some(PoolMode::Rename(..))),
|
|
||||||
":focused/pool/length" => matches!(state.pool.mode, Some(PoolMode::Length(..))),
|
|
||||||
":focused/clip" => !state.editor_focused() && matches!(state.selection(), Selection::TrackClip{..}),
|
|
||||||
":focused/track" => !state.editor_focused() && matches!(state.selection(), Selection::Track(..)),
|
|
||||||
":focused/scene" => !state.editor_focused() && matches!(state.selection(), Selection::Scene(..)),
|
|
||||||
":focused/mix" => !state.editor_focused() && matches!(state.selection(), Selection::Mix),
|
|
||||||
_ => return Err(format!("not bool: {word}").into())
|
|
||||||
})).transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_selection (state: &App, src: impl Language) -> Perhaps<Selection> {
|
|
||||||
src.word()?.map(|word|Ok(match word {
|
|
||||||
":select/scene" => state.selection().select_scene(state.tracks().len()),
|
|
||||||
":select/scene/next" => state.selection().select_scene_next(state.scenes().len()),
|
|
||||||
":select/scene/prev" => state.selection().select_scene_prev(),
|
|
||||||
":select/track" => state.selection().select_track(state.tracks().len()),
|
|
||||||
":select/track/next" => state.selection().select_track_next(state.tracks().len()),
|
|
||||||
":select/track/prev" => state.selection().select_track_prev(),
|
|
||||||
_ => return Err(format!("not selection: {word}").into())
|
|
||||||
})).transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_color (_state: &App, src: impl Language) -> Perhaps<Color> {
|
|
||||||
Ok(if let Ok(Some(expr)) = src.expr() {
|
|
||||||
match expr.head()? {
|
|
||||||
Some("g") => {
|
|
||||||
let n = try_to_u8(expr.tail())?.ok_or::<Box<dyn Error>>(
|
|
||||||
format!("not gray: {expr}").into()
|
|
||||||
)?;
|
|
||||||
Some(Color::Rgb(n, n, n))
|
|
||||||
},
|
|
||||||
Some("rgb") => {
|
|
||||||
let r = try_to_u8(expr.tail().head())?.ok_or::<Box<dyn Error>>(
|
|
||||||
format!("not red: {expr}").into()
|
|
||||||
)?;
|
|
||||||
let g = try_to_u8(expr.tail().tail().head())?.ok_or::<Box<dyn Error>>(
|
|
||||||
format!("not green: {expr}").into()
|
|
||||||
)?;
|
|
||||||
let b = try_to_u8(expr.tail().tail().tail().head())?.ok_or::<Box<dyn Error>>(
|
|
||||||
format!("not blue: {expr}").into()
|
|
||||||
)?;
|
|
||||||
Some(Color::Rgb(r, g, b))
|
|
||||||
},
|
|
||||||
Some(_) => return Err(format!("not a color expression: {expr}").into()),
|
|
||||||
None => return Err(format!("not a color expression: {expr}").into())
|
|
||||||
}
|
|
||||||
} else if let Ok(Some(sym)) = src.word() {
|
|
||||||
match sym {
|
|
||||||
":color/bg" => Some(Color::Rgb(28, 32, 36)),
|
|
||||||
_ => return Err(format!("not a color: {sym}").into())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(format!("not a color: {:?}", src.src()?).into())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_opt_u7 (state: &App, src: impl Language) -> Perhaps<Option<u7>> {
|
|
||||||
src.word()?.map(|word|Ok(match word {
|
|
||||||
":editor/pitch" => Some((
|
|
||||||
state.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8
|
|
||||||
).into()),
|
|
||||||
_ => return Err(format!("unknown midi note: {word}").into())
|
|
||||||
})).transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_opt_u16 (_state: &App, _src: impl Language) -> Perhaps<Option<u16>> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_opt_usize (state: &App, src: impl Language) -> Perhaps<Option<usize>> {
|
|
||||||
src.word()?.map(|word|Ok(match word {
|
|
||||||
":selected/scene" => state.selection().scene(),
|
|
||||||
":selected/track" => state.selection().track(),
|
|
||||||
_ => return Err(format!("unknown opt<usize>: {word}").into())
|
|
||||||
})).transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_clip (state: &App, src: impl Language) -> Perhaps<Option<Arc<RwLock<MidiClip>>>> {
|
|
||||||
src.word()?.map(|word|Ok(match word {
|
|
||||||
":selected/clip" if let Selection::TrackClip { track, scene } = state.selection() =>
|
|
||||||
state.scenes()[*scene].clips[*track].clone(),
|
|
||||||
_ => return Err(format!("not a clip: {word}").into())
|
|
||||||
})).transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
||||||
/// Create a new application instance from a backend, project, config, and mode
|
/// Create a new application instance from a backend, project, config, and mode
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -212,36 +105,6 @@ impl App {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn confirm (&mut self) -> Perhaps<AppCommand> {
|
|
||||||
Ok(match &self.dialog {
|
|
||||||
Dialog::Menu(index, items) => {
|
|
||||||
let callback = items.0[*index].1.clone();
|
|
||||||
callback(self)?;
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ => todo!(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inc (&mut self, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
|
||||||
Ok(match (&self.dialog, axis) {
|
|
||||||
(Dialog::None, _) => todo!(),
|
|
||||||
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
|
||||||
AppCommand::SetDialog { dialog: self.dialog.menu_next() }.act(self)?,
|
|
||||||
_ => todo!()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dec (&mut self, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
|
||||||
Ok(match (&self.dialog, axis) {
|
|
||||||
(Dialog::None, _) => None,
|
|
||||||
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
|
||||||
AppCommand::SetDialog { dialog: self.dialog.menu_prev() }.act(self)?,
|
|
||||||
_ => todo!()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update memoized render of clock values.
|
/// Update memoized render of clock values.
|
||||||
/// ```
|
/// ```
|
||||||
/// tek::App::default().update_clock();
|
/// tek::App::default().update_clock();
|
||||||
|
|
@ -360,26 +223,138 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn get_arc_str (&self, src: impl Language) -> Perhaps<Arc<str>> {
|
||||||
|
Ok(src.src()?.map(|x|x.into()))
|
||||||
|
}
|
||||||
|
fn get_u16 (&self, src: impl Language) -> Perhaps<u16> {
|
||||||
|
Ok(Some(match src.word()? {
|
||||||
|
Some(":w/sidebar") => self.project.w_sidebar(self.editor().is_some()),
|
||||||
|
Some(":h/sample-detail") => 6.max(self.size.h() as u16 * 3 / 9),
|
||||||
|
_ => return try_to_u16(src)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
fn get_usize (&self, src: impl Language) -> Perhaps<usize> {
|
||||||
|
Ok(Some(match src.word()? {
|
||||||
|
Some(":scene-count") => self.scenes().len(),
|
||||||
|
Some(":track-count") => self.tracks().len(),
|
||||||
|
Some(":device-kind") => self.dialog.device_kind().unwrap_or(0),
|
||||||
|
Some(":device-kind/next") => self.dialog.device_kind_next().unwrap_or(0),
|
||||||
|
Some(":device-kind/prev") => self.dialog.device_kind_prev().unwrap_or(0),
|
||||||
|
_ => return try_to_usize(src)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
fn get_bool (&self, src: impl Language) -> Perhaps<bool> {
|
||||||
|
src.word()?.map(|word|Ok(match word {
|
||||||
|
"Y" => true,
|
||||||
|
"N" => false,
|
||||||
|
":mode/editor" => self.project.editor.is_some(),
|
||||||
|
":focused/dialog" => !matches!(self.dialog, Dialog::None),
|
||||||
|
":focused/message" => matches!(self.dialog, Dialog::Message(..)),
|
||||||
|
":focused/add_device" => matches!(self.dialog, Dialog::Device(..)),
|
||||||
|
":focused/browser" => self.dialog.browser().is_some(),
|
||||||
|
":focused/pool/import" => matches!(self.pool.mode, Some(PoolMode::Import(..))),
|
||||||
|
":focused/pool/export" => matches!(self.pool.mode, Some(PoolMode::Export(..))),
|
||||||
|
":focused/pool/rename" => matches!(self.pool.mode, Some(PoolMode::Rename(..))),
|
||||||
|
":focused/pool/length" => matches!(self.pool.mode, Some(PoolMode::Length(..))),
|
||||||
|
":focused/clip" => !self.editor_focused() && matches!(self.selection(), Selection::TrackClip{..}),
|
||||||
|
":focused/track" => !self.editor_focused() && matches!(self.selection(), Selection::Track(..)),
|
||||||
|
":focused/scene" => !self.editor_focused() && matches!(self.selection(), Selection::Scene(..)),
|
||||||
|
":focused/mix" => !self.editor_focused() && matches!(self.selection(), Selection::Mix),
|
||||||
|
_ => return Err(format!("not bool: {word}").into())
|
||||||
|
})).transpose()
|
||||||
|
}
|
||||||
|
fn get_selection (&self, src: impl Language) -> Perhaps<Selection> {
|
||||||
|
src.word()?.map(|word|Ok(match word {
|
||||||
|
":select/scene" => self.selection().select_scene(self.tracks().len()),
|
||||||
|
":select/scene/next" => self.selection().select_scene_next(self.scenes().len()),
|
||||||
|
":select/scene/prev" => self.selection().select_scene_prev(),
|
||||||
|
":select/track" => self.selection().select_track(self.tracks().len()),
|
||||||
|
":select/track/next" => self.selection().select_track_next(self.tracks().len()),
|
||||||
|
":select/track/prev" => self.selection().select_track_prev(),
|
||||||
|
_ => return Err(format!("not selection: {word}").into())
|
||||||
|
})).transpose()
|
||||||
|
}
|
||||||
|
fn get_color (&self, src: impl Language) -> Perhaps<Color> {
|
||||||
|
if let Some(expr) = src.expr()? {
|
||||||
|
match (expr.head()?, expr.tail()?) {
|
||||||
|
(Some("g"), Some(tail)) => {
|
||||||
|
let n = try_to_u8(expr.tail().map_err(Into::into))?.ok_or(LanguageError::Domain("not gray"))?;
|
||||||
|
Ok(Some(Color::Rgb(n, n, n)))
|
||||||
|
},
|
||||||
|
(Some("rgb"), Some(tail)) => {
|
||||||
|
let r = try_to_u8(expr.tail().map_err(Into::into))?
|
||||||
|
.ok_or(LanguageError::Domain("not red"))?;
|
||||||
|
let g = try_to_u8(expr.tail().tail().head().map_err(Into::into))?
|
||||||
|
.ok_or(LanguageError::Domain("not green"))?;
|
||||||
|
let b = try_to_u8(expr.tail().tail().tail().head().map_err(Into::into))?
|
||||||
|
.ok_or(LanguageError::Domain("not blue"))?;
|
||||||
|
Ok(Some(Color::Rgb(r, g, b)))
|
||||||
|
},
|
||||||
|
(Some(_), _) => return Err(format!("not a color expression: {expr}").into()),
|
||||||
|
(None, _) => return Err(format!("not a color expression: {expr}").into()),
|
||||||
|
}
|
||||||
|
} else if let Ok(Some(sym)) = src.word() {
|
||||||
|
Ok(match sym {
|
||||||
|
":color/bg" => Some(Color::Rgb(28, 32, 36)),
|
||||||
|
":color/fg" => Some(Color::Rgb(98, 92, 96)),
|
||||||
|
_ => return Err(format!("not a color: {sym}").into())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return Err(format!("not a color: {:?}", src.src()?).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_opt_u7 (&self, src: impl Language) -> Perhaps<Option<u7>> {
|
||||||
|
src.word()?.map(|word|Ok(match word {
|
||||||
|
":editor/pitch" => Some((
|
||||||
|
self.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8
|
||||||
|
).into()),
|
||||||
|
_ => return Err(format!("unknown midi note: {word}").into())
|
||||||
|
})).transpose()
|
||||||
|
}
|
||||||
|
fn get_opt_u16 (&self, _src: impl Language) -> Perhaps<Option<u16>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
fn get_opt_usize (&self, src: impl Language) -> Perhaps<Option<usize>> {
|
||||||
|
src.word()?.map(|word|Ok(match word {
|
||||||
|
":selected/scene" => self.selection().scene(),
|
||||||
|
":selected/track" => self.selection().track(),
|
||||||
|
_ => return Err(format!("unknown opt<usize>: {word}").into())
|
||||||
|
})).transpose()
|
||||||
|
}
|
||||||
|
fn get_clip (&self, src: impl Language) -> Perhaps<Option<Arc<RwLock<MidiClip>>>> {
|
||||||
|
src.word()?.map(|word|Ok(match word {
|
||||||
|
":selected/clip" if let Selection::TrackClip { track, scene } = self.selection() =>
|
||||||
|
self.scenes()[*scene].clips[*track].clone(),
|
||||||
|
_ => return Err(format!("not a clip: {word}").into())
|
||||||
|
})).transpose()
|
||||||
|
}
|
||||||
|
pub fn inc (&mut self, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
||||||
|
Ok(match (&self.dialog, axis) {
|
||||||
|
(Dialog::None, _) => todo!(),
|
||||||
|
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
||||||
|
AppCommand::SetDialog { dialog: self.dialog.menu_next() }.act(self)?,
|
||||||
|
_ => todo!()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn dec (&mut self, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
||||||
|
Ok(match (&self.dialog, axis) {
|
||||||
|
(Dialog::None, _) => None,
|
||||||
|
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
||||||
|
AppCommand::SetDialog { dialog: self.dialog.menu_prev() }.act(self)?,
|
||||||
|
_ => todo!()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn confirm (&mut self) -> Perhaps<AppCommand> {
|
||||||
|
Ok(match &self.dialog {
|
||||||
|
Dialog::Menu(index, items) => {
|
||||||
|
let callback = items.0[*index].1.clone();
|
||||||
|
callback(self)?;
|
||||||
|
None
|
||||||
|
},
|
||||||
|
_ => todo!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_has!(Clock: |self: App|self.project.clock);
|
|
||||||
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
|
||||||
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
|
||||||
impl_has!(Dialog: |self: App|self.dialog);
|
|
||||||
impl_has!(Jack<'static>: |self: App|self.jack);
|
|
||||||
impl_has!(Pool: |self: App|self.pool);
|
|
||||||
impl_has!(Selection: |self: App|self.project.selection);
|
|
||||||
impl_as_ref!(Vec<Scene>: |self: App|self.project.as_ref());
|
|
||||||
impl_as_mut!(Vec<Scene>: |self: App|self.project.as_mut());
|
|
||||||
impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt());
|
|
||||||
impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt());
|
|
||||||
impl_has_clips!( |self: App|self.pool.clips);
|
|
||||||
|
|
||||||
primitive!(u8: try_to_u8);
|
|
||||||
primitive!(u16: try_to_u16);
|
|
||||||
primitive!(usize: try_to_usize);
|
|
||||||
primitive!(isize: try_to_isize);
|
|
||||||
|
|
||||||
|
|
||||||
pub fn swap_value <T: Clone + PartialEq, U> (
|
pub fn swap_value <T: Clone + PartialEq, U> (
|
||||||
target: &mut T, value: &T, returned: impl Fn(T)->U
|
target: &mut T, value: &T, returned: impl Fn(T)->U
|
||||||
|
|
@ -421,4 +396,3 @@ pub fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
|
||||||
files.sort();
|
files.sort();
|
||||||
Ok((subdirs, files))
|
Ok((subdirs, files))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
tui_keys!(|self: App, input| {
|
tui_keys!(self: App, input {
|
||||||
let commands = tek_commands_collect(self, input)?;
|
let commands = tek_commands_collect(self, input)?;
|
||||||
let results = tek_commands_execute(self, commands)?;
|
let results = tek_commands_execute(self, commands)?;
|
||||||
self.history.extend(results.into_iter());
|
self.history.extend(results.into_iter());
|
||||||
|
|
|
||||||
259
src/app/cli.rs
259
src/app/cli.rs
|
|
@ -1,259 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// The command-line interface descriptor.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let cli: tek::Cli = Default::default();
|
|
||||||
///
|
|
||||||
/// use clap::CommandFactory;
|
|
||||||
/// tek::Cli::command().debug_assert();
|
|
||||||
/// ```
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))]
|
|
||||||
#[derive(Debug, Default)] pub struct Cli {
|
|
||||||
/// Pre-defined configuration modes.
|
|
||||||
///
|
|
||||||
/// TODO: Replace these with scripted configurations.
|
|
||||||
#[command(subcommand)] pub action: Action,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Application modes that can be passed to the mommand line interface.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let action: tek::Action = Default::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Subcommand, Default)] pub enum Action {
|
|
||||||
/// Continue where you left off
|
|
||||||
#[default] Resume,
|
|
||||||
/// Run headlessly in current session.
|
|
||||||
Headless,
|
|
||||||
/// Show status of current session.
|
|
||||||
Status,
|
|
||||||
/// List known sessions.
|
|
||||||
List,
|
|
||||||
/// Continue work in a copy of the current session.
|
|
||||||
Fork,
|
|
||||||
/// Create a new empty session.
|
|
||||||
New {
|
|
||||||
/// Name of JACK client
|
|
||||||
#[arg(short='n', long)] name: Option<String>,
|
|
||||||
/// Whether to attempt to become transport master
|
|
||||||
#[arg(short='Y', long, default_value_t = false)] sync_lead: bool,
|
|
||||||
/// Whether to sync to external transport master
|
|
||||||
#[arg(short='y', long, default_value_t = true)] sync_follow: bool,
|
|
||||||
/// Initial tempo in beats per minute
|
|
||||||
#[arg(short='b', long, default_value = None)] bpm: Option<f64>,
|
|
||||||
/// Whether to include a transport toolbar (default: true)
|
|
||||||
#[arg(short='c', long, default_value_t = true)] show_clock: bool,
|
|
||||||
/// MIDI outs to connect to (multiple instances accepted)
|
|
||||||
#[arg(short='I', long)] midi_from: Vec<String>,
|
|
||||||
/// MIDI outs to connect to (multiple instances accepted)
|
|
||||||
#[arg(short='i', long)] midi_from_re: Vec<String>,
|
|
||||||
/// MIDI ins to connect to (multiple instances accepted)
|
|
||||||
#[arg(short='O', long)] midi_to: Vec<String>,
|
|
||||||
/// MIDI ins to connect to (multiple instances accepted)
|
|
||||||
#[arg(short='o', long)] midi_to_re: Vec<String>,
|
|
||||||
/// Audio outs to connect to left input
|
|
||||||
#[arg(short='l', long)] left_from: Vec<String>,
|
|
||||||
/// Audio outs to connect to right input
|
|
||||||
#[arg(short='r', long)] right_from: Vec<String>,
|
|
||||||
/// Audio ins to connect from left output
|
|
||||||
#[arg(short='L', long)] left_to: Vec<String>,
|
|
||||||
/// Audio ins to connect from right output
|
|
||||||
#[arg(short='R', long)] right_to: Vec<String>,
|
|
||||||
/// Tracks to creat
|
|
||||||
#[arg(short='t', long)] tracks: Option<usize>,
|
|
||||||
/// Scenes to create
|
|
||||||
#[arg(short='s', long)] scenes: Option<usize>,
|
|
||||||
},
|
|
||||||
/// Import media as new session.
|
|
||||||
Import,
|
|
||||||
/// Show configuration.
|
|
||||||
Config,
|
|
||||||
/// Show version.
|
|
||||||
Version,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Command-line configuration.
|
|
||||||
#[cfg(feature = "cli")]
|
|
||||||
impl Cli {
|
|
||||||
pub fn run (&self) -> Usually<()> {
|
|
||||||
self.action.run(Config::init_new(None)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
|
||||||
impl Action {
|
|
||||||
fn run (&self, config: Config) -> Usually<()> {
|
|
||||||
use Action::*;
|
|
||||||
match self {
|
|
||||||
Version => tek_show_version(),
|
|
||||||
Config => tek_print_config(&config),
|
|
||||||
List => todo!("list sessions"),
|
|
||||||
Resume => todo!("resume session"),
|
|
||||||
New {
|
|
||||||
name, bpm, tracks, scenes, sync_lead, sync_follow,
|
|
||||||
midi_from: mf, midi_from_re: mfr, midi_to: mt, midi_to_re: mtr,
|
|
||||||
left_from: lf, right_from: rf, left_to: lt, right_to: rt, ..
|
|
||||||
} => {
|
|
||||||
let name = name.as_ref().map_or("tek", |x|x.as_str());
|
|
||||||
let jack = Jack::new(&name)?;
|
|
||||||
let mut proj = tek_project_new(
|
|
||||||
&jack, Clock::new(&jack, *bpm)?, &lf, <, &rf, &rt, &mf, &mt, &mfr, &mtr
|
|
||||||
)?;
|
|
||||||
proj.tracks_add(tracks.unwrap_or(0), None, &[], &[])?;
|
|
||||||
proj.scenes_add(scenes.unwrap_or(0))?;
|
|
||||||
//if matches!(self, Action::Status) {
|
|
||||||
//// Show status and exit
|
|
||||||
//tek_print_status(&proj);
|
|
||||||
//return Ok(())
|
|
||||||
//}
|
|
||||||
// Initialize the app state
|
|
||||||
let app = Arc::new(RwLock::new(App::new(&jack, proj, config, ":menu")));
|
|
||||||
//if matches!(self, Action::Headless) {
|
|
||||||
//// TODO: Headless mode (daemon + client over IPC, then over network...)
|
|
||||||
//println!("todo headless");
|
|
||||||
//return Ok(())
|
|
||||||
//}
|
|
||||||
let (_keyboard, _terminal) = Exit::run(|exited|tui_io(
|
|
||||||
exited.as_ref(),
|
|
||||||
&app,
|
|
||||||
Duration::from_millis(100),
|
|
||||||
Duration::from_millis(10),
|
|
||||||
std::io::stdout()
|
|
||||||
))?;
|
|
||||||
// Run the [Tui] and [Jack] threads with the [App] state.
|
|
||||||
//Tui::new(Box::new(std::io::stdout()))?.run(true, &jack.run(move|jack|{
|
|
||||||
//// Between jack init and app's first cycle:
|
|
||||||
//jack.sync_lead(*sync_lead, |mut state|{
|
|
||||||
//let clock = app.clock();
|
|
||||||
//clock.playhead.update_from_sample(state.position.frame() as f64);
|
|
||||||
//state.position.bbt = Some(clock.bbt());
|
|
||||||
//state.position
|
|
||||||
//})?;
|
|
||||||
//jack.sync_follow(*sync_follow)?;
|
|
||||||
//// FIXME: They don't work properly.
|
|
||||||
//Ok(app)
|
|
||||||
//})?)?;
|
|
||||||
},
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tek_project_new (
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
clock: Clock,
|
|
||||||
left_from: &[impl AsRef<str>],
|
|
||||||
left_to: &[impl AsRef<str>],
|
|
||||||
right_from: &[impl AsRef<str>],
|
|
||||||
right_to: &[impl AsRef<str>],
|
|
||||||
midi_from: &[impl AsRef<str>],
|
|
||||||
midi_to: &[impl AsRef<str>],
|
|
||||||
midi_from_re: &[impl AsRef<str>],
|
|
||||||
midi_to_re: &[impl AsRef<str>],
|
|
||||||
) -> Usually<Arrangement> {
|
|
||||||
// TODO: Collect audio IO:
|
|
||||||
let empty = &[] as &[&str];
|
|
||||||
let left_froms = Connect::collect(left_from, empty, empty);
|
|
||||||
let left_tos = Connect::collect(left_to, empty, empty);
|
|
||||||
let right_froms = Connect::collect(right_from, empty, empty);
|
|
||||||
let right_tos = Connect::collect(right_to, empty, empty);
|
|
||||||
let _audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
|
||||||
let _audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
|
||||||
Ok(Arrangement::new(
|
|
||||||
jack,
|
|
||||||
None,
|
|
||||||
clock,
|
|
||||||
vec![],
|
|
||||||
vec![],
|
|
||||||
Connect::collect(&midi_from, &[] as &[&str], &midi_from_re).iter().enumerate()
|
|
||||||
.map(|(index, connect)|jack.midi_in(&format!("M/{index}"), &[connect.clone()]))
|
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
|
||||||
Connect::collect(&midi_to, &[] as &[&str], &midi_to_re).iter().enumerate()
|
|
||||||
.map(|(index, connect)|jack.midi_out(&format!("{index}/M"), &[connect.clone()]))
|
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tek_show_version () {
|
|
||||||
println!("todo version");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tek_print_config (config: &Config) {
|
|
||||||
use ::ansi_term::Color::*;
|
|
||||||
println!("{:?}", config.dirs);
|
|
||||||
for (k, v) in config.views.read().unwrap().iter() {
|
|
||||||
println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")));
|
|
||||||
}
|
|
||||||
for (k, v) in config.binds.read().unwrap().iter() {
|
|
||||||
println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}")));
|
|
||||||
for (k, v) in v.0.iter() {
|
|
||||||
print!("{} ", &Yellow.paint(match &k.0 {
|
|
||||||
Event::Key(KeyEvent { modifiers, .. }) =>
|
|
||||||
format!("{:>16}", format!("{modifiers}")),
|
|
||||||
_ => unimplemented!()
|
|
||||||
}));
|
|
||||||
print!("{}", &Yellow.bold().paint(match &k.0 {
|
|
||||||
Event::Key(KeyEvent { code, .. }) =>
|
|
||||||
format!("{:<10}", format!("{code}")),
|
|
||||||
_ => unimplemented!()
|
|
||||||
}));
|
|
||||||
for v in v.iter() {
|
|
||||||
print!(" => {:?}", v.commands);
|
|
||||||
print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default());
|
|
||||||
println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default());
|
|
||||||
//println!(" {:?}", v.source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config.modes.for_each(|k, v|{
|
|
||||||
println!();
|
|
||||||
for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); }
|
|
||||||
for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); }
|
|
||||||
print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}")));
|
|
||||||
print!("\n{}", Blue.paint("KEYS"));
|
|
||||||
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
|
||||||
println!();
|
|
||||||
v.modes.for_each(|k, v|{
|
|
||||||
print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name);
|
|
||||||
print!( " INFO={:?}", v.info);
|
|
||||||
print!( " VIEW={:?}", v.view);
|
|
||||||
println!(" KEYS={:?}", v.keys);
|
|
||||||
});
|
|
||||||
print!("{}", Blue.paint("VIEW"));
|
|
||||||
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
|
||||||
println!();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tek_print_status (project: &Arrangement) {
|
|
||||||
println!("Name: {:?}", &project.name);
|
|
||||||
println!("JACK: {:?}", &project.jack);
|
|
||||||
println!("Buffer: {:?}", &project.clock.chunk);
|
|
||||||
println!("Sample rate: {:?}", &project.clock.timebase.sr);
|
|
||||||
println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq);
|
|
||||||
println!("Tempo: {:?}", &project.clock.timebase.bpm);
|
|
||||||
println!("Quantize: {:?}", &project.clock.quant);
|
|
||||||
println!("Launch: {:?}", &project.clock.sync);
|
|
||||||
println!("Playhead: {:?}us", &project.clock.playhead.usec);
|
|
||||||
println!("Playhead: {:?}s", &project.clock.playhead.sample);
|
|
||||||
println!("Playhead: {:?}p", &project.clock.playhead.pulse);
|
|
||||||
println!("Started: {:?}", &project.clock.started);
|
|
||||||
println!("Tracks:");
|
|
||||||
for (i, t) in project.tracks.iter().enumerate() {
|
|
||||||
println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width,
|
|
||||||
&t.sequencer.play_clip, &t.sequencer.next_clip);
|
|
||||||
}
|
|
||||||
println!("Scenes:");
|
|
||||||
for (i, t) in project.scenes.iter().enumerate() {
|
|
||||||
println!(" Scene {i}: {} {:?}", &t.name, &t.clips);
|
|
||||||
}
|
|
||||||
println!("MIDI Ins: {:?}", &project.midi_ins);
|
|
||||||
println!("MIDI Outs: {:?}", &project.midi_outs);
|
|
||||||
println!("Audio Ins: {:?}", &project.audio_ins);
|
|
||||||
println!("Audio Outs: {:?}", &project.audio_outs);
|
|
||||||
// TODO git integration
|
|
||||||
// TODO dawvert integration
|
|
||||||
}
|
|
||||||
|
|
@ -39,6 +39,9 @@ impl Config {
|
||||||
|
|
||||||
pub fn watch <T> (callback: impl FnOnce(Self)->T) -> Usually<T> {
|
pub fn watch <T> (callback: impl FnOnce(Self)->T) -> Usually<T> {
|
||||||
let config = Self::init_new(None)?;
|
let config = Self::init_new(None)?;
|
||||||
|
let watcher = notify_debouncer_mini::new_debouncer(Duration::from_millis(500), |res| {
|
||||||
|
println!("{res:?}");
|
||||||
|
})?;
|
||||||
let result = callback(config);
|
let result = callback(config);
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ fn tek_draw_expr (state: &App, to: &mut Tui, lang: &impl Expression) -> Drawn<u1
|
||||||
fn tek_draw_word (state: &App, to: &mut Tui, dsl: &impl Expression) -> Drawn<u16> {
|
fn tek_draw_word (state: &App, to: &mut Tui, dsl: &impl Expression) -> Drawn<u16> {
|
||||||
let mut frags = dsl.src()?.unwrap().split("/");
|
let mut frags = dsl.src()?.unwrap().split("/");
|
||||||
match frags.next() {
|
match frags.next() {
|
||||||
Some(":logo") => view_logo().draw(to),
|
//Some(":logo") => view_logo().draw(to),
|
||||||
Some(":meters") => draw_meter_section(to, frags),
|
Some(":meters") => draw_meter_section(to, frags),
|
||||||
Some(":tracks") => draw_tracks(to, frags, state),
|
Some(":tracks") => draw_tracks(to, frags, state),
|
||||||
Some(":scenes") => draw_scenes(to, frags),
|
Some(":scenes") => draw_scenes(to, frags),
|
||||||
|
|
@ -58,6 +58,7 @@ fn tek_draw_word (state: &App, to: &mut Tui, dsl: &impl Expression) -> Drawn<u16
|
||||||
Some(":device") => view_device(state).draw(to),
|
Some(":device") => view_device(state).draw(to),
|
||||||
Some(":status") => "TODO: Status Bar".exact_h(1).draw(to),
|
Some(":status") => "TODO: Status Bar".exact_h(1).draw(to),
|
||||||
Some(":editor") => "TODO Editor".draw(to),
|
Some(":editor") => "TODO Editor".draw(to),
|
||||||
|
Some(":transport") => view_transport(true, "", "", "").draw(to),
|
||||||
Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).draw(to),
|
Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).draw(to),
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
let views = state.config.views.read().unwrap();
|
let views = state.config.views.read().unwrap();
|
||||||
|
|
@ -111,7 +112,7 @@ pub fn draw_dialog (
|
||||||
let f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) };
|
let f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) };
|
||||||
let b = if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) };
|
let b = if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) };
|
||||||
fg_bg(f, b, item.full_w().align_w().exact_h(2))
|
fg_bg(f, b, item.full_w().align_w().exact_h(2))
|
||||||
.push_y((2 * index) as u16).draw(to)?;
|
.push_y((4 * index) as u16).draw(to)?;
|
||||||
}
|
}
|
||||||
Ok(Some(to.area().into()))
|
Ok(Some(to.area().into()))
|
||||||
}).full_wh())
|
}).full_wh())
|
||||||
|
|
@ -143,18 +144,6 @@ pub fn draw_templates (to: &mut Tui, frags: std::str::Split<&str>, state: &App)
|
||||||
}).min_w(30).exact_h(height).draw(to)
|
}).min_w(30).exact_h(height).draw(to)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::view_logo();
|
|
||||||
/// ```
|
|
||||||
pub fn view_logo () -> impl Draw<Tui> {
|
|
||||||
fg(Rgb(240, 200, 180), south!{
|
|
||||||
"~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~".exact_h(1),
|
|
||||||
"~~~~ ║ ~ ╟─╌ ~╟─< ~~ v0.3.0 ~~".exact_h(1),
|
|
||||||
//east("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", east(fg(Rgb(230,100,40), "v0.3.0"), " ~~")).exact_h(1),
|
|
||||||
"~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~".exact_h(1),
|
|
||||||
}.exact_w(32))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
/// ```
|
||||||
/// let x = "";
|
/// let x = "";
|
||||||
/// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref());
|
/// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref());
|
||||||
|
|
@ -163,7 +152,7 @@ pub fn view_logo () -> impl Draw<Tui> {
|
||||||
pub fn view_transport (play: bool, bpm: &str, beat: &str, time: &str) -> impl Draw<Tui> {
|
pub fn view_transport (play: bool, bpm: &str, beat: &str, time: &str) -> impl Draw<Tui> {
|
||||||
let theme = ItemTheme::G[96];
|
let theme = ItemTheme::G[96];
|
||||||
bg(Black, east!(above(
|
bg(Black, east!(above(
|
||||||
button_play_pause(play, false).align_w().full_wh(),
|
button_play_pause(play, false).align_w(),
|
||||||
east!(
|
east!(
|
||||||
field_h(theme, "BPM", bpm),
|
field_h(theme, "BPM", bpm),
|
||||||
field_h(theme, "Beat", beat),
|
field_h(theme, "Beat", beat),
|
||||||
|
|
@ -711,12 +700,12 @@ pub fn per_track_top <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_h <T: Screen> (
|
pub fn field_h <T: Screen> (
|
||||||
theme: ItemTheme, head: impl Draw<T>, body: impl Draw<T>
|
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
||||||
) -> impl Draw<T> {
|
) -> impl Draw<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_v <T: Screen> (
|
pub fn field_v <T: Screen> (
|
||||||
theme: ItemTheme, head: impl Draw<T>, body: impl Draw<T>
|
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
||||||
) -> impl Draw<T> {
|
) -> impl Draw<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
34
src/deps.rs
34
src/deps.rs
|
|
@ -1,21 +1,3 @@
|
||||||
#[cfg(feature = "cli")]
|
|
||||||
pub(crate) use ::clap::{self, Parser, Subcommand};
|
|
||||||
|
|
||||||
pub(crate) use ::xdg::BaseDirectories;
|
|
||||||
|
|
||||||
pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*};
|
|
||||||
|
|
||||||
pub(crate) use tengri::{
|
|
||||||
*,
|
|
||||||
lang::*,
|
|
||||||
crossterm::event::{Event, KeyEvent},
|
|
||||||
ratatui::{
|
|
||||||
self,
|
|
||||||
prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}},
|
|
||||||
widgets::{Widget, canvas::{Canvas, Line}},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub(crate) use ::{
|
pub(crate) use ::{
|
||||||
std::{
|
std::{
|
||||||
|
|
@ -31,4 +13,20 @@ pub(crate) use ::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
thread::{spawn, JoinHandle},
|
thread::{spawn, JoinHandle},
|
||||||
},
|
},
|
||||||
|
xdg::{
|
||||||
|
BaseDirectories,
|
||||||
|
},
|
||||||
|
tengri::{
|
||||||
|
*,
|
||||||
|
lang::*,
|
||||||
|
midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*},
|
||||||
|
crossterm::event::{Event, KeyEvent},
|
||||||
|
ratatui::{
|
||||||
|
self,
|
||||||
|
prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}},
|
||||||
|
widgets::{Widget, canvas::{Canvas, Line}},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
pub(crate) use ::clap::{self, Parser, Subcommand};
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ pub trait HasDevices: AsRef<Vec<Device>> + AsMut<Vec<Device>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use ::tengri::sing::*;
|
||||||
pub mod arrange; pub use self::arrange::*;
|
pub mod arrange; pub use self::arrange::*;
|
||||||
pub mod browse; pub use self::browse::*;
|
pub mod browse; pub use self::browse::*;
|
||||||
pub mod clock; pub use self::clock::*;
|
pub mod clock; pub use self::clock::*;
|
||||||
|
|
@ -108,9 +109,36 @@ pub mod editor; pub use self::editor::*;
|
||||||
pub mod menu; pub use self::menu::*;
|
pub mod menu; pub use self::menu::*;
|
||||||
pub mod meter; pub use self::meter::*;
|
pub mod meter; pub use self::meter::*;
|
||||||
pub mod mix; pub use self::mix::*;
|
pub mod mix; pub use self::mix::*;
|
||||||
pub mod port; pub use self::port::*;
|
|
||||||
pub mod sampler; pub use self::sampler::*;
|
pub mod sampler; pub use self::sampler::*;
|
||||||
pub mod sequence; pub use self::sequence::*;
|
pub mod sequence; pub use self::sequence::*;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")] pub mod plugin;
|
#[cfg(feature = "plugin")] pub mod plugin;
|
||||||
#[cfg(feature = "plugin")] pub use self::plugin::*;
|
#[cfg(feature = "plugin")] pub use self::plugin::*;
|
||||||
|
|
||||||
|
def_command!(AudioInputCommand: |port: AudioInput| {
|
||||||
|
Close => todo!(),
|
||||||
|
Connect { audio_out: Arc<str> } => todo!(),
|
||||||
|
});
|
||||||
|
|
||||||
|
def_command!(AudioOutputCommand: |port: AudioOutput| {
|
||||||
|
Close => todo!(),
|
||||||
|
Connect { audio_in: Arc<str> } => todo!(),
|
||||||
|
});
|
||||||
|
|
||||||
|
def_command!(MidiInputCommand: |port: MidiInput| {
|
||||||
|
Close => todo!(),
|
||||||
|
Connect { midi_out: Arc<str> } => todo!(),
|
||||||
|
});
|
||||||
|
|
||||||
|
def_command!(MidiOutputCommand: |port: MidiOutput| {
|
||||||
|
Close => todo!(),
|
||||||
|
Connect { midi_in: Arc<str> } => todo!(),
|
||||||
|
});
|
||||||
|
|
||||||
|
pub struct Junction<T: JackPort>(T);
|
||||||
|
|
||||||
|
impl<T: JackPort> View<Tui> for Junction<T> {
|
||||||
|
fn view (&self) -> impl Draw<Tui> {
|
||||||
|
T::KIND
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ use crate::*;
|
||||||
/// ```
|
/// ```
|
||||||
/// let arranger = tek::Arrangement::default();
|
/// let arranger = tek::Arrangement::default();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default, Debug)] pub struct Arrangement {
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Arrangement {
|
||||||
/// Project name.
|
/// Project name.
|
||||||
pub name: Arc<str>,
|
pub name: Arc<str>,
|
||||||
/// Base color.
|
/// Base color.
|
||||||
|
|
@ -19,6 +20,8 @@ use crate::*;
|
||||||
pub size: Sizer,
|
pub size: Sizer,
|
||||||
/// Display size of clips area
|
/// Display size of clips area
|
||||||
pub size_inner: Sizer,
|
pub size_inner: Sizer,
|
||||||
|
/// Selected UI element
|
||||||
|
pub selection: Selection,
|
||||||
/// Source of time
|
/// Source of time
|
||||||
#[cfg(feature = "clock")] pub clock: Clock,
|
#[cfg(feature = "clock")] pub clock: Clock,
|
||||||
/// Allows one MIDI clip to be edited
|
/// Allows one MIDI clip to be edited
|
||||||
|
|
@ -31,8 +34,6 @@ use crate::*;
|
||||||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||||||
/// List of global audio outputs
|
/// List of global audio outputs
|
||||||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||||||
/// Selected UI element
|
|
||||||
#[cfg(feature = "select")] pub selection: Selection,
|
|
||||||
/// Last track number (to avoid duplicate port names)
|
/// Last track number (to avoid duplicate port names)
|
||||||
#[cfg(feature = "track")] pub track_last: usize,
|
#[cfg(feature = "track")] pub track_last: usize,
|
||||||
/// List of tracks
|
/// List of tracks
|
||||||
|
|
@ -45,34 +46,58 @@ use crate::*;
|
||||||
#[cfg(feature = "scene")] pub scene_scroll: usize,
|
#[cfg(feature = "scene")] pub scene_scroll: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
/// Represents the current user selection in the arranger
|
||||||
impl_has!(Jack<'static>: |self: Arrangement| self.jack);
|
#[derive(PartialEq, Clone, Copy, Debug, Default)]
|
||||||
impl_has!(Sizer: |self: Arrangement| self.size);
|
pub enum Selection {
|
||||||
impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
|
#[default]
|
||||||
impl_has!(Vec<MidiOutput>: |self: Arrangement| self.midi_outs);
|
/// Nothing is selected
|
||||||
impl_has!(Clock: |self: Arrangement| self.clock);
|
Nothing,
|
||||||
impl_has!(Selection: |self: Arrangement| self.selection);
|
/// The whole mix is selected
|
||||||
impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref());
|
Mix,
|
||||||
impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut());
|
/// A MIDI input is selected.
|
||||||
|
Input(usize),
|
||||||
|
/// A MIDI output is selected.
|
||||||
|
Output(usize),
|
||||||
|
/// A scene is selected.
|
||||||
|
#[cfg(feature = "scene")] Scene(usize),
|
||||||
|
/// A track is selected.
|
||||||
|
#[cfg(feature = "track")] Track(usize),
|
||||||
|
/// A clip (track × scene) is selected.
|
||||||
|
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
|
||||||
|
/// A track's MIDI input connection is selected.
|
||||||
|
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
|
||||||
|
/// A track's MIDI output connection is selected.
|
||||||
|
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
|
||||||
|
/// A track device slot is selected.
|
||||||
|
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
|
||||||
|
}
|
||||||
|
|
||||||
impl Arrangement {
|
impl Arrangement {
|
||||||
|
|
||||||
/// Create a new arrangement.
|
/// Create a new arrangement.
|
||||||
pub fn new (
|
pub fn new (
|
||||||
jack: &Jack<'static>,
|
jack: &Jack<'static>,
|
||||||
name: Option<Arc<str>>,
|
name: Arc<str>,
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
tracks: Vec<Track>,
|
tracks: impl Iterator<Item = Track>,
|
||||||
scenes: Vec<Scene>,
|
scenes: impl Iterator<Item = Scene>,
|
||||||
midi_ins: Vec<MidiInput>,
|
midi_ins: impl Iterator<Item = MidiInput>,
|
||||||
midi_outs: Vec<MidiOutput>,
|
midi_outs: impl Iterator<Item = MidiOutput>,
|
||||||
|
audio_ins: impl Iterator<Item = AudioInput>,
|
||||||
|
audio_outs: impl Iterator<Item = AudioOutput>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
clock, tracks, scenes, midi_ins, midi_outs,
|
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
name: name.unwrap_or_default(),
|
|
||||||
color: ItemTheme::random(),
|
color: ItemTheme::random(),
|
||||||
selection: Selection::TrackClip { track: 0, scene: 0 },
|
selection: Selection::TrackClip { track: 0, scene: 0 },
|
||||||
|
tracks: tracks.collect(),
|
||||||
|
scenes: scenes.collect(),
|
||||||
|
midi_ins: midi_ins.collect(),
|
||||||
|
midi_outs: midi_outs.collect(),
|
||||||
|
audio_ins: audio_ins.collect(),
|
||||||
|
audio_outs: audio_outs.collect(),
|
||||||
|
clock,
|
||||||
|
name,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +142,759 @@ impl Arrangement {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "clip")] mod clip; #[cfg(feature = "clip")] pub use self::clip::*;
|
impl Selection {
|
||||||
#[cfg(feature = "scene")] mod scene; #[cfg(feature = "scene")] pub use self::scene::*;
|
pub fn describe (
|
||||||
#[cfg(feature = "track")] mod track; #[cfg(feature = "track")] pub use self::track::*;
|
&self,
|
||||||
#[cfg(feature = "select")] mod select; #[cfg(feature = "select")] pub use self::select::*;
|
#[cfg(feature = "track")] tracks: &[Track],
|
||||||
|
#[cfg(feature = "scene")] scenes: &[Scene],
|
||||||
|
) -> Arc<str> {
|
||||||
|
use Selection::*;
|
||||||
|
format!("{}", match self {
|
||||||
|
Mix => "Everything".to_string(),
|
||||||
|
#[cfg(feature = "scene")] Scene(s) =>
|
||||||
|
scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()),
|
||||||
|
#[cfg(feature = "track")] Track(t) =>
|
||||||
|
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()),
|
||||||
|
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
|
||||||
|
(Some(_), Some(s)) => match s.clip(*track) {
|
||||||
|
Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name),
|
||||||
|
None => format!("T{track} S{scene}: Empty")
|
||||||
|
},
|
||||||
|
_ => format!("T{track} S{scene}: Empty"),
|
||||||
|
},
|
||||||
|
_ => todo!()
|
||||||
|
}).into()
|
||||||
|
}
|
||||||
|
#[cfg(feature = "scene")] pub fn scene (&self) -> Option<usize> {
|
||||||
|
use Selection::*;
|
||||||
|
match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None }
|
||||||
|
}
|
||||||
|
#[cfg(feature = "scene")] pub fn select_scene (&self, scene_count: usize) -> Self {
|
||||||
|
use Selection::*;
|
||||||
|
match self {
|
||||||
|
Mix | Track(_) => Scene(0),
|
||||||
|
Scene(s) => Scene((s + 1) % scene_count),
|
||||||
|
TrackClip { scene, .. } => Track(*scene),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "scene")] pub fn select_scene_next (&self, len: usize) -> Self {
|
||||||
|
use Selection::*;
|
||||||
|
match self {
|
||||||
|
Mix => Scene(0),
|
||||||
|
Track(t) => TrackClip { track: *t, scene: 0 },
|
||||||
|
Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix },
|
||||||
|
TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) },
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "scene")] pub fn select_scene_prev (&self) -> Self {
|
||||||
|
use Selection::*;
|
||||||
|
match self {
|
||||||
|
Mix | Scene(0) => Mix,
|
||||||
|
Scene(s) => Scene(s - 1),
|
||||||
|
Track(t) => Track(*t),
|
||||||
|
TrackClip { track, scene: 0 } => Track(*track),
|
||||||
|
TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 },
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "track")] pub fn track (&self) -> Option<usize> {
|
||||||
|
use Selection::*;
|
||||||
|
if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self {
|
||||||
|
Some(*track)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "track")] pub fn select_track (&self, track_count: usize) -> Self {
|
||||||
|
use Selection::*;
|
||||||
|
match self {
|
||||||
|
Mix => Track(0),
|
||||||
|
Scene(_) => Mix,
|
||||||
|
Track(t) => Track((t + 1) % track_count),
|
||||||
|
TrackClip { track, .. } => Track(*track),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "track")] pub fn select_track_next (&self, len: usize) -> Self {
|
||||||
|
use Selection::*;
|
||||||
|
match self {
|
||||||
|
Mix => Track(0),
|
||||||
|
Scene(s) => TrackClip { track: 0, scene: *s },
|
||||||
|
Track(t) => if t + 1 < len { Track(t + 1) } else { Mix },
|
||||||
|
TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) },
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "track")] pub fn select_track_prev (&self) -> Self {
|
||||||
|
use Selection::*;
|
||||||
|
match self {
|
||||||
|
Mix => Mix,
|
||||||
|
Scene(s) => Scene(*s),
|
||||||
|
Track(0) => Mix,
|
||||||
|
Track(t) => Track(t - 1),
|
||||||
|
TrackClip { track: 0, scene } => Scene(*scene),
|
||||||
|
TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene },
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <T: AsRef<Selection>+AsMut<Selection>> HasSelection for T {}
|
||||||
|
|
||||||
|
pub trait HasSelection: AsRef<Selection> + AsMut<Selection> {
|
||||||
|
fn selection (&self) -> &Selection { self.as_ref() }
|
||||||
|
fn selection_mut (&mut self) -> &mut Selection { self.as_mut() }
|
||||||
|
/// Get the active track
|
||||||
|
#[cfg(feature = "track")]
|
||||||
|
fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
|
||||||
|
let index = self.selection().track()?;
|
||||||
|
self.tracks().get(index)
|
||||||
|
}
|
||||||
|
/// Get a mutable reference to the active track
|
||||||
|
#[cfg(feature = "track")]
|
||||||
|
fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
|
||||||
|
let index = self.selection().track()?;
|
||||||
|
self.tracks_mut().get_mut(index)
|
||||||
|
}
|
||||||
|
/// Get the active scene
|
||||||
|
#[cfg(feature = "scene")]
|
||||||
|
fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
|
||||||
|
let index = self.selection().scene()?;
|
||||||
|
self.scenes().get(index)
|
||||||
|
}
|
||||||
|
/// Get a mutable reference to the active scene
|
||||||
|
#[cfg(feature = "scene")]
|
||||||
|
fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
|
||||||
|
let index = self.selection().scene()?;
|
||||||
|
self.scenes_mut().get_mut(index)
|
||||||
|
}
|
||||||
|
/// Get the active clip
|
||||||
|
#[cfg(feature = "clip")]
|
||||||
|
fn selected_clip (&self) -> Option<Arc<RwLock<MidiClip>>> where Self: HasScenes + HasTracks {
|
||||||
|
self.selected_scene()?.clips.get(self.selection().track()?)?.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||||
|
impl_has!(Jack<'static>: |self: Arrangement| self.jack);
|
||||||
|
impl_has!(Sizer: |self: Arrangement| self.size);
|
||||||
|
impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
|
||||||
|
impl_has!(Vec<MidiOutput>: |self: Arrangement| self.midi_outs);
|
||||||
|
impl_has!(Clock: |self: Arrangement| self.clock);
|
||||||
|
impl_has!(Selection: |self: Arrangement| self.selection);
|
||||||
|
impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref());
|
||||||
|
impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut());
|
||||||
|
|
||||||
|
pub trait HasClipsSize {
|
||||||
|
fn clips_size (&self) -> &Sizer;
|
||||||
|
}
|
||||||
|
pub trait ClipsView: TracksView + ScenesView {
|
||||||
|
/// Draw clips per scene
|
||||||
|
fn view_scenes_clips <'a> (&'a self) -> impl Draw<Tui> + 'a {
|
||||||
|
view_scenes_clips(
|
||||||
|
||self.scenes_with_sizes(),
|
||||||
|
self.tracks_with_sizes(),
|
||||||
|
self.selection(),
|
||||||
|
self.editor(),
|
||||||
|
self.clips_size(),
|
||||||
|
self.is_editing(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasClipsSize for App {
|
||||||
|
fn clips_size (&self) -> &Sizer { &self.project.size_inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasClipsSize for Arrangement {
|
||||||
|
fn clips_size (&self) -> &Sizer { &self.size_inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO: Preserve the generic passthru syntax;
|
||||||
|
/// remove this macro (only used twice) and potentially the trait.
|
||||||
|
#[macro_export] macro_rules! impl_has_clips {
|
||||||
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
|
impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> {
|
||||||
|
$cb.read().unwrap()
|
||||||
|
}
|
||||||
|
fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> {
|
||||||
|
$cb.write().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[macro_export] macro_rules! has_clip {
|
||||||
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
|
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
fn clip (&$self) -> Option<Arc<RwLock<MidiClip>>> { $cb }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def_command!(ClipCommand: |clip: MidiClip| {
|
||||||
|
SetColor { color: Option<ItemTheme> } => {
|
||||||
|
//(SetColor [t: usize, s: usize, c: ItemTheme]
|
||||||
|
//clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o)))));
|
||||||
|
//("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random())))
|
||||||
|
todo!()
|
||||||
|
},
|
||||||
|
SetLoop { looping: Option<bool> } => {
|
||||||
|
//(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
|
//("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
impl Arrangement {
|
||||||
|
/// Put a clip in a slot
|
||||||
|
pub fn clip_put (
|
||||||
|
&mut self, track: usize, scene: usize, clip: Option<Arc<RwLock<MidiClip>>>
|
||||||
|
) -> Option<Arc<RwLock<MidiClip>>> {
|
||||||
|
let old = self.scenes[scene].clips[track].clone();
|
||||||
|
self.scenes[scene].clips[track] = clip;
|
||||||
|
old
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the color of a clip, returning the previous one
|
||||||
|
pub fn clip_set_color (
|
||||||
|
&self, track: usize, scene: usize, color: ItemTheme
|
||||||
|
) -> Option<ItemTheme> {
|
||||||
|
self.scenes[scene].clips[track].as_ref().map(|clip|{
|
||||||
|
let mut clip = clip.write().unwrap();
|
||||||
|
let old = clip.color.clone();
|
||||||
|
clip.color = color.clone();
|
||||||
|
panic!("{color:?} {old:?}");
|
||||||
|
//old
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggle looping for the active clip
|
||||||
|
pub fn toggle_loop (&mut self) {
|
||||||
|
if let Some(clip) = self.selected_clip() {
|
||||||
|
clip.write().unwrap().toggle_loop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default scene height.
|
||||||
|
pub const H_SCENE: usize = 2;
|
||||||
|
|
||||||
|
/// Default editor height.
|
||||||
|
pub const H_EDITOR: usize = 15;
|
||||||
|
|
||||||
|
/// A scene consists of a set of clips to play together.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let scene: tek::Scene = Default::default();
|
||||||
|
/// let _ = scene.pulses();
|
||||||
|
/// let _ = scene.is_playing(&[]);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Scene {
|
||||||
|
/// Name of scene
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Identifying color of scene
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Clips in scene, one per track
|
||||||
|
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
/// Returns the pulse length of the longest clip in the scene
|
||||||
|
pub fn pulses (&self) -> usize {
|
||||||
|
self.clips.iter().fold(0, |a, p|{
|
||||||
|
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Returns true if all clips in the scene are
|
||||||
|
/// currently playing on the given collection of tracks.
|
||||||
|
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
||||||
|
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
|
||||||
|
.all(|(track_index, clip)|match clip {
|
||||||
|
Some(c) => tracks
|
||||||
|
.get(track_index)
|
||||||
|
.map(|track|{
|
||||||
|
if let Some((_, Some(clip))) = track.sequencer().play_clip() {
|
||||||
|
*clip.read().unwrap() == *c.read().unwrap()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false),
|
||||||
|
None => true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
|
||||||
|
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
||||||
|
}
|
||||||
|
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||||
|
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||||
|
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
||||||
|
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync {
|
||||||
|
fn h_scenes (&self) -> u16;
|
||||||
|
fn w_side (&self) -> u16;
|
||||||
|
fn w_mid (&self) -> u16;
|
||||||
|
|
||||||
|
fn view_scenes_names (&self) -> impl Draw<Tui> {
|
||||||
|
view_scenes_names(
|
||||||
|
self.scenes_with_sizes(), self.selection(), self.editor(), self.is_editing()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> {
|
||||||
|
let mut y = 0;
|
||||||
|
self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{
|
||||||
|
let height = if self.selection().scene() == Some(s) && self.editor().is_some() {
|
||||||
|
8
|
||||||
|
} else {
|
||||||
|
H_SCENE
|
||||||
|
};
|
||||||
|
if y + height <= self.clips_size().h() as usize {
|
||||||
|
let data = (s, scene, y, y + height);
|
||||||
|
y += height;
|
||||||
|
Some(data)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasSceneScroll: HasScenes {
|
||||||
|
fn scene_scroll (&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasScene: AsRefOpt<Scene> + AsMutOpt<Scene> {
|
||||||
|
fn scene_mut (&mut self) -> Option<&mut Scene> { self.as_mut_opt() }
|
||||||
|
fn scene (&self) -> Option<&Scene> { self.as_ref_opt() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
|
||||||
|
fn scenes (&self) -> &Vec<Scene> { self.as_ref() }
|
||||||
|
fn scenes_mut (&mut self) -> &mut Vec<Scene> { self.as_mut() }
|
||||||
|
/// Generate the default name for a new scene
|
||||||
|
fn scene_default_name (&self) -> Arc<str> { format!("s{:3>}", self.scenes().len() + 1).into() }
|
||||||
|
fn scene_longest_name (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) }
|
||||||
|
/// Add multiple scenes
|
||||||
|
fn scenes_add (&mut self, n: usize) -> Usually<()> where Self: HasTracks {
|
||||||
|
let scene_color_1 = ItemColor::random();
|
||||||
|
let scene_color_2 = ItemColor::random();
|
||||||
|
for i in 0..n {
|
||||||
|
let _ = self.scene_add(None, Some(
|
||||||
|
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Add a scene
|
||||||
|
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemTheme>)
|
||||||
|
-> Usually<(usize, &mut Scene)> where Self: HasTracks
|
||||||
|
{
|
||||||
|
let scene = Scene {
|
||||||
|
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
||||||
|
clips: vec![None;self.tracks().len()],
|
||||||
|
color: color.unwrap_or_else(ItemTheme::random),
|
||||||
|
};
|
||||||
|
self.scenes_mut().push(scene);
|
||||||
|
let index = self.scenes().len() - 1;
|
||||||
|
Ok((index, &mut self.scenes_mut()[index]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasSceneScroll for Arrangement {
|
||||||
|
fn scene_scroll (&self) -> usize { self.scene_scroll }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasSceneScroll for App {
|
||||||
|
fn scene_scroll (&self) -> usize { self.project.scene_scroll() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScenesView for Arrangement {
|
||||||
|
fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) }
|
||||||
|
fn w_side (&self) -> u16 { (self.size.w() as u16 * 2 / 10).max(20) }
|
||||||
|
fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SceneWith<'a, T> =
|
||||||
|
(usize, &'a Scene, usize, usize, T);
|
||||||
|
|
||||||
|
def_command!(SceneCommand: |scene: Scene| {
|
||||||
|
SetSize { size: usize } => { todo!() },
|
||||||
|
SetZoom { size: usize } => { todo!() },
|
||||||
|
SetName { name: Arc<str> } =>
|
||||||
|
swap_value(&mut scene.name, name, |name|Self::SetName{name}),
|
||||||
|
SetColor { color: ItemTheme } =>
|
||||||
|
swap_value(&mut scene.color, color, |color|Self::SetColor{color}),
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt());
|
||||||
|
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());
|
||||||
|
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene());
|
||||||
|
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut());
|
||||||
|
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
|
||||||
|
impl<T: TracksView+ScenesView+Send+Sync> ClipsView for T {}
|
||||||
|
impl<T: AsRef<Vec<Scene>>+AsMut<Vec<Scene>>> HasScenes for T {}
|
||||||
|
impl<T: AsRefOpt<Scene>+AsMutOpt<Scene>+Send+Sync> HasScene for T {}
|
||||||
|
|
||||||
|
impl ScenesView for App {
|
||||||
|
fn w_mid (&self) -> u16 {
|
||||||
|
(self.size.w() as u16).saturating_sub(self.w_side())
|
||||||
|
}
|
||||||
|
fn w_side (&self) -> u16 {
|
||||||
|
20
|
||||||
|
}
|
||||||
|
fn h_scenes (&self) -> u16 {
|
||||||
|
(self.size.h() as u16).saturating_sub(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A track consists of a sequencer and zero or more devices chained after it.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let track: tek::Track = Default::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Track {
|
||||||
|
/// Name of track
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Identifying color of track
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Preferred width of track column
|
||||||
|
pub width: usize,
|
||||||
|
/// MIDI sequencer state
|
||||||
|
pub sequencer: Sequencer,
|
||||||
|
/// Device chain
|
||||||
|
pub devices: Vec<Device>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Track {
|
||||||
|
/// Create a new track with only the default [Sequencer].
|
||||||
|
pub fn new (
|
||||||
|
name: &impl AsRef<str>,
|
||||||
|
color: Option<ItemTheme>,
|
||||||
|
jack: &Jack<'static>,
|
||||||
|
clock: Option<&Clock>,
|
||||||
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||||
|
midi_from: &[Connect],
|
||||||
|
midi_to: &[Connect],
|
||||||
|
) -> Usually<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name: name.as_ref().into(),
|
||||||
|
color: color.unwrap_or_default(),
|
||||||
|
sequencer: Sequencer::new(
|
||||||
|
format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to
|
||||||
|
)?,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn audio_ins (&self) -> &[AudioInput] {
|
||||||
|
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
pub fn audio_outs (&self) -> &[AudioOutput] {
|
||||||
|
self.devices.last().map(|x|x.audio_outs()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||||
|
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||||
|
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
||||||
|
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||||
|
|
||||||
|
pub fn per <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||||
|
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||||
|
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||||
|
) -> impl Draw<Tui> + 'a {
|
||||||
|
view_track_per(tracks, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sampler")]
|
||||||
|
impl Track {
|
||||||
|
|
||||||
|
/// Create a new track connecting the [Sequencer] to a [Sampler].
|
||||||
|
pub fn new_with_sampler (
|
||||||
|
name: &impl AsRef<str>,
|
||||||
|
color: Option<ItemTheme>,
|
||||||
|
jack: &Jack<'static>,
|
||||||
|
clock: Option<&Clock>,
|
||||||
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||||
|
midi_from: &[Connect],
|
||||||
|
midi_to: &[Connect],
|
||||||
|
audio_from: &[&[Connect];2],
|
||||||
|
audio_to: &[&[Connect];2],
|
||||||
|
) -> Usually<Self> {
|
||||||
|
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
|
||||||
|
let client_name = jack.with_client(|c|c.name().to_string());
|
||||||
|
let port_name = track.sequencer.midi_outs[0].port_name();
|
||||||
|
let connect = [Connect::exact(format!("{client_name}:{}", port_name))];
|
||||||
|
track.devices.push(Device::Sampler(Sampler::new(
|
||||||
|
jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to
|
||||||
|
)?));
|
||||||
|
Ok(track)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> {
|
||||||
|
for device in self.devices.iter() {
|
||||||
|
match device {
|
||||||
|
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> {
|
||||||
|
for device in self.devices.iter_mut() {
|
||||||
|
match device {
|
||||||
|
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasWidth for Track {
|
||||||
|
const MIN_WIDTH: usize = 9;
|
||||||
|
fn width_inc (&mut self) { self.width += 1; }
|
||||||
|
fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> {
|
||||||
|
fn tracks (&self) -> &Vec<Track> { self.as_ref() }
|
||||||
|
fn tracks_mut (&mut self) -> &mut Vec<Track> { self.as_mut() }
|
||||||
|
/// Run audio callbacks for every track and every device
|
||||||
|
fn process_tracks (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
for track in self.tracks_mut().iter_mut() {
|
||||||
|
if Control::Quit == Audio::process(&mut track.sequencer, client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
for device in track.devices.iter_mut() {
|
||||||
|
if Control::Quit == DeviceAudio(device).process(client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
fn track_longest_name (&self) -> usize {
|
||||||
|
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||||
|
}
|
||||||
|
/// Stop all playing clips
|
||||||
|
fn tracks_stop_all (&mut self) {
|
||||||
|
for track in self.tracks_mut().iter_mut() {
|
||||||
|
track.sequencer.enqueue_next(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Stop all playing clips
|
||||||
|
fn tracks_launch (&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>>) {
|
||||||
|
if let Some(clips) = clips {
|
||||||
|
for (clip, track) in clips.iter().zip(self.tracks_mut()) {
|
||||||
|
track.sequencer.enqueue_next(clip.as_ref());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for track in self.tracks_mut().iter_mut() {
|
||||||
|
track.sequencer.enqueue_next(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Spacing between tracks.
|
||||||
|
const TRACK_SPACING: usize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
|
||||||
|
fn track (&self) -> Option<&Track> { self.as_ref_opt() }
|
||||||
|
fn track_mut (&mut self) -> Option<&mut Track> { self.as_mut_opt() }
|
||||||
|
|
||||||
|
#[cfg(feature = "port")]
|
||||||
|
fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> + 'a {
|
||||||
|
view_midi_ins_status(theme, self.track())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "port")]
|
||||||
|
fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
||||||
|
view_midi_outs_status(theme, self.track())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "port")]
|
||||||
|
fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
|
view_audio_ins_status(theme, self.track())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "port")]
|
||||||
|
fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
|
view_audio_outs_status(theme, self.track())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasTrackScroll: HasTracks {
|
||||||
|
fn track_scroll (&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
|
||||||
|
/// Draw name of each track
|
||||||
|
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
|
view_track_names(
|
||||||
|
theme, self.tracks_with_sizes(), self.tracks().len(), self.scenes().len(), self.selection()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/// Draw outputs per track
|
||||||
|
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
|
||||||
|
view_track_outputs(
|
||||||
|
theme, self.tracks_with_sizes(), self.midi_outs().iter()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/// Draw inputs per track
|
||||||
|
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
|
let mut h = 0u16;
|
||||||
|
for track in self.tracks().iter() {
|
||||||
|
h = h.max(track.sequencer.midi_ins.len() as u16);
|
||||||
|
}
|
||||||
|
view_track_inputs(theme, self.tracks_with_sizes(), h)
|
||||||
|
}
|
||||||
|
/// Iterate over tracks with their corresponding sizes.
|
||||||
|
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
||||||
|
let _editor_width = self.editor().map(|e|e.size.w());
|
||||||
|
let _active_track = self.selection().track();
|
||||||
|
let mut x = 0;
|
||||||
|
let w = self.clips_size().w() as usize;
|
||||||
|
self.tracks().iter().enumerate().map_while(move |(index, track)|{
|
||||||
|
let width = track.width.max(8);
|
||||||
|
if x + width < w {
|
||||||
|
let data = (index, track, x, x + width);
|
||||||
|
x += width + Self::TRACK_SPACING;
|
||||||
|
Some(data)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasTrackScroll for Arrangement {
|
||||||
|
fn track_scroll (&self) -> usize { self.track_scroll }
|
||||||
|
}
|
||||||
|
|
||||||
|
def_command!(TrackCommand: |track: Track| {
|
||||||
|
Stop => { track.sequencer.enqueue_next(None); Ok(None) },
|
||||||
|
SetRec { rec: Option<bool> } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }),
|
||||||
|
SetMon { mon: Option<bool> } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }),
|
||||||
|
SetMute { mute: Option<bool> } => todo!(),
|
||||||
|
SetSolo { solo: Option<bool> } => todo!(),
|
||||||
|
SetSize { size: usize } => todo!(),
|
||||||
|
SetZoom { zoom: usize } => todo!(),
|
||||||
|
SetName { name: Arc<str> } => swap_value(&mut track.name, name, |name|Self::SetName { name }),
|
||||||
|
SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }),
|
||||||
|
});
|
||||||
|
|
||||||
|
impl<T: AsRef<Vec<Track>>+AsMut<Vec<Track>>> HasTracks for T {}
|
||||||
|
impl<T: AsRefOpt<Track>+AsMutOpt<Track>+Send+Sync> HasTrack for T {}
|
||||||
|
impl<T: ScenesView+HasMidiIns+HasMidiOuts+HasTrackScroll> TracksView for T {}
|
||||||
|
impl_has!(Vec<Track>: |self: Arrangement| self.tracks);
|
||||||
|
impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track());
|
||||||
|
impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut());
|
||||||
|
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
|
||||||
|
impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
|
||||||
|
#[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt());
|
||||||
|
#[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt());
|
||||||
|
|
||||||
|
impl Arrangement {
|
||||||
|
|
||||||
|
pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
||||||
|
view_inputs(
|
||||||
|
self.tracks_with_sizes(), self.midi_ins().as_slice()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
|
view_outputs(
|
||||||
|
theme, self.tracks_with_sizes(), self.midi_outs(), self.outputs_height()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
|
view_track_devices(
|
||||||
|
theme, self.tracks_with_sizes(), self.track(), self.devices_height()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn devices_height (&self) -> u16 {
|
||||||
|
let mut h = 2;
|
||||||
|
for track in self.tracks().iter() {
|
||||||
|
h = h.max(track.devices.len() * 2);
|
||||||
|
}
|
||||||
|
h as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs_height (&self) -> u16 {
|
||||||
|
let mut h = 1;
|
||||||
|
for output in self.midi_outs().iter() {
|
||||||
|
h += 1 + output.connections.len();
|
||||||
|
}
|
||||||
|
h as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple tracks
|
||||||
|
pub fn tracks_add (
|
||||||
|
&mut self, count: usize, width: Option<usize>, mins: &[Connect], mouts: &[Connect],
|
||||||
|
) -> Usually<()> {
|
||||||
|
let track_color_1 = ItemColor::random();
|
||||||
|
let track_color_2 = ItemColor::random();
|
||||||
|
for i in 0..count {
|
||||||
|
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
|
||||||
|
let track = self.track_add(None, Some(color), mins, mouts)?.1;
|
||||||
|
if let Some(width) = width {
|
||||||
|
track.width = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a track
|
||||||
|
pub fn track_add (
|
||||||
|
&mut self,
|
||||||
|
name: Option<&str>,
|
||||||
|
color: Option<ItemTheme>,
|
||||||
|
mins: &[Connect],
|
||||||
|
mouts: &[Connect],
|
||||||
|
) -> Usually<(usize, &mut Track)> {
|
||||||
|
let name: Arc<str> = name.map_or_else(
|
||||||
|
||format!("trk{:02}", self.track_last).into(),
|
||||||
|
|x|x.to_string().into()
|
||||||
|
);
|
||||||
|
self.track_last += 1;
|
||||||
|
let track = Track {
|
||||||
|
width: (name.len() + 2).max(12),
|
||||||
|
color: color.unwrap_or_else(ItemTheme::random),
|
||||||
|
sequencer: Sequencer::new(
|
||||||
|
&format!("{name}"),
|
||||||
|
self.jack(),
|
||||||
|
Some(self.clock()),
|
||||||
|
None,
|
||||||
|
mins,
|
||||||
|
mouts
|
||||||
|
)?,
|
||||||
|
name,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
self.tracks_mut().push(track);
|
||||||
|
let len = self.tracks().len();
|
||||||
|
let index = len - 1;
|
||||||
|
for scene in self.scenes_mut().iter_mut() {
|
||||||
|
while scene.clips.len() < len {
|
||||||
|
scene.clips.push(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((index, &mut self.tracks_mut()[index]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasTrackScroll for App {
|
||||||
|
fn track_scroll (&self) -> usize {
|
||||||
|
self.project.track_scroll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
||||||
|
track.width as u16
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// TODO: Preserve the generic passthru syntax;
|
|
||||||
/// remove this macro (only used twice) and potentially the trait.
|
|
||||||
#[macro_export] macro_rules! impl_has_clips {
|
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? {
|
|
||||||
fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> {
|
|
||||||
$cb.read().unwrap()
|
|
||||||
}
|
|
||||||
fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> {
|
|
||||||
$cb.write().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_clip {
|
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
|
|
||||||
fn clip (&$self) -> Option<Arc<RwLock<MidiClip>>> { $cb }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ClipsView: TracksView + ScenesView {
|
|
||||||
/// Draw clips per scene
|
|
||||||
fn view_scenes_clips <'a> (&'a self) -> impl Draw<Tui> + 'a {
|
|
||||||
view_scenes_clips(
|
|
||||||
||self.scenes_with_sizes(),
|
|
||||||
self.tracks_with_sizes(),
|
|
||||||
self.selection(),
|
|
||||||
self.editor(),
|
|
||||||
self.clips_size(),
|
|
||||||
self.is_editing(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def_command!(ClipCommand: |clip: MidiClip| {
|
|
||||||
SetColor { color: Option<ItemTheme> } => {
|
|
||||||
//(SetColor [t: usize, s: usize, c: ItemTheme]
|
|
||||||
//clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o)))));
|
|
||||||
//("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random())))
|
|
||||||
todo!()
|
|
||||||
},
|
|
||||||
SetLoop { looping: Option<bool> } => {
|
|
||||||
//(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}"))
|
|
||||||
//("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
impl Arrangement {
|
|
||||||
/// Put a clip in a slot
|
|
||||||
pub fn clip_put (
|
|
||||||
&mut self, track: usize, scene: usize, clip: Option<Arc<RwLock<MidiClip>>>
|
|
||||||
) -> Option<Arc<RwLock<MidiClip>>> {
|
|
||||||
let old = self.scenes[scene].clips[track].clone();
|
|
||||||
self.scenes[scene].clips[track] = clip;
|
|
||||||
old
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the color of a clip, returning the previous one
|
|
||||||
pub fn clip_set_color (
|
|
||||||
&self, track: usize, scene: usize, color: ItemTheme
|
|
||||||
) -> Option<ItemTheme> {
|
|
||||||
self.scenes[scene].clips[track].as_ref().map(|clip|{
|
|
||||||
let mut clip = clip.write().unwrap();
|
|
||||||
let old = clip.color.clone();
|
|
||||||
clip.color = color.clone();
|
|
||||||
panic!("{color:?} {old:?}");
|
|
||||||
//old
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle looping for the active clip
|
|
||||||
pub fn toggle_loop (&mut self) {
|
|
||||||
if let Some(clip) = self.selected_clip() {
|
|
||||||
clip.write().unwrap().toggle_loop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasClipsSize {
|
|
||||||
fn clips_size (&self) -> &Sizer;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasClipsSize for App {
|
|
||||||
fn clips_size (&self) -> &Sizer { &self.project.size_inner }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasClipsSize for Arrangement {
|
|
||||||
fn clips_size (&self) -> &Sizer { &self.size_inner }
|
|
||||||
}
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Default scene height.
|
|
||||||
pub const H_SCENE: usize = 2;
|
|
||||||
|
|
||||||
/// Default editor height.
|
|
||||||
pub const H_EDITOR: usize = 15;
|
|
||||||
|
|
||||||
/// A scene consists of a set of clips to play together.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let scene: tek::Scene = Default::default();
|
|
||||||
/// let _ = scene.pulses();
|
|
||||||
/// let _ = scene.is_playing(&[]);
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Scene {
|
|
||||||
/// Name of scene
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Identifying color of scene
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Clips in scene, one per track
|
|
||||||
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scene {
|
|
||||||
/// Returns the pulse length of the longest clip in the scene
|
|
||||||
pub fn pulses (&self) -> usize {
|
|
||||||
self.clips.iter().fold(0, |a, p|{
|
|
||||||
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/// Returns true if all clips in the scene are
|
|
||||||
/// currently playing on the given collection of tracks.
|
|
||||||
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
|
||||||
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
|
|
||||||
.all(|(track_index, clip)|match clip {
|
|
||||||
Some(c) => tracks
|
|
||||||
.get(track_index)
|
|
||||||
.map(|track|{
|
|
||||||
if let Some((_, Some(clip))) = track.sequencer().play_clip() {
|
|
||||||
*clip.read().unwrap() == *c.read().unwrap()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(false),
|
|
||||||
None => true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
|
|
||||||
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
|
||||||
}
|
|
||||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
|
||||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
|
||||||
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
|
||||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync {
|
|
||||||
fn h_scenes (&self) -> u16;
|
|
||||||
fn w_side (&self) -> u16;
|
|
||||||
fn w_mid (&self) -> u16;
|
|
||||||
|
|
||||||
fn view_scenes_names (&self) -> impl Draw<Tui> {
|
|
||||||
view_scenes_names(
|
|
||||||
self.scenes_with_sizes(), self.selection(), self.editor(), self.is_editing()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> {
|
|
||||||
let mut y = 0;
|
|
||||||
self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{
|
|
||||||
let height = if self.selection().scene() == Some(s) && self.editor().is_some() {
|
|
||||||
8
|
|
||||||
} else {
|
|
||||||
H_SCENE
|
|
||||||
};
|
|
||||||
if y + height <= self.clips_size().h() as usize {
|
|
||||||
let data = (s, scene, y, y + height);
|
|
||||||
y += height;
|
|
||||||
Some(data)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasSceneScroll: HasScenes {
|
|
||||||
fn scene_scroll (&self) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasScene: AsRefOpt<Scene> + AsMutOpt<Scene> {
|
|
||||||
fn scene_mut (&mut self) -> Option<&mut Scene> { self.as_mut_opt() }
|
|
||||||
fn scene (&self) -> Option<&Scene> { self.as_ref_opt() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
|
|
||||||
fn scenes (&self) -> &Vec<Scene> { self.as_ref() }
|
|
||||||
fn scenes_mut (&mut self) -> &mut Vec<Scene> { self.as_mut() }
|
|
||||||
/// Generate the default name for a new scene
|
|
||||||
fn scene_default_name (&self) -> Arc<str> { format!("s{:3>}", self.scenes().len() + 1).into() }
|
|
||||||
fn scene_longest_name (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) }
|
|
||||||
/// Add multiple scenes
|
|
||||||
fn scenes_add (&mut self, n: usize) -> Usually<()> where Self: HasTracks {
|
|
||||||
let scene_color_1 = ItemColor::random();
|
|
||||||
let scene_color_2 = ItemColor::random();
|
|
||||||
for i in 0..n {
|
|
||||||
let _ = self.scene_add(None, Some(
|
|
||||||
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Add a scene
|
|
||||||
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemTheme>)
|
|
||||||
-> Usually<(usize, &mut Scene)> where Self: HasTracks
|
|
||||||
{
|
|
||||||
let scene = Scene {
|
|
||||||
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
|
||||||
clips: vec![None;self.tracks().len()],
|
|
||||||
color: color.unwrap_or_else(ItemTheme::random),
|
|
||||||
};
|
|
||||||
self.scenes_mut().push(scene);
|
|
||||||
let index = self.scenes().len() - 1;
|
|
||||||
Ok((index, &mut self.scenes_mut()[index]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasSceneScroll for Arrangement {
|
|
||||||
fn scene_scroll (&self) -> usize { self.scene_scroll }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasSceneScroll for App {
|
|
||||||
fn scene_scroll (&self) -> usize { self.project.scene_scroll() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScenesView for Arrangement {
|
|
||||||
fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) }
|
|
||||||
fn w_side (&self) -> u16 { (self.size.w() as u16 * 2 / 10).max(20) }
|
|
||||||
fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type SceneWith<'a, T> =
|
|
||||||
(usize, &'a Scene, usize, usize, T);
|
|
||||||
|
|
||||||
def_command!(SceneCommand: |scene: Scene| {
|
|
||||||
SetSize { size: usize } => { todo!() },
|
|
||||||
SetZoom { size: usize } => { todo!() },
|
|
||||||
SetName { name: Arc<str> } =>
|
|
||||||
swap_value(&mut scene.name, name, |name|Self::SetName{name}),
|
|
||||||
SetColor { color: ItemTheme } =>
|
|
||||||
swap_value(&mut scene.color, color, |color|Self::SetColor{color}),
|
|
||||||
});
|
|
||||||
|
|
||||||
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt());
|
|
||||||
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());
|
|
||||||
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene());
|
|
||||||
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut());
|
|
||||||
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
|
|
||||||
impl<T: TracksView+ScenesView+Send+Sync> ClipsView for T {}
|
|
||||||
impl<T: AsRef<Vec<Scene>>+AsMut<Vec<Scene>>> HasScenes for T {}
|
|
||||||
impl<T: AsRefOpt<Scene>+AsMutOpt<Scene>+Send+Sync> HasScene for T {}
|
|
||||||
|
|
||||||
impl ScenesView for App {
|
|
||||||
fn w_mid (&self) -> u16 {
|
|
||||||
(self.size.w() as u16).saturating_sub(self.w_side())
|
|
||||||
}
|
|
||||||
fn w_side (&self) -> u16 {
|
|
||||||
20
|
|
||||||
}
|
|
||||||
fn h_scenes (&self) -> u16 {
|
|
||||||
(self.size.h() as u16).saturating_sub(20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Represents the current user selection in the arranger
|
|
||||||
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
|
|
||||||
#[default]
|
|
||||||
/// Nothing is selected
|
|
||||||
Nothing,
|
|
||||||
/// The whole mix is selected
|
|
||||||
Mix,
|
|
||||||
/// A MIDI input is selected.
|
|
||||||
Input(usize),
|
|
||||||
/// A MIDI output is selected.
|
|
||||||
Output(usize),
|
|
||||||
/// A scene is selected.
|
|
||||||
#[cfg(feature = "scene")] Scene(usize),
|
|
||||||
/// A track is selected.
|
|
||||||
#[cfg(feature = "track")] Track(usize),
|
|
||||||
/// A clip (track × scene) is selected.
|
|
||||||
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
|
|
||||||
/// A track's MIDI input connection is selected.
|
|
||||||
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
|
|
||||||
/// A track's MIDI output connection is selected.
|
|
||||||
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
|
|
||||||
/// A track device slot is selected.
|
|
||||||
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Selection {
|
|
||||||
pub fn describe (
|
|
||||||
&self,
|
|
||||||
#[cfg(feature = "track")] tracks: &[Track],
|
|
||||||
#[cfg(feature = "scene")] scenes: &[Scene],
|
|
||||||
) -> Arc<str> {
|
|
||||||
use Selection::*;
|
|
||||||
format!("{}", match self {
|
|
||||||
Mix => "Everything".to_string(),
|
|
||||||
#[cfg(feature = "scene")] Scene(s) =>
|
|
||||||
scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()),
|
|
||||||
#[cfg(feature = "track")] Track(t) =>
|
|
||||||
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()),
|
|
||||||
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
|
|
||||||
(Some(_), Some(s)) => match s.clip(*track) {
|
|
||||||
Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name),
|
|
||||||
None => format!("T{track} S{scene}: Empty")
|
|
||||||
},
|
|
||||||
_ => format!("T{track} S{scene}: Empty"),
|
|
||||||
},
|
|
||||||
_ => todo!()
|
|
||||||
}).into()
|
|
||||||
}
|
|
||||||
#[cfg(feature = "scene")] pub fn scene (&self) -> Option<usize> {
|
|
||||||
use Selection::*;
|
|
||||||
match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None }
|
|
||||||
}
|
|
||||||
#[cfg(feature = "scene")] pub fn select_scene (&self, scene_count: usize) -> Self {
|
|
||||||
use Selection::*;
|
|
||||||
match self {
|
|
||||||
Mix | Track(_) => Scene(0),
|
|
||||||
Scene(s) => Scene((s + 1) % scene_count),
|
|
||||||
TrackClip { scene, .. } => Track(*scene),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "scene")] pub fn select_scene_next (&self, len: usize) -> Self {
|
|
||||||
use Selection::*;
|
|
||||||
match self {
|
|
||||||
Mix => Scene(0),
|
|
||||||
Track(t) => TrackClip { track: *t, scene: 0 },
|
|
||||||
Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix },
|
|
||||||
TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) },
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "scene")] pub fn select_scene_prev (&self) -> Self {
|
|
||||||
use Selection::*;
|
|
||||||
match self {
|
|
||||||
Mix | Scene(0) => Mix,
|
|
||||||
Scene(s) => Scene(s - 1),
|
|
||||||
Track(t) => Track(*t),
|
|
||||||
TrackClip { track, scene: 0 } => Track(*track),
|
|
||||||
TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 },
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "track")] pub fn track (&self) -> Option<usize> {
|
|
||||||
use Selection::*;
|
|
||||||
if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self {
|
|
||||||
Some(*track)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "track")] pub fn select_track (&self, track_count: usize) -> Self {
|
|
||||||
use Selection::*;
|
|
||||||
match self {
|
|
||||||
Mix => Track(0),
|
|
||||||
Scene(_) => Mix,
|
|
||||||
Track(t) => Track((t + 1) % track_count),
|
|
||||||
TrackClip { track, .. } => Track(*track),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "track")] pub fn select_track_next (&self, len: usize) -> Self {
|
|
||||||
use Selection::*;
|
|
||||||
match self {
|
|
||||||
Mix => Track(0),
|
|
||||||
Scene(s) => TrackClip { track: 0, scene: *s },
|
|
||||||
Track(t) => if t + 1 < len { Track(t + 1) } else { Mix },
|
|
||||||
TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) },
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "track")] pub fn select_track_prev (&self) -> Self {
|
|
||||||
use Selection::*;
|
|
||||||
match self {
|
|
||||||
Mix => Mix,
|
|
||||||
Scene(s) => Scene(*s),
|
|
||||||
Track(0) => Mix,
|
|
||||||
Track(t) => Track(t - 1),
|
|
||||||
TrackClip { track: 0, scene } => Scene(*scene),
|
|
||||||
TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene },
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <T: AsRef<Selection>+AsMut<Selection>> HasSelection for T {}
|
|
||||||
|
|
||||||
pub trait HasSelection: AsRef<Selection> + AsMut<Selection> {
|
|
||||||
fn selection (&self) -> &Selection { self.as_ref() }
|
|
||||||
fn selection_mut (&mut self) -> &mut Selection { self.as_mut() }
|
|
||||||
/// Get the active track
|
|
||||||
#[cfg(feature = "track")]
|
|
||||||
fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
|
|
||||||
let index = self.selection().track()?;
|
|
||||||
self.tracks().get(index)
|
|
||||||
}
|
|
||||||
/// Get a mutable reference to the active track
|
|
||||||
#[cfg(feature = "track")]
|
|
||||||
fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
|
|
||||||
let index = self.selection().track()?;
|
|
||||||
self.tracks_mut().get_mut(index)
|
|
||||||
}
|
|
||||||
/// Get the active scene
|
|
||||||
#[cfg(feature = "scene")]
|
|
||||||
fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
|
|
||||||
let index = self.selection().scene()?;
|
|
||||||
self.scenes().get(index)
|
|
||||||
}
|
|
||||||
/// Get a mutable reference to the active scene
|
|
||||||
#[cfg(feature = "scene")]
|
|
||||||
fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
|
|
||||||
let index = self.selection().scene()?;
|
|
||||||
self.scenes_mut().get_mut(index)
|
|
||||||
}
|
|
||||||
/// Get the active clip
|
|
||||||
#[cfg(feature = "clip")]
|
|
||||||
fn selected_clip (&self) -> Option<Arc<RwLock<MidiClip>>> where Self: HasScenes + HasTracks {
|
|
||||||
self.selected_scene()?.clips.get(self.selection().track()?)?.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,350 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// A track consists of a sequencer and zero or more devices chained after it.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let track: tek::Track = Default::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Track {
|
|
||||||
/// Name of track
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Identifying color of track
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Preferred width of track column
|
|
||||||
pub width: usize,
|
|
||||||
/// MIDI sequencer state
|
|
||||||
pub sequencer: Sequencer,
|
|
||||||
/// Device chain
|
|
||||||
pub devices: Vec<Device>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Track {
|
|
||||||
/// Create a new track with only the default [Sequencer].
|
|
||||||
pub fn new (
|
|
||||||
name: &impl AsRef<str>,
|
|
||||||
color: Option<ItemTheme>,
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
clock: Option<&Clock>,
|
|
||||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
|
||||||
midi_from: &[Connect],
|
|
||||||
midi_to: &[Connect],
|
|
||||||
) -> Usually<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
name: name.as_ref().into(),
|
|
||||||
color: color.unwrap_or_default(),
|
|
||||||
sequencer: Sequencer::new(
|
|
||||||
format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to
|
|
||||||
)?,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn audio_ins (&self) -> &[AudioInput] {
|
|
||||||
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
|
|
||||||
}
|
|
||||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
|
||||||
self.devices.last().map(|x|x.audio_outs()).unwrap_or_default()
|
|
||||||
}
|
|
||||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
|
||||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
|
||||||
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
|
||||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
|
||||||
|
|
||||||
pub fn per <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
|
||||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
|
||||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
|
||||||
) -> impl Draw<Tui> + 'a {
|
|
||||||
view_track_per(tracks, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sampler")]
|
|
||||||
impl Track {
|
|
||||||
|
|
||||||
/// Create a new track connecting the [Sequencer] to a [Sampler].
|
|
||||||
pub fn new_with_sampler (
|
|
||||||
name: &impl AsRef<str>,
|
|
||||||
color: Option<ItemTheme>,
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
clock: Option<&Clock>,
|
|
||||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
|
||||||
midi_from: &[Connect],
|
|
||||||
midi_to: &[Connect],
|
|
||||||
audio_from: &[&[Connect];2],
|
|
||||||
audio_to: &[&[Connect];2],
|
|
||||||
) -> Usually<Self> {
|
|
||||||
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
|
|
||||||
let client_name = jack.with_client(|c|c.name().to_string());
|
|
||||||
let port_name = track.sequencer.midi_outs[0].port_name();
|
|
||||||
let connect = [Connect::exact(format!("{client_name}:{}", port_name))];
|
|
||||||
track.devices.push(Device::Sampler(Sampler::new(
|
|
||||||
jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to
|
|
||||||
)?));
|
|
||||||
Ok(track)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> {
|
|
||||||
for device in self.devices.iter() {
|
|
||||||
match device {
|
|
||||||
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> {
|
|
||||||
for device in self.devices.iter_mut() {
|
|
||||||
match device {
|
|
||||||
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasWidth for Track {
|
|
||||||
const MIN_WIDTH: usize = 9;
|
|
||||||
fn width_inc (&mut self) { self.width += 1; }
|
|
||||||
fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> {
|
|
||||||
fn tracks (&self) -> &Vec<Track> { self.as_ref() }
|
|
||||||
fn tracks_mut (&mut self) -> &mut Vec<Track> { self.as_mut() }
|
|
||||||
/// Run audio callbacks for every track and every device
|
|
||||||
fn process_tracks (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
for track in self.tracks_mut().iter_mut() {
|
|
||||||
if Control::Quit == Audio::process(&mut track.sequencer, client, scope) {
|
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
for device in track.devices.iter_mut() {
|
|
||||||
if Control::Quit == DeviceAudio(device).process(client, scope) {
|
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
fn track_longest_name (&self) -> usize {
|
|
||||||
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
|
|
||||||
}
|
|
||||||
/// Stop all playing clips
|
|
||||||
fn tracks_stop_all (&mut self) {
|
|
||||||
for track in self.tracks_mut().iter_mut() {
|
|
||||||
track.sequencer.enqueue_next(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Stop all playing clips
|
|
||||||
fn tracks_launch (&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>>) {
|
|
||||||
if let Some(clips) = clips {
|
|
||||||
for (clip, track) in clips.iter().zip(self.tracks_mut()) {
|
|
||||||
track.sequencer.enqueue_next(clip.as_ref());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for track in self.tracks_mut().iter_mut() {
|
|
||||||
track.sequencer.enqueue_next(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Spacing between tracks.
|
|
||||||
const TRACK_SPACING: usize = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
|
|
||||||
fn track (&self) -> Option<&Track> { self.as_ref_opt() }
|
|
||||||
fn track_mut (&mut self) -> Option<&mut Track> { self.as_mut_opt() }
|
|
||||||
|
|
||||||
#[cfg(feature = "port")]
|
|
||||||
fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> + 'a {
|
|
||||||
view_midi_ins_status(theme, self.track())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "port")]
|
|
||||||
fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
|
||||||
view_midi_outs_status(theme, self.track())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "port")]
|
|
||||||
fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
|
||||||
view_audio_ins_status(theme, self.track())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "port")]
|
|
||||||
fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
|
||||||
view_audio_outs_status(theme, self.track())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasTrackScroll: HasTracks {
|
|
||||||
fn track_scroll (&self) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
|
|
||||||
/// Draw name of each track
|
|
||||||
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
|
||||||
view_track_names(
|
|
||||||
theme, self.tracks_with_sizes(), self.tracks().len(), self.scenes().len(), self.selection()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// Draw outputs per track
|
|
||||||
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
|
|
||||||
view_track_outputs(
|
|
||||||
theme, self.tracks_with_sizes(), self.midi_outs().iter()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// Draw inputs per track
|
|
||||||
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> {
|
|
||||||
let mut h = 0u16;
|
|
||||||
for track in self.tracks().iter() {
|
|
||||||
h = h.max(track.sequencer.midi_ins.len() as u16);
|
|
||||||
}
|
|
||||||
view_track_inputs(theme, self.tracks_with_sizes(), h)
|
|
||||||
}
|
|
||||||
/// Iterate over tracks with their corresponding sizes.
|
|
||||||
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
|
||||||
let _editor_width = self.editor().map(|e|e.size.w());
|
|
||||||
let _active_track = self.selection().track();
|
|
||||||
let mut x = 0;
|
|
||||||
let w = self.clips_size().w() as usize;
|
|
||||||
self.tracks().iter().enumerate().map_while(move |(index, track)|{
|
|
||||||
let width = track.width.max(8);
|
|
||||||
if x + width < w {
|
|
||||||
let data = (index, track, x, x + width);
|
|
||||||
x += width + Self::TRACK_SPACING;
|
|
||||||
Some(data)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasTrackScroll for Arrangement {
|
|
||||||
fn track_scroll (&self) -> usize { self.track_scroll }
|
|
||||||
}
|
|
||||||
|
|
||||||
def_command!(TrackCommand: |track: Track| {
|
|
||||||
Stop => { track.sequencer.enqueue_next(None); Ok(None) },
|
|
||||||
SetRec { rec: Option<bool> } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }),
|
|
||||||
SetMon { mon: Option<bool> } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }),
|
|
||||||
SetMute { mute: Option<bool> } => todo!(),
|
|
||||||
SetSolo { solo: Option<bool> } => todo!(),
|
|
||||||
SetSize { size: usize } => todo!(),
|
|
||||||
SetZoom { zoom: usize } => todo!(),
|
|
||||||
SetName { name: Arc<str> } => swap_value(&mut track.name, name, |name|Self::SetName { name }),
|
|
||||||
SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }),
|
|
||||||
});
|
|
||||||
|
|
||||||
impl<T: AsRef<Vec<Track>>+AsMut<Vec<Track>>> HasTracks for T {}
|
|
||||||
impl<T: AsRefOpt<Track>+AsMutOpt<Track>+Send+Sync> HasTrack for T {}
|
|
||||||
impl<T: ScenesView+HasMidiIns+HasMidiOuts+HasTrackScroll> TracksView for T {}
|
|
||||||
impl_has!(Vec<Track>: |self: Arrangement| self.tracks);
|
|
||||||
impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track());
|
|
||||||
impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut());
|
|
||||||
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
|
|
||||||
impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
|
|
||||||
#[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt());
|
|
||||||
#[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt());
|
|
||||||
|
|
||||||
impl Arrangement {
|
|
||||||
|
|
||||||
pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
|
||||||
view_inputs(
|
|
||||||
self.tracks_with_sizes(), self.midi_ins().as_slice()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
|
||||||
view_outputs(
|
|
||||||
theme, self.tracks_with_sizes(), self.midi_outs(), self.outputs_height()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
|
||||||
view_track_devices(
|
|
||||||
theme, self.tracks_with_sizes(), self.track(), self.devices_height()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn devices_height (&self) -> u16 {
|
|
||||||
let mut h = 2;
|
|
||||||
for track in self.tracks().iter() {
|
|
||||||
h = h.max(track.devices.len() * 2);
|
|
||||||
}
|
|
||||||
h as u16
|
|
||||||
}
|
|
||||||
|
|
||||||
fn outputs_height (&self) -> u16 {
|
|
||||||
let mut h = 1;
|
|
||||||
for output in self.midi_outs().iter() {
|
|
||||||
h += 1 + output.connections.len();
|
|
||||||
}
|
|
||||||
h as u16
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add multiple tracks
|
|
||||||
pub fn tracks_add (
|
|
||||||
&mut self, count: usize, width: Option<usize>, mins: &[Connect], mouts: &[Connect],
|
|
||||||
) -> Usually<()> {
|
|
||||||
let track_color_1 = ItemColor::random();
|
|
||||||
let track_color_2 = ItemColor::random();
|
|
||||||
for i in 0..count {
|
|
||||||
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
|
|
||||||
let track = self.track_add(None, Some(color), mins, mouts)?.1;
|
|
||||||
if let Some(width) = width {
|
|
||||||
track.width = width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a track
|
|
||||||
pub fn track_add (
|
|
||||||
&mut self,
|
|
||||||
name: Option<&str>,
|
|
||||||
color: Option<ItemTheme>,
|
|
||||||
mins: &[Connect],
|
|
||||||
mouts: &[Connect],
|
|
||||||
) -> Usually<(usize, &mut Track)> {
|
|
||||||
let name: Arc<str> = name.map_or_else(
|
|
||||||
||format!("trk{:02}", self.track_last).into(),
|
|
||||||
|x|x.to_string().into()
|
|
||||||
);
|
|
||||||
self.track_last += 1;
|
|
||||||
let track = Track {
|
|
||||||
width: (name.len() + 2).max(12),
|
|
||||||
color: color.unwrap_or_else(ItemTheme::random),
|
|
||||||
sequencer: Sequencer::new(
|
|
||||||
&format!("{name}"),
|
|
||||||
self.jack(),
|
|
||||||
Some(self.clock()),
|
|
||||||
None,
|
|
||||||
mins,
|
|
||||||
mouts
|
|
||||||
)?,
|
|
||||||
name,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
self.tracks_mut().push(track);
|
|
||||||
let len = self.tracks().len();
|
|
||||||
let index = len - 1;
|
|
||||||
for scene in self.scenes_mut().iter_mut() {
|
|
||||||
while scene.clips.len() < len {
|
|
||||||
scene.clips.push(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok((index, &mut self.tracks_mut()[index]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasTrackScroll for App {
|
|
||||||
fn track_scroll (&self) -> usize {
|
|
||||||
self.project.track_scroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
|
||||||
track.width as u16
|
|
||||||
}
|
|
||||||
|
|
@ -56,11 +56,14 @@ impl Dialog {
|
||||||
pub fn welcome () -> Self {
|
pub fn welcome () -> Self {
|
||||||
Self::Menu(1, MenuItems([
|
Self::Menu(1, MenuItems([
|
||||||
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||||
MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({
|
MenuItem("New session".into(), Arc::new(Box::new(|app|Ok({
|
||||||
app.dialog = Dialog::None;
|
app.dialog = Dialog::None;
|
||||||
app.mode = app.config.modes.get(":arranger").unwrap();
|
app.mode = app.config.modes.get(":arranger").unwrap();
|
||||||
})))),
|
})))),
|
||||||
MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))),
|
MenuItem("Load session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||||
|
MenuItem("Exit".into(), Arc::new(Box::new(|_|Ok({
|
||||||
|
()
|
||||||
|
})))),
|
||||||
].into()))
|
].into()))
|
||||||
}
|
}
|
||||||
/// FIXME: generalize
|
/// FIXME: generalize
|
||||||
|
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use ConnectName::*;
|
|
||||||
use ConnectScope::*;
|
|
||||||
use ConnectStatus::*;
|
|
||||||
|
|
||||||
mod audio; pub use self::audio::*;
|
|
||||||
mod midi; pub use self::midi::*;
|
|
||||||
mod connect; pub use self::connect::*;
|
|
||||||
mod register; pub use self::register::*;
|
|
||||||
|
|
||||||
pub trait JackPort: HasJack<'static> {
|
|
||||||
type Port: PortSpec + Default;
|
|
||||||
type Pair: PortSpec + Default;
|
|
||||||
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized;
|
|
||||||
|
|
||||||
fn register (jack: &Jack<'static>, name: &impl AsRef<str>) -> Usually<Port<Self::Port>> {
|
|
||||||
jack.with_client(|c|c.register_port::<Self::Port>(name.as_ref(), Default::default()))
|
|
||||||
.map_err(|e|e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn port_name (&self) -> &Arc<str>;
|
|
||||||
|
|
||||||
fn connections (&self) -> &[Connect];
|
|
||||||
|
|
||||||
fn port (&self) -> &Port<Self::Port>;
|
|
||||||
|
|
||||||
fn port_mut (&mut self) -> &mut Port<Self::Port>;
|
|
||||||
|
|
||||||
fn into_port (self) -> Port<Self::Port> where Self: Sized;
|
|
||||||
|
|
||||||
fn close (self) -> Usually<()> where Self: Sized {
|
|
||||||
let jack = self.jack().clone();
|
|
||||||
Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec<String> {
|
|
||||||
self.with_client(|c|c.ports(re_name, re_type, flags))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|c|c.port_by_id(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn port_by_name (&self, name: impl AsRef<str>) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|c|c.port_by_name(name.as_ref()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect_to_matching <'k> (&'k self) -> Usually<()> {
|
|
||||||
for connect in self.connections().iter() {
|
|
||||||
match &connect.name {
|
|
||||||
Some(Exact(name)) => {
|
|
||||||
*connect.status.write().unwrap() = self.connect_exact(name)?;
|
|
||||||
},
|
|
||||||
Some(RegExp(re)) => {
|
|
||||||
*connect.status.write().unwrap() = self.connect_regexp(re, connect.scope)?;
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect_exact <'k> (&'k self, name: &str) ->
|
|
||||||
Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>
|
|
||||||
{
|
|
||||||
self.with_client(move|c|{
|
|
||||||
let mut status = vec![];
|
|
||||||
for port in c.ports(None, None, PortFlags::empty()).iter() {
|
|
||||||
if port.as_str() == &*name {
|
|
||||||
if let Some(port) = c.port_by_name(port.as_str()) {
|
|
||||||
let port_status = self.connect_to_unowned(&port)?;
|
|
||||||
let name = port.name()?.into();
|
|
||||||
status.push((port, name, port_status));
|
|
||||||
if port_status == Connected {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(status)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect_regexp <'k> (
|
|
||||||
&'k self, re: &str, scope: Option<ConnectScope>
|
|
||||||
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
|
|
||||||
self.with_client(move|c|{
|
|
||||||
let mut status = vec![];
|
|
||||||
let ports = c.ports(Some(&re), None, PortFlags::empty());
|
|
||||||
for port in ports.iter() {
|
|
||||||
if let Some(port) = c.port_by_name(port.as_str()) {
|
|
||||||
let port_status = self.connect_to_unowned(&port)?;
|
|
||||||
let name = port.name()?.into();
|
|
||||||
status.push((port, name, port_status));
|
|
||||||
if port_status == Connected && scope == Some(One) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(status)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Connect to a matching port by name. */
|
|
||||||
fn connect_to_name (&self, name: impl AsRef<str>) -> Usually<ConnectStatus> {
|
|
||||||
self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) {
|
|
||||||
self.connect_to_unowned(port)
|
|
||||||
} else {
|
|
||||||
Ok(Missing)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Connect to a matching port by reference. */
|
|
||||||
fn connect_to_unowned (&self, port: &Port<Unowned>) -> Usually<ConnectStatus> {
|
|
||||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) {
|
|
||||||
Connected
|
|
||||||
} else if let Ok(_) = c.connect_ports(port, self.port()) {
|
|
||||||
Connected
|
|
||||||
} else {
|
|
||||||
Mismatch
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Connect to an owned matching port by reference. */
|
|
||||||
fn connect_to_owned (&self, port: &Port<Self::Pair>) -> Usually<ConnectStatus> {
|
|
||||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) {
|
|
||||||
Connected
|
|
||||||
} else if let Ok(_) = c.connect_ports(port, self.port()) {
|
|
||||||
Connected
|
|
||||||
} else {
|
|
||||||
Mismatch
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
def_command!(AudioInputCommand: |port: AudioInput| {
|
|
||||||
Close => todo!(),
|
|
||||||
Connect { audio_out: Arc<str> } => todo!(),
|
|
||||||
});
|
|
||||||
|
|
||||||
def_command!(AudioOutputCommand: |port: AudioOutput| {
|
|
||||||
Close => todo!(),
|
|
||||||
Connect { audio_in: Arc<str> } => todo!(),
|
|
||||||
});
|
|
||||||
|
|
||||||
impl HasJack<'static> for AudioInput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
|
||||||
impl HasJack<'static> for AudioOutput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
|
||||||
|
|
||||||
/// Audio input port.
|
|
||||||
#[derive(Debug)] pub struct AudioInput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<AudioIn>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Audio output port.
|
|
||||||
#[derive(Debug)] pub struct AudioOutput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<AudioOut>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl JackPort for AudioInput {
|
|
||||||
type Port = AudioIn;
|
|
||||||
type Pair = AudioOut;
|
|
||||||
fn port_name (&self) -> &Arc<str> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
fn port (&self) -> &Port<Self::Port> {
|
|
||||||
&self.port
|
|
||||||
}
|
|
||||||
fn port_mut (&mut self) -> &mut Port<Self::Port> {
|
|
||||||
&mut self.port
|
|
||||||
}
|
|
||||||
fn into_port (self) -> Port<Self::Port> {
|
|
||||||
self.port
|
|
||||||
}
|
|
||||||
fn connections (&self) -> &[Connect] {
|
|
||||||
self.connections.as_slice()
|
|
||||||
}
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized
|
|
||||||
{
|
|
||||||
let port = Self {
|
|
||||||
port: Self::register(jack, name)?,
|
|
||||||
jack: jack.clone(),
|
|
||||||
name: name.as_ref().into(),
|
|
||||||
connections: connect.to_vec()
|
|
||||||
};
|
|
||||||
port.connect_to_matching()?;
|
|
||||||
Ok(port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JackPort for AudioOutput {
|
|
||||||
type Port = AudioOut;
|
|
||||||
type Pair = AudioIn;
|
|
||||||
fn port_name (&self) -> &Arc<str> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
fn port (&self) -> &Port<Self::Port> {
|
|
||||||
&self.port
|
|
||||||
}
|
|
||||||
fn port_mut (&mut self) -> &mut Port<Self::Port> {
|
|
||||||
&mut self.port
|
|
||||||
}
|
|
||||||
fn into_port (self) -> Port<Self::Port> {
|
|
||||||
self.port
|
|
||||||
}
|
|
||||||
fn connections (&self) -> &[Connect] {
|
|
||||||
self.connections.as_slice()
|
|
||||||
}
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized
|
|
||||||
{
|
|
||||||
let port = Self {
|
|
||||||
port: Self::register(jack, name)?,
|
|
||||||
jack: jack.clone(),
|
|
||||||
name: name.as_ref().into(),
|
|
||||||
connections: connect.to_vec()
|
|
||||||
};
|
|
||||||
port.connect_to_matching()?;
|
|
||||||
Ok(port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use ConnectName::*;
|
|
||||||
use ConnectScope::*;
|
|
||||||
use ConnectStatus::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)] pub enum ConnectName {
|
|
||||||
/** Exact match */
|
|
||||||
Exact(Arc<str>),
|
|
||||||
/** Match regular expression */
|
|
||||||
RegExp(Arc<str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope {
|
|
||||||
One,
|
|
||||||
All
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus {
|
|
||||||
Missing,
|
|
||||||
Disconnected,
|
|
||||||
Connected,
|
|
||||||
Mismatch,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Port connection manager.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let connect = tek::Connect::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Debug, Default)] pub struct Connect {
|
|
||||||
pub name: Option<ConnectName>,
|
|
||||||
pub scope: Option<ConnectScope>,
|
|
||||||
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>>,
|
|
||||||
pub info: Arc<str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connect {
|
|
||||||
pub fn collect (exact: &[impl AsRef<str>], re: &[impl AsRef<str>], re_all: &[impl AsRef<str>])
|
|
||||||
-> Vec<Self>
|
|
||||||
{
|
|
||||||
let mut connections = vec![];
|
|
||||||
for port in exact.iter() { connections.push(Self::exact(port)) }
|
|
||||||
for port in re.iter() { connections.push(Self::regexp(port)) }
|
|
||||||
for port in re_all.iter() { connections.push(Self::regexp_all(port)) }
|
|
||||||
connections
|
|
||||||
}
|
|
||||||
/// Connect to this exact port
|
|
||||||
pub fn exact (name: impl AsRef<str>) -> Self {
|
|
||||||
let info = format!("=:{}", name.as_ref()).into();
|
|
||||||
let name = Some(Exact(name.as_ref().into()));
|
|
||||||
Self { name, scope: Some(One), status: Arc::new(RwLock::new(vec![])), info }
|
|
||||||
}
|
|
||||||
pub fn regexp (name: impl AsRef<str>) -> Self {
|
|
||||||
let info = format!("~:{}", name.as_ref()).into();
|
|
||||||
let name = Some(RegExp(name.as_ref().into()));
|
|
||||||
Self { name, scope: Some(One), status: Arc::new(RwLock::new(vec![])), info }
|
|
||||||
}
|
|
||||||
pub fn regexp_all (name: impl AsRef<str>) -> Self {
|
|
||||||
let info = format!("+:{}", name.as_ref()).into();
|
|
||||||
let name = Some(RegExp(name.as_ref().into()));
|
|
||||||
Self { name, scope: Some(All), status: Arc::new(RwLock::new(vec![])), info }
|
|
||||||
}
|
|
||||||
pub fn info (&self) -> Arc<str> {
|
|
||||||
format!(" ({}) {} {}", {
|
|
||||||
let status = self.status.read().unwrap();
|
|
||||||
let mut ok = 0;
|
|
||||||
for (_, _, state) in status.iter() {
|
|
||||||
if *state == Connected {
|
|
||||||
ok += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
format!("{ok}/{}", status.len())
|
|
||||||
}, match self.scope {
|
|
||||||
None => "x",
|
|
||||||
Some(One) => " ",
|
|
||||||
Some(All) => "*",
|
|
||||||
}, match &self.name {
|
|
||||||
None => format!("x"),
|
|
||||||
Some(Exact(name)) => format!("= {name}"),
|
|
||||||
Some(RegExp(name)) => format!("~ {name}"),
|
|
||||||
}).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,256 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl HasJack<'static> for MidiInput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
|
||||||
|
|
||||||
impl HasJack<'static> for MidiOutput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
|
||||||
|
|
||||||
/// MIDI input port.
|
|
||||||
#[derive(Debug)] pub struct MidiInput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<MidiIn>,
|
|
||||||
/// List of currently held notes.
|
|
||||||
pub held: Arc<RwLock<[bool;128]>>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MIDI output port.
|
|
||||||
#[derive(Debug)] pub struct MidiOutput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<MidiOut>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
/// List of currently held notes.
|
|
||||||
pub held: Arc<RwLock<[bool;128]>>,
|
|
||||||
/// Buffer
|
|
||||||
pub note_buffer: Vec<u8>,
|
|
||||||
/// Buffer
|
|
||||||
pub output_buffer: Vec<Vec<Vec<u8>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
def_command!(MidiInputCommand: |port: MidiInput| {
|
|
||||||
Close => todo!(),
|
|
||||||
Connect { midi_out: Arc<str> } => todo!(),
|
|
||||||
});
|
|
||||||
|
|
||||||
def_command!(MidiOutputCommand: |port: MidiOutput| {
|
|
||||||
Close => todo!(),
|
|
||||||
Connect { midi_in: Arc<str> } => todo!(),
|
|
||||||
});
|
|
||||||
/// Trait for thing that may receive MIDI.
|
|
||||||
pub trait HasMidiIns {
|
|
||||||
fn midi_ins (&self) -> &Vec<MidiInput>;
|
|
||||||
fn midi_ins_mut (&mut self) -> &mut Vec<MidiInput>;
|
|
||||||
/// Collect MIDI input from app ports (TODO preallocate large buffers)
|
|
||||||
fn midi_input_collect <'a> (&'a self, scope: &'a ProcessScope) -> CollectedMidiInput<'a> {
|
|
||||||
self.midi_ins().iter()
|
|
||||||
.map(|port|port.port().iter(scope)
|
|
||||||
.map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes)))
|
|
||||||
.collect::<Vec<_>>())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
fn midi_ins_with_sizes <'a> (&'a self) ->
|
|
||||||
impl Iterator<Item=(usize, &'a Arc<str>, &'a [Connect], usize, usize)> + Send + Sync + 'a
|
|
||||||
{
|
|
||||||
let mut y = 0;
|
|
||||||
self.midi_ins().iter().enumerate().map(move|(i, input)|{
|
|
||||||
let height = 1 + input.connections().len();
|
|
||||||
let data = (i, input.port_name(), input.connections(), y, y + height);
|
|
||||||
y += height;
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Trait for thing that may output MIDI.
|
|
||||||
pub trait HasMidiOuts {
|
|
||||||
fn midi_outs (&self) -> &Vec<MidiOutput>;
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<MidiOutput>;
|
|
||||||
fn midi_outs_with_sizes <'a> (&'a self) ->
|
|
||||||
impl Iterator<Item=(usize, &'a Arc<str>, &'a [Connect], usize, usize)> + Send + Sync + 'a
|
|
||||||
{
|
|
||||||
let mut y = 0;
|
|
||||||
self.midi_outs().iter().enumerate().map(move|(i, output)|{
|
|
||||||
let height = 1 + output.connections().len();
|
|
||||||
let data = (i, output.port_name(), output.connections(), y, y + height);
|
|
||||||
y += height;
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn midi_outs_emit (&mut self, scope: &ProcessScope) {
|
|
||||||
for port in self.midi_outs_mut().iter_mut() {
|
|
||||||
port.buffer_emit(scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JackPort for MidiInput {
|
|
||||||
type Port = MidiIn;
|
|
||||||
type Pair = MidiOut;
|
|
||||||
fn port_name (&self) -> &Arc<str> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
fn port (&self) -> &Port<Self::Port> {
|
|
||||||
&self.port
|
|
||||||
}
|
|
||||||
fn port_mut (&mut self) -> &mut Port<Self::Port> {
|
|
||||||
&mut self.port
|
|
||||||
}
|
|
||||||
fn into_port (self) -> Port<Self::Port> {
|
|
||||||
self.port
|
|
||||||
}
|
|
||||||
fn connections (&self) -> &[Connect] {
|
|
||||||
self.connections.as_slice()
|
|
||||||
}
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized
|
|
||||||
{
|
|
||||||
let port = Self {
|
|
||||||
port: Self::register(jack, name)?,
|
|
||||||
jack: jack.clone(),
|
|
||||||
name: name.as_ref().into(),
|
|
||||||
connections: connect.to_vec(),
|
|
||||||
held: Arc::new(RwLock::new([false;128]))
|
|
||||||
};
|
|
||||||
port.connect_to_matching()?;
|
|
||||||
Ok(port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JackPort for MidiOutput {
|
|
||||||
type Port = MidiOut;
|
|
||||||
type Pair = MidiIn;
|
|
||||||
fn port_name (&self) -> &Arc<str> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
fn port (&self) -> &Port<Self::Port> {
|
|
||||||
&self.port
|
|
||||||
}
|
|
||||||
fn port_mut (&mut self) -> &mut Port<Self::Port> {
|
|
||||||
&mut self.port
|
|
||||||
}
|
|
||||||
fn into_port (self) -> Port<Self::Port> {
|
|
||||||
self.port
|
|
||||||
}
|
|
||||||
fn connections (&self) -> &[Connect] {
|
|
||||||
self.connections.as_slice()
|
|
||||||
}
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized
|
|
||||||
{
|
|
||||||
let port = Self::register(jack, name)?;
|
|
||||||
let jack = jack.clone();
|
|
||||||
let name = name.as_ref().into();
|
|
||||||
let connections = connect.to_vec();
|
|
||||||
let port = Self {
|
|
||||||
jack,
|
|
||||||
port,
|
|
||||||
name,
|
|
||||||
connections,
|
|
||||||
held: Arc::new([false;128].into()),
|
|
||||||
note_buffer: vec![0;8],
|
|
||||||
output_buffer: vec![vec![];65536],
|
|
||||||
};
|
|
||||||
port.connect_to_matching()?;
|
|
||||||
Ok(port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View<Tui> for MidiInput {
|
|
||||||
fn view (&self) -> impl Draw<Tui> {
|
|
||||||
"TODO: MIDI IN"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View<Tui> for MidiOutput {
|
|
||||||
fn view (&self) -> impl Draw<Tui> {
|
|
||||||
"TODO: MIDI OUT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiOutput {
|
|
||||||
/// Clear the section of the output buffer that we will be using,
|
|
||||||
/// emitting "all notes off" at start of buffer if requested.
|
|
||||||
pub fn buffer_clear (&mut self, scope: &ProcessScope, reset: bool) {
|
|
||||||
let n_frames = (scope.n_frames() as usize).min(self.output_buffer.len());
|
|
||||||
for frame in &mut self.output_buffer[0..n_frames] {
|
|
||||||
frame.clear();
|
|
||||||
}
|
|
||||||
if reset {
|
|
||||||
all_notes_off(&mut self.output_buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Write a note to the output buffer
|
|
||||||
pub fn buffer_write <'a> (
|
|
||||||
&'a mut self,
|
|
||||||
sample: usize,
|
|
||||||
event: LiveEvent,
|
|
||||||
) {
|
|
||||||
self.note_buffer.fill(0);
|
|
||||||
event.write(&mut self.note_buffer).expect("failed to serialize MIDI event");
|
|
||||||
self.output_buffer[sample].push(self.note_buffer.clone());
|
|
||||||
// Update the list of currently held notes.
|
|
||||||
if let LiveEvent::Midi { ref message, .. } = event {
|
|
||||||
update_keys(&mut*self.held.write().unwrap(), message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Write a chunk of MIDI data from the output buffer to the output port.
|
|
||||||
pub fn buffer_emit (&mut self, scope: &ProcessScope) {
|
|
||||||
let samples = scope.n_frames() as usize;
|
|
||||||
let mut writer = self.port.writer(scope);
|
|
||||||
for (time, events) in self.output_buffer.iter().enumerate().take(samples) {
|
|
||||||
for bytes in events.iter() {
|
|
||||||
writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{
|
|
||||||
panic!("Failed to write MIDI data: {bytes:?}");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl MidiInput {
|
|
||||||
pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> {
|
|
||||||
parse_midi_input(self.port().iter(scope))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: AsRef<Vec<MidiInput>> + AsMut<Vec<MidiInput>>> HasMidiIns for T {
|
|
||||||
fn midi_ins (&self) -> &Vec<MidiInput> { self.as_ref() }
|
|
||||||
fn midi_ins_mut (&mut self) -> &mut Vec<MidiInput> { self.as_mut() }
|
|
||||||
}
|
|
||||||
impl<T: AsRef<Vec<MidiOutput>> + AsMut<Vec<MidiOutput>>> HasMidiOuts for T {
|
|
||||||
fn midi_outs (&self) -> &Vec<MidiOutput> { self.as_ref() }
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<MidiOutput> { self.as_mut() }
|
|
||||||
}
|
|
||||||
impl<T: HasMidiIns + HasJack<'static>> AddMidiIn for T {
|
|
||||||
fn midi_in_add (&mut self) -> Usually<()> {
|
|
||||||
let index = self.midi_ins().len();
|
|
||||||
let port = MidiInput::new(self.jack(), &format!("M/{index}"), &[])?;
|
|
||||||
self.midi_ins_mut().push(port);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Trail for thing that may gain new MIDI ports.
|
|
||||||
impl<T: HasMidiOuts + HasJack<'static>> AddMidiOut for T {
|
|
||||||
fn midi_out_add (&mut self) -> Usually<()> {
|
|
||||||
let index = self.midi_outs().len();
|
|
||||||
let port = MidiOutput::new(self.jack(), &format!("{index}/M"), &[])?;
|
|
||||||
self.midi_outs_mut().push(port);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// May create new MIDI input ports.
|
|
||||||
pub trait AddMidiIn {
|
|
||||||
fn midi_in_add (&mut self) -> Usually<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// May create new MIDI output ports.
|
|
||||||
pub trait AddMidiOut {
|
|
||||||
fn midi_out_add (&mut self) -> Usually<()>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub trait RegisterPorts: HasJack<'static> {
|
|
||||||
/// Register a MIDI input port.
|
|
||||||
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput>;
|
|
||||||
/// Register a MIDI output port.
|
|
||||||
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput>;
|
|
||||||
/// Register an audio input port.
|
|
||||||
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput>;
|
|
||||||
/// Register an audio output port.
|
|
||||||
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<J: HasJack<'static>> RegisterPorts for J {
|
|
||||||
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput> {
|
|
||||||
MidiInput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput> {
|
|
||||||
MidiOutput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput> {
|
|
||||||
AudioInput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput> {
|
|
||||||
AudioOutput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{*, clock::*, device::*};
|
use crate::{*, clock::*, device::*};
|
||||||
|
|
||||||
|
|
||||||
impl <T: AsRef<Sequencer>+AsMut<Sequencer>> HasSequencer for T {}
|
impl <T: AsRef<Sequencer>+AsMut<Sequencer>> HasSequencer for T {}
|
||||||
|
|
||||||
/// A MIDI sequence.
|
/// A MIDI sequence.
|
||||||
|
|
@ -229,7 +230,6 @@ pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip {
|
||||||
|
|
||||||
pub type MidiData = Vec<Vec<MidiMessage>>;
|
pub type MidiData = Vec<Vec<MidiMessage>>;
|
||||||
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
||||||
pub type CollectedMidiInput<'a> = Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
|
||||||
|
|
||||||
pub trait HasClips {
|
pub trait HasClips {
|
||||||
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
||||||
|
|
@ -592,35 +592,6 @@ pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16)
|
||||||
(note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n))
|
(note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return boxed iterator of MIDI events
|
|
||||||
pub fn parse_midi_input <'a> (input: ::tengri::jack::MidiIter<'a>)
|
|
||||||
-> Box<dyn Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> + 'a>
|
|
||||||
{
|
|
||||||
Box::new(input.map(|::tengri::jack::RawMidi { time, bytes }|(
|
|
||||||
time as usize,
|
|
||||||
LiveEvent::parse(bytes).unwrap(),
|
|
||||||
bytes
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add "all notes off" to the start of a buffer.
|
|
||||||
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
|
|
||||||
let mut buf = vec![];
|
|
||||||
let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() };
|
|
||||||
let evt = LiveEvent::Midi { channel: 0.into(), message: msg };
|
|
||||||
evt.write(&mut buf).unwrap();
|
|
||||||
output[0].push(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update notes_in array
|
|
||||||
pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
|
|
||||||
match message {
|
|
||||||
MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; }
|
|
||||||
MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; },
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the next shorter length
|
/// Returns the next shorter length
|
||||||
pub fn note_duration_prev (pulses: usize) -> usize {
|
pub fn note_duration_prev (pulses: usize) -> usize {
|
||||||
for (length, _) in NOTE_DURATIONS.iter().rev() { if *length < pulses { return *length } }
|
for (length, _) in NOTE_DURATIONS.iter().rev() { if *length < pulses { return *length } }
|
||||||
|
|
|
||||||
31
src/tek.edn
31
src/tek.edn
|
|
@ -10,25 +10,18 @@
|
||||||
(keys :axis/i2 (@lt i2/dec) (@gt z2/inc))
|
(keys :axis/i2 (@lt i2/dec) (@gt z2/inc))
|
||||||
(keys :axis/w (@openbracket w/dec) (@closebracket w/inc))
|
(keys :axis/w (@openbracket w/dec) (@closebracket w/inc))
|
||||||
(keys :axis/w2 (@openbrace w2/dec) (@closebrace w2/inc))
|
(keys :axis/w2 (@openbrace w2/dec) (@closebrace w2/inc))
|
||||||
|
(mode :menu (name Menu) (info Mode selector.) (keys :axis/y :confirm) (view (bg (g 0)
|
||||||
(mode :menu
|
(bsp/s (exact/y 2 :transport)
|
||||||
(name Menu)
|
(bsp/s (exact/y 3 (bg (g 80) :ports/out))
|
||||||
(info Mode selector.)
|
(bsp/n (exact/y 3 (bg (g 80) :ports/in))
|
||||||
(keys :axis/y :confirm)
|
(bg (g 30) (bsp/s (exact/y 6 (bg (g 70) :logo)) :dialog/menu))))))))
|
||||||
(view (bg (g 0) (bsp/s
|
(view :logo (bsp/s (exact/y 1 (text ~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~))
|
||||||
(bg (g 10) :ports/out)
|
(bsp/s (exact/y 1 (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ v0.3.0 ~~))
|
||||||
(bsp/n
|
(bsp/s (exact/y 1 (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~)) (text dig?)))))
|
||||||
(bg (g 10) :ports/in)
|
(view :ports/out (fill/x (bsp/s (fill/x (align/w (text L-AUDIO-OUT)))
|
||||||
(bg (g 30) (bsp/s
|
(bsp/e (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R)))))))
|
||||||
:logo
|
(view :ports/in (fill/x (bsp/s (fill/x (align/w (text L-AUDIO-IN)))
|
||||||
:dialog/menu)))))))
|
(bsp/e (text MIDI-IN) (fill/x (align/e (text AUDIO-IN-R)))))))
|
||||||
|
|
||||||
(view :ports/out (fill/x (fixed/y 3 (bsp/a
|
|
||||||
(fill/x (align/w (text L-AUDIO-OUT)))
|
|
||||||
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R))))))))
|
|
||||||
(view :ports/in (fill/x (fixed/y 3 (bsp/a
|
|
||||||
(fill/x (align/w (text L-AUDIO-IN)))
|
|
||||||
(bsp/a (text MIDI-IN) (fill/x (align/e (text AUDIO-IN-R))))))))
|
|
||||||
(mode :transport (name Transport) (info JACK transport controller.)
|
(mode :transport (name Transport) (info JACK transport controller.)
|
||||||
(keys :clock :global)
|
(keys :clock :global)
|
||||||
:transport)
|
:transport)
|
||||||
|
|
|
||||||
292
src/tek.rs
292
src/tek.rs
|
|
@ -6,37 +6,295 @@
|
||||||
trait_alias,
|
trait_alias,
|
||||||
type_changing_struct_update
|
type_changing_struct_update
|
||||||
)]
|
)]
|
||||||
|
|
||||||
pub extern crate atomic_float;
|
pub extern crate atomic_float;
|
||||||
pub extern crate xdg;
|
pub extern crate xdg;
|
||||||
pub extern crate midly;
|
|
||||||
pub extern crate tengri;
|
pub extern crate tengri;
|
||||||
|
|
||||||
/// Command-line entrypoint.
|
/// Command-line entrypoint.
|
||||||
#[cfg(feature = "cli")] pub fn main () -> Usually<()> {
|
#[cfg(feature = "cli")] pub fn main () -> Usually<()> {
|
||||||
tui_setup_panic();
|
tengri::Tui::setup_panic();
|
||||||
Config::watch(|config|{
|
let name = "tek";
|
||||||
tui_run_main(Jack::new_run("tek", move|jack|{
|
let mode = ":menu";
|
||||||
let clock = Clock::new(&jack, Some(51.))?;
|
let title = "untitled!";
|
||||||
let project = Arrangement::new(&jack, None, clock, vec![], vec![], vec![], vec![]);
|
let bpm = 74.;
|
||||||
let app = App::new(&jack, project, config, ":menu");
|
Config::watch(|config|tengri::Tui::run_main(Jack::new_run(name, move|jack|{
|
||||||
Ok(app)
|
let clock = Clock::new(&jack, Some(bpm))?;
|
||||||
})?)
|
let tracks = [];
|
||||||
}).map(|_|())
|
let scenes = [];
|
||||||
|
let project = Arrangement::new(
|
||||||
|
&jack, title.into(), clock,
|
||||||
|
tracks.into_iter(),
|
||||||
|
scenes.into_iter(),
|
||||||
|
Connect::midi_ins(&jack, &"M", &[], None)?.into_iter(),
|
||||||
|
Connect::midi_outs(&jack, &"M", &[], None)?.into_iter(),
|
||||||
|
[].into_iter()
|
||||||
|
.chain(Connect::audio_ins(&jack, &"L", &[], None)?.into_iter())
|
||||||
|
.chain(Connect::audio_ins(&jack, &"R", &[], None)?.into_iter()),
|
||||||
|
[].into_iter()
|
||||||
|
.chain(Connect::audio_outs(&jack, &"L", &[], None)?.into_iter())
|
||||||
|
.chain(Connect::audio_outs(&jack, &"R", &[], None)?.into_iter()),
|
||||||
|
);
|
||||||
|
Ok(App::new(&jack, project, config, mode))
|
||||||
|
})?)).map(|_|())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod deps;
|
pub mod deps;
|
||||||
pub(crate) use self::deps::*;
|
pub(crate) use self::deps::*;
|
||||||
|
|
||||||
pub mod device;
|
pub mod device;
|
||||||
pub use self::device::*;
|
pub use self::device::*;
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub use self::app::*;
|
pub use self::app::*;
|
||||||
|
|
||||||
/// CLI banner.
|
/// CLI banner.
|
||||||
pub(crate) const HEADER: &'static str = r#"
|
pub(crate) const HEADER: &'static str = r#"
|
||||||
|
|
||||||
~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~
|
~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~
|
||||||
█ █▀ █▀▀▄ ~ v0.4.0, 2026 heatwave edition ~
|
█ █▀ █▀▀▄ ~ v0.4.0, 2026 heatwave edition ~
|
||||||
~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#;
|
~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#;
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
mod cli {
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// The command-line interface descriptor.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let cli: tek::Cli = Default::default();
|
||||||
|
///
|
||||||
|
/// use clap::CommandFactory;
|
||||||
|
/// tek::Cli::command().debug_assert();
|
||||||
|
/// ```
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))]
|
||||||
|
#[derive(Debug, Default)] pub struct Cli {
|
||||||
|
/// Pre-defined configuration modes.
|
||||||
|
///
|
||||||
|
/// TODO: Replace these with scripted configurations.
|
||||||
|
#[command(subcommand)] pub action: Action,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Application modes that can be passed to the mommand line interface.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let action: tek::Action = Default::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Subcommand, Default)] pub enum Action {
|
||||||
|
/// Continue where you left off
|
||||||
|
#[default] Resume,
|
||||||
|
/// Run headlessly in current session.
|
||||||
|
Headless,
|
||||||
|
/// Show status of current session.
|
||||||
|
Status,
|
||||||
|
/// List known sessions.
|
||||||
|
List,
|
||||||
|
/// Continue work in a copy of the current session.
|
||||||
|
Fork,
|
||||||
|
/// Create a new empty session.
|
||||||
|
New {
|
||||||
|
/// Name of JACK client
|
||||||
|
#[arg(short='n', long)] name: Option<String>,
|
||||||
|
/// Whether to attempt to become transport master
|
||||||
|
#[arg(short='Y', long, default_value_t = false)] sync_lead: bool,
|
||||||
|
/// Whether to sync to external transport master
|
||||||
|
#[arg(short='y', long, default_value_t = true)] sync_follow: bool,
|
||||||
|
/// Initial tempo in beats per minute
|
||||||
|
#[arg(short='b', long, default_value = None)] bpm: Option<f64>,
|
||||||
|
/// Whether to include a transport toolbar (default: true)
|
||||||
|
#[arg(short='c', long, default_value_t = true)] show_clock: bool,
|
||||||
|
/// MIDI outs to connect to (multiple instances accepted)
|
||||||
|
#[arg(short='I', long)] midi_from: Vec<String>,
|
||||||
|
/// MIDI outs to connect to (multiple instances accepted)
|
||||||
|
#[arg(short='i', long)] midi_from_re: Vec<String>,
|
||||||
|
/// MIDI ins to connect to (multiple instances accepted)
|
||||||
|
#[arg(short='O', long)] midi_to: Vec<String>,
|
||||||
|
/// MIDI ins to connect to (multiple instances accepted)
|
||||||
|
#[arg(short='o', long)] midi_to_re: Vec<String>,
|
||||||
|
/// Audio outs to connect to left input
|
||||||
|
#[arg(short='l', long)] left_from: Vec<String>,
|
||||||
|
/// Audio outs to connect to right input
|
||||||
|
#[arg(short='r', long)] right_from: Vec<String>,
|
||||||
|
/// Audio ins to connect from left output
|
||||||
|
#[arg(short='L', long)] left_to: Vec<String>,
|
||||||
|
/// Audio ins to connect from right output
|
||||||
|
#[arg(short='R', long)] right_to: Vec<String>,
|
||||||
|
/// Tracks to creat
|
||||||
|
#[arg(short='t', long)] tracks: Option<usize>,
|
||||||
|
/// Scenes to create
|
||||||
|
#[arg(short='s', long)] scenes: Option<usize>,
|
||||||
|
},
|
||||||
|
/// Import media as new session.
|
||||||
|
Import,
|
||||||
|
/// Show configuration.
|
||||||
|
Config,
|
||||||
|
/// Show version.
|
||||||
|
Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Command-line configuration.
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
impl Cli {
|
||||||
|
pub fn run (&self) -> Usually<()> {
|
||||||
|
self.action.run(Config::init_new(None)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
impl Action {
|
||||||
|
fn run (&self, config: Config) -> Usually<()> {
|
||||||
|
use Action::*;
|
||||||
|
match self {
|
||||||
|
Version => show_version(),
|
||||||
|
Config => print_config(&config),
|
||||||
|
List => todo!("list sessions"),
|
||||||
|
Resume => todo!("resume session"),
|
||||||
|
New {
|
||||||
|
name, bpm, tracks, scenes,
|
||||||
|
sync_lead, sync_follow,
|
||||||
|
midi_from: mf, midi_from_re: mfr, midi_to: mt, midi_to_re: mtr,
|
||||||
|
left_from: lf, right_from: rf, left_to: lt, right_to: rt, ..
|
||||||
|
} => {
|
||||||
|
let name = name.as_ref().map_or("tek", |x|x.as_str());
|
||||||
|
let jack = Jack::new(&name)?;
|
||||||
|
let mut proj = Arrangement::new(
|
||||||
|
&jack,
|
||||||
|
name.into(),
|
||||||
|
Clock::new(&jack, None)?,
|
||||||
|
[].into_iter(),
|
||||||
|
[].into_iter(),
|
||||||
|
Connect::midi_ins(&jack, &"M", &[], None)?.into_iter(),
|
||||||
|
Connect::midi_outs(&jack, &"M", &[], None)?.into_iter(),
|
||||||
|
[].into_iter()
|
||||||
|
.chain(Connect::audio_ins(&jack, &"L", &[], None)?.into_iter())
|
||||||
|
.chain(Connect::audio_ins(&jack, &"R", &[], None)?.into_iter()),
|
||||||
|
[].into_iter()
|
||||||
|
.chain(Connect::audio_outs(&jack, &"L", &[], None)?.into_iter())
|
||||||
|
.chain(Connect::audio_outs(&jack, &"R", &[], None)?.into_iter()),
|
||||||
|
);
|
||||||
|
//&jack, Clock::new(&jack, *bpm)?, &lf, <, &rf, &rt, &mf, &mt, &mfr, &mtr)?;
|
||||||
|
proj.tracks_add(tracks.unwrap_or(0), None, &[], &[])?;
|
||||||
|
proj.scenes_add(scenes.unwrap_or(0))?;
|
||||||
|
//if matches!(self, Action::Status) {
|
||||||
|
//// Show status and exit
|
||||||
|
//tek_print_status(&proj);
|
||||||
|
//return Ok(())
|
||||||
|
//}
|
||||||
|
// Initialize the app state
|
||||||
|
let app = Arc::new(RwLock::new(App::new(&jack, proj, config, ":menu")));
|
||||||
|
//if matches!(self, Action::Headless) {
|
||||||
|
//// TODO: Headless mode (daemon + client over IPC, then over network...)
|
||||||
|
//println!("todo headless");
|
||||||
|
//return Ok(())
|
||||||
|
//}
|
||||||
|
let (_keyboard, _terminal) = Exit::run(|exited|Tui::io(
|
||||||
|
exited.as_ref(),
|
||||||
|
&app,
|
||||||
|
Duration::from_millis(100),
|
||||||
|
Duration::from_millis(10),
|
||||||
|
std::io::stdout()
|
||||||
|
))?;
|
||||||
|
},
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//pub fn tui (
|
||||||
|
//app: Arc<RwLock<App>>,
|
||||||
|
//jack: Jack,
|
||||||
|
//sync_lead: &bool,
|
||||||
|
//sync_follow: &bool,
|
||||||
|
//) -> Usually<()> {
|
||||||
|
//// Run the [Tui] and [Jack] threads with the [App] state.
|
||||||
|
//Tui::run_main(&jack.run(move|jack|{
|
||||||
|
//// Between jack init and app's first cycle:
|
||||||
|
////jack.sync_lead(*sync_lead, |mut state|{
|
||||||
|
////let clock = app.write().unwrap().clock();
|
||||||
|
////clock.playhead.update_from_sample(state.position.frame() as f64);
|
||||||
|
////state.position.bbt = Some(clock.bbt());
|
||||||
|
////state.position
|
||||||
|
////})?;
|
||||||
|
////jack.sync_follow(*sync_follow)?;
|
||||||
|
//// FIXME: They don't work properly.
|
||||||
|
//Ok(app)
|
||||||
|
//})?)?
|
||||||
|
//}
|
||||||
|
|
||||||
|
pub fn show_version () {
|
||||||
|
println!("todo version");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_config (config: &Config) {
|
||||||
|
use ::ansi_term::Color::*;
|
||||||
|
println!("{:?}", config.dirs);
|
||||||
|
for (k, v) in config.views.read().unwrap().iter() {
|
||||||
|
println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")));
|
||||||
|
}
|
||||||
|
for (k, v) in config.binds.read().unwrap().iter() {
|
||||||
|
println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}")));
|
||||||
|
for (k, v) in v.0.iter() {
|
||||||
|
print!("{} ", &Yellow.paint(match &k.0 {
|
||||||
|
Event::Key(KeyEvent { modifiers, .. }) =>
|
||||||
|
format!("{:>16}", format!("{modifiers}")),
|
||||||
|
_ => unimplemented!()
|
||||||
|
}));
|
||||||
|
print!("{}", &Yellow.bold().paint(match &k.0 {
|
||||||
|
Event::Key(KeyEvent { code, .. }) =>
|
||||||
|
format!("{:<10}", format!("{code}")),
|
||||||
|
_ => unimplemented!()
|
||||||
|
}));
|
||||||
|
for v in v.iter() {
|
||||||
|
print!(" => {:?}", v.commands);
|
||||||
|
print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default());
|
||||||
|
println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default());
|
||||||
|
//println!(" {:?}", v.source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.modes.for_each(|k, v|{
|
||||||
|
println!();
|
||||||
|
for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); }
|
||||||
|
for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); }
|
||||||
|
print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}")));
|
||||||
|
print!("\n{}", Blue.paint("KEYS"));
|
||||||
|
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||||
|
println!();
|
||||||
|
v.modes.for_each(|k, v|{
|
||||||
|
print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name);
|
||||||
|
print!( " INFO={:?}", v.info);
|
||||||
|
print!( " VIEW={:?}", v.view);
|
||||||
|
println!(" KEYS={:?}", v.keys);
|
||||||
|
});
|
||||||
|
print!("{}", Blue.paint("VIEW"));
|
||||||
|
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||||
|
println!();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_status (project: &Arrangement) {
|
||||||
|
println!("Name: {:?}", &project.name);
|
||||||
|
println!("JACK: {:?}", &project.jack);
|
||||||
|
println!("Buffer: {:?}", &project.clock.chunk);
|
||||||
|
println!("Sample rate: {:?}", &project.clock.timebase.sr);
|
||||||
|
println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq);
|
||||||
|
println!("Tempo: {:?}", &project.clock.timebase.bpm);
|
||||||
|
println!("Quantize: {:?}", &project.clock.quant);
|
||||||
|
println!("Launch: {:?}", &project.clock.sync);
|
||||||
|
println!("Playhead: {:?}us", &project.clock.playhead.usec);
|
||||||
|
println!("Playhead: {:?}s", &project.clock.playhead.sample);
|
||||||
|
println!("Playhead: {:?}p", &project.clock.playhead.pulse);
|
||||||
|
println!("Started: {:?}", &project.clock.started);
|
||||||
|
println!("Tracks:");
|
||||||
|
for (i, t) in project.tracks.iter().enumerate() {
|
||||||
|
println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width,
|
||||||
|
&t.sequencer.play_clip, &t.sequencer.next_clip);
|
||||||
|
}
|
||||||
|
println!("Scenes:");
|
||||||
|
for (i, t) in project.scenes.iter().enumerate() {
|
||||||
|
println!(" Scene {i}: {} {:?}", &t.name, &t.clips);
|
||||||
|
}
|
||||||
|
println!("MIDI Ins: {:?}", &project.midi_ins);
|
||||||
|
println!("MIDI Outs: {:?}", &project.midi_outs);
|
||||||
|
println!("Audio Ins: {:?}", &project.audio_ins);
|
||||||
|
println!("Audio Outs: {:?}", &project.audio_outs);
|
||||||
|
// TODO git integration
|
||||||
|
// TODO dawvert integration
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
2
tengri
2
tengri
|
|
@ -1 +1 @@
|
||||||
Subproject commit ad1a514eddbf3104b7631dedef786389863988bf
|
Subproject commit c0d6d0174e8858108ec053f4ac486cf6229980e1
|
||||||
Loading…
Add table
Add a link
Reference in a new issue