use crate::*; use tek_core::midly::Smf; use std::path::PathBuf; pub trait HasPhrases { fn phrases (&self) -> &Vec>>; fn phrases_mut (&mut self) -> &mut Vec>>; } #[derive(Clone, Debug, PartialEq)] pub enum PhrasePoolCommand { Add(usize, Phrase), Delete(usize), Swap(usize, usize), Import(usize, PathBuf), Export(usize, PathBuf), SetName(usize, String), SetLength(usize, usize), SetColor(usize, ItemColor), } impl Command for PhrasePoolCommand { fn execute (self, model: &mut T) -> Perhaps { use PhrasePoolCommand::*; Ok(match self { Add(mut index, phrase) => { let phrase = Arc::new(RwLock::new(phrase)); let phrases = model.phrases_mut(); if index >= phrases.len() { index = phrases.len(); phrases.push(phrase) } else { phrases.insert(index, phrase); } Some(Self::Delete(index)) }, Delete(index) => { let phrase = model.phrases_mut().remove(index).read().unwrap().clone(); Some(Self::Add(index, phrase)) }, Swap(index, other) => { model.phrases_mut().swap(index, other); Some(Self::Swap(index, other)) }, Import(index, path) => { let bytes = std::fs::read(&path)?; let smf = Smf::parse(bytes.as_slice())?; let mut t = 0u32; let mut events = vec![]; for track in smf.tracks.iter() { for event in track.iter() { t += event.delta.as_int(); if let TrackEventKind::Midi { channel, message } = event.kind { events.push((t, channel.as_int(), message)); } } } let mut phrase = Phrase::new("imported", true, t as usize + 1, None, None); for event in events.iter() { phrase.notes[event.0 as usize].push(event.2); } Self::Add(index, phrase).execute(model)? }, Export(_index, _path) => { todo!("export phrase to midi file"); }, SetName(index, name) => { let mut phrase = model.phrases()[index].write().unwrap(); let old_name = phrase.name.clone(); phrase.name = name; Some(Self::SetName(index, old_name)) }, SetLength(index, length) => { let mut phrase = model.phrases()[index].write().unwrap(); let old_len = phrase.length; phrase.length = length; Some(Self::SetLength(index, old_len)) }, SetColor(index, color) => { let mut color = ItemColorTriplet::from(color); std::mem::swap(&mut color, &mut model.phrases()[index].write().unwrap().color); Some(Self::SetColor(index, color.base)) }, }) } } /// A MIDI sequence. #[derive(Debug, Clone)] pub struct Phrase { pub uuid: uuid::Uuid, /// Name of phrase pub name: String, /// Temporal resolution in pulses per quarter note pub ppq: usize, /// Length of phrase in pulses pub length: usize, /// Notes in phrase pub notes: PhraseData, /// Whether to loop the phrase or play it once pub loop_on: 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 phrase pub color: ItemColorTriplet, } /// MIDI message structural pub type PhraseData = Vec>; impl Phrase { pub fn new ( name: impl AsRef, loop_on: bool, length: usize, notes: Option, color: Option, ) -> Self { Self { uuid: uuid::Uuid::new_v4(), name: name.as_ref().to_string(), ppq: PPQ, length, notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), loop_on, loop_start: 0, loop_length: length, percussive: true, color: color.unwrap_or_else(ItemColorTriplet::random) } } pub fn duplicate (&self) -> Self { let mut clone = self.clone(); clone.uuid = uuid::Uuid::new_v4(); clone } pub fn toggle_loop (&mut self) { self.loop_on = !self.loop_on; } pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { if pulse >= self.length { panic!("extend phrase 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 { //panic!("{:?} {start} {end}", &self); 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 } } } } return false } } impl Default for Phrase { fn default () -> Self { Self::new("(empty)", false, 0, None, Some(ItemColor::from(Color::Rgb(0, 0, 0)).into())) } } impl PartialEq for Phrase { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } impl Eq for Phrase {}