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 = 16)] scenes: usize, /// Number of tracks #[arg(short = 'x', long, default_value_t = 12)] tracks: usize, /// Width of tracks #[arg(short = 'w', long, default_value_t = 15)] 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 empty = &[] as &[&str]; let mut midi_ins = vec![]; let mut midi_outs = vec![]; let mut tracks = vec![]; let mut scenes = vec![]; 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 = Arc::new(RwLock::new(MidiClip::new( "Clip", true, 384usize, None, Some(ItemColor::random().into())), )); Tui::new()?.run(&Jack::new(name)?.run(|jack|{ 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 = Configuration::new(&match self.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!("{:?}", self.mode), }, false)?; let clock = Clock::new(jack, self.bpm)?; match self.mode { LaunchMode::Sequencer => tracks.push(Track::new( &name, None, jack, Some(&clock), Some(&clip), midi_froms.as_slice(), midi_tos.as_slice() )?), LaunchMode::Groovebox | LaunchMode::Sampler => tracks.push(Track::new_with_sampler( &name, None, jack, Some(&clock), Some(&clip), midi_froms.as_slice(), midi_tos.as_slice(), audio_froms, audio_tos, )?), _ => {} } let mut app = App { jack: jack.clone(), config, color: ItemTheme::random(), pool: match self.mode { LaunchMode::Sequencer | LaunchMode::Groovebox => (&clip).into(), _ => Default::default() }, project: Arrangement { name: Default::default(), color: ItemTheme::random(), jack: jack.clone(), clock, tracks, scenes, selection: Selection::TrackClip { track: 0, scene: 0 }, midi_ins, midi_outs, editor: match self.mode { LaunchMode::Sequencer | LaunchMode::Groovebox => Some((&clip).into()), _ => None }, ..Default::default() }, ..Default::default() }; if let LaunchMode::Arranger { scenes, tracks, track_width, .. } = self.mode { app.project.arranger = Default::default(); app.project.selection = Selection::TrackClip { track: 1, scene: 1 }; app.project.scenes_add(scenes)?; app.project.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 _ = App::new_clock(&jack, None, false, false, &[], &[]); //let _ = App::new_sequencer(&jack, None, false, false, &[], &[]); //let _ = App::new_groovebox(&jack, None, false, false, &[], &[], &[&[], &[]], &[&[], &[]]); //let _ = App::new_arranger(&jack, None, false, false, &[], &[], &[&[], &[]], &[&[], &[]], 0, 0, 0); }