use crate::*; pub trait HasMidiClip { fn clip (&self) -> Option>>; } #[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 } } } } /// A MIDI sequence. #[derive(Debug, Clone, Default)] pub struct MidiClip { pub uuid: uuid::Uuid, /// Name of clip pub name: Arc, /// Temporal resolution in pulses per quarter note pub ppq: usize, /// Length of clip in pulses pub length: usize, /// Notes in clip pub notes: MidiData, /// Whether to loop the clip or play it once pub looped: bool, /// Start of loop pub loop_start: usize, /// Length of loop pub loop_length: usize, /// All notes are displayed with minimum length pub percussive: bool, /// Identifying color of clip pub color: ItemTheme, } /// MIDI message structural pub type MidiData = Vec>; impl MidiClip { pub fn new ( name: impl AsRef, looped: bool, length: usize, notes: Option, color: Option, ) -> Self { Self { uuid: uuid::Uuid::new_v4(), name: name.as_ref().into(), ppq: PPQ, length, notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), looped, loop_start: 0, loop_length: length, percussive: true, color: color.unwrap_or_else(ItemTheme::random) } } pub fn count_midi_messages (&self) -> usize { let mut count = 0; for tick in self.notes.iter() { count += tick.len(); } count } pub fn set_length (&mut self, length: usize) { self.length = length; self.notes = vec![Vec::with_capacity(16);length]; } pub fn duplicate (&self) -> Self { let mut clone = self.clone(); clone.uuid = uuid::Uuid::new_v4(); clone } pub fn toggle_loop (&mut self) { self.looped = !self.looped; } pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { if pulse >= self.length { panic!("extend clip first") } self.notes[pulse].push(message); } /// Check if a range `start..end` contains MIDI Note On `k` pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool { for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() { for event in events.iter() { if let MidiMessage::NoteOn {key,..} = event { if *key == k { return true } } } } false } pub fn stop_all () -> Self { Self::new( "Stop", false, 1, Some(vec![vec![MidiMessage::Controller { controller: 123.into(), value: 0.into() }]]), Some(ItemColor::from_rgb(Color::Rgb(32, 32, 32)).into()) ) } } impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } impl Eq for MidiClip {} impl MidiClip { fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } fn _todo_bool_stub_ (&self) -> bool { todo!() } fn _todo_usize_stub_ (&self) -> usize { todo!() } fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } } def_command!(ClipCommand: |clip: MidiClip| { SetColor { color: Option } => { //(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!() }, SetLoop { looping: Option } => { //(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!() } }); pub trait ClipsView: TracksView + ScenesView + HasClipsSize + Send + Sync { fn view_scenes_clips <'a> (&'a self) -> impl Content + 'a { self.clips_size().of(Fill::XY(Bsp::a( Fill::XY(Align::se(Tui::fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))), Thunk::new(|to: &mut TuiOut|for ( track_index, track, _, _ ) in self.tracks_with_sizes() { to.place(&Fixed::X(track.width as u16, Fill::Y(self.view_track_clips(track_index, track)))) })))) } fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Content + 'a { Thunk::new(move|to: &mut TuiOut|for ( scene_index, scene, .. ) in self.scenes_with_sizes() { let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { let clip = clip.read().unwrap(); (format!(" ⏹ {}", &clip.name).into(), clip.color) } else { (" ⏹ -- ".into(), ItemTheme::G[32]) }; let fg = theme.lightest.rgb; let mut outline = theme.base.rgb; let bg = if self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) { outline = theme.lighter.rgb; theme.light.rgb } else if self.selection().track() == Some(track_index) || self.selection().scene() == Some(scene_index) { outline = theme.darkest.rgb; theme.base.rgb } else { theme.dark.rgb }; let w = if self.selection().track() == Some(track_index) && let Some(editor) = self.editor () { editor.width().max(24).max(track.width) } else { track.width } as u16; let y = if self.selection().scene() == Some(scene_index) && let Some(editor) = self.editor () { editor.height().max(12) } else { Self::H_SCENE } as u16; to.place(&Fixed::XY(w, y, Bsp::b( Fill::XY(Outer(true, Style::default().fg(outline))), Fill::XY(Bsp::b( Bsp::b( Tui::fg_bg(outline, bg, Fill::XY("")), Fill::XY(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))), ), Fill::XY(When::new(self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) && self.is_editing(), self.editor()))))))); }) } } //take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref() //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));