diff --git a/app/src/cli.rs b/app/src/cli.rs deleted file mode 100644 index 38b52c9e..00000000 --- a/app/src/cli.rs +++ /dev/null @@ -1,221 +0,0 @@ -use crate::*; -use clap::{self, Parser, Subcommand}; -const HEADER: &'static str = r#" - -░▒▓████████▓▒░▒▓███████▓▒░▒▓█▓▒░░▒▓█▓▒░░ -░░░░▒▓█▓▒░░░░░▒▓█▓▒░░░░░░░▒▓█▓▒░▒▓█▓▒░░░ -░░░░▒▓█▓▒░░░░░▒▓█████▓▒░░░▒▓██████▓▒░░░░ -░░░░▒▓█▓▒░░░░░▒▓█▓▒░░░░░░░▒▓█▓▒░▒▓█▓▒░░░ -░░░░▒▓█▓▒░░░░░▒▓█▓▒░░░░░░░▒▓█▓▒░░▒▓█▓▒░░ -░░░░▒▓█▓▒░░░░░▒▓███████▓▒░▒▓█▓▒░░▒▓█▓▒░░"#; - -#[derive(Debug, Parser)] -#[command(version, about = Some(HEADER), long_about = Some(HEADER))] -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 = 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 TekCli { - pub fn run (&self) -> Usually<()> { - let name = self.name.as_ref().map_or("tek", |x|x.as_str()); - //let color = ItemPalette::random(); - let jack = Jack::new(name)?; - let engine = Tui::new()?; - 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 jack = jack.run(|jack|match self.mode { - TekMode::Clock => Tek::new_clock( - jack, self.bpm, self.sync_lead, self.sync_follow, - &midi_froms, &midi_tos), - TekMode::Sequencer => Tek::new_sequencer( - jack, self.bpm, self.sync_lead, self.sync_follow, - &midi_froms, &midi_tos), - TekMode::Groovebox => Tek::new_groovebox( - jack, self.bpm, self.sync_lead, self.sync_follow, - &midi_froms, &midi_tos, - &audio_froms, &audio_tos), - TekMode::Arranger { scenes, tracks, track_width, .. } => Tek::new_arranger( - jack, self.bpm, self.sync_lead, self.sync_follow, - &midi_froms, &midi_tos, - &audio_froms, &audio_tos, - scenes, tracks, track_width), - _ => todo!() - })?; - engine.run(&jack) - } -} -impl Tek { - pub fn new_clock ( - jack: &Jack, - bpm: Option, sync_lead: bool, sync_follow: bool, - midi_froms: &[PortConnect], midi_tos: &[PortConnect], - ) -> Usually { - let tek = Self { - view: SourceIter(include_str!("../edn/view_transport.edn")), - jack: jack.clone(), - color: ItemPalette::random(), - clock: Clock::new(jack, bpm)?, - midi_ins: { - let mut midi_ins = vec![]; - for (index, connect) in midi_froms.iter().enumerate() { - let port = JackMidiIn::new(jack, &format!("M/{index}"), &[connect.clone()])?; - midi_ins.push(port); - } - midi_ins - }, - midi_outs: { - let mut midi_outs = vec![]; - for (index, connect) in midi_tos.iter().enumerate() { - let port = JackMidiOut::new(jack, &format!("{index}/M"), &[connect.clone()])?; - midi_outs.push(port); - } - midi_outs - }, - keys: SourceIter(KEYS_APP), - keys_clip: SourceIter(KEYS_CLIP), - keys_track: SourceIter(KEYS_TRACK), - keys_scene: SourceIter(KEYS_SCENE), - keys_mix: SourceIter(KEYS_MIX), - tracks: vec![], - scenes: vec![], - ..Default::default() - }; - jack.sync_lead(sync_lead, |mut state|{ - let clock = tek.clock(); - clock.playhead.update_from_sample(state.position.frame() as f64); - state.position.bbt = Some(clock.bbt()); - state.position - }); - jack.sync_follow(sync_follow); - Ok(tek) - } - pub fn new_sequencer ( - jack: &Jack, - bpm: Option, sync_lead: bool, sync_follow: bool, - midi_froms: &[PortConnect], midi_tos: &[PortConnect], - ) -> Usually { - let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into())); - let clip = Arc::new(RwLock::new(clip)); - let this = Self::new_clock(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)?; - Ok(Self { - view: SourceIter(include_str!("../edn/view_sequencer.edn")), - pool: Some((&clip).into()), - editor: Some((&clip).into()), - editing: false.into(), - midi_buf: vec![vec![];65536], - tracks: vec![Track::default()], - //player: Some(MidiPlayer::new("sequencer", &jack, Some(&this.clock), Some(&clip), &midi_froms, &midi_tos)?), - ..this - }) - } - pub fn new_groovebox ( - jack: &Jack, - bpm: Option, sync_lead: bool, sync_follow: bool, - midi_froms: &[PortConnect], midi_tos: &[PortConnect], - audio_froms: &[&[PortConnect];2], audio_tos: &[&[PortConnect];2], - ) -> Usually { - let tek = Self { - view: SourceIter(include_str!("../edn/view_groovebox.edn")), - tracks: vec![Track { - devices: vec![Sampler::new(jack, &"sampler", midi_froms, audio_froms, audio_tos)?.boxed()], - ..Track::default() - }], - ..Self::new_sequencer(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)? - }; - //if let Some(sampler) = tek.sampler.as_ref().unwrap().midi_in.as_ref() { - //tek.player.as_ref().unwrap().midi_outs[0].connect_to(sampler.port())?; - //} - Ok(tek) - } - pub fn new_arranger ( - jack: &Jack, - bpm: Option, sync_lead: bool, sync_follow: bool, - midi_froms: &[PortConnect], midi_tos: &[PortConnect], - audio_froms: &[&[PortConnect];2], audio_tos: &[&[PortConnect];2], - scenes: usize, tracks: usize, track_width: usize, - ) -> Usually { - let mut tek = Self { - view: SourceIter(include_str!("../edn/view_arranger.edn")), - pool: Some(Default::default()), - editor: Some(Default::default()), - editing: false.into(), - midi_buf: vec![vec![];65536], - tracks: vec![], - scenes: vec![], - ..Self::new_clock(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)? - }; - tek.arranger = Default::default(); - tek.selected = Selection::Clip(1, 1); - tek.scenes_add(scenes); - tek.tracks_add(tracks, Some(track_width), &[], &[]); - Ok(tek) - } -} -#[cfg(test)] #[test] fn test_tek_cli () { - use clap::CommandFactory; - TekCli::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); -} diff --git a/app/src/lib.rs b/app/src/lib.rs index f7d8947e..84166cc8 100644 --- a/app/src/lib.rs +++ b/app/src/lib.rs @@ -14,7 +14,6 @@ #![feature(type_alias_impl_trait)] #![feature(trait_alias)] #![feature(type_changing_struct_update)] -mod cli; pub use self::cli::*; mod audio; pub use self::audio::*; mod device; pub use self::device::*; mod keys; pub use self::keys::*; diff --git a/app/src/model.rs b/app/src/model.rs index e076e0c8..ec462a13 100644 --- a/app/src/model.rs +++ b/app/src/model.rs @@ -148,6 +148,129 @@ impl Tek { Ok(()) } } +impl Tek { + pub fn new_clock ( + jack: &Jack, + bpm: Option, + sync_lead: bool, + sync_follow: bool, + midi_froms: &[PortConnect], + midi_tos: &[PortConnect], + ) -> Usually { + let tek = Self { + view: SourceIter(include_str!("../edn/view_transport.edn")), + jack: jack.clone(), + color: ItemPalette::random(), + clock: Clock::new(jack, bpm)?, + midi_ins: { + let mut midi_ins = vec![]; + for (index, connect) in midi_froms.iter().enumerate() { + let port = JackMidiIn::new(jack, &format!("M/{index}"), &[connect.clone()])?; + midi_ins.push(port); + } + midi_ins + }, + midi_outs: { + let mut midi_outs = vec![]; + for (index, connect) in midi_tos.iter().enumerate() { + let port = JackMidiOut::new(jack, &format!("{index}/M"), &[connect.clone()])?; + midi_outs.push(port); + } + midi_outs + }, + keys: SourceIter(KEYS_APP), + keys_clip: SourceIter(KEYS_CLIP), + keys_track: SourceIter(KEYS_TRACK), + keys_scene: SourceIter(KEYS_SCENE), + keys_mix: SourceIter(KEYS_MIX), + tracks: vec![], + scenes: vec![], + ..Default::default() + }; + jack.sync_lead(sync_lead, |mut state|{ + let clock = tek.clock(); + clock.playhead.update_from_sample(state.position.frame() as f64); + state.position.bbt = Some(clock.bbt()); + state.position + }); + jack.sync_follow(sync_follow); + Ok(tek) + } + pub fn new_sequencer ( + jack: &Jack, + bpm: Option, + sync_lead: bool, + sync_follow: bool, + midi_froms: &[PortConnect], + midi_tos: &[PortConnect], + ) -> Usually { + let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into())); + let clip = Arc::new(RwLock::new(clip)); + let this = Self::new_clock(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)?; + Ok(Self { + view: SourceIter(include_str!("../edn/view_sequencer.edn")), + pool: Some((&clip).into()), + editor: Some((&clip).into()), + editing: false.into(), + midi_buf: vec![vec![];65536], + tracks: vec![Track::default()], + //player: Some(MidiPlayer::new("sequencer", &jack, Some(&this.clock), Some(&clip), &midi_froms, &midi_tos)?), + ..this + }) + } + pub fn new_groovebox ( + jack: &Jack, + bpm: Option, + sync_lead: bool, + sync_follow: bool, + midi_froms: &[PortConnect], + midi_tos: &[PortConnect], + audio_froms: &[&[PortConnect];2], + audio_tos: &[&[PortConnect];2], + ) -> Usually { + let tek = Self { + view: SourceIter(include_str!("../edn/view_groovebox.edn")), + tracks: vec![Track { + devices: vec![Sampler::new(jack, &"sampler", midi_froms, audio_froms, audio_tos)?.boxed()], + ..Track::default() + }], + ..Self::new_sequencer(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)? + }; + //if let Some(sampler) = tek.sampler.as_ref().unwrap().midi_in.as_ref() { + //tek.player.as_ref().unwrap().midi_outs[0].connect_to(sampler.port())?; + //} + Ok(tek) + } + pub fn new_arranger ( + jack: &Jack, + bpm: Option, + sync_lead: bool, + sync_follow: bool, + midi_froms: &[PortConnect], + midi_tos: &[PortConnect], + audio_froms: &[&[PortConnect];2], + audio_tos: &[&[PortConnect];2], + scenes: usize, + tracks: usize, + track_width: usize, + ) -> Usually { + let mut tek = Self { + view: SourceIter(include_str!("../edn/view_arranger.edn")), + pool: Some(Default::default()), + editor: Some(Default::default()), + editing: false.into(), + midi_buf: vec![vec![];65536], + tracks: vec![], + scenes: vec![], + ..Self::new_clock(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)? + }; + tek.arranger = Default::default(); + tek.selected = Selection::Clip(1, 1); + tek.scenes_add(scenes); + tek.tracks_add(tracks, Some(track_width), &[], &[]); + Ok(tek) + } +} #[cfg(test)] #[test] fn test_model () { let mut tek = Tek::default(); let _ = tek.clip(); diff --git a/cli/tek.rs b/cli/tek.rs index 854c45ea..e1ffb44d 100644 --- a/cli/tek.rs +++ b/cli/tek.rs @@ -1,10 +1,119 @@ pub use tek::*; -pub(crate) use clap::{self, Parser}; +pub(crate) use clap::{self, Parser, Subcommand}; /// Application entrypoint. pub fn main () -> Usually<()> { - TekCli::parse().run() + Cli::parse().run() } -#[test] fn verify_cli () { +#[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 color = ItemPalette::random(); + let jack = Jack::new(name)?; + let engine = Tui::new()?; + 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 jack = jack.run(|jack|match self.mode { + Mode::Clock => Tek::new_clock( + jack, self.bpm, self.sync_lead, self.sync_follow, + &midi_froms, &midi_tos), + Mode::Sequencer => Tek::new_sequencer( + jack, self.bpm, self.sync_lead, self.sync_follow, + &midi_froms, &midi_tos), + Mode::Groovebox => Tek::new_groovebox( + jack, self.bpm, self.sync_lead, self.sync_follow, + &midi_froms, &midi_tos, + &audio_froms, &audio_tos), + Mode::Arranger { scenes, tracks, track_width, .. } => Tek::new_arranger( + jack, self.bpm, self.sync_lead, self.sync_follow, + &midi_froms, &midi_tos, + &audio_froms, &audio_tos, + scenes, tracks, track_width), + _ => todo!() + })?; + engine.run(&jack) + } +} + +const HEADER: &'static str = r#" + +░▒▓████████▓▒░▒▓███████▓▒░▒▓█▓▒░░▒▓█▓▒░░ +░░░░▒▓█▓▒░░░░░▒▓█▓▒░░░░░░░▒▓█▓▒░▒▓█▓▒░░░ +░░░░▒▓█▓▒░░░░░▒▓█████▓▒░░░▒▓██████▓▒░░░░ +░░░░▒▓█▓▒░░░░░▒▓█▓▒░░░░░░░▒▓█▓▒░▒▓█▓▒░░░ +░░░░▒▓█▓▒░░░░░▒▓█▓▒░░░░░░░▒▓█▓▒░░▒▓█▓▒░░ +░░░░▒▓█▓▒░░░░░▒▓███████▓▒░▒▓█▓▒░░▒▓█▓▒░░"#; + +#[cfg(test)] #[test] fn test_cli () { use clap::CommandFactory; - TekCli::command().debug_assert(); + 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); }