From 9ca1a1c1c7bf56e39b516c299b4e0d3efe59652b Mon Sep 17 00:00:00 2001 From: Alexander Kassouni Date: Tue, 8 Sep 2026 20:11:06 -0700 Subject: better docs --- docs/disc.html | 413 +++++++++++++++++++++++++++++++++++++++++++++++++++ docs/editor.html | 33 ++-- docs/encoding.html | 3 + docs/index.html | 30 ++-- docs/playback.html | 51 ++++--- docs/quirks.html | 29 ++-- docs/reference.html | 74 ++++++++- docs/scheduling.html | 4 +- docs/soundfont.html | 20 +-- 9 files changed, 586 insertions(+), 71 deletions(-) create mode 100644 docs/disc.html (limited to 'docs') diff --git a/docs/disc.html b/docs/disc.html new file mode 100644 index 0000000..5f4a4ae --- /dev/null +++ b/docs/disc.html @@ -0,0 +1,413 @@ + + + + + +8. Walking the disc image — Animalese + + + + + + +

8. Walking the disc image

+

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.

+ +

The chain, outermost to innermost:

+ +
CISO container (optional)
+  └ GameCube disc
+      ├ main DOL  ──►  the five audio tables (compiled-in .data)
+      └ /audiorom.img  ──►  Audioseq · Audiobank · Audiowave regions
+            ├ sequences 243/244/245   (dyntbl: phoneme code → instrument + note)
+            ├ banks 156/157/158       (instrument → sample descriptor)
+            └ wave bank 5             (raw VADPCM frames)
+ +

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:

+ + + + + + +
OffsetSizeField
0x00004magic "CISO" (43 49 53 4F)
0x00044block size, little-endian u32 (commonly 0x200000)
0x00080x7FF8block 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:

+ + + + + + + +
OffsetSizeFieldGAFE01 rev 0 value
0x00006game code — verify "GAFE01"
0x04204offset of the main DOL0x0001E000
0x04244offset of the FST0x000FE500
0x04284size of the FST0x102
+ +

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:

+ +
0x00  u32 text_offset[7]     0x48  u32 text_address[7]     0x90  u32 text_size[7]
+0x1C  u32 data_offset[11]    0x64  u32 data_address[11]    0xAC  u32 data_size[11]
+0xD8  u32 bss_address        0xDC  u32 bss_size            0xE0  u32 entry_point
+ +

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:

+ + + + + + + + + + + + + +
SectionFile offset (in DOL)Virtual addressSize
text00x0001000x800031000x02520
text10x0026200x800056C00xA40C0
data00x0A66E00x800056200x00040
data10x0A67200x800056600x00060
data20x0A67800x800A97800x00040
data30x0A67C00x800A97C00x00020
data40x0A67E00x800A97E00x06080
data50x0AC8600x800AF8600x32A20
data60x0DF2800x80217B800x00700
data70x0DF9800x80218BE00x00B40
+ +

The five tables all live in data5 (addresses from the ac-decomp symbol +map for GAFE01 rev 0; other revisions have different addresses):

+ + + + + + + + +
SymbolVirtual addressSizeEntriesDisc offset
AudioseqHeaderStart0x800CCA400xFA02490x0E7A40
AudiobankHeaderStart0x800CD9E00xA001590x0E89E0
AudiowaveHeaderStart0x800CE3E00x7060x0E93E0
AudiodataHeaderStart0x800CE4500x4030x0E9450
AudiomapHeaderStart0x800CE4900x3F0—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:

+ + + + + + +
RegionOffset in fileSizeAbsolute disc offset
Audioseq0x0000000x0CF7000x626D10
Audiobank0x0CF7000x067C800x6F6410
Audiowave0x1373800x6B33E00x75E090
+ +

Entry addresses in the sequence, bank and wave tables are offsets into their region. +The animalese entries:

+ + + + + + + + + + +
EntryRegion offsetSizeAbsolute disc offsetparam0param1
seq 2430x0ABA600x6600x6D2770
seq 2440x0AC0C00x4800x6D2DD0
seq 2450x0AC5400x6600x6D3250
bank 1560x05C2600x45000x7526700x05FF0x7700
bank 1570x0607600x30200x756B700x05FF0x5300
bank 1580x0637800x45000x759B900x05FF0x7700
wave bank 50x2C5DC00x3ED6200xA23E50
+ +

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.

