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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>5. Playback — Animalese</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav class="crumb"><a href="index.html">Animalese pipeline</a> / 5. Playback</nav>
<h1>5. Playback</h1>
<p class="lede">A phoneme code becomes an instrument number, written into a port on a
looping JAudio2 sequence. Everything expressive happens through two float multipliers.</p>
<h2>Punctuation, applied at play time</h2>
<p>By the time the rule engine finishes, punctuation codes are still in <code>a</code>.
The <code>sou_voice_mode</code> switch (<code>game64.c_inc:2810</code>) resolves them, and
splits on whether this is the player's voice:</p>
<h3>Specs 5 and 6 — the player</h3>
<pre>switch (a) {
case 0x81: case 0x84: case 0x85: case 0x86:
Na_SysTrgStart(NA_SE_BEBE); a = 0; break;
}</pre>
<p>Every punctuation code clicks. <code>?</code> and <code>!</code> fall through unmodified
into the phoneme path — the pitch and volume bumps below never apply to the player.</p>
<h3>All other specs</h3>
<table>
<tr><th>Code</th><th>Char</th><th>Effect</th></tr>
<tr><td><code>0x86</code></td><td>click</td><td><code>NA_SE_BEBE</code>, then mute</td></tr>
<tr><td><code>0x81</code> <code>0x84</code> <code>0x85</code></td><td>space, newline, mute</td><td>mute</td></tr>
<tr><td><code>0x82</code></td><td><code>?</code></td><td>mute, and pitch × 1.24 / 1.30 / 1.13 for <code>sou_now_voice_seq</code> 1 / 2 / 3 — the small index <code>Na_SpecChange</code> sets alongside sequence 243 / 244 / 245, not the sequence number itself</td></tr>
<tr><td><code>0x83</code></td><td><code>!</code></td><td>mute, and volume × 1.15</td></tr>
<tr><td>anything else</td><td></td><td>plays; sets <code>sou_now_boin</code> (dead, see <a href="quirks.html">quirks</a>)</td></tr>
</table>
<p>Both multipliers are applied to the spec's <em>base</em> value, which was just reset at
the top of the call, so they last exactly one utterance. The pitch factor differing per
sequence is a per-bank tuning correction, not an expressive choice.</p>
<h2>The emotion LFO</h2>
<p>Six emotion states are set by text control codes and stored in
<code>sou_message_status</code>:</p>
<table>
<tr><th>Control code</th><th>Value</th><th>State</th></tr>
<tr><td><code>SET_MESSAGE_CONTENTS_NORMAL</code></td><td>75</td><td><code>VOICE_STATUS_NORMAL</code></td></tr>
<tr><td><code>SET_MESSAGE_CONTENTS_ANGRY</code></td><td>76</td><td><code>VOICE_STATUS_ANGRY</code></td></tr>
<tr><td><code>SET_MESSAGE_CONTENTS_SAD</code></td><td>77</td><td><code>VOICE_STATUS_SAD</code></td></tr>
<tr><td><code>SET_MESSAGE_CONTENTS_FUN</code></td><td>78</td><td><code>VOICE_STATUS_FUN</code></td></tr>
<tr><td><code>SET_MESSAGE_CONTENTS_SLEEPY</code></td><td>79</td><td><code>VOICE_STATUS_SLEEPY</code></td></tr>
<tr><td><code>SET_MESSAGE_CONTENTS_GLOOMY</code></td><td>97</td><td><code>VOICE_STATUS_GLOOMY</code></td></tr>
</table>
<p>Each drives a short random-period modulation of <code>_18</code> (pitch multiplier) and
<code>_0C</code> (volume multiplier), recomputed per utterance
(<code>game64.c_inc:2880</code>). It is skipped for punctuation
(<code>r29 > 0x77</code>) and for specs 5 and 6, in which case the previous values
persist.</p>
<h3>Angry (1)</h3>
<pre>if (counter == 0) counter = (rand() >> 6) + 10; // 10–13
else counter--;
switch (counter) {
case 0: case 1: case 2: pitch = 1.25, vol = 1.25; break;
case 3: case 4: pitch = 1.20, vol = 1.20; break;
case 9: case 10: pitch = 1.25, vol = 1.25; break;
default: pitch = 0.90, vol = 1.05; break;
}</pre>
<p>Mostly flat and slightly quiet, with two bursts near the top and bottom of each
10–13 syllable cycle — a jagged, shouty contour.</p>
<h3>Sad (2), Sleepy (4), Gloomy (5)</h3>
<pre>if (counter == 0) { effect = (rand() >> 6) + 14; counter = 1; } // 14–17
else if (counter == effect) { counter = 0; sad_toguru ^= 1; }
else counter++;
pitch = (sad_toguru ? 1.1 : 1.0) - counter * 0.022;
vol = (sad_toguru ? 1.1 : 1.0) - counter * 0.025;</pre>
<p>A sawtooth: pitch and volume slide down over 14–17 syllables, then jump back up,
alternating between two baselines. The three states share the code exactly — they
differ only in what the writers chose to tag.</p>
<h3>Fun (3)</h3>
<pre>if (counter == 0) counter = (rand() >> 6) + 10;
else counter--;
switch (counter) {
case 0: case 1: pitch = 1.05, vol = 1.05; break;
case 2: pitch = 0.90, vol = 1.15; break;
default: pitch = 1.15, vol = 1.05; break;
}</pre>
<p>Consistently bright with a dip at the end of each cycle.</p>
<h3>Normal (0)</h3>
<pre>pitch = 1.0; vol = 1.0;</pre>
<h2>Reaching the DSP</h2>
<p>Two subtracks in <code>NA_GROUP4</code> alternate, toggled by
<code>sou_voice_se_toguru</code> on every call, so consecutive syllables can overlap
rather than cutting each other off:</p>
<pre>void Sou_VoiceStart(u8 subtrack, u8 b) {
NA_COMMAND_AUDIO_SUBTRACK_SET_PORT(VOICE_GROUP, subtrack, 0, b);
}</pre>
<p>The guard before it is <code>if (a != 0 && a <= 0x77)</code> — code 0 never plays,
which is why <code>h</code> is silent. The call is also skipped entirely when the raw
code was space, newline or mute (<code>r29 != 0x85 && r29 != 0x81 &&
r29 != 0x84</code>).</p>
<h3>The sequence side</h3>
<p>Each voice sequence's subtrack sits in a spin loop:</p>
<pre>loop: delay 1 tick
MACRO_SET 1 ; publish "I am sequence 1/2/3" …
PORT1_WRITE_MACRO_REG ; … to port 1 (the load-race handshake)
MACRO_READ_PORT0 ; value ← port 0, port 0 ← -1
branch loop if value < 0 ; port empty
SET_DYNTBL 0x0026
DYNTBL_CALL ; call routine dyntbl[phoneme code]
branch loop</pre>
<p>The dynamic table has 128 entries pointing at routines of the shape
<code>C1 <instrument> / 88 <layer addr> / FF</code>, where the note layer holds the
actual note, delay and velocity. Entry <em>v</em> selects instrument
<strong><em>v</em> − 1</strong>; entry 0 is an immediate <code>FF</code> — nothing. The
full annotated disassembly, the per-code note/delay/velocity map, and the tick math are
on <a href="disc.html">the disc page</a>. Two headlines from it: dialogue phonemes play
at semitone 44 / 41 / 39 (sequences 243 / 244 / 245) against a pitch table with unity at
48, so each bank is pitched down 4–9 semitones from its recordings; and port reads are
destructive (the port resets to −1), which is why one <code>Sou_VoiceStart</code> write
triggers exactly one syllable.</p>
<h3>Per-frame parameter push</h3>
<p><code>Sou_VoiceMake</code> (<code>game64.c_inc:1174</code>) runs for both subtracks once
per game frame, inside <code>Na_GameFrame</code>:</p>
<pre>volumeScale = _08 * _0C; // spec base × emotion volume
frequencyScale = _14 * _18; // spec base × emotion pitch
NA_COMMAND_AUDIO_SUBTRACK_SET_VOL_SCALE (VOICE_GROUP, index, volumeScale);
NA_COMMAND_AUDIO_SUBTRACK_SET_FREQ_SCALE(VOICE_GROUP, index, frequencyScale);
NA_COMMAND_AUDIO_SUBTRACK_SET_PAN (VOICE_GROUP, index, pan);
NA_COMMAND_AUDIO_SUBTRACK_REVERB_VOLUME (VOICE_GROUP, index, reverbVolume);</pre>
<p><code>pan</code> is initialised to <code>0x3F</code> (centre) and <code>reverbVolume</code> to 0,
and neither is ever written for voice subtracks. <strong>Animalese is always centred, dry,
and unaffected by the speaker's distance from the camera</strong> — unlike every other
positional SE in the game.</p>
<p>Because both subtracks share <code>sou_voice_se[0]</code> and <code>[1]</code> which are
always written together, an in-flight syllable's volume and pitch are retroactively
changed by the next syllable's settings.</p>
<h3>Frame ordering</h3>
<p><code>graph.c</code> runs <code>game_main</code> (including the draw pass that calls
<code>Na_VoiceSe</code>) and <em>then</em> <code>sAdo_GameFrame</code>. So within one frame:</p>
<pre>game_main → mFontSentence_animal_voice_se → Na_VoiceSe (sets params, writes port)
Na_GameFrame → Sou_VoiceMake (pushes params to the DSP)
→ Sou_SpecialRoutine10 (fires any queued second syllable)</pre>
<p><code>Sou_VoiceMake</code> runs <em>before</em> <code>Sou_SpecialRoutine10</code>, so a
deferred syllable's parameters land one frame late.</p>
<h2>Banks</h2>
<p>From <code>src/static/jaudio_NES/game/audioheaders.c</code>:</p>
<table>
<tr><th>Bank</th><th>ROM addr</th><th>Size</th><th>param0</th><th>param1</th><th>Instruments</th></tr>
<tr><td>156</td><td><code>0x0005C260</code></td><td><code>0x4500</code></td><td><code>0x05FF</code></td><td><code>0x7700</code></td><td>119</td></tr>
<tr><td>157</td><td><code>0x00060760</code></td><td><code>0x3020</code></td><td><code>0x05FF</code></td><td><code>0x5300</code></td><td>83</td></tr>
<tr><td>158</td><td><code>0x00063780</code></td><td><code>0x4500</code></td><td><code>0x05FF</code></td><td><code>0x7700</code></td><td>119</td></tr>
</table>
<p><code>param1 >> 8</code> is the instrument count, <code>param0 >> 8</code> the sample
bank id (5 for all three), and all have zero drums and zero SFX. 119 instruments =
phoneme codes <code>0x01</code>–<code>0x77</code> exactly.</p>
<p>Bank 157 stopping at 83 is not an omission, and nothing is lost. The phonology
engine's maximum output is <code>0x52</code> plus <code>0x77</code>; instruments 0–81
cover <code>0x01</code>–<code>0x52</code>, and the missing
<code>0x53</code>–<code>0x76</code> range is the
<a href="editor.html">editor's letter-name block</a>, which spec 3 can never reach.
For <code>you</code> (<code>0x77</code>), sequence 244's dynamic table explicitly routes
the code to <strong>instrument 82</strong> — the bank's 83rd and last instrument, which
sits above the phonology range and is reachable by nothing else. Bank 157 carries one
extra instrument precisely to be its <code>you</code>.</p>
<p>Sample details are on <a href="disc.html">the disc page</a>: N64 VADPCM, tuning
0.689062 (22050 Hz at a 32000 Hz mixer), order 2, two predictors, no loop,
32–407 ms each (mean ≈ 190 ms).</p>
<div class="pager">
<span><a href="phonology.html">← 4. The phonology engine</a></span>
<span><a href="soundfont.html">6. The soundfont lookup →</a></span>
</div>
</body>
</html>
|