arranger: now we're talkin
Some checks are pending
/ build (push) Waiting to run

This commit is contained in:
🪞👃🪞 2025-05-17 20:58:02 +03:00
parent eb0547dc37
commit 2858b01bd4
12 changed files with 379 additions and 391 deletions

View file

@ -1,4 +1,5 @@
use crate::*;
use std::path::PathBuf;
#[derive(Default, Debug)]
pub struct App {
@ -28,6 +29,7 @@ pub struct App {
has!(Jack: |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);
@ -91,12 +93,6 @@ impl App {
pub(crate) fn device_pick (&mut self, index: usize) {
self.dialog = Some(Dialog::Device(index));
}
pub(crate) fn device_kinds (&self) -> &'static [&'static str] {
&[
"Sampler",
"Plugin (LV2)",
]
}
pub(crate) fn device_add (&mut self, index: usize) -> Usually<()> {
match index {
0 => self.device_add_sampler(),
@ -166,35 +162,6 @@ impl App {
}
}
/// Various possible dialog overlays
#[derive(Clone, Debug)]
pub enum Dialog {
Help(usize),
Menu(usize),
Device(usize),
Message(Message),
Browser(BrowserTarget, Browser),
Options,
}
#[derive(Clone, Debug)]
pub enum BrowserTarget {
SaveProject,
LoadProject,
ImportSample(Arc<RwLock<Option<Sample>>>),
ExportSample(Arc<RwLock<Option<Sample>>>),
ImportClip(Arc<RwLock<Option<MidiClip>>>),
ExportClip(Arc<RwLock<Option<MidiClip>>>),
}
/// Various possible messages
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Message {
FailedToAddDevice,
}
content!(TuiOut: |self: Message| match self { Self::FailedToAddDevice => "Failed to add device." });
#[tengri_proc::expose]
impl App {
fn _todo_isize_stub (&self) -> isize {
@ -332,16 +299,214 @@ impl App {
}
fn device_kind_prev (&self) -> usize {
if let Some(Dialog::Device(index)) = self.dialog {
index.overflowing_sub(1).0.min(self.device_kinds().len().saturating_sub(1))
index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1))
} else {
0
}
}
fn device_kind_next (&self) -> usize {
if let Some(Dialog::Device(index)) = self.dialog {
(index + 1) % self.device_kinds().len()
(index + 1) % device_kinds().len()
} else {
0
}
}
}
/// Configuration
#[derive(Default, Debug)]
pub struct Configuration {
/// Path of configuration entrypoint
pub path: PathBuf,
/// Name of configuration
pub name: Option<Arc<str>>,
/// Description of configuration
pub info: Option<Arc<str>>,
/// View definition
pub view: TokenIter<'static>,
// Input keymap
pub keys: InputMap<'static, App, AppCommand, TuiIn, TokenIter<'static>>,
}
impl Configuration {
pub fn new (path: &impl AsRef<Path>, _watch: bool) -> Usually<Self> {
let text = read_and_leak(path.as_ref())?;
let [name, info, view, keys] = Self::parse(TokenIter::from(text))?;
Ok(Self {
path: path.as_ref().into(),
info: info.map(Self::parse_info).flatten(),
name: name.map(Self::parse_name).flatten(),
view: Self::parse_view(view)?,
keys: Self::parse_keys(&path, keys)?,
})
}
fn parse (iter: TokenIter) -> Usually<[Option<TokenIter>;4]> {
let mut name: Option<TokenIter> = None;
let mut info: Option<TokenIter> = None;
let mut view: Option<TokenIter> = None;
let mut keys: Option<TokenIter> = None;
for token in iter {
match token.value {
Value::Exp(_, mut exp) => {
let next = exp.next();
match next {
Some(Token { value: Value::Key(sym), .. }) => match sym {
"name" => name = Some(exp),
"info" => info = Some(exp),
"keys" => keys = Some(exp),
"view" => view = Some(exp),
_ => return Err(
format!("(e3) unexpected symbol {sym:?}").into()
)
},
_ => return Err(
format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into()
)
}
},
t => return Err(
format!("(e1) unexpected token {token:?}").into()
)
};
}
Ok([name, info, view, keys])
}
fn parse_info (mut iter: TokenIter) -> Option<Arc<str>> {
iter.next().and_then(|x|if let Value::Str(x) = x.value {
Some(x.into())
} else {
None
})
}
fn parse_name (mut iter: TokenIter) -> Option<Arc<str>> {
iter.next().and_then(|x|if let Value::Str(x) = x.value {
Some(x.into())
} else {
None
})
}
fn parse_view (iter: Option<TokenIter>) -> Usually<TokenIter> {
if let Some(view) = iter {
Ok(view)
} else {
Err(format!("missing view definition").into())
}
}
fn parse_keys (base: &impl AsRef<Path>, iter: Option<TokenIter<'static>>)
-> Usually<InputMap<'static, App, AppCommand, TuiIn, TokenIter<'static>>>
{
if iter.is_none() {
return Err(format!("missing keys definition").into())
}
let mut keys = iter.unwrap();
let mut map = InputMap::default();
while let Some(token) = keys.next() {
if let Value::Exp(_, mut exp) = token.value {
let next = exp.next();
if let Some(Token { value: Value::Key(sym), .. }) = next {
match sym {
"layer" => {
if let Some(Token { value: Value::Str(path), .. }) = exp.peek() {
let path = base.as_ref().parent().unwrap().join(unquote(path));
if !std::fs::exists(&path)? {
return Err(format!("(e5) not found: {path:?}").into())
}
map.add_layer(read_and_leak(path)?.into());
print!("layer:\n path: {:?}...", exp.0.0.trim());
println!("ok");
} else {
return Err(format!("(e4) unexpected non-string {next:?}").into())
}
},
"layer-if" => {
let mut cond = None;
let next = exp.next();
if let Some(Token { value: Value::Sym(sym), .. }) = next {
cond = Some(leak(sym));
} else {
return Err(format!("(e4) unexpected non-symbol {next:?}").into())
};
if let Some(Token { value: Value::Str(path), .. }) = exp.peek() {
let path = base.as_ref().parent().unwrap().join(unquote(path));
if !std::fs::exists(&path)? {
return Err(format!("(e5) not found: {path:?}").into())
}
print!("layer-if:\n cond: {}\n path: {path:?}...",
cond.unwrap_or_default());
let keys = read_and_leak(path)?.into();
let cond = cond.unwrap();
println!("ok");
map.add_layer_if(
Box::new(move |state|{
let mut exp = exp.clone();
Context::get(state, &mut exp).unwrap_or(false)
}),
keys
);
} else {
return Err(format!("(e4) unexpected non-symbol {next:?}").into())
}
},
_ => return Err(format!("(e3) unexpected symbol {sym:?}").into())
}
} else {
return Err(format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into())
}
} else {
return Err(format!("(e1) unexpected token {token:?}").into())
}
}
Ok(map)
}
}
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"),
];