use crate::*; /// Banner. pub(crate) const HEADER: &'static str = r#" ~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ █ █▀ █▀▀▄ ~ heatwave is the new darkwave ~ ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; pub fn show_version () { println!("versions aint real man"); } 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(), { #[allow(unused_mut)] let mut app = App::new(Some(exit), sesh.init()?, config, "Menu"); app.dialog = Dialog::welcome(); #[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 = Project::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: &Project) { 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); config.views.for_each(|k, v|{ println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source); }); config.binds.for_each(|k, v|{ 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) //})?)? //}