Things a faithful port has to reproduce, things a clean reimplementation should drop, and where earlier notes were wrong.
if (scale > 0x20) {
scale = scale - 0x20;
f31 = scale;
f31 = f31 * 0.15f;
f30 = sou_voice_se[0]._08 + f31; // computed…
f30 = 4.f; // …and immediately discarded
sou_voice_se[0]._08 = f30;
} else {
scale = 0x1f - scale;
f31 = scale * 0.02f;
f30 = sou_voice_se[0]._08 - f31; // computed…
f30 = 0.4f; // …and immediately discarded
sou_voice_se[0]._08 = f30;
}
game64.c_inc:2994
The intent was clearly a smooth scale-to-volume ramp. What ships is a step function:
any glyph larger than normal slams volume to 4.0 (a 6× jump over the
0.65 baseline), anything smaller to 0.4. Normal-size text passes
scale == 32 and skips the block entirely, so this is only audible on
deliberately scaled dialogue.
This is in the matching decomp, so retail does it. A faithful port needs it; a clean library does not.
case 2: {
if (sou_num_org == sou_num_org && sou_num_org == sou_last_num2) i = 1;
}
game64.c_inc:2711
The first comparison is always true. The intent was presumably a three-in-a-row check mirroring mode 0; the effect is a two-in-a-row check. Deferred syllables therefore trigger the random substitution more often than intended.
const u8 SHIIN2BOIN[] = { 0, 1, 2, 3, 4, 5, 1, 2, … }; // 86 entries
…
sou_now_boin = SHIIN2BOIN[a]; // a can be up to 0x77 = 119
game64.c_inc:236, written at :2863
sou_now_boin is written here and initialised in Sou_InitAudio, and
never read anywhere in the tree. Same for sou_nobasu_count
("elongation count"), which is only ever set to 0. Both are vestigial mouth-shape /
vowel tracking, presumably live in the N64 Dōbutsu no Mori code this was ported
from. Because the table is 86 entries and a can reach 119, the write reads
past the array — harmless only because the result is discarded.
The table also has an obvious data oddity: entry 45 is the value 45, where
every neighbour is 0–5.
n is a vowel. Sou_BoinShiinCheck groups it with
a e i o u. This changes when silent e and magic e fire, so
done, gone and none get the silent-e drop.h is silent. Sou_TanboinHenkan('h') returns
0x00, and code 0 has no instrument (guarded by
if (a != 0 && a <= 0x77)). Standalone h makes no sound;
it only speaks through ch sh th ha he hi ho oh.mMsg_sound_voice_entry
refuses voice_no == 0x81 in the "first character" branch, so word-initial
pairing always starts on a letter.pan stays at
0x3F and reverbVolume at 0 for the life of the program. A
villager at the far edge of the screen is exactly as loud and as centred as one in
your face — the only positional SE in the game that ignores position.sou_voice_se[0]
and [1] are always written together, and Sou_VoiceMake pushes
both every frame — so a new syllable retroactively changes the volume and pitch
of the one still ringing.mMsg_check_sound_animal; and the submenu forces
sAdo_SetVoiceMode(0) regardless of config, so letter-writing always
speaks even for players who set the game to silent.Na_VoiceSe returns early until
Nap_ReadSubPort(VOICE_GROUP, 0, 1) == sou_now_voice_seq. The first
syllables of the first line with a new speaker can be silently lost.The following supersede statements from earlier working notes
(extraction.md, since retired — its surviving content lives on
the disc page).
| Was | Actually |
|---|---|
"Bank 157 has only 83 instruments … that voice collapses every digraph sample
above 0x53 onto one sound." |
Nothing above 0x53 is a digraph. The phonology engine's maximum output is
0x52, plus 0x77. Bank 157 covers the phonology range with one
instrument to spare, the missing block is the editor's letter
names (unreachable at spec 3), and nothing is lost: sequence 244 explicitly maps
you to that 83rd instrument — a dedicated substitute, not a clamp. |
| "43 of the 119 codes are unreachable through normal text … only ever hit by the anti-repetition random substitution." | 36 of them (0x53–0x76) are reachable — through the mode-1
editor path — and the random substitution can not reach them, since it only
produces 0x00–0x3F. |
"Sou_SpecialRoutine10 replays it on the next audio frame." |
It runs inside Na_GameFrame, which graph.c calls once per
game frame. Same ~17 ms at 60 fps, but the mechanism is the game loop, and it
halves under SetGameFrame(2). |
"♪ … calls Na_MelodyVoice(npc_id) — the villager's tune." |
It plays a fixed 16-note pattern {13, 14 × 15}, not a tune. The per-NPC
part is which instrument: npc_voice_id selects a chunk of sequence
248 to copy in. It is the speaker's timbre playing a constant figure. |
| Specs 5 and 6 listed only in the spec→sequence table, unattributed. | They are the player's own voice in text-entry menus — 5 male, 6
female — set from m_submenu.c:364, and are special-cased throughout
Na_VoiceSe. |
| Voice mode not covered. | Three modes gate the whole pipeline, and the gate depends on the message, not just the config. See gating. |
Mode f == 1 not covered. |
A third calling convention that bypasses phonology entirely. See the editor path. |
Everything else in the retired notes was re-verified against the current tree and holds: the voice table, the 53 digraph entries, both vowel tables, the per-spec timbre values, and the spec→sequence→bank mapping. The table contents, bank layouts and sequence bytecode were additionally verified byte-for-byte against a dumped disc image (see the disc page).