Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Audio Functions

Sound effects and music playback with 16 channels.

Loading Sounds

load_sound

Loads a sound from WASM memory.

Signature:

#![allow(unused)]
fn main() {
fn load_sound(data_ptr: *const u8, byte_len: u32) -> u32
}

Parameters:

NameTypeDescription
data_ptr*const u8Pointer to PCM audio data
byte_lenu32Size of data in bytes

Returns: Sound handle (non-zero on success)

Audio Format: 22.05 kHz, 16-bit signed, mono PCM

Constraints: Init-only.

Example:

#![allow(unused)]
fn main() {
static JUMP_DATA: &[u8] = include_bytes!("jump.raw");
static mut JUMP_SFX: u32 = 0;

fn init() {
    unsafe {
        JUMP_SFX = load_sound(JUMP_DATA.as_ptr(), JUMP_DATA.len() as u32);
    }
}
}

Note: Prefer rom_sound() for sounds bundled in the ROM data pack.


Sound Effects

play_sound

Plays a sound on the next available channel.

Signature:

#![allow(unused)]
fn main() {
fn play_sound(sound: u32, volume: f32, pan: f32)
}

Parameters:

NameTypeDescription
soundu32Sound handle
volumef32Volume (0.0-1.0)
panf32Stereo pan (-1.0 = left, 0.0 = center, 1.0 = right)

Example:

#![allow(unused)]
fn main() {
fn update() {
    if button_pressed(0, BUTTON_A) != 0 {
        play_sound(JUMP_SFX, 1.0, 0.0);
    }

    // Positional audio
    let dx = enemy.x - player.x;
    let pan = (dx / 20.0).clamp(-1.0, 1.0);
    let dist = ((enemy.x - player.x).powi(2) + (enemy.z - player.z).powi(2)).sqrt();
    let vol = (1.0 - dist / 50.0).max(0.0);
    play_sound(ENEMY_GROWL, vol, pan);
}
}

channel_play

Plays a sound on a specific channel with loop control.

Signature:

#![allow(unused)]
fn main() {
fn channel_play(channel: u32, sound: u32, volume: f32, pan: f32, looping: u32)
}

Parameters:

NameTypeDescription
channelu32Channel index (0-15)
soundu32Sound handle
volumef32Volume (0.0-1.0)
panf32Stereo pan (-1.0 to 1.0)
loopingu321 to loop, 0 for one-shot

Example:

#![allow(unused)]
fn main() {
fn update() {
    // Engine sound on dedicated channel (looping)
    if vehicle.engine_on && !ENGINE_PLAYING {
        channel_play(0, ENGINE_SFX, 0.8, 0.0, 1);
        ENGINE_PLAYING = true;
    }

    // Adjust engine pitch based on speed
    if ENGINE_PLAYING {
        let vol = 0.5 + vehicle.speed * 0.005;
        channel_set(0, vol.min(1.0), 0.0);
    }
}
}

channel_set

Updates volume and pan for a playing channel.

Signature:

#![allow(unused)]
fn main() {
fn channel_set(channel: u32, volume: f32, pan: f32)
}

Parameters:

NameTypeDescription
channelu32Channel index (0-15)
volumef32New volume (0.0-1.0)
panf32New stereo pan

Example:

#![allow(unused)]
fn main() {
fn update() {
    // Fade out channel 0
    if fading {
        fade_vol -= delta_time() * 0.5;
        if fade_vol <= 0.0 {
            channel_stop(0);
            fading = false;
        } else {
            channel_set(0, fade_vol, 0.0);
        }
    }
}
}

channel_stop

Stops playback on a channel.

Signature:

#![allow(unused)]
fn main() {
fn channel_stop(channel: u32)
}

Parameters:

NameTypeDescription
channelu32Channel index (0-15)

Example:

#![allow(unused)]
fn main() {
fn update() {
    if vehicle.engine_off {
        channel_stop(0);
        ENGINE_PLAYING = false;
    }
}
}

Unified Music API

A unified API for playing both PCM music and XM tracker modules. The handle type is detected automatically:

  • PCM handles (from load_sound/rom_sound) have bit 31 = 0
  • Tracker handles (from load_tracker/rom_tracker) have bit 31 = 1

Starting one type automatically stops the other (mutually exclusive). All music functions support rollback netcode.

rom_tracker

Loads an XM tracker module from the ROM data pack.

Signature:

#![allow(unused)]
fn main() {
fn rom_tracker(id_ptr: *const u8, id_len: u32) -> u32
}

Parameters:

NameTypeDescription
id_ptr*const u8Pointer to tracker ID string
id_lenu32Length of tracker ID string

Returns: Tracker handle (with bit 31 set) on success, 0 on failure.

Constraints: Init-only. Load instrument samples via rom_sound() before loading the tracker.


load_tracker

Loads an XM tracker module from WASM memory.

Signature:

#![allow(unused)]
fn main() {
fn load_tracker(data_ptr: u32, data_len: u32) -> u32
}

Parameters:

NameTypeDescription
data_ptru32Pointer to XM file data
data_lenu32Length in bytes

Returns: Tracker handle (with bit 31 set) on success, 0 on failure.

Constraints: Init-only.


music_play

Plays music (PCM sound or XM tracker module).

Signature:

#![allow(unused)]
fn main() {
fn music_play(handle: u32, volume: f32, looping: u32)
}

Parameters:

NameTypeDescription
handleu32Sound handle (from load_sound) or tracker handle (from rom_tracker)
volumef32Volume (0.0-1.0)
loopingu321 = loop, 0 = play once

Behavior: Automatically stops any currently playing music of the other type.

