Skip to content

Audio

SDL_mixer setup, sound and music nodes, mix buses, bus effects, level meters and adaptive music.

Files: audio.odin, audio_nodes.odin, audio_effects.odin, audio_meter.odin, music_layers.odin.

Audio: SDL3_mixer, playing on the default device. The same on the desktop and in the browser, where the web export links an SDL3_mixer built with Emscripten (WAV, OGG, MP3, FLAC, AIFF, VOC). Browsers only start sound after the player clicks or presses a key on the page; SDL resumes it then by itself.

Play sounds with Game_PlaySound, or with SDL3_mixer directly (game.mixer, and game.resourceManager->loadAudio for cached sounds).

Sound nodes and mix buses, on SDL3_mixer.

  • AudioPlayer plays a sound: volume, pitch (playback speed), pan, looping, starting by itself.
  • AudioPlayer2D is one in the world: panned by where it is relative to the listener (the scene’s camera, or the middle of the game area), and quieter with distance (silent past maxDistance; attenuation shapes the curve).
  • MusicPlayer streams music (a Music asset: decoded as it plays, not loaded whole), with fades, seeking and the same volume, pitch and pan.

Every player goes to a bus (player.bus; “” is Master), and every bus to Master. A bus has a volume, a pan and a mute; what a player sounds like is its own settings times its bus’s times Master’s (Audio_Output), so changing a bus changes everything on it at once. The player’s and its bus’s gains go on its track (every frame); Master’s go on the final mix (audio_meter.odin), so each bus’s level meter reads it after its fader, Master’s the output. Projects list their buses in project.xml (<bus name="Music" volume="0.8"/>, the editor’s Mixer tab); code can add more (Audio_AddBus). Each bus has an SDL3_mixer group its players’ tracks join, where effects will go.

Players in a scene that the game switches away from stop; ones that start by themselves start again when it’s back. Without an audio device everything still works, silently.

Example

jump := cast(^e.AudioPlayer)e.Scene_FindNode(scene, "JumpSound")
e.AudioPlayer_Play(jump)
e.Audio_SetBusVolume("Music", 0.5)
music := cast(^e.MusicPlayer)e.Scene_FindNode(scene, "Theme")
e.MusicPlayer_FadeOut(music, 2)

Audio effects on mix buses: what goes through a bus can be filtered (low-pass for muffled sound under water or behind a wall, high-pass for a thin radio voice), echoed, or given a room’s reverb.

A bus has a chain of up to AUDIO_MAX_EFFECTS effects (AudioEffect: plain settings), run in order on everything the bus mixed, before its meter; Master’s run on the final mix, before Master’s volume and pan. In project.xml they’re <effect> children of a <bus>:

<bus name="Music" volume="0.8">
<effect kind="LowPass" cutoff="900"/>
<effect kind="Reverb" roomSize="0.7" mix="0.25"/>
</bus>

From code, Audio_SetBusEffects replaces a bus’s chain and Audio_SetBusEffect changes one effect (a low-pass sweeping down as the player dives, say); Audio_GetBusEffects reads them.

The effects run on the audio thread, in the bus group’s post-mix callback (audio_meter.odin). A chain’s settings are changed under its lock, which the audio thread only tries to take: when it’s busy, that block plays with the previous settings, so the game never waits for audio and audio never waits for the game. Filters, delay lines and reverb tanks keep their state across blocks, sized for the stereo channels (other channels pass through untouched).

Level meters and output stages for mix buses, on SDL3_mixer’s mix callbacks (they run on the audio thread; everything here is shared through atomics).

A meter attached to a group (AudioMeter_AttachGroup) measures what the group’s tracks mixed together; one attached to a mixer (AudioMeter_AttachMixer) is the final stage: it applies its gains (AudioMeter_SetGains) to the whole mix, then measures it. The game’s buses use both: each bus’s tracks carry the player’s and the bus’s gains, so its group’s meter reads the bus after its fader; Master’s gain and pan are applied to the final mix, which Master’s meter reads. Editors use the same pieces for their own playback.

Example

left, right := e.Audio_GetBusLevel("Music")
db := e.AudioLevel_ToDecibels(max(left, right))

