diff --git a/config/profiles.edn b/config/profiles.edn new file mode 100644 index 00000000..918d4d60 --- /dev/null +++ b/config/profiles.edn @@ -0,0 +1,80 @@ +(module :transport + (name "Transport") + (info "A JACK transport controller.") + (keys :clock) + (keys :global) + :view/transport) + +(module :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)) + (mode :device-add (keys :device-add)) + (mode :browser (keys :browser)) + (mode :rename (keys :pool-rename)) + (mode :length (keys :pool-length)) + (mode :clip (keys :clip)) + (mode :track (keys :track)) + (mode :scene (keys :scene)) + (mode :mix (keys :mix)) + (keys :clock) + (keys :arranger) + (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 :mode/editor + (bsp/e :view/scenes/names :view/editor) + :view/scenes))))))) + +(module :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) + :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.") + (keys :sampler) + (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.") + (mode :browser (keys :browser)) + (mode :rename (keys :pool-rename)) + (mode :length (keys :pool-length)) + (keys :editor) + (keys :clock) + (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))))) diff --git a/config/templates.edn b/config/templates.edn deleted file mode 100644 index c3efd64c..00000000 --- a/config/templates.edn +++ /dev/null @@ -1,74 +0,0 @@ -(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))))) diff --git a/crates/app/src/config.rs b/crates/app/src/config.rs index 9c99aba7..6fa417c6 100644 --- a/crates/app/src/config.rs +++ b/crates/app/src/config.rs @@ -1,17 +1,16 @@ use crate::*; use xdg::BaseDirectories; - -/// Configurations -#[derive(Default, Debug)] -pub struct Configurations { - pub dirs: BaseDirectories, - pub modules: Arc, Arc>>>, - pub current: Option -} - /// Configuration #[derive(Default, Debug)] -pub struct Configuration { +pub struct Configurations { + pub dirs: BaseDirectories, + pub current: Option, + pub profiles: Arc, Profile>>>, + pub bindings: Arc, Arc>>>, +} +/// Profile +#[derive(Default, Debug)] +pub struct Profile { /// Path of configuration entrypoint pub path: std::path::PathBuf, /// Name of configuration @@ -23,56 +22,82 @@ pub struct Configuration { // Input keymap pub keys: EventMap, } - -macro_rules! dsl_for_each (($dsl:expr => |$head:ident|$body:expr)=>{ - let mut dsl: Arc = $dsl.src().into(); - let mut $head: Option> = dsl.head()?.map(Into::into); - let mut tail: Option> = 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 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 Configurations { - const DEFAULT_TEMPLATES: &'static str = include_str!("../../../config/templates.edn"); - const DEFAULT_BINDINGS: &'static str = include_str!("../../../config/bindings.edn"); + const PROFILES: &'static str = "profiles.edn"; + const BINDINGS: &'static str = "bindings.edn"; + const DEFAULT_PROFILES: &'static str = include_str!("../../../config/profiles.edn"); + const DEFAULT_BINDINGS: &'static str = include_str!("../../../config/bindings.edn"); pub fn init () -> Usually { 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(()) })?; + cfgs.init_file(Self::PROFILES, Self::DEFAULT_PROFILES)?; + cfgs.load_file(Self::PROFILES, |cfgs, dsl|{ + Ok(if dsl.exp().head().key() == Ok(Some("module")) { + let exp = dsl.exp()?; + let tail = exp.tail()?; + let head = tail.head()?; + if let Some(id) = head.sym()? { + cfgs.profiles.write().unwrap().insert( + id.into(), + Profile::from_dsl(tail.tail()?)? + ); + } + } else { + return Err("unexpected: {exp:?}".into()); + }) + })?; + //cfgs.init_file(Self::BINDINGS, Self::DEFAULT_BINDINGS)?; + //cfgs.load_file(Self::BINDINGS, |cfgs, dsl|Ok( + //if let Some(exp) = dsl.head()?.exp()? && exp.head()?.key()? == Some("module") { + //let name = exp.tail()?.head()?.unwrap_or_default().into(); + //println!("name = {name}"); + //let body = exp.tail()?.tail()?.unwrap_or_default().into(); + //println!("body = {body}"); + //cfgs.bindings.write().unwrap().insert(name, body); + //} else { + //return Err("unexpected: {exp:?}".into()); + //} + //))?; + println!("{cfgs:#?}"); 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); + if self.dirs.find_config_file(path).is_none() { + std::fs::write(self.dirs.place_config_file("profiles.edn")?, Self::DEFAULT_PROFILES); } Ok(()) } - fn load_file (&mut self, path: &str, mut each: impl FnMut(&Arc)->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)); + fn load_file ( + &mut self, + path: &str, + mut each: impl FnMut(&mut Self, &str)->Usually<()> + ) -> Usually<()> { + Ok(if let Some(path) = self.dirs.find_config_file(path) { + let src = std::fs::read_to_string(&path)?; + src.as_str().each(move|item|each(self, item))?; } else { return Err(format!("{path}: not found").into()) }) } } - -impl Configuration { +impl Profile { fn load_template (&mut self, dsl: impl Dsl) -> Usually<&mut Self> { - dsl_for_each!(dsl => |dsl|match () { + 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()), @@ -87,14 +112,13 @@ impl Configuration { _ => return Err(format!("missing keys definition").into()) }, Some("view") => match exp.tail()? { - Some(tail) => self.view = tail.src().into(), + 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> { diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index e31fe771..23a1f0ab 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -38,11 +38,19 @@ impl App { "nil" } pub fn view_menu (&self) -> impl Content + use<'_> { - Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ - add(&Tui::bold(true, "tek 0.3.0-rc0")); - add(&""); - add(&"+ new session"); - }) + Bsp::s(Fill::x(Fixed::y(3, Tui::bg(Rgb(33,33,33), Tui::bold(true, "tek 0.3.0-rc0")))), + Bsp::n(Fill::x(Fixed::y(3, Tui::bg(Rgb(33,33,33), "+ new session"))), + Fill::xy(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ + for (index, (id, profile)) in self.configs.profiles.read().unwrap().iter().enumerate() { + add(&Fixed::y(3, Tui::bg(if index == 0 { Rgb(64,64,64) } else { Rgb(32,32,32) }, Bsp::s( + Fill::x(Bsp::a( + Fill::x(Align::w(Tui::fg(Rgb(224,192,128), &profile.name))), + Fill::x(Align::e(Tui::fg(Rgb(224,128,32), id))) + )), + Fill::x(Align::w(&profile.info)) + )))); + } + })))) } pub fn view_dialog (&self) -> impl Content + use<'_> { self.dialog.as_ref().map(|dialog|Bsp::b("", diff --git a/deps/tengri b/deps/tengri index 9e0b7be9..104bb1c8 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 9e0b7be9a9c80b5df52854ab426bc60b794931ed +Subproject commit 104bb1c8e76cacf249ccd340712ea7bd2d33b5f6