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:
| Name | Type | Description |
|---|---|---|
| data_ptr | *const u8 | Pointer to PCM audio data |
| byte_len | u32 | Size 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:
| Name | Type | Description |
|---|---|---|
| sound | u32 | Sound handle |
| volume | f32 | Volume (0.0-1.0) |
| pan | f32 | Stereo 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:
| Name | Type | Description |
|---|---|---|
| channel | u32 | Channel index (0-15) |
| sound | u32 | Sound handle |
| volume | f32 | Volume (0.0-1.0) |
| pan | f32 | Stereo pan (-1.0 to 1.0) |
| looping | u32 | 1 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:
| Name | Type | Description |
|---|---|---|
| channel | u32 | Channel index (0-15) |
| volume | f32 | New volume (0.0-1.0) |
| pan | f32 | New 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:
| Name | Type | Description |
|---|---|---|
| channel | u32 | Channel 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:
| Name | Type | Description |
|---|---|---|
| id_ptr | *const u8 | Pointer to tracker ID string |
| id_len | u32 | Length 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:
| Name | Type | Description |
|---|---|---|
| data_ptr | u32 | Pointer to XM file data |
| data_len | u32 | Length 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:
| Name | Type | Description |
|---|---|---|
| handle | u32 | Sound handle (from load_sound) or tracker handle (from rom_tracker) |
| volume | f32 | Volume (0.0-1.0) |
| looping | u32 | 1 = 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:
| Name | Type | Description |
|---|---|---|
| paused | u32 | 1 = 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:
| Name | Type | Description |
|---|---|---|
| volume | f32 | Volume (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:
| Name | Type | Description |
|---|---|---|
| order | u32 | Order position (0-based) |
| row | u32 | Row 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:
| Name | Type | Description |
|---|---|---|
| handle | u32 | Music 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:
| Name | Type | Description |
|---|---|---|
| speed | u32 | 1-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:
| Name | Type | Description |
|---|---|---|
| bpm | u32 | 32-255 (XM default is 125) |
music_info
Gets music info.
Signature:
#![allow(unused)]
fn main() {
fn music_info(handle: u32) -> u32
}
Parameters:
| Name | Type | Description |
|---|---|---|
| handle | u32 | Music 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:
| Name | Type | Description |
|---|---|---|
| handle | u32 | Music handle |
| out_ptr | *mut u8 | Output buffer pointer |
| max_len | u32 | Maximum 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