Example:

#![allow(unused)]
fn main() {
// PCM music
let bgm = rom_sound(b"menu_bgm".as_ptr(), 8);
music_play(bgm, 0.7, 1); // Loop

// XM tracker music
let tracker = rom_tracker(b"level1".as_ptr(), 6);
music_play(tracker, 0.8, 1); // Loop
}

music_stop

Stops currently playing music (both PCM and tracker).

Signature:

#![allow(unused)]
fn main() {
fn music_stop()
}

music_pause

Pauses or resumes music playback (tracker only, no-op for PCM).

Signature:

#![allow(unused)]
fn main() {
fn music_pause(paused: u32)
}

Parameters:

NameTypeDescription
pausedu321 = pause, 0 = resume

music_set_volume

Sets the music volume (works for both PCM and tracker).

Signature:

#![allow(unused)]
fn main() {
fn music_set_volume(volume: f32)
}

Parameters:

NameTypeDescription
volumef32Volume (0.0-1.0)

music_is_playing

Checks if music is currently playing.

Signature:

#![allow(unused)]
fn main() {
fn music_is_playing() -> u32
}

Returns: 1 if playing (and not paused), 0 otherwise.


music_type

Gets the current music type.

Signature:

#![allow(unused)]
fn main() {
fn music_type() -> u32
}

Returns:

  • 0 = none
  • 1 = PCM
  • 2 = tracker

music_jump

Jumps to a specific position (tracker only, no-op for PCM).

Signature:

#![allow(unused)]
fn main() {
fn music_jump(order: u32, row: u32)
}

Parameters:

NameTypeDescription
orderu32Order position (0-based)
rowu32Row within pattern (0-based)

music_position

Gets the current music position.

Signature:

#![allow(unused)]
fn main() {
fn music_position() -> u32
}

Returns:

  • For tracker: (order << 16) | row
  • For PCM: sample position

music_length

Gets the music length.

Signature:

#![allow(unused)]
fn main() {
fn music_length(handle: u32) -> u32
}

Parameters:

NameTypeDescription
handleu32Music handle (PCM or tracker)

Returns:

  • For tracker: number of orders
  • For PCM: number of samples

music_set_speed

Sets tracker speed (tracker only, ticks per row).

Signature:

#![allow(unused)]
fn main() {
fn music_set_speed(speed: u32)
}

Parameters:

NameTypeDescription
speedu321-31 (XM default is 6)

music_set_tempo

Sets tracker tempo (tracker only, BPM).

Signature:

#![allow(unused)]
fn main() {
fn music_set_tempo(bpm: u32)
}

Parameters:

NameTypeDescription
bpmu3232-255 (XM default is 125)

music_info

Gets music info.

Signature:

#![allow(unused)]
fn main() {
fn music_info(handle: u32) -> u32
}

Parameters:

NameTypeDescription
handleu32Music handle (PCM or tracker)

Returns:

  • For tracker: (num_channels << 24) | (num_patterns << 16) | (num_instruments << 8) | song_length
  • For PCM: (sample_rate << 16) | (channels << 8) | bits_per_sample

music_name

Gets the music name (tracker only, returns 0 for PCM).

Signature:

#![allow(unused)]
fn main() {
fn music_name(handle: u32, out_ptr: *mut u8, max_len: u32) -> u32
}

Parameters:

NameTypeDescription
handleu32Music handle
out_ptr*mut u8Output buffer pointer
max_lenu32Maximum bytes to write

Returns: Actual length written (0 if PCM or invalid handle).


Audio Architecture

  • 16 SFX channels (0-15) for sound effects
  • 1 Music channel (separate) for background music
  • 22.05 kHz sample rate, 16-bit mono PCM
  • Rollback-safe: Audio state is part of rollback snapshots
  • Per-frame audio generation with ring buffer

Complete Example

#![allow(unused)]
fn main() {
static mut JUMP_SFX: u32 = 0;
static mut LAND_SFX: u32 = 0;
static mut COIN_SFX: u32 = 0;
static mut MUSIC: u32 = 0;
static mut AMBIENT: u32 = 0;

fn init() {
    unsafe {
        // Load sounds from ROM
        JUMP_SFX = rom_sound(b"jump".as_ptr(), 4);
        LAND_SFX = rom_sound(b"land".as_ptr(), 4);
        COIN_SFX = rom_sound(b"coin".as_ptr(), 4);
        MUSIC = rom_sound(b"level1".as_ptr(), 6);
        AMBIENT = rom_sound(b"wind".as_ptr(), 4);

        // Start music and ambient
        music_play(MUSIC, 0.6, 1); // 1 = loop
        channel_play(15, AMBIENT, 0.3, 0.0, 1); // Looping ambient
    }
}

fn update() {
    unsafe {
        // Jump sound
        if button_pressed(0, BUTTON_A) != 0 && player.on_ground {
            play_sound(JUMP_SFX, 0.8, 0.0);
        }

        // Land sound
        if player.just_landed {
            play_sound(LAND_SFX, 0.6, 0.0);
        }

        // Coin pickup with positional audio
        for coin in &coins {
            if coin.just_collected {
                let dx = coin.x - player.x;
                let pan = (dx / 10.0).clamp(-1.0, 1.0);
                play_sound(COIN_SFX, 1.0, pan);
            }
        }

        // Pause menu - duck audio
        if game_paused {
            music_set_volume(0.2);
            channel_set(15, 0.1, 0.0);
        } else {
            music_set_volume(0.6);
            channel_set(15, 0.3, 0.0);
        }
    }
}
}

See Also: rom_sound