Everything a reimplementation needs to pull the samples and sequences out
+of a dumped disc, byte by byte: the CISO container, the GameCube filesystem, the DOL,
+the five audio tables, the bank blobs, and the VADPCM decoder. Every offset on this page
+was verified against a real GAFE01 USA rev 0 image.
All disc and audio structures are big-endian. The single exception is
+the CISO header's block size, which is little-endian.
+
+
1. The CISO container
+
+
A .ciso file is a raw disc image with all-zero blocks removed. The header
+is 0x8000 bytes:
+
+
+
Offset
Size
Field
+
0x0000
4
magic "CISO" (43 49 53 4F)
+
0x0004
4
block size, little-endian u32 (commonly 0x200000)
+
0x0008
0x7FF8
block map: one byte per logical block; 1 = stored, 0 = omitted (reads as zeros)
+
+
+
Stored blocks follow the header back to back, in logical order. To read logical
+offset x:
+
+
b = x / block_size
+if map[b] == 0: the bytes are zero
+else: physical = 0x8000 + rank(b) * block_size + x % block_size
+ where rank(b) = number of set map entries before index b
+
+
Precomputing a prefix-sum over the map gives O(1) reads. A plain .iso /
+.gcm is the same image without this layer — detect by checking for the
+CISO magic and fall through to raw offsets if absent.
+
+
2. The GameCube disc
+
+
Only four header fields matter here:
+
+
+
Offset
Size
Field
GAFE01 rev 0 value
+
0x0000
6
game code — verify "GAFE01"
+
0x0420
4
offset of the main DOL
0x0001E000
+
0x0424
4
offset of the FST
0x000FE500
+
0x0428
4
size of the FST
0x102
+
+
+
The FST
+
+
The filesystem table is an array of 12-byte entries followed by a string table:
+
+
entry {
+ u8 flags; // 0 = file, 1 = directory
+ u24 name_offset; // into the string table (NUL-terminated)
+ u32 offset; // file: absolute disc offset dir: parent index
+ u32 length; // file: byte length dir: index of next sibling
+}
+
+
Entry 0 is the root; its length is the total entry count, and the string
+table starts at fst + count * 12. Animal Crossing's FST has just 11 entries.
+The one that matters:
+
+
audiorom.img offset 0x626D10 size 0x7EA760
+
+
The game opens it by name — Jac_InitARAM checks
+Jac_CheckFile("/audiorom.img") and neosthread.c DMAs the whole file
+into ARAM. Walk the FST by name rather than hardcoding the offset; the size, however, is
+structural (it is the sum of the three region sizes below) and makes a good integrity
+check. internal/dummyrom.c:68,
+internal/neosthread.c:56
+
+
3. The DOL and the five tables
+
+
The audio tables are compiled into the main executable, not stored in
+audiorom.img. A DOL starts with a 0x100-byte header:
To read virtual address va: find the section with
+address ≤ va < address + size, then
+file_offset = dol_offset + section_offset + (va − address). The GAFE01 rev 0
+DOL's populated sections:
+
+
+
Section
File offset (in DOL)
Virtual address
Size
+
text0
0x000100
0x80003100
0x02520
+
text1
0x002620
0x800056C0
0xA40C0
+
data0
0x0A66E0
0x80005620
0x00040
+
data1
0x0A6720
0x80005660
0x00060
+
data2
0x0A6780
0x800A9780
0x00040
+
data3
0x0A67C0
0x800A97C0
0x00020
+
data4
0x0A67E0
0x800A97E0
0x06080
+
data5
0x0AC860
0x800AF860
0x32A20
+
data6
0x0DF280
0x80217B80
0x00700
+
data7
0x0DF980
0x80218BE0
0x00B40
+
+
+
The five tables all live in data5 (addresses from the ac-decomp symbol
+map for GAFE01 rev 0; other revisions have different addresses):
+
+
+
Symbol
Virtual address
Size
Entries
Disc offset
+
AudioseqHeaderStart
0x800CCA40
0xFA0
249
0x0E7A40
+
AudiobankHeaderStart
0x800CD9E0
0xA00
159
0x0E89E0
+
AudiowaveHeaderStart
0x800CE3E0
0x70
6
0x0E93E0
+
AudiodataHeaderStart
0x800CE450
0x40
3
0x0E9450
+
AudiomapHeaderStart
0x800CE490
0x3F0
—
0x0E9490
+
+
+
"Disc offset" is the absolute image offset:
+0x1E000 + 0xAC860 + (va − 0x800AF860). The
+ArcHeader/ArcEntry formats, the meaning of
+param0/1/2, and the zero-size alias-entry rule are on
+the soundfont page. An alternative to parsing the DOL is to
+hardcode the table contents — they are plain data in
+audioheaders.c of the decomp — but reading them from the image keeps the
+tool self-contained and self-checking.
+
+
4. audiorom.img
+
+
AudiodataHeaderStart carves the file into three regions. Their sizes sum
+to exactly the file size:
+
+
+
Region
Offset in file
Size
Absolute disc offset
+
Audioseq
0x000000
0x0CF700
0x626D10
+
Audiobank
0x0CF700
0x067C80
0x6F6410
+
Audiowave
0x137380
0x6B33E0
0x75E090
+
+
+
Entry addresses in the sequence, bank and wave tables are offsets into their region.
+The animalese entries:
+
+
+
Entry
Region offset
Size
Absolute disc offset
param0
param1
+
seq 243
0x0ABA60
0x660
0x6D2770
+
seq 244
0x0AC0C0
0x480
0x6D2DD0
+
seq 245
0x0AC540
0x660
0x6D3250
+
bank 156
0x05C260
0x4500
0x752670
0x05FF
0x7700
+
bank 157
0x060760
0x3020
0x756B70
0x05FF
0x5300
+
bank 158
0x063780
0x4500
0x759B90
0x05FF
0x7700
+
wave bank 5
0x2C5DC0
0x3ED620
0xA23E50
+
+
+
5. Walking a bank blob
+
+
The structures are defined on the soundfont page; this is
+the walk, with bank 156's real numbers. All internal fields are offsets
+relative to the bank blob's start, except sample, which is
+relative to the wave bank's start.
Facts that hold across every instrument in all three voice banks, verified by
+exhaustive scan:
+
+
+
Only the normal wtstr slot is populated; low/high are null.
+
tuning is always 0.689062 = 22050 / 32000 — the recordings
+ are 22050 Hz and the mixer runs at 32000 Hz.
+
codec = 0 (VADPCM), order = 2,
+ n_predictors = 2, loop.count = 0 (no looping);
+ loop.end equals the decoded sample count.
+
Every instrument shares the same envelope: (1,0) (1,32700) (-1,0) —
+ one envelope step at zero, one step rising to 32700/32768, then hang
+ (-1 = ADSR_HANG) until the note is released, then fade at
+ adsr_decay_table[250]. Effectively: instant attack, full sustain,
+ fast release.
+
Durations run 32–407 ms (mean ≈ 189 ms). The longest samples are the
+ editor's letter-name block — "zero" at 407 ms is a whole word.
+
The three banks' samples tile wave bank 5 contiguously and share nothing:
+ 156 owns 0x341130–0x385890, 157 0x385890–0x3AAAAC,
+ 158 0x3AAAB0–0x3ED612 — each voice is a separate recording session.
+ (This is also what pins sample as wave-bank-relative: bank 158's last
+ sample ends 14 bytes short of the wave bank's exact end, and treating the offsets
+ as region-relative lands mid-stream in invalid VADPCM.)
+
+
+
6. Decoding VADPCM
+
+
Each frame is 9 bytes → 16 samples. Total samples = size / 9 × 16.
+
+
byte 0: scale = header >> 4 (0–12 in these banks)
+ predictor = header & 0xF (0 or 1)
+bytes 1–8: 16 nibbles, high first — sign-extend each from 4 bits,
+ then residual r[i] = nibble << scale
+
+
The codebook is stored as order × 8 signed 16-bit coefficients per
+predictor. Expand each predictor into an 8-row × (order+8)-column matrix
+M:
+
+
M[row][col] for col < order: book[predictor][col*8 + row]
+M[row][order + i] = 2048 if i == row
+ = M[row − i − 1][order−1] if i < row
+ = 0 if i > row
+
+
Decode two 8-sample halves per frame. Each half takes an input vector of the previous
+order (= 2) decoded samples followed by that half's 8
+scaled residuals:
+
+
in = [ out[-2], out[-1], r[0..7] ]
+out[row] = floor( Σ M[row][k] · in[k] / 2048 ) for row 0..7
+
+
Initial state is two zero samples; state carries across frames for the length of the
+sample. The division is a floor (round toward −∞), not truncation.
+
+
+The one easy mistake is feeding decoded outputs back per-sample inside a
+half — i.e. running out[n] = r[n] + (c₁·out[n−1] + c₂·out[n−2]) / 2048
+with rounding at every step. The matrix form convolves the scaled residuals
+and rounds once per output sample; cascading the rounding decodes audibly noisy audio.
+
+
+
Validation for this exact algorithm against this exact image: instrument 0 of bank
+156 decodes to 3,328 samples (equal to its loop.end), peak amplitude 19,213,
+nothing out of s16 range, and a smooth vowel envelope.
+
+
7. The voice sequences, disassembled
+
+
Sequences 243/244/245 are tiny programs in the JAudio-NES (ported Zelda 64) sequence
+bytecode. They are structurally identical; byte-for-byte they differ only in one
+handshake constant and in their note/timing parameters. Sequence 243 annotated:
+
+
; ---- group (master) script @ 0x00 ----
+0x00 db 7f master volume 127
+0x02 dd 78 tempo 120 BPM
+0x04 d7 00 03 enable subtracks 0 and 1
+0x07 90 00 13 start subtrack 0 at 0x13
+0x0A 91 00 13 start subtrack 1 at 0x13 ; same program, separate state
+0x0D fd fd 00 delay 0x7D00 ticks
+0x10 fb 00 0d branch → 0x0D ; idle forever
+
+; ---- subtrack (channel) program @ 0x13, shared by both ----
+0x13 e9 88 SET_PRIORITY 0x88
+0x15 c4 LARGE_NOTE_ON
+0x16 dc 7f SET_PAN_WEIGHT 0x7F
+0x18 ec RESET_VIBRATO
+loop:
+0x19 fe delay 1 tick
+0x1A cc 01 MACRO_SET: value ← 1 ; 2 in seq 244, 3 in seq 245
+0x1C 71 PORT1_WRITE_MACRO_REG ; port[1] ← value (see below)
+0x1D 60 MACRO_READ_PORT0 ; value ← port[0]; port[0] ← -1
+0x1E f2 f9 branch → loop if value < 0 ; port empty
+0x20 c2 00 26 SET_DYNTBL 0x0026
+0x23 e4 DYNTBL_CALL ; call dyntbl[value] (skips -1)
+0x24 f4 f3 branch → loop
+
+; ---- dyntbl @ 0x26: 128 big-endian u16 script addresses ----
+; entry v is the routine for phoneme code v
+
+; ---- per-phoneme routine (codes 0x01–0x52, 10 bytes each) ----
+c1 <inst> VOICE_SET: instrument = code − 1
+88 <addr16> NOTE0_SET_PC: start note layer at addr
+ff return to the spin loop
+; layer:
+<note> <delay> <velocity> ff ; e.g. 6c 0d 6e ff
+
+
Semantics that matter for a reimplementation:
+
+
+
The port-1 handshake. Every iteration the subtrack publishes its
+ identity (1/2/3) to its port 1. This is what
+ Nap_ReadSubPort(VOICE_GROUP, 0, 1) == sou_now_voice_seq polls — the
+ load-race gate on the gating page. It is the
+ only byte that differs between the three sequences' programs.
+
Port reads are destructive.MACRO_READ_PORT0 returns
+ the value and resets the port to −1, and both the branch (0xF2 branches
+ on negative) and DYNTBL_CALL (skips −1) treat −1 as "empty". So each
+ Sou_VoiceStart write triggers exactly one routine, at most one per
+ tick per subtrack.
+
Note command decode (large-note mode, opcode < 0xC0):
+ commands 0x40–0x7F are note-on with
+ semitone = opcode − 0x40, then a varint delay and a velocity byte,
+ gate 0. Volume contribution is (velocity / 127)².
+ internal/track.c:1028 __SetNote
Ticks. One sequence tick ≈ 9.9 ms: ticks fire at
+ 240 updates/s × tempo / max_tempo with
+ tempo = 120 × 48 = 5760 and max_tempo = 13726
+ (from 60000 × 48 × 4 / 48 / 16.713 / 1.04613,
+ internal/memory.c:998) — ≈ 100.7 ticks/s. When the layer's delay
+ expires the note releases through adsr_decay_table[250].
+
+
+
The complete per-code parameter map
+
+
Extracted from the shipped sequences. "Semitone" is relative to unity at 48, so 44
+plays a sample at 2^(−4/12) ≈ 0.794× recorded speed, 41 at 0.667×, 39 at 0.595× —
+each bank's recordings are tuned differently, which is part of why the three voices
+sound distinct.
+
+
+
Seq
Codes
Instrument
Semitone
Delay (ticks)
Velocity
Extras
+
243
+
0x01–0x38
code − 1
44
13
110
+
0x39–0x52
code − 1
44
15
110
+
0x53–0x76
code − 1
44
28
95
pitch sweep from semitone 41 (portamento c7 81 29 ff)
+
0x77
0x76
44
20
110
+
0x78–0x7F
silence (routine is an immediate ff)
+
244
+
0x01–0x38
code − 1
41
16
110
+
0x39–0x52
code − 1
39
20
110
+
0x77
0x52
41
20
110
the you substitute — see below
+
0x53–0x76, 0x78–0x7F
silence
+
245
+
0x01–0x38
code − 1
39
17
110
+
0x39–0x52, 0x77
code − 1
39
20
110
+
0x53–0x76
code − 1
44
28
95
pitch sweep from semitone 41
+
0x78–0x7F
silence
+
+
+
Two things this table reveals that the C code cannot:
+
+
+
The editor letter names have a rising intonation. Codes
+ 0x53–0x76 play with portamento mode 1 sweeping from semitone 41 up to
+ 44, longer (28 ticks) and quieter (velocity 95) than dialogue phonemes — each
+ keystroke's letter name lilts upward.
+
Spec 3's you is deliberate, not clamped. Sequence
+ 244 maps code 0x77 to instrument 0x52 — bank 157's 83rd
+ and last instrument, which sits above the phonology range
+ (0x01–0x52) and is reachable by nothing else. Bank 157 carries one
+ extra instrument specifically to voice you.
+
+
+
8. Reassembling audible animalese
+
+
For one phoneme code v spoken by spec s:
+
+
seq, bank = spec table (page 1) ; 243/156, 244/157, 245/158
+inst, semitone, delay, velocity = per-code map above (dyntbl[v])
+pcm = vadpcm_decode(bank.instrument[inst].normal.sample)
+rate = 32000 × tuning × 2^((semitone − 48)/12)
+ × spec_pitch × emotion_pitch × punctuation_pitch ; pages 1 and 5
+gain = (velocity/127)² × master(127/127) × spec_vol × emotion_vol
+env = near-instant attack to 32700/32768, hold,
+ release after `delay` ticks via adsr_decay_table[250]
+pan = centre, no reverb, no distance attenuation
+
+
Practically: decoding the sample and playing it at
+22050 × 2^((semitone − 48 + 12·log₂(pitch_mults))/12) Hz with a
+(velocity/127)² gain gets within a hair of the real thing; the envelope
+release only matters when a sample outlasts its note delay. Timing of successive
+syllables comes from the scheduler, not from the audio
+engine — new syllables do not cut old ones off, they land on the alternating other
+subtrack and overlap.
+
+
+Scope. These offsets are for the USA rev 0 image (GAFE01,
+the one ac-decomp matches). The FST walk, DOL walk and all structure layouts are
+revision-independent; the DOL virtual addresses and disc offsets are not.
+
+
+
+NKit-shrunk images work unmodified. An NKit-shrunk rip (marker
+"NKIT v01" at disc offset 0x200; original size as a big-endian
+u32 at 0x210) strips the ~1.4 GB of junk padding after the
+real data without relocating it — and Animal Crossing's 11 files all end by
+0xE11470, so every offset on this page still reads valid data (verified
+against such an image). Two rules keep a loader safe on any variant: never derive
+anything from total file size, and never read past the extents the FST declares.
+
the phonology engine — short/long vowels, consonants, all 52 two-character digraphs plus you
0x01–0x40
also the anti-repetition random substitution (rand() >> 2)
-
0x53–0x76
82–117
only mode 1 — one entry per digit and letter, in order
+
0x53–0x76
82–117
only mode 1 — one entry per digit and letter, in order (on bank 157, instrument 82 is instead the you substitute, see below)
0x77
118
the you digraph, and nothing else
@@ -92,24 +92,31 @@ phonology-only bank — and that is exactly what it is.
Why bank 157 is short
Spec 3 (Wendell, Pete, Booker, Jack) uses sequence 244 / bank 157, which has 83
-instruments — codes 0x01–0x53. That covers the entire phonology
-output range (≤ 0x52) with one to spare, and omits the letter-name block
-that spec 3 can never reach, since the editor is always spec 5 or 6.
-
-
The one real casualty is you (0x77): the sequence's dynamic
-table clamps out-of-range entries to the last routine, so a spec-3 voice saying "you"
-plays instrument 82 instead. That is one wrong syllable on a common word, in four
-NPCs' voices.
+instruments — one more than the phonology output range
+(0x01–0x52 → instruments 0–81) — and omits the letter-name
+block that spec 3 can never reach, since the editor is always spec 5 or 6.
+
+
The 83rd instrument is not spare. Sequence 244's dynamic table maps codes
+0x53–0x76 to explicit silence and maps you
+(0x77) to instrument 82 — a sample reachable through no
+other path, played at a lower note than the other two voices'
+you (semitone 41 vs 44; see the disc page). So bank
+157 carries a dedicated you substitute, and spec-3 voices lack nothing
+audible. Whether instrument 82 is an actual "you" recording is testable the same way as
+the letter names: extract it and listen.
-This corrects an earlier reading of extraction.md §2.4, which described bank 157
-as "collapsing every digraph sample above 0x53 onto one sound". Nothing above
-0x53 is a digraph — the block is not phonology at all.
+This corrects two earlier readings. Working notes once described bank 157 as
+"collapsing every digraph sample above 0x53 onto one sound" — nothing above
+0x53 is a digraph, the block is not phonology at all. A later revision of
+these pages then claimed the dynamic table "clamps out-of-range entries to the last
+routine" — it does not; the mapping of 0x77 → instrument 82 is an explicit
+table entry, verified on disc.