From d495d97516246a2a67d16a8b30b8619329069225 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Mon, 31 Aug 2026 19:05:10 +0300 Subject: [PATCH] separate cli; optimize arranger grid rendering --- .gitignore | 27 ++-- Cargo.lock | 4 - Cargo.toml | 5 +- Justfile | 12 +- shell.nix | 1 + src/cli.rs | 306 ++++++++++++++++++++++++++++++++++++ src/config.rs | 105 +++++-------- src/deps.rs | 4 +- src/device/arrange.rs | 1 + src/device/arrange/clip.rs | 55 +++---- src/device/arrange/port.rs | 12 +- src/device/arrange/scene.rs | 59 ++++--- src/device/arrange/track.rs | 5 +- src/tek.edn | 21 ++- src/tek.rs | 268 ++----------------------------- tengri | 2 +- 16 files changed, 462 insertions(+), 425 deletions(-) create mode 100644 src/cli.rs diff --git a/.gitignore b/.gitignore index dcfba704..52b9e82c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,19 @@ -*/target -target/* -!target/.gitkeep -perf.data* -flamegraph*.svg -vgcore* -example.mid -cov -*/cov -*.profraw -build/* -!build/README.md !build/*.sh !build/Dockerfile.* -.misc +!build/README.md +!target/.gitkeep +*.profraw +*/cov +*/target .direnv +.misc +build/* callgrind.* +cov +example.mid +flamegraph*.svg +perf.data* +profile.json.gz +target/* tracing*.* +vgcore* diff --git a/Cargo.lock b/Cargo.lock index 387d9832..d4e3b5ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3893,13 +3893,9 @@ dependencies = [ "midly", "palette", "parking_lot 0.12.5", - "profiling", "quanta", "rand 0.8.7", "ratatui", - "tracing", - "tracing-flame", - "tracing-subscriber", "unicode-width 0.2.0", ] diff --git a/Cargo.toml b/Cargo.toml index 8aca972f..920482ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,9 +51,10 @@ proptest = { version = "^1" } proptest-derive = { version = "^0.5.1" } [features] -default = ["cli", "arranger", "sampler"] +default = ["cli", "arranger", "sampler", "prof"] -prof = ["tengri/prof"] +prof = [] +#prof2 = ["prof", "tengri/prof"] hotpath = ["hotpath/hotpath"] hotpath-cpu = ["hotpath/hotpath-cpu"] hotpath-alloc = ["hotpath/hotpath-alloc"] diff --git a/Justfile b/Justfile index c06f8166..0d6c64be 100644 --- a/Justfile +++ b/Justfile @@ -48,12 +48,16 @@ run: run-init: rm -rf ~/.config/tek && {{debug}} -prof: - CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 10000 -- new +prof +ARGS="new": + CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 10000 -- {{ARGS}} +tracy +ARGS="new": + {{release}} -F prof {{ARGS}} +samply +ARGS="new": + samply record target/release/tek {{ARGS}} -release := "reset && cargo run --release --" +release := "reset && cargo run --release" release +ARGS="new": - {{release}} {{ARGS}} + {{release}} -- {{ARGS}} build-release: time cargo build -j4 --release diff --git a/shell.nix b/shell.nix index 66738184..43e75410 100755 --- a/shell.nix +++ b/shell.nix @@ -12,6 +12,7 @@ pkgs.perf pkgs.pkg-config pkgs.watchexec + pkgs.samply ]; buildInputs = [ pkgs.libclang diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 00000000..a8d760b6 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,306 @@ +use crate::*; + +/// Banner. +pub(crate) const HEADER: &'static str = r#" +~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ + █ █▀ █▀▀▄ ~ heatwave is the new darkwave ~ + ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; + +pub fn show_version () { + println!("versions aint real man"); +} + +#[cfg(not(feature = "cli"))] +fn run_new_plain (config: Config) -> Usually<()> { + let name = "tek"; + tengri::Tui::run_main(Jack::new_run(name, move|jack|{ + let mode = ":menu"; + let title = "untitled!"; + let bpm = 74.; + let clock = Clock::new(&jack, Some(bpm))?; + let tracks = []; + let scenes = []; + Ok(App::new(None, Arrangement::new( + &jack, + title.into(), + clock, + tracks.into_iter(), + scenes.into_iter(), + connect_midi_ins(&jack, &"M", &[], None)?.into_iter(), + connect_midi_outs(&jack, &"M", &[], None)?.into_iter(), + [].into_iter().chain(connect_audio_ins(&jack, &"L", &[], None)?.into_iter()) + .chain(connect_audio_ins(&jack, &"R", &[], None)?.into_iter()), + [].into_iter().chain(connect_audio_outs(&jack, &"L", &[], None)?.into_iter()) + .chain(connect_audio_outs(&jack, &"R", &[], None)?.into_iter()), + ), config, mode)) + })?) +} + +pub fn run_with_config (config: Arc) -> Usually<()> { + Cli::parse().run(Some(config)) +} + +/// The command-line interface descriptor. +/// +/// ``` +/// let cli: tek::Cli = Default::default(); +/// +/// use clap::CommandFactory; +/// tek::Cli::command().debug_assert(); +/// ``` +#[derive(Parser, Debug, Default)] +#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))] +pub struct Cli { + /// Pre-defined configuration modes. + /// + /// TODO: Replace these with scripted configurations. + #[command(subcommand)] pub action: Action, + + /// Record data for performance flamegraph. + #[arg(long)] + trace: bool, +} + +/// Command-line configuration. +impl Cli { + pub fn run (&self, mut config: Option>) -> Usually<()> { + if config.is_none() { + config = Some(Config::init_new(None)?); + } + self.action.run(config.unwrap(), self.trace) + } +} + +/// Application modes that can be passed to the mommand line interface. +/// +/// ``` +/// let action: tek::Action = Default::default(); +/// ``` +#[derive(Debug, Clone, Subcommand, Default)] +pub enum Action { + /// Continue where you left off + #[default] Resume, + /// Run headlessly in current session. + Headless, + /// Show status of current session. + Status, + /// List known sessions. + List, + /// Continue work in a copy of the current session. + Fork, + /// Create a new empty session. + New(ProjectInit), + /// Import media as new session. + Import, + /// Show configuration. + Config, + /// Show version. + Version, +} + +impl Action { + fn run (&self, config: Arc, trace: bool) -> Usually<()> { + use Action::*; + match self { + Version => show_version(), + Config => config.print(), + Resume => todo!("resume session"), + List => todo!("list sessions"), + New(sesh) => Exit::run(|exit|Tui::run_main( + exit.clone(), + { + let mut app = App::new(Some(exit), sesh.init()?, config, ":menu"); + #[cfg(feature = "prof2")] { + if trace { + app.guard = Some({ + use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + //let tracy = tracing_tracy::TracyLayer::default(); + let (flame, _guard) = tracing_flame::FlameLayer::with_file("./tracing.folded").unwrap(); + //let registry = tracing_subscriber::registry().with(flame).init(); + //tracing::subscriber::set_global_default(registry)?; + tracing::subscriber::set_global_default(tracing_subscriber::registry().with(flame))?; + _guard + }) + } + } + Arc::new(RwLock::new(app)) + })).map(|_|())?, + _ => todo!() + } + Ok(()) + } +} + +#[derive(Debug, Clone, Parser, Default)] +pub struct ProjectInit { + /// Name of JACK client + #[arg(short='n', long)] name: Option, + /// Whether to attempt to become transport master + #[arg(short='Y', long, default_value_t = false)] sync_lead: bool, + /// Whether to sync to external transport master + #[arg(short='y', long, default_value_t = true)] sync_follow: bool, + /// Initial tempo in beats per minute + #[arg(short='b', long, default_value = None)] bpm: Option, + /// Whether to include a transport toolbar (default: true) + #[arg(short='c', long, default_value_t = true)] show_clock: bool, + /// MIDI outs to connect to (multiple instances accepted) + #[arg(short='I', long)] midi_from: Vec, + /// MIDI outs to connect to (multiple instances accepted) + #[arg(short='i', long)] midi_from_re: Vec, + /// MIDI ins to connect to (multiple instances accepted) + #[arg(short='O', long)] midi_to: Vec, + /// MIDI ins to connect to (multiple instances accepted) + #[arg(short='o', long)] midi_to_re: Vec, + /// Audio outs to connect to left input + #[arg(short='l', long)] left_from: Vec, + /// Audio outs to connect to right input + #[arg(short='r', long)] right_from: Vec, + /// Audio ins to connect from left output + #[arg(short='L', long)] left_to: Vec, + /// Audio ins to connect from right output + #[arg(short='R', long)] right_to: Vec, + /// Tracks to creat + #[arg(short='t', long)] tracks: Option, + /// Scenes to create + #[arg(short='s', long)] scenes: Option, +} +impl ProjectInit { + pub fn init (&self) -> Usually { + let Self { + name, bpm, tracks, scenes, + sync_lead: _, sync_follow: _, + left_from, right_from, midi_from, midi_from_re, + left_to, right_to, midi_to, midi_to_re, + .. + } = self; + let name = name.as_ref().map_or("tek", |x|x.as_str()); + let jack = Jack::new(&name)?; + let mut proj = Arrangement::new( + &jack, + name.into(), + Clock::new(&jack, *bpm)?, + [].into_iter(), + [].into_iter(), + connect_midi_ins( + &jack, &"M".to_string(), midi_from.as_ref(), Some(midi_from_re) + )?.into_iter(), + connect_midi_outs( + &jack, &"M".to_string(), midi_to.as_ref(), Some(midi_to_re) + )?.into_iter(), + [].into_iter() + .chain( + connect_audio_ins(&jack, &"L".to_string(), &left_from, None)?.into_iter() + ) + .chain( + connect_audio_ins(&jack, &"R".to_string(), &right_from, None)?.into_iter() + ), + [].into_iter() + .chain( + connect_audio_outs(&jack, &"L".to_string(), &left_to, None)?.into_iter() + ) + .chain( + connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter() + )); + proj.tracks_add_many(tracks.unwrap_or(0), None, [].into(), [].into())?; + proj.scenes_add_many(scenes.unwrap_or(0))?; + Ok(proj) + } +} + +pub fn print_status (project: &Arrangement) { + println!("Name: {:?}", &project.name); + println!("JACK: {:?}", &project.jack); + println!("Buffer: {:?}", &project.clock.chunk); + println!("Sample rate: {:?}", &project.clock.timebase.sr); + println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); + println!("Tempo: {:?}", &project.clock.timebase.bpm); + println!("Quantize: {:?}", &project.clock.quant); + println!("Launch: {:?}", &project.clock.sync); + println!("Playhead: {:?}us", &project.clock.playhead.usec); + println!("Playhead: {:?}s", &project.clock.playhead.sample); + println!("Playhead: {:?}p", &project.clock.playhead.pulse); + println!("Started: {:?}", &project.clock.started); + println!("Tracks:"); + for (i, t) in project.tracks.iter().enumerate() { + println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, + &t.sequencer.play_clip, &t.sequencer.next_clip); + } + println!("Scenes:"); + for (i, t) in project.scenes.iter().enumerate() { + println!(" Scene {i}: {} {:?}", &t.name, &t.clips); + } + println!("MIDI Ins: {:?}", &project.midi_ins); + println!("MIDI Outs: {:?}", &project.midi_outs); + println!("Audio Ins: {:?}", &project.audio_ins); + println!("Audio Outs: {:?}", &project.audio_outs); + // TODO git integration + // TODO dawvert integration +} + +pub fn print_config (config: &Config) { + use ::ansi_term::Color::*; + println!("{:?}", config.dirs); + for (k, v) in config.views.try_read().unwrap().iter() { + println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source); + } + for (k, v) in config.binds.try_read().unwrap().iter() { + println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}"))); + for (k, v) in v.0.iter() { + print!("{} ", &Yellow.paint(match &k.0 { + Event::Key(KeyEvent { modifiers, .. }) => + format!("{:>16}", format!("{modifiers}")), + _ => unimplemented!() + })); + print!("{}", &Yellow.bold().paint(match &k.0 { + Event::Key(KeyEvent { code, .. }) => + format!("{:<10}", format!("{code}")), + _ => unimplemented!() + })); + for v in v.iter() { + print!(" => {:?}", v.commands); + print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); + println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); + //println!(" {:?}", v.source); + } + } + } + config.modes.for_each(|k, v|{ + println!(); + for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } + for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } + print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); + print!("\n{}", Blue.paint("KEYS")); + for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } + println!(); + v.modes.for_each(|k, v|{ + print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); + print!( " INFO={:?}", v.info); + print!( " VIEW={:?}", v.view); + println!(" KEYS={:?}", v.keys); + }); + print!("{}", Blue.paint("VIEW")); + for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } + println!(); + }); +} + + //pub fn tui ( + //app: Arc>, + //jack: Jack, + //sync_lead: &bool, + //sync_follow: &bool, + //) -> Usually<()> { + //// Run the [Tui] and [Jack] threads with the [App] state. + //Tui::run_main(&jack.run(move|jack|{ + //// Between jack init and app's first cycle: + ////jack.sync_lead(*sync_lead, |mut state|{ + ////let clock = app.try_write().unwrap().clock(); + ////clock.playhead.update_from_sample(state.position.frame() as f64); + ////state.position.bbt = Some(clock.bbt()); + ////state.position + ////})?; + ////jack.sync_follow(*sync_follow)?; + //// FIXME: They don't work properly. + //Ok(app) + //})?)? + //} diff --git a/src/config.rs b/src/config.rs index e3652fac..dca95edb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -60,7 +60,7 @@ pub fn config_watch ( *config.as_ref().error.try_write().unwrap() = Some(format!("{e:?}").into()); panic!("{e:?}"); } else { - println!("config updated"); + //println!("config updated"); }, Err(errors) => { panic!("{errors:?}"); @@ -141,7 +141,7 @@ pub fn mode_add > (mut mode: T, dsl: impl Language) -> Usually } pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> { - println!("\n\rload_bind: {expr:?}"); + //println!("\n\rload_bind: {expr:?}"); let name = expr.head()?.ok_or("bind: missing name")?; let body = expr.tail()?.unwrap_or(""); binds.try_write().unwrap().insert(name.into(), { @@ -329,12 +329,32 @@ impl View { }; let a = Self::compile(arg!(expr, head, 1, "content A"))?; let b = Self::compile(arg!(expr, head, 2, "content B"))?; - Self::boxed(move|state, screen|{ - split.stack( - draw(|screen|a(state, screen)), - draw(|screen|b(state, screen)), - ).draw(screen) - }) + Self::boxed(move|state, screen|split.stack( + draw(|screen|a(state, screen)), + draw(|screen|b(state, screen)), + ).draw(screen)) + }, + + "bar" => { + let split = head.split('/').skip(1).next(); + let split = match split { + Some("n") => Split::North, + Some("s") => Split::South, + Some("e") => Split::East, + Some("w") => Split::West, + Some("a") => Split::Above, + Some("b") => Split::Below, + _ => return Err(format!("invalid split: {split:?}").into()) + }; + let size = Arc::from(arg!(expr, head, 1, "size")); + let size = move|state: &App|state.namespace(&size)?.ok_or_else(||Box::::from("bar: no size")); + let a = Self::compile(arg!(expr, head, 2, "content A"))?; + let b = Self::compile(arg!(expr, head, 3, "content B"))?; + Self::boxed(move|state, screen|split.bar( + size(state)?, + draw(|screen|a(state, screen)), + draw(|screen|b(state, screen)), + ).draw(screen)) }, "align" => { @@ -462,6 +482,10 @@ impl View { fn compile_word (word: Arc) -> UsuallyDrawn + Send + Sync>>> { + macro_rules! draw { + (|$state:ident|$body:expr)=>{Self::boxed(move|$state, to|$body.draw(to))} + } + Ok(Arc::new(match word.split("/").next() { //Some(":logo") => view_logo().draw(to), Some(":meters") => match word.split("/").skip(1).next() { @@ -471,22 +495,22 @@ impl View { }, Some(":tracks") => match word.split("/").skip(1).next() { None => Self::boxed(move|_, to|"TODO tracks".draw(to)), - Some("names") => Self::boxed(move|state, to|state.project.view_track_names(state.color.clone()).draw(to)),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))), - Some("inputs") => Self::boxed(move|state, to|state.project.view_track_inputs(state.color.clone()).draw(to)),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to), - Some("devices") => Self::boxed(move|state, to|state.project.view_track_devices(state.color.clone()).draw(to)),//bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to), - Some("outputs") => Self::boxed(move|state, to|state.project.view_track_outputs(state.color.clone(), 0).draw(to)), + Some("names") => draw!(|app|app.project.view_track_names(app.color.clone())), + Some("inputs") => draw!(|app|app.project.view_track_inputs(app.color.clone())), + Some("devices") => draw!(|app|app.project.view_track_devices(app.color.clone())), + Some("outputs") => draw!(|app|app.project.view_track_outputs(app.color.clone(), 0)), _ => panic!() }, Some(":scenes") => match word.split("/").skip(1).next() { - None => Self::boxed(move|state, to|state.view_scenes_clips().draw(to)), - Some("names") => Self::boxed(move|state, to|state.view_scenes_names().draw(to)), + Some("clips") => draw!(|app|app.view_scenes_clips()), + Some("names") => draw!(|app|app.view_scenes_names()), _ => panic!() }, + Some(":templates") => draw!(|app|view_templates(app)), + Some(":browse/title") => draw!(|app|view_browse_title(app)), + Some(":device") => draw!(|app|view_device(app)), Some(":dialog") => Self::boxed(move|state, to|draw_dialog(to, word.split("/").skip(1), state)), - Some(":templates") => Self::boxed(move|state, to|view_templates(state).draw(to)), Some(":sessions") => Self::boxed(move|_, to|view_sessions().draw(to)), - Some(":browse/title") => Self::boxed(move|state, to|view_browse_title(state).draw(to)), - Some(":device") => Self::boxed(move|state, to|view_device(state).draw(to)), Some(":status") => Self::boxed(move|_, to|"TODO: Status Bar".draw(to)), Some(":editor") => Self::boxed(move|_, to|"TODO Editor".draw(to)), Some(":transport") => Self::boxed(move|_, to|view_transport(true, "", "", "").draw(to)), @@ -702,50 +726,3 @@ mod bind { impl_debug!(Condition |self, w| { write!(w, "*") }); } - -pub fn print_config (config: &Config) { - use ::ansi_term::Color::*; - println!("{:?}", config.dirs); - for (k, v) in config.views.try_read().unwrap().iter() { - println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source); - } - for (k, v) in config.binds.try_read().unwrap().iter() { - println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}"))); - for (k, v) in v.0.iter() { - print!("{} ", &Yellow.paint(match &k.0 { - Event::Key(KeyEvent { modifiers, .. }) => - format!("{:>16}", format!("{modifiers}")), - _ => unimplemented!() - })); - print!("{}", &Yellow.bold().paint(match &k.0 { - Event::Key(KeyEvent { code, .. }) => - format!("{:<10}", format!("{code}")), - _ => unimplemented!() - })); - for v in v.iter() { - print!(" => {:?}", v.commands); - print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); - println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); - //println!(" {:?}", v.source); - } - } - } - config.modes.for_each(|k, v|{ - println!(); - for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } - for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } - print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); - print!("\n{}", Blue.paint("KEYS")); - for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } - println!(); - v.modes.for_each(|k, v|{ - print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); - print!( " INFO={:?}", v.info); - print!( " VIEW={:?}", v.view); - println!(" KEYS={:?}", v.keys); - }); - print!("{}", Blue.paint("VIEW")); - for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } - println!(); - }); -} diff --git a/src/deps.rs b/src/deps.rs index 54bb3222..67dfeb42 100644 --- a/src/deps.rs +++ b/src/deps.rs @@ -46,11 +46,11 @@ pub(crate) use ::{ }, }; -#[cfg(feature = "prof")] +#[cfg(feature = "prof2")] pub use ::tengri::{ profiling, tracing, - tracing_flame, + tracing_tracy, tracing_subscriber }; diff --git a/src/device/arrange.rs b/src/device/arrange.rs index 3bea040c..724673fa 100644 --- a/src/device/arrange.rs +++ b/src/device/arrange.rs @@ -70,6 +70,7 @@ impl Arrangement { midi_outs: midi_outs.collect(), audio_ins: audio_ins.collect(), audio_outs: audio_outs.collect(), + size_inner: Sizer(Arc::new(40.into()), Arc::new(25.into())), clock, name, ..Default::default() diff --git a/src/device/arrange/clip.rs b/src/device/arrange/clip.rs index dbef30c6..dd728264 100644 --- a/src/device/arrange/clip.rs +++ b/src/device/arrange/clip.rs @@ -50,36 +50,32 @@ pub trait ClipsView: TracksView + ScenesView { fn view_scenes_clips (&self) -> impl Draw { let select = self.selection(); let editor = self.editor(); - let size = self.clips_size(); let editing = self.is_editing(); - return size.of( - above( - fg(Green, format!("{}x{}", size.w(), size.h())).align_se().full_wh(), - iter_east(move||self.tracks_with_sizes().map(move|( - track_index, track, _, _ - )| { - iter_south(move||self.scenes_with_sizes().map(move|( - scene_index, scene, _, _ - )| { - let (name, theme): (Arc, ItemTheme) = view_scene_name_theme(scene, track_index); - let f = theme.lightest.term; - let (b, o) = view_scene_bg(theme, select, track_index, scene_index); - let w = view_scene_w(track, select, track_index, editor); - let y = view_scene_y(select, scene_index, editor); - let is_selected = view_scene_sel(select, track_index, scene_index, editing); + with_clips_size(true, self.clips_size(), iter_east(move||self.tracks_with_sizes() + .map(move|(track_index, track, _, _)|iter_south(move||self.scenes_with_sizes() + .map(move|(scene_index, scene, _, _)|{ + let (name, theme): (Arc, ItemTheme) = view_scene_name_theme(scene, track_index); + let f = theme.lightest.term; + let (b, o) = view_scene_bg(theme, select, track_index, scene_index); + let is_selected = view_scene_sel(select, track_index, scene_index, editing); + below( + Outer(true, Style::default().fg(o)).full_wh(), below( - Outer(true, Style::default().fg(o)).full_wh(), below( - below( - fg_bg(o, b, "".full_wh()), - fg_bg(f, b, bold(true, name)).align_nw().full_wh(), - ), - when(is_selected, editor).full_wh() - ).full_wh() - ).exact_wh(w, y) - })).full_h().exact_w(track.width as u16) - })) - ).full_wh()); + fg_bg(o, b, "".full_wh()), + fg_bg(f, b, bold(true, name)).align_nw().full_wh(), + ), + when(is_selected, editor).full_wh() + ).full_wh() + ).exact_wh( + view_scene_w(track, select, track_index, editor), + view_scene_y(select, scene_index, editor), + ) + }) + ) + .full_h() + .exact_w(track.width as u16)) + )).align_c() } } @@ -92,6 +88,11 @@ fn view_scene_name_theme (scene: &Scene, track_index: usize) -> (Arc, ItemT } } +fn with_clips_size (show: bool, size: &Sizer, content: impl Draw) -> impl Draw { + let wh = east!(size.w() as usize, "x", size.h() as usize); + size.of(above(when(show, fg(Green, wh).align_se().full_wh()), content)) +} + fn view_scene_bg ( theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize ) -> (Color, Color) { diff --git a/src/device/arrange/port.rs b/src/device/arrange/port.rs index 38ae3997..ffe9398a 100644 --- a/src/device/arrange/port.rs +++ b/src/device/arrange/port.rs @@ -24,13 +24,13 @@ pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T]) -> impl Draw + use<'a, T> { - let ins = ports.len() as u16; + let ins = ports.len() as u16; let frame = Outer(true, Style::default().fg(g(96))); - let names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!( - " {index} {}", port.port_name() - ).align_w().full_h())); - let field = field_v(theme, title, names); - border(true, frame, field.exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins) + border(true, frame, field_v(theme, title, iter_south({ + move||ports.iter().enumerate().map(|(index, port)|{ + east!(" ", index, " ", port.port_name()).align_w().full_h() + }) + })).exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins) } pub fn view_io_ports <'a, T: PortsSizes<'a>> ( diff --git a/src/device/arrange/scene.rs b/src/device/arrange/scene.rs index 87fdac3f..7d77d177 100644 --- a/src/device/arrange/scene.rs +++ b/src/device/arrange/scene.rs @@ -207,13 +207,9 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + let select = self.selection(); let editor = self.editor(); let editing = self.is_editing(); - draw(move |to: &mut Tui|{ - for (index, scene, ..) in self.scenes_with_sizes() { - view_scene_name(select, editor, index, scene, editing).draw(to)?; - } - Ok(Some(XYWH(1, 1, 1, 1))) - }) - .exact_w(20) + iter_south(move||self.scenes_with_sizes().map(move|(index, scene, ..)|{ + view_scene_name(select, editor, index, scene, editing) + })) } fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { @@ -259,6 +255,30 @@ impl ScenesView for Arrangement { } } +pub fn view_scene_name <'a> ( + select: &Selection, + editor: Option<&'a MidiEditor>, + index: usize, + scene: &Scene, + editing: bool +) -> impl Draw { + let h = if select.scene() == Some(index) && let Some(_editor) = editor { + 7 + } else { + Scene::DEFAULT_HEIGHT as u16 + }; + let a = east!("·s", index, " ", fg(g(255), bold(true, &scene.name))).align_w().full_w(); + let b = when(select.scene() == Some(index) && editing, south( + editor.as_ref().map(|e|e.clip_status()), + editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh()); + let c = if select.scene() == Some(index) { + scene.color.light.term + } else { + scene.color.base.term + }; + bg(c, south(a, b).align_nw()).exact_wh(20, h) +} + pub trait HasSceneScroll: HasScenes { fn scene_scroll (&self) -> usize; } @@ -274,28 +294,3 @@ impl HasSceneScroll for App { self.project.scene_scroll() } } - -pub fn view_scene_name <'a> ( - select: &Selection, - editor: Option<&'a MidiEditor>, - index: usize, - scene: &Scene, - editing: bool -) -> impl Draw { - let h = if select.scene() == Some(index) && let Some(_editor) = editor { - 7 - } else { - Scene::DEFAULT_HEIGHT as u16 - }; - let a = east(format!("·s{index:02} "), - fg(g(255), bold(true, &scene.name))).align_w().full_w(); - let b = when(select.scene() == Some(index) && editing, south( - editor.as_ref().map(|e|e.clip_status()), - editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh()); - let c = if select.scene() == Some(index) { - scene.color.light.term - } else { - scene.color.base.term - }; - bg(c, south(a, b).align_nw()).exact_wh(20, h) -} diff --git a/src/device/arrange/track.rs b/src/device/arrange/track.rs index b7a0a5ce..d9dc1ebf 100644 --- a/src/device/arrange/track.rs +++ b/src/device/arrange/track.rs @@ -433,8 +433,9 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra view_track_row_section(theme, self.view_track_input_count(), self.view_track_input_add(), - bg(theme.darker.term, - iter_east(move||self.tracks_with_sizes().map(move|(index, track, _x1, _x2)|{ + bg(theme.darker.term, iter_east(move||self + .tracks_with_sizes() + .map(move|(index, track, _x1, _x2)|{ south( bg(track.color.base.term, east!( diff --git a/src/tek.edn b/src/tek.edn index bf204e91..37f3a9b4 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -34,17 +34,16 @@ (mode :scene (keys :scene)) (mode :mix (keys :mix)) (view - (bsp/n (bg (g 10) (bsp/e :transport :status)) - (bsp/w (bg (g 20) (exact/w 4 (align/ne :meters/output))) - (bsp/e (bg (g 30) (exact/w 4 (align/nw :meters/input))) - (full/xy (align/c (max/wh 80 80 - (bsp/s (bg (g 40) (exact/h 4 :tracks/outputs)) - (bsp/s (bg (g 60) (exact/h 4 :tracks/devices)) - (bsp/s (bg (g 50) (exact/h 2 :tracks/names)) - (bsp/s (either :mode/editor - (bg (g 80) (bsp/e :scenes/names :editor)) - (bg (g 90) :scenes)) - (bg (g 70) (exact/h 4 :tracks/inputs)))))))))))))) + (bsp/n (bsp/e :transport :status) + (bar/w 2 (align/ne :meters/output) + (bar/e 2 (align/nw :meters/input) + (full/xy (pad/xy 2 1 (align/c + (bar/s 2 :tracks/outputs + (bar/s 2 :tracks/devices + (bar/s 2 :tracks/names + (bar/n 2 :tracks/inputs + (bar/e 16 (bg (g 50) :scenes/names) + (align/c :scenes/clips)))))))))))))) (keys :clock (@space clock/toggle 0) (@shift/space clock/toggle 0)) diff --git a/src/tek.rs b/src/tek.rs index 5f123387..ee414d61 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -2,31 +2,12 @@ //#![feature(impl_trait_in_assoc_type)] // Used by EntriesIterator; TODO remove mod deps; pub use self::deps::*; -/// Banner. -pub(crate) const HEADER: &'static str = r#" -~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ - █ █▀ █▀▀▄ ~ heatwave is the new darkwave ~ - ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; - -pub fn show_version () { - println!("versions aint real man"); -} - /// Command-line entrypoint. #[allow(unused)] #[hotpath::main] fn main () -> Usually<()> { - #[cfg(feature = "prof")] let _flame_guard = { - use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; - //let tracy = tracing_tracy::TracyLayer::default(); - let (flame, _guard) = tracing_flame::FlameLayer::with_file("./tracing.folded").unwrap(); - //let registry = tracing_subscriber::registry().with(flame).init(); - //tracing::subscriber::set_global_default(registry)?; - tracing::subscriber::set_global_default(tracing_subscriber::registry().with(flame))?; - _guard - }; tengri::Tui::setup_panic(); - #[cfg(feature = "cli")] let outcome = Config::watched(crate::cli::run_with_config); + #[cfg(feature = "cli")] let outcome = Config::watched(run_with_config); #[cfg(not(feature = "cli"))] let outcome = Config::watched(run_new_plain); if let Err(e) = outcome { println!("{e:#?}"); @@ -35,209 +16,8 @@ fn main () -> Usually<()> { Ok(()) } -#[cfg(not(feature = "cli"))] -fn run_new_plain (config: Config) -> Usually<()> { - let name = "tek"; - tengri::Tui::run_main(Jack::new_run(name, move|jack|{ - let mode = ":menu"; - let title = "untitled!"; - let bpm = 74.; - let clock = Clock::new(&jack, Some(bpm))?; - let tracks = []; - let scenes = []; - Ok(App::new(None, Arrangement::new( - &jack, - title.into(), - clock, - tracks.into_iter(), - scenes.into_iter(), - connect_midi_ins(&jack, &"M", &[], None)?.into_iter(), - connect_midi_outs(&jack, &"M", &[], None)?.into_iter(), - [].into_iter().chain(connect_audio_ins(&jack, &"L", &[], None)?.into_iter()) - .chain(connect_audio_ins(&jack, &"R", &[], None)?.into_iter()), - [].into_iter().chain(connect_audio_outs(&jack, &"L", &[], None)?.into_iter()) - .chain(connect_audio_outs(&jack, &"R", &[], None)?.into_iter()), - ), config, mode)) - })?) -} - - //pub fn tui ( - //app: Arc>, - //jack: Jack, - //sync_lead: &bool, - //sync_follow: &bool, - //) -> Usually<()> { - //// Run the [Tui] and [Jack] threads with the [App] state. - //Tui::run_main(&jack.run(move|jack|{ - //// Between jack init and app's first cycle: - ////jack.sync_lead(*sync_lead, |mut state|{ - ////let clock = app.try_write().unwrap().clock(); - ////clock.playhead.update_from_sample(state.position.frame() as f64); - ////state.position.bbt = Some(clock.bbt()); - ////state.position - ////})?; - ////jack.sync_follow(*sync_follow)?; - //// FIXME: They don't work properly. - //Ok(app) - //})?)? - //} - -#[cfg(feature = "cli")] pub mod cli { - use crate::*; - - pub fn run_with_config (config: Arc) -> Usually<()> { - Cli::parse().run(Some(config)) - } - - /// Command-line configuration. - impl Cli { - pub fn run (&self, mut config: Option>) -> Usually<()> { - if config.is_none() { - config = Some(Config::init_new(None)?); - } - self.action.run(config.unwrap()) - } - } - - /// The command-line interface descriptor. - /// - /// ``` - /// let cli: tek::cli::Cli = Default::default(); - /// - /// use clap::CommandFactory; - /// tek::cli::Cli::command().debug_assert(); - /// ``` - #[derive(Parser, Debug, Default)] - #[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))] - pub struct Cli { - /// Pre-defined configuration modes. - /// - /// TODO: Replace these with scripted configurations. - #[command(subcommand)] pub action: Action, - } - impl Action { - fn run (&self, config: Arc) -> Usually<()> { - use Action::*; - match self { - Version => show_version(), - Config => config.print(), - Resume => todo!("resume session"), - List => todo!("list sessions"), - New(sesh) => Exit::run(|exit|Tui::run_main( - exit.clone(), - Arc::new(RwLock::new(App::new( - Some(exit), - sesh.init()?, - config, - ":menu" - ))))).map(|_|())?, - _ => todo!() - } - Ok(()) - } - } - /// Application modes that can be passed to the mommand line interface. - /// - /// ``` - /// let action: tek::cli::Action = Default::default(); - /// ``` - #[derive(Debug, Clone, Subcommand, Default)] - pub enum Action { - /// Continue where you left off - #[default] Resume, - /// Run headlessly in current session. - Headless, - /// Show status of current session. - Status, - /// List known sessions. - List, - /// Continue work in a copy of the current session. - Fork, - /// Create a new empty session. - New(ProjectInit), - /// Import media as new session. - Import, - /// Show configuration. - Config, - /// Show version. - Version, - } - #[derive(Debug, Clone, Parser, Default)] - pub struct ProjectInit { - /// Name of JACK client - #[arg(short='n', long)] name: Option, - /// Whether to attempt to become transport master - #[arg(short='Y', long, default_value_t = false)] sync_lead: bool, - /// Whether to sync to external transport master - #[arg(short='y', long, default_value_t = true)] sync_follow: bool, - /// Initial tempo in beats per minute - #[arg(short='b', long, default_value = None)] bpm: Option, - /// Whether to include a transport toolbar (default: true) - #[arg(short='c', long, default_value_t = true)] show_clock: bool, - /// MIDI outs to connect to (multiple instances accepted) - #[arg(short='I', long)] midi_from: Vec, - /// MIDI outs to connect to (multiple instances accepted) - #[arg(short='i', long)] midi_from_re: Vec, - /// MIDI ins to connect to (multiple instances accepted) - #[arg(short='O', long)] midi_to: Vec, - /// MIDI ins to connect to (multiple instances accepted) - #[arg(short='o', long)] midi_to_re: Vec, - /// Audio outs to connect to left input - #[arg(short='l', long)] left_from: Vec, - /// Audio outs to connect to right input - #[arg(short='r', long)] right_from: Vec, - /// Audio ins to connect from left output - #[arg(short='L', long)] left_to: Vec, - /// Audio ins to connect from right output - #[arg(short='R', long)] right_to: Vec, - /// Tracks to creat - #[arg(short='t', long)] tracks: Option, - /// Scenes to create - #[arg(short='s', long)] scenes: Option, - } - impl ProjectInit { - pub fn init (&self) -> Usually { - let Self { - name, bpm, tracks, scenes, - sync_lead: _, sync_follow: _, - left_from, right_from, midi_from, midi_from_re, - left_to, right_to, midi_to, midi_to_re, - .. - } = self; - let name = name.as_ref().map_or("tek", |x|x.as_str()); - let jack = Jack::new(&name)?; - let mut proj = Arrangement::new( - &jack, - name.into(), - Clock::new(&jack, *bpm)?, - [].into_iter(), - [].into_iter(), - connect_midi_ins( - &jack, &"M".to_string(), midi_from.as_ref(), Some(midi_from_re) - )?.into_iter(), - connect_midi_outs( - &jack, &"M".to_string(), midi_to.as_ref(), Some(midi_to_re) - )?.into_iter(), - [].into_iter() - .chain( - connect_audio_ins(&jack, &"L".to_string(), &left_from, None)?.into_iter() - ) - .chain( - connect_audio_ins(&jack, &"R".to_string(), &right_from, None)?.into_iter() - ), - [].into_iter() - .chain( - connect_audio_outs(&jack, &"L".to_string(), &left_to, None)?.into_iter() - ) - .chain( - connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter() - )); - proj.tracks_add_many(tracks.unwrap_or(0), None, [].into(), [].into())?; - proj.scenes_add_many(scenes.unwrap_or(0))?; - Ok(proj) - } - } -} +pub use self::cli::*; +mod cli; pub use self::config::*; mod config; @@ -326,7 +106,11 @@ mod app { /// Contains the currently edited musical arrangement pub project: Arrangement, /// Error, if any - pub error: Arc>>> + pub error: Arc>>>, + + #[cfg(feature = "prof2")] + /// Tracing guard + pub guard: Option>>, } impl App { @@ -501,7 +285,7 @@ mod bind { use crate::*; tui_keys!(self: App, input { - #[cfg(feature = "prof")] profiling::scope!("App::tui_keys!"); + #[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!"); let name = self.mode.as_ref(); let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone); if let Some(mode) = mode { @@ -907,12 +691,12 @@ mod draw { /// Then, every top-level form of the DSL description is rendered. impl Draw for App { fn draw (&self, to: &mut Tui) -> Drawn { - #[cfg(feature = "prof")] profiling::scope!("App::draw"); + #[cfg(feature = "prof2")] profiling::scope!("App::draw"); //self.perf.cycle(&mut |_|{ self.draw_error(to)?; self.draw_modes(to)?; self.draw_debug(to)?; - #[cfg(feature = "prof")] profiling::finish_frame!(); + #[cfg(feature = "prof2")] profiling::finish_frame!(); Ok(Some(to.area().into())) //}) } @@ -1116,33 +900,3 @@ mod draw { } } - -pub fn print_status (project: &Arrangement) { - println!("Name: {:?}", &project.name); - println!("JACK: {:?}", &project.jack); - println!("Buffer: {:?}", &project.clock.chunk); - println!("Sample rate: {:?}", &project.clock.timebase.sr); - println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); - println!("Tempo: {:?}", &project.clock.timebase.bpm); - println!("Quantize: {:?}", &project.clock.quant); - println!("Launch: {:?}", &project.clock.sync); - println!("Playhead: {:?}us", &project.clock.playhead.usec); - println!("Playhead: {:?}s", &project.clock.playhead.sample); - println!("Playhead: {:?}p", &project.clock.playhead.pulse); - println!("Started: {:?}", &project.clock.started); - println!("Tracks:"); - for (i, t) in project.tracks.iter().enumerate() { - println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, - &t.sequencer.play_clip, &t.sequencer.next_clip); - } - println!("Scenes:"); - for (i, t) in project.scenes.iter().enumerate() { - println!(" Scene {i}: {} {:?}", &t.name, &t.clips); - } - println!("MIDI Ins: {:?}", &project.midi_ins); - println!("MIDI Outs: {:?}", &project.midi_outs); - println!("Audio Ins: {:?}", &project.audio_ins); - println!("Audio Outs: {:?}", &project.audio_outs); - // TODO git integration - // TODO dawvert integration -} diff --git a/tengri b/tengri index 7f067b84..b318d498 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 7f067b848fb504feb312033bf0a485bb2ccacef6 +Subproject commit b318d498e544fa65bbf3a3ff4dabde166352b045