From f1756f9a0e081c098cfce2a88838c847ec15b8b3 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Sat, 29 Aug 2026 18:46:46 +0300 Subject: [PATCH] wip: compiled layouts --- .gitignore | 2 +- Cargo.lock | 5 + Cargo.toml | 5 +- Justfile | 8 +- src/.scratch.rs | 21 ++ src/config.rs | 390 ++++++++++++++++++++++++++++----- src/deps.rs | 25 ++- src/device/arrange/clip.rs | 29 +-- src/device/arrange/scene.rs | 4 +- src/device/arrange/select.rs | 2 +- src/device/clock.rs | 10 +- src/device/clock/clock_view.rs | 4 +- src/device/dialog.rs | 2 +- src/device/editor.rs | 14 +- src/device/editor/piano.rs | 14 +- src/device/pool.rs | 42 ++-- src/device/sampler.rs | 30 +-- src/device/sequence.rs | 45 +++- src/tek.edn | 24 +- src/tek.rs | 119 +++------- tengri | 2 +- 21 files changed, 534 insertions(+), 263 deletions(-) diff --git a/.gitignore b/.gitignore index 61d988af..dcfba704 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,4 @@ build/* .misc .direnv callgrind.* -tracing.* +tracing*.* diff --git a/Cargo.lock b/Cargo.lock index 52f07dbc..387d9832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3892,9 +3892,14 @@ dependencies = [ "konst", "midly", "palette", + "parking_lot 0.12.5", + "profiling", "quanta", "rand 0.8.7", "ratatui", + "tracing", + "tracing-flame", + "tracing-subscriber", "unicode-width 0.2.0", ] diff --git a/Cargo.toml b/Cargo.toml index bd1cb9ae..f0fa68e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,8 +51,9 @@ proptest = { version = "^1" } proptest-derive = { version = "^0.5.1" } [features] -default = ["cli", "arranger", "sampler"] +default = ["cli", "arranger", "sampler", "prof"] +prof = ["tengri/prof"] hotpath = ["hotpath/hotpath"] hotpath-cpu = ["hotpath/hotpath-cpu"] hotpath-alloc = ["hotpath/hotpath-alloc"] @@ -82,7 +83,7 @@ vst3 = [] [profile.release] lto = true -debug = "line-tables-only" +debug = true [profile.coverage] inherits = "test" diff --git a/Justfile b/Justfile index 1c6633f7..c06f8166 100644 --- a/Justfile +++ b/Justfile @@ -1,6 +1,6 @@ #export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold" 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] list: @@ -49,11 +49,11 @@ run-init: rm -rf ~/.config/tek && {{debug}} 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: - {{release}} +release +ARGS="new": + {{release}} {{ARGS}} build-release: time cargo build -j4 --release diff --git a/src/.scratch.rs b/src/.scratch.rs index 6c98f399..39e3a044 100644 --- a/src/.scratch.rs +++ b/src/.scratch.rs @@ -1186,3 +1186,24 @@ //take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref() //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); + + //pub fn tui ( + //app: Arc>, + //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) + //})?)? + //} diff --git a/src/config.rs b/src/config.rs index bd8d7dbb..55e34ed6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -57,10 +57,10 @@ pub fn config_watch ( move|result|{ match result { 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:?}"); } else { - //println!("config updated"); + println!("config updated"); }, Err(errors) => { panic!("{errors:?}"); @@ -71,7 +71,7 @@ pub fn config_watch ( if let Some(path) = config.as_ref().get_file() { //println!("watching: {path:?}"); watcher.watch(&path, RecursiveMode::NonRecursive)?; - *config.as_ref().watch.write().unwrap() = Some(watcher); + *config.as_ref().watch.try_write().unwrap() = Some(watcher); Ok(()) } else { 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 body = expr.tail()?.ok_or("mode: missing body")?; let mode = Mode::default(); - let mode = body.each(mode, |c,s|mode_add(c,s))?; - modes.0.write().unwrap().insert(name.into(), Arc::new(mode)); + let mode = body.each(mode, |c, s|mode_add(c, s))?; + modes.0.try_write().unwrap().insert(name.into(), Arc::new(mode)); Ok(()) } @@ -113,43 +113,33 @@ pub fn mode_add (mut mode: Mode, dsl: impl Language) -> Usually { let submode = Mode::default(); let submode = body.each(submode, |c,s|mode_add(c,s))?; 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 }, "keys" => { - dsl.each(mode, |mut mode: Mode, expr: &str|{ + tail.each(mode, |mut mode: Mode, expr: &str|{ mode.keys.push(expr.trim().into()); Ok(mode) })? }, "name" => { mode.name.push(tail.into()); mode }, "info" => { mode.info.push(tail.into()); mode }, - "view" => { mode.view.push(tail.into()); mode }, - _ => { mode.view.push(expr.into()); mode }, + "view" => { mode.view.push(View::new(tail)?.into()); mode }, + _ => { mode.view.push(View::new(tail)?.into()); mode }, } } else if let Ok(Some(word)) = dsl.word() { - mode.view.push(word.into()); + mode.view.push(View::new(word)?.into()); mode } else { 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, ()> { + println!("\n\rload_bind: {expr:?}"); let name = expr.head()?.ok_or("bind: missing name")?; let body = expr.tail()?.unwrap_or(""); - binds.write().unwrap().insert(name.into(), { + binds.try_write().unwrap().insert(name.into(), { let mut map = Bind::new(); body.each((), |_, item: &str|if item.expr().head() == Ok(Some("see")) { // TODO @@ -169,10 +159,10 @@ pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> // TODO return Ok(()) } 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 { - return Err(format!("Config::load_bind: unexpected: {item:?}").into()) + return Err(format!("load_bind: unexpected: {item:?}").into()) })?; map }); @@ -238,11 +228,307 @@ pub struct Mode { pub path: PathBuf, pub name: Vec>, pub info: Vec>, - pub view: Vec>, + pub view: Vec>>, pub keys: Vec>, pub modes: Modes, } +/// Collection of custom view definitions. +pub type Views = Arc, Arc>>>>; + +/// Custom view definition is a boxed closure emitting a [Draw]able from state `S`. +pub struct View { + pub source: Arc, + pub render: ArcDrawn + Send + Sync>> +} + +impl_debug!( View |self, w| { write!(w, "View({})", self.source) }); + +impl_display!( View |self, w| { write!(w, "View({})", self.source) }); + +impl View { + + pub fn new (source: impl AsRef) -> Usually { + Ok(Self { + source: source.as_ref().into(), + render: Self::compile(source)? + }) + } + + fn boxed Drawn + Send + Sync + 'static> (f: F) + -> BoxDrawn + Send + Sync + 'static> + { + Box::new(f) + } + + fn compile (source: impl AsRef) -> + UsuallyDrawn + 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) -> + UsuallyDrawn + 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::::from("when: no arg0: condition"))?); + let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::::from("when: no condition value")); + let thunk = expr.nth(2)?.ok_or_else(||Box::::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::::from("either: no arg0: condition"))?); + let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::::from("either: no condition value")); + let a = expr.nth(2)?.ok_or_else(||Box::::from("either: no arg1: content"))?; + let a = Self::compile(a)?; + let b = expr.nth(3)?.ok_or_else(||Box::::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::::from("either: no arg0: content"))?)?; + let b = Self::compile(expr.nth(2)?.ok_or_else(||Box::::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::::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::::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::::from("{}: no arg1: value"))?); + let value = move|state: &App|state.namespace(&value); + let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::::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::::from("{}: no arg1: value"))?); + let value = move|state: &App|state.namespace(&value); + let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::::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::::from("{}: no arg1: value"))?); + let value1 = move|state: &App|state.namespace(&value1); + let value2 = Arc::from(expr.nth(2)?.ok_or_else(||Box::::from("{}: no arg2: value"))?); + let value2 = move|state: &App|state.namespace(&value2); + let thunk = Self::compile(expr.nth(3)?.ok_or_else(||Box::::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::::from("{}: no arg1: color"))?; + let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::::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) + -> UsuallyDrawn + 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 { /// Default configuration directory. @@ -308,44 +594,32 @@ impl Config { /// Make this configuration empty. fn clear (&self) { - *self.modes.0.write().unwrap() = Default::default(); - *self.views.write().unwrap() = Default::default(); - *self.binds.write().unwrap() = Default::default(); + *self.modes.0.try_write().unwrap() = Default::default(); + *self.views.try_write().unwrap() = Default::default(); + *self.binds.try_write().unwrap() = Default::default(); } - pub fn get_view (&self, name: impl AsRef) -> Option> { - self.views.read().unwrap().get(name.as_ref()).cloned() + pub fn get_view (&self, name: impl AsRef) -> Option>> { + self.views.try_read().unwrap().get(name.as_ref()).cloned() } } -pub use self::view::*; -mod view { - use crate::*; - /// Collection of custom view definitions. - pub type Views = Arc, Arc>>>; -} - -pub use self::mode::*; -mod mode { - use crate::*; - - impl Modes { - /// Get a mode by name. - pub fn get (&self, name: impl AsRef) -> Option> { - self.0.read().unwrap().get(name.as_ref()).cloned() - } - /// Run something for each mode. - pub fn for_each (&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() +impl Modes { + /// Get a mode by name. + pub fn get (&self, name: impl AsRef) -> Option> { + self.0.try_read().unwrap().get(name.as_ref()).cloned() + } + /// Run something for each mode. + pub fn for_each (&self, mut ator: impl FnMut(&str, &Mode)->T) { + for (k, v) in self.0.try_read().unwrap().iter() { + let _ = ator(k.as_ref(), v.as_ref()); } } + /// Count modes. + pub fn len (&self) -> usize { + self.0.try_read().unwrap().len() + } } pub use self::bind::*; @@ -449,10 +723,10 @@ mod bind { pub fn print_config (config: &Config) { use ::ansi_term::Color::*; println!("{:?}", config.dirs); - for (k, v) in config.views.read().unwrap().iter() { - println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}"))); + for (k, v) in config.views.try_read().unwrap().iter() { + 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}"))); for (k, v) in v.0.iter() { print!("{} ", &Yellow.paint(match &k.0 { diff --git a/src/deps.rs b/src/deps.rs index 540a4f68..54bb3222 100644 --- a/src/deps.rs +++ b/src/deps.rs @@ -1,8 +1,7 @@ pub extern crate atomic_float; pub extern crate xdg; pub extern crate tengri; -#[cfg(feature = "cli")] -pub(crate) use ::clap::{self, Parser, Subcommand}; + #[allow(unused)] pub(crate) use ::{ std::{ @@ -14,10 +13,13 @@ pub(crate) use ::{ fs::File, ops::{Add, Sub, Mul, Div, Rem}, path::{Path, PathBuf}, - sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, AtomicU64, Ordering::Relaxed}}, + sync::{Arc, atomic::{AtomicBool, AtomicUsize, AtomicU64, Ordering::Relaxed}}, time::Duration, thread::{spawn, JoinHandle}, }, + atomic_float::{ + AtomicF64 + }, xdg::{ BaseDirectories, }, @@ -35,5 +37,22 @@ pub(crate) use ::{ prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}}, 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}; diff --git a/src/device/arrange/clip.rs b/src/device/arrange/clip.rs index aaed1248..0b42334b 100644 --- a/src/device/arrange/clip.rs +++ b/src/device/arrange/clip.rs @@ -1,33 +1,10 @@ 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>> { $cb } - } - } -} - impl Arrangement { /// Toggle looping for the active clip pub fn toggle_loop (&mut self) { 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 ) -> Option { 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(); clip.color = color.clone(); panic!("{color:?} {old:?}"); @@ -116,7 +93,7 @@ pub trait ClipsView: TracksView + ScenesView { fn view_scene_name_theme (scene: &Scene, track_index: usize) -> (Arc, ItemTheme) { 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) } else { (" ⏹ -- ".into(), ItemTheme::G[32]) diff --git a/src/device/arrange/scene.rs b/src/device/arrange/scene.rs index 6de330c9..87fdac3f 100644 --- a/src/device/arrange/scene.rs +++ b/src/device/arrange/scene.rs @@ -31,7 +31,7 @@ impl Scene { /// Get pulse length of the longest clip in the scene pub fn pulses (&self) -> usize { 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) .map(|track|{ if let Some((_, Some(clip))) = track.sequencer().play_clip() { - *clip.read().unwrap() == *c.read().unwrap() + *clip.try_read().unwrap() == *c.try_read().unwrap() } else { false } diff --git a/src/device/arrange/select.rs b/src/device/arrange/select.rs index af7a45a0..b3d23a5f 100644 --- a/src/device/arrange/select.rs +++ b/src/device/arrange/select.rs @@ -91,7 +91,7 @@ impl Selection { 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)) { (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") }, _ => format!("T{track} S{scene}: Empty"), diff --git a/src/device/clock.rs b/src/device/clock.rs index b058e24a..009fb8b2 100644 --- a/src/device/clock.rs +++ b/src/device/clock.rs @@ -1,6 +1,4 @@ use crate::*; -use ::std::sync::{Arc, RwLock, atomic::AtomicUsize}; -use ::atomic_float::AtomicF64; mod memo; pub use self::memo::*; mod moment; pub use self::moment::*; @@ -328,11 +326,11 @@ impl Clock { } /// Is currently paused? pub fn is_stopped (&self) -> bool { - self.started.read().unwrap().is_none() + self.started.try_read().unwrap().is_none() } /// Is currently playing? pub fn is_rolling (&self) -> bool { - self.started.read().unwrap().is_some() + self.started.try_read().unwrap().is_some() } /// Update chunk size pub fn set_chunk (&self, n_frames: usize) { @@ -347,7 +345,7 @@ impl Clock { self.global.sample.set(current_frames 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, // update starting point: @@ -401,7 +399,7 @@ impl Clock { pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{ (scope.last_frame_time() as usize).saturating_sub( 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 ) } diff --git a/src/device/clock/clock_view.rs b/src/device/clock/clock_view.rs index 009982b1..0cffcde3 100644 --- a/src/device/clock/clock_view.rs +++ b/src/device/clock/clock_view.rs @@ -38,7 +38,7 @@ impl ClockView { let chunk = clock.chunk.load(Relaxed) as f64; let lat = chunk / rate * 1000.; 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( 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 time = now/1000000.; let bpm = clock.timebase.bpm.get(); diff --git a/src/device/dialog.rs b/src/device/dialog.rs index 03941dcb..a5430b7c 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -1,6 +1,6 @@ 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> (to: &mut Tui, mut frags: I, state: &App) -> Drawn { match frags.next() { diff --git a/src/device/editor.rs b/src/device/editor.rs index b06107d4..edc897fb 100644 --- a/src/device/editor.rs +++ b/src/device/editor.rs @@ -30,7 +30,7 @@ impl App { let (_index, clip) = self.pool.add_new_clip(); // autocolor: new clip colors from scene and track color 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 { editor.set_clip(Some(&clip)); } @@ -46,11 +46,11 @@ impl App { { // Remove clip from arrangement when exiting empty clip editor 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); } 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) { let mut redraw = false; 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_pos = self.get_note_pos(); let note_len = self.get_note_len(); @@ -236,7 +236,7 @@ impl MidiEditor { 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_pos (&self) -> usize { self.get_note_pos() } 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)) } pub fn clip_status (&self) -> impl Draw + '_ { - 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) } else { (ItemTheme::G[64], String::new().into(), 0, false) }; south!( @@ -282,7 +282,7 @@ impl MidiEditor { ).exact_w(20) } pub fn edit_status (&self) -> impl Draw + '_ { - 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) } else { (ItemTheme::G[64], 0) }; let time_pos = self.get_time_pos(); diff --git a/src/device/editor/piano.rs b/src/device/editor/piano.rs index 7a6ee3aa..36e2b8f2 100644 --- a/src/device/editor/piano.rs +++ b/src/device/editor/piano.rs @@ -53,7 +53,7 @@ impl PianoHorizontal { buffer: RwLock::new(Default::default()).into(), point: MidiCursor::default(), 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 @@ -140,7 +140,7 @@ impl PianoHorizontal { draw(move|to: &mut Tui|{ let xywh = to.area().into(); let XYWH(x0, y0, w, _h) = xywh; - let source = buffer.read().unwrap(); + let source = buffer.try_read().unwrap(); //if h as usize != note_axis { //panic!("area height mismatch: {h} <> {note_axis}"); //} @@ -234,7 +234,7 @@ impl PianoHorizontal { let xywh = to.area().into(); let XYWH(x, y, w, _h) = xywh; 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)) { let t = area_x as usize * self.time_zoom().load(Relaxed); if t < length { @@ -276,8 +276,8 @@ impl MidiViewer for PianoHorizontal { (clip.length / self.range.time_zoom().load(Relaxed), 128) } fn redraw (&self) { - *self.buffer.write().unwrap() = if let Some(clip) = self.clip.as_ref() { - let clip = clip.read().unwrap(); + *self.buffer.try_write().unwrap() = if let Some(clip) = self.clip.as_ref() { + let clip = clip.try_read().unwrap(); let buf_size = self.buffer_size(&clip); let mut buffer = BigBuffer::from(buf_size); let time_zoom = self.get_time_zoom(); @@ -291,14 +291,14 @@ impl MidiViewer for PianoHorizontal { } fn set_clip (&mut self, clip: Option<&Arc>>) { *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(); } } impl std::fmt::Debug for PianoHorizontal { 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") .field("time_zoom", &self.range.time_zoom) .field("buffer", &format!("{}x{}", buffer.width, buffer.height)) diff --git a/src/device/pool.rs b/src/device/pool.rs index a3e7731b..fa278a3f 100644 --- a/src/device/pool.rs +++ b/src/device/pool.rs @@ -151,7 +151,7 @@ pub trait PoolController: HasPool /// Delete a clip from the pool #[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps { - 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 })) } @@ -181,8 +181,8 @@ pub trait PoolController: HasPool #[command(SetName = "set-name")] fn clip_set_name (&mut self, index: usize, name: Arc) -> Perhaps { let clip = &mut self.pool_mut().clips_mut()[index]; - let old_name = clip.read().unwrap().name.clone(); - clip.write().unwrap().name = name.clone(); + let old_name = clip.try_read().unwrap().name.clone(); + clip.try_write().unwrap().name = name.clone(); Ok(Some(PoolCommand::SetName { index, name: old_name })) } @@ -190,8 +190,8 @@ pub trait PoolController: HasPool #[command(SetLength = "set-length")] fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps { let clip = &mut self.pool_mut().clips_mut()[index]; - let old_len = clip.read().unwrap().length; - clip.write().unwrap().length = length; + let old_len = clip.try_read().unwrap().length; + clip.try_write().unwrap().length = length; Ok(Some(PoolCommand::SetLength { index, length: old_len })) } @@ -199,7 +199,7 @@ pub trait PoolController: HasPool #[command(SetColor = "set-color")] fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps { 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 })) } @@ -207,7 +207,7 @@ pub trait PoolController: HasPool #[command(CropBegin = "crop/begin")] fn crop_begin (&mut self) -> Perhaps { 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)); Ok(None) } @@ -228,9 +228,9 @@ pub trait PoolController: HasPool { let old_length; { - let clip = self.pool().clips()[clip].clone();//.write().unwrap(); - old_length = Some(clip.read().unwrap().length); - clip.write().unwrap().length = *length; + let clip = self.pool().clips()[clip].clone();//.try_write().unwrap(); + old_length = Some(clip.try_read().unwrap().length); + clip.try_write().unwrap().length = *length; } *self.pool_mut().mode_mut() = None; return Ok(old_length.map(|l|PoolCommand::CropSet { _length: l })) @@ -290,7 +290,7 @@ pub trait PoolController: HasPool #[command(RenameBegin = "rename/begin")] fn rename_begin (&mut self) -> Perhaps { 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)); Ok(None) } @@ -299,7 +299,7 @@ pub trait PoolController: HasPool #[command(RenameCancel = "rename/cancel")] fn rename_cancel (&mut self) -> Perhaps { 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) } @@ -319,7 +319,7 @@ pub trait PoolController: HasPool #[command(RenameSet = "rename/set")] fn rename_set (&mut self, value: Arc) -> Perhaps { 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) } @@ -332,7 +332,7 @@ impl_has_clips!(|self: Pool|self.clips); impl_from!(Pool: |clip:&Arc>|{ 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 }); @@ -378,14 +378,14 @@ impl Pool { } pub fn cloned_clip (&self) -> MidiClip { 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 } pub fn add_new_clip (&self) -> (usize, Arc>) { let clip = Arc::new(RwLock::new(self.new_clip())); let index = { - let mut clips = self.clips.write().unwrap(); + let mut clips = self.clips.try_write().unwrap(); clips.push(clip.clone()); clips.len().saturating_sub(1) }; @@ -393,9 +393,9 @@ impl Pool { (index, clip) } 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 { - self.clips.write().unwrap().remove(index); + self.clips.try_write().unwrap().remove(index); return true } false @@ -441,14 +441,14 @@ impl Pool { impl<'a> PoolView<'a> { //fn tui (&self) -> impl Draw<'_, Tui> { //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 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( //||pool.clips().clone().into_iter(), //move|clip: Arc>, 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_offset = i as u16 * item_height; //let selected = i == pool.clip_index(); diff --git a/src/device/sampler.rs b/src/device/sampler.rs index f9203341..5f568f5c 100644 --- a/src/device/sampler.rs +++ b/src/device/sampler.rs @@ -71,7 +71,7 @@ pub trait SamplerController: HasSampler fn sample_play (&mut self, slot: usize) -> Perhaps { let sampler = self.sampler_mut(); 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) } @@ -244,7 +244,7 @@ impl Sampler { /// Record from inputs to sample fn record_into (&mut self, scope: &ProcessScope) { 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() { panic!("channel count mismatch"); } @@ -294,10 +294,10 @@ impl Sampler { let Sampler { buffer, voices, output_gain, mixing_mode, .. } = self; let _channel_count = buffer.len(); 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()) }), - 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()) }), } @@ -316,7 +316,7 @@ impl Sampler { fn draw_list_item (sample: &Option>>) -> String { if let Some(sample) = sample { - let sample = sample.read().unwrap(); + let sample = sample.try_read().unwrap(); format!("{:8}", sample.name) //format!("{:8} {:3} {:6}-{:6}/{:6}", //sample.name, @@ -337,7 +337,7 @@ fn draw_viewer (sample: Option<&Arc>>) -> impl Draw + use<'_ let XYWH(x, y, width, height) = xywh; let area = Rect { x, y, width, height }; if let Some(sample) = &sample { - let sample = sample.read().unwrap(); + let sample = sample.try_read().unwrap(); let start = sample.start as f64; let end = sample.end as f64; let length = end - start; @@ -400,7 +400,7 @@ fn sampler_midi_in ( match message { MidiMessage::NoteOn { ref key, ref vel } => { 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: _ } => { @@ -444,7 +444,7 @@ impl Iterator for Voice { self.after -= 1; return Some([0.0, 0.0]) } - let sample = self.sample.read().unwrap(); + let sample = self.sample.try_read().unwrap(); if self.position < sample.end { let position = self.position; self.position += 1; @@ -514,7 +514,7 @@ impl Sample { Voice { sample: sample.clone(), after, - position: sample.read().unwrap().start, + position: sample.try_read().unwrap().start, velocity: velocity.as_int() as f32 / 127.0, } } @@ -679,8 +679,8 @@ impl SampleAdd { fn try_preview (&mut self) -> Usually<()> { if let Some(path) = self.cursor_file() { if let Ok(sample) = Sample::from_file(&path) { - *self.sample.write().unwrap() = sample; - self.voices.write().unwrap().push( + *self.sample.try_write().unwrap() = sample; + self.voices.try_write().unwrap().push( Sample::play(&self.sample, 0, &u7::from(100u8)) ); } @@ -736,7 +736,7 @@ impl SampleAdd { } if let Some(path) = self.cursor_file() { 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.end = end; sample.channels = channels; @@ -752,7 +752,7 @@ fn read_sample_data (_: &str) -> Usually<(usize, Vec>)> { pub fn view_sample_info (sample: Option<&Arc>>) -> impl Draw + use<'_> { 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; east!( field_h(theme, "Name", format!("{:<10}", sample.name.clone())), @@ -767,7 +767,7 @@ pub fn view_sample_info (sample: Option<&Arc>>) -> impl Draw pub fn view_sample_info_v (sample: Option<&Arc>>) -> impl Draw + use<'_> { let a = draw(move|to: &mut Tui|{ - let sample = sample.unwrap().read().unwrap(); + let sample = sample.unwrap().try_read().unwrap(); let theme = sample.color; south!( 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>>) -> impl Draw>>) -> impl Draw { bold(true, fg(g(224), sample .map(|sample|{ - let sample = sample.read().unwrap(); + let sample = sample.try_read().unwrap(); format!("Sample {}-{}", sample.start, sample.end) }) .unwrap_or_else(||"No sample".to_string()))) diff --git a/src/device/sequence.rs b/src/device/sequence.rs index 1ccc1d9d..125c64a4 100644 --- a/src/device/sequence.rs +++ b/src/device/sequence.rs @@ -100,7 +100,7 @@ pub trait HasPlayClip: HasClock { fn pulses_since_start_looped (&self) -> Option<(f64, f64)> { if let Some((started, Some(clip))) = self.play_clip().as_ref() { 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 elapsed = (elapsed as usize % length) as f64; return Some((times, elapsed)) @@ -115,7 +115,7 @@ pub trait HasPlayClip: HasClock { fn play_status (&self) -> impl Draw { let (name, color): (Arc, 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) } else { ("".into(), ItemTheme::G[64].into()) @@ -136,7 +136,7 @@ pub trait HasPlayClip: HasClock { let mut color = ItemTheme::G[64]; let clock = self.clock(); if let Some((t, Some(clip))) = self.next_clip() { - let clip = clip.read().unwrap(); + let clip = clip.try_read().unwrap(); name = clip.name.clone(); color = clip.color.clone(); time = { @@ -150,7 +150,7 @@ pub trait HasPlayClip: HasClock { } }.into() } else if let Some((t, Some(clip))) = self.play_clip() { - let clip = clip.read().unwrap(); + let clip = clip.try_read().unwrap(); if clip.looped { name = clip.name.clone(); color = clip.color.clone(); @@ -205,7 +205,7 @@ pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip { let _recording = self.recording(); let timebase = self.clock().timebase().clone(); let quant = self.clock().quant.get(); - let mut clip = clip.write().unwrap(); + let mut clip = clip.try_write().unwrap(); let length = clip.length; for input in self.midi_ins_mut().iter() { for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) { @@ -232,8 +232,8 @@ pub type MidiData = Vec>; pub type ClipPool = Vec>>; pub trait HasClips { - fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; - fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>; + fn clips <'a> (&'a self) -> RwLockReadGuard<'a, ClipPool>; + fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, ClipPool>; fn add_clip (&self) -> (usize, Arc>) { let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None))); 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>> { $cb } + } + } +} + pub trait HasMidiClip { fn clip (&self) -> Option>>; } @@ -433,7 +456,7 @@ impl Sequencer { self.midi_buf[sample].push(bytes.to_vec()); } // 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 let Some((started, clip)) = &self.play_clip { // 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. let offset = self.clock().get_sample_offset(scope, &started); // 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 let Some(clip) = clip { // 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 if clip.length > 0 { // Current pulse index in source clip @@ -513,7 +536,7 @@ impl Sequencer { //let samples = scope.n_frames() as usize; if let Some((start_at, clip)) = &self.next_clip() { 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; // If it's time to switch to the next clip: if start <= sample0.saturating_sub(sample) { diff --git a/src/tek.edn b/src/tek.edn index 2698ee44..a2fd3dc2 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -35,16 +35,16 @@ (mode :mix (keys :mix)) (view (bsp/n (bg (g 10) (bsp/e :transport :status)) - (bsp/w (bg (g 20) (exact/x 4 (align/ne :meters/output))) - (bsp/e (bg (g 30) (exact/x 4 (align/nw :meters/input))) - (full/xy (align/c (max/xy 80 80 - (bsp/s (bg (g 40) (exact/y 4 :tracks/outputs)) - (bsp/s (bg (g 60) (exact/y 4 :tracks/devices)) - (bsp/s (bg (g 50) (exact/y 2 :tracks/names)) + (bsp/w (bg (g 20) (exact/w 4 (align/ne :meters/output))) + (bsp/e (bg (g 30) (exact/w 4 (align/nw :meters/input))) + (full/xy (align/c (max/wh 80 80 + (bsp/s (bg (g 40) (exact/h 4 :tracks/outputs)) + (bsp/s (bg (g 60) (exact/h 4 :tracks/devices)) + (bsp/s (bg (g 50) (exact/h 2 :tracks/names)) (bsp/s (either :mode/editor (bg (g 80) (bsp/e :scenes/names :editor)) (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) (@shift/space clock/toggle 0)) @@ -82,14 +82,14 @@ (mode browse (keys :browse)) (mode rename (keys :pool/rename)) (mode length (keys :pool/length)) - (bsp/s (exact/y 1 :transport) - (bsp/n (exact/y 1 :status) + (bsp/s (exact/h 1 :transport) + (bsp/n (exact/h 1 :status) (fill (bsp/a (fill/xy (align/e :pool)) :editor))))) (mode :sampler (name Sampler) (info Sample player.) (keys :sampler/directions :sampler/record :sampler/play) - (bsp/s (exact/y 1 :transport) - (bsp/n (exact/y 1 :status) + (bsp/s (exact/h 1 :transport) + (bsp/n (exact/h 1 :status) (fill :samples/grid)))) (mode :groovebox (name Groovebox) (info Sequencer with sampler.) @@ -103,7 +103,7 @@ (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))) diff --git a/src/tek.rs b/src/tek.rs index 185eed13..5c64590c 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -16,6 +16,15 @@ pub fn show_version () { #[allow(unused)] #[hotpath::main] 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(); #[cfg(feature = "cli")] { 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|{ //// Between jack init and app's first cycle: ////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); ////state.position.bbt = Some(clock.bbt()); ////state.position @@ -493,12 +502,13 @@ mod bind { use crate::*; tui_keys!(self: App, input { + #[cfg(feature = "prof")] profiling::scope!("App::tui_keys!"); let name = self.mode.as_ref(); let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone); if let Some(mode) = mode { let binds = self.config.binds.clone(); 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) { for binding in bindings { 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); @@ -894,10 +908,12 @@ mod draw { /// Then, every top-level form of the DSL description is rendered. impl Draw for App { fn draw (&self, to: &mut Tui) -> Drawn { + #[cfg(feature = "prof")] profiling::scope!("App::draw"); //self.perf.cycle(&mut |_|{ self.draw_error(to)?; self.draw_modes(to)?; - //self.draw_debug(to)?; + self.draw_debug(to)?; + #[cfg(feature = "prof")] profiling::finish_frame!(); Ok(Some(to.area().into())) //}) } @@ -905,7 +921,7 @@ mod draw { impl App { 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)?; } Ok(()) @@ -914,27 +930,27 @@ mod draw { fn draw_modes (&self, to: &mut Tui) -> Usually<()> { if let Some(mode) = self.mode.as_ref().and_then(|m|self.config.get_mode(m)) { let mut error = false; - for (index, dsl) in mode.view.iter().enumerate() { - match self.interpret(to, dsl) { + for (index, view) in mode.view.iter().enumerate() { + match (view.render)(self, to) { Ok(None) => {}, Ok(Some(XYWH(.., w, h))) => { self.size.0.store(w as usize, Relaxed); self.size.1.store(h as usize, Relaxed); }, Err(e) => { - let src = &dsl.src().unwrap_or(Some("")).unwrap_or(""); let message = format!( - "Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{src}", - &mode.name + "Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", + &mode.name, + &view.source ); - *self.error.write().unwrap() = Some(message.into()); + *self.error.try_write().unwrap() = Some(message.into()); error = true; break; } } } if !error { - *self.error.write().unwrap() = None; + *self.error.try_write().unwrap() = None; } } Ok(()) @@ -942,78 +958,17 @@ mod draw { #[allow(unused)] fn draw_debug (&self, to: &mut Tui) -> Drawn { east( - format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)), - format!("{}% {} ", self.perf.percentage().unwrap_or_default(), self.perf.clock.raw() / 1000000000), + format!("{}x{} ", + 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) } } - impl Interpret>> for App { - fn interpret (&self, to: &mut Tui, dsl: L) -> Drawn { - 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); pub trait HasWidth { @@ -1024,9 +979,7 @@ mod draw { fn width_dec (&mut self); } - pub fn view_templates <'a> (_frags: std::str::Split<&str>, state: &'a App) - -> impl Draw + use<'a> - { + pub fn view_templates <'a> (state: &'a App) -> impl Draw + use<'a> { let height = (state.config.modes.len() * 2) as u16; draw(move |to: &mut Tui|{ let mut index = 0; diff --git a/tengri b/tengri index 4172fa25..8e7286e4 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 4172fa257776f5c6c7b406429b2244d630702458 +Subproject commit 8e7286e409ec6d4ac4382856ff93767a4758e11e