aboutsummaryrefslogtreecommitdiff
path: root/docs/gating.html
blob: 9e78ac027fb9d18ce74743467c7266e4f8c1e7e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>1. Gating &amp; voice selection — Animalese</title>
<link rel="stylesheet" href="style.css">
</head>
<body>

<nav class="crumb"><a href="index.html">Animalese pipeline</a> / 1. Gating &amp; voice selection</nav>

<h1>1. Gating &amp; voice selection</h1>
<p class="lede">Before a single character is converted, two decisions are made: whether
this message speaks at all, and with whose voice.</p>

<h2>Voice mode</h2>

<p>There are three voice modes, pushed to the audio engine by
<code>sAdo_SetVoiceMode</code> and stored in <code>sou_voice_mode</code>:</p>

<table>
<tr><th>Mode</th><th>Name</th><th>Behaviour</th></tr>
<tr><td><code>0</code></td><td><code>VOICE_MODE_ANIMALESE</code></td><td>full phonology, samples from the voice bank</td></tr>
<tr><td><code>1</code></td><td><code>VOICE_MODE_CLICK</code></td><td>every non-space character plays <code>NA_SE_BEBE</code>, one blip</td></tr>
<tr><td><code>2</code></td><td><code>VOICE_MODE_SILENT</code></td><td>nothing</td></tr>
</table>

<p>The mode is not simply the player's config setting. It is the product of the setting
and of <em>who is talking</em> — <code>mMsg_sound_voice_mode_get</code>:</p>

<pre>mode_type = Save_Get(config).voice_mode        // 0 animalese, 1 bebese, 2 silent
if (msg has VOICE_SILENT flag) mode_type = SILENT

if (mMsg_check_sound_animal(msg))
    mode = { ANIMALESE, CLICK, SILENT }[mode_type]   // an animal is speaking
else if (mode_type == SILENT)
    mode = SILENT
else
    mode = CLICK                                     // narration, signs, menus</pre>

<p class="src">Source: <code>src/game/m_msg_sound.c_inc:259</code></p>

<p>So the config's "animalese" setting only unlocks animalese for messages that pass
<code>mMsg_check_sound_animal</code>. Everything else — item descriptions, bulletin
boards, system prompts — is click mode no matter what.</p>

<h3>What counts as an animal talking</h3>

<p><code>mMsg_check_sound_animal</code> (<code>m_msg_sound.c_inc:66</code>) returns true when the
<code>VOICE_CLICK</code> flag is clear <em>and</em> any of:</p>

<ul>
  <li><code>msg_p-&gt;show_actor_name</code> — the window has a speaker nameplate;</li>
  <li><code>mMsg_check_sound_special</code> — message number in
      <code>0x1743–0x1756</code> (event start), <code>0x1799–0x17AC</code> (event end),
      or <code>0x3A8A–0x3AF0</code> (Dial-a-Psychic);</li>
  <li><code>mMsg_check_sound_shasho</code> — message number in <code>0x35AF–0x35BE</code>
      (the conductor).</li>
</ul>

<p>The special ranges exist so that disembodied event narration still gets a voice.
They are forced to <strong>spec 4</strong> regardless of any actor
(<code>mMsg_sound_spec_change_voice_force</code>, <code>:288</code>).</p>

<h2>Spec: which of the three voices</h2>

<p>The speaker maps to a "spec" (2–9), and the spec selects a sequence and bank.
<code>Na_SpecChange</code> (<code>game64.c_inc:3459</code>) is the switch, and it is a no-op
if the spec is unchanged — otherwise it stops the voice sequence, clears the stay
cache, and starts a new one:</p>

<table>
<tr><th>Spec</th><th>Sequence</th><th>Bank</th><th>Instruments</th><th>Used by</th></tr>
<tr><td>2, 5, 7, 9</td><td>243</td><td>156</td><td>119</td><td>default villagers, most specials, male player, Timmy/Tommy, Farley</td></tr>
<tr><td>3</td><td>244</td><td>157</td><td>83</td><td>Wendell, Pete, Booker, Jack</td></tr>
<tr><td>4, 6, 8</td><td>245</td><td>158</td><td>119</td><td>Pelly, Phyllis, Mabel, Sable, Blanca, female player, Joan, event narration</td></tr>
</table>

<p>Spec also selects a base timbre inside <code>Na_VoiceSe</code>
(<code>game64.c_inc:2760</code>), reapplied on every call:</p>

<table>
<tr><th>Spec</th><th>Volume (<code>_08</code>)</th><th>Pitch (<code>_14</code>)</th><th>Character</th></tr>
<tr><td>2, 3, 4</td><td>0.65</td><td>1.00</td><td>baseline</td></tr>
<tr><td>5, 6</td><td>0.90</td><td>1.00</td><td>player (louder, unmodulated)</td></tr>
<tr><td>7</td><td>0.90</td><td>1.30</td><td>squeaky — Timmy &amp; Tommy</td></tr>
<tr><td>8</td><td>0.90</td><td>0.75</td><td>low — Joan</td></tr>
<tr><td>9</td><td>0.70</td><td>0.65</td><td>very low — Farley</td></tr>
</table>