+ +
bank 156 @ disc 0x752670
+├ u32 drumListOffset  = 0
+├ u32 sfxListOffset   = 0
+├ u32 instrumentOffset[119]        count from param1 >> 8
+│    [0]   = 0x3610
+│    [1]   = 0x3630                voicetables are 0x20 apart
+│    …
+│    [118] = 0x44D0
+└ instrument 0 (phoneme code 0x01, short a) @ +0x3610:
+     rel=0  range=[0,127]  adsr_decay_idx=250  envelope=0x3600
+     low  = { 0, 0.0 }                          unused
+     norm = { wavetable 0x01F0, tuning 0.689062 }
+     high = { 0, 0.0 }                          unused
+     wavetable @ +0x1F0:
+        codec=0 (VADPCM)  medium=0  size=0x750
+        sample = 0x341130   → disc 0xA23E50 + 0x341130 = 0xD64F80
+        loop   = 0x250      → { start=0, end=3328, count=0, sample_end=0 }
+        book   = 0x200      → { order=2, n_predictors=2, s16 codebook[32] }
+     envelope @ +0x3600:  (1, 0) (1, 32700) (-1, 0) (0, 0)
+ +

Facts that hold across every instrument in all three voice banks, verified by +exhaustive scan:

+ + + +

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

+ + + + + + + + + + + + + + + + + + + +
SeqCodesInstrumentSemitoneDelay (ticks)VelocityExtras
2430x01–0x38code − 14413110
0x39–0x52code − 14415110
0x53–0x76code − 1442895pitch sweep from semitone 41 (portamento c7 81 29 ff)
0x770x764420110
0x78–0x7Fsilence (routine is an immediate ff)
2440x01–0x38code − 14116110
0x39–0x52code − 13920110
0x770x524120110the you substitute — see below
0x53–0x76, 0x78–0x7Fsilence
2450x01–0x38code − 13917110
0x39–0x52, 0x77code − 13920110
0x53–0x76code − 1442895pitch sweep from semitone 41
0x78–0x7Fsilence
+ +

Two things this table reveals that the C code cannot:

+ + + +

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. +
+ +
+ ← 7. The editor path + 9. Quirks & corrections → +
+ + + diff --git a/docs/editor.html b/docs/editor.html index 8e46242..22349d5 100644 --- a/docs/editor.html +++ b/docs/editor.html @@ -72,7 +72,7 @@ typing '0' → Sou_VoiceStart(subtrack, 0x53) → instrument 0x52 Code rangeInstrumentsReached by 0x01–0x520–81the phonology engine — short/long vowels, consonants, all 52 two-character digraphs plus you 0x01–0x40also the anti-repetition random substitution (rand() >> 2) -0x53–0x7682–117only mode 1 — one entry per digit and letter, in order +0x53–0x7682–117only mode 1 — one entry per digit and letter, in order (on bank 157, instrument 82 is instead the you substitute, see below) 0x77118the 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.
← 6. The soundfont lookup - 8. Quirks & corrections → + 8. Walking the disc image →
diff --git a/docs/encoding.html b/docs/encoding.html index 8b57261..bc5bcc7 100644 --- a/docs/encoding.html +++ b/docs/encoding.html @@ -42,6 +42,9 @@ diverges everywhere else. Notable non-ASCII placements:

return -1; // no sound, and no state change return voice_array[code]; +

The full 256-byte array is reproduced verbatim in the +reference tables; this page describes its shape.

+

Those are the only two entries in the array holding 0xFF, so 0xFF is never actually returned from this function — it arrives at Na_VoiceSe only as a padding value for the unused b/c diff --git a/docs/index.html b/docs/index.html index 7017da5..86f2f9f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -37,7 +37,7 @@ playback │ 6. Sou_VoiceStart() write port 0 of a JAudio2 subtrack │ sequence dyntbl ──► instrument (code − 1) ▼ 7. ProgToVp(bank, inst) ──► NoteToVoice() ──► smzwavetable -DSP ──► one ~190 ms VADPCM sample, 22050 Hz +DSP ──► one 30–400 ms VADPCM sample, 22050 Hz, pitched down 4–9 semitones

A phoneme code is literally a soundfont instrument number plus one. The whole @@ -75,27 +75,33 @@ entirely from the samples.

