godot-audio-systems

Expert patterns for Godot audio including AudioStreamPlayer variants (2D positional, 3D spatial), AudioBus mixing architecture, dynamic effects (reverb, EQ,compression), audio pooling for performance, music transitions (crossfade, bpm-sync), and procedural audio generation. Use for music systems, sound effects, spatial audio, or audio-reactive gameplay. Trigger keywords: AudioStreamPlayer, AudioStreamPlayer2D, AudioStreamPlayer3D, AudioBus, AudioServer, AudioEffect, music_crossfade, audio_pool, positional_audio, reverb, bus_volume.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "godot-audio-systems" with this command: npx skills add thedivergentai/gd-agentic-skills/thedivergentai-gd-agentic-skills-godot-audio-systems

Audio Systems

Expert guidance for Godot's audio engine and mixing architecture.

NEVER Do

  • NEVER create new AudioStreamPlayer nodes for every sound — Causes memory bloat and GC spikes. Use audio pooling (reuse players) or one-shot helper function.
  • NEVER set AudioServer bus volume with linear valuesset_bus_volume_db() expects decibels (-80 to 0). Use linear_to_db() for 0.0-1.0 conversion.
  • NEVER forget to set autoplay = false on music players — Music autoplays on scene load by default. Causes overlapping tracks when changing scenes.
  • NEVER use AudioStreamPlayer3D without attenuation model — Default attenuation is NONE (no falloff). Set attenuation_model to ATTENUATION_INVERSE_DISTANCE or audio is global.
  • NEVER play AudioStreamPlayer without checking playing first — Restarting an already-playing sound cuts it off. Check if not player.playing: before play().

Available Scripts

MANDATORY: Read the appropriate script before implementing the corresponding pattern.

audio_manager.gd

AudioManager singleton with sound pooling (32-player pool), bus assignment, and crossfade preparation. Prevents node spam and GC spikes.

audio_visualizer.gd

Real-time FFT spectrum analysis. Captures low/mid/high frequency ranges to drive visual effects like lighting pulses or shader parameters.


AudioStreamPlayer Variants

AudioStreamPlayer (Global/UI)

# No spatial positioning, same volume everywhere
# Use for: Music, UI sounds, voiceovers

@onready var music := AudioStreamPlayer.new()

func _ready() -> void:
    music.stream = load("res://audio/music_main.ogg")
    music.volume_db = -10  # Quieter
    music.autoplay = false
    music.bus = "Music"  # Route to Music bus
    add_child(music)
    music.play()

AudioStreamPlayer2D (Positional)

# 2D panning based on distance from camera
# Use for: 2D games, top-down audio cues

extends Area2D

@onready var footstep := AudioStreamPlayer2D.new()

func _ready() -> void:
    footstep.stream = load("res://audio/footstep.ogg")
    footstep.max_distance = 500  # Audible range (pixels)
    footstep.attenuation = 2.0  # Falloff curve (higher = faster fadeout)
    add_child(footstep)

func play_footstep() -> void:
    if not footstep.playing:
        footstep.play()

AudioStreamPlayer3D (Spatial)

# 3D spatial audio with doppler, reverb send
# Use for: 3D games, realistic sound positioning

extends Node3D

@onready var explosion := AudioStreamPlayer3D.new()

func _ready() -> void:
    explosion.stream = load("res://audio/explosion.ogg")
    explosion.unit_size = 10.0  # Size of sound source
    explosion.max_distance = 100.0  # Range
    explosion.attenuation_model = AudioStreamPlayer3D.ATTENUATION_INVERSE_DISTANCE
    explosion.doppler_tracking = AudioStreamPlayer3D.DOPPLER_TRACKING_PHYSICS_STEP
    add_child(explosion)
    
    explosion.play()

AudioBus Architecture

Bus Setup (Project Settings)

Master (always exists)
  ├─ Music
  │   └─ Effects: Compressor, EQ
  ├─ SFX
  │   └─ Effects: Reverb (for environment)
  └─ Ambient
      └─ Effects: LowPassFilter (muffled ambience)

Volume Control (Decibels)

# ❌ BAD: Linear volume (doesn't work)
AudioServer.set_bus_volume_db(music_bus_idx, 0.5)  # WRONG!

# ✅ GOOD: Use decibels
var music_bus := AudioServer.get_bus_index("Music")
AudioServer.set_bus_volume_db(music_bus, -10)  # -10 dB (quieter)

# Convert linear (0.0-1.0) to dB:
var linear_volume := 0.5  # 50%
var db := linear_to_db(linear_volume)  # ~-6 dB
AudioServer.set_bus_volume_db(music_bus, db)

