use std::error::Error;
use std::fmt;
const NOTE_NAMES: [&str; 12] = [
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
];
const SYLLABLES: [&str; 16] = [
"ka", "zu", "mi", "te", "ra", "lo", "fi", "na", "se", "di", "vo", "pa", "qu", "xe", "yo", "tu",
];
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CrossModalError {
InvalidToken,
UnknownSyllable,
InvalidNote,
}
impl fmt::Display for CrossModalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CrossModalError::InvalidToken => write!(f, "invalid word token"),
CrossModalError::UnknownSyllable => write!(f, "unknown syllable"),
CrossModalError::InvalidNote => write!(f, "invalid note descriptor"),
}
}
}
impl Error for CrossModalError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ModalFrame {
pub byte: u8,
pub note_name: &'static str,
pub register: u8,
pub hue_index: u8,
pub rgb: [u8; 3],
pub token: String,
}
pub fn encode_byte(byte: u8) -> ModalFrame {
let pitch_class = byte % 12;
let register = byte / 12;
let hue_index = pitch_class;
let rgb = hue_to_rgb(hue_index);
ModalFrame {
byte,
note_name: NOTE_NAMES[pitch_class as usize],
register,
hue_index,
rgb,
token: token_for_byte(byte),
}
}
pub fn decode_from_note(note_name: &str, register: u8) -> Result<u8, CrossModalError> {
let pitch_class = NOTE_NAMES
.iter()
.position(|n| *n == note_name)
.ok_or(CrossModalError::InvalidNote)? as u8;
Ok(register.saturating_mul(12).saturating_add(pitch_class))
}
pub fn decode_from_hue(hue_index: u8, register: u8) -> Result<u8, CrossModalError> {
if hue_index >= 12 {
return Err(CrossModalError::InvalidNote);
}
Ok(register.saturating_mul(12).saturating_add(hue_index))
}
pub fn token_for_byte(byte: u8) -> String {
let hi = (byte >> 4) as usize;
let lo = (byte & 0x0f) as usize;
format!("{}-{}", SYLLABLES[hi], SYLLABLES[lo])
}
pub fn byte_from_token(token: &str) -> Result<u8, CrossModalError> {
let mut parts = token.split('-');
let hi = parts.next().ok_or(CrossModalError::InvalidToken)?;
let lo = parts.next().ok_or(CrossModalError::InvalidToken)?;
if parts.next().is_some() {
return Err(CrossModalError::InvalidToken);
}
let hi_idx = syllable_index(hi)?;
let lo_idx = syllable_index(lo)?;
Ok((hi_idx << 4) | lo_idx)
}
fn syllable_index(s: &str) -> Result<u8, CrossModalError> {
SYLLABLES
.iter()
.position(|x| *x == s)
.map(|i| i as u8)
.ok_or(CrossModalError::UnknownSyllable)
}
fn hue_to_rgb(hue_index: u8) -> [u8; 3] {
const HUE: [[u8; 3]; 12] = [
[255, 0, 0],
[255, 128, 0],
[255, 220, 0],
[170, 255, 0],
[50, 255, 0],
[0, 255, 80],
[0, 255, 220],
[0, 170, 255],
[0, 60, 255],
[120, 0, 255],
[220, 0, 255],
[255, 0, 140],
];
HUE[hue_index as usize]
}