Sound¶
Verified against: SLUS_212.15 (NTSC-U, SHA1 e9cb2cc49aa046b9e494313dce2f5038ed17b2f4): Ghidra, the disc's data
read with coney-tools, and PCSX2 2.9.94 over PINE (2026-10-06, the user's quick-save slot 1 copied to a file: level99
checkpoint 3, the audio manager's state read while the game ran).
Purpose¶
How the game plays its sound effects, speech, ambience and music: the audio manager, its sound tasks and their
voices, priorities, 3D attenuation and panning, volumes, banks, the music player and the speech lines. The data
formats (the sound list, sound classes, banks, BFW.SND, MUSIC.SND and the ADPCM codec) are on
Audio data. The names are listed in Sound and music and
Speech commands and voices; the script bindings in
Sound bindings. Movies (PSS/*.BIK) play their own Bink audio.
In one paragraph: every sound is a task (up to 256) started by name hash. The sound list gives its sample and
its class; the class gives its priority, distances and flags. A task gets one of 48 SPU2 voices: 13-47 for samples of
the bank loaded into sound RAM, the stream channels 5-9 (10-12 for a few loops) for sounds streamed from
BFW.SND; when none is free it steals a less important task's voice or plays virtually (tracked, silent, with
its length timed). Each update, a 3D task's left and right volumes are worked out from its distance to the listener
(full within near, silent beyond far, quadratic between) and from a two-ear pan, times the record's volume, the
options' sound volume and fades. Music streams from MUSIC.SND on two stereo stream pairs, switching tracks on
bar boundaries with cross-fades, either as scripted tracks or as the system music that follows the mood of
the fight.
Original structure¶
Audio/ (0x0010edd0-0x001167b8, Source map): MusicList.cpp, SoundList.cpp,
SoundListener.cpp (the tasks: FreeList<SoundTask> at 0x00547128), SoundMatrix.cpp (the voice table, the
allocation tag SoundVoice and folder vags/character/voices/ at 0x00548188-0x005481c8); the music player sits
just before (0x0010d758-0x0010e7d0, inferred; Source map).
Device/ps2/sound/msaudiodevice.cpp (0x0014ba18-0x0014d508) is the platform device over SCEE's MultiStream library
(0x0014d760-0x00151ed0; The PS2 sound device, Audio data).
Names are ours; every other function of the unit is in the Function index.
| Address | Name | Role | Evidence |
|---|---|---|---|
0x0050aa84 |
g_AudioManager |
pointer to the audio manager (0x00598aa0 at runtime) |
confirmed (code), confirmed (runtime) |
0x0050bce4 |
g_AudioDevice |
the MultiStream device, vtable 0x00537f88 |
confirmed (code) |
0x00143f68 |
Crc32_Hash(table, name) |
CRC-32 of a name as written (no case folding), table at 0x005d91e0 |
confirmed (code) |
0x0010f618 |
AudioManager_Reset |
defaults (below); bank none |
confirmed (code) |
0x0010f810 |
AudioManager_Update(listeners) |
the tasks, the ambient emitters, the music, the device | confirmed (code) |
0x00111de8 |
AudioManager_Play(vol, pitch, a8, mgr, owner, hash, pos, fade, p9, p10, duckable) |
start a task | confirmed (code) |
0x001120c8 |
AudioManager_NewTask(hash, pos) |
admission, task, voice | confirmed (code) |
0x00112560 / 0x00112700 |
Task_GetVoice / Task_FindVictim |
voice allocation and stealing | confirmed (code) |
0x0011a170 |
SoundTask_Update(task, listeners) |
fades, 3D volume and pan, pitch | confirmed (code) |
0x00112b10 |
Tasks_Update |
per update: run each task, free the finished | confirmed (code) |
0x0010fcd8 / 0x0010fc70 |
PlaySound2DByHash / PlaySound2DByName(flags...) |
a sound without position (SoundPlay2D, the interface cues) |
confirmed (code) |
0x0010fdd0 / 0x0010fd48 |
PlaySound3DByHash / PlaySound3DByName(pos...) |
a sound at a position (SoundPlay) |
confirmed (code) |
0x0010fa50 |
AudioManager_LoadBank(name, partial) |
loads a bank unless it is the current one | confirmed (code) |
0x00111178 / 0x00111428 |
AudioManager_StartLoadScreen / _StopLoadScreen |
the load-screen bank and its two sounds (Banks) | confirmed (code) |
0x00110b60 / 0x00110c70 |
AmbientTrack_Play / _Stop |
the level's ambience bed | confirmed (code) |
0x0010d8e8 |
Music_Play(hash, loop, callback, fadeBars) |
queue a music track | confirmed (code) |
0x0010dfe0 |
MusicChannel_Update |
the music state machine | confirmed (code) |
0x0010e7d0 |
SystemMusic_Update |
mood-driven track choice | confirmed (code) |
0x0010e558 |
Music_UpdateVolumes |
music volume, ducking | confirmed (code) |
0x001164a8 |
VoiceTable_Build(n) |
allocates n voice sets and counts each one's lines per speech command |
confirmed (code) |
0x00114b20 |
VoiceTable_NextLine(set, command) |
the next line's sound for a voice set and command, or none | confirmed (code) |
0x00114c98 |
VoiceTable_IsBlocked |
a fixed list of lines not played in certain levels | confirmed (code) |
0x002205e0 |
Human_SayCommand |
a human says a speech command (the work of SoundPlayCommand) |
confirmed (code) |
0x0021e400 / 0x0021e698 |
Human_PlaySpeech / Human_PlaySpeechCutting |
plays a line at the human with an end callback (the second also cuts a line off) | confirmed (code) |
0x0041cc40 |
WarChief_SayCommand |
the line a war chief says for a Warrior command (AI) | confirmed (code) |
Data¶
The audio manager¶
Fields used here (offsets from *(0x0050aa84)); defaults from 0x0010f618 and the options' "restore defaults"
(0x001d80e0). Confirmed (code); the values marked runtime were read in level99.
| Offset | Meaning |
|---|---|
+0x00, +0x04, +0x08 |
the task list: head, tail, count (tasks linked by +0x08 previous, +0x0c next) |
+0x20 |
two listeners of 32 bytes, {position, vector}, copied in each update (one per player) |
+0x64 |
the music player (below) |
+0x1d8, +0x1dc |
"a non-duckable sound is playing" flags (set by tasks with +0xa4 = 0, cleared each update; both also set for the whole update while a cinematic runs, scene state +0x410, 0x00112b10) |
+0x1e0 |
the sound matrix: material and animation sound tables, the voice table at +0x34, interface cues |
+0x24268, +0x24274 |
handles of the scene soundtrack (Scene soundtracks) and of the ambient track |
+0x2426c |
the scene soundtrack has been started (set by scene event 13, cleared by its stop) |
+0x24270 |
set: new positional sounds and 2D mono sounds play virtually (set during the load screen; stereo sounds are exempt) |
+0x3fa20 |
sound-effect volume, 0-1 (options; default 0.9, runtime 0.9) |
+0x3fa24 |
paused (SoundPauseSound); pitch updates stop |
+0x3fa28 |
the next load-screen number, 0-6 (random at start-up, 0x0010f768) |
+0x3fa34 |
low-priority sounds started this update (reset each update) |
+0x3fa38 |
the current bank's name (16 chars; runtime sound); +0x3fa48 the bank to load after loading (none) |
+0x3fa58 |
defer SndLoadBank to the end of loading (set by mode 1's enter, 0x00158334) |
+0x3fa5c |
global pitch factor (SndSetPitchMod, 1.0) |
+0x3faa8 |
pan mode: 0 two-ear pan (default; forced when there are two players), else an angle table (0x005d8600) |
+0x3faac |
duck factor for directional sounds under a non-duckable one (SndSetNIDuck, 0.2) |
+0x3fab0, +0x3fab4 |
music duck factor during scenes (0.75) and whether ducking is on (SndEnableMusicDuck, 1) |
Sound tasks¶
256 tasks of 0xf0 bytes are allocated at start-up (0x00111ac0). Confirmed (code):
| Offset | Meaning |
|---|---|
+0x04 |
pointer to the sound's record (sound list) |
+0x3c, +0x40 |
voice (SPU2 voice or stream channel, -1 none) and stream (999 for a bank sample) |
+0x44, +0x48, +0x4c |
length in ms, start time, virtual (no voice) |
+0x50, +0x54 |
a 2D sound's left and right pan gains |
+0x58, +0x5c |
the volumes last sent, left and right (0-1) |
+0x64 |
the rate last sent (Hz × the pitch factors) |
+0x68, +0x6c, +0x70 |
pitch factors: random variation, the caller's, 0.85 in the +0x268 state below |
+0x74 |
the caller's volume (× the priority-21 random factor, below) |
+0x78, +0x7c, +0x80, +0x84 |
fade level, fade mode (1 in, 2 out), fade start and length in ms |
+0x8c |
distance to the listener, m |
+0x94 |
state (1 playing, 2 faded out, 3 stopped) |
+0x9c |
started (0: prepared and primed but not started, Scene soundtracks) |
+0xa4 |
duckable (the play call's last argument; 0 also marks the manager's +0x1d8) |
+0xa8 |
a8, the play call's third volume factor |
+0xc0, +0xd0 |
position and facing (directional sounds) |
+0xe0, +0xe4 |
the play call's ninth and tenth arguments, sent to the IOP with a bank sample's start (0x0014e370); +0xe4 set also keeps a stopped real task until its voice ends (0x00112c60) |
+0xe8 |
the owner's handle (a human: its voice is cut when it speaks again) |
+0xec |
the owning human's +0x16c flag, copied at the play (0x0011b3f8): a stream started with it gets IOP stream priority 0x3f (Saying a speech command) |
Runtime check (level99 checkpoint 3): 5-6 live tasks; a streamed loop on stream 6, streamed positional sounds on streams 5, 7, 8 and 9, and one bank sample on SPU2 voice 13 with stream 999.
The music player¶
At audio manager +0x64: the track list (+0x0c count, +0x10 16-byte tracks {record, hash, 1, bar ms}), the
system music (+0x1c playing mood, +0x20 last mood, +0x25 + mood track count, +0x2c + 12 × mood + 4 × i track
hashes), the music volume +0x5c (SoundSetMusicVolume), the options' music volume +0x60 (default 0.9, runtime
0.9), and three channels of 0x4c bytes at +0x70 (the queued request is the third, +0x15c). A channel: +0x04
track index, +0x08 stream pair (1 or 3), +0x0c loop, +0x10 fade length in bars (0: at once), +0x11 state,
+0x14/+0x18/+0x1c/+0x20 fade start, play start, next bar, fade length in ms, +0x24 fade level, +0x2c the
callback name (32 chars). The states are named by strings at 0x00546da8: ST_Idle 0, ST_Queued 1, ST_PreLoading
2, ST_Playing 3, ST_FadeIn 4, ST_Blocked 5, ST_MSPMSync 6, ST_FadeOut 7. Confirmed (code) at 0x0010dfe0.
The sound list, speech commands and the voice table¶
The sound list's records are on Audio data: 25,395 sounds, every name the voice table
probes and 595 of the 597 ambient names the scripts give among them. Confirmed (code) at 0x0010f900.
Speech commands¶
A table of 207 records {u32 id, char *name} at 0x0050aaa8 (id equal to the index), read by 0x001164a8 and
0x00114b20: 0 nothing, 1 attack, 2 follow ... 206 hide_response. Confirmed (code). The names are listed in
Speech.
The voice table¶
SndAllocateCharacterVoices(n) (config_preload.lua passes 350) makes n voice sets of 207 entries of 3 bytes
(0x26d per set, pointer at sound system +0x1e0 + 0x34), confirmed (code) at 0x001164a8:
| Byte | Meaning |
|---|---|
+0 |
the next line to play (0-based) |
+1 |
how many lines the set has for the command |
+2 |
the chance in percent that the command is said, 100 at the start (SndSetCommandSoundPercent(set, command, percent), set -1 for all) |
The line count is found at start-up: for each set s and command c, the names vags/character/voices/<s>/<c>_01,
_02 ... (format at 0x005481e0) are hashed and looked up in the sound list until one is missing or 54 are
found. The set is written in decimal and the line in two digits, inferred from the sound list: it holds
voices/0/attack_01 and voices/100/attack_01, not voices/00/attack_01.
A human's voice set is CfgChar's voice (character type +0x118, Characters); the human keeps it
at +0x3b0. HuSetStateRespVoiceIndex stores a second set at +0x3b4 for its state responses (inferred).
The sound matrix¶
At audio manager +0x1e0 (SoundMatrix_Init, 0x00114220): the sounds that hits, footsteps and animations play,
filled by the matrix's <name>_preload.lua (SndLoadMatrix, default sound, kept at +0x23d64; a new name resets
the matrix first, 0x00114628). Confirmed (code):
| Offset | Meaning |
|---|---|
+0x00-+0x0c |
pool of 3,000 sound-hash words |
+0x10-+0x1c, +0x20-+0x2c |
pools of 1,200 material records and 150 animation records, 28 bytes each |
+0x30, +0x34 |
voice-set count and the voice table |
+0x38 |
the material table: 191 × 191 record pointers, [m1 × 191 + m2] (row 0x2fc bytes) |
+0x23a3c |
the animation table: 162 record pointers, one per animation sound event (SA.*) |
+0x23cc4 |
the interface cues' hashes |
+0x23d64 |
the matrix name (16 chars) |
+0x23da4, +0x23f20, +0x24070 |
the DJ's failure-line tables |
A record: byte 0 the alternatives in use (NewMaterialSlots' count, changed by SetNumberOfMaterialSlots), byte 1
the animation cursor, byte 2 the material cursor, +0x04/+0x08/+0x0c the three columns' volumes (the binding's
first three floats; the last three are not read), +0x10/+0x14/+0x18 each column's hash array (null for an
unused column). NewMaterialSound / NewAnimSound fill one alternative's columns (none stores 0).
DuplicateSoundMaterials(a, b) makes [b][a] the same record as [a][b].
A lookup (0x001146c0 materials, 0x00114800 animations) returns the cursor's alternative, its three hashes and
volumes, then moves the cursor on, wrapping at the count: the alternatives play in turn, not at random. For
materials a second material of 0 or 1 means the caller's default (5 for most callers), and an empty entry falls back
to [m1][default]. The players: Sound_PlayMaterialPair (0x00110830, 16 callers: contacts, objects, footsteps)
plays column 2 then column 1 at a position, each at its column volume, the second at a random pitch factor (× 0.2)
when the first material is 13; Sound_PlayMaterialHit plays column 1 only; Sound_PlayAnimSound (0x00110a08)
plays an animation entry's column 1. The footstep caller first remaps materials 0x12 → 0x6a, 6 → 5 and 0x79 →
0x23 (that last at half volume, 0x00110aa8).
Behaviour¶
Starting a sound¶
AudioManager_NewTask (0x001120c8), confirmed (code):
- Find the record by hash (binary search); none: nothing plays.
- Admission. With more than 100 tasks alive a sound of priority 20 or above is refused; with more than 240 any sound is; a sound of priority 21 or above is refused once 5 have started this update.
- Take a free task, link it at the tail, give it the next id.
- 3D cull. A positional sound farther than
far + 10m from the listener that does not loop plays virtually; during the load screen (+0x24270) every positional sound does (unless the flag at0x00512c14is set: onlySoundPreLoad,0x00113700, sets it) and every 2D sound that is not stereo. A stereo sound is never made virtual by the load screen. - Voice (
0x00112560): a streamed stereo sound takes a stream pair, 1+2 or 3+4, shared with the music (Stream pairs), and never fails; a streamed mono sound the first free stream channel of 5-9 (10-12 for class flag0x20), or, failing that and with priority below 12, a victim's; a bank sample the first free SPU2 voice of 13-47, or a victim's. No voice: virtual. - Length:
AudioDevice_Duration(size, rate)in ms, whole seconds only (0x0014d2d8); a virtual sound of priority 20 or more is given at most 500 ms.
AudioManager_Play (0x00111de8) then sets the caller's volume, pitch (1 for a non-duckable sound), owner,
position, facing and fade, runs one SoundTask_Update and starts the voice (0x0014caf8): a bank sample by hash with
its volumes and pitch; a stream by pointing its channel at the record's offset and size in BFW.SND (a loop flag the
inverse of class bit 0); a stereo stream as parent and child channels with the stereo
interleave and end offset. Priority-21 sounds get a random volume factor of 1 ± 0-19 % (0x00112ee0).
Pitch. Sent as the SPU2 pitch rate × 4096 / 48000 (0x00150078) of rate = record rate × variation ×
caller's pitch × +0x70 × global pitch; the variation is a random factor in 1 ± v / 100 chosen at the start, drawn,
like the priority-21 volume factor and the system music's pick, from the audio generator 0x006eb8b0.
Confirmed (code) at 0x0014caf8, 0x0011a170.
Stealing a voice¶
Task_FindVictim (0x00112700) walks the live tasks and picks one that is mono, real (not virtual), not stopping,
streams (or not) as the new sound does, has the same class flag 0x20, and has priority 5 or more (4 or more for
the last one checked); among them the least important (highest priority number), and between equals of priority 8
or more the quietest (mean of its two volumes). It is taken only when its priority number is higher than the new
sound's; it is then stopped and marked state 3. Confirmed (code); the exact tie order is the list order.
Task update: fades, 3D volume and pan¶
Each update Tasks_Update (0x00112b10) stores the listeners, runs SoundTask_Update on every real task and frees
those whose voice has finished or whose state is 2 or 3; a virtual task is freed when its length has passed (never,
if it loops). Confirmed (code). SoundTask_Update (0x0011a170), confirmed (code):
- Fade: mode 1 raises
+0x78from 0 to 1 over the fade length; mode 2 lowers it to 0 and then stops the sound. The ambient track fades in and out over 2,000 ms (0x00110b60). - Distance attenuation, per listener, with
dthe distance,nearandfarthe class's:a = 0beyondfar,a = 1withinnear, elsea = (1 − (d − near) / (far − near))². - Directional sounds (class flag
0x10, the voices): a further factor from a 200-byte table of percentages at0x0050a910, indexed by(cos θ + 1) × 100where θ is between the sound's facing and the direction to the listener; not applied within 1 m. Entries 0-100 are 100; 101-199 fall about linearly to 49 (about100 − 51 × cforc = cos θ); 200 and above give 0. So, inferred, a voice is at full volume anywhere ahead of its speaker's side line and at about half straight behind. - Pan (pan mode 0): two ear points half a metre either side of the listener (along the listener's side axis,
inferred) give distances
dLanddR; the nearer ear gets gain 1, the farther0.75 × a + 1 − |dL − dR|, clamped to 0-1. Mode 1 reads left and right gains from tables at0x005d8600and0x005d8ba4by the angle to the listener in degrees (180 + angle);PanTables_Build(0x00117450, at reset) fills their 361 entries withleft[a] = cos(45° + a / 2)andright[a] = sin(45° + a / 2), a constant-power pan. - Volume per ear:
a × ear gain × record volume % × caller's volume × sound volume (+0x3fa20) × directional × fade × a8 × state factor, then the loudest over the listeners, then × the duck factor (+0x3faac) for a duckable directional sound not owned by a player while a non-duckable one plays, × 0.25 for a duckable positional sound whose owner's flag+0x5b7differs from every listener's (covered ground), clamped to 1. Sent only when changed. - State factor: when game state
+0x268is positive (meaning not traced) a sound with task+0xa0set whose owner is not a player (human+0x1b0= -1) is scaled by 0.75, or 0.15 beyond 2 m, and its pitch by 0.85. - 2D sounds:
record volume × sound volume × fade × caller's volume × pan gains (+0x50, +0x54) × a8; a stereo bed sends left only to its left channel and right only to its right. One sound (hash0x510bb577) ignores the sound volume.
Runtime check (level99, sound volume 0.9): a streamed bed of class 105 (near 20 m, far 100 m, record volume
40 %) at 75.31 m read left 0.034, the formula's 0.0952 × 0.4 × 0.9 = 0.0343; one of class 61 (5-100 m, 40 %) at
75.41 m read right 0.024 against 0.0670 × 0.4 × 0.9 = 0.0241, its far ear 0.75 of that; a 2D loop of record volume
20 % read 0.180 on both sides.
Volumes and the options¶
The options' sound and music volumes default to 0.9 (0x0010f768, 0x001d80e0: 0x00110638 sets +0x3fa20 and
mirrors it at game state +0x57a8; 0x001105b0 sets the music player's +0x60). SoundSetSoundVolume does
nothing in this build. Volumes reach the device as int(v × 16383) & 0x7fff per side (0x0014d188). Confirmed
(code).
Reverb¶
SoundSetEffect(type, depth, delay, feedback) (AudioManager_SetEffect, 0x00113000) sets
the SPU2 effect on both cores: device slot +0xa0 sends IOP command 0x13 (core, type, depth × 32,767 on the left
and right, delay, feedback; a type of 10 or more is refused), then slot +0xa8 command 0x15, the effect's return
volume, the depth on both sides (0x00113118). SoundEnableEffects(on) keeps the flag at +0x1c4; off also sends
command 0x17 on both cores at once (0x001130c8). Mode 1's enter (0x00158334) sets the bank deferral
+0x3fa58 and calls SoundSetEffect with type 1
(bindings). Confirmed (code); that the types are the SPU2 library's
ten reverb presets (off, rooms, studios, hall, space, echo, delay, pipe) is inferred.
Pause (SoundPauseSound, 0x0010fb20 / 0x0010fb68): sets +0x3fa24 and pauses or resumes every voice through
the device (0x0014c5e0 / 0x0014c600). Confirmed (code).
Banks¶
One bank is in sound RAM at a time. AudioManager_LoadBank(name) (0x0010fa50) loads it unless it is already
current: the device waits for stream 0, streams <name>.msb into sound RAM and <name>.msd to the IOP
(banks). Confirmed (code). Who loads which:
- Front end:
menu(0x0015c4b0,0x0015d648);0x00159630loadssound. - Load screen (
0x00111178, from level loading0x0015fe90): bankload_NN(NN =+0x3fa28, then +1 mod 7), orarmloadwhen0x0041d110says so (the Armies levels, inferred), and its two soundsvags/load_screen/load_NN_l/_r(armload_l/_r) started as 2D loops (flags0x41,0x10) hard left and hard right with the load-screen flag clear; then it sets+0x24270, so later positional and mono 2D sounds play virtually.0x00111428stops both after the preload and, when the left half was real, blocks (yielding and updating the audio manager and the device) until its voice is idle (Level loading). A preload made throughSoundPreLoad(0x00113700) sets0x00512c14for its call, which spares it the virtual rule. - Level (
0x0015fe90, after loading): the pending bank+0x3fa48if a script asked for one, elsesound; then the pending name is reset tonone.SndLoadBank(name)loads at once, or, while+0x3fa58is set, only records the name as pending (0x001133d0).
Music¶
Scripts configure each track with SndCfgMusicInfo(track, bar, volume) at the legal screen: the volume overwrites
the record's, and the second number (1,548-3,780 ms; Sound and music) is the
track's bar length: the configured values are four beats at the tempo in the track names (2,034 ms for the
118 tracks, 4 × 60 / 118 = 2.034 s; 1,890 for the 127 ones). Confirmed (code) that the player times its changes
in multiples of it (0x0010dfe0); "bar" is inferred.
Music_Play(track, loop, callback, fadeBars) (0x0010d8e8; SoundPlayMusicTrack / SoundLoopMusicTrack) does
nothing while the game state forbids music (0x0041cf30) or when the track is not configured; otherwise it queues the
request, and MusicChannel_Update (0x0010dfe0) runs each channel, confirmed (code):
- Queued: take the stream pair the old track is not using, open
MUSIC.SNDon it, mute it, start preloading. - Pre-loading until the device says the stream is ready; then, by the other channel's state: none playing,
start now (fading in over
fadeBars × barms, or at once); one playing, wait for its next bar boundary (start + ⌈(now − start) / bar⌉ × bar, stateST_MSPMSync), and 33 ms before it start the new track and fade the old out over its own bar length × the fade bars; one fading in or out, start now and fade the other out. - Playing: when the stream ends (a track played once), stop it and call the callback (a Lua function name) if one was given.
- Fade out to 0, then stop and free the pair.
SoundStopMusicTrack (0x0010d9a0) works per channel by state: a queued request is dropped; one pre-loading or
blocked is stopped at once; one playing or waiting for the bar fades out over one bar of its track from now; one
fading in turns into a fade-out from the level it has reached. The music update (0x0010df30) runs on every audio
manager update. The music volume
(0x0010e558) is fade × track volume × music volume (+0x5c) × options' music volume (+0x60), × 0.75 while a scene
plays (game state +0x410) with ducking on, × 0.5 while game state +0x268 is positive; each channel's left volume
goes to its left stream and its right to its right.
System music (0x0010e7d0, every update while game state +0x3f8 is set): the mood is game state +0x40c
(0, 1 or 2; set by SoundSetSystemMusicState and the game, not traced). When it changes, or a change is forced,
a random one of that mood's up to three tracks (SoundSetMusicTrack(mood, a, b, c)) is played looping. Its fade
in bars: 0 (a cut at the bar) into mood 1; 4 into mood 0 from mood 1 or 2; otherwise 2. With no track for the mood
the music stops. The scripts' callback (SoundSetMusicStateCallback) is told (0x0041b520).
Confirmed (code); the moods' meaning (inferred from the tracks' names: 0 idle, 1 fight, 2 search) and who sets them
are not traced.
Ambience¶
- Ambient track:
SoundPlayAmbientTrack(name)(0x00110b60) plays one looping 2D sound with a 2 s fade-in, replacing the current one unless it is the same; in level 82 one track is swapped for another (0x63f1c8b3→0x0ffbaa27). Its volume is the task's caller volume (SetAmbientTrackVolume). Confirmed (code). - Ambient table:
AddAmbientSound(slot, name)(global.lua, 1,077 calls) stores a name's hash in slotslotof the ambient table (ambient manager+0x1a014 + 4 × slot). Confirmed (code). - The
small_loopssounds (class flag0x20) take the stream channels 10-12, so they never steal from effects.
Ambient emitters¶
The ambient manager (audio manager +0x24280; its functions are on Animation) holds up to
512 emitters of 0xd0 bytes. AddAmbientSoundEmitter2(name, pos1, pos2, slot, sound, count, range, plays, minDelay,
maxDelay, mode, filter) makes one through AmbientManager_AddEmitter (0x0010cf58); a name already used returns
the existing emitter and switches it on. Offsets below are from the emitter's start (ambient manager +0x10 + 0xd0 ×
id; the Animation page counts from the manager, 16 bytes more). Confirmed (code):
| Offset | Meaning |
|---|---|
+0x00 |
the one sound's hash, when slot is -1 |
+0x10 |
up to five points the sound plays from, 16 bytes each: pos2, or SetAmbientEmitterPositions' list |
+0x60 |
pos1, the point the range is measured from |
+0x70 |
the playing sound's handle |
+0x74 |
range in m; -1 gives the first sound's far + 10 |
+0x78 |
volume (SetAmbientEmitterVolumeMod, 1) |
+0x7c |
on (EnableAmbientEmitter) |
+0x80, +0x84 |
the last play's time (ms) and the delay in whole seconds before the next, drawn in minDelay-maxDelay (+0x86, +0x88) |
+0x8a, +0x8f, +0x94 |
first table slot (-1: the one sound), slot count, next slot (a random one to start) |
+0x8c |
plays left (plays; -1 without limit); reaching -1 switches the emitter off |
+0x8d |
mode (below; the scripts pass 3 or 4) |
+0x8e |
number of points (1, or the positions' count) |
+0x90 |
listener filter (filter 0-2, larger is 0) |
+0xac |
name kind: 1 when the name contains _DAM_, 2 for _FHT_, 3 when its lower-case form contains music |
+0xad |
the name (31 chars) |
The update (AmbientManager_UpdateEmitters, 0x0010c100) runs at most once a second (game clock) and not at
all while game-state flag 2 is set or the load screen is up (+0x24270). For each player and each emitter:
- Who hears it. A player counts unless the filter says otherwise: filter 0 skips a player whose
+0x5b7is set, 1 one whose+0x5b7is clear, 2 skips none (+0x5b7: the player stands on covered ground, inferred indoors). An emitter that is off, or that no player counts for, has its sound stopped. - Volume. A playing sound's
a8factor becomes the ambient factor (0.75 while a scene plays, else 1), and amusicemitter's then the duck instead, when it is not 1: 0.5 while a music channel is busy and the mood is not 2 (AmbientSound_ApplyVolume,AmbientSound_ApplyDuck). - Distance: to the nearest listener from
pos1(0x00113188). Beyond the range, or while0x005147c8is set, nothing starts, and a playing sound is stopped once the listener is beyond that sound's ownfar. - Kind gates: a
_DAM_emitter plays only from 2 s to 15 s after the AI's event stamp (AmbientManager_MarkEvent, Animation) and not while the fight timer is on; a_FHT_one only while the fight timer is on (mood 1 and 5 s after). The stamp (manager+0x1b78c): every noise of 10 m or more (AI_ReportNoise,0x002936a8, Stealth) marks it before the noise goes out, whatever its source or kind; a smaller noise does not.AI_ReportNoiseis the mark's only caller (one call, at0x002936f0, ahead ofAI_AlertNearby), so nothing else stamps it. The mark writes the game clock (milliseconds,*(0x0050b734) + 0x38) when the stamp is 0, and the clock minus 2 s when any stamp is set, wherever it is in the window, so a second noise opens the window at once and it closes 13 s later. Each audio update (AmbientManager_UpdateTimers,0x0010be78, before the once-a-second gate) sets the window flag+0x1b788when the clock is more than 2 s past the stamp, and clears flag and stamp once it is more than 15 s past. The noises: 30 m from a strike's contact, a car hit or explosion and a thrown or world object's contact; a thrown object hitting a human, a world object's impact and a body landing after a climb use their sound's far distance (15 m or 30 m when there is none), so they mark only when that is 10 m or more. Confirmed (code) at0x002936a8,0x0010be40,0x0010be78and the callers' arguments; the mark's callers from Ghidra's references. - Play, by mode, at most two new sounds per update over all emitters:
- 1, 2: when the delay has passed and nothing is playing, take one play, play the next sound (the table slots in turn, from the random start) at a random one of the points, positional and duckable, at the emitter's volume with the ambient factor; then draw the next delay. A play count that reaches -1 switches the emitter off instead of playing (so 0 plays allows none). When the delay has passed while its sound still plays, the delay is drawn again; after the last play of a count (0 left) the next delay is 1 s.
- 3: a loop: whenever the listener is in range and nothing is playing, start the next sound at the first
point (the ambient factor, or for a
musicemitter outside a scene the duck); beyond the range, stop it. No delays, no play count, not counted in the two new sounds. - 4: as 1 and 2, but its first play waits for neither the delay nor a playing sound; it then becomes mode 2.
- 5: as 1 and 2, but a playing sound is stopped at the next update (inferred to suit sounds of a second or less); a delay not yet passed is set to 0 from now, so it plays at the following update; a count reaching -1 switches it off after that play.
- 6: nothing.
- 7: a conversation (
AmbientSound_AddStatResponse→AmbientManager_AddSphereEmitter,0x0010cd68): up to five voice sets (+0x98, -1 ends the list) take turns, each saying thestatementcommand (20) and the next set theresponse(21), the line number moving on after each response; when the listener is farther than the line'sfar− 20 m the distantstatement2/response2lines (77, 78) are used, or the same line at half volume.
Confirmed (code). AmbientSound_AddOneOff (0x00113b78) is an emitter of one sound with one play;
AddAmbientSoundEmitter (no name) is the same call named particle task
(Sound bindings).
Radios¶
SetupRadio (Radio_Setup, 0x003ac4d0) makes a world object (a boom box) a radio run by Radio_Update
(0x003ad440, every update interval of the object) and Radio_HandleMessage (0x003ac7e8). Everything a radio
plays is a streamed sound of the sound list (stream flags, BFW.SND) started with PlaySound3DByHash (0x0010fdd0,
volume and pitch 1) at the radio's position: an ordinary positional sound task, not the music player; each
update moves the task to the radio and stops it once the player is more than 40 m away. While a scene plays
(0x0051489c +0x410) the task's volume is 0.5, otherwise 1. Confirmed (code).
The tables (hashes of the sound list; names found by the CRC-32 of candidate names and checked against the
position of the sound in BFW.SND, which keeps each folder's files in upper-case alphabetical order; inferred
where the match is one word pair among many tried; none are runtime-checked):
| Table | Entries | Names |
|---|---|---|
Tracks 0x00512cd8 |
21 (0-20) | 0 echoes_in_my_mind, 1 last_of_an_ancient_breed, 2 love_is_a_fire, 3 nowhere_to_run, 4 you_re_movin_too_slow, 5 0x60b5c0c3, 6 get_down_radio (inferred), 7 0x73f3d710, 8 0x3be47458, 9 0xf33104cd, 10 remember, 11 0x46b4f271, 12 in_the_city, 13 baseball_furies_chase, 14 the_fight, 15 theme_from_the_warriors, 16 sho_radioloop_01, 17 alberto, 18 punkemitter (inferred), 19 spanish_killer_loop, 20 tna_funk |
Announcements 0x00512d30 |
13 (0-12) | 0 is track 13 again (never played: djLine 0 means none); 1-12 djlady_01 ... djlady_12 (djlady_13 exists and is in no table) |
DJ links, kind 0 0x00512d68 |
18 | no names recovered; class 134 |
DJ links, kind 1 0x00512db0 |
15 | no names recovered; class 134 |
DJ links, kind 2 0x00512df0 |
9 | dj_rumble_01 ... dj_rumble_09 in order, except entry 6 is _08, 7 _09, 8 _07 |
| Retune | 0x09a4be6a |
one sound, class 72 (1-15 m) |
| Switch | 0x5b601235 |
one click, class 49 (1-10 m) |
Every name is vags/music/<name>. The tracks use classes 106 (5-50 m), 210 (track 16) and 214 (tracks 18-20); the
announcements class 213 (1-40 m). Confirmed (code) for the tables, sizes and classes (the sound list).
The record (via vtable +0x18c): +0x24 the sound task, +0x28 the current track or clip index, +0x2c the
next track (or the announcement), +0x30 the state, +0x34 the clip table (0, 1, 2 the DJ link kinds, 3
announcements), +0x36 the announcement armed, +0x38 onPickUp, +0x3c onSegment. SetupRadio with
djLine stores it in +0x2c, arms +0x36 and starts in state 1 with track; without, a track above 0 starts
state 1 and 0 or less state 9 (off). Confirmed (code).
The update. First, an announcement armed and the player within 5 m: it is disarmed, the sound stopped, the
announcement becomes the clip (kind 3), a random next track is drawn and the state becomes 5. Then by state
(confirmed (code); "draws a track" is 0x00335498(rng, n), Random % n, with n = 18 once level 84 is complete
and 12 before):
| State | Does |
|---|---|
| 1 | player within 40 m and track ≥ 0: play the track, state 2; else stop |
| 2 | track playing: follow the radio. Track ended (within 40 m): an armed announcement plays next; otherwise pick a DJ link with Random_Int(9) (0-9): 0-6 kind 1 (one of 15), 7-8 kind 0 (one of 14-18, below), 9 kind 2 (one of 9, or 8 once level 93 is complete); draw the next track; state 5 |
| 5 | play the clip from its kind's table (within 40 m), call onSegment; state 6 |
| 6 | clip playing: follow the radio; ended, or a scene running (stops it): state 8 |
| 8 | the next track becomes current (above 11 it becomes 0), state 1, call onSegment |
| 3 | stop, play the retune sound, call onSegment, state 4 |
| 4 | retune sound ended: state 7 |
| 7 | draw a track (not limited to 11), state 1, call onSegment |
| 9 | keep the track as next, track −1, play the switch click; state 10 |
| 10 | off: stop any sound |
| 11 / 12 | play the click; when it ends the next track (above 11: 0) plays, state 1 |
The kind-0 pool is 14 entries once level 84 is complete, else 15 once level 31 is, 16 once level 93 is, 17 once
level 81 is, and all 18 before. "Complete" is 0x004241d8(0x006fe998, level): the level's (level, 0, 0)
unlockable is unlocked. Confirmed (code); why later missions shrink the pool (stale
news, inferred) is not known. Tracks 18-20 are reached only through SetupRadio's track.
Turning it (Radio_SetMode, 0x003ac600, which stops the current sound whenever it changes the state): mode 1
retunes (state 3) a radio in state 1, 2, 11 or 12 and does nothing otherwise; 0 and 2 switch it off (state 9); 3
pauses (state 11); 4-9 step to the next track (above 20: 0) and 10 + n selects track n, both through state 3.
Only mode 1 is used: by the player holding R1 (command 4) while carrying an item of weapon type 6
(Player_UpdateActions, 0x0027c624), and when the radio is smashed (message 1, which also throws debris and marks
the object dead so the next update stops the sound and ends the task). Confirmed (code); that weapon type 6 is the
carried boom box is inferred.
Messages: 0x12 plays the current track if nothing is playing and the player is within 40 m; 0x20 stops the
sound; 10 with 0 stops it and leaves the radio off (state 10); 0x1b (picked up) calls onPickUp with the player's
handle and queues hint text 0x11. Confirmed (code).
A destroyed radio. ObjDestroy of the boom box (the level95 hub's mission action does it) removes the object
that holds the radio record. What happens to a sound still playing is not traced; Coney stops it and forgets the
radio, as the smash (message 1) does on the next update. Inferred.
Interface sounds¶
SoundCfgInterfaceSound(n, name) fills a numbered cue table in the sound matrix; 0x0010fc30 plays cue n as a 2D
sound with flags 0x12 (the front end's cue 9 on START, 0xf on back; Front end). The 39 cues are in
Sound and music; the menu cues are in the menu bank. Confirmed (code).
Animation sounds¶
A clip event of type 11 (Animation data, fired by
Anim_FireFrameEvents, 0x00101dd8) sends its human message 0x8b with the event's value, an animation sound id
(SA.*, 0-161, the matrix's animation table). The value is the 32-bit word at event +8
(lw a1,0x8(s1) at 0x0010246c, pushed with Msg_PushInt), not +4 or +6, which are 0 in these events; the
handler switches on the whole word (sltiu against 162 at 0x0021f72c), so a word of 162 or more takes the default
path (Human_PlayAnimSound with that value). Reading the s16 at +8 matches whenever +0xa-+0xb are 0, which the
footstep ids 1 and 3 need to work as they do. Confirmed (code). Gameplay clips and scene role clips carry them alike:
the footsteps, cloth, grunts and impacts of every move. The human's handler (Human_OnAnimSoundEvent, 0x0021f700)
acts by id. Confirmed (code):
- Most ids: the animation entry played at the human's position (
Human_PlayAnimSound,0x0021f548), owned by the human. A player's (human+0x1b0not -1) play at twice the entry's volume times the combat factor (game state+0x24cwhile the camera is in combat framing,0x0021e3a8), and add column 2, and column 3 under combat framing; anyone else's play column 1 only. - Footsteps and body falls (ids 1, 3 and some others): the material pair of a body material (8 for feet) and the
ground under the human (
+0x1d8, remapped as above), throughHuman_PlayFootstep(0x0021f290); twice as loud for a player, half for one hidden in shadow. - Held objects (ids
0x34,0x56): the held object's material against itself or against 17. - Vocal ids (grunts and efforts): the entry's first sound said as a speech line (
Human_SayAnimLine,0x0021f410; needs+0x199, a player twice as loud), some only when no line plays, some cutting it, none over a line that may not be cut (Cutting in on a line). - Speech-command ids:
Human_SayCommandwith a fixed command (0x8bitself saysonfire, 107, while+0x19bis set; others taunts and reactions, some only whenAmbient_MayGestureallows).
Every id's sound, material and condition is on Sound events.
Types 12, 14, 69, 70 and 71 send messages 0x8c, 0x8e, 0xc5, 0xc6 and 0xc7 with the value; 14 and 71 are the
sound-by-hash events of Scene soundtracks. Type 13 starts the scene soundtrack.
Saying a speech command¶
SoundPlayCommand(human, command, callback, flag, target, flag2) resolves the human and calls Human_SayCommand
(0x002205e0), confirmed (code):
- Nothing when the human cannot speak (
+0x199clear). A line already playing is cut off when the fourth argument (interrupt, default true) is set, unless it may not be cut (+0x194clear, aHuSpeakNIline or an order): then nothing is said and the command's callback runs at once (Cutting in on a line). Withoutinterruptnothing is said while any line plays. VoiceTable_NextLine(+0x3b0, command)picks the line. With a chance below 100 it rolls 0-99 and says nothing when the roll is higher than the chance. With no lines it says nothing. Otherwise it takes linenext + 1, adds one tonextand wraps it to 0 at the count: a set's lines for a command play in turn, not at random. A line on the fixed list of0x00114c98(current level, set, command and line) is skipped; the counter still moves.- Commands 1-6,
0x12,0x46,0x56and0x9e(and0x28while0x0041d160is 1) set human+0x16c(0x001106d0) and first stop every real, streamed, mono sound of priority 8 or more whose last volumes are both 0.2 or less (0x00113258), so a stream channel is free. The flag goes with the line to its task (+0xec), and its stream gets IOP stream priority0x3f(0x0014c8b8→0x00151990;0x100is the default). Confirmed (code); that this keeps the Warriors' orders audible over other streams is inferred. - The line plays at the human (
0x0021e400; the war chief's lines use0x0021e698, which can also cut a line off), unless a scene is playing (game state+0x410, read at0x0021e400). The third argument is a callback name: the function is called with the speaker's handle when the line ends, or at once when no line plays. With a target, the speaker looks at it for the line's length plus 0.5 s.
Voice lines are streamed, positional and directional (class flags 0x1c or 0x4c, priorities 7-11, far 20-50 m).
No lip-sync data was found: the speaking human only turns to its target. Voice lines in play have no subtitles; only
scenes and movies do (Scenes: subtitles). Scenes play their own soundtrack
(Scene soundtracks).
Gang warning barks¶
When a gang first notices a target (0x001691a0, from the gang brains at 0x00311928 and 0x00313d18; once until
the gang's +0xd0 is cleared, and only while game state +0x56fc is 1), a member says a warning bark: the name is
built by VoiceTable_WarnLineHash (0x001148e8) in the speaker's voice set (human +0x3b0) and played with
Human_PlaySpeech at volume 1, after stopping his current line. Confirmed (code):
- Target within 12 m (squared distance 144 or less):
vags/character/voices/<set>/warn/warn_o_<NN>, only when the caller asks for a near bark (its fourth argument 1); otherwise nothing. - Farther:
warn_<n|s>c_<NN>when the target's brain+0x04is 1, elsewarn_<n|s><o|w|t>_<NN>by the gang's living members (Gang_CountLiving):wfor 2-5,tfor more than 5,ootherwise. swhen one of the gang's members (+0x48, up to 16) already tracks (brain+0x164, up to 16) a human whose brain+0x20cmatches the speaker's, elsen. Thatsmeans "seen again" is speculative.NNis the gang's counter+0x60d, moved on after each bark and held at 1 (0x00169630), so a gang's first bark uses its starting value and every later one_01.
A name that is not in the sound list gives hash 0 and plays nothing. The bark's handle goes to the gang's +0x610.
The DJ's failure lines¶
On the mission-failed screen (mode 0xc, update 0x0015cc78; Pause) the radio
DJ comments once: when the mode's handle is idle and nothing has played yet, DjLines_PickFailureLine (0x001170e8)
gives a hash for the current level, checkpoint (game state +0x33a) and failure kind (game state +0x118: 0
wrecked, 1 busted, 2 obj), played as a 2D sound (PlaySound2DByHash, duckable). The three tables are filled
at start-up (DjLines_Init, 0x00116848); names are under vags/character/voices/dj/fail/. Confirmed (code):
- Per checkpoint (
0x00116d48):dj_l<level>_c<cp>_<kind>_<NN>for an entry (level, checkpoint, count, kind): (20, 4, 1, obj), (20, 5, 1, obj), (3, 2, 3, obj), (5, 3, 2, obj), (5, 4, 2, wrecked), (55, 2, 3, wrecked), (55, 3, 3, wrecked), (80, 4, 3, obj), (81, 3, 2, obj), (82, 2, 3, obj), (82, 3, 2, wrecked), (83, 1, 3, wrecked), (86, 1-5, 1, obj: five entries), (92, 1, 2, obj), (93, 6, 2, wrecked). - With none, kind
objis treated aswrecked, as is every kind in level 82; then per level (0x00116ec0):dj_l<level>_<kind>_<NN>, 3 lineswreckedfor levels 2, 3, 11, 14, 20, 31, 34, 51, 54, 81, 82, 83, 84, 87, 92 and 93, 2 for level 5;busted3 lines for levels 9, 31 and 52 (52 haswreckedtoo). - With none: level 82 plays
dj_l82_wrecked_01; any other level a generic linedj_<kind>_<NN>,NNrandom in 1-14 forwreckedand 1-10 forbusted(0x00116fe8).
In the per-checkpoint and per-level tables each entry's NN runs 1 to its count in turn, one step per failure.
Speech lines by name¶
HuSpeak and HuSpeakNI take a sound name (vags/speeches/l31/l31_t7_001). The global.lua helper SetVag(name)
builds one from a short name: l11_t25_006 stands for vags/speeches/l11/l11_t25_006, the folder being the name's
first part. Speech lines are class flags 0x06 (streamed, positional) at priority 4, so only priority-2 and -3
sounds outrank them. 537 of the 566 names the scripts form are in the sound list; every bank sound is in the list too,
so the other 29 (and the 2 missing ambient names) are not on the disc at all (inferred: the scripts name lines
that were cut).
How a line plays (Human_Speak, 0x00239370 → Human_PlaySpeech, 0x0021e400; HuSpeakNI →
Human_PlaySpeechCutting, 0x0021e698), confirmed (code):
- Nothing plays, and the callback runs at once, when the human is missing, the line is nil, the human's speech is
off (
+0x198,HuEnableSpeaking) or a cinematic is playing (game state+0x410): characters do not speak over cinematics; their dialogue there is the scene's soundtrack (Scene soundtracks). HuSpeakalso gives up while a line is alive or the human's "may be cut" byte+0x194is clear.HuSpeakNIstops the current line first, unless that line is itself an uncutHuSpeakNIone: it clears+0x194while it plays, so a secondHuSpeakNI(orHuShutUp(false)) leaves it alone and runs its own callback at once.HuShutUp(true)always stops.- The line is prepared, not started: a positional, directional stream 1.8 m above the human's feet, at volume 1
(× the combat factor of Animation sounds for a player). The fifth argument is the play call's
duckable flag: false makes a non-duckable line, which plays at pitch 1 and ducks every other duckable
directional sound of a non-player to
+0x3faac(0.2) while it lasts. Human_UpdateSpeech(0x0021e940, each update) starts the stream once it is primed (and the speaking pose with it), keeps it at the human's head, and when the line is gone calls the Lua callback with its argument, or, when there is none, sets+0x194again. A line stopped byHuShutUpkeeps its Lua callback, which runs at that next update; a line cut off by a new one loses it (the new line's callback replaces it). The full rule is below. The speaker looks at thelistenerfor the line's length plus 0.5 s.
Cutting in on a line¶
Every way a human's line starts or stops goes through the same four fields and two functions, so one rule covers
HuSpeak, HuSpeakNI, HuShutUp, speech commands and the grunts of vocal ids.
Confirmed (code) at the addresses given:
| Human field | Meaning | Written by |
|---|---|---|
+0x178 |
the current line's sound handle; "alive" means it still resolves to a task (SoundHandle_Resolve, 0x0010f578) |
every line start |
+0x188 / +0x190 |
the Lua callback (an interned name, 0 for none) and its argument | every line start writes both, 0 when the new line has none (0x0021e56c, 0x0021e818); Human_UpdateSpeech clears +0x188 just before it calls it (0x0021eb20) |
+0x194 |
"the line may be cut": 1 normally; 0 while a line that must play out runs | 0: Human_PlaySpeechCutting (0x0021e744, every HuSpeakNI line and every cutting line), Player_StartPreparedLine (0x0021f26c) and Strike_Contact after its fuck_you (88) complaint (0x0021b4a4, Combat); 1: Human_StopSpeech when it stops (0x0021ecac), Human_UpdateSpeech when no line is alive and no callback is pending (0x0021ebbc), Human_Init |
+0x195 |
set with +0x194 by a stop: the stopped line's queued voice follow-up (+0x184) is dropped |
Human_StopSpeech |
Stopping (Human_StopSpeech(human, force), 0x0021ec38): nothing when no line is alive. Without force,
nothing either while +0x194 is 0: an uncut line is never stopped by an unforced stop. Otherwise the stream
stops (and is freed at once, AudioManager_StopTask 0x00112c60, so the handle no longer resolves), the speaking
pose ends and +0x194 goes back to 1. A stop never touches the callback (+0x188). HuShutUp(human, force)
passes its flag; the game's own forced stops are a knock-out, a defeat, a lethal reaction, a paired attack or grab
starting (Attack_StartPaired, Grab_StartPairedMove), mugging, some goals' ends and Humans_StopAllSpeech; every
other caller (the speech commands, the vocal ids, the gang barks, banter, scenes) stops without force.
Starting a line has two forms, and neither will start over a line that is alive and uncut:
Human_PlaySpeech(0x0021e400;HuSpeak, the non-cutting vocal ids, uncut speech commands) plays only when no line is alive and+0x194is 1.Human_PlaySpeechCutting(0x0021e698;HuSpeakNI, the cutting idsdieandexpire, speech commands said withuncut0) plays unless a line is alive with+0x1940. It stops the current line first (unforced) and clears+0x194for its own.
A start that plays nothing calls its own callback at once (when it has one) and leaves the human's line, callback
and +0x194 as they were. A start that plays writes its own callback (or 0) over +0x188.
When a pending callback runs or is lost: the callback in +0x188 runs at the first Human_UpdateSpeech that
finds no live line (once; +0x194 is then set back to 1 an update later, by the next update's no-callback branch). It
is lost only when another line starts on the same human before that update: a cutting line over a cut-able one,
or any line (a grunt included) in the gap between a stop and the next update. Hence:
- A vocal id with
overLineover an uncut line (HuSpeakNI's,+0x1940):Human_SayAnimLinecalls the unforced stop, which does nothing, thenHuman_PlaySpeech, which finds the line alive and plays nothing; the grunt has no callback, so nothing runs. The line and its callback are untouched; the grunt is simply not heard. The cutting ids (die58,expire115) go throughHuman_PlaySpeechCuttingand are refused the same way. Confirmed (code) at0x0021f410,0x0021ec38,0x0021e400,0x0021e698; confirmed (runtime), below. - The same over a cut-able line (
HuSpeak's, a speech command's said uncut 1): the stop ends the line, the grunt plays in its place and writes 0 over+0x188: the line's callback is lost and never runs. Confirmed (code) and (runtime), below. Human_SayCommand(0x002205e0) withinterrupt(thepaingrunt 12 ofSA28,SoundPlayCommand's default) calls the same unforced stop and then one of the two starts: over an uncut line it says nothing, though the voice table's counter has moved andSound_MarkCommandSaidhas run (0x001106d0), and the command's own callback runs at once; the uncut line and its callback carry on. Over a cut-able line it cuts it and the old callback is lost. Withoutinterrupt, nothing is said while any line is alive. Confirmed (code).HuSpeakwhile a line is alive, or in the one update after an uncut line with a callback ended (+0x194still 0): nothing plays and its callback runs at once.HuSpeakNIover an uncut line: the same. Confirmed (code).HuShutUp(false)on an uncut line does nothing;HuShutUp(true)stops it and the callback runs at the next update. Confirmed (code) and (runtime), below.- A line that ends by itself, or is stopped, and has its callback replaced by a new line started in the same update
before
Human_UpdateSpeechruns (a forced stop followed by a grunt in one update) loses it: inferred from the writes above, not seen at runtime.
At runtime (2026-10-12, PCSX2 2.9.94, confirmed (runtime)). Scenario repo:research/traces/scenarios/speech_cutin.toml
on level80 checkpoint 1 with Cleon as the player, the hooks say-anim-line, stop-speech, play-speech,
play-speech-cut and event-callback of repo:research/traces/patches/: the game's own HuSpeakNI and
HuSpeak workers called through the call hook on Cleon with probe callback names, square tapped every 8 updates
during the first two lines. Updates are the trace's steps:
| Step | What happened | Cleon's line, callback, +0x194 after |
|---|---|---|
| 321 | HuSpeakNI (callback SpeechProbeA): Human_PlaySpeechCutting plays |
new line, SpeechProbeA, 0 |
| 351, 399 | square swings: Human_SayAnimLine(Cleon, 9 grunt_pch, overLine 1, cut 0); Human_StopSpeech(force 0) returns, Human_PlaySpeech plays nothing |
unchanged (same handle), SpeechProbeA, 0 |
| 412 | the line ends by itself (91 updates long): SpeechProbeA looked up from Human_UpdateSpeech (0x0021eb3c) |
+0x188 0, +0x194 still 0 |
| 413 | the next update sets +0x194 back |
1 |
| 521 | HuSpeak (callback SpeechProbeB) |
new line, SpeechProbeB, 1 |
| 548 | a swing's grunt_pch: the stop ends the line, the grunt plays in its place |
the grunt, 0, 1; SpeechProbeB is never looked up |
| 701-792 | the control: HuSpeak (SpeechProbeC), no swings; it ends and SpeechProbeC is looked up |
|
| 821 | HuSpeakNI (SpeechProbeD) |
new line, SpeechProbeD, 0 |
| 851 | HuShutUp(Cleon, false) |
unchanged |
| 871 | HuShutUp(Cleon, true): stopped, and SpeechProbeD looked up in the same update (the call hook runs before the humans' speech update) |
0, 1 |
The follow command at step 41 shows one more case: Cleon's own command line goes through Human_PlaySpeechCutting
(called from Human_SayCommand, 0x0022073c its return), so a Warrior order is an uncut line too (+0x194 0 for
its 30 updates). The run does not reach mission 2's end; the end's line is HuSpeakNI with a callback, which is
case 1 above whatever swings, grunts or pain lines Cleon makes while it plays.
Scene soundtracks¶
An in-engine scene (Scenes) plays one scene soundtrack: a 32,250 Hz stereo stream (class 224, stereo table) as long as the scene (speculative: it carries the scene's dialogue). It runs in two steps, so the stream is buffering before the first frame:
- Preload (
0x0010ff68): called from the record's fix-up (0x00352098) and each streamed segment's (0x00352788), from the music update for a pending soundtrack (0x0010df30, below) and fromSoundPreLoadScene(0x00114178). A fix-up preloads once for every role whose clip has a type-13 event (its first,0x00101c58) and then for the camera track's (0x00354d28), each call replacing the last, so the last one wins. Each call: stop the current soundtrack if its task is alive (0x00110078, which also clears+0x2426c), claim a stream pair (Stream pairs), then prepare the sound without starting it (0x001100e8→0x00111f78→AudioManager_NewTask, duckable, volume and pitch 1): the stream is set up and primed (0x0011b128, device slots+0x90and+0x70) but not started (task+0x9c= 0). The task's handle goes to+0x24268and to the music player's+0x08. It goes throughNewTask's rules, but the load-screen flag (+0x24270) spares stereo sounds and the 3D cull does not apply to a 2D sound, so a scene loaded while the load screen is up is prepared on a real pair, not virtually; only the admission (more than 240 tasks) could refuse it. Confirmed (code). - Start: scene event 13 (
0x00110018, from the camera track at0x00355200and from a role's clip at0x001024ec) starts whatever task+0x24268holds (0x00110258→ device slot+0x78, task+0x9c= 1) and sets+0x2426cto 1, or to 0 when the handle is dead. It reads no hash: nothing checks that the prepared sound is this scene's. A dead handle starts nothing, and a virtual task (no stream) is refused by the device (0x0014c968), so neither is heard. Confirmed (code).
Between the two, the scene's start waits for the soundtrack (0x0039d870, at 0x0039de14, in start step 6 of
Scenes), for a cinematic (+0xed) or when the current level's id is 0x3c (which level is
not traced). It re-enters the start (state 4) while the task in +0x24268 is alive but its stream is not yet
primed (0x001102a0 → device slot +0x80, 0x0014c9c0: a stream's status byte 1; a virtual task counts as ready),
or while the task is dead and a hash is pending. While it waits and both stereo slots hold music (the slot bytes
read as 0x0101, 0x0011b760), it turns the system music off (remembering it in the scene task's +0xf0) and
stops the music (0x00110528 → SoundStopMusicTrack), which frees a pair for the pending soundtrack. A dead task
with nothing pending does not hold the scene. The start's 10 s give-up still applies. Confirmed (code). A
non-cinematic scene (ScenePlayFixedScene, most ScenePlayAnimation calls) does not wait.
AudioManager_Update (0x0010f810) clears +0x2426c on every update in which the soundtrack's handle is dead,
so the flag means "a started soundtrack is still alive". Confirmed (code) at 0x0010f894.
Runtime (PCSX2 2.9.94, level99, a scratch state taken before the Warriors' fight lesson, the audio manager read
over PINE every 30 ms): the soundtrack is class 224, flags 0x04 (streamed, 2D, played once), priority 3. Scene
one was prepared on pair 1+2 as the cinematic began (scene state +0x410 1), started 0.28 s later (+0x2426c = 1)
and kept playing after the scene ended (+0x410 0, task still live and started); 0.4 s later the next scene's
fix-up found it alive and stopped it, found pair 1+2 still marked as a scene sound, recorded the hash as pending,
and the music update prepared it again on pair 1+2 0.43 s later, before that cinematic's start; event 13 then
started it. A third scene's soundtrack outlived its scene by 0.47 s and then ended by itself, and +0x2426c fell
to 0 with it. Mono streams (ambience, voices) sat on channels 5-10 throughout.
Stream pairs¶
Stereo streams use two pairs: 1+2 (slot 0) and 3+4 (slot 1). The music
player's first two bytes (manager +0x64, +0x65) record each slot's user: 0 free, 1 a music channel, 2 a stereo
sound (in practice the scene soundtrack). Mono streams use only 5-9 (and 10-12), so ambient emitters, speech
lines and voices never compete with a scene soundtrack. Confirmed (code) at 0x0011b450, 0x0011b498,
0x0011b750; confirmed (runtime): music on 3+4 with a soundtrack prepared on 1+2, slot bytes 2 and 1.
- The preload's claim (
0x0011b450): refused when either slot already holds a stereo sound; otherwise slot 0 if free, else slot 1 if free, else refused. The result only gates the preload; the pair itself is picked again when the task is made. - Its fallback (
0x00112d60→0x0011b558), when refused: if the previous soundtrack's task is still alive, every slot holding a stereo sound is freed, that task is stopped, and the preload goes on. Otherwise the hash is stored as pending (music player+0x04) and nothing is prepared now. The preload stops a live soundtrack first, and a stopped task whose+0xe4is clear is freed at once (0x00112c60), so after one scene's soundtrack the next scene's preload takes the pending path (seen at runtime): its slot stays marked until the next music update. - Each music update (
0x0010df30): first, if a hash is pending and the claim would succeed, preload it and clear the pending hash; then free every slot marked as a stereo sound when the task in music player+0x08is dead (0x0011b5e8). So a pending soundtrack is prepared on the music update after the one that frees its slot (0.43 s after the fix-up in the runtime trace above). A cinematic's start waits for it (above). A scene that does not wait and reaches event 13 first starts nothing, and the later re-preload is never started: that scene plays silent. Inferred from the code; not seen. - Making the task (
0x00112560→0x0011b498(…, 2)): slot 0 if free, else slot 1 if free. With both taken: while a started soundtrack is alive (+0x2426c) or a cinematic runs (+0x410), no slot; otherwise a slot holding a stereo sound is taken over (0x0011b6c0: the current soundtrack's hash goes to pending and its task is stopped). With no slot the pair number is still made from −1 (0x0011b750gives pair 3), so the sound plays on 3+4 over whatever uses them; a stereo sound never goes virtual for want of a pair. Confirmed (code). - Music claims a pair the same way (
MusicChannel_Update, state Queued,0x0011b498(…, 1)): when both slots are in use, no started soundtrack is alive and no cinematic runs, a queued track takes the soundtrack's pair, stopping the prepared soundtrack and leaving its hash pending; it is prepared again when a pair frees (a cross-fade's old track ending, or the cinematic start stopping the music). While a started soundtrack is alive or a cinematic runs, the track waits in Queued. Confirmed (code). For a cinematic this only delays the scene; a scene that does not wait can lose its soundtrack this way (inferred).
Ending. Nothing stops the soundtrack when a scene ends normally: the end (SceneTask_End, 0x0039f450) stops
it only when the scene was skipped (task +0xe4), and the abort (0x0039ec60) only for a cinematic (+0xed);
both through 0x00110078, whose only other caller is the preload; freeing the scene task and unloading the slot
do not stop it. Otherwise it runs to its own end or until the next preload stops it; at runtime two unskipped
soundtracks outlived their scenes by 0.4-0.5 s (above). Confirmed (code); confirmed (runtime) for the unskipped end.
Several scenes loaded. Each load's fix-up preloads its own soundtrack and stops the previous one, so with two
scenes loaded only the last loaded one's soundtrack is prepared. Playing a scene whose slot is already loaded
(Scene_Play, 0x00353818, the start 0x0039d870) preloads nothing: the only callers of 0x0010ff68 are the two
fix-ups, the pending re-preload and SoundPreLoadScene. So a scene played after another scene was loaded later
starts the other scene's soundtrack (the start's wait sees a ready task and event 13 starts it), and a scene
played again from its loaded slot after its soundtrack has ended starts nothing (dead handle, nothing pending).
Segment fix-ups preload too, which would stop the playing soundtrack mid-scene if a later segment carried a
type-13 event (inferred not to happen: each scene names one stereo sound, below). Confirmed (code).
Other things that silence scene sound.
- The soundtrack is 2D: its volume is
record volume × sound volume (+0x3fa20) × fade × pan(Task update); the music volume, the music duck and the 3D rules do not touch it. - While a cinematic runs the manager's non-duckable flags are set (
0x00112b10), so every duckable directional sound not owned by a player is scaled by the duck factor (+0x3faac, 0.2). Events 14 and 71 play duckable sounds with no owner (0x0010fdd0, last argument 1), so directional voice lines among them play at a fifth. Confirmed (code). Human_PlaySpeechplays nothing while a scene plays (Saying a speech command), and the start stops every human's held sound for a cinematic with roles (Scenes).SoundPauseSoundpauses every voice, the soundtrack included.
Music ducks to 0.75 while a cinematic runs (Music). Scene events 14 and 71 play a sound by hash at a human's
position (the human transform at 0x00714b00 + index × 0x20) only when that human's speech handle (+0x168) is idle,
storing the handle at 0x0021ef58; event 71 on a car goes through 0x00110258. Confirmed (code).
On the disc (inferred, from scanning the .scn files for listed hashes): 228 scenes name a stereo sound, using 228
of the 248 once each; every main level99 scene (l99_c1-c9, l99_t1, l99_ash_intro) has its own. The scenes
also name streamed mono sounds 280 times and bank sounds 4 times: the event 14/71 sounds.
World sound during a scene¶
The engine does not silence the world for a scene. Nothing in the scene task's start (SceneTask_Start,
0x0039d870) or end (SceneTask_End, 0x0039f450) stops, pauses or fades the ambient bed, the ambient emitters or
the other sounds; their sound calls are the soundtrack's wait and start, the system music off and back on, the held
speech stopped (Humans_StopAllSpeech, 0x0021ecd0, a cinematic with roles) and each bound human's and object's
sounds stopped at the end. What a cinematic (scene state 0x0051489c + 0x410 set) changes is a set of volume
factors and the listener, each read every update, so all of it undoes itself the update after the end clears
+0x410. Confirmed (code) at the addresses below:
| What | During a cinematic | Where |
|---|---|---|
| The listener | both listeners are the current camera (the scene camera) for the whole scene: Gm_Level_Update takes the camera's matrix and skips the player-ear position (player 1 + 1.8 m) it uses outside a scene when the audio manager's +0x60 is 0. So emitter ranges, distance attenuation and pan are heard from the scene camera. The same holds while +0x411 is set (not traced) |
0x00158d58, Gm_Level_Update (0x00158728) |
| Ambient emitters | every emitter sound's a8 is the ambient factor 0.75 (Ambient_GetVolume), applied to playing ones each emitter update (once a second) and to new ones as they start; a music emitter's mode-3 loop starts at 0.75 instead of its music duck. Emitters keep starting, stopping and measuring range as usual |
0x0010bf90, 0x0010c100 |
The ambient bed (SoundPlayAmbientTrack) |
unchanged, full volume | none reads +0x410 |
| Directional voices not a player's | × the duck factor 0.2 (+0x3faac): Tasks_Update sets both non-duckable flags for the whole update |
0x00112b10, Task update |
| Speech lines | Human_PlaySpeech / _Cutting play nothing; held lines stopped at the start |
0x0021e458, 0x0021e6f0 |
| Music | × 0.75 (with ducking on); the system music is switched off at the start and back on at the end, only if it was on | 0x0010e5c4, Scenes |
| Radios | volume 0.5 | 0x003ad4fc, Radios |
Runtime (PCSX2 2.9.94, the audio manager's task list read over PINE about every 55 ms; a scratch state in a
level34 cinematic, +0x410 = 3): for the last 6.4 s of the scene the live tasks were the scene soundtrack (class
224, sent at 1.0 per side) and the ambient bed (0x83b1ba40, class 51, record volume 20 %), sent at 0.18 per
side, which is its full 0.2 × 0.9 (sound volume): the bed is not lowered by a cinematic. No emitter sound was
live during the scene (the camera was out of their ranges); after the end, emitter and world sounds started again
within 2 s. Confirmed (runtime).
What silences the world is the scripts. The hub's mission actions (level95.lua, story.RunMissionAction, read
as bytecode) do it around their cutscenes, confirmed (code) for the scripts:
MA_CUTSCENE(a mission's start or end scene played in the hub, the yellow marker's): beforeSuperRunScene,SoundStopAmbientTrack()(the bed fades out over 2 s,AmbientTrack_Stop0x00110c70),ScreenQueueEffect(0, 0.5), and, when the clubhouse'seventsexist,events.Mute(true, true): every Warrior sitting at a chat flag has his sound commands switched off (HuEnableSoundCommands(h, false)) and is shut up (HuShutUp(h, true)), and the workout's Warrior is stopped; the second argument true skips theSoundPauseSound(true)it would otherwise make. Nothing in the action plays the bed again: it comes back when the player next crosses asoundboxvolume (level95), or with the next level.- Entering or leaving the clubhouse (the door actions):
SoundStopAmbientTrack()at the door, thenSoundPauseSound(true)while the other half's script loads (level95_clubhouse/level95_coney); the loaded half's set-up plays its own bed and un-pauses. - At runtime (the same PCSX2,
level95checkpoint 2 reached by setting the level and checkpoint over PINE, player in the clubhouse): no bed was live inside, one loop emitter (class 106) and the Warriors' chat lines (class 142) played, and the next mission scene's soundtrack was already prepared (+0x9c= 0). The cutscene itself was not reached in this pass.
Movies differ: Movie_Play (0x0042a938) stops the music and every sound (0x0010fba8 → 0x00112a90)
before the movie, bed and emitters included, and restarts nothing (Movies); the emitters'
loops come back on their own (mode 3 restarts a loop whenever none plays), the bed only when a script plays it.
What Coney must do (for an implementer; the engine side is repo:src/audio/game_sound.cpp,
repo:src/audio/ambient_emitters.cpp, the stage repo:src/platform/play_level_scene.cpp):
- Listener at the scene camera while a cinematic runs, whatever
SndSetListenerchose: Coney'sGameSound::updateListeneruses the player's ear when the listener mode is 1, also during a scene. During a cinematic both the position and the ears must come from the scene camera's view (0x00158d58). - Keep the bed at full volume during a scene (no engine duck), and the emitters at 0.75 (already built); do not add a blanket mute: the original plays world sound under every scene that no script silences.
- Make the scripts' silencing work in play:
story.RunMissionAction'sMA_CUTSCENEpath must reachSoundStopAmbientTrack(a 2 s fade-out of the bed) before the scene, andevents.Mute(true, true)must reachHuEnableSoundCommands(h, false)andHuShutUp(h, true)for the chat Warriors. If the bed still plays in Coney's hub cutscene (#404), the first thing to check is whether this action runs and whether Coney'sstopAmbientTrackstops the bed that thesoundboxset-up started (the same handle). - Do not restart the bed at the scene's end; it returns only through a script (
soundboxenter or exit) or the next level. - The test (offline mixer, no disc): with the bed and one in-range emitter playing, start a cinematic: the bed's
sent level stays the same, the emitter's falls to 0.75 of it, a non-player directional voice to 0.2; moving the
player 30 m away while the scene camera stays near the emitter keeps it audible (the listener is the camera); a
SoundStopAmbientTrackcall fades the bed to 0 over 2 s; after the end everything returns to its own factors on the next update. A disc test then plays the hub's firstMA_CUTSCENEand checks the bed is stopped and stays stopped through the scene.
What #404 was (Coney's disc test repo:tests/platform/disc_hub_cutscene_audio_test.cpp, hub at checkpoint 2):
not the bed (stopped, 0 frames heard) nor an emitter nor the chat Warriors, but a radio announcement. Walking to
the marker passes the hub's boom box within 5 m, which starts its armed announcement (djlady_02, class 213); the
mission action then ObjDestroys the boom box (it spawns a dyn_s_blaster in its place) and starts the scene.
Coney kept the destroyed object's radio, which could no longer be found, so it was never updated: the announcement
played on at full level through all 1,779 frames of the scene, when the radio's update would have stopped it (state
6 with a scene running). Coney now drops a radio whose object is gone and stops its sound (Radios).
Confirmed (runtime, Coney); the original's own result for that frame is inferred, not traced.
Open: what +0x411 is; the hub cutscene itself at runtime in the original (bed fade, emitter levels from the scene
camera, and whether ObjDestroy of a radio's object stops its sound at once).
The front end and level99¶
- Front end (level 100): bank
menu(14 sounds: the interface cues); music loopsMenuTrack(global.lua:music/in_the_cityonce level 84 is complete; Scripts) andmusic/wonderwheel_132bfromlevel100.lua; UI actions and the cues they play are on Front end. - level99: bank
sound(runtime), sound matrixsound, listener 0; its ambience (emitters such astGulls01,tTrainyard01,tCreakHollowMetal01-03) and speech (vags/speeches/l99/...) are listed in Sound and music;level99_combat.luaconfiguresmusic/155e_fight_1andmusic/stripped_war. The level's script flow is on Scripting.
The PS2 sound device¶
Device/ps2/sound/msaudiodevice.cpp (0x0014ba18-0x0014d508; the code before it, from 0x0014b158, is the memory
card's mcbase.cpp) is the audio manager's platform device: one object at 0x005df1e0 (built by its static
initialiser 0x0014d4d8, pointer 0x0050bce4), vtable 0x00537f88. A slot on this page is an offset into
that vtable; each entry is 8 bytes, {this adjustment, function}. Below it, 0x0014d760-0x00151ed0 is SCEE's
MultiStream library, named by its own error strings (SOUND_PlayStream, SOUND_FlushIOPCommand ...). Confirmed
(code) throughout; the IOP side (IOP.IRX) was not read, so what a command does there is inferred from the EE
side's arguments and callers.
Commands. Each library call appends a command, {id, length, 16-bit words}, to a 2 KB EE buffer
(0x005dfdc0; SOUND_StartCommand, SOUND_AddData, SOUND_EndCommand), under one semaphore. The buffer goes to
the IOP in one RPC per send (SOUND_FlushIOPCommand, 0x0014fae8), which also asks for the IOP's status; the
reply (0x0014ee88) gives each stream's state, position and flags, the SPU2 voices' idle bits and the disc-error
bytes, so the EE learns of a stream's end on a later send (inferred: at least a frame late). The device's update
(slot +0x10, 0x0014c140, each frame) sends without waiting; loading uses a blocking copy (0x0014c250). A
buffer that fills mid-frame is sent at once, waiting for the previous send.
Start-up (slot +0x08, 0x0014bbd8): the IOP connection, a reset (command 7), the stream limit 13, the
fast load thread (an EE RPC server, id 0x12344321, that the IOP asks before it reads the disc, so the game's
own reads and the streams share the drive), then the stream buffers from the constructor's table, in IOP memory and
sound RAM, the sound-RAM cursor starting at 0x5010:
| Streams | Use | IOP buffer | Sound-RAM buffer |
|---|---|---|---|
| 0 | loading banks (.msb into sound RAM, .msd into IOP memory) |
0x60000 |
none |
| 1+2, 3+4 | the stereo pairs: music and scene soundtracks (the child shares the parent's buffers) | 0x20000 |
0x10000 |
| 5-9 | streamed mono sounds | 0x10000 |
0x2000 |
| 10-12 | the small loops (class flag 0x20) |
0x2000 |
0x2000 |
The bank goes after the stream buffers (device +0x844); 0x1c0000 minus the buffers is left for it (+0x834).
.msd indexes get a 4 KB IOP block (+0x884). Then four volume groups at full (0x1000), the effects cleared on
both cores, and cdrom0:\IOP\BFW.SND;1 opened on streams 1-12; music opens cdrom0:\IOP\MUSIC.SND;1 on its pair
when a track starts (slot +0x20). Stream state lives on the EE in 0x005e3ac8 (0 free, 2 starting, 3 reserved
or stopped, 5 stopping); SPU2 voices 13-47 serve bank samples (slot +0xf8), reserved (3) when taken and free once
the IOP reports them idle.
Per voice. A bank sample starts by hash (command 0, or 0x39 from an offset); a stream by
SOUND_PlayStream (command 1) with its file offset and size (command 0x29). Volumes go through
SOUND_SetChannelVolumeSmooth (command 0x60) as 15-bit levels scaled by the voice's group; pitch through command
3. A positional sound (class flag 0x02 or 0x08) feeds the reverb while effects are on (slot +0xb0, command
0x18); every other sound, and the music's streams 1-4, is kept dry (slot +0xb8, command 0x19).
Disc errors (SOUND_HandleCDErrors, 0x00150cf0, each update): when the IOP reports a read error the streams
pause (command 0x37) and the probe file cdrom0:\SLUS_212.15;1 is tried each update until it opens; then they
resume (command 0x35) and the IOP asks for the last stream again. Meanwhile the device sets 0x005e5580 to 4, 5
or 3 by the error (-1, -3, other) for the disc-error message, or, while loading, pushes the error mode
(0x00157930(0x005e5560, 0, n)). Confirmed (code); the readers' side is on File I/O.
The device's and the library's functions, by address (names ours where the library's string gives none):
| Address | Name | Role | Evidence |
|---|---|---|---|
0x0014ba18 |
AudioDevice_Construct |
the device's constructor (vtable 0x00537f88): the 13 streams' buffer sizes (below) |
confirmed (code) |
0x0014bbb0 |
AudioDevice_SetStreamSizes |
stores one stream's four sizes at +0x888 + 16 × stream: mono IOP buffer, pair IOP buffer, SPU2 buffer, pair-child flag |
confirmed (code) |
0x0014bbd8 |
AudioDevice_Init |
slot +0x08: MultiStream start-up, stream buffers in IOP and sound RAM, BFW.SND opened on streams 1-12 (The device) |
confirmed (code) |
0x0014bf80 |
AudioDevice_Nop38 |
slot +0x38: empty |
confirmed (code) |
0x0014bf88 |
AudioDevice_AllocPairBuffer |
allocates a stereo pair's stream buffers (IOP +0x88c, SPU2 +0x890) and advances the SPU2 cursor +0x840 |
confirmed (code) |
0x0014c020 |
AudioDevice_PrintFreeIopRam |
asks the IOP for its free memory (command 0x2d), waits, prints and keeps it (0x0050bcec) |
confirmed (code) |
0x0014c070 |
AudioDevice_AllocMonoBuffer |
allocates a mono stream's buffers (IOP +0x888, SPU2 +0x890) |
confirmed (code) |
0x0014c118 |
AudioDevice_ListsLoaded |
slot +0x160: empty on the PS2 |
confirmed (code) |
0x0014c120 |
AudioDevice_Flush |
slot +0x168: sends the command buffer and waits (0x0014fae8(0)) |
confirmed (code) |
0x0014c140 |
AudioDevice_Update |
slot +0x10, each frame: deferred command 0x3f for flagged voices, disc-error check (sets 0x005e5580 to 3, 4 or 5), command buffer sent without waiting, the debug history (slot +0x118) when 0x0050bce8 is 1 |
confirmed (code) |
0x0014c250 |
AudioDevice_UpdateBlocking |
the update used while loading: as slot +0x10 but on a disc error pushes the error mode (0x00157930(0x005e5560, 0, n)) and waits for the send |
confirmed (code) |
0x0014c358 |
AudioDevice_Nop158 |
slot +0x158: calls an empty function (0x0014c378) |
confirmed (code) |
0x0014c378 |
AudioDevice_Empty |
empty | confirmed (code) |
0x0014c380 |
AudioDevice_DebugHistory |
slot +0x118: one step of the streams' debug history (0x0014c448, 0x0014c3b8) |
confirmed (code) |
0x0014c3b8 |
AudioDevice_DebugPollStreams |
moves the history's 40-entry cursor and reads each of the 13 streams' info | confirmed (code) |
0x0014c448 |
AudioDevice_DebugRecordStreams |
records each stream's state (0 idle, 2, 3) in a 40-entry history (+0x04) |
confirmed (code) |
0x0014c5c0 |
AudioDevice_Command25 |
slot +0x18: IOP command 0x25 with one value (Init passes 0x200); meaning not traced |
confirmed (code) |
0x0014c5e0 |
AudioDevice_PauseAll |
slot +0x60: IOP command 10, every voice paused |
confirmed (code) |
0x0014c600 |
AudioDevice_ResumeAll |
slot +0x68: IOP command 11 |
confirmed (code) |
0x0014c620 |
AudioDevice_LoadBank |
slot +0x30: waits for stream 0, then loads <name>.msb into sound RAM at +0x844 and <name>.msd into IOP memory at +0x884 through stream 0 (./ee_files/%s.msb and .msd in the WAD; partial: the first 64 KB of the .msb), running the blocking update and the loading callback 0x0050b728 while it waits; then registers the index (0x00151dc0) |
confirmed (code) |
0x0014c8b8 |
AudioDevice_SetStreamPriority |
slot +0xd8: for stream channels 5-9 with the flag set, IOP stream priority 0x3f (0x00151990) |
confirmed (code) |
0x0014c8f8 |
AudioDevice_RateToPitch |
slot +0x28: RateToPitch |
confirmed (code) |
0x0014c918 |
AudioDevice_PrimeStream |
slot +0x70: for a real streamed task, preloads its stream without starting it (command 0x2f) |
confirmed (code) |
0x0014c968 |
AudioDevice_StartPrimed |
slot +0x78: starts a primed stream (command 0x2e); -1 for a virtual or bank task |
confirmed (code) |
0x0014c9c0 |
AudioDevice_IsPrimed |
slot +0x80: a real stream's info byte +0x12 is 1; true for bank and virtual tasks |
confirmed (code) |
0x0014ca20 |
AudioDevice_IsPrimedStrict |
slot +0x88: as +0x80 but false for a bank sample |
confirmed (code) |
0x0014ca88 |
AudioDevice_IsStreamActive |
slot +0x140: stream info +0x11 is 1 |
confirmed (code) |
0x0014cab8 |
AudioDevice_IsStreamPlaying |
slot +0x148: stream info +0x11 and +0x12 both 1 |
confirmed (code) |
0x0014cfe0 |
AudioDevice_StopVoice |
slot +0x98: a real task's stream (both of a pair) stopped with command 5, or its SPU2 voice keyed off with command 4 |
confirmed (code) |
0x0014d0c8 |
AudioDevice_FindFreeVoice |
slot +0x100: MS_FindFreeVoice(lo, hi) |
confirmed (code) |
0x0014d0e8 |
AudioDevice_FindFreeBankVoice |
slot +0xf8: a free SPU2 voice of 13-47 |
confirmed (code) |
0x0014d108 |
AudioDevice_FindFreeStream |
slot +0xe0: the first free stream from 5 |
confirmed (code) |
0x0014d128 |
AudioDevice_FindFreeStreamIn |
slot +0xe8: the first free stream in a range |
confirmed (code) |
0x0014d148 |
AudioDevice_ReleaseVoice |
slot +0xf0: frees a reserved SPU2 voice (state 3 → 0) |
confirmed (code) |
0x0014d168 |
AudioDevice_VoiceState |
slot +0x108: the voice's EE-side state once the IOP reports it idle, else -1 (Tasks_Update's "finished" test) |
confirmed (code) |
0x0014d188 |
AudioDevice_VolumeToSpu |
int(v × 16383) & 0x7fff |
confirmed (code) |
0x0014d1b0 |
AudioDevice_SetVolume |
slot +0x50: left and right clamped to ±1, sent with MS_SetChannelVolumeSmooth |
confirmed (code) |
0x0014d288 |
AudioDevice_SetPitch |
slot +0x58: command 3 (voice, pitch word) |
confirmed (code) |
0x0014d2b8 |
AudioDevice_PitchToRate |
slot +0x40: pitch × 48000 / 4096 |
confirmed (code) |
0x0014d2d8 |
AudioDevice_Duration |
slot +0x48: ⌊⌊size × 3.5⌋ / (2 × rate)⌋ × 1000 ms |
confirmed (code) |
0x0014d338 |
AudioDevice_IsValidVoiceCount |
slot +0x110: value below 0x201 |
confirmed (code) |
0x0014d340 |
AudioDevice_SetEffect |
slot +0xa0: MS_EnableEffects(core, type, depth L × 32767, depth R × 32767, delay, feedback); +0xb88 = 1 |
confirmed (code) |
0x0014d398 |
AudioDevice_SetEffectVolume |
slot +0xa8: MS_SetEffectMasterVolume(core, L × 32767, R × 32767) |
confirmed (code) |
0x0014d3e0 |
AudioDevice_VoiceEffectOn |
slot +0xb0: command 0x18: the voice feeds the reverb (for positional sounds while effects are on) |
confirmed (code) |
0x0014d408 |
AudioDevice_VoiceEffectOff |
slot +0xb8: command 0x19: the voice is dry (other sounds, the music's streams 1-4) |
confirmed (code) |
0x0014d428 |
AudioDevice_DisableEffects |
slot +0xc0: command 0x17 on both cores |
confirmed (code) |
0x0014d450 |
AudioDevice_ClearEffects |
slot +0xc8: command 0x14 on both cores (start-up, the load screen) |
confirmed (code) |
0x0014d478 |
AudioDevice_VoiceRegister |
slot +0xd0: command 0x5c on the task's voice (0x00151a98) |
confirmed (code) |
0x0014d498 |
AudioDevice_StreamInfoWord |
slot +0x120: stream info word +0x38 |
confirmed (code) |
0x0014d4c0 |
AudioDevice_UpdateListeners |
slot +0x178: empty on the PS2 |
confirmed (code) |
0x0014d4c8 |
AudioDevice_Nop170 |
slot +0x170: empty |
confirmed (code) |
0x0014d4d0 |
AudioDevice_Nop180 |
slot +0x180: empty |
confirmed (code) |
0x0014d4d8 |
AudioDevice_StaticInit |
constructs the device at 0x005df1e0 |
confirmed (code) |
0x0014d508 |
AudioDevice_StaticInitStub |
static-init stub ending msaudiodevice.cpp |
confirmed (code) |
0x0014d528 |
AudioDevice_OpenMusic |
slot +0x20: opens MUSIC.SND on a stream pair (command 0x1f) and sets the track's range (offset, interleave × blocks × channels; command 0x29) |
confirmed (code) |
0x0014d590 |
AudioDevice_PrepareMusic |
slot +0x128: sizes the pair's buffers to the track's interleave, plays the parent stream muted (looping or once), links the child (SOUND_SetStreamParent_Int / _Child_Int), sets the last block's end and preloads (command 0x2f) |
confirmed (code) |
0x0014d718 |
AudioDevice_StopMusic |
slot +0x138: stops a stream (command 5) |
confirmed (code) |
0x0014d738 |
AudioDevice_StartMusic |
slot +0x130: starts a preloaded stream (command 0x2e) |
confirmed (code) |
0x0014d758 |
AudioDevice_Zero150 |
slot +0x150: returns 0 |
confirmed (code) |
0x0014d760 |
MS_InitIOP |
SOUND_InitIOP: the semaphore, the library's tables cleared, the RPC bound to IOP.IRX |
confirmed (code) |
0x0014d9c8 |
MS_RpcDone |
the RPC's end callback → MS_ReadReply |
confirmed (code) |
0x0014d9e8 |
MS_CallRpc |
sceSifCallRpc of the command buffer (0x005e2dc0 reply, 2 KB), no wait |
confirmed (code) |
0x0014da38 |
MS_StartCommand |
SOUND_StartCommand(id) |
confirmed (code) |
0x0014daa8 |
MS_EndCommand |
SOUND_EndCommand: appends {id, length, data} to the 2 KB buffer, flushing first when full |
confirmed (code) |
0x0014dc88 |
MS_AddShortWord |
SOUND_AddData: a 16-bit word |
confirmed (code) |
0x0014dcf8 |
MS_AddLongData |
SOUND_AddLongData: a 32-bit word |
confirmed (code) |
0x0014dd78 |
MS_AddString |
SOUND_AddString |
confirmed (code) |
0x0014de28 |
MS_PlayStream |
SOUND_PlayStream(source, stream, voice, volL, volR, pitch, flags, ...): command 1; stream 0x80 any free, 0x81/0x82 a free one with or without a flag |
confirmed (code) |
0x0014e148 |
MS_LoadFile |
SOUND_LoadFile(dest, stream, source, address): 0x7f sound RAM, 0x7d IOP memory, 0x7e EE memory, 0x7b; through MS_PlayStream |
confirmed (code) |
0x0014e260 |
MS_FindFreeStream |
SOUND_FindFreeStream: state 0 or 3 |
confirmed (code) |
0x0014e2d8 |
MS_FindStreamInRange |
SOUND_FindFreeStreamRange(lo, hi) |
confirmed (code) |
0x0014e370 |
MS_PlayBankSample |
a bank sample by hash on an SPU2 voice (command 0; volumes, pitch, two flags from the task's +0xe0/+0xe4) |
confirmed (code) |
0x0014e4b0 |
MS_PlaySampleFromOffset |
the same from an offset into the sample (command 0x39) |
confirmed (code) |
0x0014e628 |
MS_SetChannelPitch |
command 3 (voice, pitch) | confirmed (code) |
0x0014e6a0 |
MS_StopStream |
command 5; a value of 64 or more names a voice's stream | confirmed (code) |
0x0014e770 |
MS_StopChannel |
command 4: an SPU2 voice keyed off | confirmed (code) |
0x0014e7d0 |
MS_RequestStatus |
command 9 (value, 0): the status request appended to each send | confirmed (code) |
0x0014e858 |
MS_PauseAll |
command 10 | confirmed (code) |
0x0014e890 |
MS_ResumeAll |
command 11 | confirmed (code) |
0x0014e8c8 |
MS_ResetIop |
command 7 at start-up | confirmed (code) |
0x0014e900 |
MS_InitStreams |
command 0xc (three values and the stream count limit 0x0050bd50) at start-up |
confirmed (code) |
0x0014e998 |
MS_SetMaxStreamLimit |
SOUND_SetMaxStreamLimit(n) (0-47; 13 here) |
confirmed (code) |
0x0014ea28 |
MS_AllocateStreamBuffer |
SOUND_AllocateStreamBuffer(stream, spu, size): command 0xd |
confirmed (code) |
0x0014eb40 |
MS_ResizeStreamBuffer |
SOUND_ResizeStreamBuffer: command 0x47 |
confirmed (code) |
0x0014ec68 |
MS_ResizeSPUBuffer |
SOUND_ResizeSPUBuffer: command 0x52, at least 0x400 bytes |
confirmed (code) |
0x0014ee30 |
MS_SetSpuLoadAddress |
command 0xe: the sound-RAM address for a load (0x0050bd60) |
confirmed (code) |
0x0014ee88 |
MS_ReadReply |
parses the IOP's reply: each stream's state, position and flags, the voices' idle bits (0x0050c518, 0x0050c51c), the disc-error bytes |
confirmed (code) |
0x0014f938 |
MS_FindFreeVoice |
a voice in lo-hi that is free and idle; marks it reserved (3) |
confirmed (code) |
0x0014fa38 |
MS_GetVoiceState |
the voice's state when the IOP reports it idle, else -1 | confirmed (code) |
0x0014fac8 |
MS_FlushWait |
MS_FlushCommands(0) |
confirmed (code) |
0x0014fae8 |
MS_FlushCommands |
SOUND_FlushIOPCommand(noWait): sends the buffer by RPC (waiting for the last unless noWait, then -1 if busy); keeps send statistics |
confirmed (code) |
0x0014fcd8 |
MS_EnableEffects |
SOUND_EnableEffects(core, type, depthL, depthR, delay, feedback): command 0x13; type below 10; picks one of two effect work areas |
confirmed (code) |
0x0014feb0 |
MS_ClearEffect |
command 0x14 (core), clearing its stored type |
confirmed (code) |
0x0014ff20 |
MS_SetEffectMasterVolume |
command 0x15 (core, L, R) |
confirmed (code) |
0x0014ff98 |
MS_ChannelEffectOn |
command 0x18 (voice) |
confirmed (code) |
0x0014ffe0 |
MS_ChannelEffectOff |
command 0x19 (voice) |
confirmed (code) |
0x00150028 |
MS_DisableEffect |
command 0x17 (core) |
confirmed (code) |
0x00150078 |
RateToPitch |
rate × 4096 / 48000 |
confirmed (code) |
0x001500a8 |
MS_OpenStreamFile |
command 0x1f (stream, offset, path): the IOP opens a disc file for a stream; a new id when -1 |
confirmed (code) |
0x00150148 |
MS_SendCommand25 |
command 0x25 (value) |
confirmed (code) |
0x001501a8 |
MS_SetStreamRange |
command 0x29 (stream, offset, size) |
confirmed (code) |
0x00150218 |
MS_Command2A |
command 0x2a (value: 2 and 4 at start-up and after a movie) |
confirmed (code) |
0x00150260 |
MS_Command2A6 |
command 0x2a (6) |
confirmed (code) |
0x001502a0 |
MS_Command3A |
command 0x3a (before a movie) |
confirmed (code) |
0x001502d8 |
MS_Command3B |
command 0x3b (before a movie) |
confirmed (code) |
0x00150310 |
MS_Command3C |
command 0x3c (after a movie) |
confirmed (code) |
0x00150348 |
MS_GetVoiceStream |
the stream a voice is playing, or -1 | confirmed (code) |
0x00150380 |
MS_SetStreamParent |
SOUND_SetStreamParent_Int: command 0x2b |
confirmed (code) |
0x00150448 |
MS_SetStreamChild |
SOUND_SetStreamChild_Int: command 0x2c |
confirmed (code) |
0x00150628 |
MS_RequestFreeIopRam |
command 0x2d |
confirmed (code) |
0x00150660 |
MS_SetMIBEndOffset |
SOUND_SetMIBEndOffset: command 99 |
confirmed (code) |
0x00150768 |
MS_StartPreloaded |
command 0x2e: starts a preloaded stream |
confirmed (code) |
0x001507d0 |
MS_PreloadStream |
command 0x2f: fills a stream's buffer without starting it |
confirmed (code) |
0x00150838 |
MS_GetStreamInfo |
SOUND_GetStreamInfo(stream, info): IOP free memory +0x00, state +0x11 (0 idle, 1 active, 6 starting), primed +0x12, play time +0x42-+0x48 |
confirmed (code) |
0x00150b48 |
MS_NextFileId |
the next stream-file id | confirmed (code) |
0x00150b70 |
MS_SetEeLoadAddress |
command 0x31: the EE address for a load |
confirmed (code) |
0x00150bc8 |
MS_SetIopLoadAddress |
command 0x32: the IOP address for a load |
confirmed (code) |
0x00150c28 |
MS_ResumeAfterDiscError |
command 0x35 |
confirmed (code) |
0x00150c60 |
MS_PauseForDiscError |
command 0x37 |
confirmed (code) |
0x00150ca8 |
MS_SetDiscProbeFile |
keeps the file opened to test the disc (cdrom0:\SLUS_212.15;1) |
confirmed (code) |
0x00150cf0 |
MS_HandleCDErrors |
SOUND_HandleCDErrors: on the IOP's disc error, pauses the streams, and once the probe file opens again resumes them; returns the error |
confirmed (code) |
0x00150ed0 |
MS_Command3E |
command 0x3e (1) at start-up |
confirmed (code) |
0x00150f18 |
MS_Command3F |
command 0x3f (voice), sent after a bank sample's start |
confirmed (code) |
0x00150fc8 |
MS_SetGroupMasterVolume |
SOUND_SetGroupMasterVolume(group, L, R) (0-0x1000; four groups, full at start-up) |
confirmed (code) |
0x001510c8 |
MS_ApplyGroupVolume |
command 2: resends the volumes of a group's voices | confirmed (code) |
0x001511a8 |
MS_SetVoiceGroup |
a voice's group | confirmed (code) |
0x001511c0 |
MS_ScaleByGroup |
a voice's volumes × its group's master volume | confirmed (code) |
0x00151250 |
MS_SetVoiceVolumes |
keeps a voice's volumes and scales them by its group | confirmed (code) |
0x001512d8 |
MS_ScaleVolume |
v × master / 4096, mirrored for inverted-phase volumes |
confirmed (code) |
0x00151360 |
MS_FastLoadThread |
the EE RPC server thread (id 0x12344321) the IOP asks during fast loads |
confirmed (code) |
0x001513d0 |
MS_InitInternalFastLoad |
SOUND_InitINTERNALFastLoad: creates that thread (priority 9, a 32 KB buffer); command 0x51 |
confirmed (code) |
0x001514a8 |
MS_FastLoadRpc |
the server's handler: 999 invalidates a cache range, otherwise asks the game's callback (0x0050c504) for the load mode |
confirmed (code) |
0x00151640 |
MS_SetInternalFastLoad |
SOUND_SetINTERNALFastLoad(mode): command 0x51 |
confirmed (code) |
0x00151750 |
MS_SendCommand53 |
command 0x53 (core) |
confirmed (code) |
0x00151798 |
MS_SendCommand57 |
command 0x57 |
confirmed (code) |
0x001517f0 |
MS_SetChannelVolumeSmooth |
SOUND_SetChannelVolumeSmooth(voice, L, R): command 0x60 |
confirmed (code) |
0x001518c0 |
MS_WriteRegister |
command 0x5c (register, value) |
confirmed (code) |
0x00151930 |
MS_GetLastStatus |
0x0050bd74 |
confirmed (code) |
0x00151980 |
MS_IsRpcBusy |
0x0050bd30 |
confirmed (code) |
0x00151990 |
MS_SetStreamPriority |
SOUND_SetStreamPriority(stream, priority): command 100; 0x100 the default |
confirmed (code) |
0x00151a98 |
MS_SetVoiceRegister |
a value below 32 into a voice's SPU2 register (0x400 block) through command 0x5c |
confirmed (code) |
0x00151bc0 |
MS_LockStart |
SOUND_MultiThreadSafeCheckStart: takes the semaphore |
confirmed (code) |
0x00151c70 |
MS_LockEnd |
SOUND_MultiThreadSafeCheckEnd |
confirmed (code) |
0x00151cd8 |
MS_ScanStreamsFrom |
the first stream from n in state 0 or 3 |
confirmed (code) |
0x00151d28 |
MS_ScanStreamsBetween |
the same within a range | confirmed (code) |
0x00151d88 |
MS_ReleaseVoice |
state 3 → 0 | confirmed (code) |
0x00151dc0 |
MS_RegisterBank |
command 0x74 (index address, size): the loaded .msd |
confirmed (code) |
0x00151e68 |
Gif_RemoveCallback |
removes a callback from the 8-entry list (0x005e4448, Movies) |
confirmed (code) |
0x00151ed0 |
Gif_AddCallback |
adds a callback and its data to that list | confirmed (code) |
Function index¶
The unit's other functions (0x0010d758-0x0011b770), by address; names are ours. The device layer's are under
The PS2 sound device.
| Address | Name | Role | Evidence |
|---|---|---|---|
0x0010d758 |
MusicPlayer_Init |
the music player's constructor: the stream-slot bytes (0x0011b400), mood 3 (none) at +0x1c and +0x20, music volume 1.0, options' music volume 0.9, the empty track list, the three channels and their pointers +0x154-+0x15c, no tracks per mood |
confirmed (code) |
0x0010d830 |
MusicPlayer_AddTrack |
appends one track to the track list (0x0010ed50; MusicList_Load) |
confirmed (code) |
0x0010d850 |
MusicPlayer_AllocTracks |
allocates the track list for n tracks (0x0010edd0) |
confirmed (code) |
0x0010d870 |
MusicPlayer_ConfigTrack |
SndCfgMusicInfo: finds the track by the name's CRC-32 and stores its volume in the record and its bar length |
confirmed (code) |
0x0010d9a0 |
Music_StopTrack |
SoundStopMusicTrack, per channel by state: queued, dropped; pre-loading or blocked, stopped at once; playing or waiting for the bar, a fade-out over one bar of its track from now; fading in, the same fade-out started from the level it has reached |
confirmed (code) |
0x0010db18 |
MusicChannel_Start |
starts a pre-loaded channel: fade level 0 when it fades in, else 1; volumes; device slot +0x130 (start the stream pair); play start = now |
confirmed (code) |
0x0010dbb0 |
MusicChannel_Preload |
state 2: stops the pair (slot +0x138), opens the track of MUSIC.SND on it (slot +0x128, with the loop flag), mutes both sides (slot +0x50), fade level 0 |
confirmed (code) |
0x0010dd50 |
MusicChannel_Stop |
stops an active channel's pair and frees its slot (0x0011b740) |
confirmed (code) |
0x0010ddd8 |
Music_StopAll |
clears the queued request, stops and resets both playing channels, then device slot +0x168 |
confirmed (code) |
0x0010def8 |
Music_IsAnyChannelBusy |
whether any of the three channels is not idle (state +0x11) |
confirmed (code) |
0x0010df28 |
SystemMusic_GetMood |
the playing mood +0x1c |
confirmed (code) |
0x0010df30 |
Music_Update |
each music update: the pending scene soundtrack's re-preload, freeing stereo slots (0x0011b5e8), SystemMusic_Update, the three channels, the volumes; the debug check 0x0010eb40 when 0x0050a8dc is set |
confirmed (code) |
0x0010e468 |
MusicChannel_FadeProgress |
(now - fade start) / fade length, clamped to 0-1 |
confirmed (code) |
0x0010e530 |
MusicChannel_FadeRemaining |
1 - MusicChannel_FadeProgress |
confirmed (code) |
0x0010e710 |
Music_SetVolume |
SoundSetMusicVolume: +0x5c, clamped to 0-1, and the "changed" flag +0x64 |
confirmed (code) |
0x0010e758 |
Music_SetOptionsVolume |
the options' music volume +0x60, clamped to 0-1 |
confirmed (code) |
0x0010e798 |
MusicChannel_IsActive |
channel non-null and its +0x00 set |
confirmed (code) |
0x0010e7b8 |
MusicChannel_IsActiveAt |
whether channel i's +0x00 is set |
confirmed (code) |
0x0010e9e0 |
SystemMusic_SetTracks |
SoundSetMusicTrack(mood, a, b, c): up to three track hashes for a mood and their count; for the playing mood forces a change and turns the system music on (0x0041d3d8-style game-state call) |
confirmed (code) |
0x0010eab0 |
MusicStream_GetStateName |
the state's name (ST_Idle ...), for the debug check |
confirmed (code) |
0x0010eb40 |
Music_DebugCheck |
a debug pass: formats the channels' state names and re-checks the configured tracks, with no visible output; runs only when 0x0050a8dc is set, which nothing writes (0 in the executable) |
confirmed (code); never runs |
0x0010ed20 |
MusicPlayer_FreeTracks |
releases the track list (0x0010ee90) |
confirmed (code) |
0x0010ed40 |
TrackList_Init |
empty list, owner flag 1 | confirmed (code) |
0x0010ed50 |
TrackList_Append |
copies a 16-byte track into the next entry | confirmed (code) |
0x0010ed90 |
TrackList_Find |
index of the track with that name hash, or -1 | confirmed (code) |
0x0010edd0 |
TrackList_Alloc |
allocates n 16-byte tracks (tag MusicTrack) in the global heap; MusicList.cpp's anchor |
confirmed (code) |
0x0010ee90 |
TrackList_Release |
tells the device to release each track (slot +0x20), clears the owner flag |
confirmed (code) |
0x0010ef18 |
MusicTrack_Init |
{record, crc32(name at +0x28), 1, 2000}; adds the track's size to a running total 0x0050a8f0 |
confirmed (code) |
0x0010ef90 |
MusicTrack_Copy |
copies a 16-byte track | confirmed (code) |
0x0010efc0 |
Handle_Set |
stores a word (a sound handle) | confirmed (code) |
0x0010efd0 |
Handle_Copy |
copies a sound handle | confirmed (code) |
0x0010efe0 |
Sound_IsDirectional |
class flag 0x10 |
confirmed (code) |
0x0010f018 |
Sound_IsPositional |
class flag 0x02 or 0x08 |
confirmed (code) |
0x0010f050 |
Sound_UsesReservedStreams |
class flag 0x20 (stream channels 10-12) |
confirmed (code) |
0x0010f080 |
Sound_GetHash |
record +0x08 |
confirmed (code) |
0x0010f098 |
Sound_GetStereoInterleave |
the stereo table record's interleave for this hash, or 0 | confirmed (code) |
0x0010f0e8 |
Sound_GetStereoBlocks |
the stereo record's block count, or 0 | confirmed (code) |
0x0010f138 |
Sound_GetStereoLastBlock |
the stereo record's last-block bytes, or 0 | confirmed (code) |
0x0010f188 |
Sound_GetVolume |
record +0x0d / 100 |
confirmed (code) |
0x0010f1b0 |
Sound_GetNear |
class byte 0, metres | confirmed (code) |
0x0010f1e8 |
Sound_GetFar |
class byte 1 × 5, metres | confirmed (code) |
0x0010f228 |
Sound_GetPitchVariation |
record +0x0c, percent |
confirmed (code) |
0x0010f238 |
Sound_GetRate |
the rate table 0x0050a9e0 at record +0x0e |
confirmed (code) |
0x0010f258 |
Sound_GetSize |
record +0x00 |
confirmed (code) |
0x0010f268 |
Sound_GetOffset |
record +0x04 |
confirmed (code) |
0x0010f278 |
Sound_GetPriority |
class byte 3 | confirmed (code) |
0x0010f2a0 |
Sound_GetChannels |
class byte 4 | confirmed (code) |
0x0010f2c8 |
Sound_IsLooping |
class flag 0x01 |
confirmed (code) |
0x0010f2f0 |
Sound_IsStreamed |
class flag 0x04 |
confirmed (code) |
0x0010f320 |
SoundList_LoadChunk |
chunk 0x29 handler: keeps the chunk at 0x0059867c and hands it to SoundList_Load (0x0010f900) |
confirmed (code) |
0x0010f360 |
MusicList_LoadChunk |
chunk 0x31 handler: 0x00598680, then MusicList_Load (0x0010f9d8) |
confirmed (code) |
0x0010f3a0 |
SoundClasses_LoadChunk |
chunk 0x48 handler: the class table 0x00598670 |
confirmed (code) |
0x0010f3d8 |
StereoTable_LoadChunk |
chunk 0x49 handler: 0x00598688, then StereoTable_Load (0x0010f988) |
confirmed (code) |
0x0010f410 |
SoundAnimData_LoadChunk |
chunk 0x4a (Sound Anim Data) handler: keeps it at 0x0059868c, which no code reads |
confirmed (code); unused inferred |
0x0010f450 |
SoundHandles_FindFree |
the first free slot of the 256-entry handle table 0x00598698 from a start index, or -1 |
confirmed (code) |
0x0010f4b0 |
SoundHandles_FirstFree |
SoundHandles_FindFree(1): slot 0 is never used |
confirmed (code) |
0x0010f4d0 |
SoundHandle_Make |
a handle with the slot in the top 16 bits and the serial (0x00598a98, counting up) in the low 16 for a task, stored in the slot |
confirmed (code) |
0x0010f520 |
SoundHandle_Free |
clears the handle's slot | confirmed (code) |
0x0010f558 |
SoundHandles_Set |
stores a task pointer in a slot | confirmed (code) |
0x0010f578 |
SoundHandle_Resolve |
the task of a handle when its slot holds a task whose first word (the handle) matches, else null; -1 is no sound (154 callers) | confirmed (code) |
0x0010f5c8 |
SoundHandles_StaticInit |
the unit's static initialiser: the null handle 0x00598690 = -1, which the game copies wherever it means "no sound" |
confirmed (code) |
0x0010f5f0 |
SoundHandles_StaticInitStub |
its stub; MusicList.cpp's unit ends here |
confirmed (code) |
0x0010f768 |
AudioManager_InitDefaults |
seeds the audio random (0x006eb8b0) from the timer, picks the first load screen (Random % 7, +0x3fa28) and sets both option volumes to 0.9 |
confirmed (code) |
0x0010f7f8 |
AudioManager_ResetLowPriorityCount |
+0x3fa34 = 0 |
confirmed (code) |
0x0010f900 |
SoundList_Load |
makes the sound list: n 4-byte entries, each a pointer to a 16-byte record of the chunk |
confirmed (code) |
0x0010f978 |
SoundClasses_SetTable |
stores the class table pointer at 0x00598670 (from SoundClasses_LoadChunk) |
confirmed (code) |
0x0010f988 |
StereoTable_Load |
stores the stereo table's count 0x00598678 and records 0x00598674 |
confirmed (code) |
0x0010f9a0 |
AudioDevice_ListsLoaded |
device slot +0x160 after a list is loaded; the PS2 device's is empty (0x0014c118) |
confirmed (code) |
0x0010f9d0 |
Audio_IopCommandHook |
an empty function the IOP command sender calls (0x0014daa8) |
confirmed (code) |
0x0010f9d8 |
MusicList_Load |
makes the track list: per 104-byte record a 16-byte track (MusicTrack_Init) |
confirmed (code) |
0x0010fba8 |
Sound_StopAll |
AudioManager_StopAll on the global manager: from Movie_Play, the pause menu, mode changes |
confirmed (code) |
0x0010fbc8 |
AudioManager_Play2DFadeIn |
plays a 2D sound at volume and pitch 1 with fade mode 1 (in), duckable; returns its handle or the null handle (the ambient track) | confirmed (code) |
0x0010fc30 |
Sound_PlayInterfaceCue |
plays interface cue n of the sound matrix (0x00111130 → 0x00116828) as a 2D sound with flags 0x12 |
confirmed (code) |
0x0010fe30 |
AudioManager_PlayAt |
plays a sound by hash at a position with the default owner (0x006ebd30), no fade; returns the handle |
confirmed (code) |
0x0010fe90 |
AudioManager_PlayOwned |
plays a sound for an owner at a copy of a position (the human speech and grunt calls); returns the handle | confirmed (code) |
0x0010fee8 |
SoundTask_SetFadeLevel |
the task of a handle: fade level +0x78 |
confirmed (code) |
0x0010ff28 |
SoundTask_SetVolume |
the task of a handle: caller's volume +0x74 (SetAmbientTrackVolume and others) |
confirmed (code) |
0x0010ff68 |
Scene_PreloadSoundtrack |
stops a live soundtrack, claims a stream pair or falls back (0x00112d60), prepares the sound and keeps its handle in +0x24268 and the music player's +0x08 (Scene soundtracks) |
confirmed (code) |
0x00110018 |
Scene_StartSoundtrack |
scene event 13: starts whatever task +0x24268 holds and sets +0x2426c to whether it is alive |
confirmed (code) |
0x00110078 |
Scene_StopSoundtrack |
stops the soundtrack's task and clears +0x2426c |
confirmed (code) |
0x001100b0 |
Sound_Prepare2DByName |
Sound_Prepare2D with the name's CRC-32 (the lock-pick dial, Rumble screens) |
confirmed (code) |
0x001100e8 |
Sound_Prepare2D |
makes a 2D task at volume and pitch 1, duckable, without starting it (0x00111f78); returns the handle |
confirmed (code) |
0x00110138 |
Sound_PrepareAt |
the same at a position the task keeps a pointer to | confirmed (code) |
0x00110188 |
Sound_PrepareFull |
AudioManager_Prepare with every argument passed through (Human_PlaySpeech) |
confirmed (code) |
0x001101b8 |
Sound_PrepareAtCopy |
the same as Sound_PrepareAt with a copy of the position |
confirmed (code) |
0x00110210 |
Sound_PrepareAtByName |
Sound_PrepareAtCopy with the name's CRC-32 (WorldObject_PreloadSound) |
confirmed (code) |
0x00110258 |
Sound_StartPrepared |
starts a prepared task by handle (0x00111f40 → 0x0011b298, device slot +0x78); 0 for a dead handle |
confirmed (code) |
0x001102a0 |
Sound_IsPrepared |
a prepared task's stream is primed (device slot +0x80); a bank sound counts as ready, a dead handle as not |
confirmed (code) |
0x00110318 |
Sound_Stop |
stops the task of a handle (AudioManager_StopTask, 0x00112c60), with the "free its stream" flag |
confirmed (code) |
0x00110370 |
Sound_FadeOut |
starts a fade-out of the given length on a task not already fading out (0x0011b1b0) |
confirmed (code) |
0x001103d0 |
Sound_SetPositionAndFacing |
copies a position and facing into a task (+0xc0, +0xd0); false for a dead handle |
confirmed (code) |
0x00110430 |
Sound_SetPosition |
copies a position into a task (+0xc0) |
confirmed (code) |
0x00110488 |
Sound_Nop |
calls an empty function (0x001120c0) |
confirmed (code) |
0x001104a8 |
Sound_CfgMusicInfo |
MusicPlayer_ConfigTrack on the global manager (SndCfgMusicInfo) |
confirmed (code) |
0x001104c8 |
Sound_SetMusicTrack |
SystemMusic_SetTracks on the global manager (SoundSetMusicTrack; mode 1's enter clears three moods with it) |
confirmed (code) |
0x001104e8 |
Sound_LoopMusicTrack |
Music_Play(track, loop 1, no callback, fade bars) |
confirmed (code) |
0x00110508 |
Sound_PlayMusicTrack |
Music_Play(track, loop 0, callback, fade bars) |
confirmed (code) |
0x00110528 |
Sound_StopMusicTrack |
SoundStopMusicTrack (0x0010d9a0) |
confirmed (code) |
0x00110548 |
Sound_StopAllMusic |
Music_StopAll |
confirmed (code) |
0x00110570 |
Sound_GetMusicVolume |
the music volume (music player +0x5c) |
confirmed (code) |
0x00110590 |
Sound_SetMusicVolume |
Music_SetVolume |
confirmed (code) |
0x001105b0 |
Sound_SetOptionsMusicVolume |
the options' music volume, clamped to 0-1, into the music player (0x00112ec0) and game state +0x57ac |
confirmed (code) |
0x00110618 |
Sound_GetOptionsMusicVolume |
the options' music volume (0x00112eb8) |
confirmed (code) |
0x00110638 |
Sound_SetEffectsVolume |
the options' sound-effect volume +0x3fa20, clamped, mirrored at game state +0x57a8 |
confirmed (code) |
0x00110690 |
Sound_LoadMatrix |
SndLoadMatrix on the global manager (SoundMatrix_Load, 0x00114628) |
confirmed (code) |
0x001106b0 |
Sound_VoiceNextLine |
VoiceTable_NextLine through the matrix (0x00114b00) |
confirmed (code) |
0x001106d0 |
Sound_MarkCommandSaid |
for commands 1-6, 0x12, 0x46, 0x56 and 0x9e (and 0x28 while 0x0041d160 is 1): calls 0x00111688 and sets human +0x16c |
confirmed (code) |
0x00110790 |
Sound_PlayMaterialHit |
plays the first sound of a material pair's matrix entry at a position, volume × the entry's first column volume (0x0021f700) |
confirmed (code) |
0x00110830 |
Sound_PlayMaterialPair |
plays columns 2 and 1 of a material pair's entry at a position, each with its column volume; for first material 13 the second plays at pitch 0x003354e0() × 0.2 (16 callers: contacts, objects, footsteps) |
confirmed (code) |
0x00110940 |
Sound_PlayMaterialPairPlain |
the same without the material-13 pitch (0x0021f290) |
confirmed (code) |
0x00110a08 |
Sound_PlayAnimSound |
plays column 1 of an animation sound entry at a position with its column volume | confirmed (code) |
0x00110aa8 |
Sound_RemapFootMaterial |
for the footstep caller (0x0021f290): material 0x12 → 0x6a, 6 → 5, 0x79 → 0x23 at half volume, else unchanged at 1 |
confirmed (code) |
0x00110b18 |
AmbientTrack_PlayByName |
AmbientTrack_Play with the name's CRC-32; returns +0x24274 |
confirmed (code) |
0x00110cc0 |
AmbientTrack_SetVolume |
SetAmbientTrackVolume: the bed task's caller volume, clamped |
confirmed (code) |
0x00110d50 |
Ambient_TryPlay |
forwards to AmbientEmitter_TryPlay (0x0010d480) on the ambient manager +0x24280 |
confirmed (code) |
0x00110d78 |
Sound_FindByName |
Sound_Find with the name's CRC-32 |
confirmed (code) |
0x00110db0 |
Sound_Find |
the sound list record for a hash, or null (0x00111c80); the matrix and voice-table builders use it to skip missing sounds |
confirmed (code) |
0x00110dd0 |
Sound_GetFarByName |
AudioManager_GetFarByHash with the name's CRC-32 |
confirmed (code) |
0x00110e08 |
Sound_GetFarByHash |
AudioManager_GetFarByHash on the global manager |
confirmed (code) |
0x00110e28 |
Ambient_AddSphereEmitter |
forwards to AmbientManager_AddSphereEmitter (0x0010cd68) |
confirmed (code) |
0x00110e58 |
Ambient_AddParticleEmitter |
forwards to AmbientManager_AddParticleEmitter (0x0010ced0) |
confirmed (code) |
0x00110e98 |
Ambient_AddEmitter |
forwards to AmbientManager_AddEmitter (0x0010cf58) |
confirmed (code) |
0x00110ee0 |
Ambient_SetEmitterPoints |
forwards to AmbientEmitter_SetPoints (0x0010d590) |
confirmed (code) |
0x00110f08 |
Ambient_SetEmitterEnabled |
forwards to AmbientEmitter_SetEnabled (0x0010d270) |
confirmed (code) |
0x00110f30 |
Ambient_SetTableSlot |
forwards to AmbientTable_Set (0x0010d3c8): AddAmbientSound |
confirmed (code) |
0x00110f58 |
Ambient_Reset |
forwards to AmbientManager_Reset (0x0010bf40) |
confirmed (code) |
0x00110f80 |
Sound_NewMaterialSlots |
forwards to SoundMatrix_NewMaterialSlots (0x00115cb0) |
confirmed (code) |
0x00110fa0 |
Sound_NewMaterialSound |
forwards to SoundMatrix_NewMaterialSound (0x00115ee8) |
confirmed (code) |
0x00110fc0 |
Sound_SetNumberOfMaterialSlots |
forwards to 0x00116480 |
confirmed (code) |
0x00110fe0 |
Sound_DuplicateSoundMaterials |
forwards to 0x00116438 |
confirmed (code) |
0x00111000 |
Sound_NewAnimSlots |
forwards to 0x00116080 |
confirmed (code) |
0x00111020 |
Sound_NewAnimSound |
forwards to 0x001162a0 |
confirmed (code) |
0x00111040 |
Sound_AllocateCharacterVoices |
forwards to VoiceTable_Build |
confirmed (code) |
0x00111060 |
Sound_SetCommandSoundPercent |
forwards to 0x00115bb0 |
confirmed (code) |
0x00111080 |
Sound_WarnLineHash |
forwards to VoiceTable_WarnLineHash (0x001148e8), for the gangs' warning barks |
confirmed (code) |
0x001110a8 |
Sound_VoiceLineHash |
forwards to VoiceTable_LineHash (0x00114a38) |
confirmed (code) |
0x001110c8 |
Sound_SetEffect |
SoundSetEffect: forwards to AudioManager_SetEffect (0x00113000); mode 1's enter also calls it |
confirmed (code) |
0x001110e8 |
Sound_EnableEffects |
SoundEnableEffects: forwards to AudioManager_EnableEffects (0x001130c8) |
confirmed (code) |
0x00111110 |
Sound_CfgInterfaceSound |
forwards to SoundMatrix_SetCue (0x001167b8) |
confirmed (code) |
0x00111130 |
AudioManager_GetCueTable |
the sound matrix's interface-cue lookup (0x00116828) |
confirmed (code) |
0x00111150 |
Sound_DistanceToListener |
a copy of a point's distance to the nearest player listener (0x00113188), the emitters' range test |
confirmed (code) |
0x00111428 |
AudioManager_StopLoadScreen |
stops both halves; when the left one was real, waits (yielding, updating the audio manager and the device) until its voice is idle (device slot +0x108) |
confirmed (code) |
0x00111558 |
SoundTask_SetPan |
a 2D task's left and right pan gains (+0x50, +0x54) |
confirmed (code) |
0x001115a8 |
AudioManager_GetBankName |
copies the current bank's name (15 chars) | confirmed (code) |
0x001115e0 |
AudioManager_GetPendingBank |
copies the pending bank's name | confirmed (code) |
0x00111618 |
AudioManager_SetPendingBank |
keeps a bank name to load after loading (+0x3fa48) |
confirmed (code) |
0x00111650 |
AudioManager_VolumeHook |
an empty function the material and animation sound players call with their volume factor (left at 1) | confirmed (code) |
0x00111658 |
AudioManager_GetTask |
the task of a handle, or null | confirmed (code) |
0x00111688 |
AudioManager_CullQuietStreamsFwd |
forwards to 0x00113258 |
confirmed (code) |
0x001116a8 |
AudioManager_StaticInit |
the unit's static initialiser: constructs the global audio manager 0x00598aa0 (music player 0x0010d758, matrix 0x001141e0, ambient manager 0x0010bdb0) and sets its handle fields to -1 |
confirmed (code) |
0x001118a0 |
AudioManager_StaticInitStub |
its stub | confirmed (code) |
0x001118c0 |
SoundList_Clear |
count and capacity 0 | confirmed (code) |
0x001118d0 |
SoundList_Alloc |
n 4-byte entries (tag Sound); SoundList.cpp's anchor |
confirmed (code) |
0x00111990 |
SoundList_Append |
copies one record pointer into the next entry | confirmed (code) |
0x001119d0 |
SoundList_Find |
binary search by the record's hash +0x08; index or -1 |
confirmed (code) |
0x00111aa0 |
SoundList_EntryAt |
entry i |
confirmed (code) |
0x00111ab0 |
SoundList_EntryAt2 |
entry i (a second copy) |
confirmed (code) |
0x00111ac0 |
SoundTasks_CreatePool |
the FreeList<SoundTask> (0x0050aa94): 256 tasks of 0xf0 bytes and their free list, in the global heap at start-up; SoundListener.cpp's anchor |
confirmed (code) |
0x00111c30 |
AudioManager_ClearLists |
empties the task list, the sound list and other counters | confirmed (code) |
0x00111c80 |
AudioManager_FindSound |
binary search of the sound list (SoundList_Find), the record or null |
confirmed (code) |
0x00111cc0 |
TaskList_Append |
links a task at the tail of the task list and counts it | confirmed (code) |
0x00111d00 |
AudioManager_FreeTask |
unlinks a task, stops its voice unless already stopped (device slot +0x98), clears it (0x0011a150) and returns it to the pool |
confirmed (code) |
0x00111f40 |
AudioManager_StartTask |
starts a task's voice (0x0011b298); 0 for none |
confirmed (code) |
0x00111f78 |
AudioManager_Prepare |
the same without fade and a8; a bank sound starts at once, a streamed one is only primed (0x0011b128(task, 0)) |
confirmed (code) |
0x001120a0 |
AudioManager_IsTaskReady |
0x0011b3c0: the device's "stream primed" status |
confirmed (code) |
0x001120c0 |
AudioManager_Nop |
empty | confirmed (code) |
0x001129b8 |
AudioManager_UpdateTask |
SoundTask_Update with the manager's listeners |
confirmed (code) |
0x001129e0 |
AudioManager_DeviceEC |
device slot +0xe8 (0x0014d128 → 0x00151d28) |
confirmed (code) |
0x00112a10 |
AudioManager_DeviceFlush |
device slot +0x100 (0x0014d0c8 → 0x0014f938) |
confirmed (code) |
0x00112a40 |
AudioManager_GetFarByHash |
the far distance of the sound with a hash (class byte 1 × 5 m), 0 for hash 0 | confirmed (code) |
0x00112a90 |
AudioManager_StopAll |
stops every live task (0x00111d00), then device slot +0x168 |
confirmed (code) |
0x00112c48 |
AudioManager_MarkNonDuckable |
sets both "a non-duckable sound is playing" flags +0x1d8 / +0x1dc when given true (the cinematic case of Tasks_Update) |
confirmed (code) |
0x00112c60 |
AudioManager_StopTask |
stops a task: a bank sample (stream 999) with the flag also releases its stream slot (0x0011b2f0); a playing task's voice is stopped (device slot +0x98); the task is freed unless +0xe4 is set and it is real |
confirmed (code) |
0x00112d00 |
AudioManager_AddSound |
appends one record pointer to the sound list +0x0c (0x00111990) |
confirmed (code) |
0x00112d20 |
AudioManager_AllocSounds |
allocates the sound list for n sounds (0x001118d0, SoundList.cpp) |
confirmed (code) |
0x00112d40 |
AudioManager_AllocTracks |
MusicPlayer_AllocTracks on +0x64 |
confirmed (code) |
0x00112d60 |
AudioManager_ClaimStereoFallback |
0x0011b558 on the music player: the soundtrack preload's fallback |
confirmed (code) |
0x00112d80 |
AudioManager_AddTrack |
MusicPlayer_AddTrack on +0x64 |
confirmed (code) |
0x00112da0 |
AudioManager_CfgMusicInfo |
MusicPlayer_ConfigTrack on +0x64 |
confirmed (code) |
0x00112dc0 |
AudioManager_SetMusicTrack |
SystemMusic_SetTracks on +0x64 |
confirmed (code) |
0x00112de0 |
AudioManager_LoopMusicTrack |
Music_Play looping |
confirmed (code) |
0x00112e08 |
AudioManager_PlayMusicTrack |
Music_Play once with a callback |
confirmed (code) |
0x00112e30 |
AudioManager_StopMusicTrack |
0x0010d9a0 on +0x64 |
confirmed (code) |
0x00112e50 |
AudioManager_StopAllMusic |
Music_StopAll on +0x64 |
confirmed (code) |
0x00112e70 |
AudioManager_UpdateMusic |
Music_Update on +0x64 |
confirmed (code) |
0x00112e90 |
AudioManager_GetMusicVolume |
+0xc0 (music player +0x5c) |
confirmed (code) |
0x00112e98 |
AudioManager_SetMusicVolume |
Music_SetVolume on +0x64 |
confirmed (code) |
0x00112eb8 |
AudioManager_GetOptionsMusicVolume |
+0xc4 (music player +0x60) |
confirmed (code) |
0x00112ec0 |
AudioManager_SetOptionsMusicVolume |
Music_SetOptionsVolume on +0x64 |
confirmed (code) |
0x00112ee0 |
SoundTask_RandomVolume |
for priority-21 sounds a random factor 1 ± Random_Int(20) / 100 (the sign from a second draw); else 1 |
confirmed (code) |
0x00113000 |
AudioManager_SetEffect |
the reverb: device slot +0xa0 on cores 0 and 1 (IOP command 0x13: type, depth on both sides, delay, feedback), then 0x00113118 |
confirmed (code) |
0x001130c8 |
AudioManager_EnableEffects |
keeps the flag at +0x1c4; off also turns the effect off at once (device slot +0xc0, IOP command 0x17 on both cores) |
confirmed (code) |
0x00113118 |
AudioManager_SetEffectVolume |
device slot +0xa8 on both cores (IOP command 0x15: the effect's return volume, the depth on both sides) |
confirmed (code) |
0x00113188 |
Listeners_NearestDistance |
the distance from a point to the nearest player camera's listener (players +0x224 of the game state), and that listener's index; 10,000 when none |
confirmed (code) |
0x00113258 |
AudioManager_CullQuietStreams |
stops every real, streamed, mono sound of priority 8 or more whose last left and right volumes are both 0.2 or less: run whenever a command line is said, so it finds a stream channel | confirmed (code) |
0x00113340 |
Snd_FadeOut |
SndFadeOut(handle, ms) → Sound_FadeOut |
confirmed (code) |
0x00113370 |
Audio_SetPitchMod |
SndSetPitchMod: the global pitch factor +0x3fa5c |
confirmed (code) |
0x00113390 |
Audio_EnableMusicDuck |
SndEnableMusicDuck: +0x3fab4 |
confirmed (code) |
0x001133b0 |
Audio_SetNIDuck |
SndSetNIDuck: +0x3faac |
confirmed (code) |
0x001133d0 |
Sound_LoadBank |
SndLoadBank: loads at once, or while +0x3fa58 is set keeps the name pending |
confirmed (code) |
0x00113438 |
Snd_CfgMusicInfo |
SndCfgMusicInfo: clamps the volume to 0-1, then Sound_CfgMusicInfo |
confirmed (code) |
0x00113490 |
Snd_LoadMatrix |
SndLoadMatrix → Sound_LoadMatrix |
confirmed (code) |
0x001134b8 |
Snd_SetMusicVolume |
SoundSetMusicVolume → Sound_SetMusicVolume |
confirmed (code) |
0x001134e0 |
Snd_PlayMusicTrackHash |
SoundPlayMusicTrack by hash, fade bars 1 |
confirmed (code) |
0x00113510 |
Snd_PlayMusicTrack |
SoundPlayMusicTrack by name, fade bars 1 |
confirmed (code) |
0x00113560 |
Snd_LoopMusicTrackHash |
SoundLoopMusicTrack by hash |
confirmed (code) |
0x00113590 |
Snd_LoopMusicTrack |
SoundLoopMusicTrack by name |
confirmed (code) |
0x001135e0 |
Snd_StopMusicTrack |
SoundStopMusicTrack |
confirmed (code) |
0x00113608 |
Snd_PlayAmbientTrack |
SoundPlayAmbientTrack → AmbientTrack_PlayByName |
confirmed (code) |
0x00113630 |
Snd_StopAmbientTrack |
SoundStopAmbientTrack → AmbientTrack_Stop |
confirmed (code) |
0x00113658 |
Snd_SetAmbientTrackVolume |
SetAmbientTrackVolume → AmbientTrack_SetVolume |
confirmed (code) |
0x00113680 |
Audio_PlaySoundAt |
SoundPlay(name, pos): PlaySound3DByHash at volume and pitch 1, duckable |
confirmed (code) |
0x00113700 |
Audio_PreloadSoundAt |
SoundPreLoad(name, pos): Sound_PrepareAtCopy with 0x00512c14 set for the call, which spares the sound the load screen's virtual rule |
confirmed (code) |
0x00113780 |
Audio_StartPreloadedSound |
SoundStart(handle) → Sound_StartPrepared |
confirmed (code) |
0x001137a8 |
Snd_Play2D |
SoundPlay2D(name): PlaySound2DByHash with flags 0, 0 and duckable |
confirmed (code) |
0x001137e8 |
Snd_Stop |
SoundStop(handle) → Sound_Stop |
confirmed (code) |
0x00113810 |
Snd_AddAmbientSound |
AddAmbientSound(slot, name) → Ambient_SetTableSlot |
confirmed (code) |
0x00113840 |
Sound_AddAmbientEmitter |
AddAmbientSoundEmitter: the sound name's hash (0 for none) → Ambient_AddParticleEmitter |
confirmed (code) |
0x00113920 |
Snd_AddAmbientSoundEmitter2 |
AddAmbientSoundEmitter2: the sound name's hash (0 for none) → Ambient_AddEmitter |
confirmed (code) |
0x00113a08 |
AmbientSound_AddStatResponse |
→ Ambient_AddSphereEmitter (effects bindings) |
confirmed (code) |
0x00113a58 |
Snd_SetAmbientEmitterPositions |
SetAmbientEmitterPositions(emitter, p1-p5, n): up to 5 points (w = 1) → Ambient_SetEmitterPoints |
confirmed (code) |
0x00113b28 |
Snd_AddAmbientEmitter |
AddAmbientEmitter: a particle emitter (range -1) → Ambient_AddParticleEmitter |
confirmed (code) |
0x00113b78 |
AmbientSound_AddOneOff |
→ Ambient_AddParticleEmitter with one sound, played once |
confirmed (code) |
0x00113bc8 |
Audio_EnableAmbientEmitter |
EnableAmbientEmitter → AmbientManager_EnableEmitter |
confirmed (code) |
0x00113c00 |
Sound_SetAmbientEmitterVolume |
SetAmbientEmitterVolumeMod → AmbientEmitter_SetVolume |
confirmed (code) |
0x00113c30 |
Snd_PlayAmbientEmitter |
PlayAmbientEmitter(emitter) → Ambient_TryPlay |
confirmed (code) |
0x00113c58 |
Snd_NewMaterialSlots |
NewMaterialSlots → Sound_NewMaterialSlots |
confirmed (code) |
0x00113c98 |
Snd_NewMaterialSound |
NewMaterialSound → Sound_NewMaterialSound |
confirmed (code) |
0x00113ce8 |
Snd_DuplicateSoundMaterials |
DuplicateSoundMaterials → Sound_DuplicateSoundMaterials |
confirmed (code) |
0x00113d18 |
Snd_SetNumberOfMaterialSlots |
SetNumberOfMaterialSlots → Sound_SetNumberOfMaterialSlots |
confirmed (code) |
0x00113d50 |
Snd_NewAnimSlots |
NewAnimSlots → Sound_NewAnimSlots |
confirmed (code) |
0x00113d88 |
Snd_NewAnimSound |
NewAnimSound → Sound_NewAnimSound |
confirmed (code) |
0x00113dd0 |
Snd_AllocateCharacterVoices |
SndAllocateCharacterVoices → Sound_AllocateCharacterVoices |
confirmed (code) |
0x00113df8 |
Snd_SetCommandSoundPercent |
SndSetCommandSoundPercent → Sound_SetCommandSoundPercent |
confirmed (code) |
0x00113e30 |
Snd_DisableCombatMusic |
SoundDisableCombatMusic: the system music off (GameState_SetSystemMusic(0)) |
confirmed (code) |
0x00113e60 |
Sound_SetSystemMusicState |
SoundSetSystemMusicState(mood) → GameState_SetMusicMood |
confirmed (code) |
0x00113ea8 |
Sound_EnableSystemMusic |
SoundEnableSystemMusic → GameState_SetSystemMusic |
confirmed (code) |
0x00113ed0 |
Snd_SetMusicTrack |
SoundSetMusicTrack(mood, a, b, c): the non-empty names' hashes → Sound_SetMusicTrack, then the system music on and the music player's +0x68 (manager +0xcc) set |
confirmed (code) |
0x00113ff0 |
Snd_SetMusicStateCallback |
SoundSetMusicStateCallback → GameState_SetMusicStateCallback |
confirmed (code) |
0x00114018 |
Snd_SetListener |
SndSetListener(n): the audio manager's +0x60 |
confirmed (code) |
0x00114028 |
Snd_SetEffect |
SoundSetEffect(type, depth, delay, feedback) → Sound_SetEffect |
confirmed (code) |
0x00114060 |
Snd_EnableEffects |
SoundEnableEffects(on) → Sound_EnableEffects |
confirmed (code) |
0x00114088 |
Audio_PauseSound |
SoundPauseSound(on) → AudioManager_Pause / _Resume |
confirmed (code) |
0x001140d0 |
Sound_SetSoundVolume |
SoundSetSoundVolume: returns at once |
confirmed (code) |
0x001140d8 |
Sound_PlayHumanCommand |
SoundPlayCommand → Human_SayCommand |
confirmed (code) |
0x00114178 |
Snd_PreLoadScene |
SoundPreLoadScene(name) → Scene_PreloadSoundtrack with its hash |
confirmed (code) |
0x001141b0 |
Snd_CfgInterfaceSound |
SoundCfgInterfaceSound → Sound_CfgInterfaceSound |
confirmed (code) |
0x001141e0 |
SoundMatrix_Construct |
zeroes the three pools' fields | confirmed (code) |
0x00114220 |
SoundMatrix_Init |
allocates the matrix pools: 3,000 sound-hash words, 1,200 material records and 150 animation records of 28 bytes; clears both tables, name sound, then 0x00116848 |
confirmed (code) |
0x00114528 |
SoundMatrix_Reset |
returns every pool entry and clears the material and animation tables | confirmed (code) |
0x00114628 |
SoundMatrix_Load |
SndLoadMatrix(name): when the name differs from the current one (+0x23d64), resets the matrix, keeps the name and runs the script <name>_preload |
confirmed (code) |
0x001146c0 |
SoundMatrix_GetMaterialSounds |
a material pair's next alternative: up to three hashes and the three column volumes; a second material of 0 or 1 uses the caller's default, and an empty entry falls back to it; the alternatives play in turn (cursor byte 2, wrapping at byte 0) | confirmed (code) |
0x00114800 |
SoundMatrix_GetAnimSounds |
an animation sound's next alternative (cursor byte 1), hashes and volumes | confirmed (code) |
0x001148e8 |
VoiceTable_WarnLineHash |
the name of a gang warning line, its hash when the sound exists, else 0 | confirmed (code) |
0x00114a38 |
VoiceTable_LineHash |
the hash of vags/character/voices/<set>/<command>_<NN> when it exists and is not blocked (VoiceTable_IsBlocked), else 0 |
confirmed (code) |
0x00114b00 |
VoiceTable_NextLineFwd |
forwards to VoiceTable_NextLine |
confirmed (code) |
0x00115bb0 |
VoiceTable_SetCommandPercent |
SndSetCommandSoundPercent(set, command, percent): byte 2 of the entry; set -1 for every set |
confirmed (code) |
0x00115c30 |
SoundMatrix_ClearMaterials |
clears the 191 × 191 material table | confirmed (code) |
0x00115c80 |
SoundMatrix_ClearAnims |
clears the 162-entry animation table | confirmed (code) |
0x00115cb0 |
SoundMatrix_NewMaterialSlots |
NewMaterialSlots(m1, m2, count, columns, v1, v2, v3): takes a record, count hash words per column (1-3), zeroes them, stores the three volumes; the last three floats are not read |
confirmed (code) |
0x00115ee8 |
SoundMatrix_NewMaterialSound |
NewMaterialSound(i, m1, m2, s1, s2, s3): sets alternative i's columns to the names' hashes (none stores 0, nil leaves it) |
confirmed (code) |
0x00116080 |
SoundMatrix_NewAnimSlots |
NewAnimSlots(event, count, columns, v1, v2, v3): as the material version, for the animation table (cursor byte 1) |
confirmed (code) |
0x001162a0 |
SoundMatrix_NewAnimSound |
NewAnimSound(i, event, s1, s2, s3) |
confirmed (code) |
0x00116438 |
SoundMatrix_DuplicateMaterials |
DuplicateSoundMaterials(a, b): entry [b][a] = entry [a][b] (the pair works both ways) |
confirmed (code) |
0x00116480 |
SoundMatrix_SetMaterialCount |
SetNumberOfMaterialSlots(m1, m2, n): alternatives used, cursor reset |
confirmed (code) |
0x001167b8 |
SoundMatrix_SetCue |
SoundCfgInterfaceSound(n, name): cue n's hash (+0x23cc4) |
confirmed (code) |
0x00116828 |
SoundMatrix_GetCue |
cue n's hash |
confirmed (code) |
0x00116848 |
DjLines_Init |
fills the three tables of the DJ's failure lines | confirmed (code) |
0x00116c98 |
DjLines_SetCheckpointEntry |
one entry of the per-checkpoint table (level, checkpoint, count, kind), cursor 1 | confirmed (code) |
0x00116cd8 |
DjLines_SetLevelEntry |
one entry of the per-level table (level, count, kind) | confirmed (code) |
0x00116d10 |
DjLines_SetGenericEntry |
one entry of the generic table (count, kind) | confirmed (code) |
0x00116d48 |
DjLines_CheckpointLine |
the hash of dj_l<level>_c<cp>_<kind>_<NN> for a matching entry, NN its cursor, which then moves on (1 to count, in turn); 0 when none matches |
confirmed (code) |
0x00116ec0 |
DjLines_LevelLine |
the same for dj_l<level>_<kind>_<NN> |
confirmed (code) |
0x00116fe8 |
DjLines_GenericLine |
dj_<kind>_<NN> with NN random in 1-count (0x003353f0) |
confirmed (code) |
0x001170e8 |
DjLines_PickFailureLine |
the failure line for (level, checkpoint, kind), in the order of DJ failure lines | confirmed (code) |
0x00117190 |
Sound_PlayAtDefault |
PlaySound3DByHash at a copy of a position, volume and pitch 1, duckable (seven object-behaviour callers) |
confirmed (code) |
0x001171d8 |
Sound_StopFwd |
Sound_Stop on the global manager; returns 1 |
confirmed (code) |
0x00117208 |
Sound_SetPositionFwd |
Sound_SetPosition with a copy of the position |
confirmed (code) |
0x00117238 |
Sound_PlayAtDefault2 |
the same as Sound_PlayAtDefault (17 object-behaviour callers) |
confirmed (code) |
0x00117280 |
Sound_PlayMaterialPairAt |
Sound_PlayMaterialPair(1.0, a, b, pos, default 5): glass and other objects |
confirmed (code) |
0x001172c8 |
Sound_AddScriptEmitter |
adds an emitter for an object behaviour: kind 0 with the hash of vags/ambient/alarms/alarmbell_loop (0xfae63afd) is an alarm_emitter, kind 1 a script_emitter, else none; range -1, one sound |
confirmed (code) |
0x001173e8 |
Sound_DisableEmitter |
switches an emitter off (AmbientManager_EnableEmitter(…, 0)) |
confirmed (code) |
0x00117420 |
Sound_SetEmitterEnabled |
Ambient_SetEmitterEnabled on the global manager (object behaviours) |
confirmed (code) |
0x00117450 |
PanTables_Build |
fills the pan-mode-1 gain tables at reset: left 0x005d8600 and right 0x005d8ba4, 361 entries each, left[a] = cos(45° + a/2), right[a] = sin(45° + a/2) for a = 0-360 (the two at a = 90 / 270 stored as 0) |
confirmed (code) |
0x00119f78 |
MusicChannel_Init |
a channel's defaults: inactive, track -1, pair byte 0xff, loop 1, fade bars 1, state idle, fade length 2,000 ms, no callback |
confirmed (code) |
0x00119fc0 |
MusicChannel_Copy |
copies a request into a channel (track, pair, loop, fade bars, callback), state idle | confirmed (code) |
0x0011a038 |
MusicChannel_Set |
fills a request: active, track index, loop, fade bars, callback name (32 chars) | confirmed (code) |
0x0011a0a8 |
SoundTask_Init |
a fresh task: a new handle (SoundHandle_Make at the first free slot), defaults (fade level 1, fade length 5,000 ms, volumes and pitch factors 1, duckable, a8 1) |
confirmed (code) |
0x0011a150 |
SoundTask_ReleaseHandle |
frees the task's handle slot | confirmed (code) |
0x0011b108 |
SoundTask_GetRecord |
copies the task's record pointer | confirmed (code) |
0x0011b128 |
SoundTask_StartVoice |
+0x9c = start; device slot +0x90 (set up the voice, 0x0014caf8), and when not starting, slot +0x70 (prime the stream) |
confirmed (code) |
0x0011b1b0 |
SoundTask_SetFade |
fade mode 1 (in): level 0 from now; mode 2 (out): from the current level when fading in (start time moved so the level carries on), else from 1; other modes stored as given | confirmed (code) |
0x0011b298 |
SoundTask_Start |
starts a prepared voice (device slot +0x78), +0x9c = 1; true when the device accepted it |
confirmed (code) |
0x0011b2f0 |
SoundTask_ReleaseStream |
+0xe4 = flag, then device slot +0xd0 |
confirmed (code) |
0x0011b330 |
SoundTask_SetVirtual |
+0x4c virtual, +0x44 length, +0x48 start = now |
confirmed (code) |
0x0011b350 |
SoundTask_VirtualDone |
a virtual task has ended: never for a loop; else when its length has passed or it is stopped (state 3) | confirmed (code) |
0x0011b3c0 |
SoundTask_IsPrimed |
device slot +0x80 |
confirmed (code) |
0x0011b3f8 |
SoundTask_SetOwnerFlag |
task +0xec (the owner's human +0x16c) |
confirmed (code) |
0x0011b400 |
StereoSlots_Init |
both stereo slot bytes free, pending hash 0, soundtrack handle -1 | confirmed (code) |
0x0011b450 |
StereoSlots_Claim |
the preload's claim: refused (-1) when either slot holds a stereo sound and the claim is for one; slot 0 if free, else 1 if free, else -1 | confirmed (code) |
0x0011b498 |
StereoSlots_Take |
making a task or a music channel: slot 0 if free, else 1; both taken: -1 while a started soundtrack lives or a cinematic runs, else a slot holding a stereo sound is taken over (0x0011b6c0) |
confirmed (code) |
0x0011b558 |
StereoSlots_Fallback |
when the claim fails: a live soundtrack is stopped and its slots freed (true); otherwise the hash becomes pending (false) | confirmed (code) |
0x0011b5e8 |
StereoSlots_FreeDead |
frees every slot marked stereo when the task in +0x08 is dead |
confirmed (code) |
0x0011b660 |
StereoSlots_FreeStereo |
frees every slot marked stereo | confirmed (code) |
0x0011b6c0 |
StereoSlots_Evict |
the current soundtrack's hash becomes pending and its task is stopped | confirmed (code) |
0x0011b740 |
StereoSlots_Set |
sets slot i's user byte |
confirmed (code) |
0x0011b750 |
StereoSlots_PairOf |
stream pair 1 for slot 0, else 3 | confirmed (code) |
0x0011b760 |
StereoSlots_BothMusic |
both slot bytes are 1 (music) | confirmed (code) |
Game-state functions¶
Functions of the game-state module (0x00417af0-0x00424e50: inventory, statistics, flags, configuration
workers) that belong to this page, by address.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x00419030 / 0x00419108 |
Breathing_Start / Breathing_Stop |
starts the 2D loop (handle +0x234) at volume 0 rising to 1.0, or turns a fading one round; stops it and clears the state +0x248 |
confirmed (code) |
0x00419150 |
Breathing_Update |
each frame (from 0x0041a370): while the camera frames combat (Camera_IsCombatFraming) the loop fades in over +0x250 ms; once it does not, it fades out over +0x254 ms and stops at 0 |
confirmed (code) |
0x004193e8 |
Breathing_Configure |
CfgBreathingSound's worker: game state +0x258 the sound hash, +0x250 fade-in ms, +0x254 fade-out ms |
confirmed (code) |
0x00419fc8 |
GameState_SetMusicMood |
the system music's mood +0x40c; mood 4 switches the system music off and stores 3 |
confirmed (code) |
0x0041a060 |
SystemMusic_UpdateMood |
every 30th update, and from GameState_SetSystemMusic and Gang_SetAlertState: with system music off (+0x3f8 0) mood 4; otherwise, unless locked (+0x3f4) outside mood 3, picks a mood from player 1's gang and the nearest other gang: 1 (combat) when one of that gang's members fights ours, or when it is farther than both radii + 30 m but at least CfgGangSizeForCombatMusic strong (0x005148a8) and in sight of a player; 2 when our gang is hunted (Gang_IsBeingHunted); else 0 |
confirmed (code); the reading of each test inferred |
0x0041b4c0 |
GameState_SetMusicStateCallback |
the name at +0x384; from the set-up and Snd_SetMusicStateCallback |
confirmed (code) |
0x0041d480 |
Cfg_SetBreathingSound |
CfgBreathingSound(v, in, out, name): the sound's hash and fades to Breathing_Configure, v to +0x24c |
confirmed (code) |
0x0041d920 / 0x0041d930 |
Cfg_SetGangSizeForCombatMusic / Cfg_SetDisableMusicForScenes |
CfgGangSizeForCombatMusic (0x005148a8, read by SystemMusic_UpdateMood) / CfgDisableMusicForScenes (0x005148ac) |
confirmed (code) |
Coney's implementation¶
src/audio/, from this page and Audio data:
SoundEngine(sound_engine.cpp): the audio manager: 256 tasks, the admission, voice and stealing rules, virtual plays and their lengths,SoundTask_Update's fades, distance attenuation, two-ear pan, ducking and rate; banks (decoded to PCM at load), the load screen's banks and halves, the ambient bed, interface cues and scene soundtracks as above: stereo sounds on pairs 1+2 and 3+4, shared with the music through its channels' slots (a music channel at index 0 or 1 holds slot 0 or 1), the claim, the pending soundtrack prepared by a later update, the take-overs both ways, event 13 starting whatever is prepared, the cinematic's start waiting for it (sceneSoundReady(), stopping the music when it holds both pairs), and, while a cinematic runs, the music ducked and duckable directional sounds of non-players at 0.2. A task whose voice the mixer no longer plays ends at the next update, whatever its stream, and a movie stops the music and every task through the engine before Bink's sound starts (Movies).MusicPlayer(music_player.cpp): the three channels and their states, bar-synchronised starts and cross-fades, the volumes and the system music's moods.StreamFeeder(sound_stream.cpp): decodesBFW.SNDandMUSIC.SNDstreams a little each step, mono or block-interleaved stereo, keeping each stream two seconds ahead of its voice: the game thread fills them once a frame, and a stalled frame (0.2-1.1 s on a busy machine in real-time play) emptied the old half-second ring, which cut streamed speech and ambient loops short.Mixer: the game's voices as the device takes them: a 15-bit level per side and the SPU2 pitch word, mixed at 48 kHz; SDL3's device or, in test mode, offline (Running Coney).SoundPlayer: what game code calls (play by name hash, 2D or at a position, stop, pause);engine()for banks, music, the load screen and scenes. Main gives it the engine when a disc is given.ObjectSounds(repo:src/audio/object_sounds.h): the glass panes', doors' and barriers' sounds: a name hash as a positional sound at the object throughSoundPlayer, a material pair from the sound matrix (World objects).SoundMatrix(repo:src/audio/sound_matrix.h) andMaterialSoundPlayer(repo:src/audio/material_sounds.h): the sound matrix as the preloads'NewMaterialSlots,NewMaterialSound,SetNumberOfMaterialSlots,DuplicateSoundMaterials,NewAnimSlotsandNewAnimSoundfill it (kept byGameSound;SndLoadMatrixempties it for a new name), its lookups (alternatives in turn, the default material's fallback) and its players (a pair's columns 2 then 1, a hit's column 1, an animation sound's column 1, the footstep remap) into the engine. A level started directly (--play-level) gives its preloads the sound before they run, as the story does. Coney's stand-in:CAR_HOOD's varied pitch is not applied (0x003354e0is not on this page).GameSound(game_sound.cpp): the game's sound as the rest of the game drives it. The sound bindings (repo:src/scripting/sound_bindings.cpp: the preloads' configuration, the ambience, the music,SndSetListener,HuSpeak,HuSpeakNI,HuShutUp,SoundPlayCommand) reach it through the binding context; the front end's bank, music and cues throughFrontEndAudio; gameplay (mode 1) tells it of its enter (the defaults of Entering gameplay), the load screen's start and end, and its exit (every sound and the music stopped, the level's emitters and lines forgotten). Each frame it puts the listener at player 1's camera (SndSetListener(1): at player 1, 1.8 m above his feet), runs the ambient emitters and moves each line to its speaker; when a line ends it calls the line's script callback.- The humans' sounds (
HumanSoundEvents,repo:src/audio/human_sound_events.h, from Sound events): a human keeps the type-11 clip events his animation passes (the newest task's clip, a gait blend's leading clip, none from a task with flag0x10) and the hits he takes in an outbox (repo:src/human/human_sounds.h); gameplay hands each scripted human's toGameSoundevery step with what the sounds read of him (player or not, the ground under him, his heading, his target).HumanSoundEventsplaysHuman_OnAnimSoundEvent's table: footsteps and body falls on the remapped ground, the fixed kick pairs, the cloth and swooshes, the fence rattles, the spray loop, the vocal ids as his lines (a woman's from her entries) and the speech-command ids throughSpeech; andHuman_PlayImpactSoundfor a landed strike, the striking material from the limb and strength (Hit_ResolveBlock's tables), the struck oneDEADon the ground,BLOCKwhen blocking or ducking,HEADfor a boss or a high hit, elseTORSO; a charging attacker's strike addsHUMANagainstHUMAN. A human's strike shapes on the level (FISTagainst the triangle met, at 116, once while the shapes stay on, unless his bits0x400800or anims 2, 4 and0x1b2keep it quiet) and on a pane or door (against its type's material), and player 1's object attack on a car (CAR_HOODat 0.5 when a part was reached), sound as his,HUMANinstead ofFISTwhile he charges (Sound events). The SA id is the event's 32-bit word at+8. A scene's role clip sends its event 11 the same way: the human's animation sound at the place the scene holds him and on the ground there (a ray from 1 m above his feet, 1.5 m down; Coney's reading, as the scene poses him without moving his body); a stand-in the stage draws has no body and stays silent. Player 1's combat framing is the follow camera's lock-on test (L1 held at his fight target, not in a grab), so his sounds add column 3 under it, and the angry breathing (CfgBreathingSound's sound, a 2D loop) fades in over the first time while it holds and out over the second after, stopping at 0 (linearly on the engine's clock: Coney's choice). A disc test ([disc][story][audio]) plays STORY into level99's lessons 1-6 and checks the hints' cue, thel99_t1humans' shuffles and the lock-on's breathing are asked for, as a differential run against the PS2 found them missing. Coney's stand-ins: the striking limb comes from the strike clip's name (kick,stomp,kneea foot;headbuttthe head; else a hand), not the striking shape's bone; nobody is hidden in shadow, burning or holding a world object, and only the scripts' humans report sounds; there are no punch bags (BAG); a strike shape meets the level when its segment, as a ray, crosses a level triangle, without the turn limits and state0x4000000; a pane sounds asGLASS; a thrown human against the level (Human_OnContact) does not sound yet. VoiceTable(voice_table.cpp, The voice table) andSpeech(speech.cpp, Saying a speech command): one line per human at a time, positional and directional at him.Speechkeeps the human's "may be cut" flag and callback as Cutting in on a line says: the two starts (Start::WaitingforHuSpeak, the grunts and commands said uncut;Start::CuttingforHuSpeakNI,die,expireand commands said cutting), the one stop (shutUpwithforce:HuShutUppasses its argument, every other caller stops unforced) and the speech update that runs a gone line's callback once. A grunt over aHuSpeakNIline is silent and the line plays on, which is what lets mission 2's last line (FadeOut) end.AmbientEmitters(ambient_emitters.cpp, Ambient emitters): both binding forms (the older one namedparticle task; the same name, sound and first point is the existing emitter, switched on), modes 1-5, the range frompos1, the plays, the delays, the points, the volume, the name kinds, the filter on player 1's covered ground (his last ground snap's triangle flag0x20), the ambient factor and themusicduck, once a second and at most two new sounds an update. Coney's stand-ins: one listener (the game's) and one player; the fight timer is on while the music's mood is the fight; the_DAM_window opens 2 s after a strike on a world object or a car (each reports an AI noise,AI_ReportNoise, which Coney reads as always stamping the event; a stamp already set puts the new one 2 s back), and the stamp is forgotten when gameplay is left; mode 7 (no script makes one) and the0x005147c8block are not modelled.- System music (
repo:src/gamemodes/system_music.cpp, Music): on a mood change a random track of the mood loops through the music player, cross-faded on the bar as0x0010e7d0does (a cut into the fight, 4 bars back to calm from the fight or the hunt, else 2);SoundSetSystemMusicStateholds a mood (3 hands it back, 4 turns the music off) andSoundEnableSystemMusicends a hold. Coney's stand-ins: the game's mood is the fight while an AI targeting player 1 has a fight or melee goal, else calm (no hunted mood, no gang-size or sight test); the mood is judged every frame, not every 30th update; the scripts' mood callback is not called. - Radios (
world_objects::Radios,repo:src/world_objects/radios.h; Radios):SetupRadiopins the object and makes it a radio; gameplay updates each radio every frame throughGameSound::play3D(a positional sound at the radio, half volume during a scene), with player 1's place, the game's random draws and the levels' completion, running the states, tables and DJ link picks above and callingonSegment. Coney's stand-ins: a sound stopped because the player went beyond 40 m starts its track again when he comes back; the DJ links of kinds 0 and 1 have no names, so they play nothing and end at once;Radio_SetMode's modes 4 and up, the pick-up (onPickUp), the smash and the R1 retune are not built (Coney has no carried boom box).
Coney's stand-ins where this page is open, each marked in the code: the directional table is 1 (as loud behind as in
front); the +0x268 state factors, the +0x5b7 owner duck and level 82's ambient swap are not applied; a stopped task
is freed at once (the game keeps one whose +0xe4 is set), so a preload after a live soundtrack always takes the
pending path; stopping the soundtrack also forgets one pending; the load screen plays only positional sounds virtually
(not yet the other 2D sounds that are not stereo); the soundtrack's pending re-preload runs on every update, as the
game's music update does; the random factors come from the engine's own seeded source (the game's is a separate audio
generator, 0x006eb8b0, seeded from the timer at start-up, 0x0010f768). The speech and ambience stand-ins: an emitter
plays one sound at a time, waiting a random whole number of seconds in its two delays before the first and after each
one ends (level99's pairs, 1-3 to 10-30, read as seconds), from a random point of its line or a random one of its
positions; its range, plays, mode, filter and the name kinds are not read; a line follows its speaker; a
script's SoundPlayCommand says its line as a cut-able one (CommandCall::cutting false: which uncut the script's
call passes is not traced; Warrior orders, which say theirs cutting, are not built);
HuSpeak's fifth argument is not read, and neither speaker turns to a look-at target; a human's voice set is his type's
own CfgChar voice (no alias rule, no HuSetVoiceIndex); the fixed list of blocked lines is not applied;
SndSetListener 0 is the camera and 1 the player (inferred); the bank deferral of mode 1's enter ends with the load
screen. The system music (repo:src/gamemodes/system_music.h) keeps each mood's track hashes and, while on, loops a
random track of the mood each frame's surroundings give when the mood changes; the mood is 1 while an AI human with
health left targets player 1 with a fight or melee goal, else 0 (the hunted mood 2, its chase goals not built, never
comes), the pick draws from the game's random index, and the fades are the music player's own. SoundSetEffect and
SoundEnableEffects are kept in the game state only. Not built yet: reverb, the other ambient bindings
(AddAmbientSoundEmitter, SetAmbientEmitterVolumeMod) and the game's own speech commands outside the animation
sounds. The hub's sound bindings
(repo:src/scripting/hub_world_bindings.cpp): EnableAmbientEmitter switches an emitter off (it stops its sound and
plays nothing more) and on; SoundPlay plays a sound once at a point; SndLoadMatrix empties the sound matrix and
runs <name>_preload.lua when the name changes; HuSay speaks a line with no callback.
Speech cut-in: what Coney changed¶
Coney used to drop a line's callback whenever the line was stopped, and let any stop end any line, so a grunt from
Cleon's swing ended mission 2's last line (HuSpeakNI(Cleon, line, "FadeOut")) and FadeOut never ran (#239). It now
matches Cutting in on a line (repo:src/audio/speech.cpp, GameSound); the five changes, each
tested in repo:tests/audio/game_sound_test.cpp ([cut-in]):
- Keep the "may be cut" flag per human (the original's
+0x194): set by default, cleared while a line started byHuSpeakNI, a cutting vocal id (die,expire) or a speech command said cutting (every Warrior order) plays, set again by a stop that stops, and by the speech update when no line is alive and no callback is waiting. - Keep the callback on the human, not on the line. A stop leaves it in place; the speech update runs it (once) at the first update that finds no live line, whether the line ended or was stopped. Starting a line that plays overwrites it with the new line's callback, or with none. A start that plays nothing runs its own callback at once and leaves the human's untouched.
- One unforced stop (
Speech::shutUpwith aforceflag): an unforced stop does nothing while the flag of 1 is clear.HuShutUppasses its argument; the vocal ids, speech commands and every other game-side caller stop unforced (a forced stop is only for knock-outs, defeats, lethal reactions, paired attacks and grabs, mugging and stopping every line). - Two starts. The waiting start (
HuSpeak, non-cutting vocal ids, commands said uncut) plays only when no line is alive and the flag is set; the cutting start (HuSpeakNI,die,expire, commands said cutting) plays unless a line is alive with the flag clear, stopping the current line unforced first and clearing the flag.HumanSoundEvents::sayAnimLinethen needs no special case: over an uncut line its stop does nothing and its start refuses, so the grunt is silent and the line plays on. - Speech commands with
interruptstop unforced and then start (cutting or waiting by the command'suncut); over an uncut line nothing is said, but the voice table's counter still moves and the command's callback runs at once.
The tests (unit, GameSound with an offline engine, no disc): a human says a line through the
HuSpeakNI path with callback A; while it plays, a vocal id with overLine (SA 9) is said for him: the line's
handle is unchanged and still playing, no grunt plays, and when the line ends A is reported once. Its mirror: the
same with the HuSpeak path and callback B: the grunt replaces the line and B is never reported. And
HuShutUp(false) on the HuSpeakNI line leaves it playing; HuShutUp(true) stops it and A is reported at the next
update. The disc test [disc][story][level80-end] (repo:tests/platform/disc_level80_end_test.cpp, square held until
MS_C4_2 is ticked + 5 s) reaches l80_c3 and HUDLaunchMissionComplete: before the change the mission never
completed under the swinging, after it it does.
Coney's choices: a script's SoundPlayCommand says its line cut-able (the uncut it passes is not traced, see
the open question below), and a refused start in Speech::say leaves the human's callback and flag as they were,
the caller running its own callback at once.
Open questions¶
-
Which
uncutthe script'sSoundPlayCommand(Sound_PlayHumanCommand,0x001140d8) passes toHuman_SayCommand: Coney says its lines cut-able, so a later command or grunt replaces them. -
What game state
+0x268is (it scales sounds of others, music and pitch). - Who sets the system-music mood (game state
+0x40c). - The listener vector's meaning and which object each listener is (
SndSetListener's values). - What
a8(the play call's third volume factor,+0xa8) is used for by each caller. - How long a primed but unstarted soundtrack survives:
Tasks_Updatefrees a real task when the device reports its voice idle (0x0014d168), and whether a primed stream reads as idle was not seen (the trace's waits were under 0.5 s). Which callers pass the play call's tenth argument (task+0xe4) and what the IOP does with it. - Whether a scene soundtrack holds the dialogue alone or a full mix, and what the 20 unused stereo sounds are.
- The IOP side (
IOP.IRX): the exact SPU2 voice assignment of streams and its mixing. - Who ends the bank deferral (
+0x3fa58). - The blocked lines of
0x00114c98: a hand-written list for levels 11, 20, 31, 34, 82, 83, 84, 92 and 95 (some by checkpoint, game state+0x33a), each naming voice sets, commands and 1-based lines; not yet tabulated. - The gang counter
+0x60d's starting value (the first bark'sNN) and the meaning of game state+0x56fc.