Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/pmret/papermario/llms.txt

Use this file to discover all available pages before exploring further.

Paper Mario’s audio system drives background music, sound effects, ambient loops, and music events. The implementation is split across several files under src/audio/, with a public C API in src/audio/public.h and an EVT scripting layer in src/evt/audio_api.c. Understanding the system is useful any time you work on a map, battle, or cutscene that needs sound.

Subsystem structure

Source fileResponsibility
src/audio/snd_interface.cLow-level sound driver interface — start/stop/adjust individual sounds
src/audio/bgm_control.cBGM player management, song loading, proximity mixing
src/audio/bgm_player.cBGM playback and sequencer update
src/audio/sfx_control.cSound effect spatialization, looping sound tracking
src/audio/sfx_player.cSFX playback dispatch
src/audio/mseq_player.cMSEQ (music sequence) player
src/audio/ambience.cAmbient sound loop management
src/audio/load_banks.cSound bank DMA loading
src/audio/core/Core audio engine (AU library)
src/evt/audio_api.cEVT-callable wrappers for all audio functions

BGM players

The game maintains multiple simultaneous BGM player slots. Each slot can load and play an independent song. bgm_set_song is the primary entry point:
// src/audio/bgm_control.c
s32 bgm_set_song(s32 playerIndex, s32 songID, s32 variation,
                 s32 fadeOutTime, s16 volume);
  • playerIndex — which BGM player slot to use (0 is the main music player)
  • songID — the AU_SONG_* constant identifying the track
  • variation — selects an alternate arrangement (0 or 1); each MapConfig stores a songVariation field
  • fadeOutTime — milliseconds to fade out the current track before switching
  • volume — target volume level
Songs can also be stacked with bgm_push_song / bgm_pop_song, which is used for short jingles (e.g. item get, level up) that play over the map music and then restore it.
void bgm_push_song(s32 songID, s32 variation);
void bgm_pop_song(void);
Battle songs use their own push/pop stack:
void bgm_set_battle_song(s32 songID, s32 variation);
void bgm_push_battle_song(void);
void bgm_pop_battle_song(void);

Sound effects

SFX are managed through two layers. The low-level layer in snd_interface.c sends commands directly to the audio engine:
void snd_start_sound(s32 soundID, u8 volume, u8 pan);
void snd_stop_sound(s32 soundID);
void snd_adjust_sound(s32 soundID, u8 volume, u8 pan);
void snd_start_sound_with_shift(s32 soundID, u8 volume, u8 pan, s16 pitchShift);
The higher-level layer in sfx_control.c adds spatialization and looping support:
// One-shot sounds
void sfx_play_sound(s32 soundID);
void sfx_play_sound_with_params(s32 soundID, u8 volume, u8 pan, s16 pitchShift);
void sfx_play_sound_at_position(s32 soundID, s32 flags, f32 posX, f32 posY, f32 posZ);
void sfx_play_sound_at_player(s32 soundID, s32 arg1);
void sfx_play_sound_at_npc(s32 soundID, s32 arg1, s32 npcID);

// Looping / positional sounds
void sfx_play_sound_looping(s32 soundID, u8 volume, u8 pan, s16 pitchShift);
void sfx_register_looping_sound_at_position(s32 soundID, s32 flags, f32 x, f32 y, f32 z);
s32  sfx_adjust_env_sound_pos(s32 soundID, s32 sourceFlags, f32 x, f32 y, f32 z);
void sfx_stop_tracking_env_sound_pos(s32 soundID, s32 keepPlaying);
void sfx_stop_sound(s32 soundID);
Spatialization computes volume and stereo pan from the sound’s world position relative to the camera:
void sfx_get_spatialized_sound_params(f32 x, f32 y, f32 z,
                                      s16* volume, s16* pan, s32 flags);
The sfxReverb field in MapConfig sets the reverb type for the current room, applied via sfx_set_reverb_mode.

Ambient sounds

