include!("./lib.rs"); use tek::Arranger; pub fn main () -> Usually<()> { ArrangerCli::parse().run() } /// Launches an interactive MIDI arranger. #[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct ArrangerCli { /// Name of JACK client #[arg(short, long)] name: Option, /// Whether to include a transport toolbar (default: true) #[arg(short, long, default_value_t = true)] transport: bool, /// Number of tracks #[arg(short = 'x', long, default_value_t = 16)] tracks: usize, /// Width of tracks #[arg(short = 'w', long, default_value_t = 6)] track_width: usize, /// Number of scenes #[arg(short, long, default_value_t = 8)] scenes: usize, /// MIDI outs to connect each track to. #[arg(short='i', long)] midi_from: Vec, /// MIDI ins to connect each track to. #[arg(short='o', long)] midi_to: Vec, } impl ArrangerCli { /// Run the arranger TUI from CLI arguments. fn run (&self) -> Usually<()> { let name = self.name.as_deref().unwrap_or("tek_arranger"); let engine = Tui::new()?; let state = JackConnection::new(name)?.activate_with(|jack|{ let mut app = Arranger::try_from(jack)?; let jack = jack.read().unwrap(); app.color = ItemPalette::random(); add_tracks(&jack, &mut app, self)?; add_scenes(&mut app, self.scenes)?; Ok(app) })?; engine.run(&state) } } fn add_tracks (jack: &JackConnection, app: &mut Arranger, cli: &ArrangerCli) -> Usually<()> { let n = cli.tracks; let track_color_1 = ItemColor::random(); let track_color_2 = ItemColor::random(); for i in 0..n { let track = app.track_add(None, Some( track_color_1.mix(track_color_2, i as f32 / n as f32).into() ))?; track.width = cli.track_width; let name = track.name.read().unwrap(); track.player.midi_ins.push( jack.register_port(&format!("{}I", &name), MidiIn::default())? ); track.player.midi_outs.push( jack.register_port(&format!("{}O", &name), MidiOut::default())? ); } for connection in cli.midi_from.iter() { let mut split = connection.split("="); let number = split.next().unwrap().trim(); if let Ok(track) = number.parse::() { if track < 1 { panic!("Tracks are zero-indexed") } if track > n { panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.") } if let Some(port) = split.next() { if let Some(port) = jack.port_by_name(port).as_ref() { jack.client().connect_ports(port, &app.tracks[track-1].player.midi_ins[0])?; } else { panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); } } else { panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME") } } else { panic!("Failed to parse track number: {number}") } } for connection in cli.midi_to.iter() { let mut split = connection.split("="); let number = split.next().unwrap().trim(); if let Ok(track) = number.parse::() { if track < 1 { panic!("Tracks are zero-indexed") } if track > n { panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.") } if let Some(port) = split.next() { if let Some(port) = jack.port_by_name(port).as_ref() { jack.client().connect_ports(&app.tracks[track-1].player.midi_outs[0], port)?; } else { panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); } } else { panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME") } } else { panic!("Failed to parse track number: {number}") } } Ok(()) } fn add_scenes (app: &mut Arranger, n: usize) -> Usually<()> { let scene_color_1 = ItemColor::random(); let scene_color_2 = ItemColor::random(); for i in 0..n { let _scene = app.scene_add(None, Some( scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() ))?; } Ok(()) } #[test] fn verify_arranger_cli () { use clap::CommandFactory; ArrangerCli::command().debug_assert(); }