From 46227e2003de28202cc4e82bca4f7f4745476c4e Mon Sep 17 00:00:00 2001 From: Alexander Kassouni Date: Sat, 5 Sep 2026 17:51:13 -0700 Subject: init --- docs/phonology.html | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 docs/phonology.html (limited to 'docs/phonology.html') diff --git a/docs/phonology.html b/docs/phonology.html new file mode 100644 index 0000000..ebbc872 --- /dev/null +++ b/docs/phonology.html @@ -0,0 +1,235 @@ + + + + + +4. The phonology engine — Animalese + + + + + + +

4. The phonology engine

+

Na_VoiceSe — src/static/jaudio_NES/game/game64.c_inc:2598. +Given three voice codes it produces one phoneme code now and optionally queues a second +for the next frame.

+ +
void Na_VoiceSe(u8 a, u8 b, u8 c, s16 voice, u8 scale, u8 f)
+ +

f is the calling mode: 0 is the normal message path, +2 is the deferred second syllable, 1 is the +editor path which skips this page entirely.

+ +

Vowel / consonant classification

+ +

Sou_BoinShiinCheck (:2564) — boin = vowel, +shiin = consonant:

+ + + + + + +
ReturnsClassMembers
0vowela e i o u and n
1consonantthe other 20 letters
2neitherdigits and every punctuation code
+ +

n being a vowel is deliberate — it is a syllabic nasal in Japanese, and this +engine was written by Japanese programmers modelling English through a kana-shaped +lens. It changes when the silent-e and magic-e rules fire, and it is +the single most surprising thing in the engine.

+ +

Rule order for mode 0

+ +

State is carried in sou_last_num and sou_last_num2, the previous +call's raw a and b. The originals are also stashed in +sou_num_org/sou_num2_org/sou_num3_org before anything is +rewritten.

+ +

0. Lone mute

+
if (a == 0x85 && b == 0xFF && c == 0xFF) return;
+

Returns before touching any state, so a lone punctuation mark does not disturb the +previous-character history.

+ +

1. Silent e (:2612)

+
if (a == 'e' && b > 0x77 && class(last_num) == vowel && class(last_num2) == consonant) {
+    a = b;  r28 = 4;                      // drop the e, speak the punctuation slot
+}
+if (b == 'e' && c > 0x77 && class(last_num2) == vowel && class(a) == consonant) {
+    b = 0;  r28 = 4;                      // drop the e, queue nothing
+}
+

A word-final e preceded by vowel + consonant is dropped: make, +time, nose. Because n classifies as a vowel, done and +gone also trip it. Setting r28 = 4 skips the digraph pass entirely.

+ +

2. Digraph merge (:2623)

+
a = Sou_ConnectCheck(a, b, c);
+r28 = (a == sou_num_org) ? 1 : 2;         // 2 = merged, both letters consumed
+

If a digraph matched, r28 = 2 and control falls straight through to +playback: the merged phoneme plays and nothing is queued, so both letters +are consumed by one sample.

+ +

The digraph table — Sou_ConnectCheck (:2316)

+

53 entries. All are two characters except you, the only rule that reads c; one pairs a letter with a space rather than another letter (i␣).

+ +
+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 +
+ +

The design is transparent. Fourteen entries are genuine English digraphs — +ch sh th ng oo ow ou oy ai ea ar er ir or. The other thirty-nine are the +openings of common English words: he ho hi ha me my no on of oh ok so to up we +by do go is it in if as an al na se si su fa fo ge ze ks you. The engine does not +model English phonetics; it memorises the most frequent English word-starts and gives +each one a recorded syllable.

+ +

3. Punctuation flush (:2630)

+
if (b is punctuation) {
+    r28 = 3;
+    if (sou_last_num2 was also punctuation) r28 = 1;   // don't flush twice
+}
+

When a word ends, r28 = 3 re-runs the merge one character further back:

+
a = Sou_ConnectCheck(sou_last_num2, sou_num_org, sou_num2_org);
+if (a < 0x53 || a > 0x76) break;      // it merged, or wasn't a letter — leave it
+a = Sou_TanboinHenkan(a, b);
+

This gives the final letter of a word a chance to pair with the letter before it +instead of being dropped against the following space. Note the guard also drops the +whole branch if a > 0x77 before it starts.

+ +

4. Vowel length (:2658, for r28 == 1 or 4)