Music that changes with the game: crossfades between songs, and adaptive layers.

  • Crossfades (MusicPlayer_CrossfadeTo): the song playing fades out while the next fades in, both at once, on the player’s bus. The old song keeps playing until its fade ends, then is freed.
  • Layers (MusicPlayer_AddLayer, a MusicPlayer’s <layer> children in scene files): extra music files played in step with the main one (stems of one song: drums, strings, a danger theme), each at its own volume. They start on the same sample as the main music (SDL3_mixer’s tags) and loop with it. MusicPlayer_SetLayerVolume fades a layer in or out (combat starts: the drums come in over two seconds). Each layer goes through the player’s volume, pan and bus, times its own volume.

Layers’ files should be as long as the main one, so they loop together. A crossfade fades the layers out with the old song (the new song starts without them: add its own).

Example

// Given: music: ^e.MusicPlayer
e.MusicPlayer_AddLayer(music, "drums", "music/battle_drums.ogg", volume = 0)
e.AudioPlayer_Play(music)
// Later, in a fight:
e.MusicPlayer_SetLayerVolume(music, "drums", 1, seconds = 2)
AudioOutputSettings :: struct {
// Samples per second (44100, 48000...); 0 for the device's own
frequency: int,
// 1 mono, 2 stereo, up to 8; 0 for the device's own
channels: int,
// The playback device, by its name (`Audio_PlaybackDevices`); "" for the system's default
device: string,
}

How a game’s sound output is opened: the format the mixer asks the device for (SDL converts it to whatever the device really is), and which device. Projects set the format (project.xml’s audioFrequency, audioChannels); the device is the machine’s choice (the player’s --audio-device, the editor’s Settings for Play).

Asking for the device’s own format (0 for both) was the old way, and failed on outputs whose channel layout SDL doesn’t take (“Unsupported audio channels”: multichannel interfaces, some Bluetooth and HDMI outputs on macOS).

struct. Source: audio.odin:30

AudioBus :: struct {
// Its name (MASTER_BUS for Master), in the game's memory
name: string,
// 0 (silent) to 1 (unchanged); up to 2 boosts
volume: f32,
// -1 left, 0 center, 1 right
pan: f32,
// Silences it (and everything on it), keeping its volume
mute: bool,
// Its SDL3_mixer group (nil without a device)
group: ^mix.Group,
// Its level (`Audio_GetBusLevel`); Master's is the final mix, where its gain and pan apply
meter: ^AudioMeter,
// Its effects (audio_effects.odin), run on what it mixed before its meter
effects: ^AudioEffectChain,
}

A mix bus: what goes through it is scaled by its volume, panned by its pan, silenced by mute

struct. Source: audio_nodes.odin:43

AudioBusData :: struct {
// The bus's name ("Master" sets Master's own values)
name: string,
// 0 to 1, up to 2 boosts
volume: f32,
// -1 left, 0 center, 1 right
pan: f32,
// Starts muted
mute: bool,
// Its effects, in order (`<effect>` children: audio_effects.odin)
effects: [dynamic]AudioEffect,
}

A bus in project.xml: <bus name="Music" volume="0.8" pan="0" mute="false"/> (format 10)

struct. Source: audio_nodes.odin:61

AudioSettings :: struct {
// 0 to 1 (up to 2 boosts)
volume: f32,
// Playback speed: 1 is normal, 2 an octave up and twice as fast, 0.5 an octave down
pitch: f32,
// -1 left, 0 center, 1 right
pan: f32,
// Plays again from the start when it ends, until stopped
loop: bool,
// Starts when its scene first updates (and again each time the game comes back to it)
autostart: bool,
// AudioPlayer2D: silent this far (px) from the listener (default 1000)
maxDistance: f32,
// AudioPlayer2D: how the volume falls with distance (1 linear, 2 faster; `Audio_Positional`)
attenuation: f32,
// MusicPlayer: seconds to fade in when it starts (0: no fade)
fadeIn: f32,
// MusicPlayer: seconds to fade out when `AudioPlayer_Stop` stops it (0: no fade)
fadeOut: f32,
}

A sound node’s settings (scene files store those that differ from DEFAULT_AUDIO_SETTINGS)

struct. Source: audio_nodes.odin:75

AudioPlayerKind :: enum {
// A sound, not placed
Sound,
// A sound in the world: panned and fading with distance from the listener
Positional,
// Music, streamed
Music,
}

Which kind of sound node

enum. Source: audio_nodes.odin:105