<p>Note that <strong>spec is orthogonal to bank</strong>: specs 5, 7 and 9 all use bank 156 and
differ only in this pitch/volume pair. There are three sample sets and eight voices.</p>

<h3>Villagers</h3>

<p><code>mNpc_GetNpcSoundSpec</code> (<code>src/game/m_npc.c:4116</code>) indexes by personality:</p>

<pre>static int spec_table[mNpc_LOOKS_NUM] = { 4, 4, 2, 2, 3, 4 };
//   normal(girl) 4 | peppy 4 | lazy(boy) 2 | jock 2 | cranky 3 | snooty 4</pre>

<p>An out-of-range <code>looks</code> falls back to <code>mNpc_LOOKS_BOY</code> (spec 2).</p>

<h3>Special NPCs</h3>

<p>Non-villagers fall through to <code>mNpc_GetNpcSoundSpecNotAnimal</code>, a linear scan of
<code>l_sp_actor_name</code> (<code>m_npc.c:32</code>). The default when not found is spec&nbsp;2.
Everything in that table is spec 2 except:</p>

<table>
<tr><th>Spec</th><th>NPCs</th></tr>
<tr><td>3</td><td>Wendell, Pete, Booker, Jack</td></tr>
<tr><td>4</td><td>Pelly, Phyllis, Mabel, Sable, Blanca</td></tr>
<tr><td>8</td><td>Joan</td></tr>
<tr><td>9</td><td>Farley</td></tr>
</table>

<h3>The player's own voice: specs 5 and 6</h3>

<p>Specs 5 and 6 never appear in any NPC table. They are set from the submenu
(<code>src/game/m_submenu.c:364</code>) when a menu that can type text opens:</p>

<pre>if ((menu_type == mSM_OVL_LEDIT &amp;&amp; param0 == 0) ||
    Common_Get(now_private)-&gt;gender == mPr_SEX_MALE) {
    sAdo_SpecChange(5);          // male
} else {
    sAdo_SpecChange(6);          // female
}
sAdo_SetVoiceMode(0);            // force animalese on regardless of config</pre>

<p>Two things follow. First, letter-writing and name entry force animalese even when the
player has set the config to click or silent. Second, specs 5 and 6 are treated as a
distinct class throughout <code>Na_VoiceSe</code>:</p>

<ul>
  <li>every punctuation code (<code>0x81</code>, <code>0x84</code>, <code>0x85</code>,
      <code>0x86</code>) plays <code>NA_SE_BEBE</code> instead of being silently dropped —
      the typing click;</li>
  <li>the <code>?</code> pitch bump and <code>!</code> volume bump never apply;</li>
  <li>the emotion LFO is skipped entirely;</li>
  <li>anti-repetition substitution is disabled.</li>
</ul>

<p>See <a href="editor.html">the editor path</a> for what actually gets spoken there.</p>

<h3>Timmy and Tommy</h3>

<p>Two text control codes toggle a temporary spec override, used for Nook's nephews:</p>

<table>
<tr><th>Code</th><th>Value</th><th>Effect</th></tr>
<tr><td><code>mFont_CONT_CODE_VOICE_TRUE</code></td><td>92 (0x5C)</td><td>restore <code>msg_p-&gt;spec</code></td></tr>
<tr><td><code>mFont_CONT_CODE_VOICE_FALSE</code></td><td>93 (0x5D)</td><td>switch to spec 7 (pitch × 1.3)</td></tr>
</table>

<p>Both guard on the actor being <code>SP_NPC_MAMEDANUKI0/1</code>, so the codes are inert in
any other message. <span class="src"><code>m_msg_sound.c_inc:317–340</code></span></p>

<h2>Hard gates inside Na_VoiceSe</h2>

<p>Four early returns drop the call regardless of everything above
(<code>game64.c_inc:2724–2735</code>):</p>

<ul>
  <li><code>voice &gt; 0x12B</code> — NPC voice id out of range (299 max);</li>
  <li>animalese mode and <code>Nap_ReadSubPort(VOICE_GROUP, 0, 1) != sou_now_voice_seq</code>
      — the voice sequence has not finished loading yet, so early characters of the
      first line after a spec change are silently dropped;</li>
  <li><code>sou_scene_mode == 0</code> — audio not in a playing scene;</li>
  <li><code>a == 0x85 &amp;&amp; b == 0xFF &amp;&amp; c == 0xFF</code> — a lone mute character,
      handled at the very top before any state is touched.</li>
</ul>

<div class="pager">
  <span><a href="index.html">← Index</a></span>
  <span><a href="encoding.html">2. Text → voice code →</a></span>
</div>

</body>
</html>