+
if (class(a) == vowel && class(b) == consonant && c == 'e')
+    a = Sou_ChouboinHenkan(a, b);      // long  — magic e, as in cake
+else
+    a = Sou_TanboinHenkan(a, b);       // short
+
+sou_num2_request   = b;                // queue the second syllable
+sou_num2_animal_id = voice;
+sou_num2_scale     = scale;
+ +

Chouboin = long vowel, tanboin = short vowel. The two tables are +identical except for five entries:

+ + + + + +
aiueon
short0x010x140x290x0A0x1D0x1B
long0x020x150x2A0x0E0x1E0x1B
+ +

n is the same in both — it is only classified as a vowel, it has no long form. +Note the aliasing: long a (0x02) is the same sample as digraph al, +long i (0x15) is ea and consonant y, long e +(0x0E) is re, long u (0x2A) is oo.

+ +

Consonants (both tables)

+ + + + + + + + +
LetterCodeLetterCodeLetterCodeLetterCode
b0x07g0x12m0x1At0x26
c0x23 / 0x18h0x00p0x21v0x2D
d0x09j0x17q0x18w0x2E
f0x11k0x18r0x22x0x31
l0x19s0x23y0x15
z0x30
+ +

Only one context rule exists: c is soft (0x23, same as s) when the +next letter is i or e, hard (0x18, same as k) +otherwise. h maps to 0x00, which has no instrument — h is +silent unless it merges into a digraph.

+ +

Digits

+ + + +
0123456789
0x320x330x260x270x340x350x360x370x100x38
+

Eight of the ten reuse a code the letter tables also produce, and the overlaps line up +with the first syllable of the spoken number: 0=ze(ro), +1=wo(one), 2=t(wo), +4=fo(ur), 5=fa(five), +6=si(x), 7=se(ven), +9=na(nine). Only 3 (0x27) and 8 (0x10) +get codes nothing else reaches. The digraph names here are just labels for shared +samples — the recording itself presumably says the number.

+ +

The deferred second syllable — mode 2

+ +

Sou_SpecialRoutine10 (:1708) runs once per game frame inside +Na_GameFrame:

+ +
if (sou_Na_VoiceSeFlag == 1) {
+    sou_Na_VoiceSeFlag = 0;               // a real call happened this frame
+} else if (sou_num2_request) {
+    Na_VoiceSe(sou_num2_request, 0, 0, sou_num2_animal_id, sou_num2_scale, 2);
+    sou_num2_request = 0;
+}
+ +

So the queued b plays on the first frame where no fresh call arrived. Mode 2 +is a stripped-down path:

+ +
if (sou_voice_mode == 1) return;                    // click mode: no second syllable
+if (a == 0xFF) return;
+if (a == 'e' && sou_num3_org > 0x77 &&
+    class(sou_last_num2) == vowel && class(sou_num_org) == consonant)
+    a = 0;                                          // silent-e check again
+else
+    a = Sou_TanboinHenkan(a, sou_num3_org);         // always short
+ +

The queued syllable never gets the long-vowel treatment and never re-checks digraphs, +which is why the second half of a pair always sounds flatter than the first.

+ +

Anti-repetition

+ +
// mode 0
+if (sou_num_org == sou_last_num2 && sou_num_org == sou_last_num) i = 1;
+…
+if (spec == 5 || spec == 6 || r29 > 0x77) i = 0;
+if (i == 1) a = Nap_GetRandom() >> 2;               // 0x00–0x3F
+ +

Three identical voice codes in a row — aaa, mmm, zzz — +substitute a uniformly random phoneme from the first 64. This is the only path that +reaches many of the codes never produced by any rule, and it is why repeated-letter +interjections in dialogue sound like nonsense rather than a stutter. It is disabled +for the player's voice (specs 5 and 6).

+ +

♪ — the melody escape

+ +
if (a == 0x80) {
+    if (voice == 0xFA) return;
+    switch (sou_voice_mode) {
+        case 0: Na_MelodyVoice(voice); break;      // animalese
+        case 1: Na_SysTrgStart(NA_SE_BEBE); break; // click
+        case 2: break;                             // silent
+    }
+    return;
+}
+ +

Na_MelodyVoice (src/static/jaudio_NES/game/melody.c:727) plays a +fixed 16-note pattern {13, 14 × 15} on subtrack 6 at volume 1.3, using the +speaker's npc_voice_id to select which chunk of sequence 248 to copy in. The +id is the NPC's instrument type (draw_data.voice_type), the same one used for +the town tune — so ♪ plays "their" instrument rather than a phoneme.

+ +
+ ← 3. Windowing & timing + 5. Playback → +
+ + + -- cgit v1.3.1