AudioPlayer :: struct {
// The node part: transform (a 2D player's position), hierarchy, hooks
using node: Node,
// Sound, Positional or Music (set by `CreateAudioPlayer`)
kind: AudioPlayerKind,
// Volume, pitch, pan, looping... (read every frame)
settings: AudioSettings,
// The bus it goes to ("" or "Master": straight to Master); in the node's memory
bus: string,
// The sound or music file, relative to the project folder (`AudioPlayer_SetSound`)
sound: string,
// The loaded sound or music (nil: none, or no audio device)
audio: ^mix.Audio,
// Music is the node's own (streamed); sounds are the resource manager's (shared)
ownsAudio: bool,
// Its SDL3_mixer track (made by the first Play; nil without a device)
track: ^mix.Track,
// Playing (or paused) since the last Play, until it stops or ends
playing: bool,
// Paused by `AudioPlayer_Pause`
paused: bool,
// Autostart happened this time its scene is current
started: bool,
// MusicPlayer: layers played in step with it, and songs fading out after a crossfade
// (music_layers.odin)
layers: [dynamic]MusicLayer,
fading: [dynamic]FadingTrack,
// What it played ended on its own (not looping, not stopped); noticed in its update
onFinished: EventSubject `event:"What it played ended on its own (not when stopped; a looping sound never ends)"`,
}

A node that plays a sound (AudioPlayer, AudioPlayer2D) or music (MusicPlayer: the same struct, kind says which). Change settings at any time: they apply the next frame.

struct. Source: audio_nodes.odin:118

AudioPlayer2D :: AudioPlayer

A positional sound player; the same struct as AudioPlayer

type. Source: audio_nodes.odin:150

MusicPlayer :: AudioPlayer

A music player; the same struct as AudioPlayer

type. Source: audio_nodes.odin:152

AudioEffectKind :: enum {
// Lets lows through, cuts above `cutoff`: muffled, far, under water
LowPass,
// Lets highs through, cuts below `cutoff`: thin, a radio, a phone
HighPass,
// Repeats the sound after `delay` seconds, each repeat `feedback` as loud
Echo,
// A room's reflections: `roomSize` (small to hall), `damping` (bright to dull)
Reverb,
}

What an effect does

enum. Source: audio_effects.odin:33

AudioEffect :: struct {
// What it does
kind: AudioEffectKind,
// Skipped (kept in the chain, doing nothing)
bypass: bool,
// LowPass, HighPass: the corner frequency in Hz (20 to 20000)
cutoff: f32 `min:"20" max:"20000"`,
// LowPass, HighPass: how much the corner rings (0.5 soft, 0.707 flat, up to 10 a sharp peak)
resonance: f32 `min:"0.3" max:"10"`,
// Echo: seconds between repeats (0.01 to 2)
delay: f32 `min:"0.01" max:"2"`,
// Echo: how loud each repeat is, relative to the one before (0 to 0.95)
feedback: f32 `min:"0" max:"0.95"`,
// Reverb: 0 a small room to 1 a hall
roomSize: f32 `min:"0" max:"1"`,
// Reverb: 0 bright reflections to 1 dull ones
damping: f32 `min:"0" max:"1"`,
// Echo, Reverb: how much of the effect is heard (0 none, 1 only the effect)
mix: f32 `min:"0" max:"1"`,
}

One effect on a bus (scene and project files store those that differ from DEFAULT_AUDIO_EFFECT).

struct. Source: audio_effects.odin:48

AudioEffectChain :: struct {
lock: sync.Atomic_Mutex,
// The effects, `count` of them
effects: [AUDIO_MAX_EFFECTS]AudioEffect,
count: int,
// Each effect's state (heap, made by AudioEffects_Set for its kind, freed when replaced), and
// the sample rate they were set up for
states: [AUDIO_MAX_EFFECTS]^EffectState,
rate: f32,
}

A bus’s effect chain and the state its effects keep between blocks (filters’ memory, the echo’s delay line, the reverb’s tanks). Shared with the audio thread: change it through AudioEffects_Set only.

struct. Source: audio_effects.odin:84

AudioMeter :: struct {
// The loudest sample per side since the last read: f32 bits, atomic
peakLeft, peakRight: u32,
// A final stage's gain per side: f32 bits, atomic
gainLeft, gainRight: u32,
// Whether the gains are applied (only a mixer's meter)
applyGains: bool,
// Effects run on the block before it's measured (a bus's chain: audio_effects.odin; nil none)
effects: ^AudioEffectChain,
}

