wip: compiled layouts
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
i do not exist 2026-08-29 18:46:46 +03:00
parent 6771b24f79
commit f1756f9a0e
21 changed files with 534 additions and 263 deletions

2
.gitignore vendored
View file

@ -15,4 +15,4 @@ build/*
.misc .misc
.direnv .direnv
callgrind.* callgrind.*
tracing.* tracing*.*

5
Cargo.lock generated
View file

@ -3892,9 +3892,14 @@ dependencies = [
"konst", "konst",
"midly", "midly",
"palette", "palette",
"parking_lot 0.12.5",
"profiling",
"quanta", "quanta",
"rand 0.8.7", "rand 0.8.7",
"ratatui", "ratatui",
"tracing",
"tracing-flame",
"tracing-subscriber",
"unicode-width 0.2.0", "unicode-width 0.2.0",
] ]

View file

@ -51,8 +51,9 @@ proptest = { version = "^1" }
proptest-derive = { version = "^0.5.1" } proptest-derive = { version = "^0.5.1" }
[features] [features]
default = ["cli", "arranger", "sampler"] default = ["cli", "arranger", "sampler", "prof"]
prof = ["tengri/prof"]
hotpath = ["hotpath/hotpath"] hotpath = ["hotpath/hotpath"]
hotpath-cpu = ["hotpath/hotpath-cpu"] hotpath-cpu = ["hotpath/hotpath-cpu"]
hotpath-alloc = ["hotpath/hotpath-alloc"] hotpath-alloc = ["hotpath/hotpath-alloc"]
@ -82,7 +83,7 @@ vst3 = []
[profile.release] [profile.release]
lto = true lto = true
debug = "line-tables-only" debug = true
[profile.coverage] [profile.coverage]
inherits = "test" inherits = "test"

View file

@ -1,6 +1,6 @@
#export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold" #export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold"
export RUST_BACKTRACE := "1" export RUST_BACKTRACE := "1"
export RUSTFLAGS := "-Zmacro-backtrace -Clink-arg=-fuse-ld=mold" export RUSTFLAGS := "-Zmacro-backtrace -Clink-arg=-fuse-ld=mold -Clink-arg=-Wl,--no-rosegment -Cforce-frame-pointers=yes"
[default] [default]
list: list:
@ -49,11 +49,11 @@ run-init:
rm -rf ~/.config/tek && {{debug}} rm -rf ~/.config/tek && {{debug}}
prof: prof:
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -- new CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 10000 -- new
release := "reset && cargo run --release --" release := "reset && cargo run --release --"
release: release +ARGS="new":
{{release}} {{release}} {{ARGS}}
build-release: build-release:
time cargo build -j4 --release time cargo build -j4 --release

View file

@ -1186,3 +1186,24 @@
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref() //take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
//pub fn tui (
//app: Arc<RwLock<App>>,
//jack: Jack,
//sync_lead: &bool,
//sync_follow: &bool,
//) -> Usually<()> {
//// Run the [Tui] and [Jack] threads with the [App] state.
//Tui::run_main(&jack.run(move|jack|{
//// Between jack init and app's first cycle:
////jack.sync_lead(*sync_lead, |mut state|{
////let clock = app.write().unwrap().clock();
////clock.playhead.update_from_sample(state.position.frame() as f64);
////state.position.bbt = Some(clock.bbt());
////state.position
////})?;
////jack.sync_follow(*sync_follow)?;
//// FIXME: They don't work properly.
//Ok(app)
//})?)?
//}

View file

@ -57,10 +57,10 @@ pub fn config_watch (
move|result|{ move|result|{
match result { match result {
Ok(_events) => if let Err(e) = config_init(config.as_ref()) { Ok(_events) => if let Err(e) = config_init(config.as_ref()) {
*config.as_ref().error.write().unwrap() = Some(format!("{e:?}").into()); *config.as_ref().error.try_write().unwrap() = Some(format!("{e:?}").into());
panic!("{e:?}"); panic!("{e:?}");
} else { } else {
//println!("config updated"); println!("config updated");
}, },
Err(errors) => { Err(errors) => {
panic!("{errors:?}"); panic!("{errors:?}");
@ -71,7 +71,7 @@ pub fn config_watch (
if let Some(path) = config.as_ref().get_file() { if let Some(path) = config.as_ref().get_file() {
//println!("watching: {path:?}"); //println!("watching: {path:?}");
watcher.watch(&path, RecursiveMode::NonRecursive)?; watcher.watch(&path, RecursiveMode::NonRecursive)?;
*config.as_ref().watch.write().unwrap() = Some(watcher); *config.as_ref().watch.try_write().unwrap() = Some(watcher);
Ok(()) Ok(())
} else { } else {
Err(format!("no config path").into()) Err(format!("no config path").into())
@ -83,8 +83,8 @@ pub fn modes_add <'a> (modes: &Modes, expr: impl Language) -> UsuallyRef<'a, ()>
let name = expr.head()?.ok_or("mode: missing name")?; let name = expr.head()?.ok_or("mode: missing name")?;
let body = expr.tail()?.ok_or("mode: missing body")?; let body = expr.tail()?.ok_or("mode: missing body")?;
let mode = Mode::default(); let mode = Mode::default();
let mode = body.each(mode, |c,s|mode_add(c,s))?; let mode = body.each(mode, |c, s|mode_add(c, s))?;
modes.0.write().unwrap().insert(name.into(), Arc::new(mode)); modes.0.try_write().unwrap().insert(name.into(), Arc::new(mode));
Ok(()) Ok(())
} }
@ -113,43 +113,33 @@ pub fn mode_add (mut mode: Mode, dsl: impl Language) -> Usually<Mode> {
let submode = Mode::default(); let submode = Mode::default();
let submode = body.each(submode, |c,s|mode_add(c,s))?; let submode = body.each(submode, |c,s|mode_add(c,s))?;
let modes = mode.modes.clone(); let modes = mode.modes.clone();
modes.0.write().unwrap().insert(name.into(), Arc::new(submode)); modes.0.try_write().unwrap().insert(name.into(), Arc::new(submode));
mode mode
}, },
"keys" => { "keys" => {
dsl.each(mode, |mut mode: Mode, expr: &str|{ tail.each(mode, |mut mode: Mode, expr: &str|{
mode.keys.push(expr.trim().into()); mode.keys.push(expr.trim().into());
Ok(mode) Ok(mode)
})? })?
}, },
"name" => { mode.name.push(tail.into()); mode }, "name" => { mode.name.push(tail.into()); mode },
"info" => { mode.info.push(tail.into()); mode }, "info" => { mode.info.push(tail.into()); mode },
"view" => { mode.view.push(tail.into()); mode }, "view" => { mode.view.push(View::new(tail)?.into()); mode },
_ => { mode.view.push(expr.into()); mode }, _ => { mode.view.push(View::new(tail)?.into()); mode },
} }
} else if let Ok(Some(word)) = dsl.word() { } else if let Ok(Some(word)) = dsl.word() {
mode.view.push(word.into()); mode.view.push(View::new(word)?.into());
mode mode
} else { } else {
return Err(format!("Mode::add: unexpected: {dsl:?}").into()); return Err(format!("Mode::add: unexpected: {dsl:?}").into());
}) })
} }
/// Load custom view definition.
pub fn load_view <'a> (views: &Views, expr: impl Language) -> UsuallyRef<'a, ()> {
let name = expr.head()?.ok_or("view: missing name")?;
let body = expr.tail()?.ok_or("view: missing body")?;
views.write().unwrap().insert(
name.into(),
body.src()?.unwrap_or_default().into()
);
Ok(())
}
pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> { pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> {
println!("\n\rload_bind: {expr:?}");
let name = expr.head()?.ok_or("bind: missing name")?; let name = expr.head()?.ok_or("bind: missing name")?;
let body = expr.tail()?.unwrap_or(""); let body = expr.tail()?.unwrap_or("");
binds.write().unwrap().insert(name.into(), { binds.try_write().unwrap().insert(name.into(), {
let mut map = Bind::new(); let mut map = Bind::new();
body.each((), |_, item: &str|if item.expr().head() == Ok(Some("see")) { body.each((), |_, item: &str|if item.expr().head() == Ok(Some("see")) {
// TODO // TODO
@ -169,10 +159,10 @@ pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()>
// TODO // TODO
return Ok(()) return Ok(())
} else { } else {
return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into()) return Err(format!("load_bind: invalid key: {:?}", item.expr()?.head()?).into())
} }
} else { } else {
return Err(format!("Config::load_bind: unexpected: {item:?}").into()) return Err(format!("load_bind: unexpected: {item:?}").into())
})?; })?;
map map
}); });
@ -238,11 +228,307 @@ pub struct Mode {
pub path: PathBuf, pub path: PathBuf,
pub name: Vec<Arc<str>>, pub name: Vec<Arc<str>>,
pub info: Vec<Arc<str>>, pub info: Vec<Arc<str>>,
pub view: Vec<Arc<str>>, pub view: Vec<Arc<View<Tui, App>>>,
pub keys: Vec<Arc<str>>, pub keys: Vec<Arc<str>>,
pub modes: Modes, pub modes: Modes,
} }
/// Collection of custom view definitions.
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>>>;
/// Custom view definition is a boxed closure emitting a [Draw]able from state `S`.
pub struct View<S: Screen, T> {
pub source: Arc<str>,
pub render: Arc<Box<dyn Fn(&T, &mut S)->Drawn<S::Unit> + Send + Sync>>
}
impl_debug!(<S: Screen, T> View<S, T> |self, w| { write!(w, "View({})", self.source) });
impl_display!(<S: Screen, T> View<S, T> |self, w| { write!(w, "View({})", self.source) });
impl View<Tui, App> {
pub fn new (source: impl AsRef<str>) -> Usually<Self> {
Ok(Self {
source: source.as_ref().into(),
render: Self::compile(source)?
})
}
fn boxed <F: Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static> (f: F)
-> Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static>
{
Box::new(f)
}
fn compile (source: impl AsRef<str>) ->
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{
let source = source.as_ref();
let layer = if let Some(expr) = source.expr()? {
Self::compile_expr(expr.into())?
} else if let Some(word) = source.word()? {
Self::compile_word(word.into())?
} else {
return Err(format!("not word/expr:\n{source:?}").into())
};
Ok(Arc::new(Box::new(move|state, screen|layer(state, screen))))
}
fn compile_expr (expr: Arc<str>) ->
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{
Ok(Arc::new(if let Some(head) = expr.head()? && let Some(ns) = head.split('/').next() {
match ns {
"when" => {
let cond = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("when: no arg0: condition"))?);
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("when: no condition value"));
let thunk = expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("when: no arg1: content"))?;
let thunk = Self::compile(thunk)?;
Self::boxed(move|state, screen|{
when(
cond(state)?,
draw(|screen|thunk(state, screen))
).draw(screen)
})
},
"either" => {
let cond = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("either: no arg0: condition"))?);
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("either: no condition value"));
let a = expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg1: content"))?;
let a = Self::compile(a)?;
let b = expr.nth(3)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: content"))?;
let b = Self::compile(b)?;
Self::boxed(move|state, screen|{
either(
cond(state)?,
draw(|screen|a(state, screen)),
draw(|screen|b(state, screen)),
).draw(screen)
})
},
"bsp" | "split" => {
let split = head.split('/').skip(1).next();
let split = match split {
Some("n") => Split::North,
Some("s") => Split::South,
Some("e") => Split::East,
Some("w") => Split::West,
Some("a") => Split::Above,
Some("b") => Split::Below,
_ => return Err(format!("invalid split: {split:?}").into())
};
let a = Self::compile(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("either: no arg0: content"))?)?;
let b = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg1: content"))?)?;
Self::boxed(move|state, screen|{
split.stack(
draw(|screen|a(state, screen)),
draw(|screen|b(state, screen)),
).draw(screen)
})
},
"align" => {
let azimuth = head.split('/').skip(1).next();
let azimuth = match azimuth {
Some("n") => Azimuth::N,
Some("s") => Azimuth::S,
Some("e") => Azimuth::E,
Some("w") => Azimuth::W,
Some("ne") => Azimuth::NE,
Some("se") => Azimuth::SE,
Some("nw") => Azimuth::NW,
Some("sw") => Azimuth::SW,
Some("c") => Azimuth::C,
Some("x") => Azimuth::X,
Some("y") => Azimuth::Y,
_ => return Err(format!("invalid azimuth: {azimuth:?}").into())
};
let thunk = Self::compile(expr.nth(2)?
.ok_or_else(||Box::<dyn Error>::from("either: no arg1: content"))?)?;
Self::boxed(move|state, screen|{
Align(
Some(azimuth),
draw(|screen|thunk(state, screen))
).draw(screen)
})
},
"full" => {
let thunk = Self::compile(expr.nth(2)?
.ok_or_else(||Box::<dyn Error>::from("either: no arg1: content"))?)?;
match head.split('/').skip(1).next() {
Some("w") | Some("x") => Self::boxed(move|state, screen|{
Full::W(draw(|screen|thunk(state, screen))).draw(screen)
}),
Some("h") | Some("y") => Self::boxed(move|state, screen|{
Full::H(draw(|screen|thunk(state, screen))).draw(screen)
}),
Some("wh") | Some("xy") => Self::boxed(move|state, screen|{
Full::WH(draw(|screen|thunk(state, screen))).draw(screen)
}),
_ => unreachable!()
}
},
"exact" | "min" | "max" | "push" | "pull" => {
match head.split('/').skip(1).next() {
Some("w") | Some("x") => {
let value = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: value"))?);
let value = move|state: &App|state.namespace(&value);
let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: content"))?)?;
match ns {
"exact" => Self::boxed(move|state, screen|Exact::W(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"push" => Self::boxed(move|state, screen|Push::X(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::X(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::W(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::W(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
_ => unreachable!()
}
},
Some("h") | Some("y") => {
let value = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: value"))?);
let value = move|state: &App|state.namespace(&value);
let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: content"))?)?;
match ns {
"exact" => Self::boxed(move|state, screen|Exact::H(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"push" => Self::boxed(move|state, screen|Push::Y(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::Y(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::H(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::H(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
_ => unreachable!()
}
},
Some("wh") | Some("xy") => {
let value1 = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: value"))?);
let value1 = move|state: &App|state.namespace(&value1);
let value2 = Arc::from(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg2: value"))?);
let value2 = move|state: &App|state.namespace(&value2);
let thunk = Self::compile(expr.nth(3)?.ok_or_else(||Box::<dyn Error>::from("either: no arg3: content"))?)?;
match ns {
"exact" => Self::boxed(move|state, screen|Exact::WH(
draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
).draw(screen)),
"push" => Self::boxed(move|state, screen|Push::XY(
draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::XY(
draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::WH(
draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::WH(
draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
).draw(screen)),
_ => unreachable!()
}
},
_ => unreachable!()
}
},
"fg" | "bg" => {
let color = expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: color"))?;
let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: thunk"))?)?;
todo!()
},
"text" => {
todo!()
},
//"align" => Self::boxed(move|state, screen|kw_align(state, screen, expr)),
//"full" => Self::boxed(move|state, screen|kw_full(state, screen, expr)),
//"exact" => Self::boxed(move|state, screen|kw_exact(state, screen, expr)),
//"min" => Self::boxed(move|state, screen|kw_min(state, screen, expr)),
//"max" => Self::boxed(move|state, screen|kw_max(state, screen, expr)),
//"push" => Self::boxed(move|state, screen|kw_push(state, screen, expr)),
//"pull" => Self::boxed(move|state, screen|kw_pull(state, screen, expr)),
//"text" => Self::boxed(move|state, screen|kw_tui_text(state, screen, expr)),
//"fg" => Self::boxed(move|state, screen|kw_tui_fg(state, screen, expr)),
//"bg" => Self::boxed(move|state, screen|kw_tui_bg(state, screen, expr)),
_ => return Err(format!("compile_expr: unexpected: {expr:?}").into())
}
} else {
return Err(format!("compile_expr: invalid expression: {expr:?}").into())
}))
}
fn compile_word (word: Arc<str>)
-> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{
Ok(Arc::new(match word.split("/").next() {
//Some(":logo") => view_logo().draw(to),
Some(":meters") => match word.split("/").skip(1).next() {
Some("input") => Self::boxed(move|_, to|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to)),
Some("output") => Self::boxed(move|_, to|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to)),
_ => panic!()
},
Some(":tracks") => match word.split("/").skip(1).next() {
None => Self::boxed(move|_, to|"TODO tracks".draw(to)),
Some("names") => Self::boxed(move|state, to|state.project.view_track_names(state.color.clone()).draw(to)),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))),
Some("inputs") => Self::boxed(move|state, to|state.project.view_track_inputs(state.color.clone()).draw(to)),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to),
Some("devices") => Self::boxed(move|state, to|state.project.view_track_devices(state.color.clone()).draw(to)),//bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to),
Some("outputs") => Self::boxed(move|state, to|state.project.view_track_outputs(state.color.clone(), 0).draw(to)),
_ => panic!()
},
Some(":scenes") => match word.split("/").skip(1).next() {
None => Self::boxed(move|state, to|state.view_scenes_clips().draw(to)),
Some("names") => Self::boxed(move|state, to|state.view_scenes_names().draw(to)),
_ => panic!()
},
Some(":dialog") => Self::boxed(move|state, to|draw_dialog(to, word.split("/").skip(1), state)),
Some(":templates") => Self::boxed(move|state, to|view_templates(state).draw(to)),
Some(":sessions") => Self::boxed(move|state, to|view_sessions().draw(to)),
Some(":browse/title") => Self::boxed(move|state, to|view_browse_title(state).draw(to)),
Some(":device") => Self::boxed(move|state, to|view_device(state).draw(to)),
Some(":status") => Self::boxed(move|state, to|"TODO: Status Bar".draw(to)),
Some(":editor") => Self::boxed(move|state, to|"TODO Editor".draw(to)),
Some(":transport") => Self::boxed(move|state, to|view_transport(true, "", "", "").draw(to)),
Some(":debug") => Self::boxed(move|state, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)),
Some(_) => Self::boxed(move|state, to|if let Some(view) = state.config.get_view(word.as_ref()) {
(view.render)(state, to)
} else {
fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to)
}),
_ => unreachable!()
}))
}
}
/// Load custom view definition.
pub fn load_view <'a> (views: &Views, expr: impl Language) -> UsuallyRef<'a, ()> {
views.try_write().unwrap().insert(
expr.head()?.ok_or("view: missing name")?.into(),
View::new(expr.tail()?.ok_or("view: missing body")?)?.into()
);
Ok(())
}
impl Config { impl Config {
/// Default configuration directory. /// Default configuration directory.
@ -308,44 +594,32 @@ impl Config {
/// Make this configuration empty. /// Make this configuration empty.
fn clear (&self) { fn clear (&self) {
*self.modes.0.write().unwrap() = Default::default(); *self.modes.0.try_write().unwrap() = Default::default();
*self.views.write().unwrap() = Default::default(); *self.views.try_write().unwrap() = Default::default();
*self.binds.write().unwrap() = Default::default(); *self.binds.try_write().unwrap() = Default::default();
} }
pub fn get_view (&self, name: impl AsRef<str>) -> Option<Arc<str>> { pub fn get_view (&self, name: impl AsRef<str>) -> Option<Arc<View<Tui, App>>> {
self.views.read().unwrap().get(name.as_ref()).cloned() self.views.try_read().unwrap().get(name.as_ref()).cloned()
} }
} }
pub use self::view::*; impl Modes {
mod view { /// Get a mode by name.
use crate::*; pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<Mode>> {
/// Collection of custom view definitions. self.0.try_read().unwrap().get(name.as_ref()).cloned()
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>; }
} /// Run something for each mode.
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode)->T) {
pub use self::mode::*; for (k, v) in self.0.try_read().unwrap().iter() {
mod mode { let _ = ator(k.as_ref(), v.as_ref());
use crate::*;
impl Modes {
/// Get a mode by name.
pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<Mode>> {
self.0.read().unwrap().get(name.as_ref()).cloned()
}
/// Run something for each mode.
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode)->T) {
for (k, v) in self.0.read().unwrap().iter() {
let _ = ator(k.as_ref(), v.as_ref());
}
}
/// Count modes.
pub fn len (&self) -> usize {
self.0.read().unwrap().len()
} }
} }
/// Count modes.
pub fn len (&self) -> usize {
self.0.try_read().unwrap().len()
}
} }
pub use self::bind::*; pub use self::bind::*;
@ -449,10 +723,10 @@ mod bind {
pub fn print_config (config: &Config) { pub fn print_config (config: &Config) {
use ::ansi_term::Color::*; use ::ansi_term::Color::*;
println!("{:?}", config.dirs); println!("{:?}", config.dirs);
for (k, v) in config.views.read().unwrap().iter() { for (k, v) in config.views.try_read().unwrap().iter() {
println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}"))); println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source);
} }
for (k, v) in config.binds.read().unwrap().iter() { for (k, v) in config.binds.try_read().unwrap().iter() {
println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}"))); println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}")));
for (k, v) in v.0.iter() { for (k, v) in v.0.iter() {
print!("{} ", &Yellow.paint(match &k.0 { print!("{} ", &Yellow.paint(match &k.0 {

View file

@ -1,8 +1,7 @@
pub extern crate atomic_float; pub extern crate atomic_float;
pub extern crate xdg; pub extern crate xdg;
pub extern crate tengri; pub extern crate tengri;
#[cfg(feature = "cli")]
pub(crate) use ::clap::{self, Parser, Subcommand};
#[allow(unused)] #[allow(unused)]
pub(crate) use ::{ pub(crate) use ::{
std::{ std::{
@ -14,10 +13,13 @@ pub(crate) use ::{
fs::File, fs::File,
ops::{Add, Sub, Mul, Div, Rem}, ops::{Add, Sub, Mul, Div, Rem},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, AtomicU64, Ordering::Relaxed}}, sync::{Arc, atomic::{AtomicBool, AtomicUsize, AtomicU64, Ordering::Relaxed}},
time::Duration, time::Duration,
thread::{spawn, JoinHandle}, thread::{spawn, JoinHandle},
}, },
atomic_float::{
AtomicF64
},
xdg::{ xdg::{
BaseDirectories, BaseDirectories,
}, },
@ -35,5 +37,22 @@ pub(crate) use ::{
prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}}, prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}},
widgets::{Widget, canvas::{Canvas, Line}}, widgets::{Widget, canvas::{Canvas, Line}},
}, },
parking_lot::{
RwLock,
RwLockReadGuard,
RwLockWriteGuard,
RawRwLock,
}
}, },
}; };
#[cfg(feature = "prof")]
pub use ::tengri::{
profiling,
tracing,
tracing_flame,
tracing_subscriber
};
#[cfg(feature = "cli")]
pub(crate) use ::clap::{self, Parser, Subcommand};

View file

@ -1,33 +1,10 @@
use crate::*; use crate::*;
/// TODO: Preserve the generic passthru syntax;
/// remove this macro (only used twice) and potentially the trait.
#[macro_export] macro_rules! impl_has_clips {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? {
fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> {
$cb.read().unwrap()
}
fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> {
$cb.write().unwrap()
}
}
}
}
#[macro_export] macro_rules! has_clip {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
fn clip (&$self) -> Option<Arc<RwLock<MidiClip>>> { $cb }
}
}
}
impl Arrangement { impl Arrangement {
/// Toggle looping for the active clip /// Toggle looping for the active clip
pub fn toggle_loop (&mut self) { pub fn toggle_loop (&mut self) {
if let Some(clip) = self.selected_clip() { if let Some(clip) = self.selected_clip() {
clip.write().unwrap().toggle_loop() clip.try_write().unwrap().toggle_loop()
} }
} }
@ -45,7 +22,7 @@ impl Arrangement {
&self, track: usize, scene: usize, color: ItemTheme &self, track: usize, scene: usize, color: ItemTheme
) -> Option<ItemTheme> { ) -> Option<ItemTheme> {
self.scenes[scene].clips[track].as_ref().map(|clip|{ self.scenes[scene].clips[track].as_ref().map(|clip|{
let mut clip = clip.write().unwrap(); let mut clip = clip.try_write().unwrap();
let old = clip.color.clone(); let old = clip.color.clone();
clip.color = color.clone(); clip.color = color.clone();
panic!("{color:?} {old:?}"); panic!("{color:?} {old:?}");
@ -116,7 +93,7 @@ pub trait ClipsView: TracksView + ScenesView {
fn view_scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, ItemTheme) { fn view_scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, ItemTheme) {
if let Some(Some(clip)) = &scene.clips.get(track_index) { if let Some(Some(clip)) = &scene.clips.get(track_index) {
let clip = clip.read().unwrap(); let clip = clip.try_read().unwrap();
(format!("{}", &clip.name).into(), clip.color) (format!("{}", &clip.name).into(), clip.color)
} else { } else {
(" ⏹ -- ".into(), ItemTheme::G[32]) (" ⏹ -- ".into(), ItemTheme::G[32])

View file

@ -31,7 +31,7 @@ impl Scene {
/// Get pulse length of the longest clip in the scene /// Get pulse length of the longest clip in the scene
pub fn pulses (&self) -> usize { pub fn pulses (&self) -> usize {
self.clips.iter().fold(0, |a, p|{ self.clips.iter().fold(0, |a, p|{
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) a.max(p.as_ref().map(|q|q.try_read().unwrap().length).unwrap_or(0))
}) })
} }
@ -43,7 +43,7 @@ impl Scene {
.get(track_index) .get(track_index)
.map(|track|{ .map(|track|{
if let Some((_, Some(clip))) = track.sequencer().play_clip() { if let Some((_, Some(clip))) = track.sequencer().play_clip() {
*clip.read().unwrap() == *c.read().unwrap() *clip.try_read().unwrap() == *c.try_read().unwrap()
} else { } else {
false false
} }

View file

@ -91,7 +91,7 @@ impl Selection {
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()), tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()),
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) { TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
(Some(_), Some(s)) => match s.clip(*track) { (Some(_), Some(s)) => match s.clip(*track) {
Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name), Some(clip) => format!("T{track} S{scene} C{}", &clip.try_read().unwrap().name),
None => format!("T{track} S{scene}: Empty") None => format!("T{track} S{scene}: Empty")
}, },
_ => format!("T{track} S{scene}: Empty"), _ => format!("T{track} S{scene}: Empty"),

View file

@ -1,6 +1,4 @@
use crate::*; use crate::*;
use ::std::sync::{Arc, RwLock, atomic::AtomicUsize};
use ::atomic_float::AtomicF64;
mod memo; pub use self::memo::*; mod memo; pub use self::memo::*;
mod moment; pub use self::moment::*; mod moment; pub use self::moment::*;
@ -328,11 +326,11 @@ impl Clock {
} }
/// Is currently paused? /// Is currently paused?
pub fn is_stopped (&self) -> bool { pub fn is_stopped (&self) -> bool {
self.started.read().unwrap().is_none() self.started.try_read().unwrap().is_none()
} }
/// Is currently playing? /// Is currently playing?
pub fn is_rolling (&self) -> bool { pub fn is_rolling (&self) -> bool {
self.started.read().unwrap().is_some() self.started.try_read().unwrap().is_some()
} }
/// Update chunk size /// Update chunk size
pub fn set_chunk (&self, n_frames: usize) { pub fn set_chunk (&self, n_frames: usize) {
@ -347,7 +345,7 @@ impl Clock {
self.global.sample.set(current_frames as f64); self.global.sample.set(current_frames as f64);
self.global.usec.set(current_usecs as f64); self.global.usec.set(current_usecs as f64);
let mut started = self.started.write().unwrap(); let mut started = self.started.try_write().unwrap();
// If transport has just started or just stopped, // If transport has just started or just stopped,
// update starting point: // update starting point:
@ -401,7 +399,7 @@ impl Clock {
pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{ pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{
(scope.last_frame_time() as usize).saturating_sub( (scope.last_frame_time() as usize).saturating_sub(
started.sample.get() as usize + started.sample.get() as usize +
self.started.read().unwrap().as_ref().unwrap().sample.get() as usize self.started.try_read().unwrap().as_ref().unwrap().sample.get() as usize
) )
} }

View file

@ -38,7 +38,7 @@ impl ClockView {
let chunk = clock.chunk.load(Relaxed) as f64; let chunk = clock.chunk.load(Relaxed) as f64;
let lat = chunk / rate * 1000.; let lat = chunk / rate * 1000.;
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
let mut cache = cache.write().unwrap(); let mut cache = cache.try_write().unwrap();
cache.buf.update( cache.buf.update(
Some(chunk), rewrite!(buf, "{chunk}") Some(chunk), rewrite!(buf, "{chunk}")
@ -59,7 +59,7 @@ impl ClockView {
} }
); );
if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) { if let Some(now) = clock.started.try_read().unwrap().as_ref().map(delta) {
let pulse = clock.timebase.usecs_to_pulse(now); let pulse = clock.timebase.usecs_to_pulse(now);
let time = now/1000000.; let time = now/1000000.;
let bpm = clock.timebase.bpm.get(); let bpm = clock.timebase.bpm.get();

View file

@ -1,6 +1,6 @@
use crate::{*, device::*}; use crate::{*, device::*};
pub fn draw_dialog <'a> (to: &mut Tui, mut frags: std::str::Split<&str>, state: &App) pub fn draw_dialog <'a, I: Debug + Iterator<Item = &'a str>> (to: &mut Tui, mut frags: I, state: &App)
-> Drawn<u16> -> Drawn<u16>
{ {
match frags.next() { match frags.next() {

View file

@ -30,7 +30,7 @@ impl App {
let (_index, clip) = self.pool.add_new_clip(); let (_index, clip) = self.pool.add_new_clip();
// autocolor: new clip colors from scene and track color // autocolor: new clip colors from scene and track color
let color = track.color.base.mix(scene.color.base, 0.5); let color = track.color.base.mix(scene.color.base, 0.5);
clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into(); clip.try_write().unwrap().color = ItemColor::random_near(color, 0.2).into();
if let Some(editor) = &mut self.project.editor { if let Some(editor) = &mut self.project.editor {
editor.set_clip(Some(&clip)); editor.set_clip(Some(&clip));
} }
@ -46,11 +46,11 @@ impl App {
{ {
// Remove clip from arrangement when exiting empty clip editor // Remove clip from arrangement when exiting empty clip editor
let mut swapped = None; let mut swapped = None;
if clip.read().unwrap().count_midi_messages() == 0 { if clip.try_read().unwrap().count_midi_messages() == 0 {
std::mem::swap(&mut swapped, slot); std::mem::swap(&mut swapped, slot);
} }
if let Some(clip) = swapped { if let Some(clip) = swapped {
self.pool.delete_clip(&clip.read().unwrap()); self.pool.delete_clip(&clip.try_read().unwrap());
} }
} }
} }
@ -210,7 +210,7 @@ impl MidiEditor {
pub fn put_note (&mut self, advance: bool) { pub fn put_note (&mut self, advance: bool) {
let mut redraw = false; let mut redraw = false;
if let Some(clip) = self.clip() { if let Some(clip) = self.clip() {
let mut clip = clip.write().unwrap(); let mut clip = clip.try_write().unwrap();
let note_start = self.get_time_pos(); let note_start = self.get_time_pos();
let note_pos = self.get_note_pos(); let note_pos = self.get_note_pos();
let note_len = self.get_note_len(); let note_len = self.get_note_len();
@ -236,7 +236,7 @@ impl MidiEditor {
self.mode.redraw(); self.mode.redraw();
} }
} }
fn clip_length (&self) -> usize { self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) } fn clip_length (&self) -> usize { self.clip().as_ref().map(|p|p.try_read().unwrap().length).unwrap_or(1) }
fn note_length (&self) -> usize { self.get_note_len() } fn note_length (&self) -> usize { self.get_note_len() }
fn note_pos (&self) -> usize { self.get_note_pos() } fn note_pos (&self) -> usize { self.get_note_pos() }
fn note_pos_next (&self) -> usize { self.get_note_pos() + 1 } fn note_pos_next (&self) -> usize { self.get_note_pos() + 1 }
@ -269,7 +269,7 @@ impl MidiEditor {
.0.min(self.clip_length().saturating_sub(1)) .0.min(self.clip_length().saturating_sub(1))
} }
pub fn clip_status (&self) -> impl Draw<Tui> + '_ { pub fn clip_status (&self) -> impl Draw<Tui> + '_ {
let (_color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { let (_color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.try_read().unwrap()) {
(clip.color, clip.name.clone(), clip.length, clip.looped) (clip.color, clip.name.clone(), clip.length, clip.looped)
} else { (ItemTheme::G[64], String::new().into(), 0, false) }; } else { (ItemTheme::G[64], String::new().into(), 0, false) };
south!( south!(
@ -282,7 +282,7 @@ impl MidiEditor {
).exact_w(20) ).exact_w(20)
} }
pub fn edit_status (&self) -> impl Draw<Tui> + '_ { pub fn edit_status (&self) -> impl Draw<Tui> + '_ {
let (_color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { let (_color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.try_read().unwrap()) {
(clip.color, clip.length) (clip.color, clip.length)
} else { (ItemTheme::G[64], 0) }; } else { (ItemTheme::G[64], 0) };
let time_pos = self.get_time_pos(); let time_pos = self.get_time_pos();

View file

@ -53,7 +53,7 @@ impl PianoHorizontal {
buffer: RwLock::new(Default::default()).into(), buffer: RwLock::new(Default::default()).into(),
point: MidiCursor::default(), point: MidiCursor::default(),
clip: clip.cloned(), clip: clip.cloned(),
color: clip.as_ref().map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]), color: clip.as_ref().map(|p|p.try_read().unwrap().color).unwrap_or(ItemTheme::G[64]),
}; };
piano.redraw(); piano.redraw();
piano piano
@ -140,7 +140,7 @@ impl PianoHorizontal {
draw(move|to: &mut Tui|{ draw(move|to: &mut Tui|{
let xywh = to.area().into(); let xywh = to.area().into();
let XYWH(x0, y0, w, _h) = xywh; let XYWH(x0, y0, w, _h) = xywh;
let source = buffer.read().unwrap(); let source = buffer.try_read().unwrap();
//if h as usize != note_axis { //if h as usize != note_axis {
//panic!("area height mismatch: {h} <> {note_axis}"); //panic!("area height mismatch: {h} <> {note_axis}");
//} //}
@ -234,7 +234,7 @@ impl PianoHorizontal {
let xywh = to.area().into(); let xywh = to.area().into();
let XYWH(x, y, w, _h) = xywh; let XYWH(x, y, w, _h) = xywh;
let style = Some(Style::default().dim()); let style = Some(Style::default().dim());
let length = self.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); let length = self.clip.as_ref().map(|p|p.try_read().unwrap().length).unwrap_or(1);
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
let t = area_x as usize * self.time_zoom().load(Relaxed); let t = area_x as usize * self.time_zoom().load(Relaxed);
if t < length { if t < length {
@ -276,8 +276,8 @@ impl MidiViewer for PianoHorizontal {
(clip.length / self.range.time_zoom().load(Relaxed), 128) (clip.length / self.range.time_zoom().load(Relaxed), 128)
} }
fn redraw (&self) { fn redraw (&self) {
*self.buffer.write().unwrap() = if let Some(clip) = self.clip.as_ref() { *self.buffer.try_write().unwrap() = if let Some(clip) = self.clip.as_ref() {
let clip = clip.read().unwrap(); let clip = clip.try_read().unwrap();
let buf_size = self.buffer_size(&clip); let buf_size = self.buffer_size(&clip);
let mut buffer = BigBuffer::from(buf_size); let mut buffer = BigBuffer::from(buf_size);
let time_zoom = self.get_time_zoom(); let time_zoom = self.get_time_zoom();
@ -291,14 +291,14 @@ impl MidiViewer for PianoHorizontal {
} }
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) { fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
*self.clip_mut() = clip.cloned(); *self.clip_mut() = clip.cloned();
self.color = clip.map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]); self.color = clip.map(|p|p.try_read().unwrap().color).unwrap_or(ItemTheme::G[64]);
self.redraw(); self.redraw();
} }
} }
impl std::fmt::Debug for PianoHorizontal { impl std::fmt::Debug for PianoHorizontal {
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let buffer = self.buffer.read().unwrap(); let buffer = self.buffer.try_read().unwrap();
f.debug_struct("PianoHorizontal") f.debug_struct("PianoHorizontal")
.field("time_zoom", &self.range.time_zoom) .field("time_zoom", &self.range.time_zoom)
.field("buffer", &format!("{}x{}", buffer.width, buffer.height)) .field("buffer", &format!("{}x{}", buffer.width, buffer.height))

View file

@ -151,7 +151,7 @@ pub trait PoolController: HasPool
/// Delete a clip from the pool /// Delete a clip from the pool
#[command(Delete = "delete")] #[command(Delete = "delete")]
fn delete (&mut self, index: usize) -> Perhaps<PoolCommand> { fn delete (&mut self, index: usize) -> Perhaps<PoolCommand> {
let clip = self.pool_mut().clips_mut().remove(index).read().unwrap().clone(); let clip = self.pool_mut().clips_mut().remove(index).try_read().unwrap().clone();
Ok(Some(PoolCommand::Add { index, clip })) Ok(Some(PoolCommand::Add { index, clip }))
} }
@ -181,8 +181,8 @@ pub trait PoolController: HasPool
#[command(SetName = "set-name")] #[command(SetName = "set-name")]
fn clip_set_name (&mut self, index: usize, name: Arc<str>) -> Perhaps<PoolCommand> { fn clip_set_name (&mut self, index: usize, name: Arc<str>) -> Perhaps<PoolCommand> {
let clip = &mut self.pool_mut().clips_mut()[index]; let clip = &mut self.pool_mut().clips_mut()[index];
let old_name = clip.read().unwrap().name.clone(); let old_name = clip.try_read().unwrap().name.clone();
clip.write().unwrap().name = name.clone(); clip.try_write().unwrap().name = name.clone();
Ok(Some(PoolCommand::SetName { index, name: old_name })) Ok(Some(PoolCommand::SetName { index, name: old_name }))
} }
@ -190,8 +190,8 @@ pub trait PoolController: HasPool
#[command(SetLength = "set-length")] #[command(SetLength = "set-length")]
fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps<PoolCommand> { fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps<PoolCommand> {
let clip = &mut self.pool_mut().clips_mut()[index]; let clip = &mut self.pool_mut().clips_mut()[index];
let old_len = clip.read().unwrap().length; let old_len = clip.try_read().unwrap().length;
clip.write().unwrap().length = length; clip.try_write().unwrap().length = length;
Ok(Some(PoolCommand::SetLength { index, length: old_len })) Ok(Some(PoolCommand::SetLength { index, length: old_len }))
} }
@ -199,7 +199,7 @@ pub trait PoolController: HasPool
#[command(SetColor = "set-color")] #[command(SetColor = "set-color")]
fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps<PoolCommand> { fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps<PoolCommand> {
let mut color = ItemTheme::from(color); let mut color = ItemTheme::from(color);
std::mem::swap(&mut color, &mut self.pool().clips()[index].write().unwrap().color); std::mem::swap(&mut color, &mut self.pool().clips()[index].try_write().unwrap().color);
Ok(Some(PoolCommand::SetColor { index, color: color.base })) Ok(Some(PoolCommand::SetColor { index, color: color.base }))
} }
@ -207,7 +207,7 @@ pub trait PoolController: HasPool
#[command(CropBegin = "crop/begin")] #[command(CropBegin = "crop/begin")]
fn crop_begin (&mut self) -> Perhaps<PoolCommand> { fn crop_begin (&mut self) -> Perhaps<PoolCommand> {
let index = self.pool().clip_index(); let index = self.pool().clip_index();
let length = self.pool().clips()[index].read().unwrap().length; let length = self.pool().clips()[index].try_read().unwrap().length;
*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar)); *self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar));
Ok(None) Ok(None)
} }
@ -228,9 +228,9 @@ pub trait PoolController: HasPool
{ {
let old_length; let old_length;
{ {
let clip = self.pool().clips()[clip].clone();//.write().unwrap(); let clip = self.pool().clips()[clip].clone();//.try_write().unwrap();
old_length = Some(clip.read().unwrap().length); old_length = Some(clip.try_read().unwrap().length);
clip.write().unwrap().length = *length; clip.try_write().unwrap().length = *length;
} }
*self.pool_mut().mode_mut() = None; *self.pool_mut().mode_mut() = None;
return Ok(old_length.map(|l|PoolCommand::CropSet { _length: l })) return Ok(old_length.map(|l|PoolCommand::CropSet { _length: l }))
@ -290,7 +290,7 @@ pub trait PoolController: HasPool
#[command(RenameBegin = "rename/begin")] #[command(RenameBegin = "rename/begin")]
fn rename_begin (&mut self) -> Perhaps<PoolCommand> { fn rename_begin (&mut self) -> Perhaps<PoolCommand> {
let index = self.pool().clip_index(); let index = self.pool().clip_index();
let name = self.pool().clips()[index].read().unwrap().name.clone(); let name = self.pool().clips()[index].try_read().unwrap().name.clone();
*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name)); *self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name));
Ok(None) Ok(None)
} }
@ -299,7 +299,7 @@ pub trait PoolController: HasPool
#[command(RenameCancel = "rename/cancel")] #[command(RenameCancel = "rename/cancel")]
fn rename_cancel (&mut self) -> Perhaps<PoolCommand> { fn rename_cancel (&mut self) -> Perhaps<PoolCommand> {
if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() {
self.pool().clips()[clip].write().unwrap().name = old_name.clone().into(); self.pool().clips()[clip].try_write().unwrap().name = old_name.clone().into();
} }
Ok(None) Ok(None)
} }
@ -319,7 +319,7 @@ pub trait PoolController: HasPool
#[command(RenameSet = "rename/set")] #[command(RenameSet = "rename/set")]
fn rename_set (&mut self, value: Arc<str>) -> Perhaps<PoolCommand> { fn rename_set (&mut self, value: Arc<str>) -> Perhaps<PoolCommand> {
if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() { if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() {
self.pool().clips()[clip].write().unwrap().name = value.clone(); self.pool().clips()[clip].try_write().unwrap().name = value.clone();
} }
Ok(None) Ok(None)
} }
@ -332,7 +332,7 @@ impl_has_clips!(|self: Pool|self.clips);
impl_from!(Pool: |clip:&Arc<RwLock<MidiClip>>|{ impl_from!(Pool: |clip:&Arc<RwLock<MidiClip>>|{
let model = Self::default(); let model = Self::default();
model.clips.write().unwrap().push(clip.clone()); model.clips.try_write().unwrap().push(clip.clone());
model.clip.store(1, Relaxed); model.clip.store(1, Relaxed);
model model
}); });
@ -378,14 +378,14 @@ impl Pool {
} }
pub fn cloned_clip (&self) -> MidiClip { pub fn cloned_clip (&self) -> MidiClip {
let index = self.clip_index(); let index = self.clip_index();
let mut clip = self.clips()[index].read().unwrap().duplicate(); let mut clip = self.clips()[index].try_read().unwrap().duplicate();
clip.color = ItemTheme::random_near(clip.color, 0.25); clip.color = ItemTheme::random_near(clip.color, 0.25);
clip clip
} }
pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) { pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
let clip = Arc::new(RwLock::new(self.new_clip())); let clip = Arc::new(RwLock::new(self.new_clip()));
let index = { let index = {
let mut clips = self.clips.write().unwrap(); let mut clips = self.clips.try_write().unwrap();
clips.push(clip.clone()); clips.push(clip.clone());
clips.len().saturating_sub(1) clips.len().saturating_sub(1)
}; };
@ -393,9 +393,9 @@ impl Pool {
(index, clip) (index, clip)
} }
pub fn delete_clip (&mut self, clip: &MidiClip) -> bool { pub fn delete_clip (&mut self, clip: &MidiClip) -> bool {
let index = self.clips.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip); let index = self.clips.try_read().unwrap().iter().position(|x|*x.try_read().unwrap()==*clip);
if let Some(index) = index { if let Some(index) = index {
self.clips.write().unwrap().remove(index); self.clips.try_write().unwrap().remove(index);
return true return true
} }
false false
@ -441,14 +441,14 @@ impl Pool {
impl<'a> PoolView<'a> { impl<'a> PoolView<'a> {
//fn tui (&self) -> impl Draw<'_, Tui> { //fn tui (&self) -> impl Draw<'_, Tui> {
//let Self(pool) = self; //let Self(pool) = self;
////let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||g(32).into()); ////let color = self.1.clip().map(|c|c.try_read().unwrap().color).unwrap_or_else(||g(32).into());
////let on_bg = |x|x;//below(Repeat(" "), bg(color.darkest.term, x)); ////let on_bg = |x|x;//below(Repeat(" "), bg(color.darkest.term, x));
////let border = |x|x;//Outer(Style::default().fg(color.dark.term).bg(color.darkest.term)).enclose(x); ////let border = |x|x;//Outer(Style::default().fg(color.dark.term).bg(color.darkest.term)).enclose(x);
////let height = pool.clips.read().unwrap().len() as u16; ////let height = pool.clips.try_read().unwrap().len() as u16;
//iter( //iter(
//||pool.clips().clone().into_iter(), //||pool.clips().clone().into_iter(),
//move|clip: Arc<RwLock<MidiClip>>, i: usize|{ //move|clip: Arc<RwLock<MidiClip>>, i: usize|{
//let MidiClip { ref name, color, length, .. } = *clip.read().unwrap(); //let MidiClip { ref name, color, length, .. } = *clip.try_read().unwrap();
//let item_height = 1; //let item_height = 1;
//let _item_offset = i as u16 * item_height; //let _item_offset = i as u16 * item_height;
//let selected = i == pool.clip_index(); //let selected = i == pool.clip_index();

View file

@ -71,7 +71,7 @@ pub trait SamplerController: HasSampler
fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCommand> { fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
let sampler = self.sampler_mut(); let sampler = self.sampler_mut();
if let Some(ref sample) = sampler.samples.0[slot] { if let Some(ref sample) = sampler.samples.0[slot] {
sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128))); sampler.voices.try_write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
} }
Ok(None) Ok(None)
} }
@ -244,7 +244,7 @@ impl Sampler {
/// Record from inputs to sample /// Record from inputs to sample
fn record_into (&mut self, scope: &ProcessScope) { fn record_into (&mut self, scope: &ProcessScope) {
if let Some(ref sample) = self.recording.as_ref().expect("no recording sample").1 { if let Some(ref sample) = self.recording.as_ref().expect("no recording sample").1 {
let mut sample = sample.write().unwrap(); let mut sample = sample.try_write().unwrap();
if sample.channels.len() != self.audio_ins.len() { if sample.channels.len() != self.audio_ins.len() {
panic!("channel count mismatch"); panic!("channel count mismatch");
} }
@ -294,10 +294,10 @@ impl Sampler {
let Sampler { buffer, voices, output_gain, mixing_mode, .. } = self; let Sampler { buffer, voices, output_gain, mixing_mode, .. } = self;
let _channel_count = buffer.len(); let _channel_count = buffer.len();
match mixing_mode { match mixing_mode {
MixingMode::Summing => voices.write().unwrap().retain_mut(|voice|{ MixingMode::Summing => voices.try_write().unwrap().retain_mut(|voice|{
mix_summing(buffer.as_mut_slice(), *output_gain, frames, ||voice.next()) mix_summing(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
}), }),
MixingMode::Average => voices.write().unwrap().retain_mut(|voice|{ MixingMode::Average => voices.try_write().unwrap().retain_mut(|voice|{
mix_average(buffer.as_mut_slice(), *output_gain, frames, ||voice.next()) mix_average(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
}), }),
} }
@ -316,7 +316,7 @@ impl Sampler {
fn draw_list_item (sample: &Option<Arc<RwLock<Sample>>>) -> String { fn draw_list_item (sample: &Option<Arc<RwLock<Sample>>>) -> String {
if let Some(sample) = sample { if let Some(sample) = sample {
let sample = sample.read().unwrap(); let sample = sample.try_read().unwrap();
format!("{:8}", sample.name) format!("{:8}", sample.name)
//format!("{:8} {:3} {:6}-{:6}/{:6}", //format!("{:8} {:3} {:6}-{:6}/{:6}",
//sample.name, //sample.name,
@ -337,7 +337,7 @@ fn draw_viewer (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_
let XYWH(x, y, width, height) = xywh; let XYWH(x, y, width, height) = xywh;
let area = Rect { x, y, width, height }; let area = Rect { x, y, width, height };
if let Some(sample) = &sample { if let Some(sample) = &sample {
let sample = sample.read().unwrap(); let sample = sample.try_read().unwrap();
let start = sample.start as f64; let start = sample.start as f64;
let end = sample.end as f64; let end = sample.end as f64;
let length = end - start; let length = end - start;
@ -400,7 +400,7 @@ fn sampler_midi_in (
match message { match message {
MidiMessage::NoteOn { ref key, ref vel } => { MidiMessage::NoteOn { ref key, ref vel } => {
if let Some(sample) = samples.get(key.as_int() as usize) { if let Some(sample) = samples.get(key.as_int() as usize) {
voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); voices.try_write().unwrap().push(Sample::play(sample, time as usize, vel));
} }
}, },
MidiMessage::Controller { controller: _, value: _ } => { MidiMessage::Controller { controller: _, value: _ } => {
@ -444,7 +444,7 @@ impl Iterator for Voice {
self.after -= 1; self.after -= 1;
return Some([0.0, 0.0]) return Some([0.0, 0.0])
} }
let sample = self.sample.read().unwrap(); let sample = self.sample.try_read().unwrap();
if self.position < sample.end { if self.position < sample.end {
let position = self.position; let position = self.position;
self.position += 1; self.position += 1;
@ -514,7 +514,7 @@ impl Sample {
Voice { Voice {
sample: sample.clone(), sample: sample.clone(),
after, after,
position: sample.read().unwrap().start, position: sample.try_read().unwrap().start,
velocity: velocity.as_int() as f32 / 127.0, velocity: velocity.as_int() as f32 / 127.0,
} }
} }
@ -679,8 +679,8 @@ impl SampleAdd {
fn try_preview (&mut self) -> Usually<()> { fn try_preview (&mut self) -> Usually<()> {
if let Some(path) = self.cursor_file() { if let Some(path) = self.cursor_file() {
if let Ok(sample) = Sample::from_file(&path) { if let Ok(sample) = Sample::from_file(&path) {
*self.sample.write().unwrap() = sample; *self.sample.try_write().unwrap() = sample;
self.voices.write().unwrap().push( self.voices.try_write().unwrap().push(
Sample::play(&self.sample, 0, &u7::from(100u8)) Sample::play(&self.sample, 0, &u7::from(100u8))
); );
} }
@ -736,7 +736,7 @@ impl SampleAdd {
} }
if let Some(path) = self.cursor_file() { if let Some(path) = self.cursor_file() {
let (end, channels) = read_sample_data(&path.to_string_lossy())?; let (end, channels) = read_sample_data(&path.to_string_lossy())?;
let mut sample = self.sample.write().unwrap(); let mut sample = self.sample.try_write().unwrap();
sample.name = path.file_name().unwrap().to_string_lossy().into(); sample.name = path.file_name().unwrap().to_string_lossy().into();
sample.end = end; sample.end = end;
sample.channels = channels; sample.channels = channels;
@ -752,7 +752,7 @@ fn read_sample_data (_: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> { pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
when(sample.is_some(), draw(move|to: &mut Tui|{ when(sample.is_some(), draw(move|to: &mut Tui|{
let sample = sample.unwrap().read().unwrap(); let sample = sample.unwrap().try_read().unwrap();
let theme = sample.color; let theme = sample.color;
east!( east!(
field_h(theme, "Name", format!("{:<10}", sample.name.clone())), field_h(theme, "Name", format!("{:<10}", sample.name.clone())),
@ -767,7 +767,7 @@ pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui>
pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> { pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
let a = draw(move|to: &mut Tui|{ let a = draw(move|to: &mut Tui|{
let sample = sample.unwrap().read().unwrap(); let sample = sample.unwrap().try_read().unwrap();
let theme = sample.color; let theme = sample.color;
south!( south!(
field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(), field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(),
@ -791,7 +791,7 @@ pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tu
pub fn view_sample_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> { pub fn view_sample_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> {
bold(true, fg(g(224), sample bold(true, fg(g(224), sample
.map(|sample|{ .map(|sample|{
let sample = sample.read().unwrap(); let sample = sample.try_read().unwrap();
format!("Sample {}-{}", sample.start, sample.end) format!("Sample {}-{}", sample.start, sample.end)
}) })
.unwrap_or_else(||"No sample".to_string()))) .unwrap_or_else(||"No sample".to_string())))

View file

@ -100,7 +100,7 @@ pub trait HasPlayClip: HasClock {
fn pulses_since_start_looped (&self) -> Option<(f64, f64)> { fn pulses_since_start_looped (&self) -> Option<(f64, f64)> {
if let Some((started, Some(clip))) = self.play_clip().as_ref() { if let Some((started, Some(clip))) = self.play_clip().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
let length = clip.read().unwrap().length.max(1); // prevent div0 on empty clip let length = clip.try_read().unwrap().length.max(1); // prevent div0 on empty clip
let times = (elapsed as usize / length) as f64; let times = (elapsed as usize / length) as f64;
let elapsed = (elapsed as usize % length) as f64; let elapsed = (elapsed as usize % length) as f64;
return Some((times, elapsed)) return Some((times, elapsed))
@ -115,7 +115,7 @@ pub trait HasPlayClip: HasClock {
fn play_status (&self) -> impl Draw<Tui> { fn play_status (&self) -> impl Draw<Tui> {
let (name, color): (Arc<str>, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() { let (name, color): (Arc<str>, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() {
let MidiClip { ref name, color, .. } = *clip.read().unwrap(); let MidiClip { ref name, color, .. } = *clip.try_read().unwrap();
(name.clone(), color) (name.clone(), color)
} else { } else {
("".into(), ItemTheme::G[64].into()) ("".into(), ItemTheme::G[64].into())
@ -136,7 +136,7 @@ pub trait HasPlayClip: HasClock {
let mut color = ItemTheme::G[64]; let mut color = ItemTheme::G[64];
let clock = self.clock(); let clock = self.clock();
if let Some((t, Some(clip))) = self.next_clip() { if let Some((t, Some(clip))) = self.next_clip() {
let clip = clip.read().unwrap(); let clip = clip.try_read().unwrap();
name = clip.name.clone(); name = clip.name.clone();
color = clip.color.clone(); color = clip.color.clone();
time = { time = {
@ -150,7 +150,7 @@ pub trait HasPlayClip: HasClock {
} }
}.into() }.into()
} else if let Some((t, Some(clip))) = self.play_clip() { } else if let Some((t, Some(clip))) = self.play_clip() {
let clip = clip.read().unwrap(); let clip = clip.try_read().unwrap();
if clip.looped { if clip.looped {
name = clip.name.clone(); name = clip.name.clone();
color = clip.color.clone(); color = clip.color.clone();
@ -205,7 +205,7 @@ pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip {
let _recording = self.recording(); let _recording = self.recording();
let timebase = self.clock().timebase().clone(); let timebase = self.clock().timebase().clone();
let quant = self.clock().quant.get(); let quant = self.clock().quant.get();
let mut clip = clip.write().unwrap(); let mut clip = clip.try_write().unwrap();
let length = clip.length; let length = clip.length;
for input in self.midi_ins_mut().iter() { for input in self.midi_ins_mut().iter() {
for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) { for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) {
@ -232,8 +232,8 @@ pub type MidiData = Vec<Vec<MidiMessage>>;
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>; pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
pub trait HasClips { pub trait HasClips {
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; fn clips <'a> (&'a self) -> RwLockReadGuard<'a, ClipPool>;
fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>; fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, ClipPool>;
fn add_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) { fn add_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None))); let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None)));
self.clips_mut().push(clip.clone()); self.clips_mut().push(clip.clone());
@ -241,6 +241,29 @@ pub trait HasClips {
} }
} }
/// TODO: Preserve the generic passthru syntax;
/// remove this macro (only used twice) and potentially the trait.
#[macro_export] macro_rules! impl_has_clips {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? {
fn clips <'a> (&'a $self) -> ::tengri::parking_lot::RwLockReadGuard<'a, ClipPool> {
$cb.try_read().unwrap()
}
fn clips_mut <'a> (&'a $self) -> ::tengri::parking_lot::RwLockWriteGuard<'a, ClipPool> {
$cb.try_write().unwrap()
}
}
}
}
#[macro_export] macro_rules! has_clip {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
fn clip (&$self) -> Option<Arc<RwLock<MidiClip>>> { $cb }
}
}
}
pub trait HasMidiClip { pub trait HasMidiClip {
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>; fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
} }
@ -433,7 +456,7 @@ impl Sequencer {
self.midi_buf[sample].push(bytes.to_vec()); self.midi_buf[sample].push(bytes.to_vec());
} }
// FIXME: don't lock on every event! // FIXME: don't lock on every event!
update_keys(&mut notes_in.write().unwrap(), &message); update_keys(&mut notes_in.try_write().unwrap(), &message);
} }
} }
} }
@ -469,7 +492,7 @@ impl Sequencer {
// If no clip is playing, prepare for switchover immediately. // If no clip is playing, prepare for switchover immediately.
if let Some((started, clip)) = &self.play_clip { if let Some((started, clip)) = &self.play_clip {
// Length of clip, to repeat or stop on end. // Length of clip, to repeat or stop on end.
let length = clip.as_ref().map_or(0, |p|p.read().unwrap().length); let length = clip.as_ref().map_or(0, |p|p.try_read().unwrap().length);
// Index of first sample to populate. // Index of first sample to populate.
let offset = self.clock().get_sample_offset(scope, &started); let offset = self.clock().get_sample_offset(scope, &started);
// Write MIDI events from clip at sample offsets corresponding to pulses. // Write MIDI events from clip at sample offsets corresponding to pulses.
@ -484,7 +507,7 @@ impl Sequencer {
// If there's a currently playing clip, output notes from it to buffer: // If there's a currently playing clip, output notes from it to buffer:
if let Some(clip) = clip { if let Some(clip) = clip {
// Source clip from which the MIDI events will be taken. // Source clip from which the MIDI events will be taken.
let clip = clip.read().unwrap(); let clip = clip.try_read().unwrap();
// Clip with zero length is not processed // Clip with zero length is not processed
if clip.length > 0 { if clip.length > 0 {
// Current pulse index in source clip // Current pulse index in source clip
@ -513,7 +536,7 @@ impl Sequencer {
//let samples = scope.n_frames() as usize; //let samples = scope.n_frames() as usize;
if let Some((start_at, clip)) = &self.next_clip() { if let Some((start_at, clip)) = &self.next_clip() {
let start = start_at.sample.get() as usize; let start = start_at.sample.get() as usize;
let sample = self.clock().started.read().unwrap() let sample = self.clock().started.try_read().unwrap()
.as_ref().unwrap().sample.get() as usize; .as_ref().unwrap().sample.get() as usize;
// If it's time to switch to the next clip: // If it's time to switch to the next clip:
if start <= sample0.saturating_sub(sample) { if start <= sample0.saturating_sub(sample) {

View file

@ -35,16 +35,16 @@
(mode :mix (keys :mix)) (mode :mix (keys :mix))
(view (view
(bsp/n (bg (g 10) (bsp/e :transport :status)) (bsp/n (bg (g 10) (bsp/e :transport :status))
(bsp/w (bg (g 20) (exact/x 4 (align/ne :meters/output))) (bsp/w (bg (g 20) (exact/w 4 (align/ne :meters/output)))
(bsp/e (bg (g 30) (exact/x 4 (align/nw :meters/input))) (bsp/e (bg (g 30) (exact/w 4 (align/nw :meters/input)))
(full/xy (align/c (max/xy 80 80 (full/xy (align/c (max/wh 80 80
(bsp/s (bg (g 40) (exact/y 4 :tracks/outputs)) (bsp/s (bg (g 40) (exact/h 4 :tracks/outputs))
(bsp/s (bg (g 60) (exact/y 4 :tracks/devices)) (bsp/s (bg (g 60) (exact/h 4 :tracks/devices))
(bsp/s (bg (g 50) (exact/y 2 :tracks/names)) (bsp/s (bg (g 50) (exact/h 2 :tracks/names))
(bsp/s (either :mode/editor (bsp/s (either :mode/editor
(bg (g 80) (bsp/e :scenes/names :editor)) (bg (g 80) (bsp/e :scenes/names :editor))
(bg (g 90) :scenes)) (bg (g 90) :scenes))
(bg (g 70) (exact/y 4 :tracks/inputs)))))))))))))) (bg (g 70) (exact/h 4 :tracks/inputs))))))))))))))
(keys :clock (@space clock/toggle 0) (keys :clock (@space clock/toggle 0)
(@shift/space clock/toggle 0)) (@shift/space clock/toggle 0))
@ -82,14 +82,14 @@
(mode browse (keys :browse)) (mode browse (keys :browse))
(mode rename (keys :pool/rename)) (mode rename (keys :pool/rename))
(mode length (keys :pool/length)) (mode length (keys :pool/length))
(bsp/s (exact/y 1 :transport) (bsp/s (exact/h 1 :transport)
(bsp/n (exact/y 1 :status) (bsp/n (exact/h 1 :status)
(fill (bsp/a (fill/xy (align/e :pool)) :editor))))) (fill (bsp/a (fill/xy (align/e :pool)) :editor)))))
(mode :sampler (name Sampler) (info Sample player.) (mode :sampler (name Sampler) (info Sample player.)
(keys :sampler/directions :sampler/record :sampler/play) (keys :sampler/directions :sampler/record :sampler/play)
(bsp/s (exact/y 1 :transport) (bsp/s (exact/h 1 :transport)
(bsp/n (exact/y 1 :status) (bsp/n (exact/h 1 :status)
(fill :samples/grid)))) (fill :samples/grid))))
(mode :groovebox (name Groovebox) (info Sequencer with sampler.) (mode :groovebox (name Groovebox) (info Sequencer with sampler.)
@ -103,7 +103,7 @@
(view :groove/editor (bsp/n :groove/sample :groove/sequence)) (view :groove/editor (bsp/n :groove/sample :groove/sequence))
(view :groove/sample (exact/y :h-sample-detail (bsp/e (fill/y (exact/x 20 (align/nw :sample-status))) :sample-viewer))) (view :groove/sample (exact/h :h-sample-detail (bsp/e (fill/y (exact/w 20 (align/nw :sample-status))) :sample-viewer)))
(view :groove/sequence (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) (bsp/e :samples/keys :editor))) (view :groove/sequence (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) (bsp/e :samples/keys :editor)))

View file

@ -16,6 +16,15 @@ pub fn show_version () {
#[allow(unused)] #[allow(unused)]
#[hotpath::main] #[hotpath::main]
fn main () -> Usually<()> { fn main () -> Usually<()> {
#[cfg(feature = "prof")] let _flame_guard = {
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
//let tracy = tracing_tracy::TracyLayer::default();
let (flame, _guard) = tracing_flame::FlameLayer::with_file("./tracing.folded").unwrap();
//let registry = tracing_subscriber::registry().with(flame).init();
//tracing::subscriber::set_global_default(registry)?;
tracing::subscriber::set_global_default(tracing_subscriber::registry().with(flame))?;
_guard
};
tengri::Tui::setup_panic(); tengri::Tui::setup_panic();
#[cfg(feature = "cli")] { #[cfg(feature = "cli")] {
Config::watched(crate::cli::run_with_config)?; Config::watched(crate::cli::run_with_config)?;
@ -62,7 +71,7 @@ fn run_new_plain (config: Config) -> Usually<()> {
//Tui::run_main(&jack.run(move|jack|{ //Tui::run_main(&jack.run(move|jack|{
//// Between jack init and app's first cycle: //// Between jack init and app's first cycle:
////jack.sync_lead(*sync_lead, |mut state|{ ////jack.sync_lead(*sync_lead, |mut state|{
////let clock = app.write().unwrap().clock(); ////let clock = app.try_write().unwrap().clock();
////clock.playhead.update_from_sample(state.position.frame() as f64); ////clock.playhead.update_from_sample(state.position.frame() as f64);
////state.position.bbt = Some(clock.bbt()); ////state.position.bbt = Some(clock.bbt());
////state.position ////state.position
@ -493,12 +502,13 @@ mod bind {
use crate::*; use crate::*;
tui_keys!(self: App, input { tui_keys!(self: App, input {
#[cfg(feature = "prof")] profiling::scope!("App::tui_keys!");
let name = self.mode.as_ref(); let name = self.mode.as_ref();
let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone); let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone);
if let Some(mode) = mode { if let Some(mode) = mode {
let binds = self.config.binds.clone(); let binds = self.config.binds.clone();
for id in mode.keys.iter() { for id in mode.keys.iter() {
if let Some(event_map) = binds.read().unwrap().get(id.as_ref()) if let Some(event_map) = binds.try_read().unwrap().get(id.as_ref())
&& let Some(bindings) = event_map.query(input) { && let Some(bindings) = event_map.query(input) {
for binding in bindings { for binding in bindings {
for command in binding.commands.iter() { for command in binding.commands.iter() {
@ -702,7 +712,11 @@ mod device {
} }
} }
impl HasJack<'static> for App { fn jack (&self) -> &Jack<'static> { &self.jack } } impl HasJack<'static> for App {
fn jack (&self) -> &Jack<'static> {
&self.jack
}
}
impl_audio!(App: tek_jack_process, tek_jack_event); impl_audio!(App: tek_jack_process, tek_jack_event);
@ -894,10 +908,12 @@ mod draw {
/// Then, every top-level form of the DSL description is rendered. /// Then, every top-level form of the DSL description is rendered.
impl Draw<Tui> for App { impl Draw<Tui> for App {
fn draw (&self, to: &mut Tui) -> Drawn<u16> { fn draw (&self, to: &mut Tui) -> Drawn<u16> {
#[cfg(feature = "prof")] profiling::scope!("App::draw");
//self.perf.cycle(&mut |_|{ //self.perf.cycle(&mut |_|{
self.draw_error(to)?; self.draw_error(to)?;
self.draw_modes(to)?; self.draw_modes(to)?;
//self.draw_debug(to)?; self.draw_debug(to)?;
#[cfg(feature = "prof")] profiling::finish_frame!();
Ok(Some(to.area().into())) Ok(Some(to.area().into()))
//}) //})
} }
@ -905,7 +921,7 @@ mod draw {
impl App { impl App {
fn draw_error (&self, to: &mut Tui) -> Usually<()> { fn draw_error (&self, to: &mut Tui) -> Usually<()> {
if let Some(e) = self.error.read().unwrap().as_ref() { if let Some(e) = self.error.try_read().unwrap().as_ref() {
e.as_ref().align_c().draw(to)?; e.as_ref().align_c().draw(to)?;
} }
Ok(()) Ok(())
@ -914,27 +930,27 @@ mod draw {
fn draw_modes (&self, to: &mut Tui) -> Usually<()> { fn draw_modes (&self, to: &mut Tui) -> Usually<()> {
if let Some(mode) = self.mode.as_ref().and_then(|m|self.config.get_mode(m)) { if let Some(mode) = self.mode.as_ref().and_then(|m|self.config.get_mode(m)) {
let mut error = false; let mut error = false;
for (index, dsl) in mode.view.iter().enumerate() { for (index, view) in mode.view.iter().enumerate() {
match self.interpret(to, dsl) { match (view.render)(self, to) {
Ok(None) => {}, Ok(None) => {},
Ok(Some(XYWH(.., w, h))) => { Ok(Some(XYWH(.., w, h))) => {
self.size.0.store(w as usize, Relaxed); self.size.0.store(w as usize, Relaxed);
self.size.1.store(h as usize, Relaxed); self.size.1.store(h as usize, Relaxed);
}, },
Err(e) => { Err(e) => {
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
let message = format!( let message = format!(
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{src}", "Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}",
&mode.name &mode.name,
&view.source
); );
*self.error.write().unwrap() = Some(message.into()); *self.error.try_write().unwrap() = Some(message.into());
error = true; error = true;
break; break;
} }
} }
} }
if !error { if !error {
*self.error.write().unwrap() = None; *self.error.try_write().unwrap() = None;
} }
} }
Ok(()) Ok(())
@ -942,78 +958,17 @@ mod draw {
#[allow(unused)] fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> { #[allow(unused)] fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> {
east( east(
format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)), format!("{}x{} ",
format!("{}% {} ", self.perf.percentage().unwrap_or_default(), self.perf.clock.raw() / 1000000000), self.size.0.load(Relaxed),
self.size.1.load(Relaxed)),
format!("{}/{} {} ",
self.perf.used.load(Relaxed),
self.perf.window.load(Relaxed),
self.perf.clock.raw() / 1000000000),
).align_se().draw(to) ).align_se().draw(to)
} }
} }
impl Interpret<Tui, Option<XYWH<u16>>> for App {
fn interpret <L: Language> (&self, to: &mut Tui, dsl: L) -> Drawn<u16> {
if let Ok(Some(expr)) = dsl.expr() {
ok_flat(expr.head()?.map(|head|{
match head.split('/').next() {
Some("when") => kw_when(self, to, expr),
Some("either") => kw_either(self, to, expr),
Some("bsp") => kw_split(self, to, expr),
Some("split") => kw_split(self, to, expr),
Some("align") => kw_align(self, to, expr),
Some("full") => kw_full(self, to, expr),
Some("exact") => kw_exact(self, to, expr),
Some("min") => kw_min(self, to, expr),
Some("max") => kw_max(self, to, expr),
Some("push") => kw_push(self, to, expr),
Some("pull") => kw_pull(self, to, expr),
Some("text") => kw_tui_text(self, to, expr),
Some("fg") => kw_tui_fg(self, to, expr),
Some("bg") => kw_tui_bg(self, to, expr),
_ => Err(format!("interpret_expr: unexpected: {expr:?}").into())
}
}))
} else if let Ok(Some(word)) = dsl.word() {
let mut frags = word.src()?.unwrap().split("/");
match frags.next() {
//Some(":logo") => view_logo().draw(to),
Some(":meters") => match frags.next() {
Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to),
Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to),
_ => panic!()
},
Some(":tracks") => match frags.next() {
None => "TODO tracks".draw(to),
Some("names") => self.project.view_track_names(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))),
Some("inputs") => self.project.view_track_inputs(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to),
Some("devices") => self.project.view_track_devices(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to),
Some("outputs") => self.project.view_track_outputs(self.color.clone(), 0).draw(to),
_ => panic!()
},
Some(":scenes") => match frags.next() {
None => self.view_scenes_clips().draw(to),
Some("names") => self.view_scenes_names().draw(to),
_ => panic!()
},
Some(":dialog") => draw_dialog(to, frags, self),
Some(":templates") => view_templates(frags, self).draw(to),
Some(":sessions") => view_sessions().draw(to),
Some(":browse/title") => view_browse_title(self).draw(to),
Some(":device") => view_device(self).draw(to),
Some(":status") => "TODO: Status Bar".draw(to),
Some(":editor") => "TODO Editor".draw(to),
Some(":transport") => view_transport(true, "", "", "").draw(to),
Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).draw(to),
Some(_) => if let Some(lang) = self.config.get_view(word) {
self.interpret(to, lang)
} else {
fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to)
},
_ => unreachable!()
}
} else {
Err(format!("not word/expr:\n{dsl:?}").into())
}
}
}
impl_has!(Sizer: |self: App|self.size); impl_has!(Sizer: |self: App|self.size);
pub trait HasWidth { pub trait HasWidth {
@ -1024,9 +979,7 @@ mod draw {
fn width_dec (&mut self); fn width_dec (&mut self);
} }
pub fn view_templates <'a> (_frags: std::str::Split<&str>, state: &'a App) pub fn view_templates <'a> (state: &'a App) -> impl Draw<Tui> + use<'a> {
-> impl Draw<Tui> + use<'a>
{
let height = (state.config.modes.len() * 2) as u16; let height = (state.config.modes.len() * 2) as u16;
draw(move |to: &mut Tui|{ draw(move |to: &mut Tui|{
let mut index = 0; let mut index = 0;

2
tengri

@ -1 +1 @@
Subproject commit 4172fa257776f5c6c7b406429b2244d630702458 Subproject commit 8e7286e409ec6d4ac4382856ff93767a4758e11e