Sound
Overview
Chisel’s sound system wraps OpenAL through SoundDevice. It handles 3D spatial audio, volume categories, and basic sound caching. All audio files must be in OGG Vorbis format. 3D sounds are forced to mono by the loader; stereo is only preserved when you disable spatialization.
Playing a Sound
The main API is SoundDevice.Device.PlaySound(). Call it from anywhere in your entity controller:
SoundDevice.Device.PlaySound(
$“{GameEngine.Instance.Content.RootDirectory}/Audio/gunshot.ogg”,
entity.position,
gain: 1f,
pitch: 0.95f + Random.Shared.NextSingle() * 0.1f,
disable3D: false,
minDist: 1f,
maxDist: 64f
);
All parameters after the file path and position are optional. The full signature is:
filePath Relative path to the OGG file from the exe directory. Use GameEngine.Instance.Content.RootDirectory to use the path relative to your content folder.
position World position to play the sound from. For sounds attached to an entity, pass entity.position.
loop (false) Whether the sound loops when it finishes. Returns a source ID you can use to stop it later.
gain (1.0) Volume multiplier. Scaled by the player’s sound category volume setting.
pitch (1.0) Playback speed multiplier. Values below 1 slow it down, above 1 speed it up. Randomizing pitch slightly helps sounds feel less repetitive.
disable3D (false) When true, disables spatial falloff and allows stereo audio. Use this for music, UI sounds, or anything that should play at a fixed volume regardless of where the player is.
minDist (1.0) Distance within which the sound plays at full gain. The falloff starts at this distance.
maxDist (64) Distance beyond which the sound is inaudible.
rolloff (1.0) How quickly volume drops off between minDist and maxDist. Higher values make sounds fall off faster.
category (SFX) Which volume category the sound belongs to. Controls which player volume slider affects it.
PlaySound returns a uint source ID. For fire-and-forget sounds you can ignore this. For looping sounds you’ll want to save it so you can stop the source later by calling SoundDevice.Device.StopSource(id).
Sound Categories
Every sound belongs to one of three categories that map to the volume sliders in the options menu:
SoundCategory.SFX Default. Covers all gameplay sounds: gunshots, footsteps, explosions, voice lines.
SoundCategory.Music Background music and ambient tracks. Typically played with disable3D: true.
SoundCategory.Master Scales all other categories. You won’t assign sounds directly to Master; it’s the player’s overall volume knob.
Pass the category in the last argument:
SoundDevice.Device.PlaySound(
$“{GameEngine.Instance.Content.RootDirectory}/Audio/Music/theme.ogg”,
Vector3.Zero,
loop: true,
disable3D: true,
category: SoundCategory.Music
);
Prefetching Sounds
The first time a sound file is played, the engine reads and decodes the OGG from disk and caches the PCM data in a buffer. That initial decode can cause a small stutter on the first play. To avoid this, prefetch sounds you know you’ll need before they’re first played.
The right place to prefetch is your controller’s Prefetch() method, which is called once per class on map load before any instances exist:
public override void Prefetch()
{
SoundDevice.Device.PrefetchSound($“{GameEngine.Instance.Content.RootDirectory}/Audio/gunshot.ogg”);
SoundDevice.Device.PrefetchSound($“{GameEngine.Instance.Content.RootDirectory}/Audio/death.ogg”);
}
Prefetching loads and caches the buffer so the first actual play call hits the cache immediately instead of disk. Don’t prefetch sounds that are rarely triggered or only used in specific circumstances — you’ll waste memory on buffers that sit idle.
Listener Position
The sound system needs to know where the “ears” are so it can calculate 3D falloff and panning. The engine expects you to update the listener position each frame with the camera position and orientation. In the FPS template this is done by the player controller. If you’re using a custom camera, update it manually:
SoundDevice.Device.listenerPosition = cameraPosition;
SoundDevice.Device.cameraForward = cameraForward;
SoundDevice.Device.cameraUp = cameraUp;
If you forget to update this, all 3D sounds will have their distance calculated from wherever the listener was last set, which is usually the world origin at startup.
Source Limits
The sound system has a pool of audio sources (default 256, changeable with snd_maxsources in the console). When the pool is full, new sounds steal the source that was least recently used. This means very loud maps can cause faint distant sounds to be dropped. Design sounds with reasonable max distances so the system doesn’t waste sources on inaudible sounds.
A sound with a maxDist smaller than the distance to the listener is skipped entirely without consuming a source at all.