#[derive(Debug, Copy, Clone)] pub struct TypingState { base: u8, current: u8, } impl TypingState { pub fn new(base: u8) -> TypingState { TypingState { base, current: 0 } } /// Add the value into the current accumulator. pub fn enter(&mut self, value: u8) { self.current = self.current.saturating_mul(self.base).saturating_add(value); } pub fn set_bits(&mut self, mask: u8) { self.current |= mask; } /// Decode the current accumulator as a character, or None if it's invalid. /// This resets the accumulator to 0! pub fn emit(&mut self) -> (u8, Option) { let x = self.current; let c = decode_alt(x); self.current = 0; (x, c) } } /// Chunks a u8 into a sequence of values in a given base. pub fn base_values(mut x: u8, base: u8, len: usize) -> Vec { let mut vals = vec![]; for _ in 0..len { vals.push(x % base); x = x / base; } vals.reverse(); vals } /// Determines the scale degree of a given MIDI note relative to the provided /// scale root. pub fn midi_to_scale_degree(root: u8, note: u8) -> Option { let semitones = (note as i32 - root as i32).rem_euclid(12); Some(match semitones { 0 => 0, 2 => 1, 4 => 2, 5 => 3, 7 => 4, 9 => 5, 11 => 6, _ => return None, }) } /// Converts a character to its assigned number for use in text entry. This is /// stores them as u8 to keep things simple and fixed length. If we want to /// support the full Unicode space, we can have expand this to u32. pub fn encode(c: char) -> Option { Some(match c { 'a'..='z' => c as u8 - 'a' as u8, 'A'..='Z' => c as u8 - 'A' as u8 + 26, '0'..='9' => c as u8 - '0' as u8 + 26 * 2, '.' => 62, ',' => 63, '!' => 64, ' ' => 65, _ => return None, }) } /// Converts a number to its corresponding character for use in text entry. /// If a character isn't implemented, it will return None. pub fn decode(x: u8) -> Option { let c = match x { 0..=25 => 'a' as u8 + x, 26..=51 => 'A' as u8 + x - 26, 52..=61 => '0' as u8 + x - 26 * 2, 62 => '.' as u8, 63 => ',' as u8, 64 => '!' as u8, 65 => ' ' as u8, _ => return None, }; Some(char::from(c)) } /// Converts a number to its corresponding characer for use in text entry. /// But louder. pub fn decode_alt(x: u8) -> Option { let c = match x { 0..=25 => 'a' as u8 + x, 64..=89 => 'A' as u8 + x - 64, _ => return None, }; Some(char::from(c)) } #[cfg(test)] mod tests { use super::*; #[test] fn encodes_lowercase() { assert_eq!(encode('a'), Some(0)); assert_eq!(encode('b'), Some(1)); assert_eq!(encode('z'), Some(25)); } #[test] fn encodes_uppercase() { assert_eq!(encode('A'), Some(26)); assert_eq!(encode('B'), Some(27)); assert_eq!(encode('Z'), Some(51)); } #[test] fn encodes_numbers() { assert_eq!(encode('0'), Some(52)); assert_eq!(encode('1'), Some(53)); assert_eq!(encode('9'), Some(61)); } #[test] fn encodes_punctuation() { assert_eq!(encode('.'), Some(62)); assert_eq!(encode(','), Some(63)); assert_eq!(encode('!'), Some(64)); } #[test] fn decodes_lowercase() { assert_eq!(decode(0), Some('a')); assert_eq!(decode(1), Some('b')); assert_eq!(decode(25), Some('z')); } #[test] fn decodes_uppercase() { assert_eq!(decode(26), Some('A')); assert_eq!(decode(27), Some('B')); assert_eq!(decode(51), Some('Z')); } #[test] fn decodes_numbers() { assert_eq!(decode(52), Some('0')); assert_eq!(decode(53), Some('1')); assert_eq!(decode(61), Some('9')); } #[test] fn decodes_punctuation() { assert_eq!(decode(62), Some('.')); assert_eq!(decode(63), Some(',')); assert_eq!(decode(64), Some('!')); } #[test] fn converts_to_scale_degree() { assert_eq!(midi_to_scale_degree(60, 58), None); assert_eq!(midi_to_scale_degree(60, 59), Some(6)); assert_eq!(midi_to_scale_degree(60, 60), Some(0)); assert_eq!(midi_to_scale_degree(60, 61), None); assert_eq!(midi_to_scale_degree(60, 62), Some(1)); assert_eq!(midi_to_scale_degree(60, 63), None); assert_eq!(midi_to_scale_degree(60, 64), Some(2)); assert_eq!(midi_to_scale_degree(60, 65), Some(3)); assert_eq!(midi_to_scale_degree(60, 66), None); assert_eq!(midi_to_scale_degree(60, 67), Some(4)); assert_eq!(midi_to_scale_degree(60, 68), None); assert_eq!(midi_to_scale_degree(60, 69), Some(5)); assert_eq!(midi_to_scale_degree(60, 70), None); assert_eq!(midi_to_scale_degree(60, 71), Some(6)); assert_eq!(midi_to_scale_degree(60, 72), Some(0)); assert_eq!(midi_to_scale_degree(60, 73), None); assert_eq!(midi_to_scale_degree(60, 74), Some(1)); } }