From 4d4c470a81e8b6dba6fb5835d8ed400f7e3d71d4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 10 Aug 2025 14:22:22 +0300 Subject: [PATCH] load up to modes the stacked modal music editor. lol --- config/binds.edn | 175 ++++++++++++++++++++--------------------- config/views.edn | 58 +++++++------- crates/app/app.rs | 158 ++++++++++++++++++------------------- crates/app/app_ctrl.rs | 11 ++- crates/app/app_dsl.rs | 17 +++- crates/app/app_view.rs | 4 +- deps/tengri | 2 +- 7 files changed, 220 insertions(+), 205 deletions(-) diff --git a/config/binds.edn b/config/binds.edn index 28db6897..e4c7be2a 100644 --- a/config/binds.edn +++ b/config/binds.edn @@ -1,75 +1,96 @@ -(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)) +(def :keys/cancel + (@escape cancel)) -(module :clock - (@space clock/toggle 0) - (@shift/space clock/toggle 0)) +(def :keys/delete + (@delete delete)) -(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)) +(def :keys/history + (@u undo 1) (@r redo 1)) -(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)) +(def :keys/nextprev + (@comma prev) (@period next)) -(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)) +(def :keys/swap + (@lt swap/prev) (@gt 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)) +(def :keys/list + (@up list/prev) (@down list/next) (@enter list/select) (@escape list/cancel)) + +(def :keys/clock + (@space clock/toggle 0) (@shift/space clock/toggle 0)) + +(def :keys/color + (@c color)) + +(def :keys/launch + (@q launch)) + +(def :keys/filter + (see :keys/list :keys/input)) + +(def :keys/input + (see :keys/cancel :keys/delete) + (@left cursor/prev) (@right cursor/next) (@backspace delete/prev) (:char input)) + +(def :keys/global + (see :keys/cancel :keys/history) + (@f1 dialog :help) (@f8 dialog :options) (@f10 dialog :quit) + (@f6 dialog :save) (@f9 dialog :load)) + +(def :keys/arranger + (see :keys/color :keys/launch :keys/arranger/direction :keys/arranger/cycle) + (@tab project/edit) (@enter project/edit) + (@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)) + +(def :keys/arranger/cycle + (@t select :select/track) (@s select :select/scene)) + +(def :keys/arranger/direction + (@up select :select/scene/prev) (@down select :select/scene/next) + (@left select :select/track/prev) (@right select :select/track/next)) + +(def :keys/scene (see :keys/color :keys/launch :keys/nextprev :keys/swap :keys/delete)) + +(def :keys/track (see :keys/color :keys/launch :keys/nextprev :keys/swap :keys/delete) + (@r toggle :rec) (@m toggle :mon) (@p toggle :play) (@P toggle :solo)) + +(def :keys/clip (see :keys/color :keys/launch :keys/nextprev :keys/swap :keys/delete) + (@l toggle :loop)) + +(def :keys/sampler (see :keys/sampler/directions :keys/sampler/record :keys/sampler/play)) + +(def :keys/sampler/play + (@p sampler/play/sample :sample/selected) + (@P sampler/stop/sample :sample/selected)) + +(def :keys/sampler/record + (@r sampler/record/toggle :sample/selected) + (@shift/R sampler/record/cancel)) + +(def :keys/sampler/import-export + (@shift/f6 dialog :dialog/export/sample) + (@shift/f9 dialog :dialog/import/sample)) + +(def :keys/sampler/directions + (@up sampler/select :sample/above) + (@down sampler/select :sample/below) + (@left sampler/select :sample/to/left) + (@right sampler/select :sample/to/right)) + +(def :keys/sequencer (see :keys/color :keys/launch) + (@shift/I input/add) + (@shift/O output/add)) (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) + (@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 @@ -112,10 +133,6 @@ (@return set :length) (@escape cancel)) -(module :message - (@esc message/dismiss) - (@enter message/dismiss)) - (module :pool (@n rename/begin) (@t length/begin) @@ -145,23 +162,3 @@ (@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)) diff --git a/config/views.edn b/config/views.edn index 435a11e9..0943020c 100644 --- a/config/views.edn +++ b/config/views.edn @@ -1,13 +1,12 @@ -(module :transport - (name "Transport") - (info "A JACK transport controller.") - (keys :clock) - (keys :global) +(mode :transport + (name Transport) + (info A JACK transport controller.) + (keys :clock :global) :view/transport) -(module :arranger - (name "Arranger") - (info "A grid of launchable clips arranged by track and scene.") +(mode :arranger + (name Arranger) + (info A grid of launchable clips arranged by track and scene.) (mode :editor (keys :editor)) (mode :dialog (keys :dialog)) (mode :message (keys :message)) @@ -19,27 +18,27 @@ (mode :track (keys :track)) (mode :scene (keys :scene)) (mode :mix (keys :mix)) - (keys :clock) - (keys :arranger) - (keys :global) + (keys :clock :arranger :global) (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 + (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 :mode/editor (bsp/e :view/scenes/names :view/editor) :view/scenes))))))) -(module :groovebox - (name "Groovebox") - (info "A sequencer with built-in sampler.") +(mode :groovebox + (name Groovebox) + (info A sequencer with built-in sampler.) (mode :browser (keys :browser)) (mode :rename (keys :pool-rename)) (mode :length (keys :pool-length)) - (keys :clock) - (keys :editor) - (keys :sampler) - (keys :global) + (keys :clock :editor :sampler :global) (bsp/w :view/meters/output (bsp/e :view/meters/input (bsp/w @@ -54,24 +53,21 @@ (bsp/e :view/samples/keys :view/editor))))))) -(module :sampler - (name "Sampler") - (info "A sampling soundboard.") - (keys :sampler) - (keys :global) +(mode :sampler + (name Sampler) + (info A sampling soundboard.) + (keys :sampler :global) (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.") +(mode :sequencer + (name Sequencer) + (info A MIDI sequencer.) (mode :browser (keys :browser)) (mode :rename (keys :pool-rename)) (mode :length (keys :pool-length)) - (keys :editor) - (keys :clock) - (keys :global) + (keys :editor :clock :global) (bsp/s (fixed/y 1 :view/transport) (bsp/n (fixed/y 1 :view/status) (fill/xy (bsp/a (fill/xy (align/e :view/pool)) diff --git a/crates/app/app.rs b/crates/app/app.rs index 9731d67e..50773c17 100644 --- a/crates/app/app.rs +++ b/crates/app/app.rs @@ -69,16 +69,36 @@ pub struct App { /// Base color. pub color: ItemTheme, } -type ModuleMap = Arc, T>>>; /// Configuration #[derive(Default, Debug)] pub struct Config { /// XDG basedirs - pub dirs: BaseDirectories, + pub dirs: BaseDirectories, /// Available view profiles - pub views: ModuleMap, + pub views: Arc, Profile>>>, /// Available input bindings - pub binds: ModuleMap, Arc>>, + pub binds: Arc, EventMap, Arc>>>>, +} +/// Profile +#[derive(Default, Debug)] +pub struct Profile { + /// Path of configuration entrypoint + pub path: PathBuf, + /// Default mode + pub mode: Mode, + /// Optional modes + pub modes: BTreeMap, Mode>, +} +#[derive(Default, Debug)] +pub struct Mode { + /// Name of configuration + pub name: Vec>, + /// Description of configuration + pub info: Vec>, + /// View definition + pub view: Vec>, + // Input keymap + pub keys: Vec>, } impl Config { const VIEWS: &'static str = "views.edn"; @@ -88,16 +108,14 @@ impl Config { pub fn init () -> Usually { let mut cfgs: Self = Default::default(); cfgs.dirs = BaseDirectories::with_profile("tek", "v0"); - cfgs.load(Self::VIEWS, Self::DEFAULT_VIEWS, |cfgs, dsl|cfgs.load_view(dsl))?; cfgs.load(Self::BINDS, Self::DEFAULT_BINDS, |cfgs, dsl|cfgs.load_bind(dsl))?; - println!("{cfgs:#?}"); + cfgs.load(Self::VIEWS, Self::DEFAULT_VIEWS, |cfgs, dsl|cfgs.load_view(dsl))?; Ok(cfgs) } pub fn load ( &mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()> ) -> Usually<()> { if self.dirs.find_config_file(path).is_none() { - println!("init: {path}"); std::fs::write(self.dirs.place_config_file(path)?, defaults); } Ok(if let Some(path) = self.dirs.find_config_file(path) { @@ -107,15 +125,10 @@ impl Config { return Err(format!("{path}: not found").into()) }) } - pub fn load_view (&mut self, dsl: D) -> Usually<()> { - load_mod(dsl, |id, tail|Ok(self.views.write().unwrap().insert(id.into(), - Profile::from_dsl(tail)?))) - } pub fn load_bind (&mut self, dsl: D) -> Usually<()> { - load_mod(dsl, |id, tail|Ok(self.binds.write().unwrap().insert(id.into(), { + load_modules(dsl, |id, tail|Ok(self.binds.write().unwrap().insert(id.into(), { let mut map = EventMap::new(); tail.each(|item|{ - println!("{item:?}"); map.add(TuiEvent::from_dsl(item.exp()?.head()?)?, Binding { command: item.exp()?.tail()?.unwrap_or_default().into(), condition: None, @@ -127,8 +140,59 @@ impl Config { map }))) } + pub fn load_view (&mut self, dsl: D) -> Usually<()> { + load_modules(&dsl, |id, tail|Ok(self.views.write().unwrap().insert(id.into(), { + let mut profile = Profile::default(); + tail.each(|item|Ok(if let Ok(Some(exp)) = item.exp() { + match exp.head()? { + Some("name") => profile.mode.name.push( + exp.tail()?.map(|x|x.trim()).unwrap_or("").into() + ), + Some("info") => profile.mode.info.push( + exp.tail()?.map(|x|x.trim()).unwrap_or("").into() + ), + Some("keys") => if let Some(tail) = exp.tail()? { + tail.each(|keys|Ok(profile.mode.keys.push(keys.trim().into())))?; + } else { + return Err(format!("load_view: empty keys: {exp}").into()) + }, + Some("mode") => if let (Some(name), Some(tail)) = ( + exp.tail()?.head()?, exp.tail()?.tail()?, + ) { + let mut mode: Mode = Default::default(); + tail.each(|item|Ok(if let Ok(Some(exp)) = item.exp() { + match exp.head()? { + Some("keys") => if let Some(tail) = exp.tail()? { + tail.each(|keys|Ok(mode.keys.push(keys.trim().into())))?; + } else { + return Err(format!("load_view: empty keys: {exp}").into()) + }, + _ => { + return Err(format!("load_view: unexpected in mode {name}: {item:?}").into()) + } + } + } else if let Ok(Some(sym)) = item.sym() { + // TODO + } else { + return Err(format!("load_view: unexpected in mode {name}: {item:?}").into()) + }))?; + profile.modes.insert(name.trim().into(), mode); + } else { + return Err(format!("load_view: empty mode: {exp}").into()) + }, + Some(_) => profile.mode.view.push(exp.into()), + None => return Err(format!("load_view: empty: {exp}").into()) + } + } else if let Ok(Some(sym)) = item.sym() { + profile.mode.view.push(sym.into()); + } else { + return Err(format!("load_view: unexpected: {dsl:?}").into()) + }))?; + profile + }))) + } } -fn load_mod (dsl: D, cb: impl Fn(&str, &str)->Usually) -> Usually<()> { +fn load_modules (dsl: D, cb: impl Fn(&str, &str)->Usually) -> Usually<()> { //dsl!(dsl|module :id ..body|dsl!(body|@bind #info? ..commands|) if let Some(exp) = dsl.exp()? && Some("module") == exp.head().key()? @@ -142,66 +206,6 @@ fn load_mod (dsl: D, cb: impl Fn(&str, &str)->Usually) -> Usually return Err("unexpected: {exp:?}".into()); } } -/// Profile -#[derive(Default, Debug)] -pub struct Profile { - /// Path of configuration entrypoint - pub path: PathBuf, - /// Name of configuration - pub name: Option>, - /// Description of configuration - pub info: Option>, - /// View definition - pub view: Arc, - // Input keymap - pub keys: EventMap, -} -impl Profile { - fn from_dsl (dsl: impl Dsl) -> Usually { - let mut profile = Self { ..Default::default() }; - dsl.each(|dsl|{ - let head = dsl.head(); - let exp = dsl.exp(); - Ok(if exp.head().key() == Ok(Some("name")) { - profile.name = Some(exp.tail()?.unwrap_or_default().into()); - } else if exp.head().key() == Ok(Some("info")) { - profile.info = Some(exp.tail()?.unwrap_or_default().into()); - }) - })?; - Ok(profile) - } -} -impl Profile { - fn load_template (&mut self, dsl: impl Dsl) -> Usually<&mut Self> { - //dsl.src()?.unwrap_or_default().each(|item|Ok(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()?.unwrap_or_default().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) - } -} /// Various possible dialog modes. impl App { pub fn update_clock (&self) { @@ -230,12 +234,6 @@ maybe_has!(Scene: |self: App| { MaybeHas::::get(&self.project) }; impl HasClipsSize for App { fn clips_size (&self) -> &Measure { &self.project.inner_size } } impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } } impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } } -fn unquote (x: &str) -> &str { - let mut chars = x.chars(); - chars.next(); - //chars.next_back(); - chars.as_str() -} #[derive(Debug, Clone, Default)] pub enum Dialog { diff --git a/crates/app/app_ctrl.rs b/crates/app/app_ctrl.rs index a1240359..44ab3bcf 100644 --- a/crates/app/app_ctrl.rs +++ b/crates/app/app_ctrl.rs @@ -2,7 +2,14 @@ use crate::*; #[derive(Debug)] pub enum AppCommand {} handle!(TuiIn:|self: App, input|{ - panic!("{input:?}"); + panic!("wat: {:?}", self.profile); + for keys in self.profile.mode.keys.iter() { + panic!("{keys} {:?}", self.config.binds.read().unwrap()); + if let Some(binding) = self.config.binds.read().unwrap().get(keys.as_ref()) { + panic!("{binding:?}"); + } + } + Ok(None) //Ok(if let Some(binding) = self.profile.as_ref() //.map(|c|c.keys.dispatch(input.event())).flatten() //{ @@ -95,12 +102,14 @@ dsl_exp!(|app: App| -> AppCommand { ["clock" / command: ClockCommand] => todo!(), ["sampler" / command: SamplerCommand] => todo!(), ["pool" / command: PoolCommand] => todo!(), + ["pool" / editor: MidiEditCommand] => todo!(), }); dsl_exp!(|app: App| -> DialogCommand {}); dsl_exp!(|app: App| -> ArrangementCommand {}); dsl_exp!(|app: App| -> ClockCommand {}); dsl_exp!(|app: App| -> SamplerCommand {}); dsl_exp!(|app: App| -> PoolCommand {}); +dsl_exp!(|app: App| -> MidiEditCommand {}); //dsl_bind!(AppCommand: App { //enqueue = |app, clip: Option>>| { todo!() }; //history = |app, delta: isize| { todo!() }; diff --git a/crates/app/app_dsl.rs b/crates/app/app_dsl.rs index 745da9a6..fe15da92 100644 --- a/crates/app/app_dsl.rs +++ b/crates/app/app_dsl.rs @@ -1,8 +1,23 @@ use crate::*; +/// Namespace. Currently linearly searched. pub struct DslNs<'t, T: 't>(pub &'t [(&'t str, T)]); -pub trait DslSymNs<'t, T: 't>: 't { const NS: DslNs<'t, fn (&'t Self)->T>; } +/// Namespace where keys are symbols. +pub trait DslSymNs<'t, T: 't>: 't { + const NS: DslNs<'t, fn (&'t Self)->T>; + fn from_sym (&'t self, dsl: D) -> Usually { + if let Some(dsl) = dsl.sym()? { + for (sym, get) in Self::NS.0 { + if dsl == *sym { + return Ok(get(self)) + } + } + } + return Err(format!("not found: sym: {dsl:?}").into()) + } +} + #[macro_export] macro_rules! dsl_sym ( (|$state:ident:$State:ty| -> $type:ty {$($lit:literal => $exp:expr),* $(,)?})=>{ impl<'t> DslSymNs<'t, $type> for $State { diff --git a/crates/app/app_view.rs b/crates/app/app_view.rs index 3e9d15c8..03dfbacb 100644 --- a/crates/app/app_view.rs +++ b/crates/app/app_view.rs @@ -20,8 +20,8 @@ pub const VIEW: DslNs<'static, DslCb> = DslNs(&[ Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, (id, profile)) in views.read().unwrap().iter().enumerate() { let bg = if index == 0 { Rgb(64,64,64) } else { Rgb(32,32,32) }; - let name = profile.name.as_ref().map(|x|unquote(unquote(x.as_ref()))); - let info = profile.info.as_ref().map(|x|unquote(unquote(x.as_ref()))); + let name = profile.mode.name.get(0).map(|x|x.as_ref()).unwrap_or(""); + let info = profile.mode.info.get(0).map(|x|x.as_ref()).unwrap_or(""); add(&Fixed::y(3, Tui::bg(bg, Bsp::s( Fill::x(Bsp::a( Fill::x(Align::w(Tui::fg(Rgb(224,192,128), name))), diff --git a/deps/tengri b/deps/tengri index 24ac52d8..e839096c 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 24ac52d8079140b2b9f363d1568597bb5600a166 +Subproject commit e839096cf33f72c000d60b73958e1d6a0ec82be5