aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlexander Kassouni <alex@kassouni.net>2026-09-05 22:43:52 -0700
committerAlexander Kassouni <alex@kassouni.net>2026-09-05 22:43:52 -0700
commit24ec2a92b56ca4296fe65b70524e36a8e1c67c2e (patch)
tree51fd27e87cbd419ab74f016852fbaa2d86babd9a /src
parent7f5dd54c60c70f29d0c8abedc28dd5ed199de94d (diff)
phonemes
Diffstat (limited to 'src')
-rw-r--r--src/main.rs2
-rw-r--r--src/phonemes/consonant.rs53
-rw-r--r--src/phonemes/digit.rs31
-rw-r--r--src/phonemes/digraph.rs118
-rw-r--r--src/phonemes/mod.rs11
-rw-r--r--src/phonemes/phonemes.rs30
-rw-r--r--src/phonemes/vowel.rs38
-rw-r--r--src/tokenizer/mod.rs2
-rw-r--r--src/tokenizer/tokens.rs202
9 files changed, 427 insertions, 60 deletions
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
}
}