load mode list

This commit is contained in:
🪞👃🪞 2025-08-03 20:55:45 +03:00
parent 3c8616deba
commit e9f912f4d9
5 changed files with 137 additions and 116 deletions

80
config/profiles.edn Normal file
View file

@ -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)))))

View file

@ -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)))))

View file

@ -1,14 +1,13 @@
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>
pub dirs: BaseDirectories,
pub current: Option<Configuration>,
pub profiles: Arc<RwLock<std::collections::BTreeMap<Arc<str>, Arc<str>>>>,
pub bindings: Arc<RwLock<std::collections::BTreeMap<Arc<str>, Arc<str>>>>,
}
/// Configuration
#[derive(Default, Debug)]
pub struct Configuration {
@ -23,56 +22,68 @@ pub struct Configuration {
// 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");
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<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(()) })?;
cfgs.init_file(Self::PROFILES, Self::DEFAULT_PROFILES)?;
cfgs.load_file(Self::PROFILES, |cfgs, dsl|{
Ok(if dsl.exp().head()?.key()? == Some("module") {
let exp = dsl.exp()?;
let tail = exp.tail()?;
let head = tail.head()?;
if let Some(name) = tail.head()?.sym()? {
let body = tail.tail()?;
cfgs.profiles.write().unwrap().insert(
name.into(),
body.unwrap_or_default().into()
);
}
} 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<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));
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 {
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 +98,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> {

View file

@ -41,6 +41,11 @@ impl App {
Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
add(&Tui::bold(true, "tek 0.3.0-rc0"));
add(&"");
for (name, _) in self.configs.profiles.read().unwrap().iter() {
add(&"");
add(&name);
}
add(&"");
add(&"+ new session");
})
}

2
deps/tengri vendored

@ -1 +1 @@
Subproject commit 9e0b7be9a9c80b5df52854ab426bc60b794931ed
Subproject commit 9ccd7e5c69f6f8aa5ab43f22d5953d56427d4f14