A level meter: a stage’s peak levels since they were last read, and (a final stage) the gains it applies. Shared with the audio thread: read and set it through the procedures, never the fields. Zero-initialized is ready to attach.

struct. Source: audio_meter.odin:30

MusicLayer :: struct {
// Its name (to set its volume), in the node's memory
name: string,
// Its music file, relative to the project folder, in the node's memory
sound: string,
// The loaded music (the player's own; nil without a device or when it couldn't be read)
audio: ^mix.Audio,
// Its SDL3_mixer track (made when added; nil without a device)
track: ^mix.Track,
// Its volume now (0 to 1, up to 2 boosts), the volume it's fading to, and how fast (per second)
volume, target, speed: f32,
}

A layer of a MusicPlayer: a music file played in step with the main one

struct. Source: music_layers.odin:34

MusicLayerData :: struct {
name: string,
sound: string,
volume: f32,
}

A layer in a scene file: <layer name="drums" sound="music/drums.ogg" volume="0"/> (format 13)

struct. Source: music_layers.odin:48

Audio_Init :: proc(output := DEFAULT_AUDIO_OUTPUT) -> (^mix.Mixer, bool)

Starts SDL3_mixer and opens a playback device (Audio_OpenMixer). A machine without one (or a browser that refuses) isn’t an error: the game runs silently.

Inputs

  • output: The format and device to ask for

Returns

  • The mixer (nil when silent), and false only if SDL3_mixer itself couldn’t start

procedure. Source: audio.odin:52

Audio_OpenMixer :: proc(output := DEFAULT_AUDIO_OUTPUT) -> ^mix.Mixer

Opens a playback device for SDL3_mixer, trying until one opens: the asked device (else the default) with the asked format, then stereo at 48 and 44.1 kHz, mono, 16-bit stereo, the device’s own format; then the default device; then every other device in stereo. Logs the device and format it got (“Audio: MacBook Speakers, 48000 Hz, 2 channels”), and what failed before it. SDL3_mixer must be started (mix.Init; Audio_Init does it).

Inputs

  • output: The format and device to ask for

Returns

  • The mixer, or nil (logged) when no device opens: the game runs silently

Example

// Given: mixer: ^mix.Mixer (an editor's own previews, apart from any game)
mixer = e.Audio_OpenMixer({frequency = 44100, channels = 2})

procedure. Source: audio.odin:77

Audio_PlaybackDevices :: proc(allocator := context.allocator) -> []string

The names of the playback devices SDL sees now (for picking one: AudioOutputSettings.device, the player’s --audio-device). SDL’s audio must be started (SDL_Init’s .AUDIO).

Inputs

  • allocator: For the list and the names

Returns

  • The names; empty when there’s no device (or no audio)

procedure. Source: audio.odin:137

Audio_Quit :: proc(mixer: ^mix.Mixer)

Closes the playback device and SDL3_mixer. Application_Cleanup calls it.

Inputs

  • mixer: The mixer from Audio_Init (nil when silent)

procedure. Source: audio.odin:153

Audio_Load :: proc(mixer: ^mix.Mixer, path: string, predecode: bool) -> ^mix.Audio

Loads a sound file, from the data pack if it’s there, else from disk. Most code loads through ResourceManager (loadAudio), which caches the sound.

Inputs

  • mixer: The mixer to load it for
  • path: The file’s path
  • predecode: Decode it all now (short effects); false streams it while it plays (music)

Returns

  • The sound (free it with Audio_Destroy), or nil if it can’t be read (SDL’s error says why)

procedure. Source: audio.odin:170

Audio_Destroy :: proc(audio: ^mix.Audio)

Frees a sound loaded with Audio_Load. Sounds in the resource manager are freed by it instead.

Inputs

  • audio: The sound

procedure. Source: audio.odin:184

Game_PlaySoundFile :: proc(game: ^Game, path: string) -> bool

Plays a sound file once, loading it the first time (it stays cached in the resource manager). Game_PlaySound with a path.

Inputs

  • game: The game
  • path: The sound file, relative to the project folder (game.projectDir) like other assets

Returns

  • false if there’s no audio device, or (logged) it couldn’t be loaded or played

Example

