From 3ef3d5eb6f6faac0343e1a305e924deb2cde1e11 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 26 Apr 2025 21:20:06 +0300 Subject: [PATCH] api: compact --- crates/app/edn/groovebox_keys.edn | 10 +- crates/app/src/api.rs | 559 ++++++++-------------------- crates/app/src/device.rs | 1 - crates/app/src/lib.rs | 2 - crates/app/src/model.rs | 234 ++++++++++-- crates/app/src/view.rs | 58 ++- crates/cli/tek.rs | 2 +- crates/midi/midi.scratch.rs | 6 +- crates/midi/src/clip.rs | 2 - crates/midi/src/clip/clip_editor.rs | 4 +- crates/midi/src/clip/clip_launch.rs | 4 +- crates/midi/src/clip/clip_model.rs | 6 +- crates/midi/src/mode.rs | 2 +- crates/midi/src/piano/piano_h.rs | 6 +- crates/midi/src/pool/pool_api.rs | 2 +- crates/midi/src/pool/pool_model.rs | 4 +- crates/sampler/sampler_scratch.rs | 2 +- crates/sampler/src/lib.rs | 5 +- crates/sampler/src/sampler_api.rs | 84 ++--- crates/sampler/src/sampler_model.rs | 2 +- crates/time/src/clock.rs | 2 - deps/tengri | 2 +- 22 files changed, 441 insertions(+), 558 deletions(-) delete mode 100644 crates/app/src/device.rs diff --git a/crates/app/edn/groovebox_keys.edn b/crates/app/edn/groovebox_keys.edn index 174f9b0f..751b1762 100644 --- a/crates/app/edn/groovebox_keys.edn +++ b/crates/app/edn/groovebox_keys.edn @@ -1,14 +1,14 @@ (@esc menu) (@f1 help) -(@u undo 1) -(@shift-u redo 1) +(@u undo 1) +(@shift-u redo 1) (@space clock toggle) (@shift-space clock toggle 0) -(@c color) +(@c color) -(@q launch) +(@q launch) -(@r record/begin :note-pos) +(@r sampler record/begin :pitch) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index 5d501a8e..5881e667 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -1,437 +1,192 @@ use crate::*; -expose!([self: Tek] { - [bool] => {} - [isize] => {} - [Color] => {} - [Arc>] => {} - [u16] => { ":w-sidebar" => self.w_sidebar(), } - [usize] => { ":scene-last" => self.scenes.len(), - ":track-last" => self.tracks.len(), } - [Option] => { ":scene" => self.selected.scene(), - ":track" => self.selected.track(), } - [Option>>] => { - ":clip" => match self.selected { - Selection::Clip(t, s) => self.scenes[s].clips[t].clone(), - _ => None - } - } - [Selection] => { - ":scene-next" => self.selected.scene_next(self.scenes.len()), - ":scene-prev" => self.selected.scene_prev(), - ":track-next" => self.selected.track_next(self.tracks.len()), - ":track-prev" => self.selected.track_prev(), - } +type MaybeClip = Option>>; + +macro_rules! ns { ($C:ty, $s:expr, $a:expr, $W:expr) => { <$C>::try_from_expr($s, $a).map($W) } } +macro_rules! cmd { ($cmd:expr) => {{ $cmd; None }}; } +macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; } + +view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { + ":nil" => Box::new("nil"), + ":modal" => self.view_modal(), + ":status" => self.view_status(), + ":transport" => self.view_transport(), + ":arranger" => self.view_arranger(), + ":pool" => self.view_pool(), + ":editor" => self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e)), + ":samples-keys" => self.sampler().map(|s|s.view_list(false, self.editor().unwrap())), + ":samples-grid" => self.sampler().map(|s|s.view_grid()), + ":sample-viewer" => self.sampler().map(|s|s.view_sample(self.editor().unwrap().note_pos())), }); -impose!([app: Tek] { +expose!([self: Tek] + ([bool]) + ([isize]) + ([Color]) + ([Arc>]) + ([u7] (":pitch" (self.editor().map(|e|e.note_pos()).unwrap() as u8).into())) + ([u16] (":w-sidebar" self.w_sidebar())) + ([usize] (":scene-last" self.scenes.len()) + (":track-last" self.tracks.len())) + ([Option] (":scene" self.selected.scene()) + (":track" self.selected.track())) + ([MaybeClip] + (":clip" match self.selected { + Selection::Clip(t, s) => self.scenes[s].clips[t].clone(), + _ => None + })) + ([Selection] + (":scene-next" self.selected.scene_next(self.scenes.len())) + (":scene-prev" self.selected.scene_prev()) + (":track-next" self.selected.track_next(self.tracks.len())) + (":track-prev" self.selected.track_prev()))); - TekCommand => { +impose!([app: Tek] + (TekCommand: ("menu" [] Some(Self::ToggleMenu)) ("help" [] Some(Self::ToggleHelp)) ("stop" [] Some(Self::StopAll)) - ("undo" [d: usize] Some(Self::History(-(d.unwrap_or(0)as isize)))) + ("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" [] Some(Self::Color(ItemPalette::random()))) - ("color" [c: Color] Some(Self::Color(c.map(ItemPalette::from).expect("no color")))) + ("color" [] Some(Self::Color(ItemTheme::random()))) + ("color" [c: Color] Some(Self::Color(c.map(ItemTheme::from).expect("no color")))) ("enqueue" [c: Arc>] Some(Self::Enqueue(c))) ("launch" [] Some(Self::Launch)) - ("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"))) - ("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)) + ("clock" [,..a] ns!(ClockCommand, app.clock(), a, Self::Clock)) + ("scene" [,..a] ns!(SceneCommand, app, a, Self::Scene)) + ("track" [,..a] ns!(TrackCommand, app, a, Self::Track)) + ("input" [,..a] ns!(InputCommand, app, a, Self::Input)) + ("output" [,..a] ns!(OutputCommand, app, a, Self::Output)) + ("clip" [,..a] ns!(ClipCommand, app, a, Self::Clip)) + ("pool" [,..a] app.pool.as_ref().map(|p|ns!(PoolCommand, p, a, Self::Pool)).flatten()) + ("editor" [,..a] app.editor().map(|e|ns!(MidiEditCommand, e, a, Self::Editor)).flatten()) + ("sampler" [,..a] app.sampler().map(|s|ns!(SamplerCommand, s, a, Self::Sampler)).flatten()) ("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)), - })) - //("sampler" [,..a] - // Self::Sampler( //SamplerCommand::try_from_expr(app.sampler().as_ref().expect("no sampler"), a).expect("invalid command"))) - } - - 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()))) + (t, s) => Self::Select(Selection::Clip(t, s)) }))) + (ClipCommand: + ("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap()))) + ("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) ("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap()))) - ("edit" [a: Option>>] Some(Self::Edit(a.unwrap()))) + ("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.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()))) - } + ("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap())))) - InputCommand => { - ("add" [] Some(Self::Add)) - } + (InputCommand: + ("add" [] Some(Self::Add))) - OutputCommand => { - ("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()))) - } + (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(), ItemTheme::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()))) + (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(), ItemTheme::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)) - } + ("play" [] Some(Self::TogglePlay)) + ("solo" [] Some(Self::ToggleSolo)) + ("rec" [] Some(Self::ToggleRec)) + ("mon" [] Some(Self::ToggleMon)))); -}); +defcom!([self, app: Tek] -defcom! { |self, app: Tek| + (TekCommand + (Sampler [cmd: SamplerCommand] cmd_todo!("\n\rtodo: sampler {cmd:?}")) + (Scene [cmd: SceneCommand] cmd.delegate(app, Self::Scene)?) + (Track [cmd: TrackCommand] cmd.delegate(app, Self::Track)?) + (Output [cmd: OutputCommand] cmd.delegate(app, Self::Output)?) + (Input [cmd: InputCommand] cmd.delegate(app, Self::Input)?) + (Clip [cmd: ClipCommand] cmd.delegate(app, Self::Clip)?) + (Clock [cmd: ClockCommand] cmd.delegate(app, Self::Clock)?) + (Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?) + (Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?) + (ToggleHelp [] cmd!({ app.modal = match app.modal { Some(Modal::Help) => None, _ => Some(Modal::Help) }})) + (ToggleMenu [] cmd!({ app.modal = match app.modal { Some(Modal::Menu) => None, _ => Some(Modal::Menu) }})) + (Color [p: ItemTheme] app.set_color(Some(p)).map(Self::Color)) + (Enqueue [c: MaybeClip] cmd_todo!("\n\rtodo: enqueue {c:?}")) + (History [d: isize] cmd_todo!("\n\rtodo: history {d:?}")) + (Zoom [z: Option] cmd_todo!("\n\rtodo: zoom {z:?}")) + (Edit [value: Option] cmd!(app.toggle_editor(value))) + (Launch [] cmd!(app.launch())) + (Select [s: Selection] cmd!(app.select(s))) + (StopAll [] cmd!(app.stop_all()))) - TekCommand { + (InputCommand + (Add [] cmd!(app.add_midi_in()?))) - ToggleHelp => { - app.modal = match app.modal { - Some(Modal::Help) => None, - _ => Some(Modal::Help) - }; - None - } + (OutputCommand + (Add [] cmd!(app.add_midi_out()?))) - ToggleMenu => { - app.modal = match app.modal { - Some(Modal::Menu) => None, - _ => Some(Modal::Menu) - }; - None - } + (TrackCommand + (TogglePlay [] Some(Self::TogglePlay)) + (ToggleSolo [] Some(Self::ToggleSolo)) + (SetSize [t: usize] cmd_todo!("\n\rtodo: {self:?}")) + (SetZoom [z: usize] cmd_todo!("\n\rtodo: {self:?}")) + (Swap [a: usize, b: usize] cmd_todo!("\n\rtodo: {self:?}")) + (Del [index: usize] cmd!(app.track_del(index))) + (Stop [index: usize] cmd!(app.tracks[index].player.enqueue_next(None))) + (Add [] Some(Self::Del(app.track_add_focus()?))) + (SetColor [i: usize, c: ItemTheme] Some(Self::SetColor(i, app.track_set_color(i, c)))) + (ToggleRec [] { app.track_toggle_record(); Some(Self::ToggleRec) }) + (ToggleMon [] { app.track_toggle_monitor(); Some(Self::ToggleMon) })) - Sampler(cmd: SamplerCommand) => { - println!("\n\rtodo: {cmd:?}"); - None - } + (SceneCommand + (Swap [a: usize, b: usize] cmd_todo!("\n\rtodo: {self:?}")) + (SetSize [index: usize] cmd_todo!("\n\rtodo: {self:?}")) + (SetZoom [zoom: usize] cmd_todo!("\n\rtodo: {self:?}")) + (Enqueue [scene: usize] cmd!(app.scene_enqueue(scene))) + (Del [index: usize] cmd!(app.scene_del(index))) + (Add [] Some(Self::Del(app.scene_add_focus()?))) + (SetColor [i: usize, c: ItemTheme] Some(Self::SetColor(i, app.scene_set_color(i, c))))) - Enqueue(clip: Option>>) => { - println!("\n\rtodo: enqueue {clip:?}"); - None - } + (ClipCommand + (Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}")) + (Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}")) + (SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) + (Put [t: usize, s: usize, c: MaybeClip] + Some(Self::Put(t, s, app.clip_put(t, s, c)))) + (Enqueue [t: usize, s: usize] + cmd!(app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()))) + (SetColor [t: usize, s: usize, c: ItemTheme] + app.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); - History(delta: isize) => { - println!("\n\rtodo: {self:?}"); - None - } - - Zoom(zoom: Option) => { - println!("\n\rtodo: {self:?}"); - None - } - - Scene(cmd: SceneCommand) => - cmd.delegate(app, Self::Scene)? - - Track(cmd: TrackCommand) => - cmd.delegate(app, Self::Track)? - - Output(cmd: OutputCommand) => - cmd.delegate(app, Self::Output)? - - Input(cmd: InputCommand) => - cmd.delegate(app, Self::Input)? - - Clip(cmd: ClipCommand) => - cmd.delegate(app, Self::Clip)? - - Clock(cmd: ClockCommand) => - cmd.delegate(app, Self::Clock)? - - Editor(cmd: MidiEditCommand) => app.editor.as_mut() - .map(|editor|cmd.delegate(editor, Self::Editor)) - .transpose()? - .flatten() - - Pool(cmd: PoolCommand) => if let Some(pool) = app.pool.as_mut() { - let undo = cmd.clone().delegate(pool, Self::Pool)?; - if let Some(editor) = app.editor.as_mut() { - match cmd { - // autoselect: automatically load selected clip in editor - // autocolor: update color in all places simultaneously - PoolCommand::Select(_) | PoolCommand::Clip(PoolClipCommand::SetColor(_, _)) => - editor.set_clip(pool.clip().as_ref()), - _ => {} - } - }; - undo - } else { - None - } - - Color(palette: ItemPalette) => { - use Selection::*; - Some(Self::Color(match app.selected { - Mix => { - let old = app.color; - app.color = palette; - old - }, - Track(t) => { - let old = app.tracks[t].color; - app.tracks[t].color = palette; - old - } - Scene(s) => { - let old = app.scenes[s].color; - app.scenes[s].color = palette; - old - } - Clip(t, s) => { - if let Some(ref clip) = app.scenes[s].clips[t] { - let mut clip = clip.write().unwrap(); - let old = clip.color; - clip.color = palette; - old - } else { - return Ok(None) - } - } - })) - } - - Edit(value: Option) => { - let editing = app.is_editing(); - let value = value.unwrap_or_else(||!app.is_editing()); - app.editing.store(value, Relaxed); - if value { - app.clip_auto_create(); - } else { - app.clip_auto_remove(); - } - None - } - - Launch => { - use Selection::*; - match app.selected { - Track(t) => app.tracks[t].player.enqueue_next(None), - Clip(t, s) => app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()), - Scene(s) => { - for t in 0..app.tracks.len() { - app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()) - } - }, - _ => {} - }; - None - } - - Select(s: Selection) => { - app.selected = s; - // autoedit: load focused clip in editor. - if let Some(ref mut editor) = app.editor { - editor.set_clip(match app.selected { - Selection::Clip(t, s) if let Some(Some(Some(clip))) = app - .scenes.get(s).map(|s|s.clips.get(t)) => Some(clip), - _ => None - }); - } - None - } - - StopAll => { - for track in 0..app.tracks.len(){app.tracks[track].player.enqueue_next(None);} - None - } - - } - - InputCommand { - - Add => { - app.midi_ins.push(JackMidiIn::new(&app.jack, &format!("M/{}", app.midi_ins.len()), &[])?); - None - } - - } - - OutputCommand { - - Add => { - app.midi_outs.push(JackMidiOut::new(&app.jack, &format!("{}/M", app.midi_outs.len()), &[])?); - None - } - - } - - TrackCommand { - - Swap(a: usize, b: usize) => { - println!("\n\rtodo: {self:?}"); - None - } - - SetSize(t: usize) => { - println!("\n\rtodo: {self:?}"); - None - } - - SetZoom(z: usize) => { - println!("\n\rtodo: {self:?}"); - None - } - - Add => { - use Selection::*; - let index = app.track_add(None, None, &[], &[])?.0; - app.selected = match app.selected { - Track(t) => Track(index), - Clip(t, s) => Clip(index, s), - _ => app.selected - }; - Some(Self::Del(index)) - } - - Del(index: usize) => { - app.track_del(index); - None - } - - Stop(index: usize) => { - app.tracks[index].player.enqueue_next(None); - None - } - - SetColor(index: usize, color: ItemPalette) => { - let old = app.tracks[index].color; - app.tracks[index].color = color; - Some(Self::SetColor(index, old)) - } - - TogglePlay => { - Some(Self::TogglePlay) - } - - ToggleSolo => { - Some(Self::ToggleSolo) - } - - ToggleRecord => { - if let Some(t) = app.selected.track() { - app.tracks[t-1].player.recording = !app.tracks[t-1].player.recording; - } - Some(Self::ToggleRecord) - } - - ToggleMonitor => { - if let Some(t) = app.selected.track() { - app.tracks[t-1].player.monitoring = !app.tracks[t-1].player.monitoring; - } - Some(Self::ToggleMonitor) - } - - } - - SceneCommand { - - Swap(a: usize, b: usize) => { - println!("\n\rtodo: {self:?}"); - None - } - - SetSize(index: usize) => { - println!("\n\rtodo: {self:?}"); - None - } - - SetZoom(zoom: usize) => { - println!("\n\rtodo: {self:?}"); - None - } - - Add => { - use Selection::*; - let index = app.scene_add(None, None)?.0; - app.selected = match app.selected { - Scene(s) => Scene(index), - Clip(t, s) => Clip(t, index), - _ => app.selected - }; - Some(Self::Del(index)) - } - - Del(index: usize) => { - app.scene_del(index); - None - } - - SetColor(index: usize, color: ItemPalette) => { - let old = app.scenes[index].color; - app.scenes[index].color = color; - Some(Self::SetColor(index, old)) - } - - Enqueue(scene: usize) => { - for track in 0..app.tracks.len() { - app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref()); - } - None - } - - } - - ClipCommand { - - Get(a: usize, b: usize) => { - println!("\n\rtodo: {self:?}"); - None - } - - Edit(clip: Option>>) => { - println!("\n\rtodo: edit {clip:?}"); - None - } - - SetLoop(track: usize, scene: usize, looped: bool) => { - println!("\n\rtodo: {self:?}"); - None - } - - Put(track: usize, scene: usize, clip: Option>>) => { - let old = app.scenes[scene].clips[track].clone(); - app.scenes[scene].clips[track] = clip; - Some(Self::Put(track, scene, old)) - } - - Enqueue(track: usize, scene: usize) => { - app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref()); - None - } - - SetColor(track: usize, scene: usize, color: ItemPalette) => { - app.scenes[scene].clips[track].as_ref().map(|clip|{ - let mut clip = clip.write().unwrap(); - let old = clip.color.clone(); - clip.color = color.clone(); - panic!("{color:?} {old:?}"); - Self::SetColor(track, scene, old) - }) - } - - } +fn delegate_to_editor (app: &mut Tek, cmd: MidiEditCommand) -> Perhaps { + Ok(app.editor.as_mut().map(|editor|cmd.delegate(editor, TekCommand::Editor)) + .transpose()? + .flatten()) +} + +fn delegate_to_pool (app: &mut Tek, cmd: PoolCommand) -> Perhaps { + Ok(if let Some(pool) = app.pool.as_mut() { + let undo = cmd.clone().delegate(pool, TekCommand::Pool)?; + if let Some(editor) = app.editor.as_mut() { + match cmd { + // autoselect: automatically load selected clip in editor + // autocolor: update color in all places simultaneously + PoolCommand::Select(_) | PoolCommand::Clip(PoolClipCommand::SetColor(_, _)) => + editor.set_clip(pool.clip().as_ref()), + _ => {} + } + }; + undo + } else { + None + }) } diff --git a/crates/app/src/device.rs b/crates/app/src/device.rs deleted file mode 100644 index c7b7e813..00000000 --- a/crates/app/src/device.rs +++ /dev/null @@ -1 +0,0 @@ -use crate::*; diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs index 4032b47c..d2ba0823 100644 --- a/crates/app/src/lib.rs +++ b/crates/app/src/lib.rs @@ -38,7 +38,6 @@ 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 model; pub use self::model::*; mod view; pub use self::view::*; mod keys; pub use self::keys::*; @@ -47,7 +46,6 @@ mod keys; pub use self::keys::*; let mut tek = Tek::default(); let _ = tek.clip(); let _ = tek.toggle_loop(); - let _ = tek.activate(); } #[cfg(test)] #[test] fn test_model_scene () { let mut app = Tek::default(); diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index 92f6d88a..a4b323c0 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -7,7 +7,7 @@ pub struct Tek { /// Source of time pub clock: Clock, /// Theme - pub color: ItemPalette, + pub color: ItemTheme, /// Contains all clips in the project pub pool: Option, /// Contains the currently edited MIDI clip @@ -63,15 +63,15 @@ impl Tek { &mut self, count: usize, width: Option, - midi_from: &[PortConnect], - midi_to: &[PortConnect], + mins: &[PortConnect], + mouts: &[PortConnect], ) -> Usually<()> { let jack = self.jack().clone(); let track_color_1 = ItemColor::random(); let track_color_2 = ItemColor::random(); for i in 0..count { let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); - let mut track = self.track_add(None, Some(color), midi_from, midi_to)?.1; + let mut track = self.track_add(None, Some(color), mins, mouts)?.1; if let Some(width) = width { track.width = width; } @@ -82,22 +82,17 @@ impl Tek { /// Add a track pub fn track_add ( &mut self, - name: Option<&str>, - color: Option, - midi_froms: &[PortConnect], - midi_tos: &[PortConnect], + name: Option<&str>, + color: Option, + mins: &[PortConnect], + mouts: &[PortConnect], ) -> Usually<(usize, &mut Track)> { let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into()); let mut track = Track { width: (name.len() + 2).max(12), - color: color.unwrap_or_else(ItemPalette::random), + color: color.unwrap_or_else(ItemTheme::random), player: MidiPlayer::new( - &format!("{name}"), - self.jack(), - Some(self.clock()), - None, - midi_froms, - midi_tos + &format!("{name}"), self.jack(), Some(self.clock()), None, mins, mouts )?, name, ..Default::default() @@ -113,6 +108,17 @@ impl Tek { Ok((index, &mut self.tracks_mut()[index])) } + /// Add and focus a track + pub(crate) fn track_add_focus (&mut self) -> Usually { + let index = self.track_add(None, None, &[], &[])?.0; + self.selected = match self.selected { + Selection::Track(t) => Selection::Track(index), + Selection::Clip(t, s) => Selection::Clip(index, s), + _ => self.selected + }; + Ok(index) + } + /// Delete a track pub fn track_del (&mut self, index: usize) { self.tracks_mut().remove(index); @@ -134,22 +140,46 @@ impl Tek { } /// Add a scene - pub fn scene_add (&mut self, name: Option<&str>, color: Option) + pub fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<(usize, &mut Scene)> { let scene = Scene { name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()), clips: vec![None;self.tracks().len()], - color: color.unwrap_or_else(ItemPalette::random), + color: color.unwrap_or_else(ItemTheme::random), }; self.scenes_mut().push(scene); let index = self.scenes().len() - 1; Ok((index, &mut self.scenes_mut()[index])) } - /// Generate the default name for a new scene - pub fn scene_default_name (&self) -> Arc { - format!("Sc{:3>}", self.scenes().len() + 1).into() + /// Add and focus an empty scene + pub fn scene_add_focus (&mut self) -> Usually { + let index = self.scene_add(None, None)?.0; + self.selected = match self.selected { + Selection::Scene(s) => Selection::Scene(index), + Selection::Clip(t, s) => Selection::Clip(t, index), + _ => self.selected + }; + Ok(index) + } + + /// Enqueue clips from a scene across all tracks + pub fn scene_enqueue (&mut self, scene: usize) { + for track in 0..self.tracks.len() { + self.tracks[track].player.enqueue_next(self.scenes[scene].clips[track].as_ref()); + } + } + + pub fn toggle_editor (&mut self, value: Option) { + let editing = self.is_editing(); + let value = value.unwrap_or_else(||!self.is_editing()); + self.editing.store(value, Relaxed); + if value { + self.clip_auto_create(); + } else { + self.clip_auto_remove(); + } } // Create new clip in pool when entering empty cell @@ -194,11 +224,43 @@ impl Tek { } } + /// Put a clip in a slot + pub(crate) fn clip_put ( + &mut self, track: usize, scene: usize, clip: Option>> + ) -> Option>> { + let old = self.scenes[scene].clips[track].clone(); + self.scenes[scene].clips[track] = clip; + old + } + + /// Change the color of a clip, returning the previous one + pub(crate) fn clip_set_color ( + &self, track: usize, scene: usize, color: ItemTheme + ) -> Option { + self.scenes[scene].clips[track].as_ref().map(|clip|{ + let mut clip = clip.write().unwrap(); + let old = clip.color.clone(); + clip.color = color.clone(); + panic!("{color:?} {old:?}"); + old + }) + } + + /// Get the clip pool, if present + pub(crate) fn pool (&self) -> Option<&MidiPool> { + self.pool.as_ref() + } + /// Get the active clip pub(crate) fn clip (&self) -> Option>> { self.scene()?.clips.get(self.selected().track()?)?.clone() } + /// Get the active editor + pub(crate) fn editor (&self) -> Option<&MidiEditor> { + self.editor.as_ref() + } + /// Toggle looping for the active clip pub(crate) fn toggle_loop (&mut self) { if let Some(clip) = self.clip() { @@ -206,30 +268,88 @@ impl Tek { } } + /// Set the selection + pub(crate) fn select (&mut self, s: Selection) { + self.selected = s; + // autoedit: load focused clip in editor. + if let Some(ref mut editor) = self.editor { + editor.set_clip(match self.selected { + Selection::Clip(t, s) if let Some(Some(Some(clip))) = self + .scenes.get(s).map(|s|s.clips.get(t)) => Some(clip), + _ => None + }); + } + } + + /// Stop all playing clips + pub(crate) fn stop_all (&mut self) { + for track in 0..self.tracks.len() { + self.tracks[track].player.enqueue_next(None); + } + } + /// Launch a clip or scene - pub(crate) fn activate (&mut self) -> Usually<()> { - let selected = self.selected().clone(); - match selected { - Selection::Scene(s) => { - let mut clips = vec![]; - for (t, _) in self.tracks().iter().enumerate() { - clips.push(self.scenes()[s].clips[t].clone()); - } - for (t, track) in self.tracks_mut().iter_mut().enumerate() { - if track.player.play_clip.is_some() || clips[t].is_some() { - track.player.enqueue_next(clips[t].as_ref()); - } - } - if self.clock().is_stopped() { - self.clock().play_from(Some(0))?; - } + pub(crate) fn launch (&mut self) { + match self.selected { + Selection::Track(t) => { + self.tracks[t].player.enqueue_next(None) }, Selection::Clip(t, s) => { - let clip = self.scenes()[s].clips[t].clone(); - self.tracks_mut()[t].player.enqueue_next(clip.as_ref()); + self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()) + }, + Selection::Scene(s) => { + for t in 0..self.tracks.len() { + self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()) + } }, _ => {} - } + }; + } + + /// Get the first sampler of the active track + pub fn sampler (&self) -> Option<&Sampler> { + self.tracks.get(0).map(|t|t.sampler(0)).flatten() + } + + /// Set the color of the selected entity + pub fn set_color (&mut self, palette: Option) -> Option { + let palette = palette.unwrap_or_else(||ItemTheme::random()); + Some(match self.selected { + Selection::Mix => { + let old = self.color; + self.color = palette; + old + }, + Selection::Track(t) => { + let old = self.tracks[t].color; + self.tracks[t].color = palette; + old + } + Selection::Scene(s) => { + let old = self.scenes[s].color; + self.scenes[s].color = palette; + old + } + Selection::Clip(t, s) => { + if let Some(ref clip) = self.scenes[s].clips[t] { + let mut clip = clip.write().unwrap(); + let old = clip.color; + clip.color = palette; + old + } else { + return None + } + } + }) + } + + pub(crate) fn add_midi_in (&mut self) -> Usually<()> { + self.midi_ins.push(JackMidiIn::new(&self.jack, &format!("M/{}", self.midi_ins.len()), &[])?); + Ok(()) + } + + pub(crate) fn add_midi_out (&mut self) -> Usually<()> { + self.midi_outs.push(JackMidiOut::new(&self.jack, &format!("{}/M", self.midi_outs.len()), &[])?); Ok(()) } @@ -379,6 +499,17 @@ pub trait HasScenes: HasSelection + HasEditor + Send + Sync { fn scene_del (&mut self, index: usize) { self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index))); } + /// Set the color of a scene, returning the previous one. + fn scene_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme { + let scenes = self.scenes_mut(); + let old = scenes[index].color; + scenes[index].color = color; + old + } + /// Generate the default name for a new scene + fn scene_default_name (&self) -> Arc { + format!("Sc{:3>}", self.scenes().len() + 1).into() + } } #[derive(Debug, Default)] pub struct Scene { @@ -387,7 +518,7 @@ pub trait HasScenes: HasSelection + HasEditor + Send + Sync { /// Clips in scene, one per track pub clips: Vec>>>, /// Identifying color of scene - pub color: ItemPalette, + pub color: ItemTheme, } impl Scene { @@ -443,6 +574,27 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync fn track_mut (&mut self) -> Option<&mut Track> { self.selected().track().and_then(|s|self.tracks_mut().get_mut(s)) } + /// Set the color of a track + fn track_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme { + let tracks = self.tracks_mut(); + let old = tracks[index].color; + tracks[index].color = color; + old + } + /// Toggle track recording + fn track_toggle_record (&mut self) { + if let Some(t) = self.selected().track() { + let tracks = self.tracks_mut(); + tracks[t-1].player.recording = !tracks[t-1].player.recording; + } + } + /// Toggle track monitoring + fn track_toggle_monitor (&mut self) { + if let Some(t) = self.selected().track() { + let tracks = self.tracks_mut(); + tracks[t-1].player.monitoring = !tracks[t-1].player.monitoring; + } + } } #[derive(Debug, Default)] pub struct Track { @@ -451,7 +603,7 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync /// Preferred width of track column pub width: usize, /// Identifying color of track - pub color: ItemPalette, + pub color: ItemTheme, /// MIDI player state pub player: MidiPlayer, /// Device chain diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index e3bbc305..18f7d105 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -2,37 +2,6 @@ use crate::*; pub(crate) use std::fmt::Write; pub(crate) use ::tengri::tui::ratatui::prelude::Position; -view!(TuiOut: |self: Tek| { - self.size.of(View(self, self.view)) -}; { - ":nil" => - Box::new("nil"), - ":modal" => - When::new(self.modal.is_some(), Bsp::b( - Fill::xy(Tui::fg_bg(Color::Rgb(64,64,64), Color::Rgb(32,32,32), "")), - Fixed::xy(30, 15, Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(16,16,16), self.modal)) - )).boxed(), - ":transport" => - self.view_transport().boxed(), - ":arranger" => - ArrangerView::new(self).boxed(), - ":editor" => - self.editor.as_ref() - .map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e)) - .boxed(), - ":samples-keys" => - self.tracks[0].sampler(0).map(|s|s.view_list(false, self.editor.as_ref().unwrap())).boxed(), - ":samples-grid" => - self.tracks[0].sampler(0).map(|s|s.view_grid()).boxed(), - ":sample-viewer" => - self.tracks[0].sampler(0).map(|s|s.view_sample(self.editor.as_ref().unwrap().note_pos())).boxed(), - ":status" => - self.view_status().boxed(), - ":pool" => self.pool.as_ref() - .map(|pool|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), pool))) - .boxed(), -}); - pub(crate) struct ArrangerView<'a> { app: &'a Tek, @@ -317,7 +286,7 @@ impl<'a> ArrangerView<'a> { let clip = clip.read().unwrap(); (Some(clip.name.clone()), clip.color) } else { - (None, ItemPalette::G[32]) + (None, ItemTheme::G[32]) }; let height = (1 + y2 - y1) as u16; let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name)))); @@ -392,7 +361,7 @@ impl<'a> ArrangerView<'a> { } else { Some(self.app.scenes[s-1].clips[track].as_ref() .map(|c|c.read().unwrap().color) - .unwrap_or(ItemPalette::G[32])) + .unwrap_or(ItemTheme::G[32])) })) } ) @@ -402,7 +371,7 @@ impl<'a> ArrangerView<'a> { trait ScenesColors<'a> = Iterator>; -type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option); +type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option); impl Tek { /// Spacing between tracks. @@ -537,6 +506,21 @@ impl Tek { ) } + pub fn view_modal (&self) -> impl Content + use<'_> { + When::new(self.modal.is_some(), Bsp::b( + Fill::xy(Tui::fg_bg(Color::Rgb(64,64,64), Color::Rgb(32,32,32), "")), + Fixed::xy(30, 15, Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(16,16,16), self.modal)) + )) + } + + pub fn view_arranger (&self) -> impl Content + use<'_> { + ArrangerView::new(self) + } + + pub fn view_pool (&self) -> impl Content + use<'_> { + self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p))) + } + } /// Define a type alias for iterators of sized items (columns). @@ -563,7 +547,7 @@ fn view_transport ( beat: Arc>, time: Arc>, ) -> impl Content { - let theme = ItemPalette::G[96]; + let theme = ItemTheme::G[96]; Tui::bg(Black, row!(Bsp::a( Fill::xy(Align::w(button_play_pause(play))), Fill::xy(Align::e(row!( @@ -580,7 +564,7 @@ fn view_status ( buf: Arc>, lat: Arc>, ) -> impl Content { - let theme = ItemPalette::G[96]; + let theme = ItemTheme::G[96]; Tui::bg(Black, row!(Bsp::a( Fill::xy(Align::w(FieldH(theme, "Selected", sel))), Fill::xy(Align::e(row!( @@ -610,7 +594,7 @@ pub(crate) fn button_play_pause (playing: bool) -> impl Content { pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content + 'a { col!( - FieldH(ItemPalette::G[128], label, format!("{:>+9.3}", value)), + FieldH(ItemTheme::G[128], label, format!("{:>+9.3}", value)), Fixed::xy(if value >= 0.0 { 13 } else if value >= -1.0 { 12 } else if value >= -2.0 { 11 } diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 8abb97c1..0f38911b 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -103,7 +103,7 @@ impl Cli { } let mut app = Tek { jack: jack.clone(), - color: ItemPalette::random(), + color: ItemTheme::random(), clock: Clock::new(jack, self.bpm)?, view: SourceIter(match mode { LaunchMode::Clock => include_str!("./edn/transport.edn"), diff --git a/crates/midi/midi.scratch.rs b/crates/midi/midi.scratch.rs index cdd1807e..b4b350eb 100644 --- a/crates/midi/midi.scratch.rs +++ b/crates/midi/midi.scratch.rs @@ -35,14 +35,14 @@ //return None //}, //kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Clip(PoolClipCommand::Add(count, MidiClip::new( - //"Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) + //"Clip", true, 4 * PPQ, None, Some(ItemTheme::random()) //))), //kpat!(Char('i')) => Cmd::Clip(PoolClipCommand::Add(index + 1, MidiClip::new( - //"Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) + //"Clip", true, 4 * PPQ, None, Some(ItemTheme::random()) //))), //kpat!(Char('d')) | kpat!(Shift-Char('D')) => { //let mut clip = state.clips()[index].read().unwrap().duplicate(); - //clip.color = ItemPalette::random_near(clip.color, 0.25); + //clip.color = ItemTheme::random_near(clip.color, 0.25); //Cmd::Clip(PoolClipCommand::Add(index + 1, clip)) //}, //_ => return None diff --git a/crates/midi/src/clip.rs b/crates/midi/src/clip.rs index a7f998ed..95295621 100644 --- a/crates/midi/src/clip.rs +++ b/crates/midi/src/clip.rs @@ -1,5 +1,3 @@ -use crate::*; - mod clip_editor; pub use self::clip_editor::*; mod clip_launch; pub use self::clip_launch::*; mod clip_model; pub use self::clip_model::*; diff --git a/crates/midi/src/clip/clip_editor.rs b/crates/midi/src/clip/clip_editor.rs index 407269b4..99f98e33 100644 --- a/crates/midi/src/clip/clip_editor.rs +++ b/crates/midi/src/clip/clip_editor.rs @@ -115,7 +115,7 @@ impl MidiEditor { pub fn clip_status (&self) -> impl Content + '_ { let (color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { (clip.color, clip.name.clone(), clip.length, clip.looped) - } else { (ItemPalette::G[64], String::new().into(), 0, false) }; + } else { (ItemTheme::G[64], String::new().into(), 0, false) }; Bsp::e( FieldH(color, "Edit", format!("{name} ({length})")), FieldH(color, "Loop", looped.to_string()) @@ -125,7 +125,7 @@ impl MidiEditor { pub fn edit_status (&self) -> impl Content + '_ { let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { (clip.color, clip.length) - } else { (ItemPalette::G[64], 0) }; + } else { (ItemTheme::G[64], 0) }; let time_pos = self.time_pos(); let time_zoom = self.time_zoom().get(); let time_lock = if self.time_lock().get() { "[lock]" } else { " " }; diff --git a/crates/midi/src/clip/clip_launch.rs b/crates/midi/src/clip/clip_launch.rs index 7c17903b..ee2f682e 100644 --- a/crates/midi/src/clip/clip_launch.rs +++ b/crates/midi/src/clip/clip_launch.rs @@ -39,7 +39,7 @@ pub trait HasPlayClip: HasClock { } fn play_status (&self) -> impl Content { - let (name, color): (Arc, ItemPalette) = if let Some((_, Some(clip))) = self.play_clip() { + let (name, color): (Arc, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() { let MidiClip { ref name, color, .. } = *clip.read().unwrap(); (name.clone(), color) } else { @@ -54,7 +54,7 @@ pub trait HasPlayClip: HasClock { fn next_status (&self) -> impl Content { let mut time: Arc = String::from("--.-.--").into(); let mut name: Arc = String::from("").into(); - let mut color = ItemPalette::G[64]; + let mut color = ItemTheme::G[64]; let clock = self.clock(); if let Some((t, Some(clip))) = self.next_clip() { let clip = clip.read().unwrap(); diff --git a/crates/midi/src/clip/clip_model.rs b/crates/midi/src/clip/clip_model.rs index 3fb8c5bd..83762fe1 100644 --- a/crates/midi/src/clip/clip_model.rs +++ b/crates/midi/src/clip/clip_model.rs @@ -34,7 +34,7 @@ pub struct MidiClip { /// All notes are displayed with minimum length pub percussive: bool, /// Identifying color of clip - pub color: ItemPalette, + pub color: ItemTheme, } /// MIDI message structural @@ -46,7 +46,7 @@ impl MidiClip { looped: bool, length: usize, notes: Option, - color: Option, + color: Option, ) -> Self { Self { uuid: uuid::Uuid::new_v4(), @@ -58,7 +58,7 @@ impl MidiClip { loop_start: 0, loop_length: length, percussive: true, - color: color.unwrap_or_else(ItemPalette::random) + color: color.unwrap_or_else(ItemTheme::random) } } pub fn count_midi_messages (&self) -> usize { diff --git a/crates/midi/src/mode.rs b/crates/midi/src/mode.rs index 6a43eb54..6fd9c01c 100644 --- a/crates/midi/src/mode.rs +++ b/crates/midi/src/mode.rs @@ -1,8 +1,8 @@ use crate::*; -mod mode_browse; pub use self::mode_browse::*; mod mode_length; pub use self::mode_length::*; mod mode_rename; pub use self::mode_rename::*; +mod mode_browse; /// Modes for clip pool #[derive(Debug, Clone)] diff --git a/crates/midi/src/piano/piano_h.rs b/crates/midi/src/piano/piano_h.rs index c647da33..2bc0cc5f 100644 --- a/crates/midi/src/piano/piano_h.rs +++ b/crates/midi/src/piano/piano_h.rs @@ -13,7 +13,7 @@ pub struct PianoHorizontal { /// The note cursor pub point: MidiPointModel, /// The highlight color palette - pub color: ItemPalette, + pub color: ItemTheme, /// Width of the keyboard pub keys_width: u16, } @@ -31,7 +31,7 @@ impl PianoHorizontal { buffer: RwLock::new(Default::default()).into(), point: MidiPointModel::default(), clip: clip.cloned(), - color: clip.as_ref().map(|p|p.read().unwrap().color).unwrap_or(ItemPalette::G[64]), + color: clip.as_ref().map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]), }; piano.redraw(); piano @@ -281,7 +281,7 @@ impl MidiViewer for PianoHorizontal { fn set_clip (&mut self, clip: Option<&Arc>>) { *self.clip_mut() = clip.cloned(); self.color = clip.map(|p|p.read().unwrap().color) - .unwrap_or(ItemPalette::G[64]); + .unwrap_or(ItemTheme::G[64]); self.redraw(); } } diff --git a/crates/midi/src/pool/pool_api.rs b/crates/midi/src/pool/pool_api.rs index d7c7bda8..ae1c2dd5 100644 --- a/crates/midi/src/pool/pool_api.rs +++ b/crates/midi/src/pool/pool_api.rs @@ -168,7 +168,7 @@ impl Command for PoolClipCommand { Some(Self::SetLength(index, old_len)) }, SetColor(index, color) => { - let mut color = ItemPalette::from(color); + let mut color = ItemTheme::from(color); std::mem::swap(&mut color, &mut model.clips()[index].write().unwrap().color); Some(Self::SetColor(index, color.base)) }, diff --git a/crates/midi/src/pool/pool_model.rs b/crates/midi/src/pool/pool_model.rs index 87112a17..f83f5a4a 100644 --- a/crates/midi/src/pool/pool_model.rs +++ b/crates/midi/src/pool/pool_model.rs @@ -85,12 +85,12 @@ impl MidiPool { Ok(()) } pub fn new_clip (&self) -> MidiClip { - MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemPalette::random())) + MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random())) } pub fn cloned_clip (&self) -> MidiClip { let index = self.clip_index(); let mut clip = self.clips()[index].read().unwrap().duplicate(); - clip.color = ItemPalette::random_near(clip.color, 0.25); + clip.color = ItemTheme::random_near(clip.color, 0.25); clip } pub fn add_new_clip (&self) -> (usize, Arc>) { diff --git a/crates/sampler/sampler_scratch.rs b/crates/sampler/sampler_scratch.rs index 2a46396d..4bbce539 100644 --- a/crates/sampler/sampler_scratch.rs +++ b/crates/sampler/sampler_scratch.rs @@ -127,7 +127,7 @@ //}); //struct SamplesTui { - //color: ItemPalette, + //color: ItemTheme, //note_hi: usize, //note_pt: usize, //height: usize, diff --git a/crates/sampler/src/lib.rs b/crates/sampler/src/lib.rs index 23e49095..8ddfc4b6 100644 --- a/crates/sampler/src/lib.rs +++ b/crates/sampler/src/lib.rs @@ -22,12 +22,13 @@ pub(crate) use symphonia::{ pub(crate) use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Line}}}; mod sampler_api; pub use self::sampler_api::*; -mod sampler_data; pub use self::sampler_data::*; mod sampler_audio; pub use self::sampler_audio::*; mod sampler_browse; pub use self::sampler_browse::*; mod sampler_midi; pub use self::sampler_midi::*; mod sampler_model; pub use self::sampler_model::*; -mod sampler_view; pub use self::sampler_view::*; + +mod sampler_data; +mod sampler_view; #[cfg(test)] #[test] fn test_sampler () { // TODO! diff --git a/crates/sampler/src/sampler_api.rs b/crates/sampler/src/sampler_api.rs index 5720c17e..900f2644 100644 --- a/crates/sampler/src/sampler_api.rs +++ b/crates/sampler/src/sampler_api.rs @@ -1,25 +1,48 @@ use crate::*; -expose! { - [self: Sampler] { - [Arc] => {} - [Option>>] => {} - [PathBuf] => {} - [f32] => {} - [u7] => { - ":sample" => (self.note_pos() as u8).into(), - } - [usize] => { - ":sample-up" => self.note_pos().min(119) + 8, - ":sample-down" => self.note_pos().max(8) - 8, - ":sample-left" => self.note_pos().min(126) + 1, - ":sample-right" => self.note_pos().max(1) - 1, - } - } -} +expose!([self: Sampler] + ([Arc]) + ([Option>>]) + ([PathBuf]) + ([f32]) + ([u7] (":pitch" (self.note_pos() as u8).into()) // TODO + (":sample" (self.note_pos() as u8).into())) + ([usize] (":sample-up" self.note_pos().min(119) + 8) + (":sample-down" self.note_pos().max(8) - 8) + (":sample-left" self.note_pos().min(126) + 1) + (":sample-right" self.note_pos().max(1) - 1))); + +impose!([state: Sampler] + (FileBrowserCommand: + ("begin" [] Some(Self::Begin)) + ("cancel" [] Some(Self::Cancel)) + ("confirm" [] Some(Self::Confirm)) + ("select" [i: usize] Some(Self::Select(i.expect("no index")))) + ("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path")))) + ("filter" [f: Arc] Some(Self::Filter(f.expect("no filter"))))) + (SamplerCommand: + ("import" [,..a] + FileBrowserCommand::try_from_expr(state, a).map(Self::Import)) + ("select" [i: usize] + Some(Self::Select(i.expect("no index")))) + ("record/begin" [i: u7] + Some(Self::RecordBegin(i.expect("no index")))) + ("record/cancel" [] + Some(Self::RecordCancel)) + ("record/finish" [] + Some(Self::RecordFinish)) + ("set/sample" [i: u7, s: Option>>] + Some(Self::SetSample(i.expect("no index"), s.expect("no sampler")))) + ("set/start" [i: u7, s: usize] + Some(Self::SetStart(i.expect("no index"), s.expect("no start")))) + ("set/gain" [i: u7, g: f32] + Some(Self::SetGain(i.expect("no index"), g.expect("no gain")))) + ("note/on" [p: u7, v: u7] + Some(Self::NoteOn(p.expect("no pitch"), v.expect("no velocity")))) + ("note/off" [p: u7] + Some(Self::NoteOff(p.expect("no pitch")))))); defcom! { |self, state: Sampler| - SamplerCommand { Import(cmd: FileBrowserCommand) => match cmd { @@ -86,28 +109,3 @@ defcom! { |self, state: Sampler| } } - -atom_command!(FileBrowserCommand: |state: Sampler| { - ("begin" [] Some(Self::Begin)) - ("cancel" [] Some(Self::Cancel)) - ("confirm" [] Some(Self::Confirm)) - ("select" [i: usize] Some(Self::Select(i.expect("no index")))) - ("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path")))) - ("filter" [f: Arc] Some(Self::Filter(f.expect("no filter")))) }); - -atom_command!(SamplerCommand: |state: Sampler| { - ("import" [,..a] FileBrowserCommand::try_from_expr(state, a).map(Self::Import)) - ("select" [i: usize] Some(Self::Select(i.expect("no index")))) - ("record/begin" [i: u7] Some(Self::RecordBegin(i.expect("no index")))) - ("record/cancel" [] Some(Self::RecordCancel)) - ("record/finish" [] Some(Self::RecordFinish)) - ("set/sample" [i: u7, s: Option>>] - Some(Self::SetSample(i.expect("no index"), s.expect("no sampler")))) - ("set/start" [i: u7, s: usize] - Some(Self::SetStart(i.expect("no index"), s.expect("no start")))) - ("set/gain" [i: u7, g: f32] - Some(Self::SetGain(i.expect("no index"), g.expect("no garin")))) - ("note/on" [p: u7, v: u7] - Some(Self::NoteOn(p.expect("no pitch"), v.expect("no velocity")))) - ("note/off" [p: u7] - Some(Self::NoteOff(p.expect("no pitch")))) }); diff --git a/crates/sampler/src/sampler_model.rs b/crates/sampler/src/sampler_model.rs index 794b3755..852386eb 100644 --- a/crates/sampler/src/sampler_model.rs +++ b/crates/sampler/src/sampler_model.rs @@ -24,7 +24,7 @@ pub struct Sampler { pub note_pt: AtomicUsize, /// Selected note as row/col pub cursor: (AtomicUsize, AtomicUsize), - pub color: ItemPalette + pub color: ItemTheme } impl Default for Sampler { diff --git a/crates/time/src/clock.rs b/crates/time/src/clock.rs index 4a7770b3..8a16808d 100644 --- a/crates/time/src/clock.rs +++ b/crates/time/src/clock.rs @@ -1,5 +1,3 @@ -use crate::*; - mod clock_api; pub use self::clock_api::*; mod clock_model; pub use self::clock_model::*; diff --git a/deps/tengri b/deps/tengri index 844681d6..7ba37f3f 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 844681d6add6838a173ddc2a5c3bb9d51451d2e1 +Subproject commit 7ba37f3f0255c2c61ed3371e2c437514f27f6d6d