Ambient loops (wind, forest, water, etc.) are loaded and played through src/audio/ambience.c:
AuResult snd_load_ambient(s32 soundID);
AuResult snd_ambient_play(s32 soundID, s32 arg1);
AuResult snd_ambient_stop_quick(s32 soundID);
AuResult snd_ambient_stop_slow(s32 soundID, s32 fadeTime);
AuResult snd_ambient_fade_out(s32 soundID, s32 fadeTime);
AuResult snd_ambient_set_volume(s32 soundID, s32 volume, s32 arg2);

Music event framework

The music event system allows BGM sequences to trigger EVT scripts at specific points in a song. This is used for synchronising gameplay events to the music. A MusicEvent struct maps an event ID to an array of EVT script pointers. You register a table of events from an EVT script using RegisterMusicEvents. The PollMusicEvents callable (called internally) reads events from the audio engine and launches the matching script:
// src/evt/audio_api.c
API_CALLABLE(RegisterMusicEvents) {
    // reads a MusicEvent table pointer from EVT args
    MusicEventList = (MusicEvent*) evt_get_variable(script, *args++);
    // ... starts EVS_MusicEventMonitor script
}
The monitor script calls PollMusicEvents every frame, which calls snd_song_poll_music_events to retrieve pending events from the audio driver and fires the corresponding EVT script.
The PollMusicEvents implementation in src/evt/audio_api.c contains a documented potential bug: the nullptr check on cur may always succeed even when the event ID was not found in the table, because cur can never become nullptr when walking a sentinel-terminated list.

EVT audio scripting API

All audio functions are accessible from EVT scripts through callables in src/evt/audio_api.c.
SetMusic(playerIndex, songID, variation, volume)
    // Load and play a song on the given player slot

FadeInMusic(playerIndex, songID, variation, fadeTime, startVol, endVol)
    // Fade in a song over fadeTime milliseconds

FadeOutMusic(playerIndex, fadeTime)
    // Fade out the current song on a player slot

PushSong(songID, variation)
    // Push a song onto the stack (saves current song)

PopSong()
    // Pop and restore the previous song

SetBattleSong(songID, variation)
    // Set the song to play when a battle starts

PushBattleSong()
    // Push and start the battle song

PopBattleSong()
    // Pop and restore the pre-battle song
EnableMusicProximityMix(playerIndex)
    // Enable volume/pan based on player distance

AdjustMusicProximityMix(playerIndex, mix, state)
    // Manually adjust proximity mix amount

SetTrackVolumes(trackVolSet)
    // Set per-track volume levels within the current song
PlaySound(soundID)
    // Play a sound effect at full volume

PlaySoundWithVolume(soundID, volume)
    // Play a sound effect at a specified volume

PlaySoundAt(soundID, flags, x, y, z)
    // Play a spatialized sound at a world position (integer coords)

PlaySoundAtF(soundID, flags, x, y, z)
    // Play a spatialized sound at a world position (float coords)

StopSound(soundID)
    // Stop a currently playing sound

StopTrackingSoundPos(soundID)
    // Stop updating the position of a looping positional sound
PlayAmbientSounds(soundID)
    // Start an ambient loop

ClearAmbientSounds(fadeTime)
    // Fade out and clear the current ambient loop
UseDoorSounds(soundSet)
    // Select the door open/close sound set for this map

UseRoomDoorSounds(soundSet)
    // Select the door sounds for the current room sub-area
RegisterMusicEvents(musicEventTablePtr)
    // Register a MusicEvent table and start the monitor script

Volume and global settings

Global BGM and SFX volume levels are controlled through:
void snd_set_bgm_volume(VolumeLevels volume);
void snd_set_sfx_volume(VolumeLevels volume);
void snd_set_stereo(void);
void snd_set_mono(void);
These are called by the options menu when the player adjusts the sound settings.

Battle system

Battle scripts use PlaySoundAtActor and PlaySoundAtPart for attack sounds.

World and map system

See how MapConfig carries songVariation and sfxReverb fields used by the audio system.

Build docs developers (and LLMs) love