e.Game_PlaySound(game, "assets/laser.wav") // or a SoundAsset (assets.odin)

procedure. Source: audio.odin:202

CreateAudioPlayer :: proc(scene: ^Scene, kind := AudioPlayerKind.Sound, settings := DEFAULT_AUDIO_SETTINGS) -> ^AudioPlayer

Creates a sound node of a kind, on Master. Set its sound with AudioPlayer_SetSound (after it’s in a scene of a game) and its bus with AudioPlayer_SetBus.

Inputs

  • scene: The scene
  • kind: Sound, Positional (AudioPlayer2D) or Music (MusicPlayer)
  • settings: Volume, pitch, pan… (DEFAULT_AUDIO_SETTINGS unless given)

Returns

  • The player

Example

music := e.CreateAudioPlayer(scene, .Music)
music.settings.loop = true
music.settings.fadeIn = 1.5
e.AudioPlayer_SetSound(music, "music/theme.ogg")
e.AudioPlayer_SetBus(music, "Music")
e.AudioPlayer_Play(music)

procedure. Source: audio_nodes.odin:174

AudioPlayer_SetSound :: proc(player: ^AudioPlayer, path: string)

Sets what a player plays, stopping what played (no fade). Sounds are loaded once by the resource manager and shared; music streams from its file and is the node’s own. Nothing is loaded when the scene has no game or the game has no audio device (a warning when music can’t be opened).

Inputs

  • player: The player
  • path: A sound or music file, relative to the project folder (“” for none)

procedure. Source: audio_nodes.odin:197

AudioPlayer_SetBus :: proc(player: ^AudioPlayer, bus: string)

Sets the bus a player goes to; it takes effect at once, also while playing.

Inputs

  • player: The player
  • bus: The bus’s name (“” or “Master” for Master; an unknown bus is Master too)

procedure. Source: audio_nodes.odin:222

AudioPlayer_Play :: proc(player: ^AudioPlayer)

Plays from the start (again, if it was playing), looping if settings.loop. Music fades in over settings.fadeIn. Without a sound or a device, it only marks the player as playing.

Inputs

  • player: The player

procedure. Source: audio_nodes.odin:234

AudioPlayer_Stop :: proc(player: ^AudioPlayer, fade := true)

Stops playing. Music fades out over settings.fadeOut unless fade is false.

Inputs

  • player: The player
  • fade: Fade music out (sounds stop at once either way)

procedure. Source: audio_nodes.odin:263

AudioPlayer_Pause :: proc(player: ^AudioPlayer)

Pauses where it is (AudioPlayer_Resume carries on). Does nothing if it isn’t playing.

Inputs

  • player: The player

procedure. Source: audio_nodes.odin:280

AudioPlayer_Resume :: proc(player: ^AudioPlayer)

Carries on from where AudioPlayer_Pause paused it. Does nothing if it isn’t paused.

Inputs

  • player: The player

procedure. Source: audio_nodes.odin:293

AudioPlayer_IsPlaying :: proc(player: ^AudioPlayer) -> bool

Whether a player is playing.

Inputs

  • player: The player

Returns

  • true if it was played and isn’t paused, stopped or finished (a finished sound is noticed on its next update)

procedure. Source: audio_nodes.odin:310

MusicPlayer_FadeOut :: proc(player: ^MusicPlayer, seconds: f32)

Fades music out and stops it, whatever its settings.fadeOut.

Inputs

  • player: The music player
  • seconds: How long the fade takes (0: stops at once)

procedure. Source: audio_nodes.odin:321

MusicPlayer_GetPosition :: proc(player: ^MusicPlayer) -> f32

Where the music is.

Inputs

  • player: The music player

Returns

  • Seconds from its start (0 before it has played)

procedure. Source: audio_nodes.odin:338

MusicPlayer_GetDuration :: proc(player: ^MusicPlayer) -> f32

How long the music is.

Inputs

  • player: The music player

Returns

  • Seconds (0 if unknown, as for some streams, or with no music loaded)

procedure. Source: audio_nodes.odin:352

MusicPlayer_Seek :: proc(player: ^MusicPlayer, seconds: f32)

Jumps to a time in the music. Does nothing before it has played.

Inputs

  • player: The music player
  • seconds: From the start (negative is 0)

procedure. Source: audio_nodes.odin:366

Audio_SetBuses :: proc(game: ^Game, buses: []AudioBusData)

