midi-keys/src/typing.rs

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));
}
}