use core::ops::{Index, IndexMut}; use core::slice; use alloc::vec::Vec; use crate::project::Song; use crate::project::note_event::NoteEvent; /// both row and channel are zero based. // If this ever changes a lot of the implementations of // Pattern need to be changed, because the searching starts working differently // don't change the Order of fields, as PartialOrd derive depends on it #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct InPatternPosition { pub row: u16, pub channel: u8, } #[cfg(test)] mod test { use crate::project::pattern::InPatternPosition; #[test] fn position_ord() { let one_zero = InPatternPosition { row: 1, channel: 0 }; let zero_one = InPatternPosition { row: 0, channel: 1 }; assert!(one_zero > zero_one); } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Pattern { rows: u16, // Events are sorted with InPatternPosition as the key. // TODO: figure out a better representation. // Maybe use array of structs for indexing, as InPatternPosition is only 32 bytes (maybe also make it actually 32 bytes with 0 padding // so comparisons don't have to care about padding) // // Maybe also use the cache optimized Binary search layout // // Or use a BTreeMap. this could have higher insertion speeds, but i probably don't care about that if i can provide an API that takes a // list of changes and only resorts it at the end // // This shouln't have any impact on the API of Pattern, as i already hid the row iterator away // and the iter element doesn't force the Position and the Event to live next to each other data: Vec<(InPatternPosition, NoteEvent)>, } pub struct RowIter<'a> { inner: slice::Iter<'a, (InPatternPosition, NoteEvent)>, } // TODO: maybe even provide a mut API based on this, now that the Position can't be modified anymore impl<'a> Iterator for RowIter<'a> { type Item = (InPatternPosition, &'a NoteEvent); fn next(&mut self) -> Option { self.inner.next().map(|(pos, event)| (*pos, event)) } } const fn key(data: &(InPatternPosition, NoteEvent)) -> InPatternPosition { data.0 } impl Default for Pattern { fn default() -> Self { Self::new(Self::DEFAULT_ROWS) } } impl Pattern { pub const MAX_ROWS: u16 = 200; pub const DEFAULT_ROWS: u16 = 64; /// panics if len larger than 'Self::MAX_LEN' pub const fn new(len: u16) -> Self { assert!(len <= Self::MAX_ROWS); Self { rows: len, data: Vec::new(), } } /// panics it the new len is larger than 'Self::MAX_LEN' /// deletes the data on higher rows pub fn set_length(&mut self, new_len: u16) { assert!(new_len <= Self::MAX_ROWS); // gets the index of the first element of the first row to be removed if new_len < self.rows { let idx = self.data.partition_point(|(pos, _)| pos.row < new_len); self.data.truncate(idx); } self.rows = new_len; } /// overwrites the event if the row already has an event for that channel /// panics if the row position is larger than current amount of rows pub fn set_event(&mut self, position: InPatternPosition, event: NoteEvent) { assert!(position.row < self.rows); match self.data.binary_search_by_key(&position, key) { Ok(idx) => self.data[idx].1 = event, Err(idx) => self.data.insert(idx, (position, event)), } } pub fn get_event(&self, index: InPatternPosition) -> Option<&NoteEvent> { self.data .binary_search_by_key(&index, key) .ok() .map(|idx| &self.data[idx].1) } pub fn get_event_mut(&mut self, index: InPatternPosition) -> Option<&mut NoteEvent> { self.data .binary_search_by_key(&index, key) .ok() .map(|idx| &mut self.data[idx].1) } /// if there is no event, does nothing pub fn remove_event(&mut self, position: InPatternPosition) { if let Ok(index) = self.data.binary_search_by_key(&position, key) { self.data.remove(index); } } pub const fn row_count(&self) -> u16 { self.rows } /// Panics if the Operation is invalid pub fn apply_operation(&mut self, op: PatternOperation) { match op { PatternOperation::SetLength { new_len } => self.set_length(new_len), PatternOperation::SetEvent { position, event } => self.set_event(position, event), PatternOperation::RemoveEvent { position } => self.remove_event(position), } } pub const fn operation_is_valid(&self, op: &PatternOperation) -> bool { match op { PatternOperation::SetLength { new_len } => *new_len < Self::MAX_ROWS, PatternOperation::SetEvent { position, event: _ } => { position.row < self.rows && position.channel as usize <= Song::MAX_CHANNELS } PatternOperation::RemoveEvent { position: _ } => true, } } pub fn is_empty(&self) -> bool { self.data.is_empty() } // TODO: think about out of bounds behaviour pub fn row(&self, row: u16) -> RowIter<'_> { let start_position = self.data.partition_point(|(pos, _)| { *pos < InPatternPosition { row: row, channel: 0, } }); // only search after start_position let end_position = self.data[start_position..self.data.len()].partition_point(|(pos, _)| { *pos < InPatternPosition { row: row + 1, channel: 0, } }) + start_position; RowIter { inner: self.data[start_position..end_position].iter(), } } } // TODO: think about panics here / provide non-panicking API impl Index for Pattern { type Output = NoteEvent; #[track_caller] fn index(&self, index: InPatternPosition) -> &Self::Output { self.get_event(index).unwrap() } } impl IndexMut for Pattern { #[track_caller] fn index_mut(&mut self, index: InPatternPosition) -> &mut Self::Output { self.get_event_mut(index).unwrap() } } #[derive(Debug, Clone, Copy)] pub enum PatternOperation { SetLength { new_len: u16, }, SetEvent { position: InPatternPosition, event: NoteEvent, }, RemoveEvent { position: InPatternPosition, }, }