Sets the game’s buses, replacing any: Game_LoadProject does it with the project’s. Master is always there (the first), whether listed or not. The buses live in the game’s memory.

Inputs

  • game: The game
  • buses: The buses (a “Master” entry sets Master’s volume, pan and mute)

procedure. Source: audio_nodes.odin:383

Audio_AddBus :: proc(name: string, volume: f32 = 1, game: ^Game = nil) -> ^AudioBus

Adds a bus from code, going to Master. If one by that name already exists, it’s kept as it is.

Inputs

  • name: The bus’s name
  • volume: Its volume (0 to 1, up to 2 boosts)
  • game: The game (nil: the current game)

Returns

  • The bus (nil without a game). The pointer is valid until the next bus is added

Example

e.Audio_AddBus("Voices", 0.9)
e.Audio_SetBusMute("Voices", true)

procedure. Source: audio_nodes.odin:438

Audio_GetBus :: proc(name: string, game: ^Game = nil) -> ^AudioBus

A bus by name. A game with no buses yet gets Master.

Inputs

  • name: The bus’s name (“” is Master)
  • game: The game (nil: the current game)

Returns

  • The bus, or nil if there’s none by that name. The pointer is valid until a bus is added

procedure. Source: audio_nodes.odin:466

Audio_SetBusVolume :: proc(name: string, volume: f32, game: ^Game = nil)

Sets a bus’s volume (everything on it changes from the next frame). Unknown buses are ignored.

Inputs

  • name: The bus’s name (“” is Master)
  • volume: 0 (silent) to 1 (unchanged), up to 2 boosts; negative is 0
  • game: The game (nil: the current game)

Example

e.Audio_SetBusVolume("Music", 0.5)

procedure. Source: audio_nodes.odin:486

Audio_SetBusPan :: proc(name: string, pan: f32, game: ^Game = nil)

Sets a bus’s pan (a balance). Unknown buses are ignored.

Inputs

  • name: The bus’s name (“” is Master)
  • pan: -1 left, 0 center, 1 right (clamped)
  • game: The game (nil: the current game)

procedure. Source: audio_nodes.odin:498

Audio_SetBusMute :: proc(name: string, mute: bool, game: ^Game = nil)

Mutes or unmutes a bus, keeping its volume. Unknown buses are ignored.

Inputs

  • name: The bus’s name (“” is Master)
  • mute: Silence it
  • game: The game (nil: the current game)

procedure. Source: audio_nodes.odin:510

Audio_Output :: proc(game: ^Game, bus: string, volume, pan: f32, master := true) -> (gain, left, right: f32)

What a player on a bus sounds like: its volume times its bus’s and Master’s (0 if either is muted), and its stereo gains, its pan’s times theirs. A pan is a balance: centered changes nothing, fully right silences the left side. An unknown bus is Master.

Inputs

  • game: The game (nil: the current game)
  • bus: The player’s bus (“” is Master)
  • volume, pan: The player’s own (positional players: with distance and position applied)
  • master: Include Master’s stage (false: the track’s part; the final mix applies Master’s)

Returns

  • The track gain, and the left and right gains

procedure. Source: audio_nodes.odin:528

Audio_Positional :: proc(position, listener: [2]f32, settings: AudioSettings, viewWidth: f32) -> (volume, pan: f32)

A positional sound’s volume factor and pan, from its world position and the listener’s (the scene’s camera, or the middle of the game area). Silent at maxDistance; between, the volume falls as (1 - distance / maxDistance) ^ attenuation. Pan follows the horizontal offset, full at half the view’s width.

Inputs

  • position: The sound’s world position (pixels)
  • listener: The listener’s world position
  • settings: The player’s settings (maxDistance, attenuation)
  • viewWidth: The width the listener hears across, in pixels (the game’s width)

Returns

  • volume: 0 to 1, to multiply the player’s volume by
  • pan: -1 to 1, to add to the player’s pan

procedure. Source: audio_nodes.odin:561

Audio_SetBusEffects :: proc(name: string, effects: []AudioEffect, game: ^Game = nil)

Replaces a bus’s effects (audio_effects.odin): they run on what it mixes from its next block.

Inputs

  • name: The bus (“” is Master)
  • effects: Up to AUDIO_MAX_EFFECTS, in order
  • game: The game (nil: the current game)

