From c5d6ce24b2b94d23b44fff4276417f521c65e2bf Mon Sep 17 00:00:00 2001 From: i do not exist Date: Sun, 20 Sep 2026 15:33:42 +0300 Subject: [PATCH] wip: simplify clip traits to implement upsert --- src/app.rs | 17 +++- src/config_bind.rs | 4 +- src/midi_clip.rs | 208 +++++++++++++++++++++++++++++++-------------- src/midi_clips.rs | 114 ------------------------- src/pool.rs | 19 ++++- 5 files changed, 178 insertions(+), 184 deletions(-) diff --git a/src/app.rs b/src/app.rs index 4f4a0d48..4bb2ea60 100644 --- a/src/app.rs +++ b/src/app.rs @@ -75,7 +75,6 @@ pub struct App { pub project: Project, /// Error, if any pub error: Arc>>>, - #[cfg(feature = "prof2")] /// Tracing guard pub guard: Option>>, @@ -278,7 +277,6 @@ impl_has!(Dialog: |self: App|self.dialog); impl_has!(Jack<'static>: |self: App|self.jack); impl_has!(Pool: |self: App|self.pool); impl_has!(Selection: |self: App|self.project.selection); -impl_has_clips!( |self: App|self.pool.clips); impl_as_ref!(Option: |self: App|self.project.as_ref()); impl_as_mut!(Option: |self: App|self.project.as_mut()); @@ -287,6 +285,21 @@ primitive!(u16: try_to_u16); primitive!(usize: try_to_usize); primitive!(isize: try_to_isize); +impl HasClips for App { + fn clip (&self) -> Option>> { + self.project.clip() + } + fn clip_mut (&self) -> &mut Option>> { + self.project.clip_mut() + } + fn clips <'a> (&'a self) -> RwLockReadGuard<'a, Clips> { + self.project.clips() + } + fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, Clips> { + self.project.clips_mut() + } +} + tui_keys!(self: App, input { #[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!"); Self::input_command(self, input) diff --git a/src/config_bind.rs b/src/config_bind.rs index 243cfda5..440feab3 100644 --- a/src/config_bind.rs +++ b/src/config_bind.rs @@ -34,8 +34,10 @@ impl Binds { let expr = item.expr()?; let head = expr.head()?; if let Some(event) = TuiKey::from_dsl(&head)?.to_crossterm() { + let mut commands: Vec> = vec![]; + item.expr()?.tail()?.each((), |_, def|Ok(commands.push(def.into())))?; map.add(TuiEvent(event), Binding { - commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(), + commands: commands.into(), condition: None, description: None, source: None diff --git a/src/midi_clip.rs b/src/midi_clip.rs index 08529843..848402fb 100644 --- a/src/midi_clip.rs +++ b/src/midi_clip.rs @@ -1,35 +1,16 @@ use crate::*; -pub fn get_opt_arc_rwlock_midiclip <'a> (state: &App, src: impl Language) - -> Perhaps>>> -{ - eval_word(src, |word|Ok(match word { +pub type Clips = Vec>>; - ":selected" if let Selection::TrackClip { - track, scene - } = state.selection() => { - state.scenes()[*scene].clips[*track].clone() - }, - - //":selected-or-create" if let Selection::TrackClip { - //track, scene - //} = state.selection() => Some(if let Some(Some(clip)) = state.scenes()[*scene].clips.get(*track) { - //clip.clone() - //} else { - //let clip: Arc> = Arc::new(RwLock::new(Default::default())); - //state.scenes()[*scene].clips[*track] = Some(clip.clone()); - //clip - //}), - - _ => return err!("not a clip: {word}") - - })) -} - -pub fn get_opt_vec_opt_arc_rwlock_midiclip <'a> (_: &App, _: impl Language) - -> Perhaps>>>>> -{ - todo!() +pub trait HasClips { + /// Active clip slot (immutable). Use this to access or modify current clip. + fn clip (&self) -> Option>>; + /// Active clip slot (mutable). Use this to place or remove a clip at current position. + fn clip_mut (&self) -> &mut Option>>; + /// Clip list (immutable). Use this to access or modify more than one clip. + fn clips <'a> (&'a self) -> RwLockReadGuard<'a, Clips>; + /// Clip list (mutable). Use this to define or delete new clips. + fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, Clips>; } /// A MIDI sequence. @@ -96,7 +77,32 @@ impl MidiClip { clone.uuid = uuid::Uuid::new_v4(); clone } - pub fn toggle_loop (&mut self) { self.looped = !self.looped; } + /// Change the color of a clip, returning the previous color. + /// If value not provided, randomize. + pub fn set_color (&mut self, color: Option) -> ItemTheme { + let mut color = color.unwrap_or_else(||ItemTheme::random()); + std::mem::swap(&mut color, &mut self.color); + color + } + /// Set loop mode for a clip, returning the previous loop state. + /// If value not provided, toggle. + pub fn toggle_loop (&mut self, looping: Option) -> bool { + match looping { + Some(true) => { + self.looped = true; + false + }, + Some(false) => { + self.looped = false; + true + }, + None => { + self.looped = !self.looped; + !self.looped + } + } + } + /// Add a [MidiMessage] at a given `pulse` position of this clip. pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { if pulse >= self.length { panic!("extend clip first") } self.notes[pulse].push(message); @@ -110,40 +116,26 @@ impl MidiClip { } false } + /// Create a panic clip. pub fn stop_all () -> Self { - Self::new( - "Stop", - false, - 1, - Some(vec![vec![MidiMessage::Controller { - controller: 123.into(), - value: 0.into() - }]]), - Some(ItemColor::from_tui(Color::Rgb(32, 32, 32)).into()) - ) + let color = Some(ItemColor::from_tui(Color::Rgb(32, 32, 32)).into()); + Self::new("Stop", false, 1, Some(vec![vec![MIDI_PANIC]]), color) } } +const MIDI_PANIC: MidiMessage = MidiMessage::Controller { + controller: u7::new(123), value: u7::new(0), +}; + pub type MidiData = Vec>; impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } impl Eq for MidiClip {} -#[macro_export] macro_rules! has_clip { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? { - fn clip (&$self) -> Option>> { $cb } - } - } +impl HasMidiClip for Project { + fn clip (&self) -> Option>> { None } } - -pub trait HasMidiClip { - fn clip (&self) -> Option>>; -} - impl HasMidiClip for App { - fn clip (&self) -> Option>> { - None - } + fn clip (&self) -> Option>> { self.project.clip() } } impl> + Namespace> { + + /// Create a clip at the current position if it does not already exist. #[command(Upsert = "upsert")] fn clip_upsert (&mut self) -> Perhaps { + let clip = self.clip(); + //let old = self.scenes[scene].clips[track].clone(); + //self.scenes[scene].clips[track] = clip; + //old todo!() } #[command(SetColor = "color")] - fn clip_set_color (&mut self, _color: Option) -> Perhaps { - //(SetColor [t: usize, s: usize, c: ItemTheme] - //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); - //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) - todo!() + fn clip_set_color (&mut self, color: Option) -> Perhaps { + Ok(self.clip().map(|clip|MidiClipCommand::SetColor { + color: Some(clip.write().set_color(color)) + })) } + /// Toggle looping for the active clip #[command(SetLoop = "loop")] - fn clip_toggle_loop (&mut self, _looping: Option) -> Perhaps { - //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) - //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) - todo!() + fn clip_toggle_loop (&mut self, looping: Option) -> Perhaps { + Ok(self.clip().map(|clip|MidiClipCommand::SetLoop { + looping: Some(clip.write().toggle_loop(looping)) + })) } } + +impl HasClipsSize for App { fn clips_size (&self) -> &Sizer { &self.project.size_inner } } +impl HasClipsSize for Project { fn clips_size (&self) -> &Sizer { &self.size_inner } } +pub trait HasClipsSize { + /// Size of clips area. + fn clips_size (&self) -> &Sizer; + /// Draw content annotated with value of [Self::clips_size]. + fn with_clips_size (&self, show: bool, content: impl Draw) -> impl Draw { + let size = self.clips_size(); + above( + when(show, fg(Green, east!(size.w() as usize, "x", size.h() as usize)).align_se()), + size.of(content.align_c().full_wh()) + ) + } +} + +impl ClipsView for T {} + +pub trait ClipsView: TracksView + ScenesView { + + /// Draw clips per scene + fn view_scenes_clips (&self) -> impl Draw { + let select = self.selection(); + let editor = self.editor(); + let editing = self.is_editing(); + self.with_clips_size(true, iter_east(move||self.tracks_with_sizes() + .map(move|(track_index, track, _, _)|iter_south(move||self.scenes_with_sizes() + .map(move|(scene_index, scene, _, _)|{ + let (name, theme): (Arc, ItemTheme) = view_scene_name_theme(scene, track_index); + let f = theme.lightest.term; + let (b, o) = view_scene_bg(theme, select, track_index, scene_index); + let is_selected = view_scene_sel(select, track_index, scene_index, editing); + below( + Outer(true, Style::default().fg(o)).full_wh(), + below( + below( + fg_bg(o, b, "".full_wh()), + fg_bg(f, b, bold(true, name)).align_nw().full_wh(), + ), + when(is_selected, editor).full_wh() + ).full_wh() + ).exact_wh( + view_scene_w(track, select, track_index, editor.as_ref()), + view_scene_y(select, scene_index, editor.as_ref()), + ) + }) + ) + .exact_w(track.width as u16)) + )).align_c() + } + +} + +pub fn get_opt_arc_rwlock_midiclip <'a> (state: &App, src: impl Language) + -> Perhaps>>> +{ + eval_word(src, |word|Ok(match word { + + ":selected" if let Selection::TrackClip { + track, scene + } = state.selection() => { + state.scenes()[*scene].clips[*track].clone() + }, + + //":selected-or-create" if let Selection::TrackClip { + //track, scene + //} = state.selection() => Some(if let Some(Some(clip)) = state.scenes()[*scene].clips.get(*track) { + //clip.clone() + //} else { + //let clip: Arc> = Arc::new(RwLock::new(Default::default())); + //state.scenes()[*scene].clips[*track] = Some(clip.clone()); + //clip + //}), + + _ => return err!("not a clip: {word}") + + })) +} + +pub fn get_opt_vec_opt_arc_rwlock_midiclip <'a> (_: &App, _: impl Language) + -> Perhaps>>>>> +{ + todo!() +} diff --git a/src/midi_clips.rs b/src/midi_clips.rs index ba2523c7..c7b7e813 100644 --- a/src/midi_clips.rs +++ b/src/midi_clips.rs @@ -1,115 +1 @@ use crate::*; - -pub type ClipPool = Vec>>; - -pub trait HasClips { - fn clips <'a> (&'a self) -> RwLockReadGuard<'a, ClipPool>; - fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, ClipPool>; - fn add_clip (&self) -> (usize, Arc>) { - let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None))); - self.clips_mut().push(clip.clone()); - (self.clips().len() - 1, clip) - } -} - -/// TODO: Preserve the generic passthru syntax; -/// remove this macro (only used twice) and potentially the trait. -#[macro_export] macro_rules! impl_has_clips { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? { - fn clips <'a> (&'a $self) -> ::tengri::parking_lot::RwLockReadGuard<'a, ClipPool> { - $cb.try_read().unwrap() - } - fn clips_mut <'a> (&'a $self) -> ::tengri::parking_lot::RwLockWriteGuard<'a, ClipPool> { - $cb.try_write().unwrap() - } - } - } -} - -impl Project { - /// Toggle looping for the active clip - pub fn toggle_loop (&mut self) { - if let Some(clip) = self.selected_clip() { - clip.try_write().unwrap().toggle_loop() - } - } - - /// Put a clip in a slot - pub 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 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.try_write().unwrap(); - let old = clip.color.clone(); - clip.color = color.clone(); - panic!("{color:?} {old:?}"); - //old - }) - } -} - -pub trait HasClipsSize { - fn clips_size (&self) -> &Sizer; -} - -impl HasClipsSize for App { - fn clips_size (&self) -> &Sizer { &self.project.size_inner } -} - -impl HasClipsSize for Project { - fn clips_size (&self) -> &Sizer { &self.size_inner } -} - -impl ClipsView for T {} - -pub trait ClipsView: TracksView + ScenesView { - - /// Draw clips per scene - fn view_scenes_clips (&self) -> impl Draw { - let select = self.selection(); - let editor = self.editor(); - let editing = self.is_editing(); - with_clips_size(true, self.clips_size(), iter_east(move||self.tracks_with_sizes() - .map(move|(track_index, track, _, _)|iter_south(move||self.scenes_with_sizes() - .map(move|(scene_index, scene, _, _)|{ - let (name, theme): (Arc, ItemTheme) = view_scene_name_theme(scene, track_index); - let f = theme.lightest.term; - let (b, o) = view_scene_bg(theme, select, track_index, scene_index); - let is_selected = view_scene_sel(select, track_index, scene_index, editing); - below( - Outer(true, Style::default().fg(o)).full_wh(), - below( - below( - fg_bg(o, b, "".full_wh()), - fg_bg(f, b, bold(true, name)).align_nw().full_wh(), - ), - when(is_selected, editor).full_wh() - ).full_wh() - ).exact_wh( - view_scene_w(track, select, track_index, editor.as_ref()), - view_scene_y(select, scene_index, editor.as_ref()), - ) - }) - ) - .exact_w(track.width as u16)) - )).align_c() - } - -} - -fn with_clips_size (show: bool, size: &Sizer, content: impl Draw) -> impl Draw { - above( - when(show, fg(Green, east!(size.w() as usize, "x", size.h() as usize)).align_se()), - size.of(content.align_c().full_wh()) - ) -} diff --git a/src/pool.rs b/src/pool.rs index fa278a3f..f5348355 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -19,6 +19,21 @@ use crate::*; #[cfg(feature = "sampler")] pub samples: Arc>>>>, } +impl HasClips for Pool { + fn clip (&self) -> Option>> { + self.clips.read().get(self.clip_index()).map(|c|c.clone()) + } + fn clip_mut (&self) -> &mut Option>> { + todo!() + } + fn clips <'a> (&'a self) -> RwLockReadGuard<'a, Clips> { + self.clips.read() + } + fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, Clips> { + self.clips.write() + } +} + /// Displays and edits clip length. #[derive(Clone, Debug, Default)] pub struct ClipLength { /// Pulses per beat (quaver) @@ -326,10 +341,6 @@ pub trait PoolController: HasPool } -has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone())); - -impl_has_clips!(|self: Pool|self.clips); - impl_from!(Pool: |clip:&Arc>|{ let model = Self::default(); model.clips.try_write().unwrap().push(clip.clone());