tek/src/midi_clip.rs
i do not exist ca774cca41
Some checks failed
/ build (push) Has been cancelled
wip: more shrunkening
2026-09-21 17:52:35 +03:00

119 lines
3.8 KiB
Rust

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<str>,
/// 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<str>,
looped: bool,
length: usize,
notes: Option<MidiData>,
color: Option<ItemTheme>,
) -> 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>) -> 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>) -> 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<Vec<MidiMessage>>;
impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } }
impl Eq for MidiClip {}