Example

// Under water: everything muffled
e.Audio_SetBusEffects("", {e.AudioEffect_Make(.LowPass)})

procedure. Source: audio_nodes.odin:605

Audio_SetBusEffect :: proc(name: string, index: int, effect: AudioEffect, game: ^Game = nil)

Changes one of a bus’s effects (a filter swept while the game plays keeps sounding smooth).

Example

// Given: depth: f32
muffle := e.AudioEffect_Make(.LowPass)
muffle.cutoff = 4000 - depth * 30
e.Audio_SetBusEffect("Music", 0, muffle)

procedure. Source: audio_nodes.odin:618

Audio_GetBusEffects :: proc(name: string, game: ^Game = nil) -> []AudioEffect

A bus’s effects (a temp copy; none for a bus that doesn’t exist)

procedure. Source: audio_nodes.odin:623

AudioEffect_Make :: proc(kind: AudioEffectKind) -> AudioEffect

An effect of a kind with the default settings

procedure. Source: audio_effects.odin:73

AudioEffects_Set :: proc(chain: ^AudioEffectChain, effects: []AudioEffect)

Replaces a chain’s effects (the audio thread picks them up on its next block; their state starts over). At most AUDIO_MAX_EFFECTS are kept.

procedure. Source: audio_effects.odin:104

AudioEffects_Destroy :: proc(chain: ^AudioEffectChain)

Frees a chain’s effects’ state (the chain must no longer be attached: the bus’s group destroyed)

procedure. Source: audio_effects.odin:121

AudioEffects_Get :: proc(chain: ^AudioEffectChain) -> []AudioEffect

A copy of a chain’s effects (temp allocated)

procedure. Source: audio_effects.odin:129

AudioEffects_SetOne :: proc(chain: ^AudioEffectChain, index: int, effect: AudioEffect)

Changes one effect’s settings, keeping its state when its kind stays (a filter swept while it plays doesn’t click).

procedure. Source: audio_effects.odin:142

AudioEffects_Process :: proc "contextless" (chain: ^AudioEffectChain, pcm: [^]f32, samples, channels: int, rate: f32)

Runs a chain over a block of interleaved samples, in place. Called on the audio thread (from a post-mix callback); when the chain is being changed, the block passes through unchanged.

Inputs

  • chain: The chain (nil does nothing)
  • pcm: Interleaved samples, samples of them
  • channels: Channels per frame
  • rate: Frames per second

procedure. Source: audio_effects.odin:170

AudioMeter_Read :: proc(meter: ^AudioMeter) -> (left, right: f32)

The loudest samples per side since the last read, then starts over. A meter shows the largest level it read and lets it fall; reading every frame suits that.

Inputs

  • meter: The meter (nil reads silence)

Returns

  • left, right: 0 (silence) to 1 (full scale); more when the mix clips

procedure. Source: audio_meter.odin:51

AudioMeter_SetGains :: proc(meter: ^AudioMeter, left, right: f32)

Sets the gains a final stage (AudioMeter_AttachMixer) applies to the mix, from the next mixed block. A group’s meter ignores them.

Inputs

  • meter: The meter (nil does nothing)
  • left, right: Gains per side (1 unchanged, 0 silences that side; negative is 0)

procedure. Source: audio_meter.odin:66

AudioMeter_AttachGroup :: proc(meter: ^AudioMeter, group: ^mix.Group)

Measures a group’s mix: the meter becomes the group’s post-mix callback (replacing any). The meter must outlive the attachment (until the group is destroyed).

Inputs

  • meter: The meter
  • group: The SDL3_mixer group (nil does nothing)

procedure. Source: audio_meter.odin:80

AudioMeter_AttachMixer :: proc(meter: ^AudioMeter, mixer: ^mix.Mixer)

Makes the meter a mixer’s final stage (its post-mix callback, replacing any): its gains are applied to the whole mix, then it’s measured. A meter whose gains are both 0 starts at 1, 1. The meter must outlive the attachment.

Inputs

  • meter: The meter
  • mixer: The SDL3_mixer mixer (nil does nothing)

procedure. Source: audio_meter.odin:94

Audio_GetBusLevel :: proc(name: string, game: ^Game = nil) -> (left, right: f32)

A bus’s level since the last call (for Master: the final mix). Reading it starts the peaks over, so one reader per bus (a mixer view reading every frame).

