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 1/7] 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 From ab6959a84f7a1cd05be3069e753944ba02f80985 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Sat, 29 Aug 2026 18:46:46 +0300 Subject: [PATCH 2/7] 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 From 30b3802b561d7050d6648b8a7b3a62b5f019599c Mon Sep 17 00:00:00 2001 From: i do not exist Date: Sun, 30 Aug 2026 20:18:29 +0300 Subject: [PATCH 3/7] prettyprint error from main; fix some doctests --- src/config.rs | 101 ++++++++++++++++++++++++++------------------------ src/tek.edn | 4 +- src/tek.rs | 23 ++++++------ tengri | 2 +- 4 files changed, 67 insertions(+), 63 deletions(-) diff --git a/src/config.rs b/src/config.rs index 55e34ed6..8875649d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -88,6 +88,12 @@ pub fn modes_add <'a> (modes: &Modes, expr: impl Language) -> UsuallyRef<'a, ()> Ok(()) } +impl AsMut for Mode { + fn as_mut (&mut self) -> &mut Self { + self + } +} + /// Add a definition to the mode. /// /// Supported definitions: @@ -99,10 +105,9 @@ pub fn modes_add <'a> (modes: &Modes, expr: impl Language) -> UsuallyRef<'a, ()> /// - ... -> view /// /// ``` -/// let mut mode: tek::Mode> = Default::default(); -/// mode.add("(name hello)").unwrap(); +/// let mut mode: tek::Mode = tek::mode_add(tek::Mode::default(), "(name hello)").unwrap(); /// ``` -pub fn mode_add (mut mode: Mode, dsl: impl Language) -> Usually { +pub fn mode_add > (mut mode: T, dsl: impl Language) -> Usually { Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() { //println!("Mode::add: {head} {:?}", expr.tail()); let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); @@ -112,23 +117,23 @@ pub fn mode_add (mut mode: Mode, dsl: impl Language) -> Usually { let body = tail.tail()?.ok_or("submode: missing body")?; let submode = Mode::default(); let submode = body.each(submode, |c,s|mode_add(c,s))?; - let modes = mode.modes.clone(); + let modes = mode.as_mut().modes.clone(); modes.0.try_write().unwrap().insert(name.into(), Arc::new(submode)); mode }, "keys" => { - tail.each(mode, |mut mode: Mode, expr: &str|{ - mode.keys.push(expr.trim().into()); + tail.each(mode, |mut mode: T, expr: &str|{ + mode.as_mut().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(View::new(tail)?.into()); mode }, - _ => { mode.view.push(View::new(tail)?.into()); mode }, + "name" => { mode.as_mut().name.push(tail.into()); mode }, + "info" => { mode.as_mut().info.push(tail.into()); mode }, + "view" => { mode.as_mut().view.push(View::new(tail)?.into()); mode }, + _ => { mode.as_mut().view.push(View::new(tail)?.into()); mode }, } } else if let Ok(Some(word)) = dsl.word() { - mode.view.push(View::new(word)?.into()); + mode.as_mut().view.push(View::new(word)?.into()); mode } else { return Err(format!("Mode::add: unexpected: {dsl:?}").into()); @@ -141,7 +146,7 @@ pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> let body = expr.tail()?.unwrap_or(""); binds.try_write().unwrap().insert(name.into(), { let mut map = Bind::new(); - body.each((), |_, item: &str|if item.expr().head() == Ok(Some("see")) { + body.each((), |_, item: &str|if matches!(item.expr().head(), Ok(Some("see"))) { // TODO Ok(()) } else if let Ok(Some(_word)) = item.expr().head().word() { @@ -172,22 +177,15 @@ pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> /// Configuration: mode, view, and bind definitions. /// /// ``` -/// let config = tek::Config::default(); -/// ``` -/// -/// ``` -/// // Some dizzle. -/// // What indentation to use here lol? -/// let source = stringify!((mode :menu (name Menu) -/// (info Mode selector.) (keys :axis/y :confirm) -/// (view (bg (g 0) (bsp/s :ports/out -/// (bsp/n :ports/in -/// (bg (g 30) (bsp/s (fixed/y 7 :logo) -/// (fill :dialog/menu))))))))); -/// // Add this definition to the config and try to load it. -/// // A "mode" is basically a state machine -/// // with associated input and output definitions. -/// tek::Config::default().add(&source).unwrap().get_mode(":menu").unwrap(); +/// let source = stringify!( +/// (mode :menu (name Menu) +/// (info Mode selector.) (keys :axis/y :confirm) +/// (view (bg (g 0) (bsp/s :ports/out +/// (bsp/n :ports/in +/// (bg (g 30) (bsp/s (fixed/y 7 :logo) +/// (fill :dialog/menu))))))))); +/// let config: tek::Config = tek::modes_add(tek::Config::default(), source).unwrap(); +/// let mode: tek::Mode = config.get_mode(":menu").unwrap(); /// ``` #[derive(Default, Debug)] pub struct Config { @@ -221,7 +219,7 @@ pub struct Modes(Arc, Arc>>>); /// Group of view and keys definitions. /// /// ``` -/// let mode = tek::Mode::>::default(); +/// let mode = tek::Mode::default(); /// ``` #[derive(Default, Debug)] pub struct Mode { @@ -279,6 +277,7 @@ impl View { UsuallyDrawn + Send + Sync>>> { Ok(Arc::new(if let Some(head) = expr.head()? && let Some(ns) = head.split('/').next() { + match ns { "when" => { @@ -452,27 +451,31 @@ impl View { }, "fg" | "bg" => { - let color = expr.nth(1)?.ok_or_else(||Box::::from("{}: no arg1: color"))?; + let color = Arc::from(expr.nth(1)?.ok_or_else(||Box::::from("{}: no arg1: color"))?); + let color = move|state: &App|state.namespace(&color); let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::::from("either: no arg2: thunk"))?)?; - todo!() + match ns { + "fg" => Self::boxed(move|state, screen|fg( + color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen)) + ).draw(screen)), + "bg" => Self::boxed(move|state, screen|bg( + color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen)) + ).draw(screen)), + _ => unreachable!() + } }, "text" => { - todo!() + let text: Arc = Arc::from(expr.tail()?.unwrap_or_default()); + Self::boxed(move|_state, screen|{ + text.draw(screen) + }) }, - //"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()) })) @@ -503,13 +506,13 @@ impl View { }, 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(":sessions") => Self::boxed(move|_, 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(":status") => Self::boxed(move|_, to|"TODO: Status Bar".draw(to)), + Some(":editor") => Self::boxed(move|_, to|"TODO Editor".draw(to)), + Some(":transport") => Self::boxed(move|_, to|view_transport(true, "", "", "").draw(to)), + Some(":debug") => Self::boxed(move|_, 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 { @@ -632,7 +635,9 @@ mod bind { /// An map of input events (e.g. [TuiEvent]) to [Binding]s. /// /// ``` - /// let lang = "(@x (nop)) (@y (nop) (nop))"; + /// let lang = stringify!( + /// (@x (nop)) + /// (@y (nop) (nop))); /// let bind = tek::Bind::>::load(&lang).unwrap(); /// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1)); /// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2)); diff --git a/src/tek.edn b/src/tek.edn index a2fd3dc2..b102b865 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -1,8 +1,8 @@ (view :logo (text tek)) (view :browse (bsp/s - (padding 3 1 :browse-title) - (enclose (fg (g 96)) browser))) + (pad 3 1 :browse-title) + (fg (g 96) browser)) (mode :transport (name Transport) diff --git a/src/tek.rs b/src/tek.rs index 5c64590c..ff6c27a7 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -26,11 +26,11 @@ fn main () -> Usually<()> { _guard }; tengri::Tui::setup_panic(); - #[cfg(feature = "cli")] { - Config::watched(crate::cli::run_with_config)?; - } - #[cfg(not(feature = "cli"))] { - Config::watched(run_new_plain)?; + #[cfg(feature = "cli")] let outcome = Config::watched(crate::cli::run_with_config); + #[cfg(not(feature = "cli"))] let outcome = Config::watched(run_new_plain); + if let Err(e) = outcome { + println!("{e:#?}"); + std::process::exit(1); } Ok(()) } @@ -334,16 +334,15 @@ mod app { /// /// ``` /// let mut proj = tek::Arrangement::default(); - /// proj.jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack"); - /// let mut conf = std::sync::Arc::new(tek::Config::default()); - /// conf.add("(mode hello)"); - /// let tek = tek::App::new(None, proj, conf, "hello"); + /// proj.jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack"); + /// let mut conf = tek::config_load(tek::Config::default(), "(mode hello)").unwrap(); + /// let tek = tek::App::new(None, proj, conf.into(), "hello"); /// ``` pub fn new ( - exit: Option, + exit: Option, project: Arrangement, - config: Arc, - mode: impl AsRef + config: Arc, + mode: impl AsRef ) -> Self { App { exit: exit.unwrap_or_default(), diff --git a/tengri b/tengri index 8e7286e4..7f067b84 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 8e7286e409ec6d4ac4382856ff93767a4758e11e +Subproject commit 7f067b848fb504feb312033bf0a485bb2ccacef6 From e29f8de174763bb7aaf99129bdd28ef152701031 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Sun, 30 Aug 2026 22:29:58 +0300 Subject: [PATCH 4/7] disable tracing --- src/tek.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/tek.rs b/src/tek.rs index ff6c27a7..f1391d8f 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -16,15 +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 - }; + //#[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")] let outcome = Config::watched(crate::cli::run_with_config); #[cfg(not(feature = "cli"))] let outcome = Config::watched(run_new_plain); From 1082f62696255f6ba59fcc87f37bea20f568d4b4 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Sun, 30 Aug 2026 22:30:30 +0300 Subject: [PATCH 5/7] fix config and compilation; add arg! macro --- src/config.rs | 130 +++++++++++++++++++++----------------------------- src/tek.edn | 25 ++++++---- 2 files changed, 70 insertions(+), 85 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8875649d..e3652fac 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,7 +23,7 @@ pub fn config_init > (config: C) -> Usually { pub fn config_load , L: Language> (config: C, src: L) -> Usually { config.as_ref().clear(); config.as_ref().stamp.store(quanta::Clock::new().raw(), Relaxed); - src.each(config, |c, s|config_load_item(c, s)) + src.each(config, |c, s|config_load_item(c, s.trim())) } pub fn config_load_item , L: Language> (config: C, src: L) -> Usually { @@ -263,9 +263,9 @@ impl View { UsuallyDrawn + Send + Sync>>> { let source = source.as_ref(); - let layer = if let Some(expr) = source.expr()? { + let layer = if let Ok(Some(expr)) = source.expr() { Self::compile_expr(expr.into())? - } else if let Some(word) = source.word()? { + } else if let Ok(Some(word)) = source.word() { Self::compile_word(word.into())? } else { return Err(format!("not word/expr:\n{source:?}").into()) @@ -276,15 +276,24 @@ impl View { fn compile_expr (expr: Arc) -> UsuallyDrawn + Send + Sync>>> { + + macro_rules! arg { + ($src:expr, $head:expr, $index:expr, $name:expr) => { + $src.nth($index)? + .ok_or_else(||Box::::from(format!( + "{}: no arg #{} ({}) in {}", $head, $index, $name, $src + )))? + }; + } + 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 = Arc::from(arg!(expr, head, 1, "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)?; + let thunk = Self::compile(arg!(expr, head, 2, "content"))?; Self::boxed(move|state, screen|{ when( cond(state)?, @@ -294,12 +303,10 @@ impl View { }, "either" => { - let cond = Arc::from(expr.nth(1)?.ok_or_else(||Box::::from("either: no arg0: condition"))?); + let cond = Arc::from(arg!(expr, head, 1, "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)?; + let a = Self::compile(arg!(expr, head, 2, "content A"))?; + let b = Self::compile(arg!(expr, head, 3, "content B"))?; Self::boxed(move|state, screen|{ either( cond(state)?, @@ -309,7 +316,7 @@ impl View { }) }, - "bsp" | "split" => { + "bsp" | "split" | "stack" => { let split = head.split('/').skip(1).next(); let split = match split { Some("n") => Split::North, @@ -320,8 +327,8 @@ impl View { 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"))?)?; + let a = Self::compile(arg!(expr, head, 1, "content A"))?; + let b = Self::compile(arg!(expr, head, 2, "content B"))?; Self::boxed(move|state, screen|{ split.stack( draw(|screen|a(state, screen)), @@ -346,8 +353,7 @@ impl View { 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"))?)?; + let thunk = Self::compile(arg!(expr, head, 1, "content"))?; Self::boxed(move|state, screen|{ Align( Some(azimuth), @@ -356,9 +362,8 @@ impl View { }) }, - "full" => { - let thunk = Self::compile(expr.nth(2)? - .ok_or_else(||Box::::from("either: no arg1: content"))?)?; + "full" | "fill" => { + let thunk = Self::compile(arg!(expr, head, 1, "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) @@ -366,83 +371,56 @@ impl View { 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|{ + Some("wh") | Some("xy") | None => Self::boxed(move|state, screen|{ Full::WH(draw(|screen|thunk(state, screen))).draw(screen) }), _ => unreachable!() } }, - "exact" | "min" | "max" | "push" | "pull" => { + "exact" | "min" | "max" | "push" | "pull" | "pad" => { 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 = Arc::from(arg!(expr, head, 1, "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"))?)?; + let thunk = Self::compile(arg!(expr, head, 2, "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)), + "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)), + "pad" => Self::boxed(move|state, screen|Pad::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 = Arc::from(arg!(expr, head, 1, "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"))?)?; + let thunk = Self::compile(arg!(expr, head, 2, "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)), + "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)), + "pad" => Self::boxed(move|state, screen|Pad::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"))?); + Some("wh") | Some("xy") | None => { + let value1 = Arc::from(arg!(expr, head, 1, "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 = Arc::from(arg!(expr, head, 2, "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"))?)?; + let thunk = Self::compile(arg!(expr, head, 3, "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)), + "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)), + "pad" => Self::boxed(move|state, screen|Pad::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!() } }, @@ -451,9 +429,9 @@ impl View { }, "fg" | "bg" => { - let color = Arc::from(expr.nth(1)?.ok_or_else(||Box::::from("{}: no arg1: color"))?); + let color = Arc::from(arg!(expr, head, 1, "color")); let color = move|state: &App|state.namespace(&color); - let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::::from("either: no arg2: thunk"))?)?; + let thunk = Self::compile(arg!(expr, head, 2, "content"))?; match ns { "fg" => Self::boxed(move|state, screen|fg( color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen)) diff --git a/src/tek.edn b/src/tek.edn index b102b865..bf204e91 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -1,8 +1,8 @@ (view :logo (text tek)) (view :browse (bsp/s - (pad 3 1 :browse-title) - (fg (g 96) browser)) + (pad/xy 3 1 :browse-title) + (fg (g 96) browser))) (mode :transport (name Transport) @@ -82,22 +82,29 @@ (mode browse (keys :browse)) (mode rename (keys :pool/rename)) (mode length (keys :pool/length)) - (bsp/s (exact/h 1 :transport) - (bsp/n (exact/h 1 :status) - (fill (bsp/a (fill/xy (align/e :pool)) :editor))))) + (view + (bsp/s (exact/h 1 :transport) + (bsp/n (exact/h 1 :status) + (fill/xy (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/h 1 :transport) - (bsp/n (exact/h 1 :status) - (fill :samples/grid)))) + (view + (bsp/s (exact/h 1 :transport) + (bsp/n (exact/h 1 :status) + (fill/xy :samples/grid))))) (mode :groovebox (name Groovebox) (info Sequencer with sampler.) (keys :clock :editor :sampler :global) (mode browse (keys :browse)) (mode rename (keys :pool-rename)) (mode length (keys :pool-length)) - (bsp/w :meters/output (bsp/e :meters/input (bsp/w :groove/meta :groove/editor)))) + (view + (bsp/w :meters/output + (bsp/e :meters/input + (bsp/w :groove/meta + :groove/editor))))) (view :groove/meta (fill/y (align/n (stack/s :midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool)))) From 3deef0641d9bec3840a894cd7bf181cf10e1ec65 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Mon, 31 Aug 2026 00:14:54 +0300 Subject: [PATCH 6/7] realign tracks --- Cargo.toml | 2 +- src/device/arrange/clip.rs | 16 +--- src/device/arrange/track.rs | 167 +++++++++++++++++------------------- src/tek.rs | 20 ++--- 4 files changed, 94 insertions(+), 111 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f0fa68e1..8aca972f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ proptest = { version = "^1" } proptest-derive = { version = "^0.5.1" } [features] -default = ["cli", "arranger", "sampler", "prof"] +default = ["cli", "arranger", "sampler"] prof = ["tengri/prof"] hotpath = ["hotpath/hotpath"] diff --git a/src/device/arrange/clip.rs b/src/device/arrange/clip.rs index 0b42334b..dbef30c6 100644 --- a/src/device/arrange/clip.rs +++ b/src/device/arrange/clip.rs @@ -63,18 +63,10 @@ pub trait ClipsView: TracksView + ScenesView { )| { let (name, theme): (Arc, ItemTheme) = view_scene_name_theme(scene, track_index); let f = theme.lightest.term; - let (b, o) = view_scene_bg( - theme, select, track_index, scene_index - ); - let w = view_scene_w( - track, select, track_index, editor - ); - let y = view_scene_y( - select, scene_index, editor - ); - let is_selected = view_scene_sel( - select, track_index, scene_index, editing - ); + let (b, o) = view_scene_bg(theme, select, track_index, scene_index); + let w = view_scene_w(track, select, track_index, editor); + let y = view_scene_y(select, scene_index, editor); + let is_selected = view_scene_sel(select, track_index, scene_index, editing); below( Outer(true, Style::default().fg(o)).full_wh(), below( diff --git a/src/device/arrange/track.rs b/src/device/arrange/track.rs index fc694fce..b7a0a5ce 100644 --- a/src/device/arrange/track.rs +++ b/src/device/arrange/track.rs @@ -345,90 +345,83 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra /// Draw name of each track fn view_track_names (&self, theme: ItemTheme) -> impl Draw { let selected = self.selection(); - east( - south( - button_3( - "t", - "rack ", - east( - selected.track().map(|track|east(track, "/")), - self.tracks().len() - ), - false - ), - button_3( - "s", - "cene ", - east( - selected.scene().map(|scene|east(scene, "/")), - self.scenes().len() - ), - false - ) - ), - west( - south( - button_2("T", "+", false), - button_2("S", "+", false), - ), - bg(theme.darker.term, iter_east(||self.tracks_with_sizes() - .map(|(index, track, _x1, _x2)|{ - let b = if selected.track() == Some(index) { - track.color.light.term - } else { - track.color.base.term - }; - bg(b, south( - east!( - "·t", - index, - " ", - fg(Rgb(255, 255, 255), bold(true, &track.name)) - ) - .align_nw() - .full_w(), - "" - )) - .exact_w(track_width(index, track)) - .exact_h(2) - }))) - ) - ) + let btn1t = button_3("t", "rack ", east( + selected.track().map(|track|east(track, "/")), + self.tracks().len() + ), false); + let btn1s = button_3("s", "cene ", east( + selected.scene().map(|scene|east(scene, "/")), + self.scenes().len() + ), false); + let btns1 = south(btn1t, btn1s); + let btns2 = south( + button_2("T", "+", false), + button_2("S", "+", false), + ); + view_track_row_section(theme, + btns1, + btns2, + bg(theme.darker.term, + iter_east(||self.tracks_with_sizes().map(|(index, track, _x1, _x2)|{ + let b = if selected.track() == Some(index) { + track.color.light.term + } else { + track.color.base.term + }; + bg(b, south( + east!( + "·t", + index, + " ", + fg(Rgb(255, 255, 255), bold(true, &track.name)) + ) + .align_nw() + .full_w(), + "" + )) + .exact_w(track_width(index, track)) + .exact_h(2) + })))) + } + + fn view_track_output_count (&self) -> impl Draw { + south(button_2("o", "utput", false).align_w().full_w(), + draw(|to: &mut Tui|{ + for port in self.midi_outs().iter() { + let _ = port.port_name().align_w().full_w().draw(to)?; + } + Ok(Some(XYWH(0, 0, 0, 0))) + })) + } + + fn view_track_output_add (&self) -> impl Draw { + button_2("O", "+", false) } /// Draw outputs per track fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw { view_track_row_section(theme, - south(button_2("o", "utput", false).align_w().full_w(), - draw(|to: &mut Tui|{ - for port in self.midi_outs().iter() { - let _ = port.port_name().align_w().full_w().draw(to)?; - } - Ok(Some(XYWH(0, 0, 0, 0))) - })), - button_2("O", "+", false), - bg(theme.darker.term, draw(|to: &mut Tui|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { + self.view_track_output_count(), + self.view_track_output_add(), + bg(theme.darker.term, + iter_east(move||self.tracks_with_sizes().map(move|(index, track, _x1, _x2)|{ let f = Rgb(255, 255, 255); let b = track.color.dark.term; - iter_south(||track.sequencer.midi_outs.iter().map(|port: &MidiOutput|{ - fg(f, bg(b, east!( - "·o", - index, - " ", - port.port_name() - ) - .full_w() - .align_w() - ).exact_h(1)) + iter_south(move||track.sequencer.midi_outs.iter().map(move|port: &MidiOutput|{ + fg(f, bg(b, east!("·o", index, " ", port.port_name()).full_w().align_w()).exact_h(1)) })) .full_h() .align_nw() .exact_w(track_width(index, track)) - .draw(to)?; - } - Ok(Some(XYWH(0, 0, 0, 0))) - }).align_w())) + })))) + } + + fn view_track_input_count (&self) -> impl Draw { + button_2("i", "nput", false) + } + + fn view_track_input_add (&self) -> impl Draw { + button_2("I", "+", false) } /// Draw inputs per track @@ -437,9 +430,11 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra for track in self.tracks().iter() { height = height.max(track.sequencer.midi_ins.len() as u16); } - view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), - bg(theme.darker.term, draw(move|to: &mut Tui|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { + view_track_row_section(theme, + self.view_track_input_count(), + self.view_track_input_add(), + bg(theme.darker.term, + iter_east(move||self.tracks_with_sizes().map(move|(index, track, _x1, _x2)|{ south( bg(track.color.base.term, east!( @@ -447,17 +442,13 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra either(track.sequencer.recording, fg(Red, "●rec "), "·rec "), either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "), ).align_w().full_w()), - iter_south(||track.sequencer.midi_ins.iter().map(|port|fg_bg(Rgb(255, 255, 255), track.color.dark.term, - east!( - "·i", - index, - " ", - port.port_name() - ).align_w().full_w()))) - ).align_nw().exact_wh(track_width(index, track), height + 1).draw(to)?; - } - Ok(Some(XYWH(0, 0, 0, 0))) - }).align_w())) + iter_south(move||track.sequencer.midi_ins.iter().map(move|port|fg_bg( + Rgb(255, 255, 255), + track.color.dark.term, + east!("·i", index, " ", port.port_name()).align_w().full_w())))) + .align_nw() + .exact_wh(track_width(index, track), height + 1) + }))).align_w()) } fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { @@ -640,7 +631,7 @@ fn view_track_row_section <'a> ( west( button_add.align_nw().exact_w(4).full_h(), east( - button.align_nw().full_h().exact_w(20), + button.align_nw().full_h().exact_w(16), content.align_c().full_wh() ) ) diff --git a/src/tek.rs b/src/tek.rs index f1391d8f..5f123387 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -16,15 +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 - //}; + #[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")] let outcome = Config::watched(crate::cli::run_with_config); #[cfg(not(feature = "cli"))] let outcome = Config::watched(run_new_plain); @@ -963,7 +963,7 @@ mod draw { format!("{}/{} {} ", self.perf.used.load(Relaxed), self.perf.window.load(Relaxed), - self.perf.clock.raw() / 1000000000), + self.perf.clock.raw() / 10000000), ).align_se().draw(to) } } From d495d97516246a2a67d16a8b30b8619329069225 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Mon, 31 Aug 2026 19:05:10 +0300 Subject: [PATCH 7/7] separate cli; optimize arranger grid rendering --- .gitignore | 27 ++-- Cargo.lock | 4 - Cargo.toml | 5 +- Justfile | 12 +- shell.nix | 1 + src/cli.rs | 306 ++++++++++++++++++++++++++++++++++++ src/config.rs | 105 +++++-------- src/deps.rs | 4 +- src/device/arrange.rs | 1 + src/device/arrange/clip.rs | 55 +++---- src/device/arrange/port.rs | 12 +- src/device/arrange/scene.rs | 59 ++++--- src/device/arrange/track.rs | 5 +- src/tek.edn | 21 ++- src/tek.rs | 268 ++----------------------------- tengri | 2 +- 16 files changed, 462 insertions(+), 425 deletions(-) create mode 100644 src/cli.rs diff --git a/.gitignore b/.gitignore index dcfba704..52b9e82c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,19 @@ -*/target -target/* -!target/.gitkeep -perf.data* -flamegraph*.svg -vgcore* -example.mid -cov -*/cov -*.profraw -build/* -!build/README.md !build/*.sh !build/Dockerfile.* -.misc +!build/README.md +!target/.gitkeep +*.profraw +*/cov +*/target .direnv +.misc +build/* callgrind.* +cov +example.mid +flamegraph*.svg +perf.data* +profile.json.gz +target/* tracing*.* +vgcore* diff --git a/Cargo.lock b/Cargo.lock index 387d9832..d4e3b5ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3893,13 +3893,9 @@ dependencies = [ "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 8aca972f..920482ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,9 +51,10 @@ proptest = { version = "^1" } proptest-derive = { version = "^0.5.1" } [features] -default = ["cli", "arranger", "sampler"] +default = ["cli", "arranger", "sampler", "prof"] -prof = ["tengri/prof"] +prof = [] +#prof2 = ["prof", "tengri/prof"] hotpath = ["hotpath/hotpath"] hotpath-cpu = ["hotpath/hotpath-cpu"] hotpath-alloc = ["hotpath/hotpath-alloc"] diff --git a/Justfile b/Justfile index c06f8166..0d6c64be 100644 --- a/Justfile +++ b/Justfile @@ -48,12 +48,16 @@ run: run-init: rm -rf ~/.config/tek && {{debug}} -prof: - CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 10000 -- new +prof +ARGS="new": + CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 10000 -- {{ARGS}} +tracy +ARGS="new": + {{release}} -F prof {{ARGS}} +samply +ARGS="new": + samply record target/release/tek {{ARGS}} -release := "reset && cargo run --release --" +release := "reset && cargo run --release" release +ARGS="new": - {{release}} {{ARGS}} + {{release}} -- {{ARGS}} build-release: time cargo build -j4 --release diff --git a/shell.nix b/shell.nix index 66738184..43e75410 100755 --- a/shell.nix +++ b/shell.nix @@ -12,6 +12,7 @@ pkgs.perf pkgs.pkg-config pkgs.watchexec + pkgs.samply ]; buildInputs = [ pkgs.libclang diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 00000000..a8d760b6 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,306 @@ +use crate::*; + +/// Banner. +pub(crate) const HEADER: &'static str = r#" +~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ + █ █▀ █▀▀▄ ~ heatwave is the new darkwave ~ + ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; + +pub fn show_version () { + println!("versions aint real man"); +} + +#[cfg(not(feature = "cli"))] +fn run_new_plain (config: Config) -> Usually<()> { + let name = "tek"; + tengri::Tui::run_main(Jack::new_run(name, move|jack|{ + let mode = ":menu"; + let title = "untitled!"; + let bpm = 74.; + let clock = Clock::new(&jack, Some(bpm))?; + let tracks = []; + let scenes = []; + Ok(App::new(None, Arrangement::new( + &jack, + title.into(), + clock, + tracks.into_iter(), + scenes.into_iter(), + connect_midi_ins(&jack, &"M", &[], None)?.into_iter(), + connect_midi_outs(&jack, &"M", &[], None)?.into_iter(), + [].into_iter().chain(connect_audio_ins(&jack, &"L", &[], None)?.into_iter()) + .chain(connect_audio_ins(&jack, &"R", &[], None)?.into_iter()), + [].into_iter().chain(connect_audio_outs(&jack, &"L", &[], None)?.into_iter()) + .chain(connect_audio_outs(&jack, &"R", &[], None)?.into_iter()), + ), config, mode)) + })?) +} + +pub fn run_with_config (config: Arc) -> Usually<()> { + Cli::parse().run(Some(config)) +} + +/// The command-line interface descriptor. +/// +/// ``` +/// let cli: tek::Cli = Default::default(); +/// +/// use clap::CommandFactory; +/// tek::Cli::command().debug_assert(); +/// ``` +#[derive(Parser, Debug, Default)] +#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))] +pub struct Cli { + /// Pre-defined configuration modes. + /// + /// TODO: Replace these with scripted configurations. + #[command(subcommand)] pub action: Action, + + /// Record data for performance flamegraph. + #[arg(long)] + trace: bool, +} + +/// Command-line configuration. +impl Cli { + pub fn run (&self, mut config: Option>) -> Usually<()> { + if config.is_none() { + config = Some(Config::init_new(None)?); + } + self.action.run(config.unwrap(), self.trace) + } +} + +/// Application modes that can be passed to the mommand line interface. +/// +/// ``` +/// let action: tek::Action = Default::default(); +/// ``` +#[derive(Debug, Clone, Subcommand, Default)] +pub enum Action { + /// Continue where you left off + #[default] Resume, + /// Run headlessly in current session. + Headless, + /// Show status of current session. + Status, + /// List known sessions. + List, + /// Continue work in a copy of the current session. + Fork, + /// Create a new empty session. + New(ProjectInit), + /// Import media as new session. + Import, + /// Show configuration. + Config, + /// Show version. + Version, +} + +impl Action { + fn run (&self, config: Arc, trace: bool) -> Usually<()> { + use Action::*; + match self { + Version => show_version(), + Config => config.print(), + Resume => todo!("resume session"), + List => todo!("list sessions"), + New(sesh) => Exit::run(|exit|Tui::run_main( + exit.clone(), + { + let mut app = App::new(Some(exit), sesh.init()?, config, ":menu"); + #[cfg(feature = "prof2")] { + if trace { + app.guard = Some({ + 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 + }) + } + } + Arc::new(RwLock::new(app)) + })).map(|_|())?, + _ => todo!() + } + Ok(()) + } +} + +#[derive(Debug, Clone, Parser, Default)] +pub struct ProjectInit { + /// Name of JACK client + #[arg(short='n', long)] name: Option, + /// Whether to attempt to become transport master + #[arg(short='Y', long, default_value_t = false)] sync_lead: bool, + /// Whether to sync to external transport master + #[arg(short='y', long, default_value_t = true)] sync_follow: bool, + /// Initial tempo in beats per minute + #[arg(short='b', long, default_value = None)] bpm: Option, + /// Whether to include a transport toolbar (default: true) + #[arg(short='c', long, default_value_t = true)] show_clock: bool, + /// MIDI outs to connect to (multiple instances accepted) + #[arg(short='I', long)] midi_from: Vec, + /// MIDI outs to connect to (multiple instances accepted) + #[arg(short='i', long)] midi_from_re: Vec, + /// MIDI ins to connect to (multiple instances accepted) + #[arg(short='O', long)] midi_to: Vec, + /// MIDI ins to connect to (multiple instances accepted) + #[arg(short='o', long)] midi_to_re: Vec, + /// Audio outs to connect to left input + #[arg(short='l', long)] left_from: Vec, + /// Audio outs to connect to right input + #[arg(short='r', long)] right_from: Vec, + /// Audio ins to connect from left output + #[arg(short='L', long)] left_to: Vec, + /// Audio ins to connect from right output + #[arg(short='R', long)] right_to: Vec, + /// Tracks to creat + #[arg(short='t', long)] tracks: Option, + /// Scenes to create + #[arg(short='s', long)] scenes: Option, +} +impl ProjectInit { + pub fn init (&self) -> Usually { + let Self { + name, bpm, tracks, scenes, + sync_lead: _, sync_follow: _, + left_from, right_from, midi_from, midi_from_re, + left_to, right_to, midi_to, midi_to_re, + .. + } = self; + let name = name.as_ref().map_or("tek", |x|x.as_str()); + let jack = Jack::new(&name)?; + let mut proj = Arrangement::new( + &jack, + name.into(), + Clock::new(&jack, *bpm)?, + [].into_iter(), + [].into_iter(), + connect_midi_ins( + &jack, &"M".to_string(), midi_from.as_ref(), Some(midi_from_re) + )?.into_iter(), + connect_midi_outs( + &jack, &"M".to_string(), midi_to.as_ref(), Some(midi_to_re) + )?.into_iter(), + [].into_iter() + .chain( + connect_audio_ins(&jack, &"L".to_string(), &left_from, None)?.into_iter() + ) + .chain( + connect_audio_ins(&jack, &"R".to_string(), &right_from, None)?.into_iter() + ), + [].into_iter() + .chain( + connect_audio_outs(&jack, &"L".to_string(), &left_to, None)?.into_iter() + ) + .chain( + connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter() + )); + proj.tracks_add_many(tracks.unwrap_or(0), None, [].into(), [].into())?; + proj.scenes_add_many(scenes.unwrap_or(0))?; + Ok(proj) + } +} + +pub fn print_status (project: &Arrangement) { + println!("Name: {:?}", &project.name); + println!("JACK: {:?}", &project.jack); + println!("Buffer: {:?}", &project.clock.chunk); + println!("Sample rate: {:?}", &project.clock.timebase.sr); + println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); + println!("Tempo: {:?}", &project.clock.timebase.bpm); + println!("Quantize: {:?}", &project.clock.quant); + println!("Launch: {:?}", &project.clock.sync); + println!("Playhead: {:?}us", &project.clock.playhead.usec); + println!("Playhead: {:?}s", &project.clock.playhead.sample); + println!("Playhead: {:?}p", &project.clock.playhead.pulse); + println!("Started: {:?}", &project.clock.started); + println!("Tracks:"); + for (i, t) in project.tracks.iter().enumerate() { + println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, + &t.sequencer.play_clip, &t.sequencer.next_clip); + } + println!("Scenes:"); + for (i, t) in project.scenes.iter().enumerate() { + println!(" Scene {i}: {} {:?}", &t.name, &t.clips); + } + println!("MIDI Ins: {:?}", &project.midi_ins); + println!("MIDI Outs: {:?}", &project.midi_outs); + println!("Audio Ins: {:?}", &project.audio_ins); + println!("Audio Outs: {:?}", &project.audio_outs); + // TODO git integration + // TODO dawvert integration +} + +pub fn print_config (config: &Config) { + use ::ansi_term::Color::*; + println!("{:?}", config.dirs); + 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.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 { + Event::Key(KeyEvent { modifiers, .. }) => + format!("{:>16}", format!("{modifiers}")), + _ => unimplemented!() + })); + print!("{}", &Yellow.bold().paint(match &k.0 { + Event::Key(KeyEvent { code, .. }) => + format!("{:<10}", format!("{code}")), + _ => unimplemented!() + })); + for v in v.iter() { + print!(" => {:?}", v.commands); + print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); + println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); + //println!(" {:?}", v.source); + } + } + } + config.modes.for_each(|k, v|{ + println!(); + for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } + for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } + print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); + print!("\n{}", Blue.paint("KEYS")); + for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } + println!(); + v.modes.for_each(|k, v|{ + print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); + print!( " INFO={:?}", v.info); + print!( " VIEW={:?}", v.view); + println!(" KEYS={:?}", v.keys); + }); + print!("{}", Blue.paint("VIEW")); + for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } + println!(); + }); +} + + //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.try_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 e3652fac..dca95edb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -60,7 +60,7 @@ pub fn config_watch ( *config.as_ref().error.try_write().unwrap() = Some(format!("{e:?}").into()); panic!("{e:?}"); } else { - println!("config updated"); + //println!("config updated"); }, Err(errors) => { panic!("{errors:?}"); @@ -141,7 +141,7 @@ pub fn mode_add > (mut mode: T, dsl: impl Language) -> Usually } pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> { - println!("\n\rload_bind: {expr:?}"); + //println!("\n\rload_bind: {expr:?}"); let name = expr.head()?.ok_or("bind: missing name")?; let body = expr.tail()?.unwrap_or(""); binds.try_write().unwrap().insert(name.into(), { @@ -329,12 +329,32 @@ impl View { }; let a = Self::compile(arg!(expr, head, 1, "content A"))?; let b = Self::compile(arg!(expr, head, 2, "content B"))?; - Self::boxed(move|state, screen|{ - split.stack( - draw(|screen|a(state, screen)), - draw(|screen|b(state, screen)), - ).draw(screen) - }) + Self::boxed(move|state, screen|split.stack( + draw(|screen|a(state, screen)), + draw(|screen|b(state, screen)), + ).draw(screen)) + }, + + "bar" => { + 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 size = Arc::from(arg!(expr, head, 1, "size")); + let size = move|state: &App|state.namespace(&size)?.ok_or_else(||Box::::from("bar: no size")); + let a = Self::compile(arg!(expr, head, 2, "content A"))?; + let b = Self::compile(arg!(expr, head, 3, "content B"))?; + Self::boxed(move|state, screen|split.bar( + size(state)?, + draw(|screen|a(state, screen)), + draw(|screen|b(state, screen)), + ).draw(screen)) }, "align" => { @@ -462,6 +482,10 @@ impl View { fn compile_word (word: Arc) -> UsuallyDrawn + Send + Sync>>> { + macro_rules! draw { + (|$state:ident|$body:expr)=>{Self::boxed(move|$state, to|$body.draw(to))} + } + Ok(Arc::new(match word.split("/").next() { //Some(":logo") => view_logo().draw(to), Some(":meters") => match word.split("/").skip(1).next() { @@ -471,22 +495,22 @@ impl View { }, 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)), + Some("names") => draw!(|app|app.project.view_track_names(app.color.clone())), + Some("inputs") => draw!(|app|app.project.view_track_inputs(app.color.clone())), + Some("devices") => draw!(|app|app.project.view_track_devices(app.color.clone())), + Some("outputs") => draw!(|app|app.project.view_track_outputs(app.color.clone(), 0)), _ => 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)), + Some("clips") => draw!(|app|app.view_scenes_clips()), + Some("names") => draw!(|app|app.view_scenes_names()), _ => panic!() }, + Some(":templates") => draw!(|app|view_templates(app)), + Some(":browse/title") => draw!(|app|view_browse_title(app)), + Some(":device") => draw!(|app|view_device(app)), 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|_, 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|_, to|"TODO: Status Bar".draw(to)), Some(":editor") => Self::boxed(move|_, to|"TODO Editor".draw(to)), Some(":transport") => Self::boxed(move|_, to|view_transport(true, "", "", "").draw(to)), @@ -702,50 +726,3 @@ mod bind { impl_debug!(Condition |self, w| { write!(w, "*") }); } - -pub fn print_config (config: &Config) { - use ::ansi_term::Color::*; - println!("{:?}", config.dirs); - 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.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 { - Event::Key(KeyEvent { modifiers, .. }) => - format!("{:>16}", format!("{modifiers}")), - _ => unimplemented!() - })); - print!("{}", &Yellow.bold().paint(match &k.0 { - Event::Key(KeyEvent { code, .. }) => - format!("{:<10}", format!("{code}")), - _ => unimplemented!() - })); - for v in v.iter() { - print!(" => {:?}", v.commands); - print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); - println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); - //println!(" {:?}", v.source); - } - } - } - config.modes.for_each(|k, v|{ - println!(); - for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } - for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } - print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); - print!("\n{}", Blue.paint("KEYS")); - for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } - println!(); - v.modes.for_each(|k, v|{ - print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); - print!( " INFO={:?}", v.info); - print!( " VIEW={:?}", v.view); - println!(" KEYS={:?}", v.keys); - }); - print!("{}", Blue.paint("VIEW")); - for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } - println!(); - }); -} diff --git a/src/deps.rs b/src/deps.rs index 54bb3222..67dfeb42 100644 --- a/src/deps.rs +++ b/src/deps.rs @@ -46,11 +46,11 @@ pub(crate) use ::{ }, }; -#[cfg(feature = "prof")] +#[cfg(feature = "prof2")] pub use ::tengri::{ profiling, tracing, - tracing_flame, + tracing_tracy, tracing_subscriber }; diff --git a/src/device/arrange.rs b/src/device/arrange.rs index 3bea040c..724673fa 100644 --- a/src/device/arrange.rs +++ b/src/device/arrange.rs @@ -70,6 +70,7 @@ impl Arrangement { midi_outs: midi_outs.collect(), audio_ins: audio_ins.collect(), audio_outs: audio_outs.collect(), + size_inner: Sizer(Arc::new(40.into()), Arc::new(25.into())), clock, name, ..Default::default() diff --git a/src/device/arrange/clip.rs b/src/device/arrange/clip.rs index dbef30c6..dd728264 100644 --- a/src/device/arrange/clip.rs +++ b/src/device/arrange/clip.rs @@ -50,36 +50,32 @@ pub trait ClipsView: TracksView + ScenesView { fn view_scenes_clips (&self) -> impl Draw { let select = self.selection(); let editor = self.editor(); - let size = self.clips_size(); let editing = self.is_editing(); - return size.of( - above( - fg(Green, format!("{}x{}", size.w(), size.h())).align_se().full_wh(), - iter_east(move||self.tracks_with_sizes().map(move|( - track_index, track, _, _ - )| { - iter_south(move||self.scenes_with_sizes().map(move|( - scene_index, scene, _, _ - )| { - let (name, theme): (Arc, ItemTheme) = view_scene_name_theme(scene, track_index); - let f = theme.lightest.term; - let (b, o) = view_scene_bg(theme, select, track_index, scene_index); - let w = view_scene_w(track, select, track_index, editor); - let y = view_scene_y(select, scene_index, editor); - let is_selected = view_scene_sel(select, track_index, scene_index, editing); + with_clips_size(true, self.clips_size(), iter_east(move||self.tracks_with_sizes() + .map(move|(track_index, track, _, _)|iter_south(move||self.scenes_with_sizes() + .map(move|(scene_index, scene, _, _)|{ + let (name, theme): (Arc, ItemTheme) = view_scene_name_theme(scene, track_index); + let f = theme.lightest.term; + let (b, o) = view_scene_bg(theme, select, track_index, scene_index); + let is_selected = view_scene_sel(select, track_index, scene_index, editing); + below( + Outer(true, Style::default().fg(o)).full_wh(), below( - Outer(true, Style::default().fg(o)).full_wh(), below( - below( - fg_bg(o, b, "".full_wh()), - fg_bg(f, b, bold(true, name)).align_nw().full_wh(), - ), - when(is_selected, editor).full_wh() - ).full_wh() - ).exact_wh(w, y) - })).full_h().exact_w(track.width as u16) - })) - ).full_wh()); + fg_bg(o, b, "".full_wh()), + fg_bg(f, b, bold(true, name)).align_nw().full_wh(), + ), + when(is_selected, editor).full_wh() + ).full_wh() + ).exact_wh( + view_scene_w(track, select, track_index, editor), + view_scene_y(select, scene_index, editor), + ) + }) + ) + .full_h() + .exact_w(track.width as u16)) + )).align_c() } } @@ -92,6 +88,11 @@ fn view_scene_name_theme (scene: &Scene, track_index: usize) -> (Arc, ItemT } } +fn with_clips_size (show: bool, size: &Sizer, content: impl Draw) -> impl Draw { + let wh = east!(size.w() as usize, "x", size.h() as usize); + size.of(above(when(show, fg(Green, wh).align_se().full_wh()), content)) +} + fn view_scene_bg ( theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize ) -> (Color, Color) { diff --git a/src/device/arrange/port.rs b/src/device/arrange/port.rs index 38ae3997..ffe9398a 100644 --- a/src/device/arrange/port.rs +++ b/src/device/arrange/port.rs @@ -24,13 +24,13 @@ pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T]) -> impl Draw + use<'a, T> { - let ins = ports.len() as u16; + let ins = ports.len() as u16; let frame = Outer(true, Style::default().fg(g(96))); - let names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!( - " {index} {}", port.port_name() - ).align_w().full_h())); - let field = field_v(theme, title, names); - border(true, frame, field.exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins) + border(true, frame, field_v(theme, title, iter_south({ + move||ports.iter().enumerate().map(|(index, port)|{ + east!(" ", index, " ", port.port_name()).align_w().full_h() + }) + })).exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins) } pub fn view_io_ports <'a, T: PortsSizes<'a>> ( diff --git a/src/device/arrange/scene.rs b/src/device/arrange/scene.rs index 87fdac3f..7d77d177 100644 --- a/src/device/arrange/scene.rs +++ b/src/device/arrange/scene.rs @@ -207,13 +207,9 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + let select = self.selection(); let editor = self.editor(); let editing = self.is_editing(); - draw(move |to: &mut Tui|{ - for (index, scene, ..) in self.scenes_with_sizes() { - view_scene_name(select, editor, index, scene, editing).draw(to)?; - } - Ok(Some(XYWH(1, 1, 1, 1))) - }) - .exact_w(20) + iter_south(move||self.scenes_with_sizes().map(move|(index, scene, ..)|{ + view_scene_name(select, editor, index, scene, editing) + })) } fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { @@ -259,6 +255,30 @@ impl ScenesView for Arrangement { } } +pub fn view_scene_name <'a> ( + select: &Selection, + editor: Option<&'a MidiEditor>, + index: usize, + scene: &Scene, + editing: bool +) -> impl Draw { + let h = if select.scene() == Some(index) && let Some(_editor) = editor { + 7 + } else { + Scene::DEFAULT_HEIGHT as u16 + }; + let a = east!("·s", index, " ", fg(g(255), bold(true, &scene.name))).align_w().full_w(); + let b = when(select.scene() == Some(index) && editing, south( + editor.as_ref().map(|e|e.clip_status()), + editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh()); + let c = if select.scene() == Some(index) { + scene.color.light.term + } else { + scene.color.base.term + }; + bg(c, south(a, b).align_nw()).exact_wh(20, h) +} + pub trait HasSceneScroll: HasScenes { fn scene_scroll (&self) -> usize; } @@ -274,28 +294,3 @@ impl HasSceneScroll for App { self.project.scene_scroll() } } - -pub fn view_scene_name <'a> ( - select: &Selection, - editor: Option<&'a MidiEditor>, - index: usize, - scene: &Scene, - editing: bool -) -> impl Draw { - let h = if select.scene() == Some(index) && let Some(_editor) = editor { - 7 - } else { - Scene::DEFAULT_HEIGHT as u16 - }; - let a = east(format!("·s{index:02} "), - fg(g(255), bold(true, &scene.name))).align_w().full_w(); - let b = when(select.scene() == Some(index) && editing, south( - editor.as_ref().map(|e|e.clip_status()), - editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh()); - let c = if select.scene() == Some(index) { - scene.color.light.term - } else { - scene.color.base.term - }; - bg(c, south(a, b).align_nw()).exact_wh(20, h) -} diff --git a/src/device/arrange/track.rs b/src/device/arrange/track.rs index b7a0a5ce..d9dc1ebf 100644 --- a/src/device/arrange/track.rs +++ b/src/device/arrange/track.rs @@ -433,8 +433,9 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra view_track_row_section(theme, self.view_track_input_count(), self.view_track_input_add(), - bg(theme.darker.term, - iter_east(move||self.tracks_with_sizes().map(move|(index, track, _x1, _x2)|{ + bg(theme.darker.term, iter_east(move||self + .tracks_with_sizes() + .map(move|(index, track, _x1, _x2)|{ south( bg(track.color.base.term, east!( diff --git a/src/tek.edn b/src/tek.edn index bf204e91..37f3a9b4 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -34,17 +34,16 @@ (mode :scene (keys :scene)) (mode :mix (keys :mix)) (view - (bsp/n (bg (g 10) (bsp/e :transport :status)) - (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/h 4 :tracks/inputs)))))))))))))) + (bsp/n (bsp/e :transport :status) + (bar/w 2 (align/ne :meters/output) + (bar/e 2 (align/nw :meters/input) + (full/xy (pad/xy 2 1 (align/c + (bar/s 2 :tracks/outputs + (bar/s 2 :tracks/devices + (bar/s 2 :tracks/names + (bar/n 2 :tracks/inputs + (bar/e 16 (bg (g 50) :scenes/names) + (align/c :scenes/clips)))))))))))))) (keys :clock (@space clock/toggle 0) (@shift/space clock/toggle 0)) diff --git a/src/tek.rs b/src/tek.rs index 5f123387..ee414d61 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -2,31 +2,12 @@ //#![feature(impl_trait_in_assoc_type)] // Used by EntriesIterator; TODO remove mod deps; pub use self::deps::*; -/// Banner. -pub(crate) const HEADER: &'static str = r#" -~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ - █ █▀ █▀▀▄ ~ heatwave is the new darkwave ~ - ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; - -pub fn show_version () { - println!("versions aint real man"); -} - /// Command-line entrypoint. #[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")] let outcome = Config::watched(crate::cli::run_with_config); + #[cfg(feature = "cli")] let outcome = Config::watched(run_with_config); #[cfg(not(feature = "cli"))] let outcome = Config::watched(run_new_plain); if let Err(e) = outcome { println!("{e:#?}"); @@ -35,209 +16,8 @@ fn main () -> Usually<()> { Ok(()) } -#[cfg(not(feature = "cli"))] -fn run_new_plain (config: Config) -> Usually<()> { - let name = "tek"; - tengri::Tui::run_main(Jack::new_run(name, move|jack|{ - let mode = ":menu"; - let title = "untitled!"; - let bpm = 74.; - let clock = Clock::new(&jack, Some(bpm))?; - let tracks = []; - let scenes = []; - Ok(App::new(None, Arrangement::new( - &jack, - title.into(), - clock, - tracks.into_iter(), - scenes.into_iter(), - connect_midi_ins(&jack, &"M", &[], None)?.into_iter(), - connect_midi_outs(&jack, &"M", &[], None)?.into_iter(), - [].into_iter().chain(connect_audio_ins(&jack, &"L", &[], None)?.into_iter()) - .chain(connect_audio_ins(&jack, &"R", &[], None)?.into_iter()), - [].into_iter().chain(connect_audio_outs(&jack, &"L", &[], None)?.into_iter()) - .chain(connect_audio_outs(&jack, &"R", &[], None)?.into_iter()), - ), config, mode)) - })?) -} - - //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.try_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) - //})?)? - //} - -#[cfg(feature = "cli")] pub mod cli { - use crate::*; - - pub fn run_with_config (config: Arc) -> Usually<()> { - Cli::parse().run(Some(config)) - } - - /// Command-line configuration. - impl Cli { - pub fn run (&self, mut config: Option>) -> Usually<()> { - if config.is_none() { - config = Some(Config::init_new(None)?); - } - self.action.run(config.unwrap()) - } - } - - /// The command-line interface descriptor. - /// - /// ``` - /// let cli: tek::cli::Cli = Default::default(); - /// - /// use clap::CommandFactory; - /// tek::cli::Cli::command().debug_assert(); - /// ``` - #[derive(Parser, Debug, Default)] - #[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))] - pub struct Cli { - /// Pre-defined configuration modes. - /// - /// TODO: Replace these with scripted configurations. - #[command(subcommand)] pub action: Action, - } - impl Action { - fn run (&self, config: Arc) -> Usually<()> { - use Action::*; - match self { - Version => show_version(), - Config => config.print(), - Resume => todo!("resume session"), - List => todo!("list sessions"), - New(sesh) => Exit::run(|exit|Tui::run_main( - exit.clone(), - Arc::new(RwLock::new(App::new( - Some(exit), - sesh.init()?, - config, - ":menu" - ))))).map(|_|())?, - _ => todo!() - } - Ok(()) - } - } - /// Application modes that can be passed to the mommand line interface. - /// - /// ``` - /// let action: tek::cli::Action = Default::default(); - /// ``` - #[derive(Debug, Clone, Subcommand, Default)] - pub enum Action { - /// Continue where you left off - #[default] Resume, - /// Run headlessly in current session. - Headless, - /// Show status of current session. - Status, - /// List known sessions. - List, - /// Continue work in a copy of the current session. - Fork, - /// Create a new empty session. - New(ProjectInit), - /// Import media as new session. - Import, - /// Show configuration. - Config, - /// Show version. - Version, - } - #[derive(Debug, Clone, Parser, Default)] - pub struct ProjectInit { - /// Name of JACK client - #[arg(short='n', long)] name: Option, - /// Whether to attempt to become transport master - #[arg(short='Y', long, default_value_t = false)] sync_lead: bool, - /// Whether to sync to external transport master - #[arg(short='y', long, default_value_t = true)] sync_follow: bool, - /// Initial tempo in beats per minute - #[arg(short='b', long, default_value = None)] bpm: Option, - /// Whether to include a transport toolbar (default: true) - #[arg(short='c', long, default_value_t = true)] show_clock: bool, - /// MIDI outs to connect to (multiple instances accepted) - #[arg(short='I', long)] midi_from: Vec, - /// MIDI outs to connect to (multiple instances accepted) - #[arg(short='i', long)] midi_from_re: Vec, - /// MIDI ins to connect to (multiple instances accepted) - #[arg(short='O', long)] midi_to: Vec, - /// MIDI ins to connect to (multiple instances accepted) - #[arg(short='o', long)] midi_to_re: Vec, - /// Audio outs to connect to left input - #[arg(short='l', long)] left_from: Vec, - /// Audio outs to connect to right input - #[arg(short='r', long)] right_from: Vec, - /// Audio ins to connect from left output - #[arg(short='L', long)] left_to: Vec, - /// Audio ins to connect from right output - #[arg(short='R', long)] right_to: Vec, - /// Tracks to creat - #[arg(short='t', long)] tracks: Option, - /// Scenes to create - #[arg(short='s', long)] scenes: Option, - } - impl ProjectInit { - pub fn init (&self) -> Usually { - let Self { - name, bpm, tracks, scenes, - sync_lead: _, sync_follow: _, - left_from, right_from, midi_from, midi_from_re, - left_to, right_to, midi_to, midi_to_re, - .. - } = self; - let name = name.as_ref().map_or("tek", |x|x.as_str()); - let jack = Jack::new(&name)?; - let mut proj = Arrangement::new( - &jack, - name.into(), - Clock::new(&jack, *bpm)?, - [].into_iter(), - [].into_iter(), - connect_midi_ins( - &jack, &"M".to_string(), midi_from.as_ref(), Some(midi_from_re) - )?.into_iter(), - connect_midi_outs( - &jack, &"M".to_string(), midi_to.as_ref(), Some(midi_to_re) - )?.into_iter(), - [].into_iter() - .chain( - connect_audio_ins(&jack, &"L".to_string(), &left_from, None)?.into_iter() - ) - .chain( - connect_audio_ins(&jack, &"R".to_string(), &right_from, None)?.into_iter() - ), - [].into_iter() - .chain( - connect_audio_outs(&jack, &"L".to_string(), &left_to, None)?.into_iter() - ) - .chain( - connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter() - )); - proj.tracks_add_many(tracks.unwrap_or(0), None, [].into(), [].into())?; - proj.scenes_add_many(scenes.unwrap_or(0))?; - Ok(proj) - } - } -} +pub use self::cli::*; +mod cli; pub use self::config::*; mod config; @@ -326,7 +106,11 @@ mod app { /// Contains the currently edited musical arrangement pub project: Arrangement, /// Error, if any - pub error: Arc>>> + pub error: Arc>>>, + + #[cfg(feature = "prof2")] + /// Tracing guard + pub guard: Option>>, } impl App { @@ -501,7 +285,7 @@ mod bind { use crate::*; tui_keys!(self: App, input { - #[cfg(feature = "prof")] profiling::scope!("App::tui_keys!"); + #[cfg(feature = "prof2")] 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 { @@ -907,12 +691,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"); + #[cfg(feature = "prof2")] profiling::scope!("App::draw"); //self.perf.cycle(&mut |_|{ self.draw_error(to)?; self.draw_modes(to)?; self.draw_debug(to)?; - #[cfg(feature = "prof")] profiling::finish_frame!(); + #[cfg(feature = "prof2")] profiling::finish_frame!(); Ok(Some(to.area().into())) //}) } @@ -1116,33 +900,3 @@ mod draw { } } - -pub fn print_status (project: &Arrangement) { - println!("Name: {:?}", &project.name); - println!("JACK: {:?}", &project.jack); - println!("Buffer: {:?}", &project.clock.chunk); - println!("Sample rate: {:?}", &project.clock.timebase.sr); - println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); - println!("Tempo: {:?}", &project.clock.timebase.bpm); - println!("Quantize: {:?}", &project.clock.quant); - println!("Launch: {:?}", &project.clock.sync); - println!("Playhead: {:?}us", &project.clock.playhead.usec); - println!("Playhead: {:?}s", &project.clock.playhead.sample); - println!("Playhead: {:?}p", &project.clock.playhead.pulse); - println!("Started: {:?}", &project.clock.started); - println!("Tracks:"); - for (i, t) in project.tracks.iter().enumerate() { - println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, - &t.sequencer.play_clip, &t.sequencer.next_clip); - } - println!("Scenes:"); - for (i, t) in project.scenes.iter().enumerate() { - println!(" Scene {i}: {} {:?}", &t.name, &t.clips); - } - println!("MIDI Ins: {:?}", &project.midi_ins); - println!("MIDI Outs: {:?}", &project.midi_outs); - println!("Audio Ins: {:?}", &project.audio_ins); - println!("Audio Outs: {:?}", &project.audio_outs); - // TODO git integration - // TODO dawvert integration -} diff --git a/tengri b/tengri index 7f067b84..b318d498 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 7f067b848fb504feb312033bf0a485bb2ccacef6 +Subproject commit b318d498e544fa65bbf3a3ff4dabde166352b045