use crate::*; /// 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 {}