# Sound bank audio — `sound.pak` / `.slb` / XMA1

Reverse-engineered 2026-07-19 (static). `dat/sound.pak` (+ `sound.p00..p04`,
~1.08 GB) is the game's entire audio bank: **9519** IPFB entries, each an XACT
`.slb` sound bank wrapping **XMA1** audio.

## Names

Entry keys are `name_hash(<string>)` of filenames stored verbatim in the IDXD
string pools of `tables.pak` → `eng\sounds.tbl` (entry 39) and `jpn\sounds.tbl`
(entry 45). All 9519 recovered. Families:

- `<lang>\Voice\VOICE_<SPK>_<NNN>.slb` — per-line character voice (SPK = ADAN,
  TCAF, RHIN, ADPL, BIRD, ACRO, ZZZZ…). Only `eng`/`jpn` voice exists.
- `<lang>\etc\VOICE_{A,B,C,D}_<NNN>.slb`, `<lang>\Briefing\BR<n>_<m>.slb`.
- **`<lang>\Movie\VOICE_<movie>.slb`** — the continuous voice track for cutscene
  `<movie>.wmv` (e.g. `eng\Movie\VOICE_RT07A.slb`). Played from the video start.
- `BGM_###.slb` (32) — music. `Static.slb`, `JNGL_###` — SFX/ambience.

Only English and Japanese have voice; subtitles cover more languages, voice does
not. See [`crate::slb::movie_voice_name`].

## Codec

XMA1: RIFF `fmt ` tag **0x0165**, a 32-byte `XMAWAVEFORMAT`, **48000 Hz**. The
content is **mono** — some clips carry it in the **left channel only** (right is
silence), others are **dual-mono** (L = R). Downmix to mono by taking the left
channel (it always holds the full signal).

`XMAWAVEFORMAT` (one stream, 32-byte `fmt `): SampleRate @ fmt+16 (u32 LE),
Channels @ fmt+29 (u8), ChannelMask @ fmt+30 (u16). Movie voices report 2
channels / mask 0x2 despite the mono content.

## `.slb` layouts → decodable RIFF

Two on-disc layouts (`sound.pak` movie voices split ≈ 63 RIFF / 15 headerless):

- **RIFF present** — a `RIFF/WAVE` sits inside the bank; its 32-byte `fmt ` is
  the real header. The XMA packets are **everything after that RIFF's `data`
  chunk header** (`slb[data_pos+8..]`); the declared `data` size is unreliable
  (sometimes half the real length), so take to end and clamp to the media length
  downstream. Some banks have trailing bank data past the real audio — always
  beyond the video length, so clamping to the video duration drops it.
- **Headerless** — no `RIFF` anywhere; a fixed **1392-byte** header precedes raw
  XMA1 packets (48 kHz, 2 channels). Data = `slb[1392..]`.

[`crate::slb::to_xma_riff`] rebuilds a standalone XMA1 `RIFF/WAVE` for either
layout, ready for FFmpeg's `xma1` decoder.

## Decode (FFmpeg)

`ffmpeg -i rebuilt.riff -af "pan=mono|c0=c0" -t <media_len> out.wav` — the RIFF
is fed to the `xma1` decoder, downmixed to the left channel, clamped to length.
Verified: `VOICE_S00A` → 93.9 s == `S00A.wmv` 93.87 s; `VOICE_ADV` → 137.3 s ==
137.7 s; `VOICE_RT07A` → 49 s ≈ 50 s.

## Reading one entry cheaply

`sound.pak` is ~1 GB. Use [`crate::PakArchive::parse_toc`] on the small `.pak`
index, binary-search the name-hash, then read just `[offset, offset+size)` from
the `.pNN` segments (the viewer's `read_segment_range` seeks into the right
segment) instead of loading the whole archive.
