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(version, about = Some(HEADER), long_about = Some(HEADER))] pub struct Cli { /// Which app to initialize #[command(subcommand)] mode: Mode, /// 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, } #[derive(Debug, Clone, Subcommand)] pub enum Mode { /// A standalone transport clock. Clock, /// A MIDI sequencer. Sequencer, /// A MIDI-controlled audio sampler. Sampler, /// Sequencer and sampler together.12 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 { Mode::Sequencer | Mode::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 mut app = Tek { jack: jack.clone(), view: SourceIter(match mode { Mode::Clock => include_str!("./edn/transport.edn"), Mode::Sequencer => include_str!("./edn/sequencer.edn"), Mode::Groovebox => include_str!("./edn/groovebox.edn"), Mode::Arranger { .. } => include_str!("./edn/arranger.edn"), _ => todo!("{mode:?}"), }), pool: match mode { Mode::Sequencer | Mode::Groovebox => clip.as_ref().map(Into::into), Mode::Arranger { .. } => Some(Default::default()), _ => None, }, editor: match mode { Mode::Sequencer | Mode::Groovebox => clip.as_ref().map(Into::into), Mode::Arranger { .. } => Some(Default::default()), _ => None }, midi_ins, midi_outs, midi_buf: match mode { Mode::Clock => vec![], Mode::Sequencer | Mode::Groovebox | Mode::Arranger {..} => vec![vec![];65536], _ => todo!("{mode:?}"), }, color: ItemPalette::random(), clock: Clock::new(jack, self.bpm)?, keys: SourceIter(include_str!("./edn/arranger_keys.edn")), keys_clip: SourceIter(include_str!("./edn/arranger_keys_clip.edn")), keys_track: SourceIter(include_str!("./edn/arranger_keys_track.edn")), keys_scene: SourceIter(include_str!("./edn/arranger_keys_scene.edn")), keys_mix: SourceIter(include_str!("./edn/arranger_keys_mix.edn")), tracks: match mode { Mode::Sequencer => vec![Track::default()], Mode::Groovebox => vec![Track { devices: vec![ Sampler::new( jack, &"sampler", midi_froms.as_slice(), audio_froms, audio_tos )?.boxed() ], ..Track::default() }], _ => vec![] }, scenes, ..Default::default() }; if let &Mode::Arranger { scenes, tracks, track_width, .. } = mode { app.arranger = Default::default(); app.selected = Selection::Clip(1, 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) })?) } } 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); }