# Convert dB to linear:
var current_db := AudioServer.get_bus_volume_db(music_bus)
var linear := db_to_linear(current_db)
print("Current volume: %d%%" % int(linear * 100))

Mute Bus

func toggle_mute(bus_name: String) -> void:
    var bus_idx := AudioServer.get_bus_index(bus_name)
    var is_muted := AudioServer.is_bus_mute(bus_idx)
    AudioServer.set_bus_mute(bus_idx, not is_muted)

Audio Pooling (Performance)

Problem: Creating Players Every Frame

# ❌ BAD: Creates 60 new nodes/second at 60 FPS
func play_footstep() -> void:
    var player := AudioStreamPlayer.new()
    add_child(player)
    player.stream = load("res://audio/footstep.ogg")
    player.finished.connect(player.queue_free)
    player.play()
    # Result: 3600 nodes created in 1 minute!

Solution: Audio Pool

# audio_pool.gd (AutoLoad)
extends Node

const POOL_SIZE = 10
var pool: Array[AudioStreamPlayer] = []
var pool_index := 0

func _ready() -> void:
    # Pre-create players
    for i in range(POOL_SIZE):
        var player := AudioStreamPlayer.new()
        player.bus = "SFX"
        add_child(player)
        pool.append(player)

func play_sound(stream: AudioStream, volume_db := 0.0) -> void:
    var player := pool[pool_index]
    pool_index = (pool_index + 1) % POOL_SIZE  # Round-robin
    
    # Stop previous sound if still playing
    if player.playing:
        player.stop()
    
    player.stream = stream
    player.volume_db = volume_db
    player.play()

# Usage:
AudioPool.play_sound(load("res://audio/coin.ogg"), -5.0)

Music Transitions

Crossfade Between Tracks

# music_manager.gd (AutoLoad)
extends Node

@onready var track_a := AudioStreamPlayer.new()
@onready var track_b := AudioStreamPlayer.new()

var current_track: AudioStreamPlayer
var fade_duration := 2.0

func _ready() -> void:
    track_a.bus = "Music"
    track_b.bus = "Music"
    add_child(track_a)
    add_child(track_b)
    current_track = track_a

func crossfade_to(new_stream: AudioStream) -> void:
    var next_track := track_b if current_track == track_a else track_a
    
    # Start new track at 0 dB
    next_track.stream = new_stream
    next_track.volume_db = -80  # Silent
    next_track.play()
    
    # Fade out current, fade in next
    var tween := create_tween().set_parallel(true)
    tween.tween_property(current_track, "volume_db", -80, fade_duration)
    tween.tween_property(next_track, "volume_db", 0, fade_duration)
    
    await tween.finished
    
    # Stop old track
    current_track.stop()
    current_track = next_track

BPM-Synced Transitions

# Transition on beat boundary
var bpm := 120.0  # Beats per minute
var beat_duration := 60.0 / bpm  # 0.5s per beat

func queue_transition_on_beat(new_stream: AudioStream) -> void:
    # Wait for next beat
    var current_time := current_track.get_playback_position()
    var time_to_next_beat := beat_duration - fmod(current_time, beat_duration)
    
    await get_tree().create_timer(time_to_next_beat).timeout
    crossfade_to(new_stream)

Dynamic Audio Effects

Add Effect at Runtime

# Add reverb to SFX bus
var sfx_bus := AudioServer.get_bus_index("SFX")
var reverb := AudioEffectReverb.new()
reverb.room_size = 0.8  # Large room
reverb.damping = 0.5
reverb.wet = 0.3  # 30% effect, 70% dry
AudioServer.add_bus_effect(sfx_bus, reverb)

Underwater Effect

func set_underwater(enabled: bool) -> void:
    var sfx_bus := AudioServer.get_bus_index("SFX")
    
    if enabled:
        # Add low-pass filter (muffled sound)
        var lowpass := AudioEffectLowPassFilter.new()
        lowpass.cutoff_hz = 500  # Cut frequencies above 500 Hz
        AudioServer.add_bus_effect(sfx_bus, lowpass)
    else:
        # Remove all effects
        for i in range(AudioServer.get_bus_effect_count(sfx_bus)):
            AudioServer.remove_bus_effect(sfx_bus, 0)

Procedural Audio

Synthesize Beep

# Generate simple sine wave
func create_beep(frequency: float, duration: float) -> AudioStreamGenerator:
    var stream := AudioStreamGenerator.new()
    stream.mix_rate = 44100  # Sample rate
    
    var playback := stream.instantiate_playback()
    
    var increment := frequency / stream.mix_rate
    var phase := 0.0
    
    for i in range(int(stream.mix_rate * duration)):
        var sample := sin(phase * TAU)
        playback.push_frame(Vector2(sample, sample))  # Stereo
        phase += increment
        phase = fmod(phase, 1.0)
    
    return stream

