Skip to content
Back to skills

Audio Patterns

ASecurity

Game audio code patterns: a Godot bus layout, an audio player pool, randomized one-shot playback, vertical-remix dynamic music, 3D occlusion approximation, and FMOD integration in Godot. Use when writing sound playback, music, or mixing code for a game.

  • 65 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 1, 2026
developmentgonode

Works with

  • cli

Security analysis

A100/100

Scanned October 1, 2026

npx -y skills add HermeticOrmus/claude-code-game-development --skill audio-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Audio Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Audio Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/hermeticormus-audio-patterns/badge)](https://www.skillsdirectory.com/skills/hermeticormus-audio-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: audio-patterns
description: "Game audio code patterns: a Godot bus layout, an audio player pool, randomized one-shot playback, vertical-remix dynamic music, 3D occlusion approximation, and FMOD integration in Godot. Use when writing sound playback, music, or mixing code for a game."
---

# Audio Patterns

## Audio Bus Architecture (Godot)

```gdscript
# AudioBusLayout setup - configure programmatically or in AudioBusLayout editor
# Bus hierarchy: Master -> Music, SFX, Voice, Ambient
# Each sub-bus routes to Master, apply effects on sub-buses

class_name AudioManager extends Node
# Bus names must match AudioBusLayout asset
const BUS_MASTER  := &"Master"
const BUS_MUSIC   := &"Music"
const BUS_SFX     := &"SFX"
const BUS_VOICE   := &"Voice"
const BUS_AMBIENT := &"Ambient"

func set_music_volume(db: float) -> void:
    var bus_idx := AudioServer.get_bus_index(BUS_MUSIC)
    AudioServer.set_bus_volume_db(bus_idx, db)

func duck_music(duck_db: float = -12.0, duration: float = 0.3) -> void:
    # Duck music when important SFX or voice plays
    var bus_idx := AudioServer.get_bus_index(BUS_MUSIC)
    var tween := create_tween()
    tween.tween_method(
        func(v: float): AudioServer.set_bus_volume_db(bus_idx, v),
        AudioServer.get_bus_volume_db(bus_idx),
        duck_db,
        duration
    )

func unduck_music(duration: float = 0.5) -> void:
    var bus_idx := AudioServer.get_bus_index(BUS_MUSIC)
    var tween := create_tween()
    tween.tween_method(
        func(v: float): AudioServer.set_bus_volume_db(bus_idx, v),
        AudioServer.get_bus_volume_db(bus_idx),
        0.0,
        duration
    )
```

## Audio Pool Implementation

```gdscript
# Fixed-size audio pool prevents per-sound node allocation
class_name AudioPool extends Node
const POOL_SIZE := 16

var _players: Array[AudioStreamPlayer] = []
var _priorities: Array[float] = []

func _ready() -> void:
    for i in POOL_SIZE:
        var player := AudioStreamPlayer.new()
        player.bus = &"SFX"
        add_child(player)
        player.finished.connect(_on_player_finished.bind(i))
        _players.append(player)
        _priorities.append(0.0)

func play(stream: AudioStream, volume_db: float = 0.0, priority: float = 1.0) -> AudioStreamPlayer:
    # Find free player
    for i in POOL_SIZE:
        if not _players[i].playing:
            return _start_player(i, stream, volume_db, priority)

    # Pool exhausted: steal lowest priority
    var lowest_idx := 0
    for i in POOL_SIZE:
        if _priorities[i] < _priorities[lowest_idx]:
            lowest_idx = i
    return _start_player(lowest_idx, stream, volume_db, priority)

func _start_player(idx: int, stream: AudioStream, volume_db: float, priority: float) -> AudioStreamPlayer:
    _players[idx].stream = stream
    _players[idx].volume_db = volume_db
    _players[idx].play()
    _priorities[idx] = priority
    return _players[idx]

func _on_player_finished(idx: int) -> void:
    _priorities[idx] = 0.0

# 3D variant - takes world position for spatialized pool
class AudioPool3D extends Node:
    # Same pattern but uses AudioStreamPlayer3D with position assignment
    pass
```

## Randomized Sound Playback

```gdscript
# Prevents "machine gun effect" - repeated identical sounds
class_name RandomizedAudioPlayer extends Node
@export var streams: Array[AudioStream]
@export var pitch_range: Vector2 = Vector2(0.9, 1.1)
@export var volume_range_db: Vector2 = Vector2(-3.0, 0.0)
@export var bus: StringName = &"SFX"

var _last_index: int = -1

func play_random() -> void:
    if streams.is_empty():
        return
    # Avoid playing the same clip twice in a row
    var idx := randi() % streams.size()
    if streams.size() > 1 and idx == _last_index:
        idx = (idx + 1) % streams.size()
    _last_index = idx

    var player := AudioStreamPlayer.new()
    player.stream = streams[idx]
    player.pitch_scale = randf_range(pitch_range.x, pitch_range.y)
    player.volume_db = randf_range(volume_range_db.x, volume_range_db.y)
    player.bus = bus
    player.autoplay = true
    player.finished.connect(player.queue_free)
    add_child(player)
```

## Dynamic Music System (Vertical Remixing)

```gdscript
# Vertical remixing: multiple stems fade in/out based on game state
class_name MusicSystem extends Node
enum MusicState { EXPLORATION, TENSION, COMBAT, BOSS }

@export var stems: Dictionary = {
    # Format: stem_name -> AudioStreamPlayer
}

var _current_state: MusicState = MusicState.EXPLORATION
var _tween: Tween

# Stem volume levels per state [exploration, tension, combat, boss]
const STEM_LEVELS := {
    "drums":   [0.0,  0.5,  1.0,  1.0],
    "bass":    [0.3,  0.6,  1.0,  1.0],
    "melody":  [1.0,  0.8,  0.3,  0.0],
    "strings": [0.8,  0.5,  0.0,  0.0],
    "intense": [0.0,  0.2,  0.8,  1.0],
}

func set_music_state(state: MusicState, fade_time: float = 2.0) -> void:
    if state == _current_state:
        return
    _current_state = state

    if _tween:
        _tween.kill()
    _tween = create_tween()
    _tween.set_parallel(true)

    for stem_name in STEM_LEVELS:
        if not stems.has(stem_name):
            continue
        var target_volume: float = STEM_LEVELS[stem_name][state]
        var player: AudioStreamPlayer = stems[stem_name]
        # Convert 0-1 to dB (-80 = silent, 0 = full)
        var target_db := linear_to_db(target_volume) if target_volume > 0.0 else -80.0
        _tween.tween_property(player, "volume_db", target_db, fade_time)

# Beat-synchronized transition (horizontal re-sequencing)
func transition_at_next_bar(bpm: float, current_beat: float) -> void:
    var beat_duration := 60.0 / bpm
    var bar_duration := beat_duration * 4.0
    var beats_until_next_bar := 4.0 - fmod(current_beat, 4.0)
    var delay := beats_until_next_bar * beat_duration
    get_tree().create_timer(delay).timeout.connect(_crossfade_to_next_section)
```

## 3D Audio Occlusion Approximation

```gdscript
# Simple raycast occlusion - attenuates audio through walls
class_name AudioOcclusionSystem extends Node
@export var occlude_db_per_wall: float = -12.0
@export var max_walls: int = 3
@export var update_rate_hz: float = 10.0  # Don't update every frame

var _timer: float = 0.0
var _listener: Node3D

func _physics_process(delta: float) -> void:
    _timer += delta
    if _timer < 1.0 / update_rate_hz:
        return
    _timer = 0.0
    _update_occlusion()

func _update_occlusion() -> void:
    if not _listener:
        _listener = get_viewport().get_camera_3d()
        if not _listener:
            return

    var space := get_world_3d().direct_space_state
    for child in get_children():
        if child is AudioStreamPlayer3D:
            _occlude_player(child, space)

func _occlude_player(player: AudioStreamPlayer3D, space: PhysicsDirectSpaceState3D) -> void:
    var from := _listener.global_position
    var to := player.global_position
    var params := PhysicsRayQueryParameters3D.create(from, to, 0b0001)  # environment layer
    params.hit_back_faces = true

    var walls := 0
    var pos := from
    while walls < max_walls:
        var result := space.intersect_ray(params)
        if not result:
            break
        walls += 1
        params.from = result.position + (to - pos).normalized() * 0.01

    # Apply occlusion as volume offset (player's base volume + occlusion)
    player.volume_db = player.volume_db + (occlude_db_per_wall * walls)
```

## FMOD Integration Pattern (Godot GDNative/GDExtension)

```gdscript
# FMOD event playback via GDExtension wrapper (e.g., fmod-gdextension)
class_name FMODAudioManager extends Node
# Assumes fmod-gdextension is installed and FMOD singleton available

func play_one_shot(event_path: String, position: Vector3 = Vector3.ZERO) -> void:
    # 2D event (UI, music - no position)
    if position == Vector3.ZERO:
        FMOD.play_one_shot(event_path)
    else:
        # 3D spatialized event
        FMOD.play_one_shot_at_position(event_path, position)

func create_instance(event_path: String) -> Object:
    return FMOD.create_event_instance(event_path)

func set_combat_intensity(intensity: float) -> void:
    # Drive FMOD global parameter from game state (0.0 = calm, 1.0 = intense)
    FMOD.set_global_parameter_by_name("CombatIntensity", intensity)
```

## Anti-Patterns

- **AudioStreamPlayer per sound effect**: Creates and destroys nodes every sound. Use the AudioPool pattern above.
- **Hard stop on music change**: Always crossfade. A 0.5-2 second overlap eliminates perceived cuts.
- **All sounds on Master bus**: No independent volume control, no effects chain separation, no ducking.
- **Identical pitch footsteps**: Monotonous. Randomize pitch 0.9-1.1 and cycle through 3-5 variants.
- **3D audio with default max_distance=0**: Godot default is 0 = no distance limit. Always set max_distance for 3D emitters.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…