Sound
#![allow(unused)]
fn main() {
let hit = ctx.load_sound("hit.ogg"); // OGG Vorbis, WAV, MP3 or FLAC from assets/
ctx.play_sound(&hit);
let music = ctx.load_sound("theme.ogg");
let song = ctx.play_sound_ex(&music, SoundParams { volume: 0.5, looping: true, ..Default::default() });
ctx.stop_sound(song);
}
const crabby::sound hit = ctx.load_sound("hit.ogg"); // OGG Vorbis, WAV, MP3 or FLAC from assets/
ctx.play_sound(hit);
const crabby::sound music = ctx.load_sound("theme.ogg");
auto params = crabby::sound_defaults();
params.volume = 0.5f;
params.looping = true;
const crabby::playing song = ctx.play_sound(music, params);
ctx.stop_sound(song);
SoundParams { volume, speed, looping }:speedalso changes the pitch, and a little random variation keeps repeated effects from sounding mechanical.ctx.create_sound(sample_rate, &samples)makes a sound from generated mono samples (-1..1). Theuiexample’s click is made this way:
#![allow(unused)]
fn main() {
let samples: Vec<f32> = (0..4410) // 0.1 s at 44.1 kHz
.map(|i| 0.3 * (i as f32 * 880.0 * std::f32::consts::TAU / 44100.0).sin())
.collect();
let click = ctx.create_sound(44100, &samples);
}
std::vector<float> samples(4410); // 0.1 s at 44.1 kHz
for (std::size_t i = 0; i < samples.size(); ++i)
samples[i] = 0.3f * std::sin(float(i) * 880.0f * 6.2831853f / 44100.0f);
const crabby::sound click = ctx.create_sound(44100, samples.data(), samples.size());
The audio device opens the first time a sound plays. Headless runs, and machines without audio, are silent but run the same code. For servers you can drop the audio library entirely:
ponko = { version = "0.0.1", default-features = false }
The sound API stays; it just makes no noise. The C library has the same
switch: build ponko_ffi with --no-default-features. Linux builds with audio need
the ALSA headers (libasound2-dev on Debian and Ubuntu).
Sound in 3D
With the 3d feature, pair AudioSource3D with Transform3D, or play a
one-off sound at a point with ctx.play_sound_at. Every frame, after your
systems, the engine moves each sound to its entity and sets its volume,
panning and doppler from where the listener is. The listener follows
ctx.camera3d; to hear from somewhere else, such as the player in a
third-person game, call ctx.set_audio_listener(Some(..)).
Positions use world units; doppler assumes metres and takes velocities
from frame to frame. Set doppler: 0.0 to turn it off, for example for
something that teleports.
fn setup(ctx: &mut Ctx) {
let samples: Vec<f32> = (0..44100)
.map(|i| (i as f32 * 220.0 * std::f32::consts::TAU / 44100.0).sin() * 0.1)
.collect();
let hum = ctx.create_sound(44100, &samples);
let settings = AudioSettings3D {
sound: SoundParams {
volume: 0.4,
looping: true,
..Default::default()
},
min_distance: 2.0,
max_distance: 60.0,
rolloff: AudioRolloff::Linear,
..Default::default()
};
ctx.world
.spawn((Transform3D::at(vec3(4.0, 0.0, 0.0)), AudioSource3D::new(hum, settings)));
ctx.play_sound_at(&hum, vec3(-4.0, 0.0, 0.0), AudioSettings3D::default());
}
fn main() {
// The sounds follow their entities and the listener (ctx.camera3d) by themselves.
Game::new("Spatial audio", 800, 600).run(setup);
}
static void sound3d(crabby::ctx& ctx) {
float samples[4410];
for (int i = 0; i < 4410; i++) samples[i] = 0.1f * std::sin(i * 220.0f * 6.2831853f / 44100.0f);
auto hum = ctx.create_sound(44100, samples, 4410);
auto settings = crabby::audio_settings3d_defaults();
settings.sound.volume = 0.4f;
settings.sound.looping = true;
settings.min_distance = 2;
settings.max_distance = 60;
settings.rolloff = CRABBY_ROLLOFF_LINEAR;
auto e = ctx.world().spawn(crabby::transform3d_at({4, 0, 0}));
ctx.set_audio_source3d(e, hum, settings);
ctx.play_sound_at(hum, {-4, 0, 0});
}
Build this example with cargo build --example audio3d --features 3d.
Only running it opens a window and plays audio. Procedural clips already use
create_sound; file-backed clips use load_sound.
AudioSettings3D controls volume, pitch (sound.speed), looping, minimum and
maximum distance, and logarithmic, linear or keyed rolloff. Keyed curves use
sorted (normalized_distance, gain) pairs in a static slice. All curves are
silent at the maximum distance and full volume inside the minimum distance.
Stereo panning follows the listener’s right axis. Kira applies gain, panning
and playback-rate changes with smoothing; this is stereo positional audio,
not an HRTF simulation. Prefer mono source clips for localized effects.
ctx.play_sound_at starts a voice at a fixed world position. Save its handle
to stop it; removing an entity source stops its voice on the next audio update.
Headless execution never opens an audio device, and disabling audio keeps
the same APIs silent. No allocation occurs during updates unless a new voice
starts. load_sound decodes clips into memory; load_stream keeps long files streamed.
Buses, low-pass and reverb
Ordinary playback uses the sfx bus. Create other named buses once with
audio_bus, save their handles, and pass one to play_sound_on or to
AudioSettings3D::bus. Handles belong to the context that created them.
Volume is a linear gain; muting preserves the configured gain. Bus changes
apply to active voices as well as future playback.
#![allow(unused)]
fn main() {
#[allow(dead_code)]
fn buses_and_effects(ctx: &mut Ctx, sound: Sound) {
let music = ctx.audio_bus("music"); // Save this handle; names are looked up once.
ctx.set_audio_bus_volume(music, 0.6);
let voice = ctx.play_sound_on(
&sound,
SoundParams {
looping: true,
..Default::default()
},
music,
AudioEffects {
low_pass_hz: 4_000.0,
reverb: 0.2,
},
);
ctx.set_sound_effects(
voice,
AudioEffects {
low_pass_hz: 800.0,
reverb: 0.5,
},
);
ctx.mute_audio_bus(music, true);
ctx.mute_audio_bus(music, false); // Restores the configured gain.
let settings = AudioSettings3D {
bus: music,
effects: AudioEffects {
low_pass_hz: 2_000.0,
reverb: 0.3,
},
..Default::default()
};
ctx.play_sound_at(&sound, vec3(3.0, 0.0, 0.0), settings);
}
}
static void buses_and_effects(crabby::ctx& ctx, crabby::sound sound) {
auto music = ctx.audio_bus("music"); // Save this handle; names are looked up once.
ctx.set_audio_bus_volume(music, 0.6f);
auto params = crabby::sound_defaults();
params.looping = true;
auto voice = ctx.play_sound_on(sound, music, params, {4000, 0.2f});
ctx.set_sound_effects(voice, {800, 0.5f});
ctx.mute_audio_bus(music, true);
ctx.mute_audio_bus(music, false); // Restores the configured gain.
auto settings = crabby::audio_settings3d_defaults();
settings.bus = music;
settings.effects = {2000, 0.3f};
ctx.play_sound_at(sound, {3, 0, 0}, settings);
}
Each voice uses a Kira low-pass filter on its own child track. The cutoff
is clamped to 20–20,000 Hz. set_sound_effects changes cutoff and reverb
send while a voice is playing, including positional voices. Reverb is a
single shared, fully wet Kira send track; the send amount is linear 0–1.
The send follows voice attenuation, filtering and bus gain. Muting a bus
stops its new reverb input; existing shared reverb tails decay naturally.
Nonfinite cutoff/send values use defaults. No custom DSP is used. Starting
a voice allocates its track and decoder; changing parameters and updating
existing spatial voices does not allocate engine-side buffers each frame.
Streaming music and ambience
#![allow(unused)]
fn main() {
#[allow(dead_code)]
fn streaming_music(ctx: &mut Ctx) {
let music = ctx.audio_bus("music");
let song = ctx.load_stream("music.ogg");
let voice = ctx.play_sound_on(
&song,
SoundParams {
looping: true,
..Default::default()
},
music,
AudioEffects::default(),
);
ctx.stop_sound(voice);
}
}
[[maybe_unused]] static void streaming_music(crabby::ctx& ctx) {
auto music = ctx.audio_bus("music");
auto song = ctx.load_stream("music.ogg");
auto params = crabby::sound_defaults();
params.looping = true;
ctx.play_sound_on(song, music, params);
}
load_stream reads the header and keeps the native filesystem path rather
than decoding the entire clip. Each playback opens a separate decoder;
Kira performs buffered decoding on its worker thread. The returned Sound
works with 2D playback, positional playback, buses, effects and looping.
The file must remain available until playback ends. Streaming files are
not hot reloaded and currently require native filesystem assets (web builds
should use load_sound for prefetched assets). With audio disabled, stream
handles are silent and report zero duration. Headless playback never creates
tracks, decoder workers or an audio device. These additions currently have
Rust APIs only; C and C++ bindings are deferred.