Audio
MicroEngine audio has two halves. The core synthesizes sound effects (Synth) and step-sequenced music (Song)
into AudioClips, and decodes PCM WAV files. The game-facing AudioMixer (Game.Audio) keeps a bank of named
sounds and music, plays them by name, and fades music in and out. Playback itself is done by the backend’s
IAudioDevice, which also streams compressed OGG and MP3 music.
Pieces
| Type | Role |
|---|---|
AudioClip |
Interleaved float samples (-1..1), 1 or 2 channels; FromWav(bytes), ToWav(), Duration, Peak |
SfxParams + Synth.Render |
Retro sound effects in the spirit of sfxr |
Synth.Layer, Synth.NoteFrequency |
Mix clips at time offsets; note name to Hz ("C5", "F#3") |
Instrument, Song |
Tracker-style voices and a step sequencer rendered to a looping stereo clip |
AudioMixer |
Named sound bank, music with fades, volumes |
IAudioDevice |
Backend: RaylibAudioDevice on desktop and web, SilentAudioDevice headless or without hardware |
Source: src/MicroEngine/Audio/
Synthesized sound effects
SfxParams is a record of waveform (Square, Triangle, Saw, Sine, Noise, Pulse25, Pulse12),
Frequency with optional EndFrequency slide, attack/sustain/decay times, volume, vibrato, arpeggio, low-pass
filter and bit-crush. Synth.Render is deterministic: the same parameters always give the same samples.
Audio.AddSound("ding", Synth.Render(new SfxParams { Wave = Wave.Square, Frequency = 880, Arpeggio = [1f, 1.5f], Sustain = 0.05f, Decay = 0.2f }));Source: samples/HelloEngine/Program.cs
Meadow Run builds its whole sound bank this way, layering clips for richer hits:
yield return ("stomp", Synth.Layer((R(Tone(Wave.Square, 260, 90, 0.02f, 0.1f, 0.35f, 2500)), 0), (R(Tone(Wave.Noise, 700, 200, 0.01f, 0.08f, 0.35f, 2000)), 0)));Source: games/MeadowRun/Audio/SoundBank.cs
Music with Song
new Song(bpm, stepsPerBeat = 4) holds tracks of whitespace-separated steps: a note (C4, F#3, Bb5), - to
hold, . to rest, | as an ignored bar line. Drums(...) uses k kick, s snare, h hat, o open hat.
Shorter tracks loop to the length of the longest one. Render() produces a stereo clip sized to loop seamlessly
(echo tails wrap around). Built-in voices: Instrument.Lead, Bass, Pad, Pluck, Bell; Echo,
EchoDelayBeats and DrumVolume tune the mix.
Audio.AddMusic("theme", new Song(120).Track(Instrument.Lead, "C5 - E5 - G5 - E5 - | C5 - - - . . . .").Track(Instrument.Bass, "C3 . C3 . G2 . G2 .").Drums("k . h . s . h .").Render());Source: samples/HelloEngine/Program.cs
Rendering happens on the CPU at load time; long songs add to start-up time, so render them once in Load.
The mixer
| Member | Behaviour |
|---|---|
AddSound(name, clip), AddMusic(name, clip, loop) |
Register clips |
AddSoundFile(name, path) |
Decode a PCM WAV file as a sound |
AddMusicFile(name, path, loop) |
WAV is decoded in managed code; any other extension is streamed by the device |
Play(name, volume, pitch, pitchJitter, pan) |
Plays a sound; unknown names throw. The same sound is not retriggered twice in one step |
TryPlay(...) |
Same, but returns false for unregistered names |
PlayMusic(name, fadeSeconds = 0.4f) |
Switches track (see below); playing the current track again does nothing |
StopMusic(fadeSeconds = 0.4f), PauseMusic(bool) |
Stop with a fade, pause or resume |
SfxVolume (default 0.8), MusicVolume (0..1) |
Global volumes |
HasSound, HasMusic, CurrentMusic |
Queries |
Music changes are fade-out then fade-in, not an overlapping crossfade. With a fade, the current track fades to
silence over fadeSeconds, then the new one starts and rises to full volume in about a quarter of a second. With
fadeSeconds 0, or when nothing is playing, the new track starts at once. Only one music track plays at a time.
Neon Rift wraps the mixer so missing cues are recorded for its tests instead of crashing:
public void Music(string name){ if (name.Length == 0) Audio.StopMusic(0.4f); else if (Audio.HasMusic(name)) Audio.PlayMusic(name, 0.6f); else MissingSounds.Add(name);}Source: games/NeonRift/RiftGame.cs
Sound and music files
public static void Register(AudioMixer audio, ContentFolder content){ foreach (var name in Effects) audio.AddSoundFile(name, content.PathOf($"sfx/{name}.wav")); foreach (var (name, file, loop) in Tracks) audio.AddMusicFile(name, content.PathOf(file), loop);}Source: games/NeonRift/Art/Sounds.cs
| Format | Support |
|---|---|
| WAV | PCM 8, 16 or 24-bit, mono or stereo, decoded in managed code (AudioClip.FromWav); other WAV encodings throw NotSupportedException |
| OGG, MP3 | Music only, through AddMusicFile; the raylib device decodes and streams them |
Where content files live and how they are located is covered in Assets.
Devices and silence
RaylibAudioDevice.TryCreate() falls back to SilentAudioDevice when no audio device is available, so a machine
without sound still runs the game. Each sound keeps four voices so rapid repeats overlap instead of cutting each
other off. --mute on desktop (or RunOptions.Mute) sets both volumes to zero and uses the silent device.
HeadlessPlatform exposes its SilentAudioDevice, whose Played list lets tests assert which cues fired.
In the browser
Browsers do not start audio until the user interacts with the page. The web host waits for a click or key before
starting the game, so the audio context is created inside that gesture; if the browser still holds audio
suspended, the page shows a “Sound is blocked” button and any key or click resumes it. Development packages with
autostart=1 skip the gesture and stay silent. See web builds and
ADR 0008.
Limitations
- One music track at a time; no simultaneous layers or true crossfades.
- No positional audio beyond a per-play stereo
pan, no effects buses, no runtime pitch or volume changes on a sound that is already playing. - Compressed formats are supported for music only; sound effects must be WAV or synthesized.