Godot Audio (4.x)
Play SFX and music, route them through buses, control volume in decibels, and time
gameplay to the beat. Targets Godot 4.7.
When to use
- Use when playing sound effects or music, routing audio to buses (Master/Music/SFX),
adjusting volume/mute from code, adding bus effects (reverb, compressor), positional 3D
audio, or syncing events to music.
When not to use: engine-agnostic audio design (adaptive music structure, mixing
philosophy, ducking patterns) → audio-design; importing/encoding assets outside Godot.
Core workflow
- Pick the player node:
AudioStreamPlayer — non-positional (music, UI, global SFX).
AudioStreamPlayer2D / AudioStreamPlayer3D — positional; volume/pan from distance.
- Assign an
AudioStream to stream (.ogg for music/loops, .wav for short SFX)
and play(). Set autoplay for music that starts with the scene.
- Route to a bus. Set the player's
bus to a named bus (e.g. "Music", "SFX").
Define buses in the Audio panel (bottom dock); each can have volume, mute, solo, and
effects.
- Control volume in dB, not linear (audio is logarithmic).
0 dB = unchanged,
-80 dB ≈ silent. Convert with linear_to_db/db_to_linear.
- Drive volume/mute from code with
AudioServer by bus index.
- For rhythm, compute precise playback time using output latency compensation.
Patterns
1. One-shot SFX (fire-and-forget)
@onready var sfx: AudioStreamPlayer = $Sfx # stream assigned in the editor
func play_jump() -> void:
sfx.pitch_scale = randf_range(0.95, 1.05) # slight variation avoids fatigue
sfx.play()
# For many overlapping copies, use an AudioStreamPlayer with an
# AudioStreamPolyphonic stream, or spawn short-lived players and free on `finished`.
2. Set a bus's volume and mute via AudioServer
func set_music_volume(linear_0_to_1: float) -> void:
var bus := AudioServer.get_bus_index("Music")
# Convert a 0..1 slider to decibels; clamp avoids -inf at 0.
AudioServer.set_bus_volume_db(bus, linear_to_db(maxf(linear_0_to_1, 0.0001)))
func toggle_sfx(muted: bool) -> void:
AudioServer.set_bus_mute(AudioServer.get_bus_index("SFX"), muted)
3. Crossfade between two music tracks
@onready var a: AudioStreamPlayer = $MusicA
@onready var b: AudioStreamPlayer = $MusicB
func crossfade_to(stream: AudioStream, secs := 1.5) -> void:
b.stream = stream
b.volume_db = -40.0
b.play()
var tw := create_tween().set_parallel(true)
tw.tween_property(a, "volume_db", -40.0, secs) # fade out current
tw.tween_property(b, "volume_db", 0.0, secs) # fade in next
tw.chain().tween_callback(a.stop)
var tmp := a; a = b; b = tmp # swap roles
4. Beat-accurate timing (compensate for output latency)
@onready var music: AudioStreamPlayer = $Music
func get_playback_time() -> float:
# Add time since the last audio mix, subtract output latency, for sub-frame accuracy.
var t := music.get_playback_position() + AudioServer.get_time_since_last_mix()
return t - AudioServer.get_output_latency()
Pitfalls
- Treating volume as linear.
volume_db/set_bus_volume_db are decibels. Setting
volume_db = 0.5 is nearly full volume, not half. Map sliders with linear_to_db.
linear_to_db(0.0) is -inf. Clamp the linear value to a small minimum (e.g.
0.0001) before converting, or special-case 0 → mute.
- Bus name typos fail quietly.
get_bus_index("Muisc") returns -1; calls then error
or no-op. Match the exact bus name from the Audio panel.
- Short SFX cut off when the same player is retriggered. Use separate players, an
AudioStreamPolyphonic, or AudioStreamPlayer per-shot freed on finished.
- Music doesn't loop unless the import/stream loop is enabled (
.ogg import has a
Loop option; AudioStreamWAV has loop_mode).
- Syncing to
get_playback_position() alone is jittery — it updates per audio mix,
not per frame; add get_time_since_last_mix() and subtract get_output_latency().
- 3D audio inaudible → no
AudioListener3D/Camera3D to hear it, or max_distance/
attenuation too tight, or wrong bus muted.
References
- For the bus layout (
.tres), adding effects (reverb/compressor/EQ) and side-chain
ducking, AudioStreamPolyphonic/AudioStreamInteractive, microphone capture, and
procedural audio with AudioStreamGenerator, read references/buses-and-effects.md.
Related skills
audio-design — engine-agnostic adaptive music, mixing, and ducking practice.
godot-animation — syncing animation/Tween to get_playback_position().
godot-ui-control — volume sliders wired to AudioServer.
1---
2name: godot-audio
3description: >
4 Play and mix audio in Godot 4.7: AudioStreamPlayer (2D/3D variants), audio buses
5 with volume/mute and effects, music vs SFX routing, db/linear volume, and precise
6 sync-to-beat playback timing. Use when playing sounds or music in a Godot project,
7 routing AudioStreamPlayer nodes to buses, adjusting bus volume via AudioServer, or
8 syncing gameplay to the beat.
9---
10
11# Godot Audio (4.x)
12
13Play SFX and music, route them through buses, control volume in decibels, and time
14gameplay to the beat. Targets **Godot 4.7**.
15
16## When to use
17
18- Use when playing sound effects or music, routing audio to buses (Master/Music/SFX),
19 adjusting volume/mute from code, adding bus effects (reverb, compressor), positional 3D
20 audio, or syncing events to music.
21
22**When *not* to use:** engine-agnostic audio *design* (adaptive music structure, mixing
23philosophy, ducking patterns) → `audio-design`; importing/encoding assets outside Godot.
24
25## Core workflow
26
271. **Pick the player node:**
28 - `AudioStreamPlayer` — non-positional (music, UI, global SFX).
29 - `AudioStreamPlayer2D` / `AudioStreamPlayer3D` — positional; volume/pan from distance.
302. **Assign an `AudioStream`** to `stream` (`.ogg` for music/loops, `.wav` for short SFX)
31 and `play()`. Set `autoplay` for music that starts with the scene.
323. **Route to a bus.** Set the player's `bus` to a named bus (e.g. `"Music"`, `"SFX"`).
33 Define buses in the Audio panel (bottom dock); each can have volume, mute, solo, and
34 effects.
354. **Control volume in dB**, not linear (audio is logarithmic). `0 dB` = unchanged,
36 `-80 dB` ≈ silent. Convert with `linear_to_db`/`db_to_linear`.
375. **Drive volume/mute from code** with `AudioServer` by bus index.
386. **For rhythm**, compute precise playback time using output latency compensation.
39
40## Patterns
41
42### 1. One-shot SFX (fire-and-forget)
43
44```gdscript
45@onready var sfx: AudioStreamPlayer = $Sfx # stream assigned in the editor
46
47func play_jump() -> void:
48 sfx.pitch_scale = randf_range(0.95, 1.05) # slight variation avoids fatigue
49 sfx.play()
50
51# For many overlapping copies, use an AudioStreamPlayer with an
52# AudioStreamPolyphonic stream, or spawn short-lived players and free on `finished`.
53```
54
55### 2. Set a bus's volume and mute via AudioServer
56
57```gdscript
58func set_music_volume(linear_0_to_1: float) -> void:
59 var bus := AudioServer.get_bus_index("Music")
60 # Convert a 0..1 slider to decibels; clamp avoids -inf at 0.
61 AudioServer.set_bus_volume_db(bus, linear_to_db(maxf(linear_0_to_1, 0.0001)))
62
63func toggle_sfx(muted: bool) -> void:
64 AudioServer.set_bus_mute(AudioServer.get_bus_index("SFX"), muted)
65```
66
67### 3. Crossfade between two music tracks
68
69```gdscript
70@onready var a: AudioStreamPlayer = $MusicA
71@onready var b: AudioStreamPlayer = $MusicB
72
73func crossfade_to(stream: AudioStream, secs := 1.5) -> void:
74 b.stream = stream
75 b.volume_db = -40.0
76 b.play()
77 var tw := create_tween().set_parallel(true)
78 tw.tween_property(a, "volume_db", -40.0, secs) # fade out current
79 tw.tween_property(b, "volume_db", 0.0, secs) # fade in next
80 tw.chain().tween_callback(a.stop)
81 var tmp := a; a = b; b = tmp # swap roles
82```
83
84### 4. Beat-accurate timing (compensate for output latency)
85
86```gdscript
87@onready var music: AudioStreamPlayer = $Music
88
89func get_playback_time() -> float:
90 # Add time since the last audio mix, subtract output latency, for sub-frame accuracy.
91 var t := music.get_playback_position() + AudioServer.get_time_since_last_mix()
92 return t - AudioServer.get_output_latency()
93```
94
95## Pitfalls
96
97- **Treating volume as linear.** `volume_db`/`set_bus_volume_db` are decibels. Setting
98 `volume_db = 0.5` is nearly full volume, not half. Map sliders with `linear_to_db`.
99- **`linear_to_db(0.0)` is `-inf`.** Clamp the linear value to a small minimum (e.g.
100 `0.0001`) before converting, or special-case 0 → mute.
101- **Bus name typos fail quietly.** `get_bus_index("Muisc")` returns `-1`; calls then error
102 or no-op. Match the exact bus name from the Audio panel.
103- **Short SFX cut off** when the same player is retriggered. Use separate players, an
104 `AudioStreamPolyphonic`, or `AudioStreamPlayer` per-shot freed on `finished`.
105- **Music doesn't loop** unless the import/stream loop is enabled (`.ogg` import has a
106 Loop option; `AudioStreamWAV` has `loop_mode`).
107- **Syncing to `get_playback_position()` alone is jittery** — it updates per audio mix,
108 not per frame; add `get_time_since_last_mix()` and subtract `get_output_latency()`.
109- **3D audio inaudible** → no `AudioListener3D`/`Camera3D` to hear it, or `max_distance`/
110 attenuation too tight, or wrong bus muted.
111
112## References
113
114- For the bus layout (`.tres`), adding effects (reverb/compressor/EQ) and side-chain
115 ducking, `AudioStreamPolyphonic`/`AudioStreamInteractive`, microphone capture, and
116 procedural audio with `AudioStreamGenerator`, read `references/buses-and-effects.md`.
117
118## Related skills
119
120- `audio-design` — engine-agnostic adaptive music, mixing, and ducking practice.
121- `godot-animation` — syncing animation/Tween to `get_playback_position()`.
122- `godot-ui-control` — volume sliders wired to `AudioServer`.