Most game audio is one AudioStreamPlayer and a volume slider — until two sounds play at
once and one chops the other off, or the slider does nothing over half its travel because
nobody converted linear to decibels. AudioVault is the drop-in that handles the parts you
only notice after they sound wrong.
Godot 4.x · GDScript · no dependencies · ~160 lines you can read in one sitting.
- No SFX cutoff. A single shared player means every new sound cuts off the last one. AudioVault keeps a round-robin pool, so the last N sounds overlap cleanly instead of chopping each other.
- Correct volume. Bus volume is in decibels; sliders are 0..1 linear. Map them wrong (the usual bug) and the slider is useless over half its range. AudioVault converts properly, with a real mute floor at 0.
- Music crossfade. Swapping tracks with
.stream = xis a hard cut. AudioVault fades the old track out while the new one fades in.
- Copy
addons/audio_vault/into your project. - Project → Project Settings → Plugins → enable AudioVault
(or add
audio_vault.gdas an autoload namedAudioVaultyourself).
# Optional: create the named buses you'll mix on, once
AudioVault.ensure_buses(["Music", "SFX"])
AudioVault.sfx_bus = "SFX"
AudioVault.music_bus = "Music"
# Fire-and-forget sound effects — they never cut each other off
AudioVault.play_sfx(preload("res://sfx/hit.wav"))
AudioVault.play_sfx(preload("res://sfx/coin.wav"), -3.0, 1.2) # quieter, higher pitch
# Crossfade music between scenes
AudioVault.play_music(preload("res://music/forest.ogg"), 1.5)
AudioVault.play_music(preload("res://music/boss.ogg"), 2.0) # fades forest out, boss in
AudioVault.stop_music(1.0)The bus volume helpers take and return plain 0..1 values, so they drop straight onto an
HSlider and pair with a save system:
func _on_music_slider_changed(value: float) -> void:
AudioVault.set_bus_volume_linear("Music", value)
func _ready() -> void:
$MusicSlider.value = AudioVault.get_bus_volume_linear("Music")0.0 is true silence (clamped to a -80 dB floor, not -inf); 1.0 is 0 dB; everything
between is mapped through linear_to_db so the slider's travel matches what you hear.
| Call | Does |
|---|---|
play_sfx(stream, volume_db=0.0, pitch=1.0) -> AudioStreamPlayer |
Play a one-shot on the next pool player. Returns the player used. |
play_music(stream, fade_sec=1.0) -> void |
Crossfade to stream. No-op if it's already playing. |
stop_music(fade_sec=1.0) -> void |
Fade the current track out and stop it. |
current_music_stream() -> AudioStream |
The track playing as music, or null. |
set_bus_volume_linear(bus, linear) -> void |
Set a bus's volume from a 0..1 slider value. |
get_bus_volume_linear(bus) -> float |
Read a bus's volume back as 0..1. |
ensure_buses(names) -> void |
Create any named buses that don't exist yet. Idempotent. |
sfx_pool_size |
How many SFX can overlap before the oldest is reused (default 16). |
sfx_bus / music_bus |
Buses new players route to (default Master). |
tests/test_audio_vault.gd is a headless suite covering the pool round-robin, the
linear↔dB volume round-trip and mute floor, bus creation, and the music track-swap
bookkeeping. Run it:
godot --headless --path . -s tests/test_audio_vault.gd
MIT. Drop it in a commercial game, no attribution required.