Inputs

  • name: The bus’s name (“” is Master)
  • game: The game (nil: the current game)

Returns

  • left, right: 0 to 1 (0 without an audio device, or for a bus that doesn’t exist)

Example

left, right := e.Audio_GetBusLevel("Music")
db := e.AudioLevel_ToDecibels(max(left, right))
// A bar over the top 60 dB
bar := sdl.FRect{20, 20, 200 * max(1 + db / 60, 0), 8}
e.DrawRectangle(renderer, &bar, {80, 220, 120, 255})

procedure. Source: audio_meter.odin:119

AudioLevel_ToDecibels :: proc(level: f32) -> f32

A level in decibels relative to full scale.

Inputs

  • level: 0 to 1 (more when clipping)

Returns

  • dBFS: 0 at full scale, negative below it, never under AUDIO_SILENCE_DB (silence)

procedure. Source: audio_meter.odin:133

MusicPlayer_AddLayer :: proc(player: ^MusicPlayer, name, path: string, volume: f32 = 1) -> ^MusicLayer

Adds a layer to a music player. When the player is playing, the layer starts at once, in step.

Inputs

  • player: The music player
  • name: The layer’s name (a name already used replaces that layer)
  • path: Its music file, relative to the project folder
  • volume: Its volume (0 silent: fade it in later with MusicPlayer_SetLayerVolume)

Returns

  • The layer (nil when the player isn’t a MusicPlayer); valid until a layer is added or removed

procedure. Source: music_layers.odin:73

MusicPlayer_RemoveLayer :: proc(player: ^MusicPlayer, name: string) -> bool

Removes a layer (stopping it). Returns whether there was one by that name

procedure. Source: music_layers.odin:109

MusicPlayer_SetLayerVolume :: proc(player: ^MusicPlayer, name: string, volume: f32, seconds: f32 = 0) -> bool

Fades a layer to a volume.

Inputs

  • player: The music player
  • name: The layer
  • volume: 0 silent, 1 full (up to 2 boosts)
  • seconds: How long the fade takes (0: at once)

Returns

  • Whether there was a layer by that name

procedure. Source: music_layers.odin:132

MusicPlayer_GetLayer :: proc(player: ^MusicPlayer, name: string) -> ^MusicLayer

A layer by name (nil: none)

procedure. Source: music_layers.odin:147

MusicPlayer_CrossfadeTo :: proc(player: ^MusicPlayer, path: string, seconds: f32)

Fades from the song playing to another: the old one fades out while the new one fades in, over seconds. Not playing: it plays the new one, fading in. The old song’s layers fade out with it.

Inputs

  • player: The music player
  • path: The next song, relative to the project folder
  • seconds: How long the crossfade takes

Example

// Given: music: ^e.MusicPlayer
e.MusicPlayer_CrossfadeTo(music, "music/boss.ogg", 3)

procedure. Source: music_layers.odin:166

DEFAULT_AUDIO_OUTPUT :: AudioOutputSettings{frequency = 48000, channels = 2}

Stereo at 48 kHz: what every device can be given (a project’s output without settings)

constant. Source: audio.odin:40

MASTER_BUS :: "Master"

The bus every player and bus ends in

constant. Source: audio_nodes.odin:40

DEFAULT_AUDIO_SETTINGS :: AudioSettings{
volume = 1,
pitch = 1,
maxDistance = 1000,
attenuation = 1,
}

A new sound node’s settings: full volume, normal pitch, centered, 1000 px reach, linear falloff

constant. Source: audio_nodes.odin:97

AUDIO_MAX_EFFECTS :: 8

The most effects a bus has

constant. Source: audio_effects.odin:30

DEFAULT_AUDIO_EFFECT :: AudioEffect{cutoff = 1000, resonance = 0.707, delay = 0.3, feedback = 0.4, roomSize = 0.5, damping = 0.5, mix = 0.3}

A new effect’s settings

constant. Source: audio_effects.odin:70

AUDIO_MAX_ECHO :: 2

The longest echo (seconds)

constant. Source: audio_effects.odin:96

AUDIO_MAX_RATE :: 96000

The highest sample rate the delay lines are sized for

constant. Source: audio_effects.odin:98

AUDIO_SILENCE_DB :: f32(-100)

Quieter than this is silence, for meters and faders

constant. Source: audio_meter.odin:139