mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 03:36:41 +01:00
the definitions are unified alright. it's just not supported yet :D the idea being that tek offers to write out the default configs to ~/.config/tek-v0 where the user can customize them.
This commit is contained in:
parent
9e147cda69
commit
3c8616deba
43 changed files with 441 additions and 465 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -2395,6 +2395,7 @@ dependencies = [
|
||||||
"tengri",
|
"tengri",
|
||||||
"tengri_proc",
|
"tengri_proc",
|
||||||
"toml",
|
"toml",
|
||||||
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3408,6 +3409,12 @@ 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 = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
|
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xdg"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xkbcommon-dl"
|
name = "xkbcommon-dl"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ toml = { version = "0.9.2" }
|
||||||
uuid = { version = "1.10.0", features = [ "v4" ] }
|
uuid = { version = "1.10.0", features = [ "v4" ] }
|
||||||
wavers = { version = "1.4.3" }
|
wavers = { version = "1.4.3" }
|
||||||
winit = { version = "0.30.4", features = [ "x11" ] }
|
winit = { version = "0.30.4", features = [ "x11" ] }
|
||||||
|
xdg = { version = "3.0.0" }
|
||||||
#once_cell = "1.19.0"
|
#once_cell = "1.19.0"
|
||||||
#no_deadlocks = "1.3.2"
|
#no_deadlocks = "1.3.2"
|
||||||
#suil-rs = { path = "../suil" }
|
#suil-rs = { path = "../suil" }
|
||||||
|
|
|
||||||
167
config/bindings.edn
Normal file
167
config/bindings.edn
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
(module :global
|
||||||
|
(@esc dialog/hide)
|
||||||
|
(@f1 dialog/show :dialog/help)
|
||||||
|
(@f6 dialog/show :dialog/save)
|
||||||
|
(@f8 dialog/show :dialog/options)
|
||||||
|
(@f9 dialog/show :dialog/load)
|
||||||
|
(@f10 dialog/show :dialog/quit)
|
||||||
|
(@u history/undo 1)
|
||||||
|
(@r history/redo 1))
|
||||||
|
|
||||||
|
(module :clock
|
||||||
|
(@space clock/toggle 0)
|
||||||
|
(@shift/space clock/toggle 0))
|
||||||
|
|
||||||
|
(module :arranger
|
||||||
|
(@c color)
|
||||||
|
(@q launch)
|
||||||
|
(@tab project/edit)
|
||||||
|
(@enter project/edit)
|
||||||
|
(@escape project/home)
|
||||||
|
(@shift/I project/input/add)
|
||||||
|
(@shift/O project/output/add)
|
||||||
|
(@shift/S project/scene/add)
|
||||||
|
(@shift/T project/track/add)
|
||||||
|
(@shift/D dialog/show :dialog/device)
|
||||||
|
(@up select :select/scene/prev)
|
||||||
|
(@down select :select/scene/next)
|
||||||
|
(@left select :select/track/prev)
|
||||||
|
(@right select :select/track/next)
|
||||||
|
(@t select :select/track)
|
||||||
|
(@s select :select/scene))
|
||||||
|
|
||||||
|
(module :track
|
||||||
|
(@delete track/delete :track)
|
||||||
|
(@q track/launch :track)
|
||||||
|
(@c track/color :track)
|
||||||
|
(@comma track/prev)
|
||||||
|
(@period track/next)
|
||||||
|
(@lt track/swap/prev)
|
||||||
|
(@gt track/swap/next)
|
||||||
|
(@r track/rec)
|
||||||
|
(@m track/mon)
|
||||||
|
(@p track/play)
|
||||||
|
(@P track/solo))
|
||||||
|
|
||||||
|
(module :scene
|
||||||
|
(@delete scene/delete :scene)
|
||||||
|
(@q scene/launch :scene)
|
||||||
|
(@c scene/color :scene)
|
||||||
|
(@comma scene/prev)
|
||||||
|
(@period scene/next)
|
||||||
|
(@lt scene/swap/prev)
|
||||||
|
(@gt scene/swap/next))
|
||||||
|
|
||||||
|
(module :clip
|
||||||
|
(@g clip/get)
|
||||||
|
(@p clip/put)
|
||||||
|
(@delete clip/del)
|
||||||
|
(@comma clip/prev)
|
||||||
|
(@period clip/next)
|
||||||
|
(@lt clip/swap/prev)
|
||||||
|
(@gt clip/swap/next)
|
||||||
|
(@l clip/loop/toggle))
|
||||||
|
|
||||||
|
(module :browser
|
||||||
|
(@escape browser/cancel)
|
||||||
|
(@return browser/confirm)
|
||||||
|
(@up browser/cursor/set :browser/cursor/prev)
|
||||||
|
(@down browser/cursor/set :browser/cursor/next)
|
||||||
|
(@right browser/address/set :browser/address/selected)
|
||||||
|
(@left browser/address/set :browser/address/parent)
|
||||||
|
(:char browser/filter/append :char)
|
||||||
|
(@backspace browser/filter/delete :last))
|
||||||
|
|
||||||
|
(module :device/add
|
||||||
|
(@up dialog :dialog/device/prev)
|
||||||
|
(@down dialog :dialog/device/next))
|
||||||
|
|
||||||
|
(module :editor
|
||||||
|
(@left editor/time/set :time/pos/prev)
|
||||||
|
(@shift/left editor/time/set :time/pos/prev/fine)
|
||||||
|
(@right editor/time/set :time/pos/next)
|
||||||
|
(@shift/right editor/time/set :time/pos/next/fine)
|
||||||
|
(@equal editor/zoom/set :time/zoom/prev)
|
||||||
|
(@minus editor/zoom/set :time/zoom/next)
|
||||||
|
(@plus editor/zoom/set :time/zoom/next/fine)
|
||||||
|
(@underscore editor/zoom/set :time/zoom/prev/fine)
|
||||||
|
(@z editor/lock/set)
|
||||||
|
(@comma editor/length/set :note/len/prev)
|
||||||
|
(@period editor/length/set :note/len/next)
|
||||||
|
(@lt editor/length/set :note/len/prev)
|
||||||
|
(@gt editor/length/set :note/len/next)
|
||||||
|
(@up editor/pitch/set :note/pos/next)
|
||||||
|
(@down editor/pitch/set :note/pos/prev)
|
||||||
|
(@pgup editor/pitch/set :note/pos/next/octave)
|
||||||
|
(@pgdn editor/pitch/set :note/pos/prev/octave)
|
||||||
|
(@a editor/append :true)
|
||||||
|
(@enter editor/append :false)
|
||||||
|
(@del editor/delete/note)
|
||||||
|
(@shift/del editor/delete/note))
|
||||||
|
|
||||||
|
(module :groovebox
|
||||||
|
(@r sampler/record/toggle :sample)
|
||||||
|
(@tab focus/next)
|
||||||
|
(@shift/tab focus/prev))
|
||||||
|
|
||||||
|
(module :length
|
||||||
|
(@up inc)
|
||||||
|
(@down dec)
|
||||||
|
(@right next)
|
||||||
|
(@left prev)
|
||||||
|
(@return set :length)
|
||||||
|
(@escape cancel))
|
||||||
|
|
||||||
|
(module :message
|
||||||
|
(@esc message/dismiss)
|
||||||
|
(@enter message/dismiss))
|
||||||
|
|
||||||
|
(module :pool
|
||||||
|
(@n rename/begin)
|
||||||
|
(@t length/begin)
|
||||||
|
(@m import/begin)
|
||||||
|
(@x export/begin)
|
||||||
|
(@c clip/color :clip :random/color)
|
||||||
|
(@openbracket select :clip/prev)
|
||||||
|
(@closebracket select :clip/next)
|
||||||
|
(@lt swap :clip :clip/prev)
|
||||||
|
(@gt swap :clip :clip/next)
|
||||||
|
(@delete clip/delete :clip)
|
||||||
|
(@shift/A clip/add :after :new/clip)
|
||||||
|
(@shift/D clip/add :after :cloned/clip))
|
||||||
|
|
||||||
|
(module :pool/file
|
||||||
|
(@up select :prev)
|
||||||
|
(@down select :next)
|
||||||
|
(@right chdir :selected)
|
||||||
|
(@left chdir :parent)
|
||||||
|
(@return confirm)
|
||||||
|
(@escape cancel)
|
||||||
|
(:char append :char)
|
||||||
|
(@backspace delete :last))
|
||||||
|
|
||||||
|
(module :rename
|
||||||
|
(:char append :char)
|
||||||
|
(@backspace delete :last)
|
||||||
|
(@return confirm)
|
||||||
|
(@escape cancel))
|
||||||
|
|
||||||
|
(module :sampler
|
||||||
|
(@up sampler/select :sample/above)
|
||||||
|
(@down sampler/select :sample/below)
|
||||||
|
(@left sampler/select :sample/to/left)
|
||||||
|
(@right sampler/select :sample/to/right)
|
||||||
|
|
||||||
|
(@r sampler/record/toggle :sample/selected)
|
||||||
|
(@shift/R sampler/record/cancel)
|
||||||
|
(@p sampler/play/sample :sample/selected)
|
||||||
|
(@P sampler/stop/sample :sample/selected)
|
||||||
|
|
||||||
|
(@shift/f6 dialog :dialog/export/sample)
|
||||||
|
(@shift/f9 dialog :dialog/import/sample))
|
||||||
|
|
||||||
|
(module :sequencer
|
||||||
|
(@c color)
|
||||||
|
(@q launch)
|
||||||
|
(@shift/I input/add)
|
||||||
|
(@shift/O output/add))
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
(name
|
|
||||||
"Arranger")
|
|
||||||
(info
|
|
||||||
"A grid of launchable clips arranged by track and scene.")
|
|
||||||
(keys
|
|
||||||
(cond :focus-editor (load "./keys_editor.edn"))
|
|
||||||
(cond :focus-dialog (load "./keys_dialog.edn"))
|
|
||||||
(cond :focus-message (load "./keys_message.edn"))
|
|
||||||
(cond :focus-device-add (load "./keys_device_add.edn"))
|
|
||||||
(cond :focus-browser (load "./keys_browser.edn"))
|
|
||||||
(cond :focus-pool-rename (load "./keys_rename.edn"))
|
|
||||||
(cond :focus-pool-length (load "./keys_length.edn"))
|
|
||||||
(cond :focus-clip (load "./keys_clip.edn"))
|
|
||||||
(cond :focus-track (load "./keys_track.edn"))
|
|
||||||
(cond :focus-scene (load "./keys_scene.edn"))
|
|
||||||
(cond :focus-mix (load "./keys_mix.edn"))
|
|
||||||
(load "./keys_clock.edn")
|
|
||||||
(load "./keys_arranger.edn")
|
|
||||||
(load "./keys_global.edn"))
|
|
||||||
(view
|
|
||||||
(bsp/a :view-dialog
|
|
||||||
(bsp/w :view-meters-output
|
|
||||||
(bsp/e :view-meters-input
|
|
||||||
(bsp/n (fixed/y 2 :view-status-h2)
|
|
||||||
(bsp/n :view-tracks-inputs
|
|
||||||
(bsp/s :view-tracks-devices
|
|
||||||
(bsp/s :view-tracks-outputs
|
|
||||||
(bsp/s :view-tracks-names
|
|
||||||
(fill/xy (either :focus-editor
|
|
||||||
(bsp/e :view-scenes-names :view-editor)
|
|
||||||
:view-scenes)))))))))))
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
(name "Groovebox")
|
|
||||||
|
|
||||||
(info "A sequencer with built-in sampler.")
|
|
||||||
|
|
||||||
(keys
|
|
||||||
(layer-if :focus-browser "./keys_browser.edn")
|
|
||||||
(layer-if :focus-pool-rename "./keys_rename.edn")
|
|
||||||
(layer-if :focus-pool-length "./keys_length.edn")
|
|
||||||
(layer "./keys_clock.edn")
|
|
||||||
(layer "./keys_editor.edn")
|
|
||||||
(layer "./keys_sampler.edn")
|
|
||||||
(layer "./keys_global.edn"))
|
|
||||||
|
|
||||||
(view (bsp/a :view-dialog (bsp/w :view-meters-output (bsp/e :view-meters-input
|
|
||||||
(bsp/w
|
|
||||||
(fill/y (align/n
|
|
||||||
(bsp/s :view-midi-ins-status
|
|
||||||
(bsp/s :view-midi-outs-status
|
|
||||||
(bsp/s :view-audio-ins-status
|
|
||||||
(bsp/s :view-audio-outs-status
|
|
||||||
:view-pool))))))
|
|
||||||
(bsp/n
|
|
||||||
(fixed/y :h-sample-detail
|
|
||||||
(bsp/e (fill/y (fixed/x 20 (align/nw :view-sample-status)))
|
|
||||||
:view-sample-viewer))
|
|
||||||
(bsp/e
|
|
||||||
(fill/y (align/n (bsp/s :view-status-v :view-editor-status)))
|
|
||||||
(bsp/e :view-samples-keys :view-editor))))))))
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
(name "Sampler")
|
|
||||||
|
|
||||||
(info "A sampling soundboard.")
|
|
||||||
|
|
||||||
(keys
|
|
||||||
(layer "./keys_sampler.edn")
|
|
||||||
(layer "./keys_global.edn"))
|
|
||||||
|
|
||||||
(view
|
|
||||||
(bsp/a :view-dialog
|
|
||||||
(bsp/s (fixed/y 1 :view-transport)
|
|
||||||
(bsp/n (fixed/y 1 :view-status)
|
|
||||||
(fill/xy :view-samples-grid)))))
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
(name "Sequencer")
|
|
||||||
|
|
||||||
(info "A MIDI sequencer.")
|
|
||||||
|
|
||||||
(keys
|
|
||||||
(layer-if :focus-browser "./keys_browser.edn")
|
|
||||||
(layer-if :mode-pool-rename "./keys_rename.edn")
|
|
||||||
(layer-if :mode-pool-length "./keys_length.edn")
|
|
||||||
(layer "./keys_editor.edn")
|
|
||||||
(layer "./keys_clock.edn")
|
|
||||||
(layer "./keys_global.edn"))
|
|
||||||
|
|
||||||
(view
|
|
||||||
(bsp/a :view-dialog
|
|
||||||
(bsp/s (fixed/y 1 :view-transport)
|
|
||||||
(bsp/n (fixed/y 1 :view-status)
|
|
||||||
(fill/xy (bsp/a
|
|
||||||
(fill/xy (align/e :view-pool))
|
|
||||||
:view-editor)))))
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
(name "Transport")
|
|
||||||
|
|
||||||
(info "A JACK transport controller.")
|
|
||||||
|
|
||||||
(keys
|
|
||||||
(layer "./keys_clock.edn")
|
|
||||||
(layer "./keys_global.edn"))
|
|
||||||
|
|
||||||
(view :view-transport)
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
(@c color)
|
|
||||||
(@q launch)
|
|
||||||
(@tab project edit)
|
|
||||||
(@enter project edit)
|
|
||||||
(@escape project home)
|
|
||||||
(@shift-I project input-add)
|
|
||||||
(@shift-O project output-add)
|
|
||||||
(@shift-S project scene-add)
|
|
||||||
(@shift-T project track-add)
|
|
||||||
(@shift-D dialog open :dialog-device)
|
|
||||||
|
|
||||||
(@up select :select-scene-prev)
|
|
||||||
(@down select :select-scene-next)
|
|
||||||
(@left select :select-track-prev)
|
|
||||||
(@right select :select-track-next)
|
|
||||||
(@t select :select-track)
|
|
||||||
(@s select :select-scene)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
(@escape browser cancel)
|
|
||||||
(@return browser confirm)
|
|
||||||
(@up browser set-cursor :browser-cursor-prev)
|
|
||||||
(@down browser set-cursor :browser-cursor-next)
|
|
||||||
(@right browser set-address :browser-address-selected)
|
|
||||||
(@left browser set-address :browser-address-parent)
|
|
||||||
(:char browser append-to-search ;char)
|
|
||||||
(@backspace browser delete-from-search :last)
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
(@g clip get)
|
|
||||||
(@p clip put)
|
|
||||||
(@delete clip del)
|
|
||||||
(@comma clip prev)
|
|
||||||
(@period clip next)
|
|
||||||
(@lt clip swap-prev)
|
|
||||||
(@gt clip swap-next)
|
|
||||||
(@l clip loop-toggle)
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
(@space clock toggle-playback 0)
|
|
||||||
(@shift-space clock toggle-playback 0)
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
(@up toggle-dialog :dialog-device-prev)
|
|
||||||
(@down toggle-dialog :dialog-device-next)
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
(@left editor set-time-pos :time-pos-prev)
|
|
||||||
(@shift-left editor set-time-pos :time-pos-prev-fine)
|
|
||||||
(@right editor set-time-pos :time-pos-next)
|
|
||||||
(@shift-right editor set-time-pos :time-pos-next-fine)
|
|
||||||
|
|
||||||
(@equal editor set-time-zoom :time-zoom-prev)
|
|
||||||
(@minus editor set-time-zoom :time-zoom-next)
|
|
||||||
(@plus editor set-time-zoom :time-zoom-next-fine)
|
|
||||||
(@underscore editor set-time-zoom :time-zoom-prev-fine)
|
|
||||||
|
|
||||||
(@z editor set-time-lock)
|
|
||||||
|
|
||||||
(@up editor set-note-pos :note-pos-next)
|
|
||||||
(@down editor set-note-pos :note-pos-prev)
|
|
||||||
(@pgup editor set-note-pos :note-pos-next-octave)
|
|
||||||
(@pgdn editor set-note-pos :note-pos-prev-octave)
|
|
||||||
|
|
||||||
(@comma editor set-note-len :note-len-prev)
|
|
||||||
(@period editor set-note-len :note-len-next)
|
|
||||||
(@lt editor set-note-len :note-len-prev)
|
|
||||||
(@gt editor set-note-len :note-len-next)
|
|
||||||
|
|
||||||
(@a editor append-note :true)
|
|
||||||
(@enter editor append-note :false)
|
|
||||||
(@del editor delete-note)
|
|
||||||
(@shift-del editor delete-note)
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
(@esc dialog :dialog-none)
|
|
||||||
(@f1 dialog :dialog-help)
|
|
||||||
(@f6 dialog :dialog-save)
|
|
||||||
(@f8 dialog :dialog-options)
|
|
||||||
(@f9 dialog :dialog-load)
|
|
||||||
(@f10 dialog :dialog-quit)
|
|
||||||
|
|
||||||
(@u undo 1)
|
|
||||||
(@r redo 1)
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
(@r sampler record/toggle :sample)
|
|
||||||
(@tab focus-next)
|
|
||||||
(@shift-tab focus-prev)
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
(@up inc)
|
|
||||||
(@down dec)
|
|
||||||
(@right next)
|
|
||||||
(@left prev)
|
|
||||||
(@return set :length)
|
|
||||||
(@escape cancel)
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
(@esc message dismiss)
|
|
||||||
(@enter message dismiss)
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
(@n rename begin)
|
|
||||||
(@t length begin)
|
|
||||||
(@m import begin)
|
|
||||||
(@x export begin)
|
|
||||||
(@c clip color :clip :random-color)
|
|
||||||
(@openbracket select :clip-prev)
|
|
||||||
(@closebracket select :clip-next)
|
|
||||||
(@lt swap :clip :clip-prev)
|
|
||||||
(@gt swap :clip :clip-next)
|
|
||||||
(@delete clip/delete :clip)
|
|
||||||
(@shift-A clip/add :after :new-clip)
|
|
||||||
(@shift-D clip/add :after :cloned-clip)
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
(@up select :prev)
|
|
||||||
(@down select :next)
|
|
||||||
(@right chdir :selected)
|
|
||||||
(@left chdir :parent)
|
|
||||||
(@return confirm)
|
|
||||||
(@escape cancel)
|
|
||||||
(:char append :char)
|
|
||||||
(@backspace delete :last)
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
(:char append :char)
|
|
||||||
(@backspace delete :last)
|
|
||||||
(@return confirm)
|
|
||||||
(@escape cancel)
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
(@up sampler select :sample-above)
|
|
||||||
(@down sampler select :sample-below)
|
|
||||||
(@left sampler select :sample-to-left)
|
|
||||||
(@right sampler select :sample-to-right)
|
|
||||||
|
|
||||||
(@r sampler record-toggle :sample-selected)
|
|
||||||
(@shift-R sampler record-cancel)
|
|
||||||
(@p sampler play-sample :sample-selected)
|
|
||||||
(@P sampler stop-sample :sample-selected)
|
|
||||||
|
|
||||||
(@shift-f6 dialog :dialog-export-sample)
|
|
||||||
(@shift-f9 dialog :dialog-import-sample)
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
(@delete scene delete :scene)
|
|
||||||
(@q scene launch :scene)
|
|
||||||
(@c scene color :scene)
|
|
||||||
(@comma scene prev)
|
|
||||||
(@period scene next)
|
|
||||||
(@lt scene swap-prev)
|
|
||||||
(@gt scene swap-next)
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
(@c color)
|
|
||||||
(@q launch)
|
|
||||||
(@shift-I input add)
|
|
||||||
(@shift-O output add)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
(@delete track delete :track)
|
|
||||||
(@q track launch :track)
|
|
||||||
(@c track color :track)
|
|
||||||
(@comma track prev)
|
|
||||||
(@period track next)
|
|
||||||
(@lt track swap-prev)
|
|
||||||
(@gt track swap-next)
|
|
||||||
(@r track rec)
|
|
||||||
(@m track mon)
|
|
||||||
(@p track play)
|
|
||||||
(@P track solo)
|
|
||||||
74
config/templates.edn
Normal file
74
config/templates.edn
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
(module :transport
|
||||||
|
(name "Transport")
|
||||||
|
(info "A JACK transport controller.")
|
||||||
|
(bind :keys/clock)
|
||||||
|
(bind :keys/global)
|
||||||
|
:view/transport)
|
||||||
|
|
||||||
|
(module :arranger
|
||||||
|
(name "Arranger")
|
||||||
|
(info "A grid of launchable clips arranged by track and scene.")
|
||||||
|
(bind :keys/editor :focused/editor)
|
||||||
|
(bind :keys/dialog :focused/dialog)
|
||||||
|
(bind :keys/message :focused/message)
|
||||||
|
(bind :keys/device_add :focused/device-add)
|
||||||
|
(bind :keys/browser :focused/browser)
|
||||||
|
(bind :keys/rename :focused/pool-rename)
|
||||||
|
(bind :keys/length :focused/pool-length)
|
||||||
|
(bind :keys/clip :focused/clip)
|
||||||
|
(bind :keys/track :focused/track)
|
||||||
|
(bind :keys/scene :focused/scene)
|
||||||
|
(bind :keys/mix :focused/mix)
|
||||||
|
(bind :keys/clock)
|
||||||
|
(bind :keys/arranger)
|
||||||
|
(bind :keys/global)
|
||||||
|
:view/dialog
|
||||||
|
(bsp/w :view/meters/output
|
||||||
|
(bsp/e :view/meters/input
|
||||||
|
(stack/n (fixed/y 2 :view/status/h2) :view/tracks/inputs
|
||||||
|
(stack/s :view/tracks/devices :view/tracks/outputs :view/tracks/names
|
||||||
|
(fill/xy (either :focused/editor
|
||||||
|
(bsp/e :view/scenes/names :view/editor)
|
||||||
|
:view/scenes)))))))
|
||||||
|
|
||||||
|
(module :groovebox
|
||||||
|
(name "Groovebox")
|
||||||
|
(info "A sequencer with built-in sampler.")
|
||||||
|
(bind :keys/browser :focused/browser)
|
||||||
|
(bind :keys/rename :focused/pool-rename)
|
||||||
|
(bind :keys/length :focused/pool-length)
|
||||||
|
(bind :keys/clock)
|
||||||
|
(bind :keys/editor)
|
||||||
|
(bind :keys/sampler)
|
||||||
|
(bind :keys/global)
|
||||||
|
:view/dialog
|
||||||
|
(bsp/w :view/meters/output (bsp/e :view/meters/input (bsp/w (fill/y (align/n (stack/s :view/midi-ins/status
|
||||||
|
:view/midi-outs/status
|
||||||
|
:view/audio-ins/status
|
||||||
|
:view/audio-outs/status
|
||||||
|
:view/pool)))
|
||||||
|
(bsp/n (fixed/y :h-sample-detail (bsp/e (fill/y (fixed/x 20 (align/nw :view/sample-status))) :view/sample-viewer))
|
||||||
|
(bsp/e (fill/y (align/n (bsp/s :view/status/v :view/editor-status))) (bsp/e :view/samples/keys :view/editor)))))))
|
||||||
|
|
||||||
|
(module :sampler
|
||||||
|
(name "Sampler")
|
||||||
|
(info "A sampling soundboard.")
|
||||||
|
(bind :keys/sampler)
|
||||||
|
(bind :keys/global)
|
||||||
|
:view/dialog
|
||||||
|
(bsp/s (fixed/y 1 :view/transport)
|
||||||
|
(bsp/n (fixed/y 1 :view/status)
|
||||||
|
(fill/xy :view/samples/grid))))
|
||||||
|
|
||||||
|
(module :sequencer
|
||||||
|
(name "Sequencer")
|
||||||
|
(info "A MIDI sequencer.")
|
||||||
|
(bind :keys/browser :focused/browser)
|
||||||
|
(bind :keys/rename :mode/pool-rename)
|
||||||
|
(bind :keys/length :mode/pool-length)
|
||||||
|
(bind :keys/editor)
|
||||||
|
(bind :keys/clock)
|
||||||
|
(bind :keys/global)
|
||||||
|
:view/dialog
|
||||||
|
(bsp/s (fixed/y 1 :view/transport) (bsp/n (fixed/y 1 :view/status)
|
||||||
|
(fill/xy (bsp/a (fill/xy (align/e :view/pool)) :view/editor)))))
|
||||||
|
|
@ -15,6 +15,7 @@ clap = { workspace = true, optional = true }
|
||||||
palette = { workspace = true }
|
palette = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
|
xdg = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = { workspace = true }
|
proptest = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; }
|
||||||
handle!(TuiIn: |self: App, input|self.handle_tui_key_with_history(input));
|
handle!(TuiIn: |self: App, input|self.handle_tui_key_with_history(input));
|
||||||
impl App {
|
impl App {
|
||||||
fn handle_tui_key_with_history (&mut self, input: &TuiIn) -> Perhaps<bool> {
|
fn handle_tui_key_with_history (&mut self, input: &TuiIn) -> Perhaps<bool> {
|
||||||
Ok(if let Some(binding) = self.config.keys.dispatch(input.event()) {
|
Ok(if let Some(binding) = self.configs.current.as_ref()
|
||||||
|
.map(|c|c.keys.dispatch(input.event())).flatten()
|
||||||
|
{
|
||||||
let binding = binding.clone();
|
let binding = binding.clone();
|
||||||
let undo = binding.command.clone().execute(self)?;
|
let undo = binding.command.clone().execute(self)?;
|
||||||
// FIXME failed commands are not persisted in undo history
|
// FIXME failed commands are not persisted in undo history
|
||||||
|
|
|
||||||
111
crates/app/src/config.rs
Normal file
111
crates/app/src/config.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
use crate::*;
|
||||||
|
use xdg::BaseDirectories;
|
||||||
|
|
||||||
|
/// Configurations
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Configurations {
|
||||||
|
pub dirs: BaseDirectories,
|
||||||
|
pub modules: Arc<RwLock<std::collections::BTreeMap<Arc<str>, Arc<str>>>>,
|
||||||
|
pub current: Option<Configuration>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Configuration {
|
||||||
|
/// Path of configuration entrypoint
|
||||||
|
pub path: std::path::PathBuf,
|
||||||
|
/// Name of configuration
|
||||||
|
pub name: Option<Arc<str>>,
|
||||||
|
/// Description of configuration
|
||||||
|
pub info: Option<Arc<str>>,
|
||||||
|
/// View definition
|
||||||
|
pub view: Arc<str>,
|
||||||
|
// Input keymap
|
||||||
|
pub keys: EventMap<TuiEvent, AppCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! dsl_for_each (($dsl:expr => |$head:ident|$body:expr)=>{
|
||||||
|
let mut dsl: Arc<str> = $dsl.src().into();
|
||||||
|
let mut $head: Option<Arc<str>> = dsl.head()?.map(Into::into);
|
||||||
|
let mut tail: Option<Arc<str>> = dsl.tail()?.map(Into::into);
|
||||||
|
loop {
|
||||||
|
if let Some($head) = $head {
|
||||||
|
$body;
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let Some(next) = tail {
|
||||||
|
$head = next.head()?.map(Into::into);
|
||||||
|
tail = next.tail()?.map(Into::into);
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
impl Configurations {
|
||||||
|
const DEFAULT_TEMPLATES: &'static str = include_str!("../../../config/templates.edn");
|
||||||
|
const DEFAULT_BINDINGS: &'static str = include_str!("../../../config/bindings.edn");
|
||||||
|
pub fn init () -> Usually<Self> {
|
||||||
|
let mut dirs = BaseDirectories::with_profile("tek", "v0");
|
||||||
|
let mut cfgs = Self { dirs, ..Default::default() };
|
||||||
|
cfgs.init_file("templates.edn", Self::DEFAULT_TEMPLATES)?;
|
||||||
|
cfgs.load_file("templates.edn", |head|{ Ok(()) })?;
|
||||||
|
cfgs.init_file("bindings.edn", Self::DEFAULT_BINDINGS)?;
|
||||||
|
cfgs.load_file("bindings.end", |head|{ Ok(()) })?;
|
||||||
|
Ok(cfgs)
|
||||||
|
}
|
||||||
|
fn init_file (&mut self, path: &str, val: &str) -> Usually<()> {
|
||||||
|
if self.dirs.find_config_file("templates.edn").is_none() {
|
||||||
|
std::fs::write(self.dirs.place_config_file("templates.edn")?, Self::DEFAULT_TEMPLATES);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn load_file (&mut self, path: &str, mut each: impl FnMut(&Arc<str>)->Usually<()> ) -> Usually<()> {
|
||||||
|
Ok(if let Some(path) = self.dirs.find_config_file("templates.edn") {
|
||||||
|
dsl_for_each!(std::fs::read_to_string(path)?.as_str() => |dsl|each(&dsl));
|
||||||
|
} else {
|
||||||
|
return Err(format!("{path}: not found").into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Configuration {
|
||||||
|
fn load_template (&mut self, dsl: impl Dsl) -> Usually<&mut Self> {
|
||||||
|
dsl_for_each!(dsl => |dsl|match () {
|
||||||
|
_ if let Some(exp) = dsl.exp()? => match exp.head()?.key()? {
|
||||||
|
Some("name") => match exp.tail()?.text()? {
|
||||||
|
Some(name) => self.name = Some(name.into()),
|
||||||
|
_ => return Err(format!("missing name definition").into())
|
||||||
|
},
|
||||||
|
Some("info") => match exp.tail()?.text()? {
|
||||||
|
Some(info) => self.info = Some(info.into()),
|
||||||
|
_ => return Err(format!("missing info definition").into())
|
||||||
|
},
|
||||||
|
Some("bind") => match exp.tail()? {
|
||||||
|
Some(keys) => self.keys = EventMap::from_dsl(&mut &keys)?,
|
||||||
|
_ => return Err(format!("missing keys definition").into())
|
||||||
|
},
|
||||||
|
Some("view") => match exp.tail()? {
|
||||||
|
Some(tail) => self.view = tail.src().into(),
|
||||||
|
_ => return Err(format!("missing view definition").into())
|
||||||
|
},
|
||||||
|
dsl => return Err(format!("unexpected: {dsl:?}").into())
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => return Err(format!("unexpected: {dsl:?}").into())
|
||||||
|
});
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
fn load_binding (&mut self, dsl: impl Dsl) -> Usually<&mut Self> {
|
||||||
|
todo!();
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unquote (x: &str) -> &str {
|
||||||
|
let mut chars = x.chars();
|
||||||
|
chars.next();
|
||||||
|
//chars.next_back();
|
||||||
|
chars.as_str()
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,7 @@ pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed};
|
||||||
|
|
||||||
mod api; pub use self::api::*;
|
mod api; pub use self::api::*;
|
||||||
mod audio; pub use self::audio::*;
|
mod audio; pub use self::audio::*;
|
||||||
|
mod config; pub use self::config::*;
|
||||||
mod model; pub use self::model::*;
|
mod model; pub use self::model::*;
|
||||||
mod view; pub use self::view::*;
|
mod view; pub use self::view::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,28 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
/// Must not be dropped for the duration of the process
|
/// Must not be dropped for the duration of the process
|
||||||
pub jack: Jack<'static>,
|
pub jack: Jack<'static>,
|
||||||
/// Display size
|
/// Display size
|
||||||
pub size: Measure<TuiOut>,
|
pub size: Measure<TuiOut>,
|
||||||
/// Performance counter
|
/// Performance counter
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
// View and input definition
|
/// Available view definitions and input bindings
|
||||||
pub config: Configuration,
|
pub configs: Configurations,
|
||||||
/// Contains all recently created clips.
|
/// Contains all recently created clips.
|
||||||
pub pool: Pool,
|
pub pool: Pool,
|
||||||
/// Contains the currently edited musical arrangement
|
/// Contains the currently edited musical arrangement
|
||||||
pub project: Arrangement,
|
pub project: Arrangement,
|
||||||
/// Undo history
|
/// Undo history
|
||||||
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
||||||
// Dialog overlay
|
// Dialog overlay
|
||||||
pub dialog: Option<Dialog>,
|
pub dialog: Option<Dialog>,
|
||||||
/// Base color.
|
/// Base color.
|
||||||
pub color: ItemTheme,
|
pub color: ItemTheme,
|
||||||
}
|
}
|
||||||
has!(Jack<'static>: |self: App|self.jack);
|
|
||||||
has!(Pool: |self: App|self.pool);
|
|
||||||
has!(Option<Dialog>: |self: App|self.dialog);
|
|
||||||
has!(Clock: |self: App|self.project.clock);
|
|
||||||
has!(Option<MidiEditor>: |self: App|self.project.editor);
|
|
||||||
has!(Selection: |self: App|self.project.selection);
|
|
||||||
has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
|
||||||
has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
|
||||||
has!(Vec<Scene>: |self: App|self.project.scenes);
|
|
||||||
has!(Vec<Track>: |self: App|self.project.tracks);
|
|
||||||
has!(Measure<TuiOut>: |self: App|self.size);
|
|
||||||
maybe_has!(Track: |self: App|
|
|
||||||
{ MaybeHas::<Track>::get(&self.project) };
|
|
||||||
{ MaybeHas::<Track>::get_mut(&mut self.project) });
|
|
||||||
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
|
|
||||||
maybe_has!(Scene: |self: App|
|
|
||||||
{ MaybeHas::<Scene>::get(&self.project) };
|
|
||||||
{ MaybeHas::<Scene>::get_mut(&mut self.project) });
|
|
||||||
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
|
||||||
has_clips!(|self: App|self.pool.clips);
|
|
||||||
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
|
|
||||||
//take!(ClockCommand |state: App, iter|Take::take(state.clock(), iter));
|
|
||||||
//take!(MidiEditCommand |state: App, iter|Ok(state.editor().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
|
||||||
//take!(PoolCommand |state: App, iter|Take::take(&state.pool, iter));
|
|
||||||
//take!(SamplerCommand |state: App, iter|Ok(state.project.sampler().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
|
||||||
//take!(ArrangementCommand |state: App, iter|Take::take(&state.project, iter));
|
|
||||||
//take!(DialogCommand |state: App, iter|Take::take(&state.dialog, iter));
|
|
||||||
//has_editor!(|self: App|{
|
|
||||||
//editor = self.editor;
|
|
||||||
//editor_w = {
|
|
||||||
//let size = self.size.w();
|
|
||||||
//let editor = self.editor.as_ref().expect("missing editor");
|
|
||||||
//let time_len = editor.time_len().get();
|
|
||||||
//let time_zoom = editor.time_zoom().get().max(1);
|
|
||||||
//(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16)
|
|
||||||
//};
|
|
||||||
//editor_h = 15;
|
|
||||||
//is_editing = self.editor.is_some();
|
|
||||||
//});
|
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn update_clock (&self) {
|
pub fn update_clock (&self) {
|
||||||
|
|
@ -190,20 +152,16 @@ impl App {
|
||||||
self.browser().is_some()
|
self.browser().is_some()
|
||||||
}
|
}
|
||||||
fn focus_clip (&self) -> bool {
|
fn focus_clip (&self) -> bool {
|
||||||
!self.focus_editor() && matches!(self.selection(),
|
!self.focus_editor() && matches!(self.selection(), Selection::TrackClip{..})
|
||||||
Selection::TrackClip{..})
|
|
||||||
}
|
}
|
||||||
fn focus_track (&self) -> bool {
|
fn focus_track (&self) -> bool {
|
||||||
!self.focus_editor() && matches!(self.selection(),
|
!self.focus_editor() && matches!(self.selection(), Selection::Track(..))
|
||||||
Selection::Track(..))
|
|
||||||
}
|
}
|
||||||
fn focus_scene (&self) -> bool {
|
fn focus_scene (&self) -> bool {
|
||||||
!self.focus_editor() && matches!(self.selection(),
|
!self.focus_editor() && matches!(self.selection(), Selection::Scene(..))
|
||||||
Selection::Scene(..))
|
|
||||||
}
|
}
|
||||||
fn focus_mix (&self) -> bool {
|
fn focus_mix (&self) -> bool {
|
||||||
!self.focus_editor() && matches!(self.selection(),
|
!self.focus_editor() && matches!(self.selection(), Selection::Mix)
|
||||||
Selection::Mix)
|
|
||||||
}
|
}
|
||||||
fn focus_pool_import (&self) -> bool {
|
fn focus_pool_import (&self) -> bool {
|
||||||
matches!(self.pool.mode, Some(PoolMode::Import(..)))
|
matches!(self.pool.mode, Some(PoolMode::Import(..)))
|
||||||
|
|
@ -318,99 +276,43 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration
|
has!(Jack<'static>: |self: App|self.jack);
|
||||||
#[derive(Default, Debug)]
|
has!(Pool: |self: App|self.pool);
|
||||||
pub struct Configuration {
|
has!(Option<Dialog>: |self: App|self.dialog);
|
||||||
/// Path of configuration entrypoint
|
has!(Clock: |self: App|self.project.clock);
|
||||||
pub path: PathBuf,
|
has!(Option<MidiEditor>: |self: App|self.project.editor);
|
||||||
/// Name of configuration
|
has!(Selection: |self: App|self.project.selection);
|
||||||
pub name: Option<Arc<str>>,
|
has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||||
/// Description of configuration
|
has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||||
pub info: Option<Arc<str>>,
|
has!(Vec<Scene>: |self: App|self.project.scenes);
|
||||||
/// View definition
|
has!(Vec<Track>: |self: App|self.project.tracks);
|
||||||
pub view: Arc<str>,
|
has!(Measure<TuiOut>: |self: App|self.size);
|
||||||
// Input keymap
|
maybe_has!(Track: |self: App|
|
||||||
pub keys: EventMap<TuiEvent, AppCommand>,
|
{ MaybeHas::<Track>::get(&self.project) };
|
||||||
}
|
{ MaybeHas::<Track>::get_mut(&mut self.project) });
|
||||||
|
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
|
||||||
|
maybe_has!(Scene: |self: App|
|
||||||
|
{ MaybeHas::<Scene>::get(&self.project) };
|
||||||
|
{ MaybeHas::<Scene>::get_mut(&mut self.project) });
|
||||||
|
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
||||||
|
has_clips!(|self: App|self.pool.clips);
|
||||||
|
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
|
||||||
|
|
||||||
impl Configuration {
|
//take!(ClockCommand |state: App, iter|Take::take(state.clock(), iter));
|
||||||
pub fn from_path (path: &impl AsRef<Path>, _watch: bool) -> Usually<Self> {
|
//take!(MidiEditCommand |state: App, iter|Ok(state.editor().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
||||||
let mut config = Self { path: path.as_ref().into(), ..Default::default() };
|
//take!(PoolCommand |state: App, iter|Take::take(&state.pool, iter));
|
||||||
let mut dsl = read_and_leak(path.as_ref())?;
|
//take!(SamplerCommand |state: App, iter|Ok(state.project.sampler().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
||||||
let mut head: Option<Arc<str>> = dsl.head()?.map(Into::into);
|
//take!(ArrangementCommand |state: App, iter|Take::take(&state.project, iter));
|
||||||
let mut tail: Option<Arc<str>> = dsl.tail()?.map(Into::into);
|
//take!(DialogCommand |state: App, iter|Take::take(&state.dialog, iter));
|
||||||
loop {
|
//has_editor!(|self: App|{
|
||||||
if let Some(exp) = head.exp()? {
|
//editor = self.editor;
|
||||||
match exp.head()?.key()? {
|
//editor_w = {
|
||||||
Some("name") => match exp.tail()?.text()? {
|
//let size = self.size.w();
|
||||||
Some(name) => config.name = Some(name.into()),
|
//let editor = self.editor.as_ref().expect("missing editor");
|
||||||
_ => return Err(format!("missing name definition").into())
|
//let time_len = editor.time_len().get();
|
||||||
},
|
//let time_zoom = editor.time_zoom().get().max(1);
|
||||||
Some("info") => match exp.tail()?.text()? {
|
//(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16)
|
||||||
Some(info) => config.info = Some(info.into()),
|
//};
|
||||||
_ => return Err(format!("missing info definition").into())
|
//editor_h = 15;
|
||||||
},
|
//is_editing = self.editor.is_some();
|
||||||
Some("keys") => match exp.tail()? {
|
//});
|
||||||
Some(keys) => config.keys = EventMap::from_dsl(&mut &keys)?,
|
|
||||||
_ => return Err(format!("missing keys definition").into())
|
|
||||||
},
|
|
||||||
Some("view") => match exp.tail()? {
|
|
||||||
Some(tail) => config.view = tail.src().into(),
|
|
||||||
_ => return Err(format!("missing view definition").into())
|
|
||||||
},
|
|
||||||
Some(k) => return Err(format!("(e3) unexpected key {k:?} in {exp:?}").into()),
|
|
||||||
None => return Err(format!("(e2) unexpected exp {exp:?}").into()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if let Some(next) = tail {
|
|
||||||
head = next.head()?.map(Into::into);
|
|
||||||
tail = next.tail()?.map(Into::into);
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_and_leak (path: impl AsRef<Path>) -> Usually<&'static str> {
|
|
||||||
Ok(leak(String::from_utf8(std::fs::read(path.as_ref())?)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn leak (x: impl AsRef<str>) -> &'static str {
|
|
||||||
Box::leak(x.as_ref().into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unquote (x: &str) -> &str {
|
|
||||||
let mut chars = x.chars();
|
|
||||||
chars.next();
|
|
||||||
//chars.next_back();
|
|
||||||
chars.as_str()
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! default_config { ($path:literal) => { ($path, include_str!($path)) }; }
|
|
||||||
pub const DEFAULT_CONFIGS: &'static [(&'static str, &'static str)] = &[
|
|
||||||
default_config!("../../../config/config_arranger.edn"),
|
|
||||||
default_config!("../../../config/config_groovebox.edn"),
|
|
||||||
default_config!("../../../config/config_sampler.edn"),
|
|
||||||
default_config!("../../../config/config_sequencer.edn"),
|
|
||||||
default_config!("../../../config/config_transport.edn"),
|
|
||||||
|
|
||||||
default_config!("../../../config/keys_arranger.edn"),
|
|
||||||
default_config!("../../../config/keys_clip.edn"),
|
|
||||||
default_config!("../../../config/keys_clock.edn"),
|
|
||||||
default_config!("../../../config/keys_editor.edn"),
|
|
||||||
default_config!("../../../config/keys_global.edn"),
|
|
||||||
default_config!("../../../config/keys_groovebox.edn"),
|
|
||||||
default_config!("../../../config/keys_length.edn"),
|
|
||||||
default_config!("../../../config/keys_mix.edn"),
|
|
||||||
default_config!("../../../config/keys_pool.edn"),
|
|
||||||
default_config!("../../../config/keys_pool_file.edn"),
|
|
||||||
default_config!("../../../config/keys_rename.edn"),
|
|
||||||
default_config!("../../../config/keys_sampler.edn"),
|
|
||||||
default_config!("../../../config/keys_scene.edn"),
|
|
||||||
default_config!("../../../config/keys_sequencer.edn"),
|
|
||||||
default_config!("../../../config/keys_track.edn"),
|
|
||||||
];
|
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,14 @@ impl<T: Content<TuiOut>> Content<TuiOut> for ErrorBoundary<TuiOut, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn view (model: &Self) -> impl Content<TuiOut> + '_ {
|
pub fn view (&self) -> impl Content<TuiOut> + '_ {
|
||||||
ErrorBoundary::new(Ok(Some(Tui::bg(Black, model.view_menu()))))
|
ErrorBoundary::new(Ok(Some(Tui::bg(Black, self.view_menu()))))
|
||||||
//ErrorBoundary::new(Take::take(model, &mut model.config.view.clone()))
|
//ErrorBoundary::new(Take::take(model, &mut model.config.view.clone()))
|
||||||
//ErrorBoundary::new(Give::give(model, &mut model.config.view.clone()))
|
//ErrorBoundary::new(Give::give(model, &mut model.config.view.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content!(TuiOut: |self: App| ErrorBoundary::new(Ok(Some(Tui::bg(Black, self.view_nil())))));
|
content!(TuiOut: |self: App| ErrorBoundary::new(Ok(Some(Tui::bg(Black, self.view())))));
|
||||||
|
|
||||||
#[tengri_proc::view(TuiOut)]
|
#[tengri_proc::view(TuiOut)]
|
||||||
impl App {
|
impl App {
|
||||||
|
|
@ -39,7 +39,7 @@ impl App {
|
||||||
}
|
}
|
||||||
pub fn view_menu (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_menu (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
add(&Tui::bold(true, "tek"));
|
add(&Tui::bold(true, "tek 0.3.0-rc0"));
|
||||||
add(&"");
|
add(&"");
|
||||||
add(&"+ new session");
|
add(&"+ new session");
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub struct Cli {
|
||||||
/// Pre-defined configuration modes.
|
/// Pre-defined configuration modes.
|
||||||
///
|
///
|
||||||
/// TODO: Replace these with scripted configurations.
|
/// TODO: Replace these with scripted configurations.
|
||||||
#[command(subcommand)] mode: LaunchMode,
|
#[command(subcommand)] mode: Option<LaunchMode>,
|
||||||
/// Name of JACK client
|
/// Name of JACK client
|
||||||
#[arg(short='n', long)] name: Option<String>,
|
#[arg(short='n', long)] name: Option<String>,
|
||||||
/// Whether to attempt to become transport master
|
/// Whether to attempt to become transport master
|
||||||
|
|
@ -45,29 +45,8 @@ pub struct Cli {
|
||||||
/// Application modes
|
/// Application modes
|
||||||
#[derive(Debug, Clone, Subcommand)]
|
#[derive(Debug, Clone, Subcommand)]
|
||||||
pub enum LaunchMode {
|
pub enum LaunchMode {
|
||||||
/// ⏯️ A standalone transport clock.
|
/// Create a new session instead of loading the previous one.
|
||||||
Clock,
|
New,
|
||||||
/// 🎼 A MIDI sequencer.
|
|
||||||
Sequencer,
|
|
||||||
/// 🎺 A MIDI-controlled audio sampler.
|
|
||||||
Sampler,
|
|
||||||
/// 📻 Sequencer and sampler together.
|
|
||||||
Groovebox,
|
|
||||||
/// 🎧 Multi-track MIDI sequencer.
|
|
||||||
Arranger {
|
|
||||||
/// Number of scenes
|
|
||||||
#[arg(short = 'y', long, default_value_t = 16)] scenes: usize,
|
|
||||||
/// Number of tracks
|
|
||||||
#[arg(short = 'x', long, default_value_t = 12)] tracks: usize,
|
|
||||||
/// Width of tracks
|
|
||||||
#[arg(short = 'w', long, default_value_t = 15)] track_width: usize,
|
|
||||||
},
|
|
||||||
/// TODO: A MIDI-controlled audio mixer
|
|
||||||
Mixer,
|
|
||||||
/// TODO: A customizable channel strip
|
|
||||||
Track,
|
|
||||||
/// TODO: An audio plugin host
|
|
||||||
Plugin,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
|
|
@ -86,9 +65,6 @@ impl Cli {
|
||||||
let right_tos = Connect::collect(&self.right_to, empty, empty);
|
let right_tos = Connect::collect(&self.right_to, empty, empty);
|
||||||
let audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
let audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
||||||
let audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
let audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
||||||
let clip = Arc::new(RwLock::new(MidiClip::new(
|
|
||||||
"Clip", true, 384usize, None, Some(ItemColor::random().into())),
|
|
||||||
));
|
|
||||||
Tui::new()?.run(&Jack::new_run(&name, move|jack|{
|
Tui::new()?.run(&Jack::new_run(&name, move|jack|{
|
||||||
for (index, connect) in midi_froms.iter().enumerate() {
|
for (index, connect) in midi_froms.iter().enumerate() {
|
||||||
midi_ins.push(jack.midi_in(&format!("M/{index}"), &[connect.clone()])?);
|
midi_ins.push(jack.midi_in(&format!("M/{index}"), &[connect.clone()])?);
|
||||||
|
|
@ -96,34 +72,12 @@ impl Cli {
|
||||||
for (index, connect) in midi_tos.iter().enumerate() {
|
for (index, connect) in midi_tos.iter().enumerate() {
|
||||||
midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?);
|
midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?);
|
||||||
};
|
};
|
||||||
let config = Configuration::from_path(&match self.mode {
|
let configs = Configurations::init();
|
||||||
LaunchMode::Clock => "config/config_transport.edn",
|
|
||||||
LaunchMode::Sequencer => "config/config_sequencer.edn",
|
|
||||||
LaunchMode::Groovebox => "config/config_groovebox.edn",
|
|
||||||
LaunchMode::Arranger { .. } => "config/config_arranger.edn",
|
|
||||||
LaunchMode::Sampler => "config/config_sampler.edn",
|
|
||||||
_ => todo!("{:?}", self.mode),
|
|
||||||
}, false)?;
|
|
||||||
let clock = Clock::new(&jack, self.bpm)?;
|
let clock = Clock::new(&jack, self.bpm)?;
|
||||||
match self.mode {
|
|
||||||
LaunchMode::Sequencer => tracks.push(Track::new(
|
|
||||||
&name, None, &jack, Some(&clock), Some(&clip),
|
|
||||||
midi_froms.as_slice(), midi_tos.as_slice()
|
|
||||||
)?),
|
|
||||||
LaunchMode::Groovebox | LaunchMode::Sampler => tracks.push(Track::new_with_sampler(
|
|
||||||
&name, None, &jack, Some(&clock), Some(&clip),
|
|
||||||
midi_froms.as_slice(), midi_tos.as_slice(), audio_froms, audio_tos,
|
|
||||||
)?),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
let mut app = App {
|
let mut app = App {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
config,
|
configs: Configurations::init()?,
|
||||||
color: ItemTheme::random(),
|
color: ItemTheme::random(),
|
||||||
pool: match self.mode {
|
|
||||||
LaunchMode::Sequencer | LaunchMode::Groovebox => (&clip).into(),
|
|
||||||
_ => Default::default()
|
|
||||||
},
|
|
||||||
project: Arrangement {
|
project: Arrangement {
|
||||||
name: Default::default(),
|
name: Default::default(),
|
||||||
color: ItemTheme::random(),
|
color: ItemTheme::random(),
|
||||||
|
|
@ -134,20 +88,16 @@ impl Cli {
|
||||||
selection: Selection::TrackClip { track: 0, scene: 0 },
|
selection: Selection::TrackClip { track: 0, scene: 0 },
|
||||||
midi_ins,
|
midi_ins,
|
||||||
midi_outs,
|
midi_outs,
|
||||||
editor: match self.mode {
|
|
||||||
LaunchMode::Sequencer | LaunchMode::Groovebox => Some((&clip).into()),
|
|
||||||
_ => None
|
|
||||||
},
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
if let LaunchMode::Arranger { scenes, tracks, track_width, .. } = self.mode {
|
//if let LaunchMode::Arranger { scenes, tracks, track_width, .. } = self.mode {
|
||||||
app.project.arranger = Default::default();
|
//app.project.arranger = Default::default();
|
||||||
app.project.selection = Selection::TrackClip { track: 1, scene: 1 };
|
//app.project.selection = Selection::TrackClip { track: 1, scene: 1 };
|
||||||
app.project.scenes_add(scenes)?;
|
//app.project.scenes_add(scenes)?;
|
||||||
app.project.tracks_add(tracks, Some(track_width), &[], &[])?;
|
//app.project.tracks_add(tracks, Some(track_width), &[], &[])?;
|
||||||
}
|
//}
|
||||||
jack.sync_lead(self.sync_lead, |mut state|{
|
jack.sync_lead(self.sync_lead, |mut state|{
|
||||||
let clock = app.clock();
|
let clock = app.clock();
|
||||||
clock.playhead.update_from_sample(state.position.frame() as f64);
|
clock.playhead.update_from_sample(state.position.frame() as f64);
|
||||||
|
|
@ -162,10 +112,12 @@ impl Cli {
|
||||||
|
|
||||||
/// CLI header
|
/// CLI header
|
||||||
const HEADER: &'static str = r#"
|
const HEADER: &'static str = r#"
|
||||||
|
~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~~~~~~
|
||||||
╓─╥─╖ ╓──╖ ╥ ╖
|
~ ~ ║ ~ ╟─╌ ~╟─< ~ v0.3.0-rc.0 "no, i insist that i am not a dj ~
|
||||||
║ ╟─╌ ╟─╡
|
~ ~ ╨ ~ ╙──╜ ╨ ╜ ~ 2025, summer, the nose of the cat. J ~
|
||||||
╨ ╙──╜ ╨ ╜"#;
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
On first run, Tek will create configuration and state dirs. ~
|
||||||
|
On subsequent runs, Tek should resume from where you left off. ~"#;
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_cli () {
|
#[cfg(test)] #[test] fn test_cli () {
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
|
|
|
||||||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 85c305385bc08ef805afb113f677c510027a7234
|
Subproject commit 9e0b7be9a9c80b5df52854ab426bc60b794931ed
|
||||||
Loading…
Add table
Add a link
Reference in a new issue