From 8e2aed58afae897117dc5899232caa6a606ca702 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 16 Jan 2025 11:37:30 +0100 Subject: [PATCH] merge cli entrypoint into main module --- Cargo.lock | 1 + cli/tek.rs | 103 +------------ tek/Cargo.toml | 4 +- tek/src/lib.rs | 402 ++++++++++++++++++++++++++++++++----------------- 4 files changed, 268 insertions(+), 242 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38c0cff8..7ab03157 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1414,6 +1414,7 @@ name = "tek" version = "0.2.0" dependencies = [ "backtrace", + "clap", "palette", "rand", "tek_jack", diff --git a/cli/tek.rs b/cli/tek.rs index c9713e69..4d47d68d 100644 --- a/cli/tek.rs +++ b/cli/tek.rs @@ -1,109 +1,8 @@ use tek::*; use clap::{self, Parser, Subcommand}; -#[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 = 1)] scenes: usize, - /// Number of tracks - #[arg(short = 'x', long, default_value_t = 1)] tracks: usize, - /// Width of tracks - #[arg(short = 'w', long, default_value_t = 9)] 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 audio_froms = &[left_froms.as_slice(), right_froms.as_slice()]; - let audio_tos = &[left_tos.as_slice(), right_tos.as_slice() ]; - Ok(match cli.mode { - TekMode::Clock => - engine.run(&jack.activate_with(|jack|App::clock( - jack, cli.bpm))?)?, - TekMode::Sequencer => - engine.run(&jack.activate_with(|jack|App::sequencer( - jack, cli.bpm, &midi_froms, &midi_tos))?)?, - TekMode::Groovebox => - engine.run(&jack.activate_with(|jack|App::groovebox( - jack, cli.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos))?)?, - TekMode::Arranger { scenes, tracks, track_width, .. } => - engine.run(&jack.activate_with(|jack|App::arranger( - jack, cli.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos, - scenes, tracks, track_width, - ))?)?, - //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(), - //size: Measure::new(), - //state: Sampler::new(jack, &"sampler", &midi_froms, - //&[&left_froms, &right_froms], - //&[&left_tos, &right_tos])?, - //color, - //} - //))?)?, - _ => todo!() - }) + TekCli::parse().run() } #[test] fn verify_cli () { use clap::CommandFactory; diff --git a/tek/Cargo.toml b/tek/Cargo.toml index 69148a37..bbb59610 100644 --- a/tek/Cargo.toml +++ b/tek/Cargo.toml @@ -15,6 +15,8 @@ backtrace = "0.3.72" palette = { version = "0.7.6", features = [ "random" ] } rand = "0.8.5" toml = "0.8.12" +clap = { optional = true, version = "4.5.4", features = [ "derive" ] } [features] -default = [] +default = ["cli"] +cli = ["clap"] diff --git a/tek/src/lib.rs b/tek/src/lib.rs index 3d31b1ca..6c5376dd 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -22,7 +22,113 @@ pub use ::tek_tui::{ Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, KeyCode::{self, *}, }, }; -#[derive(Default, Debug)] pub struct App { +use clap::{self, Parser, Subcommand}; +#[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 = 1)] scenes: usize, + /// Number of tracks + #[arg(short = 'x', long, default_value_t = 1)] tracks: usize, + /// Width of tracks + #[arg(short = 'w', long, default_value_t = 9)] 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 = JackConnection::new(name)?; + let engine = Tui::new()?; + let empty = &[] as &[&str]; + let midi_froms = PortConnection::collect(&self.midi_from, &self.midi_from_re, empty); + let midi_tos = PortConnection::collect(&self.midi_to, &self.midi_to_re, empty); + let left_froms = PortConnection::collect(&self.left_from, empty, empty); + let left_tos = PortConnection::collect(&self.left_to, empty, empty); + let right_froms = PortConnection::collect(&self.right_from, empty, empty); + let right_tos = PortConnection::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() ]; + Ok(match self.mode { + TekMode::Clock => + engine.run(&jack.activate_with(|jack|Tek::new_clock( + jack, self.bpm))?)?, + TekMode::Sequencer => + engine.run(&jack.activate_with(|jack|Tek::new_sequencer( + jack, self.bpm, &midi_froms, &midi_tos))?)?, + TekMode::Groovebox => + engine.run(&jack.activate_with(|jack|Tek::new_groovebox( + jack, self.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos))?)?, + TekMode::Arranger { scenes, tracks, track_width, .. } => + engine.run(&jack.activate_with(|jack|Tek::new_arranger( + jack, self.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos, + scenes, tracks, track_width, + ))?)?, + //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(), + //size: Measure::new(), + //state: Sampler::new(jack, &"sampler", &midi_froms, + //&[&left_froms, &right_froms], + //&[&left_tos, &right_tos])?, + //color, + //} + //))?)?, + _ => todo!() + }) + } +} +#[derive(Default, Debug)] pub struct Tek { pub jack: Arc>, pub edn: String, pub clock: Clock, @@ -45,18 +151,18 @@ pub use ::tek_tui::{ pub size: Measure, pub perf: PerfModel, pub compact: bool, - pub history: Vec, + pub history: Vec, } -has_size!(|self: App|&self.size); -has_clock!(|self: App|self.clock); -has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips); -//has_editor!(|self: App|self.editor.as_ref().expect("no editor")); -has_jack!(|self: App|&self.jack); -has_sampler!(|self: App|{ +has_size!(|self: Tek|&self.size); +has_clock!(|self: Tek|self.clock); +has_clips!(|self: Tek|self.pool.as_ref().expect("no clip pool").clips); +//has_editor!(|self: Tek|self.editor.as_ref().expect("no editor")); +has_jack!(|self: Tek|&self.jack); +has_sampler!(|self: Tek|{ sampler = self.sampler; index = self.editor.as_ref().map(|e|e.note_point()).unwrap_or(0); }); -has_editor!(|self: App|{ +has_editor!(|self: Tek|{ editor = self.editor; editor_w = { let size = self.size.w(); @@ -68,7 +174,7 @@ has_editor!(|self: App|{ editor_h = 15; is_editing = self.editing.load(Relaxed); }); -edn_provide!(# usize: |self: App| { +edn_provide!(# usize: |self: Tek| { ":scene" => 0, ":scene-next" => 0, ":scene-prev" => 0, @@ -76,7 +182,7 @@ edn_provide!(# usize: |self: App| { ":track-next" => 0, ":track-prev" => 0, }); -edn_view!(TuiOut: |self: App| self.size.of(EdnView::from_source(self, self.edn.as_ref())); { +edn_view!(TuiOut: |self: Tek| self.size.of(EdnView::from_source(self, self.edn.as_ref())); { bool {}; isize {}; Color {}; @@ -99,7 +205,7 @@ edn_view!(TuiOut: |self: App| self.size.of(EdnView::from_source(self, self.edn.a ":sample" => self.view_sample(self.is_editing()).boxed(), ":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(), ":status" => self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())).boxed(), - ":toolbar" => ClockView::new(true, &self.clock).boxed(), + ":toolbar" => self.view_clock().boxed(),//ClockView::new(true, &self.clock).boxed(), ":tracks" => self.row(self.w(), 3, self.track_header(), self.track_cells()).boxed(), ":inputs" => self.row(self.w(), 3, self.input_header(), self.input_cells()).boxed(), ":outputs" => self.row(self.w(), 3, self.output_header(), self.output_cells()).boxed(), @@ -109,8 +215,75 @@ edn_view!(TuiOut: |self: App| self.size.of(EdnView::from_source(self, self.edn.a self.scene_header(), self.scene_cells(self.is_editing()) )).boxed() }}); -impl App { - pub fn clock ( +impl Tek { + pub fn new_arranger ( + jack: &Arc>, + bpm: Option, + midi_froms: &[PortConnection], + midi_tos: &[PortConnection], + audio_froms: &[&[PortConnection];2], + audio_tos: &[&[PortConnection];2], + scenes: usize, + tracks: usize, + track_width: usize, + ) -> Usually { + let mut arranger = Self { + edn: include_str!("./view_arranger.edn").to_string(), + ..Self::new_groovebox(jack, bpm, midi_froms, midi_tos, audio_froms, audio_tos)? + }; + arranger.scenes_add(scenes); + arranger.tracks_add(tracks, track_width, &[], &[]); + Ok(arranger) + } + pub fn new_groovebox ( + jack: &Arc>, + bpm: Option, + midi_froms: &[PortConnection], + midi_tos: &[PortConnection], + audio_froms: &[&[PortConnection];2], + audio_tos: &[&[PortConnection];2], + ) -> Usually { + let app = Self { + edn: include_str!("./view_groovebox.edn").to_string(), + sampler: Some(Sampler::new( + jack, + &"sampler", + midi_froms, + audio_froms, + audio_tos + )?), + ..Self::new_sequencer(jack, bpm, midi_froms, midi_tos)? + }; + if let Some(sampler) = app.sampler.as_ref().unwrap().midi_in.as_ref() { + jack.connect_ports(&app.player.as_ref().unwrap().midi_outs[0].port, &sampler.port)?; + } + Ok(app) + } + pub fn new_sequencer ( + jack: &Arc>, + bpm: Option, + midi_froms: &[PortConnection], + midi_tos: &[PortConnection], + ) -> Usually { + let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into())); + let clip = Arc::new(RwLock::new(clip)); + Ok(Self { + edn: include_str!("./view_sequencer.edn").to_string(), + pool: Some((&clip).into()), + editor: Some((&clip).into()), + editing: false.into(), + midi_buf: vec![vec![];65536], + player: Some(MidiPlayer::new( + &jack, + "sequencer", + Some(&clip), + &midi_froms, + &midi_tos + )?), + ..Self::new_clock(jack, bpm)? + }) + } + pub fn new_clock ( jack: &Arc>, bpm: Option, ) -> Usually { @@ -139,78 +312,28 @@ impl App { ..Default::default() }) } - pub fn sequencer ( - jack: &Arc>, - bpm: Option, - midi_froms: &[PortConnection], - midi_tos: &[PortConnection], - ) -> Usually { - let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into())); - let clip = Arc::new(RwLock::new(clip)); - Ok(Self { - edn: include_str!("./view_sequencer.edn").to_string(), - pool: Some((&clip).into()), - editor: Some((&clip).into()), - editing: false.into(), - midi_buf: vec![vec![];65536], - player: Some(MidiPlayer::new( - &jack, - "sequencer", - Some(&clip), - &midi_froms, - &midi_tos - )?), - ..Self::clock(jack, bpm)? - }) - } - pub fn groovebox ( - jack: &Arc>, - bpm: Option, - midi_froms: &[PortConnection], - midi_tos: &[PortConnection], - audio_froms: &[&[PortConnection];2], - audio_tos: &[&[PortConnection];2], - ) -> Usually { - let app = Self { - edn: include_str!("./view_groovebox.edn").to_string(), - sampler: Some(Sampler::new( - jack, - &"sampler", - midi_froms, - audio_froms, - audio_tos - )?), - ..Self::sequencer(jack, bpm, midi_froms, midi_tos)? - }; - if let Some(sampler) = app.sampler.as_ref().unwrap().midi_in.as_ref() { - jack.connect_ports(&app.player.as_ref().unwrap().midi_outs[0].port, &sampler.port)?; - } - Ok(app) - } - pub fn arranger ( - jack: &Arc>, - bpm: Option, - midi_froms: &[PortConnection], - midi_tos: &[PortConnection], - audio_froms: &[&[PortConnection];2], - audio_tos: &[&[PortConnection];2], - scenes: usize, - tracks: usize, - track_width: usize, - ) -> Usually { - let mut arranger = Self { - edn: include_str!("./view_arranger.edn").to_string(), - ..Self::groovebox(jack, bpm, midi_froms, midi_tos, audio_froms, audio_tos)? - }; - arranger.scenes_add(scenes); - arranger.tracks_add(tracks, track_width, &[], &[]); - Ok(arranger) - } fn compact (&self) -> bool { self.compact } fn is_editing (&self) -> bool { self.editing.load(Relaxed) } fn editor (&self) -> impl Content + '_ { &self.editor } - fn w (&self) -> u16 { self.tracks_sizes(self.is_editing(), self.editor_w()).last().map(|x|x.3 as u16).unwrap_or(0) } - fn pool (&self) -> impl Content + use<'_> { self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))) } + fn view_clock (&self) -> impl Content + use<'_> { + Outer(Style::default().fg(TuiTheme::g(0))).enclose(row!( + OutputStats::new(self.compact, &self.clock), + " ", + PlayPause { compact: false, playing: self.clock.is_rolling() }, + " ", + BeatStats::new(self.compact, &self.clock), + )) + } + fn pool (&self) -> impl Content + use<'_> { + self.pool.as_ref() + .map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))) + } + fn w (&self) -> u16 { + self.tracks_sizes(self.is_editing(), self.editor_w()) + .last() + .map(|x|x.3 as u16) + .unwrap_or(0) + } fn sidebar_w (&self) -> u16 { let w = self.size.w(); let w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; @@ -387,11 +510,11 @@ const KEYS_CLIP: &str = include_str!("keys_clip.edn"); const KEYS_TRACK: &str = include_str!("keys_track.edn"); const KEYS_SCENE: &str = include_str!("keys_scene.edn"); const KEYS_MIX: &str = include_str!("keys_mix.edn"); -handle!(TuiIn: |self: App, input|Ok({ +handle!(TuiIn: |self: Tek, input|Ok({ use EdnItem::*; - let mut command: Option = None; + let mut command: Option = None; // Handle from root keymap - if let Some(command) = EdnKeyMapToCommand::new(KEYS_APP)?.from::<_, AppCommand>(self, input) { + if let Some(command) = EdnKeyMapToCommand::new(KEYS_APP)?.from::<_, TekCommand>(self, input) { if let Some(undo) = command.execute(self)? { self.history.push(undo); } return Ok(Some(true)) } @@ -401,7 +524,7 @@ handle!(TuiIn: |self: App, input|Ok({ Selection::Track(t) => KEYS_TRACK, Selection::Scene(s) => KEYS_SCENE, Selection::Mix => KEYS_MIX, - })?.from::<_, AppCommand>( + })?.from::<_, TekCommand>( self, input ) { if let Some(undo) = command.execute(self)? { self.history.push(undo); } @@ -409,7 +532,7 @@ handle!(TuiIn: |self: App, input|Ok({ } None })); -#[derive(Clone, Debug)] pub enum AppCommand { +#[derive(Clone, Debug)] pub enum TekCommand { Clip(ClipCommand), Clock(ClockCommand), Color(ItemPalette), @@ -425,7 +548,7 @@ handle!(TuiIn: |self: App, input|Ok({ Track(TrackCommand), Zoom(Option), } -edn_command!(AppCommand: |state: App| { +edn_command!(TekCommand: |app: Tek| { ("compact" [] Self::Compact(None)) ("compact" [c: bool] Self::Compact(c)) @@ -445,47 +568,47 @@ edn_command!(AppCommand: |state: App| { (t, s) => Self::Select(Selection::Clip(t - 1, s - 1)), }) - ("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b) + ("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(app, &a.to_ref(), b) .expect("invalid command"))) - ("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state.clock(), &a.to_ref(), b) + ("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(app.clock(), &a.to_ref(), b) .expect("invalid command"))) - ("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b) + ("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(app.editor.as_ref().expect("no editor"), &a.to_ref(), b) .expect("invalid command"))) - ("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b) + ("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(app.pool.as_ref().expect("no pool"), &a.to_ref(), b) .expect("invalid command"))) - ("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b) + ("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(app.sampler.as_ref().expect("no sampler"), &a.to_ref(), b) .expect("invalid command"))) - ("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b) + ("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(app, &a.to_ref(), b) .expect("invalid command"))) - ("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b) + ("track" [a, ..b] Self::Track(TrackCommand::from_edn(app, &a.to_ref(), b) .expect("invalid command"))) }); -command!(|self: AppCommand, state: App|match self { +command!(|self: TekCommand, app: Tek|match self { Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None }, Self::History(delta) => { println!("\n\rtodo: undo/redo"); None }, - Self::Select(s) => { state.selected = s; None }, - Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, - Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?, - Self::Track(cmd) => cmd.delegate(state, Self::Track)?, - Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?, - Self::Editor(cmd) => state.editor.as_mut() + Self::Select(s) => { app.selected = s; None }, + Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?, + Self::Scene(cmd) => cmd.delegate(app, Self::Scene)?, + Self::Track(cmd) => cmd.delegate(app, Self::Track)?, + Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?, + Self::Editor(cmd) => app.editor.as_mut() .map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(), - Self::Sampler(cmd) => state.sampler.as_mut() + Self::Sampler(cmd) => app.sampler.as_mut() .map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(), - Self::Enqueue(clip) => state.player.as_mut() + Self::Enqueue(clip) => app.player.as_mut() .map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(), Self::Color(palette) => { - let old = state.color; - state.color = palette; + let old = app.color; + app.color = palette; Some(Self::Color(old)) }, Self::StopAll => { - for track in 0..state.tracks.len(){state.tracks[track].player.enqueue_next(None);} + for track in 0..app.tracks.len(){app.tracks[track].player.enqueue_next(None);} None }, - Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() { + Self::Pool(cmd) => if let Some(pool) = app.pool.as_mut() { let undo = cmd.clone().delegate(pool, Self::Pool)?; - if let Some(editor) = state.editor.as_mut() { + if let Some(editor) = app.editor.as_mut() { match cmd { // autoselect: automatically load selected clip in editor // autocolor: update color in all places simultaneously @@ -500,16 +623,16 @@ command!(|self: AppCommand, state: App|match self { }, Self::Compact(compact) => match compact { Some(compact) => { - if state.compact != compact { - state.compact = compact; + if app.compact != compact { + app.compact = compact; Some(Self::Compact(Some(!compact))) } else { None } }, None => { - state.compact = !state.compact; - Some(Self::Compact(Some(!state.compact))) + app.compact = !app.compact; + Some(Self::Compact(Some(!app.compact))) } } }); @@ -555,7 +678,7 @@ impl Selection { }).into() } } -impl HasSelection for App { +impl HasSelection for Tek { fn selected (&self) -> &Selection { &self.selected } fn selected_mut (&mut self) -> &mut Selection { &mut self.selected } } @@ -595,7 +718,7 @@ impl Track { SetZoom(usize), SetColor(usize, ItemPalette), } -edn_command!(TrackCommand: |state: App| { +edn_command!(TrackCommand: |app: Tek| { ("add" [] Self::Add) ("size" [a: usize] Self::SetSize(a.unwrap())) ("zoom" [a: usize] Self::SetZoom(a.unwrap())) @@ -604,18 +727,18 @@ edn_command!(TrackCommand: |state: App| { ("stop" [a: usize] Self::Stop(a.unwrap())) ("swap" [a: usize, b: usize] Self::Swap(a.unwrap(), b.unwrap())) }); -command!(|self: TrackCommand, state: App|match self { - Self::Add => { state.track_add(None, None)?; None }, - Self::Del(index) => { state.track_del(index); None }, - Self::Stop(track) => { state.tracks[track].player.enqueue_next(None); None }, +command!(|self: TrackCommand, app: Tek|match self { + Self::Add => { app.track_add(None, None)?; None }, + Self::Del(index) => { app.track_del(index); None }, + Self::Stop(track) => { app.tracks[track].player.enqueue_next(None); None }, Self::SetColor(index, color) => { - let old = state.tracks[index].color; - state.tracks[index].color = color; + let old = app.tracks[index].color; + app.tracks[index].color = color; Some(Self::SetColor(index, old)) }, _ => None }); -impl HasTracks for App { +impl HasTracks for Tek { fn midi_ins (&self) -> &Vec> { &self.midi_ins } fn midi_outs (&self) -> &Vec> { &self.midi_outs } fn tracks (&self) -> &Vec { &self.tracks } @@ -782,7 +905,7 @@ impl Scene { SetColor(usize, ItemPalette), Enqueue(usize), } -edn_command!(SceneCommand: |state: App| { +edn_command!(SceneCommand: |app: Tek| { ("add" [] Self::Add) ("del" [a: usize] Self::Del(0)) ("zoom" [a: usize] Self::SetZoom(a.unwrap())) @@ -790,23 +913,23 @@ edn_command!(SceneCommand: |state: App| { ("enqueue" [a: usize] Self::Enqueue(a.unwrap())) ("swap" [a: usize, b: usize] Self::Swap(a.unwrap(), b.unwrap())) }); -command!(|self: SceneCommand, state: App|match self { - Self::Add => { state.scene_add(None, None)?; None } - Self::Del(index) => { state.scene_del(index); None }, +command!(|self: SceneCommand, app: Tek|match self { + Self::Add => { app.scene_add(None, None)?; None } + Self::Del(index) => { app.scene_del(index); None }, Self::SetColor(index, color) => { - let old = state.scenes[index].color; - state.scenes[index].color = color; + let old = app.scenes[index].color; + app.scenes[index].color = color; Some(Self::SetColor(index, old)) }, Self::Enqueue(scene) => { - for track in 0..state.tracks.len() { - state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); + for track in 0..app.tracks.len() { + app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref()); } None }, _ => None }); -impl HasScenes for App { +impl HasScenes for Tek { fn scenes (&self) -> &Vec { &self.scenes } fn scenes_mut (&mut self) -> &mut Vec { &mut self.scenes } } @@ -879,7 +1002,7 @@ pub trait HasScenes: HasSelection + HasEditor + Send + Sync { SetLoop(usize, usize, bool), SetColor(usize, usize, ItemPalette), } -edn_command!(ClipCommand: |state: App| { +edn_command!(ClipCommand: |app: Tek| { ("get" [a: usize ,b: usize] Self::Get(a.unwrap(), b.unwrap())) ("put" [a: usize, b: usize, c: Option>>] @@ -893,20 +1016,20 @@ edn_command!(ClipCommand: |state: App| { ("color" [a: usize, b: usize] Self::SetColor(a.unwrap(), b.unwrap(), ItemPalette::random())) }); -command!(|self: ClipCommand, state: App|match self { +command!(|self: ClipCommand, app: Tek|match self { Self::Get(track, scene) => { todo!() }, Self::Put(track, scene, clip) => { - let old = state.scenes[scene].clips[track].clone(); - state.scenes[scene].clips[track] = clip; + let old = app.scenes[scene].clips[track].clone(); + app.scenes[scene].clips[track] = clip; Some(Self::Put(track, scene, old)) }, Self::Enqueue(track, scene) => { - state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); + app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref()); None }, _ => None }); -audio!(|self: App, client, scope|{ +audio!(|self: Tek, client, scope|{ // Start profiling cycle let t0 = self.perf.get_t0(); // Update transport clock @@ -1071,5 +1194,6 @@ impl OutputStats { } } #[cfg(test)] fn test_tek () { - // TODO + use clap::CommandFactory; + TekCli::command().debug_assert(); }