// Copyright (c) 2021 Weird Constructor // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. use super::PatternColType; use super::MAX_PATTERN_LEN; use super::MAX_COLS; use crate::matrix_repr::PatternRepr; #[derive(Debug)] pub struct PatternData { col_types: [PatternColType; MAX_COLS], data: Vec>>, out_data: Vec<[(f32, u8); MAX_PATTERN_LEN]>, strings: Vec>>, cursor: (usize, usize), rows: usize, edit_step: usize, dirty_col: [bool; MAX_COLS], } impl PatternData { pub fn new(rows: usize) -> Self { Self { col_types: [PatternColType::Value; MAX_COLS], data: vec![vec![None; MAX_COLS]; MAX_PATTERN_LEN], out_data: vec![[(0.0, 0); MAX_PATTERN_LEN]; MAX_COLS], strings: vec![vec![None; MAX_COLS]; MAX_PATTERN_LEN], cursor: (2, 2), edit_step: 4, dirty_col: [true; MAX_COLS], rows, } } } impl PatternData { pub fn is_unset(&self) -> bool { for ct in self.col_types.iter() { if *ct != PatternColType::Value { return false; } } for rows in self.data.iter() { for col in rows.iter() { if col.is_some() { return false; } } } true } pub fn to_repr(&self) -> PatternRepr { let mut col_types = [0; MAX_COLS]; for (i, ct) in self.col_types.iter().enumerate() { col_types[i] = match ct { PatternColType::Value => 0, PatternColType::Note => 1, PatternColType::Step => 2, PatternColType::Gate => 3, }; } let mut data = vec![vec![-1; MAX_COLS]; MAX_PATTERN_LEN]; for (row_idx, row) in self.data.iter().enumerate() { for (col_idx, cell) in row.iter().enumerate() { data[row_idx][col_idx] = if let Some(c) = cell { *c as i32 } else { -1 }; } } PatternRepr { col_types, data, rows: self.rows, edit_step: self.edit_step, cursor: self.cursor, } } pub fn from_repr(&mut self, repr: &PatternRepr) { for (i, ct) in repr.col_types.iter().enumerate() { self.col_types[i] = match *ct { 0 => PatternColType::Value, 1 => PatternColType::Note, 2 => PatternColType::Step, 3 => PatternColType::Gate, _ => PatternColType::Value, }; self.modified_col(i); } for (row_idx, row) in repr.data.iter().enumerate() { for (col_idx, cell) in row.iter().enumerate() { self.data[row_idx][col_idx] = if *cell < 0 { None } else { Some(*cell as u16) }; } } self.rows = repr.rows; self.edit_step = repr.edit_step; self.cursor = repr.cursor; } pub fn get_out_data(&self) -> &[[(f32, u8); MAX_PATTERN_LEN]] { &self.out_data } fn modified_col(&mut self, col: usize) { if let Some(bit) = self.dirty_col.get_mut(col) { *bit = true; } } pub fn col_is_modified_reset(&mut self, col: usize) -> bool { if self.dirty_col.get(col).copied().unwrap_or(false) { self.dirty_col[col] = false; true } else { false } } pub fn col_type(&self, col: usize) -> PatternColType { self.col_types.get(col).copied().unwrap_or(PatternColType::Step) } pub fn sync_out_data(&mut self, col: usize) { let out_col = &mut self.out_data[col]; if self.rows == 0 { return; } match self.col_types[col] { PatternColType::Value => { let mut start_value = 0.0; let mut start_idx = 0; let mut end_idx = 0; while end_idx <= out_col.len() { let mut break_after_write = false; let cur_value = if end_idx == self.rows { end_idx -= 1; break_after_write = true; Some(self.data[end_idx][col] .map(|v| ((v & 0xFFF) as f32) / (0xFFF as f32)) .unwrap_or(0.0)) } else { self.data[end_idx][col].map(|v| ((v & 0xFFF) as f32) / (0xFFF as f32)) }; if let Some(end_value) = cur_value { out_col[start_idx] = (start_value, 0); out_col[end_idx] = (end_value, 1); let delta_rows = end_idx - start_idx; if delta_rows > 1 { for idx in (start_idx + 1)..end_idx { let x = (idx - start_idx) as f32 / (delta_rows as f32); out_col[idx] = (start_value * (1.0 - x) + end_value * x, 0); } } start_value = end_value; start_idx = end_idx; end_idx = end_idx + 1; if break_after_write { break; } } else { end_idx += 1; } } }, PatternColType::Note => { let mut cur_value = (0.0, 0); for row in 0..out_col.len() { if let Some(new_value) = self.data[row][col] { cur_value = ( ((new_value as i32 - 69) as f32 * 0.1) / 12.0, 1 ); } else { cur_value.1 = 0; } out_col[row] = (cur_value.0.clamp(-1.0, 1.0), cur_value.1); } }, PatternColType::Step => { let mut cur_value = (0.0, 0); for row in 0..out_col.len() { if let Some(new_value) = self.data[row][col] { cur_value = ( ((new_value & 0xFFF) as f32) / (0xFFF as f32), 1 ); } else { cur_value.1 = 0; } out_col[row] = cur_value; } }, PatternColType::Gate => { // XXX: We need to iterate over all rows, even if // self.rows < out_col.len(), because empty cells // are not equal to float 0.0. Maybe we should change // this internal format!? But in either case we transmit // a full row to the backend anyways. for row in 0..out_col.len() { out_col[row] = if let Some(new_value) = self.data[row][col] { (f32::from_bits(new_value as u32), 1) } else { (f32::from_bits(0xF000 as u32), 0) }; } }, } } } impl dyn UIPatternModel { pub fn change_value(&mut self, row: usize, col: usize, offs: i16) { let val = self.get_cell_value(row, col) as i16; let val = (val + offs).max(0).min(0xfff); self.set_cell_value(row, col, val as u16); } } pub trait UIPatternModel: std::fmt::Debug { fn get_cell(&mut self, row: usize, col: usize) -> Option<&str>; fn is_col_note(&self, col: usize) -> bool; fn is_col_step(&self, col: usize) -> bool; fn is_col_gate(&self, col: usize) -> bool; fn rows(&self) -> usize; fn cols(&self) -> usize; fn set_rows(&mut self, rows: usize); fn clear_cell(&mut self, row: usize, col: usize); fn set_col_note_type(&mut self, col: usize); fn set_col_step_type(&mut self, col: usize); fn set_col_value_type(&mut self, col: usize); fn set_col_gate_type(&mut self, col: usize); fn set_cell_value(&mut self, row: usize, col: usize, val: u16); fn get_cell_value(&mut self, row: usize, col: usize) -> u16; fn set_cursor(&mut self, row: usize, col: usize); fn get_cursor(&self) -> (usize, usize); fn set_edit_step(&mut self, es: usize); fn get_edit_step(&mut self) -> usize; } impl UIPatternModel for PatternData { fn get_cell(&mut self, row: usize, col: usize) -> Option<&str> { if row >= self.data.len() { return None; } if col >= self.data[0].len() { return None; } if self.strings[row][col].is_none() { if let Some(v) = self.data[row][col] { self.strings[row][col] = Some(format!("{:03x}", v)); } else { return None; } } Some(self.strings[row][col].as_ref().unwrap()) } fn clear_cell(&mut self, row: usize, col: usize) { if row >= self.data.len() { return; } if col >= self.data[0].len() { return; } self.data[row][col] = None; self.strings[row][col] = None; self.modified_col(col); } fn get_cell_value(&mut self, row: usize, col: usize) -> u16 { if row >= self.data.len() { return 0; } if col >= self.data[0].len() { return 0; } self.data[row][col].unwrap_or(0) } fn set_cell_value(&mut self, row: usize, col: usize, val: u16) { if row >= self.data.len() { return; } if col >= self.data[0].len() { return; } self.data[row][col] = Some(val); self.strings[row][col] = None; self.modified_col(col); } fn is_col_note(&self, col: usize) -> bool { if let Some(ct) = self.col_types.get(col) { *ct == PatternColType::Note } else { false } } fn is_col_step(&self, col: usize) -> bool { if let Some(ct) = self.col_types.get(col) { *ct == PatternColType::Step } else { false } } fn is_col_gate(&self, col: usize) -> bool { if let Some(ct) = self.col_types.get(col) { *ct == PatternColType::Gate } else { false } } fn cols(&self) -> usize { self.data[0].len() } fn rows(&self) -> usize { self.rows } fn set_rows(&mut self, rows: usize) { self.rows = rows.min(self.data.len()); self.modified_col(0); // modify any col, so we send an update. } fn set_col_note_type(&mut self, col: usize) { if col >= self.col_types.len() { return; } self.col_types[col] = PatternColType::Note; self.modified_col(col); } fn set_col_step_type(&mut self, col: usize) { if col >= self.col_types.len() { return; } self.col_types[col] = PatternColType::Step; self.modified_col(col); } fn set_col_value_type(&mut self, col: usize) { if col >= self.col_types.len() { return; } self.col_types[col] = PatternColType::Value; self.modified_col(col); } fn set_col_gate_type(&mut self, col: usize) { if col >= self.col_types.len() { return; } self.col_types[col] = PatternColType::Gate; self.modified_col(col); } fn set_cursor(&mut self, row: usize, col: usize) { self.cursor = (row, col); } fn get_cursor(&self) -> (usize, usize) { self.cursor } fn set_edit_step(&mut self, es: usize) { self.edit_step = es; } fn get_edit_step(&mut self) -> usize { self.edit_step } } #[cfg(test)] mod tests { use super::*; macro_rules! assert_float_eq { ($a:expr, $b:expr) => { if ($a - $b).abs() > 0.0001 { panic!(r#"assertion failed: `(left == right)` left: `{:?}`, right: `{:?}`"#, $a, $b) } } } #[test] fn check_linear_value_corner_case1_0_to_1() { let mut pats = PatternData::new(3); for col in 0..MAX_COLS { pats.set_col_value_type(col); pats.set_cell_value(0, col, 0); pats.set_cell_value(2, col, 0xFFF); pats.sync_out_data(col); let out_data = pats.get_out_data(); let inc = 1.0 / 2.0; for i in 1..2 { let delta = out_data[col][i].0 - out_data[col][i - 1].0; assert_float_eq!(delta, inc); } } } #[test] fn check_linear_value_corner_case2_0_to_1() { let mut pats = PatternData::new(4); for col in 0..MAX_COLS { pats.set_col_value_type(col); pats.set_cell_value(0, col, 0); pats.set_cell_value(3, col, 0xFFF); pats.sync_out_data(col); let out_data = pats.get_out_data(); let inc = 1.0 / 3.0; for i in 1..3 { let delta = out_data[col][i].0 - out_data[col][i - 1].0; assert_float_eq!(delta, inc); } } } #[test] fn check_linear_value_out_0_to_1() { let mut pats = PatternData::new(16); for col in 0..MAX_COLS { pats.set_col_value_type(col); pats.set_cell_value(0, col, 0); pats.set_cell_value(15, col, 0xFFF); pats.sync_out_data(col); let out_data = pats.get_out_data(); let inc = 1.0 / 15.0; //d// println!("out: {:?}", &out_data[col][0..16]); for i in 1..16 { let delta = out_data[col][i].0 - out_data[col][i - 1].0; assert_float_eq!(delta, inc); } } } #[test] fn check_linear_value_out_1_to_0() { let mut pats = PatternData::new(16); for col in 0..MAX_COLS { pats.set_col_value_type(col); pats.set_cell_value(0, col, 0xFFF); pats.sync_out_data(col); let out_data = pats.get_out_data(); let inc = 1.0 / 15.0; for i in 1..16 { let delta = out_data[col][i].0 - out_data[col][i - 1].0; assert_float_eq!(delta.abs(), inc); } } } #[test] fn check_linear_value_out_cast1_1_to_1() { let mut pats = PatternData::new(16); for col in 0..MAX_COLS { pats.set_col_value_type(col); pats.set_cell_value(7, col, 0xFFF); pats.set_cell_value(8, col, 0xFFF); pats.sync_out_data(col); let out_data = pats.get_out_data(); //d// println!("out: {:?}", &out_data[col][0..16]); for i in 0..8 { assert_float_eq!( out_data[col][i].0, out_data[col][15 - i].0); } } } #[test] fn check_linear_value_out_case2_1_to_1() { let mut pats = PatternData::new(16); for col in 0..MAX_COLS { pats.set_col_value_type(col); pats.set_cell_value(6, col, 0xFFF); pats.set_cell_value(9, col, 0xFFF); pats.sync_out_data(col); let out_data = pats.get_out_data(); //d// println!("out: {:?}", &out_data[col][0..16]); for i in 0..8 { assert_float_eq!( out_data[col][i].0, out_data[col][15 - i].0); } } } #[test] fn check_linear_value_out_case3_1_to_1() { let mut pats = PatternData::new(16); for col in 0..MAX_COLS { pats.set_col_value_type(col); pats.set_cell_value(6, col, 0xFFF); pats.set_cell_value(7, col, 0x0); pats.set_cell_value(8, col, 0x0); pats.set_cell_value(9, col, 0xFFF); pats.sync_out_data(col); let out_data = pats.get_out_data(); //d// println!("out: {:?}", &out_data[col][0..16]); for i in 0..8 { assert_float_eq!( out_data[col][i].0, out_data[col][15 - i].0); } } } #[test] fn check_linear_value_out_case4_1_to_1() { let mut pats = PatternData::new(16); for col in 0..MAX_COLS { pats.set_col_value_type(col); pats.set_cell_value(5, col, 0xFFF); pats.set_cell_value(7, col, 0x0); pats.set_cell_value(8, col, 0x0); pats.set_cell_value(10, col, 0xFFF); pats.sync_out_data(col); let out_data = pats.get_out_data(); //d// println!("out: {:?}", &out_data[col][0..16]); assert_float_eq!(0.5, out_data[col][6].0); assert_float_eq!(0.5, out_data[col][9].0); for i in 0..8 { assert_float_eq!( out_data[col][i].0, out_data[col][15 - i].0); } } } #[test] fn check_pattern_step_out() { let mut pats = PatternData::new(16); for col in 0..MAX_COLS { pats.set_col_step_type(col); pats.set_cell_value(4, col, 0x450); pats.set_cell_value(5, col, 0x0); pats.set_cell_value(7, col, 0x7ff); pats.set_cell_value(9, col, 0x800); pats.set_cell_value(10, col, 0xfff); pats.sync_out_data(col); let out_data = pats.get_out_data(); assert_float_eq!(out_data[col][0].0, 0.0); assert_float_eq!(out_data[col][4].0, 0.26959708); assert_float_eq!(out_data[col][5].0, 0.0); assert_float_eq!(out_data[col][7].0, 0.4998779); assert_float_eq!(out_data[col][8].0, 0.4998779); assert_float_eq!(out_data[col][9].0, 0.50012213); assert_float_eq!(out_data[col][10].0, 1.0); assert_float_eq!(out_data[col][15].0, 1.0); } } #[test] fn check_pattern_note_out() { let mut pats = PatternData::new(16); for col in 0..MAX_COLS { pats.set_col_note_type(col); pats.set_cell_value(4, col, 0x45); pats.set_cell_value(5, col, 0x0); pats.set_cell_value(7, col, 0x45 - 12); pats.set_cell_value(10, col, 0x45 + 12); pats.sync_out_data(col); let out_data = pats.get_out_data(); assert_float_eq!(out_data[col][0].0, 0.0); assert_float_eq!(out_data[col][4].0, 0.0); assert_float_eq!(out_data[col][5].0, -0.575); assert_float_eq!(out_data[col][7].0, -0.1); assert_float_eq!(out_data[col][9].0, -0.1); assert_float_eq!(out_data[col][10].0, 0.1); assert_float_eq!(out_data[col][15].0, 0.1); } } #[test] fn check_pattern_repr() { let mut pat = PatternData::new(MAX_PATTERN_LEN); for col in 0..MAX_COLS { pat.set_col_note_type(col); for v in 1..(MAX_PATTERN_LEN + 1) { pat.set_cell_value(v - 1, col, v as u16); } pat.set_cursor(16, 3); pat.set_edit_step(5); pat.set_rows(133); } let repr = pat.to_repr(); let mut pat2 = PatternData::new(MAX_PATTERN_LEN); pat2.from_repr(&repr); for col in 0..MAX_COLS { assert!(pat.is_col_note(col)); for v in 1..(MAX_PATTERN_LEN + 1) { assert_eq!(pat.get_cell_value(v - 1, col), v as u16); } assert_eq!(pat.get_cursor(), (16, 3)); assert_eq!(pat.get_edit_step(), 5); assert_eq!(pat.rows(), 133); } } }