# Usage:
var beep_stream := create_beep(440.0, 0.1)  # 440 Hz (A4), 0.1s
$AudioStreamPlayer.stream = beep_stream
$AudioStreamPlayer.play()

Advanced Patterns

Audio Ducking (Lower Music During Dialogue)

# auto_duck.gd (on Dialogue AudioStreamPlayer)
extends AudioStreamPlayer

func _ready() -> void:
    playing.connect(_on_playing)
    finished.connect(_on_finished)

func _on_playing() -> void:
    # Duck music to -15 dB
    var music_bus := AudioServer.get_bus_index("Music")
    var tween := create_tween()
    tween.tween_method(set_music_volume, 0.0, -15.0, 0.5)

func _on_finished() -> void:
    # Restore music to 0 dB
    var tween := create_tween()
    tween.tween_method(set_music_volume, -15.0, 0.0, 0.5)

func set_music_volume(db: float) -> void:
    var music_bus := AudioServer.get_bus_index("Music")
    AudioServer.set_bus_volume_db(music_bus, db)

Randomize Pitch for Variation

# Prevent identical sounds (footsteps, gunshots)
func play_varied_sound(stream: AudioStream) -> void:
    $AudioStreamPlayer.stream = stream
    $AudioStreamPlayer.pitch_scale = randf_range(0.9, 1.1)  # ±10% pitch
    $AudioStreamPlayer.play()

Layered Music (Adaptive)

# Intensity-based music layers (start quiet, add layers as intensity increases)
# Example: Peaceful exploration → Combat

@onready var layer_drums := $Music/Drums
@onready var layer_bass := $Music/Bass
@onready var layer_melody := $Music/Melody

var intensity := 0.0  # 0.0 = calm, 1.0 = intense

func _ready() -> void:
    # Start all layers in sync
    layer_drums.play()
    layer_bass.play()
    layer_melody.play()
    
    # Mute high-intensity layers
    layer_bass.volume_db = -80
    layer_melody.volume_db = -80

func set_music_intensity(new_intensity: float) -> void:
    intensity = clamp(new_intensity, 0.0, 1.0)
    
    # Fade in layers based on intensity
    var tween := create_tween().set_parallel(true)
    
    # Layer 1 (drums): always audible
    tween.tween_property(layer_drums, "volume_db", 0, 1.0)
    
    # Layer 2 (bass): fade in at 33% intensity
    var bass_db := -80 if intensity < 0.33 else lerp(-80.0, 0.0, (intensity - 0.33) / 0.67)
    tween.tween_property(layer_bass, "volume_db", bass_db, 1.0)
    
    # Layer 3 (melody): fade in at 66% intensity
    var melody_db := -80 if intensity < 0.66 else lerp(-80.0, 0.0, (intensity - 0.66) / 0.34)
    tween.tween_property(layer_melody, "volume_db", melody_db, 1.0)

# Usage (combat system):
func _on_enemy_spotted() -> void:
    MusicManager.set_music_intensity(1.0)  # Full intensity

func _on_all_enemies_defeated() -> void:
    MusicManager.set_music_intensity(0.0)  # Back to calm

Performance Optimization

Disable Far Audio

# Don't play sounds the player can't hear
extends AudioStreamPlayer3D

func _process(delta: float) -> void:
    var listener := get_viewport().get_camera_3d()
    if not listener:
        return
    
    var distance := global_position.distance_to(listener.global_position)
    
    if distance > max_distance * 1.5:  # 1.5x max range
        if playing:
            stop()

Edge Cases

Audio Doesn't Play

# Check:
# 1. Is stream assigned?
if not $AudioStreamPlayer.stream:
    push_error("No audio stream assigned!")

# 2. Is bus muted?
var bus_idx := AudioServer.get_bus_index($AudioStreamPlayer.bus)
if AudioServer.is_bus_mute(bus_idx):
    print("Bus is muted!")

# 3. Is volume too low?
if $AudioStreamPlayer.volume_db < -60:
    print("Volume too quiet (< -60 dB)")

Decision Matrix: Which AudioStreamPlayer?

FeatureAudioStreamPlayerAudioStreamPlayer2DAudioStreamPlayer3D
Spatial❌ Global✅ 2D panning✅ 3D positioning
Doppler
Attenuation✅ Distance-based✅ 3D falloff
Reverb send
Use forMusic, UI2D games3D games
PerformanceFastestMediumSlowest

Reference

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

godot-master

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-shaders-basics

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-ui-theming

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-particles

No summary provided by upstream source.

Repository SourceNeeds Review