From 24ec2a92b56ca4296fe65b70524e36a8e1c67c2e Mon Sep 17 00:00:00 2001 From: Alexander Kassouni Date: Sat, 5 Sep 2026 22:43:52 -0700 Subject: phonemes --- src/main.rs | 2 +- src/phonemes/consonant.rs | 53 ++++++++++++ src/phonemes/digit.rs | 31 +++++++ src/phonemes/digraph.rs | 118 +++++++++++++++++++++++++++ src/phonemes/mod.rs | 11 +++ src/phonemes/phonemes.rs | 30 ++++++- src/phonemes/vowel.rs | 38 +++++++++ src/tokenizer/mod.rs | 2 +- src/tokenizer/tokens.rs | 202 +++++++++++++++++++++++++++++++++------------- 9 files changed, 427 insertions(+), 60 deletions(-) create mode 100644 src/phonemes/consonant.rs create mode 100644 src/phonemes/digit.rs create mode 100644 src/phonemes/digraph.rs create mode 100644 src/phonemes/vowel.rs (limited to 'src') diff --git a/src/main.rs b/src/main.rs index d35753c..44d0887 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,8 @@ struct Args { timbre: f64, } +mod phonemes; mod tokenizer; -use tokenizer::VoiceCode; fn main() { let _args = Args::parse(); diff --git a/src/phonemes/consonant.rs b/src/phonemes/consonant.rs new file mode 100644 index 0000000..bdf07ee --- /dev/null +++ b/src/phonemes/consonant.rs @@ -0,0 +1,53 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Consonant { + B, + CHard, + CSoft, + D, + F, + G, + H, + J, + K, + L, + M, + P, + Q, + R, + S, + T, + V, + W, + X, + Y, + Z, +} + +impl Consonant { + pub const fn code(self) -> u8 { + use Consonant::*; + match self { + B => 0x07, + CHard => 0x18, + CSoft => 0x23, + D => 0x09, + F => 0x11, + G => 0x12, + H => 0x00, + J => 0x17, + K => 0x18, + L => 0x19, + M => 0x1A, + P => 0x21, + Q => 0x18, + R => 0x22, + S => 0x23, + T => 0x26, + V => 0x2D, + W => 0x2E, + X => 0x31, + Y => 0x15, + Z => 0x30, + } + } +} diff --git a/src/phonemes/digit.rs b/src/phonemes/digit.rs new file mode 100644 index 0000000..a694016 --- /dev/null +++ b/src/phonemes/digit.rs @@ -0,0 +1,31 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Digit { + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, + D9, +} + +impl Digit { + pub const fn code(self) -> u8 { + use Digit::*; + match self { + D0 => 0x32, + D1 => 0x33, + D2 => 0x26, + D3 => 0x27, + D4 => 0x34, + D5 => 0x35, + D6 => 0x36, + D7 => 0x37, + D8 => 0x10, + D9 => 0x38, + } + } +} diff --git a/src/phonemes/digraph.rs b/src/phonemes/digraph.rs new file mode 100644 index 0000000..c332520 --- /dev/null +++ b/src/phonemes/digraph.rs @@ -0,0 +1,118 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum Digraph { + AI, + AL, + AN, + AR, + AS, + BE, + BY, + CH, + DO, + EA, + ER, + FA, + FO, + GE, + GO, + HA, + HE, + HI, + HO, + I_, + IF, + IN, + IR, + IS, + IT, + KS, + ME, + MY, + NA, + NG, + NO, + OF, + OH, + OK, + ON, + OO, + OR, + OU, + OW, + OY, + RE, + SE, + SH, + SI, + SO, + SU, + TH, + TO, + UP, + WE, + WO, + YOU, + ZE, +} + +impl Digraph { + pub const fn code(self) -> u8 { + use Digraph::*; + match self { + AI => 0x0F, + AL => 0x02, + AN => 0x39, + AR => 0x16, + AS => 0x3A, + BE => 0x3B, + BY => 0x3C, + CH => 0x08, + DO => 0x3D, + EA => 0x15, + ER => 0x0D, + FA => 0x35, + FO => 0x34, + GE => 0x17, + GO => 0x3E, + HA => 0x3F, + HE => 0x40, + HI => 0x41, + HO => 0x42, + I_ => 0x05, + IF => 0x43, + IN => 0x44, + IR => 0x0C, + IS => 0x45, + IT => 0x46, + KS => 0x31, + ME => 0x47, + MY => 0x48, + NA => 0x38, + NG => 0x1C, + NO => 0x49, + OF => 0x4A, + OH => 0x4B, + OK => 0x4C, + ON => 0x4D, + OO => 0x2A, + OR => 0x2C, + OU => 0x20, + OW => 0x06, + OY => 0x1F, + RE => 0x0E, + SE => 0x37, + SH => 0x24, + SI => 0x36, + SO => 0x4F, + SU => 0x25, + TH => 0x28, + TO => 0x50, + UP => 0x51, + WE => 0x52, + WO => 0x33, + YOU => 0x77, + ZE => 0x32, + } + } +} diff --git a/src/phonemes/mod.rs b/src/phonemes/mod.rs index e69de29..3f0276e 100644 --- a/src/phonemes/mod.rs +++ b/src/phonemes/mod.rs @@ -0,0 +1,11 @@ +mod consonant; +mod digit; +mod digraph; +mod phonemes; +mod vowel; + +pub use consonant::Consonant; +pub use digit::Digit; +pub use digraph::Digraph; +pub use phonemes::Phoneme; +pub use vowel::{Vowel, VowelLen, VowelPhoneme}; diff --git a/src/phonemes/phonemes.rs b/src/phonemes/phonemes.rs index 9313c21..3ec5e56 100644 --- a/src/phonemes/phonemes.rs +++ b/src/phonemes/phonemes.rs @@ -1,6 +1,32 @@ -#[derive(Debug, PartialEq, Eq)] +use super::consonant::Consonant; +use super::digit::Digit; +use super::digraph::Digraph; +use super::vowel::VowelPhoneme; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Phoneme { - + Vowel(VowelPhoneme), + Consonant(Consonant), + Digit(Digit), + Digraph(Digraph), + Silent, + Raw(u8), } +impl Phoneme { + pub const fn code(self) -> u8 { + match self { + Phoneme::Vowel(v) => v.code(), + Phoneme::Consonant(c) => c.code(), + Phoneme::Digit(d) => d.code(), + Phoneme::Digraph(d) => d.code(), + Phoneme::Silent => 0x00, + Phoneme::Raw(n) => n, + } + } + pub const fn plays(self) -> bool { + let n = self.code(); + n != 0 && n <= 0x77 + } +} diff --git a/src/phonemes/vowel.rs b/src/phonemes/vowel.rs new file mode 100644 index 0000000..622185d --- /dev/null +++ b/src/phonemes/vowel.rs @@ -0,0 +1,38 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Vowel { + A, + I, + U, + E, + O, + N, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VowelLen { + Short, + Long, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct VowelPhoneme(pub Vowel, pub VowelLen); + +impl VowelPhoneme { + pub const fn code(self) -> u8 { + use Vowel::*; + use VowelLen::*; + match (self.0, self.1) { + (A, Short) => 0x01, + (A, Long) => 0x02, + (I, Short) => 0x14, + (I, Long) => 0x15, + (U, Short) => 0x29, + (U, Long) => 0x2A, + (E, Short) => 0x0A, + (E, Long) => 0x0E, + (O, Short) => 0x1D, + (O, Long) => 0x1E, + (N, _) => 0x1B, + } + } +} diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index 54ea9a3..1651f76 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -1,2 +1,2 @@ mod tokens; -pub use tokens::VoiceCode; +pub use tokens::{ControlCode, Digit, Letter, VoiceCode}; diff --git a/src/tokenizer/tokens.rs b/src/tokenizer/tokens.rs index fa1d7e7..d013497 100644 --- a/src/tokenizer/tokens.rs +++ b/src/tokenizer/tokens.rs @@ -1,74 +1,164 @@ -#[derive(Debug, PartialEq, Eq)] +//! The voice-code alphabet. +//! +//! `mMsg_sound_voice_get` folds the game's 256-entry character set down to these 34 +//! values. Everything after this point — word boundaries, capitalisation, sentence +//! structure, and any letter outside `a-z0-9` — is gone. + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ControlCode { - Melody, // 0x80 - Space, // 0x81 - Question, // 0x82 - Bang, // 0x83 - Newline, // 0x84 - Punct, // 0x85 - Bebe, // 0x86, - Control, // -1 + /// ♪ — plays the speaker's melody and bypasses the whole engine. + Melody, // 0x80 + Space, // 0x81 + Question, // 0x82 + Bang, // 0x83 + /// Injected by `mMsg_sound_voice_get2`; never present in the raw table. + Newline, // 0x84 + /// The catch-all for punctuation and symbols. + Punct, // 0x85 + /// The typing click. Only the interpunct (char 133) maps here. + Bebe, // 0x86 + /// An empty `b` / `c` slot in a half-built window. + Empty, // 0xFF + /// `mMsg_sound_voice_get` returning `-1` for `CHAR_CONTROL_CODE` / + /// `CHAR_MESSAGE_TAG`. Never reaches the audio engine: `mMsg_sound_CodeVoice` + /// drops it, and the `c` lookahead folds it to [`ControlCode::Empty`]. + Control, // -1 +} + +impl ControlCode { + /// [`ControlCode::Control`] has no code of its own; it reports `0xFF` because that is + /// what the lookahead substitutes, and it can never reach playback by any other path. + pub const fn code(self) -> u8 { + match self { + ControlCode::Melody => 0x80, + ControlCode::Space => 0x81, + ControlCode::Question => 0x82, + ControlCode::Bang => 0x83, + ControlCode::Newline => 0x84, + ControlCode::Punct => 0x85, + ControlCode::Bebe => 0x86, + ControlCode::Empty | ControlCode::Control => 0xFF, + } + } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Letter { - A, // 0x5D - B, // 0x5E - C, // 0x5F - D, // 0x60 - E, // 0x61 - F, // 0x62 - G, // 0x63 - H, // 0x64 - I, // 0x65 - J, // 0x66 - K, // 0x67 - L, // 0x68 - M, // 0x69 - N, // 0x6A - O, // 0x6B - P, // 0x6C - Q, // 0x6D - R, // 0x6E - S, // 0x6F - T, // 0x70 - U, // 0x71 - V, // 0x72 - W, // 0x73 - X, // 0x74 - Y, // 0x75 - Z, // 0x76 + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, } -#[derive(Debug, PartialEq, Eq)] +impl Letter { + /// `0x5D + (letter - 'a')`. + pub const fn code(self) -> u8 { + use Letter::*; + match self { + A => 0x5D, + B => 0x5E, + C => 0x5F, + D => 0x60, + E => 0x61, + F => 0x62, + G => 0x63, + H => 0x64, + I => 0x65, + J => 0x66, + K => 0x67, + L => 0x68, + M => 0x69, + N => 0x6A, + O => 0x6B, + P => 0x6C, + Q => 0x6D, + R => 0x6E, + S => 0x6F, + T => 0x70, + U => 0x71, + V => 0x72, + W => 0x73, + X => 0x74, + Y => 0x75, + Z => 0x76, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Digit { - D0, // 0x53 - D1, // 0x54 - D2, // 0x55 - D3, // 0x56 - D4, // 0x57 - D5, // 0x58 - D6, // 0x59 - D7, // 0x5A - D8, // 0x5B - D9, // 0x5C + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, + D9, +} + +impl Digit { + /// `0x53 + digit`. This is the *voice* code, not the phoneme code — see + /// `phonemes::digit_code` for what the conversion tables turn it into. + pub const fn code(self) -> u8 { + use Digit::*; + match self { + D0 => 0x53, + D1 => 0x54, + D2 => 0x55, + D3 => 0x56, + D4 => 0x57, + D5 => 0x58, + D6 => 0x59, + D7 => 0x5A, + D8 => 0x5B, + D9 => 0x5C, + } + } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VoiceCode { Letter(Letter), ControlCode(ControlCode), - Digit(Digit) + Digit(Digit), } - -impl Letter { - fn is_shiin(&self) -> bool { - !self.is_boin() +impl VoiceCode { + pub const fn code(self) -> u8 { + match self { + VoiceCode::Letter(l) => l.code(), + VoiceCode::ControlCode(c) => c.code(), + VoiceCode::Digit(d) => d.code(), + } } - fn is_boin(&self) -> bool { - use Letter::*; - [A, E, I, O, U, N].contains(&self) + /// `> 0x77` — the test several rules use to mean "punctuation, not a letter". + pub const fn is_punctuation(self) -> bool { + self.code() > 0x77 } } -- cgit v1.3.1