pub(crate) use tek::*; pub(crate) use std::sync::{Arc, RwLock}; pub(crate) use clap::{self, Parser, Subcommand}; /// Application entrypoint. pub fn main () -> Usually<()> { Cli::parse().run() } #[derive(Debug, Parser)] #[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)] mode: LaunchMode, /// Name of JACK client #[arg(short='n', long)] name: Option, /// Whether to attempt to become transport master #[arg(short='S', long, default_value_t = false)] sync_lead: bool, /// Whether to sync to external transport master #[arg(short='s', 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='t', 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, } /// Application modes #[derive(Debug, Clone, Subcommand)] pub enum LaunchMode { /// ⏯️ A standalone transport clock. Clock, /// 🎼 A MIDI sequencer. Sequencer, /// 🎺 A MIDI-controlled audio sampler. Sampler, /// 📻 Sequencer and sampler together. Groovebox, /// 🎧 Multi-track MIDI sequencer. Arranger { /// Number of scenes #[arg(short = 'y', long, default_value_t = 4)] scenes: usize, /// Number of tracks #[arg(short = 'x', long, default_value_t = 4)] tracks: usize, /// Width of tracks #[arg(short = 'w', long, default_value_t = 12)] track_width: usize, }, /// TODO: A MIDI-controlled audio mixer Mixer, /// TODO: A customizable channel strip Track, /// TODO: An audio plugin host Plugin, } impl Cli { pub fn run (&self) -> Usually<()> { let name = self.name.as_ref().map_or("tek", |x|x.as_str()); let mode = &self.mode; let empty = &[] as &[&str]; let midi_froms = PortConnect::collect(&self.midi_from, empty, &self.midi_from_re); let midi_tos = PortConnect::collect(&self.midi_to, empty, &self.midi_to_re); let left_froms = PortConnect::collect(&self.left_from, empty, empty); let left_tos = PortConnect::collect(&self.left_to, empty, empty); let right_froms = PortConnect::collect(&self.right_from, empty, empty); let right_tos = PortConnect::collect(&self.right_to, empty, empty); let audio_froms = &[left_froms.as_slice(), right_froms.as_slice()]; let audio_tos = &[left_tos.as_slice(), right_tos.as_slice()]; let clip = match mode { LaunchMode::Sequencer | LaunchMode::Groovebox => Some(Arc::new(RwLock::new(MidiClip::new( "Clip", true, 384usize, None, Some(ItemColor::random().into())), ))), _ => None, }; let scenes = vec![]; Tui::new()?.run(&Jack::new(name)?.run(|jack|{ let mut midi_ins = vec![]; let mut midi_outs = vec![]; for (index, connect) in midi_froms.iter().enumerate() { let port = JackMidiIn::new(jack, &format!("M/{index}"), &[connect.clone()])?; midi_ins.push(port); } for (index, connect) in midi_tos.iter().enumerate() { let port = JackMidiOut::new(jack, &format!("{index}/M"), &[connect.clone()])?; midi_outs.push(port); }; let config_path = match mode { LaunchMode::Clock => "config/config_transport.edn", LaunchMode::Sequencer => "config/config_sequencer.edn", LaunchMode::Groovebox => "config/config_groovebox.edn", LaunchMode::Arranger { .. } => "config/config_arranger.edn", LaunchMode::Sampler => "config/config_sampler.edn", _ => todo!("{mode:?}"), }; let config = Configuration::new(&config_path, false)?; let mut app = Tek { jack: jack.clone(), color: ItemTheme::random(), clock: Clock::new(jack, self.bpm)?, pool: match mode { LaunchMode::Sequencer | LaunchMode::Groovebox => clip.as_ref().map(Into::into), LaunchMode::Arranger { .. } => Some(Default::default()), _ => None, }, editor: match mode { LaunchMode::Sequencer | LaunchMode::Groovebox => clip.as_ref().map(Into::into), LaunchMode::Arranger { .. } => Some(Default::default()), _ => None }, midi_ins, midi_outs, midi_buf: match mode { LaunchMode::Clock | LaunchMode::Sampler => vec![], LaunchMode::Sequencer | LaunchMode::Groovebox | LaunchMode::Arranger {..} => vec![vec![];65536], _ => todo!("{mode:?}"), }, tracks: match mode { LaunchMode::Sequencer => vec![ Track::new_sequencer() ], LaunchMode::Groovebox => vec![ Track::new_groovebox(jack, midi_froms.as_slice(), audio_froms, audio_tos)? ], LaunchMode::Sampler => vec![ Track::new_sampler(jack, midi_froms.as_slice(), audio_froms, audio_tos)? ], _ => vec![] }, scenes, selected: Selection::TrackClip { track: 0, scene: 0 }, config, ..Default::default() }; if let &LaunchMode::Arranger { scenes, tracks, track_width, .. } = mode { app.arranger = Default::default(); app.selected = Selection::TrackClip { track: 1, scene: 1 }; app.scenes_add(scenes)?; app.tracks_add(tracks, Some(track_width), &[], &[])?; } jack.sync_lead(self.sync_lead, |mut state|{ let clock = app.clock(); clock.playhead.update_from_sample(state.position.frame() as f64); state.position.bbt = Some(clock.bbt()); state.position })?; jack.sync_follow(self.sync_follow)?; Ok(app) })?) } } /// CLI header const HEADER: &'static str = r#" ╓─╥─╖ ╓──╖ ╥ ╖ ║ ╟─╌ ╟─╡ ╨ ╙──╜ ╨ ╜"#; #[cfg(test)] #[test] fn test_cli () { use clap::CommandFactory; Cli::command().debug_assert(); let jack = Jack::default(); //TODO: //let _ = Tek::new_clock(&jack, None, false, false, &[], &[]); //let _ = Tek::new_sequencer(&jack, None, false, false, &[], &[]); //let _ = Tek::new_groovebox(&jack, None, false, false, &[], &[], &[&[], &[]], &[&[], &[]]); //let _ = Tek::new_arranger(&jack, None, false, false, &[], &[], &[&[], &[]], &[&[], &[]], 0, 0, 0); }