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 f0fa68e1..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", "prof"] +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 55e34ed6..dca95edb 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 { @@ -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,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()); @@ -136,12 +141,12 @@ pub fn mode_add (mut mode: Mode, 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 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 { @@ -265,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()) @@ -278,14 +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)?, @@ -295,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)?, @@ -310,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, @@ -321,14 +327,34 @@ 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"))?)?; - Self::boxed(move|state, screen|{ - split.stack( - draw(|screen|a(state, screen)), - draw(|screen|b(state, screen)), - ).draw(screen) - }) + 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)) }, "align" => { @@ -347,8 +373,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), @@ -357,9 +382,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) @@ -367,83 +391,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!() } }, @@ -452,27 +449,31 @@ impl View { }, "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!() + 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!() + } }, "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()) })) @@ -481,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() { @@ -490,26 +495,26 @@ 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|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(":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(_) => Self::boxed(move|state, to|if let Some(view) = state.config.get_view(word.as_ref()) { (view.render)(state, to) } else { @@ -632,7 +637,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)); @@ -719,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 0b42334b..dd728264 100644 --- a/src/device/arrange/clip.rs +++ b/src/device/arrange/clip.rs @@ -50,44 +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() } } @@ -100,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 fc694fce..d9dc1ebf 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,12 @@ 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 +443,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 +632,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.edn b/src/tek.edn index a2fd3dc2..37f3a9b4 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/xy 3 1 :browse-title) + (fg (g 96) browser))) (mode :transport (name Transport) @@ -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)) @@ -82,22 +81,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)))) diff --git a/src/tek.rs b/src/tek.rs index 5c64590c..ee414d61 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -2,242 +2,22 @@ //#![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")] { - Config::watched(crate::cli::run_with_config)?; - } - #[cfg(not(feature = "cli"))] { - Config::watched(run_new_plain)?; + #[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); } 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 { @@ -334,16 +118,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(), @@ -502,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 { @@ -908,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())) //}) } @@ -964,7 +747,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) } } @@ -1117,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 8e7286e4..b318d498 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 8e7286e409ec6d4ac4382856ff93767a4758e11e +Subproject commit b318d498e544fa65bbf3a3ff4dabde166352b045