diff --git a/app/src/api.rs b/app/src/api.rs new file mode 100644 index 00000000..03b03862 --- /dev/null +++ b/app/src/api.rs @@ -0,0 +1,200 @@ +use crate::*; + +macro_rules! expose { + ($([$self:ident:$State:ty] { $($Type:ty => { $($pat:pat => $expr:expr),* $(,)? })* })*) => { + $(expose!(@impl [$self: $State] { $($Type => { $($pat => $expr),* })* });)* + }; + (@impl [$self:ident:$State:ty] { $($Type:ty => { $($pat:pat => $expr:expr),* $(,)? })* }) => { + $(expose!(@type $Type [$self: $State] => { $($pat => $expr),* });)* + }; + (@type bool [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + provide_bool!(bool: |$self: $State| { $($pat => $expr),* }); + }; + (@type isize [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + provide_num!(isize: |$self: $State| { $($pat => $expr),* }); + }; + (@type usize [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + provide_num!(usize: |$self: $State| { $($pat => $expr),* }); + }; + (@type $Type:ty [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + provide!($Type: |$self: $State| { $($pat => $expr),* }); + }; +} + +expose!([self: Tek] { + bool => {} + isize => {} + usize => { + ":scene-last" => self.scenes.len(), + ":track-last" => self.tracks.len(), + } + Option => { + ":scene" => self.selected.scene(), + ":track" => self.selected.track(), + } + Color => {} + Arc> => {} + Option>> => { + ":clip" => match self.selected { + Selection::Clip(t, s) => self.scenes[s].clips[t].clone(), + _ => None + } + } + Selection => { + ":scene-next" => match self.selected { + Selection::Mix => Selection::Scene(0), + Selection::Track(t) => Selection::Clip(t, 0), + Selection::Scene(s) if s + 1 < self.scenes.len() => Selection::Scene(s + 1), + Selection::Scene(s) => Selection::Mix, + Selection::Clip(t, s) if s + 1 < self.scenes.len() => Selection::Clip(t, s + 1), + Selection::Clip(t, s) => Selection::Track(t), + }, + ":scene-prev" => match self.selected { + Selection::Mix => Selection::Mix, + Selection::Track(t) => Selection::Track(t), + Selection::Scene(0) => Selection::Mix, + Selection::Scene(s) => Selection::Scene(s - 1), + Selection::Clip(t, 0) => Selection::Track(t), + Selection::Clip(t, s) => Selection::Clip(t, s - 1), + }, + ":track-next" => match self.selected { + Selection::Mix => Selection::Track(0), + Selection::Track(t) if t + 1 < self.tracks.len() => Selection::Track(t + 1), + Selection::Track(t) => Selection::Mix, + Selection::Scene(s) => Selection::Clip(0, s), + Selection::Clip(t, s) if t + 1 < self.tracks.len() => Selection::Clip(t + 1, s), + Selection::Clip(t, s) => Selection::Scene(s), + }, + ":track-prev" => match self.selected { + Selection::Mix => Selection::Mix, + Selection::Scene(s) => Selection::Scene(s), + Selection::Track(0) => Selection::Mix, + Selection::Track(t) => Selection::Track(t - 1), + Selection::Clip(0, s) => Selection::Scene(s), + Selection::Clip(t, s) => Selection::Clip(t - 1, s), + }, + } +}); + +macro_rules! impose { + ([$self:ident:$Struct:ty] { $($Command:ty => $variants:tt)* }) => { + $(atom_command!($Command: |$self: $Struct| $variants);)* + }; +} + +impose!([app: Tek] { + + TekCommand => { + ("stop" [] + Some(Self::StopAll)) + ("undo" [d: usize] + Some(Self::History(-(d.unwrap_or(0)as isize)))) + ("redo" [d: usize] + Some(Self::History(d.unwrap_or(0) as isize))) + ("zoom" [z: usize] + Some(Self::Zoom(z))) + ("edit" [] + Some(Self::Edit(None))) + ("edit" [c: bool] + Some(Self::Edit(c))) + ("color" [c: Color] + Some(Self::Color(ItemPalette::random()))) + ("color" [c: Color] + Some(Self::Color(c.map(ItemPalette::from).expect("no color")))) + ("enqueue" [c: Arc>] + Some(Self::Enqueue(c))) + ("launch" [] + Some(Self::Launch)) + ("clip" [,..a] + ClipCommand::try_from_expr(app, a).map(Self::Clip)) + ("clock" [,..a] + ClockCommand::try_from_expr(app.clock(), a).map(Self::Clock)) + ("editor" [,..a] + MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a).map(Self::Editor)) + ("pool" [,..a] + PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a).map(Self::Pool)) + //("sampler" [,..a] + // Self::Sampler( //SamplerCommand::try_from_expr(app.sampler().as_ref().expect("no sampler"), a).expect("invalid command"))) + ("scene" [,..a] + SceneCommand::try_from_expr(app, a).map(Self::Scene)) + ("track" [,..a] + TrackCommand::try_from_expr(app, a).map(Self::Track)) + ("input" [,..a] + InputCommand::try_from_expr(app, a).map(Self::Input)) + ("output" [,..a] + OutputCommand::try_from_expr(app, a).map(Self::Output)) + ("select" [t: Selection] + Some(t.map(Self::Select).expect("no selection"))) + ("select" [t: usize, s: usize] + Some(match (t.expect("no track"), s.expect("no scene")) { + (0, 0) => Self::Select(Selection::Mix), + (t, 0) => Self::Select(Selection::Track(t)), + (0, s) => Self::Select(Selection::Scene(s)), + (t, s) => Self::Select(Selection::Clip(t, s)), + })) + } + + ClipCommand => { + ("get" [a: usize, b: usize] + Some(Self::Get(a.unwrap(), b.unwrap()))) + ("put" [a: usize, b: usize, c: Option>>] + Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap()))) + ("enqueue" [a: usize, b: usize] + Some(Self::Enqueue(a.unwrap(), b.unwrap()))) + ("edit" [a: Option>>] + Some(Self::Edit(a.unwrap()))) + ("loop" [a: usize, b: usize, c: bool] + Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) + ("color" [a: usize, b: usize] + Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemPalette::random()))) + } + + InputCommand => { + ("add" [] Some(Self::Add)) + } + + OutputCommand => { + ("add" [] Some(Self::Add)) + } + + SceneCommand => { + ("add" [] + Some(Self::Add)) + ("del" [a: usize] + Some(Self::Del(0))) + ("zoom" [a: usize] + Some(Self::SetZoom(a.unwrap()))) + ("color" [a: usize] + Some(Self::SetColor(a.unwrap(), ItemPalette::G[128]))) + ("enqueue" [a: usize] + Some(Self::Enqueue(a.unwrap()))) + ("swap" [a: usize, b: usize] + Some(Self::Swap(a.unwrap(), b.unwrap()))) + } + + TrackCommand => { + ("add" [] + Some(Self::Add)) + ("size" [a: usize] + Some(Self::SetSize(a.unwrap()))) + ("zoom" [a: usize] + Some(Self::SetZoom(a.unwrap()))) + ("color" [a: usize] + Some(Self::SetColor(a.unwrap(), ItemPalette::random()))) + ("del" [a: usize] + Some(Self::Del(a.unwrap()))) + ("stop" [a: usize] + Some(Self::Stop(a.unwrap()))) + ("swap" [a: usize, b: usize] + Some(Self::Swap(a.unwrap(), b.unwrap()))) + ("play" [] + Some(Self::TogglePlay)) + ("solo" [] + Some(Self::ToggleSolo)) + ("rec" [] + Some(Self::ToggleRecord)) + ("mon" [] + Some(Self::ToggleMonitor)) + } + +}); diff --git a/app/src/keys.rs b/app/src/keys.rs index 0943118d..9f6d4788 100644 --- a/app/src/keys.rs +++ b/app/src/keys.rs @@ -52,34 +52,6 @@ handle!(TuiIn: |self: Tek, input|Ok({ Track(TrackCommand), Zoom(Option), } -atom_command!(TekCommand: |app: Tek| { - ("stop" [] Some(Self::StopAll)) - ("undo" [d: usize] Some(Self::History(-(d.unwrap_or(0)as isize)))) - ("redo" [d: usize] Some(Self::History(d.unwrap_or(0) as isize))) - ("zoom" [z: usize] Some(Self::Zoom(z))) - ("edit" [] Some(Self::Edit(None))) - ("edit" [c: bool] Some(Self::Edit(c))) - ("color" [c: Color] Some(Self::Color(ItemPalette::random()))) - ("color" [c: Color] Some(Self::Color(c.map(ItemPalette::from).expect("no color")))) - ("enqueue" [c: Arc>] Some(Self::Enqueue(c))) - ("launch" [] Some(Self::Launch)) - ("clip" [,..a] ClipCommand::try_from_expr(app, a).map(Self::Clip)) - ("clock" [,..a] ClockCommand::try_from_expr(app.clock(), a).map(Self::Clock)) - ("editor" [,..a] MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a).map(Self::Editor)) - ("pool" [,..a] PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a).map(Self::Pool)) - //("sampler" [,..a] Self::Sampler( //SamplerCommand::try_from_expr(app.sampler().as_ref().expect("no sampler"), a).expect("invalid command"))) - ("scene" [,..a] SceneCommand::try_from_expr(app, a).map(Self::Scene)) - ("track" [,..a] TrackCommand::try_from_expr(app, a).map(Self::Track)) - ("input" [,..a] InputCommand::try_from_expr(app, a).map(Self::Input)) - ("output" [,..a] OutputCommand::try_from_expr(app, a).map(Self::Output)) - ("select" [t: Selection] Some(t.map(Self::Select).expect("no selection"))) - ("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) { - (0, 0) => Self::Select(Selection::Mix), - (t, 0) => Self::Select(Selection::Track(t)), - (0, s) => Self::Select(Selection::Scene(s)), - (t, s) => Self::Select(Selection::Clip(t, s)), - })) -}); command!(|self: TekCommand, app: Tek|match self { Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None }, Self::History(delta) => { println!("\n\rtodo: undo/redo"); None }, diff --git a/app/src/keys/keys_clip.rs b/app/src/keys/keys_clip.rs index cb21ef7b..2fb59fae 100644 --- a/app/src/keys/keys_clip.rs +++ b/app/src/keys/keys_clip.rs @@ -7,14 +7,6 @@ use crate::*; SetLoop(usize, usize, bool), SetColor(usize, usize, ItemPalette), } -atom_command!(ClipCommand: |app: Tek| { - ("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap()))) - ("put" [a: usize, b: usize, c: Option>>] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap()))) - ("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap()))) - ("edit" [a: Option>>] Some(Self::Edit(a.unwrap()))) - ("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) - ("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemPalette::random()))) -}); command!(|self: ClipCommand, app: Tek|match self { Self::Get(track, scene) => { todo!() }, Self::Put(track, scene, clip) => { diff --git a/app/src/keys/keys_ins.rs b/app/src/keys/keys_ins.rs index fd443d18..4460ce72 100644 --- a/app/src/keys/keys_ins.rs +++ b/app/src/keys/keys_ins.rs @@ -1,8 +1,5 @@ use crate::*; #[derive(Clone, Debug)] pub enum InputCommand { Add } -atom_command!(InputCommand: |app: Tek| { - ("add" [] Some(Self::Add)) -}); command!(|self: InputCommand, app: Tek|match self { Self::Add => { app.midi_ins.push(JackMidiIn::new(&app.jack, &format!("M/{}", app.midi_ins.len()), &[])?); diff --git a/app/src/keys/keys_outs.rs b/app/src/keys/keys_outs.rs index 6d1b3235..a39ce29c 100644 --- a/app/src/keys/keys_outs.rs +++ b/app/src/keys/keys_outs.rs @@ -1,8 +1,5 @@ use crate::*; #[derive(Clone, Debug)] pub enum OutputCommand { Add } -atom_command!(OutputCommand: |app: Tek| { - ("add" [] Some(Self::Add)) -}); command!(|self: OutputCommand, app: Tek|match self { Self::Add => { app.midi_outs.push(JackMidiOut::new(&app.jack, &format!("{}/M", app.midi_outs.len()), &[])?); diff --git a/app/src/keys/keys_scene.rs b/app/src/keys/keys_scene.rs index c7d5ba8b..a6c7c244 100644 --- a/app/src/keys/keys_scene.rs +++ b/app/src/keys/keys_scene.rs @@ -8,14 +8,6 @@ use crate::*; SetColor(usize, ItemPalette), Enqueue(usize), } -atom_command!(SceneCommand: |app: Tek| { - ("add" [] Some(Self::Add)) - ("del" [a: usize] Some(Self::Del(0))) - ("zoom" [a: usize] Some(Self::SetZoom(a.unwrap()))) - ("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemPalette::G[128]))) - ("enqueue" [a: usize] Some(Self::Enqueue(a.unwrap()))) - ("swap" [a: usize, b: usize] Some(Self::Swap(a.unwrap(), b.unwrap()))) -}); command!(|self: SceneCommand, app: Tek|match self { Self::Add => { use Selection::*; diff --git a/app/src/keys/keys_track.rs b/app/src/keys/keys_track.rs index 34d8fb19..37bea45f 100644 --- a/app/src/keys/keys_track.rs +++ b/app/src/keys/keys_track.rs @@ -12,19 +12,6 @@ use crate::*; ToggleRecord, ToggleMonitor, } -atom_command!(TrackCommand: |app: Tek| { - ("add" [] Some(Self::Add)) - ("size" [a: usize] Some(Self::SetSize(a.unwrap()))) - ("zoom" [a: usize] Some(Self::SetZoom(a.unwrap()))) - ("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemPalette::random()))) - ("del" [a: usize] Some(Self::Del(a.unwrap()))) - ("stop" [a: usize] Some(Self::Stop(a.unwrap()))) - ("swap" [a: usize, b: usize] Some(Self::Swap(a.unwrap(), b.unwrap()))) - ("play" [] Some(Self::TogglePlay)) - ("solo" [] Some(Self::ToggleSolo)) - ("rec" [] Some(Self::ToggleRecord)) - ("mon" [] Some(Self::ToggleMonitor)) -}); command!(|self: TrackCommand, app: Tek|match self { Self::Add => { use Selection::*; diff --git a/app/src/lib.rs b/app/src/lib.rs index 09dea93b..d0a9b9e9 100644 --- a/app/src/lib.rs +++ b/app/src/lib.rs @@ -14,11 +14,6 @@ #![feature(type_alias_impl_trait)] #![feature(trait_alias)] #![feature(type_changing_struct_update)] -mod audio; pub use self::audio::*; -mod device; pub use self::device::*; -mod keys; pub use self::keys::*; -mod model; pub use self::model::*; -mod view; pub use self::view::*; /// Standard result type. pub type Usually = std::result::Result>; /// Standard optional result type. @@ -39,3 +34,17 @@ pub use ::tengri::tui::ratatui::prelude::{Style, Stylize, Buffer, Modifier}; pub use ::tengri::tui::crossterm; pub use ::tengri::tui::crossterm::event::{Event, KeyCode::{self, *}}; pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::Relaxed}}; + +mod api; pub use self::api::*; +mod audio; pub use self::audio::*; +mod device; pub use self::device::*; +mod keys; pub use self::keys::*; +mod model; pub use self::model::*; +mod view; pub use self::view::*; + +#[cfg(test)] #[test] fn test_model () { + let mut tek = Tek::default(); + let _ = tek.clip(); + let _ = tek.toggle_loop(); + let _ = tek.activate(); +} diff --git a/app/src/model.rs b/app/src/model.rs index ac92c452..57bd4fa5 100644 --- a/app/src/model.rs +++ b/app/src/model.rs @@ -1,7 +1,9 @@ use crate::*; + mod model_track; pub use self::model_track::*; mod model_scene; pub use self::model_scene::*; mod model_select; pub use self::model_select::*; + #[derive(Default, Debug)] pub struct Tek { /// Must not be dropped for the duration of the process pub jack: Jack, @@ -62,73 +64,7 @@ mod model_select; pub use self::model_select::*; // Cache of formatted strings pub view_cache: Arc>, } -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_sampler!(|self: Tek|{ - //sampler = self.sampler; - //index = self.editor.as_ref().map(|e|e.note_pos()).unwrap_or(0); }); -has_editor!(|self: Tek|{ - editor = self.editor; - editor_w = { - let size = self.size.w(); - let editor = self.editor.as_ref().expect("missing editor"); - let time_len = editor.time_len().get(); - let time_zoom = editor.time_zoom().get().max(1); - (5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16) - }; - editor_h = 15; - is_editing = self.editing.load(Relaxed); }); -provide!(Color: |self: Tek| {}); -provide!(Arc>: |self: Tek| {}); -provide!(Option>>: |self: Tek| { - ":clip" => match self.selected { - Selection::Clip(t, s) => self.scenes[s].clips[t].clone(), - _ => None - } -}); -provide_bool!(bool: |self: Tek| {}); -provide_num!(isize: |self: Tek| {}); -provide_num!(usize: |self: Tek| { - ":scene-last" => self.scenes.len(), - ":track-last" => self.tracks.len() }); -provide!(Option: |self: Tek| { - ":scene" => self.selected.scene(), - ":track" => self.selected.track() }); -provide!(Selection: |self: Tek| { - ":scene-next" => match self.selected { - Selection::Mix => Selection::Scene(0), - Selection::Track(t) => Selection::Clip(t, 0), - Selection::Scene(s) if s + 1 < self.scenes.len() => Selection::Scene(s + 1), - Selection::Scene(s) => Selection::Mix, - Selection::Clip(t, s) if s + 1 < self.scenes.len() => Selection::Clip(t, s + 1), - Selection::Clip(t, s) => Selection::Track(t), - }, - ":scene-prev" => match self.selected { - Selection::Mix => Selection::Mix, - Selection::Track(t) => Selection::Track(t), - Selection::Scene(0) => Selection::Mix, - Selection::Scene(s) => Selection::Scene(s - 1), - Selection::Clip(t, 0) => Selection::Track(t), - Selection::Clip(t, s) => Selection::Clip(t, s - 1), - }, - ":track-next" => match self.selected { - Selection::Mix => Selection::Track(0), - Selection::Track(t) if t + 1 < self.tracks.len() => Selection::Track(t + 1), - Selection::Track(t) => Selection::Mix, - Selection::Scene(s) => Selection::Clip(0, s), - Selection::Clip(t, s) if t + 1 < self.tracks.len() => Selection::Clip(t + 1, s), - Selection::Clip(t, s) => Selection::Scene(s), - }, - ":track-prev" => match self.selected { - Selection::Mix => Selection::Mix, - Selection::Scene(s) => Selection::Scene(s), - Selection::Track(0) => Selection::Mix, - Selection::Track(t) => Selection::Track(t - 1), - Selection::Clip(0, s) => Selection::Scene(s), - Selection::Clip(t, s) => Selection::Clip(t - 1, s), - }, -}); + impl Tek { fn clip (&self) -> Option>> { self.scene()?.clips.get(self.selected().track()?)?.clone() @@ -164,9 +100,27 @@ impl Tek { Ok(()) } } -#[cfg(test)] #[test] fn test_model () { - let mut tek = Tek::default(); - let _ = tek.clip(); - let _ = tek.toggle_loop(); - let _ = tek.activate(); -} + +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|{ + editor = self.editor; + editor_w = { + let size = self.size.w(); + let editor = self.editor.as_ref().expect("missing editor"); + let time_len = editor.time_len().get(); + let time_zoom = editor.time_zoom().get().max(1); + (5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16) + }; + editor_h = 15; + is_editing = self.editing.load(Relaxed); +}); + +//has_sampler!(|self: Tek|{ + //sampler = self.sampler; + //index = self.editor.as_ref().map(|e|e.note_pos()).unwrap_or(0); +//});