Audio Extensions
Sound for a game with no sound assets: tones generated in a callback, .wav files read at runtime, and a listener that follows the camera. Everything lives in Stride.CommunityToolkit.Audio and hangs off game.Audio.
Sound, the type an AudioEmitterComponent plays, only comes out of the asset pipeline, so a code-only game has none. The toolkit instead feeds the SoundInstance constructor a DynamicSoundSource: one backed by a callback, one by an array decoded from a .wav.
Making sound
CreateProceduralSound()- A sound whose samples come from aSampleFillercallback: a tone, noise, a synthesiser. Four buffers of 4096 samples, so a change is heard within about a tenth of a second. The callback runs on the audio worker thread; hand it singleintorfloatfields, not structures.LoadWav(path)/LoadWav(stream)- Decodes a.wav(8/16/24/32-bit PCM or 32-bit float, mono or stereo, any sample rate) into aWavSoundheld in memory. Compressed formats are rejected with a message naming them.WavSound.CreateInstance()- OneWavSound, many instances: each plays independently, so effects overlap. SetIsLoopingbeforePlay()for music. Dispose an instance once it reportsStopped; each holds a native source.
var chime = game.Audio.LoadWav(Path.Combine(directory, "chime.wav"));
var instance = chime.CreateInstance();
instance.Volume = 0.8f;
instance.Play();
Positioning sound
AttachListener()- Puts anAudioListenerComponenton an entity - the camera, almost always - and returns its listener, which the engine then moves every frame. Pass it as thelistenerof every spatialised instance.SoundEmitterScript- ASyncScriptthat positions a spatialised instance at its entity every frame: position, orientation and velocity from the world transform, throughApply3D.
var listener = game.Audio.AttachListener(game.GetCameraEntity());
var pad = game.Audio.LoadWav(Path.Combine(directory, "pad-loop.wav"));
var instance = pad.CreateInstance(spatialized: true, listener: listener);
orb.Add(new SoundEmitterScript { Instance = instance });
instance.Play();
Warning
Without AttachListener, a runtime sound is heard by the engine's default listener, which nothing ever moves: it sits at the origin facing +Z however the camera turns. The engine only updates listeners that belong to an AudioListenerComponent, and the listener on that component is internal - AttachListener reads it for you.
Spatialised instances must be mono, and Pan and spatialisation are exclusive: both files the examples ship are mono for that reason.
HRTF
Head-related transfer function spatialisation takes two switches: HrtfSupport in the game settings before Run(), which UseGameSettings() provides, then useHrtf: true per instance. It works on Windows, where the engine's XAudio2 backend carries the HRTF processor; OpenAL on Linux and macOS ignores the flag and uses its own model.
game.UseGameSettings(settings => settings.GetOrCreateConfiguration<AudioEngineSettings>().HrtfSupport = true);
When there is no audio engine
AudioSystem.Initialize swallows the native initialisation failure and leaves game.Audio.AudioEngine null - on Linux without OpenAL, and on headless machines. Every method here throws InvalidOperationException naming that cause; check game.Audio.AudioEngine first to degrade silently instead. The methods also need the engine to exist, so call them from the start callback of Run() or later, never before.
The audio engine pauses whenever the game window loses focus, and while paused a Play() call is silently ignored. A game launched into the background therefore never starts its music until something calls Play() again with the window focused.
Examples
- Procedural Sound - a tone with no file, waveform and pitch changed live.
- Wav File - a chime that overlaps with itself and a looping pad, from files on disk.
- Spatial Sound - a sound on an orbiting orb, heard from the camera, with an HRTF toggle.