184 lines
5.1 KiB
Rust
184 lines
5.1 KiB
Rust
#[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<char>) {
|
|
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<u8> {
|
|
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<u8> {
|
|
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<u8> {
|
|
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<char> {
|
|
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<char> {
|
|
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));
|
|
}
|
|
}
|