use crate::*; pub type Clips = Vec>>; 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. /// /// ``` /// let clip = tek::MidiClip::default(); /// ``` #[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, } 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 } /// 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); } /// 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 } /// Create a panic clip. pub fn stop_all () -> Self { 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 {} impl HasMidiClip for Project { fn clip (&self) -> Option>> { None } } impl HasMidiClip for App { fn clip (&self) -> Option>> { self.project.clip() } } impl> + Namespace> > MidiClipController for T {} #[tek_proc::commands(MidiClipCommand)] pub trait MidiClipController: HasMidiClip + Namespace> + 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 { 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 { 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!() }