diff --git a/.gitignore b/.gitignore index 52b9e82c..dcfba704 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,18 @@ +*/target +target/* +!target/.gitkeep +perf.data* +flamegraph*.svg +vgcore* +example.mid +cov +*/cov +*.profraw +build/* +!build/README.md !build/*.sh !build/Dockerfile.* -!build/README.md -!target/.gitkeep -*.profraw -*/cov -*/target -.direnv .misc -build/* +.direnv callgrind.* -cov -example.mid -flamegraph*.svg -perf.data* -profile.json.gz -target/* tracing*.* -vgcore* diff --git a/Cargo.lock b/Cargo.lock index d4e3b5ca..387d9832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3893,9 +3893,13 @@ 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 920482ea..f0fa68e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,10 +51,9 @@ proptest = { version = "^1" } proptest-derive = { version = "^0.5.1" } [features] -default = ["cli", "arranger", "sampler", "prof"] +default = ["cli", "arranger", "sampler", "prof"] -prof = [] -#prof2 = ["prof", "tengri/prof"] +prof = ["tengri/prof"] hotpath = ["hotpath/hotpath"] hotpath-cpu = ["hotpath/hotpath-cpu"] hotpath-alloc = ["hotpath/hotpath-alloc"] diff --git a/Justfile b/Justfile index 0d6c64be..c06f8166 100644 --- a/Justfile +++ b/Justfile @@ -48,16 +48,12 @@ run: run-init: rm -rf ~/.config/tek && {{debug}} -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}} +prof: + CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 10000 -- new -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 43e75410..66738184 100755 --- a/shell.nix +++ b/shell.nix @@ -12,7 +12,6 @@ pkgs.perf pkgs.pkg-config pkgs.watchexec - pkgs.samply ]; buildInputs = [ pkgs.libclang diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index a8d760b6..00000000 --- a/src/cli.rs +++ /dev/null @@ -1,306 +0,0 @@ -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 dca95edb..55e34ed6 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.trim())) + src.each(config, |c, s|config_load_item(c, s)) } pub fn config_load_item , L: Language> (config: C, src: L) -> Usually { @@ -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:?}"); @@ -88,12 +88,6 @@ 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: @@ -105,9 +99,10 @@ impl AsMut for Mode { /// - ... -> view /// /// ``` -/// let mut mode: tek::Mode = tek::mode_add(tek::Mode::default(), "(name hello)").unwrap(); +/// let mut mode: tek::Mode> = Default::default(); +/// mode.add("(name hello)").unwrap(); /// ``` -pub fn mode_add > (mut mode: T, dsl: impl Language) -> Usually { +pub fn mode_add (mut mode: Mode, 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(""); @@ -117,23 +112,23 @@ pub fn mode_add > (mut mode: T, 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.as_mut().modes.clone(); + let modes = mode.modes.clone(); modes.0.try_write().unwrap().insert(name.into(), Arc::new(submode)); mode }, "keys" => { - tail.each(mode, |mut mode: T, expr: &str|{ - mode.as_mut().keys.push(expr.trim().into()); + tail.each(mode, |mut mode: Mode, expr: &str|{ + mode.keys.push(expr.trim().into()); Ok(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 }, + "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 }, } } else if let Ok(Some(word)) = dsl.word() { - mode.as_mut().view.push(View::new(word)?.into()); + mode.view.push(View::new(word)?.into()); mode } else { return Err(format!("Mode::add: unexpected: {dsl:?}").into()); @@ -141,12 +136,12 @@ 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(), { let mut map = Bind::new(); - body.each((), |_, item: &str|if matches!(item.expr().head(), Ok(Some("see"))) { + body.each((), |_, item: &str|if item.expr().head() == Ok(Some("see")) { // TODO Ok(()) } else if let Ok(Some(_word)) = item.expr().head().word() { @@ -177,15 +172,22 @@ pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> /// Configuration: mode, view, and bind definitions. /// /// ``` -/// 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(); +/// 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(); /// ``` #[derive(Default, Debug)] pub struct Config { @@ -219,7 +221,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 { @@ -263,9 +265,9 @@ impl View { UsuallyDrawn + Send + Sync>>> { let source = source.as_ref(); - let layer = if let Ok(Some(expr)) = source.expr() { + let layer = if let Some(expr) = source.expr()? { Self::compile_expr(expr.into())? - } else if let Ok(Some(word)) = source.word() { + } else if let Some(word) = source.word()? { Self::compile_word(word.into())? } else { return Err(format!("not word/expr:\n{source:?}").into()) @@ -276,24 +278,14 @@ 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(arg!(expr, head, 1, "condition")); + 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 = Self::compile(arg!(expr, head, 2, "content"))?; + 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)?, @@ -303,10 +295,12 @@ impl View { }, "either" => { - let cond = Arc::from(arg!(expr, head, 1, "condition")); + 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 = Self::compile(arg!(expr, head, 2, "content A"))?; - let b = Self::compile(arg!(expr, head, 3, "content B"))?; + 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)?, @@ -316,7 +310,7 @@ impl View { }) }, - "bsp" | "split" | "stack" => { + "bsp" | "split" => { let split = head.split('/').skip(1).next(); let split = match split { Some("n") => Split::North, @@ -327,34 +321,14 @@ impl View { Some("b") => Split::Below, _ => return Err(format!("invalid split: {split:?}").into()) }; - 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)) - }, - - "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)) + 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" => { @@ -373,7 +347,8 @@ impl View { Some("y") => Azimuth::Y, _ => return Err(format!("invalid azimuth: {azimuth:?}").into()) }; - let thunk = Self::compile(arg!(expr, head, 1, "content"))?; + let thunk = Self::compile(expr.nth(2)? + .ok_or_else(||Box::::from("either: no arg1: content"))?)?; Self::boxed(move|state, screen|{ Align( Some(azimuth), @@ -382,8 +357,9 @@ impl View { }) }, - "full" | "fill" => { - let thunk = Self::compile(arg!(expr, head, 1, "content"))?; + "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) @@ -391,56 +367,83 @@ impl View { Some("h") | Some("y") => Self::boxed(move|state, screen|{ Full::H(draw(|screen|thunk(state, screen))).draw(screen) }), - Some("wh") | Some("xy") | None => Self::boxed(move|state, 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" | "pad" => { + "exact" | "min" | "max" | "push" | "pull" => { match head.split('/').skip(1).next() { Some("w") | Some("x") => { - let value = Arc::from(arg!(expr, head, 1, "value")); + 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(arg!(expr, head, 2, "content"))?; + 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)), - "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)), + "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(arg!(expr, head, 1, "value")); + 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(arg!(expr, head, 2, "content"))?; + 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)), - "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)), + "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") | None => { - let value1 = Arc::from(arg!(expr, head, 1, "value")); + 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(arg!(expr, head, 2, "value")); + 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(arg!(expr, head, 3, "content"))?; + 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)), - "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)), + "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!() } }, @@ -449,31 +452,27 @@ impl View { }, "fg" | "bg" => { - let color = Arc::from(arg!(expr, head, 1, "color")); - let color = move|state: &App|state.namespace(&color); - 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)) - ).draw(screen)), - "bg" => Self::boxed(move|state, screen|bg( - color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen)) - ).draw(screen)), - _ => unreachable!() - } + 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" => { - let text: Arc = Arc::from(expr.tail()?.unwrap_or_default()); - Self::boxed(move|_state, screen|{ - text.draw(screen) - }) + 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()) })) @@ -482,10 +481,6 @@ 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() { @@ -495,26 +490,26 @@ impl View { }, Some(":tracks") => match word.split("/").skip(1).next() { None => Self::boxed(move|_, to|"TODO tracks".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)), + 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() { - Some("clips") => draw!(|app|app.view_scenes_clips()), - Some("names") => draw!(|app|app.view_scenes_names()), + 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(":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(":sessions") => Self::boxed(move|_, to|view_sessions().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(":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 { @@ -637,9 +632,7 @@ mod bind { /// An map of input events (e.g. [TuiEvent]) to [Binding]s. /// /// ``` - /// let lang = stringify!( - /// (@x (nop)) - /// (@y (nop) (nop))); + /// let lang = "(@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)); @@ -726,3 +719,50 @@ 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 67dfeb42..54bb3222 100644 --- a/src/deps.rs +++ b/src/deps.rs @@ -46,11 +46,11 @@ pub(crate) use ::{ }, }; -#[cfg(feature = "prof2")] +#[cfg(feature = "prof")] pub use ::tengri::{ profiling, tracing, - tracing_tracy, + tracing_flame, tracing_subscriber }; diff --git a/src/device/arrange.rs b/src/device/arrange.rs index 724673fa..3bea040c 100644 --- a/src/device/arrange.rs +++ b/src/device/arrange.rs @@ -70,7 +70,6 @@ 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 dd728264..0b42334b 100644 --- a/src/device/arrange/clip.rs +++ b/src/device/arrange/clip.rs @@ -50,32 +50,44 @@ 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(); - 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(), + 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 + ); below( + Outer(true, Style::default().fg(o)).full_wh(), 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( - 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() + 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()); } } @@ -88,11 +100,6 @@ 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 ffe9398a..38ae3997 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))); - 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) + 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) } pub fn view_io_ports <'a, T: PortsSizes<'a>> ( diff --git a/src/device/arrange/scene.rs b/src/device/arrange/scene.rs index 7d77d177..87fdac3f 100644 --- a/src/device/arrange/scene.rs +++ b/src/device/arrange/scene.rs @@ -207,9 +207,13 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + let select = self.selection(); let editor = self.editor(); let editing = self.is_editing(); - iter_south(move||self.scenes_with_sizes().map(move|(index, scene, ..)|{ - view_scene_name(select, editor, index, scene, 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) } fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { @@ -255,30 +259,6 @@ 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; } @@ -294,3 +274,28 @@ 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 d9dc1ebf..fc694fce 100644 --- a/src/device/arrange/track.rs +++ b/src/device/arrange/track.rs @@ -345,83 +345,90 @@ 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(); - 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) + 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) + }))) + ) + ) } /// Draw outputs per track fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw { view_track_row_section(theme, - 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)|{ + 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() { let f = Rgb(255, 255, 255); let b = track.color.dark.term; - 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)) + 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)) })) .full_h() .align_nw() .exact_w(track_width(index, track)) - })))) - } - - 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(to)?; + } + Ok(Some(XYWH(0, 0, 0, 0))) + }).align_w())) } /// Draw inputs per track @@ -430,12 +437,9 @@ 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, - 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)|{ + 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() { south( bg(track.color.base.term, east!( @@ -443,13 +447,17 @@ 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(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()) + 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())) } fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { @@ -632,7 +640,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(16), + button.align_nw().full_h().exact_w(20), content.align_c().full_wh() ) ) diff --git a/src/tek.edn b/src/tek.edn index 37f3a9b4..a2fd3dc2 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -1,8 +1,8 @@ (view :logo (text tek)) (view :browse (bsp/s - (pad/xy 3 1 :browse-title) - (fg (g 96) browser))) + (padding 3 1 :browse-title) + (enclose (fg (g 96)) browser))) (mode :transport (name Transport) @@ -34,16 +34,17 @@ (mode :scene (keys :scene)) (mode :mix (keys :mix)) (view - (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)))))))))))))) + (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)))))))))))))) (keys :clock (@space clock/toggle 0) (@shift/space clock/toggle 0)) @@ -81,29 +82,22 @@ (mode browse (keys :browse)) (mode rename (keys :pool/rename)) (mode length (keys :pool/length)) - (view - (bsp/s (exact/h 1 :transport) - (bsp/n (exact/h 1 :status) - (fill/xy (bsp/a (fill/xy (align/e :pool)) - :editor)))))) + (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) - (view - (bsp/s (exact/h 1 :transport) - (bsp/n (exact/h 1 :status) - (fill/xy :samples/grid))))) + (bsp/s (exact/h 1 :transport) + (bsp/n (exact/h 1 :status) + (fill :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)) - (view - (bsp/w :meters/output - (bsp/e :meters/input - (bsp/w :groove/meta - :groove/editor))))) + (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)))) diff --git a/src/tek.rs b/src/tek.rs index ee414d61..5c64590c 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -2,22 +2,242 @@ //#![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(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); + #[cfg(feature = "cli")] { + Config::watched(crate::cli::run_with_config)?; + } + #[cfg(not(feature = "cli"))] { + Config::watched(run_new_plain)?; } Ok(()) } -pub use self::cli::*; -mod cli; +#[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::config::*; mod config; @@ -106,11 +326,7 @@ mod app { /// Contains the currently edited musical arrangement pub project: Arrangement, /// Error, if any - pub error: Arc>>>, - - #[cfg(feature = "prof2")] - /// Tracing guard - pub guard: Option>>, + pub error: Arc>>> } impl App { @@ -118,15 +334,16 @@ 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 = tek::config_load(tek::Config::default(), "(mode hello)").unwrap(); - /// let tek = tek::App::new(None, proj, conf.into(), "hello"); + /// 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"); /// ``` 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(), @@ -285,7 +502,7 @@ mod bind { use crate::*; tui_keys!(self: App, input { - #[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!"); + #[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 { @@ -691,12 +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 = "prof2")] profiling::scope!("App::draw"); + #[cfg(feature = "prof")] profiling::scope!("App::draw"); //self.perf.cycle(&mut |_|{ self.draw_error(to)?; self.draw_modes(to)?; self.draw_debug(to)?; - #[cfg(feature = "prof2")] profiling::finish_frame!(); + #[cfg(feature = "prof")] profiling::finish_frame!(); Ok(Some(to.area().into())) //}) } @@ -747,7 +964,7 @@ mod draw { format!("{}/{} {} ", self.perf.used.load(Relaxed), self.perf.window.load(Relaxed), - self.perf.clock.raw() / 10000000), + self.perf.clock.raw() / 1000000000), ).align_se().draw(to) } } @@ -900,3 +1117,33 @@ 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 b318d498..8e7286e4 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit b318d498e544fa65bbf3a3ff4dabde166352b045 +Subproject commit 8e7286e409ec6d4ac4382856ff93767a4758e11e