Mode 1 — an undocumented third calling convention that bypasses phonology entirely, and what it says about samples 0x53–0x76. -
  • 8. Quirks, dead code & corrections +
  • 8. Walking the disc image + CISO, the GameCube filesystem, the DOL, the bank blobs, the VADPCM + decoder, and the voice sequences disassembled — every offset needed to + extract and replay the samples from a dump.
  • + +
  • 9. Quirks, dead code & corrections The dead volume store, the tautological dedup, vestigial mouth-shape tracking, and corrections to earlier notes.
  • -
  • 9. Reference tables - Everything in one place: voice codes, digraphs, phoneme tables, - control codes, source line index.
  • +
  • 10. Reference tables + Everything in one place: the full voice table, digraphs, phoneme + tables, control codes, disc offsets, source line index.
  • Scope

    -

    These pages document the runtime pipeline as decompiled. Extracting the -actual audio samples from a disc image is covered separately in -../extraction.md, which also carries the audio table-of-contents, -bank/sample formats, and VADPCM decoding notes.

    +

    Pages 1–7 document the runtime pipeline as decompiled. Page 8 documents the +data: how to walk a dumped disc image down to individual VADPCM frames, with +the audio table-of-contents, bank/sample formats, sequence disassembly and decoding +notes. Together they are intended to be a complete reference for a reimplementation.

    No game assets here. Everything on these pages is derived from decompiled -source. Reproducing audible animalese needs a legally dumped copy of the game. -
    +source and structural analysis. Reproducing audible animalese needs a legally dumped +copy of the game. -

    Verified against ac-decomp at commit 09ca8e8b (DOL and REL 100 % linked).

    +

    Verified against ac-decomp at commit 09ca8e8b (DOL and REL +100 % linked) and against a dumped GAFE01 USA rev 0 disc image.

    diff --git a/docs/playback.html b/docs/playback.html index 73be9a2..4addb30 100644 --- a/docs/playback.html +++ b/docs/playback.html @@ -118,16 +118,25 @@ r29 != 0x84).

    Each voice sequence's subtrack sits in a spin loop:

    -
    L_0019: delay 1 frame
    -        macro_read_port0
    -        branch_rel_not_eq_zero L_0019   ; wait until Sou_VoiceStart writes the port
    -        set_dyntbl D_0026
    -        dyntbl_call                     ; index the table with the phoneme code
    -        branch L_0019
    - -

    The dynamic table has 128 entries pointing at 10-byte routines -C1 <instrument> / 88 <note> / FF. Entry v selects instrument -v − 1. Entry 0 is an immediate FF — nothing.

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

    The dynamic table has 128 entries pointing at routines of the shape +C1 <instrument> / 88 <layer addr> / FF, where the note layer holds the +actual note, delay and velocity. Entry v selects instrument +v − 1; entry 0 is an immediate FF — nothing. The +full annotated disassembly, the per-code note/delay/velocity map, and the tick math are +on the disc page. 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 Sou_VoiceStart write +triggers exactly one syllable.

    Per-frame parameter push

    @@ -177,15 +186,19 @@ deferred syllable's parameters land one frame late.

    bank id (5 for all three), and all have zero drums and zero SFX. 119 instruments = phoneme codes 0x01–0x77 exactly.

    -

    Bank 157 stopping at 83 is not an omission: instruments 0–82 cover codes -0x01–0x53, and the phonology engine never produces a code above -0x52 apart from 0x77. The missing range -0x53–0x76 is the editor's letter-name block, -which spec 3 can never reach. The only thing spec-3 voices genuinely lack is -you.

    - -

    Sample details are in ../extraction.md: N64 VADPCM, tuning 0.6891 -(22050 Hz), order 2, two predictors, no loop, ~190 ms each.

    +

    Bank 157 stopping at 83 is not an omission, and nothing is lost. The phonology +engine's maximum output is 0x52 plus 0x77; instruments 0–81 +cover 0x01–0x52, and the missing +0x53–0x76 range is the +editor's letter-name block, which spec 3 can never reach. +For you (0x77), sequence 244's dynamic table explicitly routes +the code to instrument 82 — 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 you.

    + +

    Sample details are on the disc page: 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).

    ← 4. The phonology engine diff --git a/docs/quirks.html b/docs/quirks.html index c46fdb2..b9b1246 100644 --- a/docs/quirks.html +++ b/docs/quirks.html @@ -3,14 +3,14 @@ -8. Quirks, dead code & corrections — Animalese +9. Quirks, dead code & corrections — Animalese - + -

    8. Quirks, dead code & corrections

    +

    9. Quirks, dead code & corrections

    Things a faithful port has to reproduce, things a clean reimplementation should drop, and where earlier notes were wrong.

    @@ -102,7 +102,9 @@ every neighbour is 0–5.

    Corrections to earlier notes

    -

    The following supersede statements in ../extraction.md as originally written.

    +

    The following supersede statements from earlier working notes +(extraction.md, since retired — its surviving content lives on +the disc page).

    @@ -110,9 +112,10 @@ every neighbour is 0–5.

    +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. @@ -144,13 +147,15 @@ the config. See gating.the editor path.
    WasActually
    "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 exactly; the -missing block is the editor's letter names, unreachable at spec 3. -The only real loss is you.
    "43 of the 119 codes are unreachable through normal text … only ever hit by the anti-repetition random substitution."
    -

    Everything else in extraction.md §1 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.

    +

    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).

    diff --git a/docs/reference.html b/docs/reference.html index 5b86ebb..1abf660 100644 --- a/docs/reference.html +++ b/docs/reference.html @@ -3,17 +3,42 @@ -9. Reference tables — Animalese +10. Reference tables — Animalese - + -

    9. Reference tables

    +

    10. Reference tables

    Everything needed to reimplement the pipeline, plus an index into the decomp.

    +

    The character → voice code table

    + +

    mMsg_sound_voice_get's full 256-byte array, indexed by the game character +code (row × 16 + column). 0x7F and 0x80 hold 0xFF +but the function returns −1 for them before the lookup.

    + +
          x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF
    +0x00  85 85 5D 5D 5D 5D 5D 5D 5F 61 61 61 61 65 65 65
    +0x10  65 60 6A 6B 6B 6B 6B 6B 85 71 71 71 71 5E 6C 5D
    +0x20  81 83 85 5D 5D 85 85 85 85 85 85 85 85 85 85 80
    +0x30  53 54 55 56 57 58 59 5A 5B 5C 85 85 85 85 85 82
    +0x40  85 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B
    +0x50  6C 6D 6E 6F 70 71 72 73 74 75 76 5D 85 5D 5D 85
    +0x60  5F 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B
    +0x70  6C 6D 6E 6F 70 71 72 73 74 75 76 61 61 61 61 FF
    +0x80  FF 65 65 65 65 86 60 6A 6B 6B 6B 6B 6B 85 71 71
    +0x90  85 71 71 75 75 6C 75 85 85 5D 6B 85 85 85 85 85
    +0xA0  85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85
    +0xB0  85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85
    +0xC0  85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85
    +0xD0  85 85 81 81 85 85 85 85 85 85 85 85 85 85 85 85
    +0xE0  85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85
    +0xF0  85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 81
    +

    Source: src/game/m_msg_sound.c_inc:2, verified verbatim.

    +

    Voice codes

    @@ -118,6 +143,32 @@ nothing at all.

    92431560.700.65Farley
    +

    Per-code playback parameters

    + +

    What each phoneme code actually plays — instrument, semitone, note delay, velocity, +per sequence — is tabulated on the disc page, extracted from the +shipped sequence data. The headline constants: dialogue phonemes play at semitone 44 +(seq 243), 41/39 (seq 244) or 39 (seq 245) against unity-at-48 +(PITCHTABLE[i] = 2^((i−48)/12)), velocity 110 (gain +(110/127)² ≈ 0.75); editor letter names at semitone 44 with a rising sweep +from 41, velocity 95; all samples tuned 0.689062 = 22050/32000.

    + +

    Disc image quick reference (GAFE01 USA rev 0)

    + + + + + + + + + + +
    ThingWhere
    main DOLdisc 0x1E000 (from header 0x420)
    FSTdisc 0xFE500, size 0x102 (from header 0x424/0x428)
    /audiorom.imgdisc 0x626D10, size 0x7EA760
    the five audio tablesDOL .data5; VAs 0x800CCA40 (seq), 0x800CD9E0 (bank), 0x800CE3E0 (wave), 0x800CE450 (data), 0x800CE490 (map)
    seq 243 / 244 / 245disc 0x6D2770 / 0x6D2DD0 / 0x6D3250, sizes 0x660 / 0x480 / 0x660
    bank 156 / 157 / 158disc 0x752670 / 0x756B70 / 0x759B90
    wave bank 5disc 0xA23E50, size 0x3ED620; smzwavetable.sample is relative to this
    + +

    Full derivations, structure layouts and the VADPCM decoder are on +the disc page.

    +

    Text control codes that affect voice

    @@ -208,8 +259,23 @@ nothing at all.

    ​:1399SUBTRACK_CMD_SET_INSTRUMENT_BANK — reverse bank-list index
    +

    Sequence playback internals

    + + + + + + + + + + + + +
    include/jaudio_NES/audiocommon.h:405common / group / subtrack / note command opcodes
    internal/track.c:543Nas_NoteSeq — note layer interpreter
    ​:1028__SetNote — note byte → semitone/delay/velocity
    ​:780__SetVoice — frequency_scale = PITCHTABLE[semitone] × tuning, portamento
    ​:1746MACRO_READ_PORT0 — destructive port read (resets to −1)
    ​:1877GRP_CMD_SET_TEMPO — tempo = byte × 48 tatums
    internal/audiotable.c:321PITCHTABLE — 2^((i−48)/12), unity at 48
    internal/memory.c:998max_tempo derivation (tick rate)
    internal/audioconst.c:32AGC — timeBase = 48
    internal/dummyrom.c:68Jac_InitARAM — opens /audiorom.img
    internal/neosthread.c:56DVDT_LoadtoARAM("/audiorom.img", …)
    + diff --git a/docs/scheduling.html b/docs/scheduling.html index 07f1e52..844b5f8 100644 --- a/docs/scheduling.html +++ b/docs/scheduling.html @@ -32,7 +32,7 @@ runs once per game frame while a message is scrolling. Its loop:

    The flag toggle is the speed control: the index moves on alternate frames, so text -advances at one character per two frames, roughly 33 characters/second +advances at one character per two frames — 30 characters/second at 60 fps.

    Holding B sets mMsg_STATUS_FLAG_FAST_TEXT, which zeroes @@ -138,7 +138,7 @@ frame 4 char C entry: a = C

    So: one Na_VoiceSe call per two characters, one pair every four frames (~66 ms), with the deferred second syllable one game frame (~17 ms) behind the first. -Samples run about 190 ms, so syllables overlap heavily — which is why there are +Samples average about 190 ms, so syllables overlap heavily — which is why there are two alternating subtracks.

    diff --git a/docs/soundfont.html b/docs/soundfont.html index c07caf9..436071d 100644 --- a/docs/soundfont.html +++ b/docs/soundfont.html @@ -178,11 +178,13 @@ NoteToVoice(vtbl, note) channel.c:354 wtstr { smzwavetable*, tuning } → sample, loop, book, codec, size

    ProgToVp is the only consumer of the instrument number, and it is a plain -array index bounded by num_instruments. This is exactly why -you (instrument 118) cannot work on bank 157's 83 instruments: the bounds -check fails, audio_error_flags is set to -0x03000000 + (prog << 8) + inst, and nothing plays — though in practice the -sequence's dynamic table has already clamped the index before it gets there.

    +array index bounded by num_instruments: on an out-of-range instrument the +bounds check fails, audio_error_flags is set to +0x03000000 + (prog << 8) + inst, and nothing plays. In retail data the +guard never trips — each sequence's dynamic table only names instruments its own bank +has. In particular you on bank 157 is not an error case: sequence 244 maps +code 0x77 to instrument 82, the last instrument of the bank +(see the disc page).

    Every voice instrument uses only the normal slot, so NoteToVoice is a formality here.

    @@ -205,7 +207,7 @@ formality here.

    119 + 83 + 119 instrument slots — point into that one 4 MB wave bank.

    -Field naming. ../extraction.md §2.2 guessed +Field naming. Earlier working notes guessed u8 loaded, rangeLo, rangeHi, releaseRate for the instrument header. The decomp calls them is_relocated, normal_range_low, normal_range_high, adsr_decay_idx. Same layout, and @@ -213,9 +215,9 @@ calls them is_relocated, normal_range_low, the file.
    -

    Decoding the VADPCM frames themselves is covered in ../extraction.md §2.3 — -including the one easy mistake, which is convolving the decoded outputs instead of the -scaled residuals.

    +

    Decoding the VADPCM frames themselves is covered on +the disc page — including the one easy mistake, which is +convolving the decoded outputs instead of the scaled residuals.

    ← 5. Playback -- cgit v1.3.1