use std::sync::{Arc, RwLock}; use tek::*; #[allow(unused_imports)] use clap::{self, Parser, Subcommand, ValueEnum}; #[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct TekCli { /// Which app to initialize #[command(subcommand)] mode: TekMode, /// 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 TekMode { /// 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 = 12)] scenes: usize, /// Number of tracks #[arg(short = 'x', long, default_value_t = 8)] tracks: usize, /// Width of tracks #[arg(short = 'w', long, default_value_t = 8)] track_width: usize, }, /// TODO: A MIDI-controlled audio mixer Mixer, /// TODO: A customizable channel strip Track, /// TODO: An audio plugin host Plugin, } /// Application entrypoint. pub fn main () -> Usually<()> { let cli = TekCli::parse(); let name = cli.name.as_ref().map_or("tek", |x|x.as_str()); let color = ItemPalette::random(); let jack = JackConnection::new(name)?; let engine = Tui::new()?; let empty = &[] as &[&str]; let midi_froms = PortConnection::collect(&cli.midi_from, &cli.midi_from_re, empty); let midi_tos = PortConnection::collect(&cli.midi_to, &cli.midi_to_re, empty); let left_froms = PortConnection::collect(&cli.left_from, empty, empty); let left_tos = PortConnection::collect(&cli.left_to, empty, empty); let right_froms = PortConnection::collect(&cli.right_from, empty, empty); let right_tos = PortConnection::collect(&cli.right_to, empty, empty); let perf = PerfModel::default(); let size = Measure::new(); let default_clip = ||Arc::new(RwLock::new(MidiClip::new( "Clip", true, 384usize, None, Some(ItemColor::random().into())))); let default_player = |jack: &Arc>, clip: Option<&Arc>>| MidiPlayer::new(&jack, name, clip, &midi_froms, &midi_tos); let default_sampler = |jack: &Arc>| Sampler::new(jack, &"sampler", &midi_froms, &[&left_froms, &right_froms], &[&left_tos, &right_tos]); let default_bpm = |clock: Clock|{ if let Some(bpm) = cli.bpm { clock.timebase.bpm.set(bpm); } clock }; let default_clock = |jack: &Arc>|{ let clock = Clock::from(jack); default_bpm(clock) }; // TODO: enable sync master/follow //let sync_clock = |jack: &Arc>, app|{ //if cli.sync_lead { //jack.read().unwrap().client().register_timebase_callback(false, |mut state|{ //app.clock().playhead.update_from_sample(state.position.frame() as f64); //state.position.bbt = Some(app.clock().bbt()); //state.position //}) //} else if cli.sync_follow { //jack.read().unwrap().client().register_timebase_callback(false, |state|{ //app.clock().playhead.update_from_sample(state.position.frame() as f64); //state.position //}) //} else { //Ok(()) //} //}; Ok(match cli.mode { TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui { jack: jack.clone(), clock: default_clock(jack), }))?)?, TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({ let clip = default_clip(); let mut player = default_player(jack, Some(&clip))?; player.clock = default_bpm(player.clock); Sequencer { _jack: jack.clone(), player, pool: (&clip).into(), editor: (&clip).into(), midi_buf: vec![vec![];65536], note_buf: vec![], status: true, perf, size, compact: true, transport: true, selectors: true, } }))?)?, TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok( SamplerTui { cursor: (0, 0), editing: None, mode: None, note_lo: 36.into(), note_pt: 36.into(), state: default_sampler(jack)?, color, size, } ))?)?, TekMode::Groovebox => engine.run(&jack.activate_with(|jack|Ok({ let clip = default_clip(); let mut player = default_player(jack, Some(&clip))?; player.clock = default_bpm(player.clock); let sampler = default_sampler(jack)?; jack.connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?; let app = Groovebox { _jack: jack.clone(), player, sampler, pool: (&clip).into(), editor: (&clip).into(), midi_buf: vec![vec![];65536], note_buf: vec![], perf, size, compact: true, status: true, }; app }))?)?, TekMode::Arranger { scenes, tracks, track_width, .. } => engine.run(&jack.activate_with(|jack|Ok({ let clip = default_clip(); let clock = default_clock(jack); let mut app = Arranger { jack: jack.clone(), clock, pool: (&clip).into(), editor: (&clip).into(), selected: ArrangerSelection::Clip(0, 0), scenes: vec![], tracks: vec![], splits: [12, 20], midi_buf: vec![vec![];65536], note_buf: vec![], compact: false, editing: true, color, perf, size, }; app.tracks_add(tracks, track_width, &midi_froms, &midi_tos)?; app.scenes_add(scenes)?; app }))?)?, _ => todo!() }) } #[test] fn verify_cli () { use clap::CommandFactory; TekCli::command().debug_assert(); }