---
description: Set up audio — bus layout, music manager, SFX pool, spatial audio, volume UI
user-invocable: false
---
# /godot-audio — Audio : bus, musique, SFX, spatial, volume

> **Synonymes** : audio, son, sound, music, musique, sfx, bus, volume, crossfade, spatial

Tu es un expert audio pour Godot 4.x. Ton rôle : architecturer et implémenter un système audio complet — bus layout, musique avec crossfade, SFX avec pooling, audio spatial, volume settings UI.

## Sommaire

1. [Audio Bus Layout — Fondamentaux](#section-1--audio-bus-layout--fondamentaux)
2. [AudioStreamPlayer — Les 3 types](#section-2--audiostreamplayer--les-3-types)
3. [MusicManager Autoload](#section-3--musicmanager-autoload)
4. [SFXPool — Gestion des effets sonores](#section-4--sfxpool--gestion-des-effets-sonores)
5. [Audio Spatial 2D](#section-5--audio-spatial-2d)
6. [Volume Settings UI](#section-6--volume-settings-ui)
7. [Audio Effects](#section-7--audio-effects)
8. [Recettes complètes](#section-8--recettes-complètes)
9. [Checklist Review](#section-9--checklist-review)
10. [Pièges courants](#section-10--pièges-courants)

---

## Philosophie

- **Au service du gameplay** — l'audio guide l'émotion, renforce les actions
- **Cohérence** — même type d'action = même type de son
- **Performance** — pool de players, pas d'instanciation runtime
- **Simplicité** — ConfigFile pour les settings, pas de base de données

---

## Section 1 : Audio Bus Layout — Fondamentaux

### Concept

Godot utilise un système de **bus audio** inspiré des DAW (Digital Audio Workstations).

- Tous les sons passent par un bus (Master par défaut)
- Les bus peuvent être routés les uns vers les autres
- Le routing est **right-to-left** : SFX → Master signifie que SFX envoie vers Master
- Contrôler le volume d'un bus affecte tous les sons qui passent par lui

### Bus minimaux

| Bus | Usage | Parent |
|-----|-------|--------|
| **Master** | Sortie finale, tout y passe | - |
| **Music** | Musiques de fond | Master |
| **SFX** | Effets sonores gameplay | Master |
| **UI** (optionnel) | Sons d'interface | Master |
| **Ambient** (optionnel) | Sons d'ambiance (vent, eau) | Master |

### Échelle de volume (dB)

| dB | Perception | Usage |
|----|------------|-------|
| 0 dB | Maximum (digital full scale) | Peak, ne jamais dépasser |
| -3 dB | ~70% du max | Bon niveau général |
| -6 dB | Moitié perçue | Musique de fond |
| -12 dB | Quart perçu | Ambiance discrète |
| -20 dB | Background subtil | Vent, eau, loin |
| -80 dB | Silence pratique | Mute |

### Configuration via code

Le layout par défaut est chargé depuis `res://default_bus_layout.tres`. Tu peux aussi créer les bus via code (utile pour un setup dynamique).

```gdscript
## audio_setup.gd — Créer les bus au boot
extends Node

func _ready():
    # Vérifier si les bus existent déjà
    if AudioServer.get_bus_index("Music") == -1:
        _create_bus_layout()

func _create_bus_layout():
    # Master existe toujours (index 0)
    # Ajouter Music
    var music_idx = AudioServer.bus_count
    AudioServer.add_bus(music_idx)
    AudioServer.set_bus_name(music_idx, "Music")
    AudioServer.set_bus_send(music_idx, "Master")

    # Ajouter SFX
    var sfx_idx = AudioServer.bus_count
    AudioServer.add_bus(sfx_idx)
    AudioServer.set_bus_name(sfx_idx, "SFX")
    AudioServer.set_bus_send(sfx_idx, "Master")

    # Ajouter UI (optionnel)
    var ui_idx = AudioServer.bus_count
    AudioServer.add_bus(ui_idx)
    AudioServer.set_bus_name(ui_idx, "UI")
    AudioServer.set_bus_send(ui_idx, "Master")
```

> **Recommandé** : Configurer le layout dans l'éditeur (Audio > Show Audio Buses), sauvegarder dans `res://default_bus_layout.tres`, et laisser Godot le charger automatiquement.

---

## Section 2 : AudioStreamPlayer — Les 3 types

Godot a trois types de players audio, selon le besoin.

### Comparatif

| Type | Usage | Positionnement | Bus typique |
|------|-------|----------------|-------------|
| **AudioStreamPlayer** | Musique, sons UI, narration | Global, non-spatial | Music, UI |
| **AudioStreamPlayer2D** | SFX gameplay, pas, impacts | Positionnel 2D (panning + volume selon distance) | SFX |
| **AudioStreamPlayer3D** | SFX 3D | Positionnel 3D (rare en 2D) | SFX |

### Quand utiliser quoi

- **Musique** → `AudioStreamPlayer` sur bus Music
- **Sons UI** (clic, hover) → `AudioStreamPlayer` sur bus UI
- **SFX gameplay** (impact, tir, pas) → `AudioStreamPlayer2D` sur bus SFX
- **Ambiance sans position** (vent global) → `AudioStreamPlayer` sur bus Ambient
- **Ambiance positionnelle** (cascade) → `AudioStreamPlayer2D` sur bus Ambient

### Formats recommandés

| Format | Usage | Raison |
|--------|-------|--------|
| **WAV** | SFX courts (< 2s) | Pas de decode overhead, playback instantané |
| **OGG** | Musique, ambiances longues | Streaming, fichiers légers |
| **MP3** | Éviter | Latence de decode, artefacts de compression |

### AudioStreamRandomizer

Pour varier les SFX sans coder.

```gdscript
## Créer un AudioStreamRandomizer
func _ready():
    var randomizer = AudioStreamRandomizer.new()
    randomizer.add_stream(0, preload("res://audio/sfx/jump_01.wav"))
    randomizer.add_stream(1, preload("res://audio/sfx/jump_02.wav"))
    randomizer.add_stream(2, preload("res://audio/sfx/jump_03.wav"))
    randomizer.random_pitch = 1.2  # Variation de pitch (0.8-1.2)
    randomizer.random_volume_offset_db = 2.0  # Variation de volume (-2 à 0 dB)

    $AudioStreamPlayer.stream = randomizer
    $AudioStreamPlayer.play()
```

---

## Section 3 : MusicManager Autoload

Architecture classique pour un système de musique avec crossfade.

### Principe

Deux `AudioStreamPlayer` alternent : pendant que l'un joue, l'autre fade in la prochaine piste. Pas de silence entre les morceaux.

### Code complet

```gdscript
## music_manager.gd — Autoload
extends Node

@export var fade_duration: float = 1.0
@export var playlist: Array[AudioStream] = []
@export var shuffle: bool = false

var player_a: AudioStreamPlayer
var player_b: AudioStreamPlayer
var current_player: AudioStreamPlayer
var next_player: AudioStreamPlayer

var current_track_index: int = -1
var is_playing: bool = false

func _ready():
    # Créer les deux players
    player_a = AudioStreamPlayer.new()
    player_a.bus = "Music"
    add_child(player_a)

    player_b = AudioStreamPlayer.new()
    player_b.bus = "Music"
    add_child(player_b)

    current_player = player_a
    next_player = player_b

    # Connecter les signaux de fin
    player_a.finished.connect(_on_track_finished)
    player_b.finished.connect(_on_track_finished)

func change_music(stream: AudioStream, fade: float = -1.0):
    """Change la musique avec crossfade"""
    if fade < 0:
        fade = fade_duration

    # Fade out le player actuel
    if current_player.playing:
        var tween_out = create_tween()
        tween_out.tween_property(current_player, "volume_db", -80.0, fade)
        tween_out.tween_callback(current_player.stop)

    # Swap les players
    var temp = current_player
    current_player = next_player
    next_player = temp

    # Setup le nouveau
    current_player.stream = stream
    current_player.volume_db = -80.0
    current_player.play()

    # Fade in
    var tween_in = create_tween()
    tween_in.tween_property(current_player, "volume_db", 0.0, fade)

    is_playing = true

func stop_music(fade: float = -1.0):
    """Arrête la musique avec fade out"""
    if fade < 0:
        fade = fade_duration / 2.0

    if current_player.playing:
        var tween = create_tween()
        tween.tween_property(current_player, "volume_db", -80.0, fade)
        tween.tween_callback(current_player.stop)

    is_playing = false

func play_playlist():
    """Lance la playlist depuis le début"""
    if playlist.is_empty():
        push_error("MusicManager: playlist is empty")
        return

    if shuffle:
        playlist.shuffle()

    current_track_index = 0
    change_music(playlist[current_track_index])

func next_track():
    """Passe au morceau suivant (manuel ou auto)"""
    if playlist.is_empty():
        return

    current_track_index = (current_track_index + 1) % playlist.size()
    change_music(playlist[current_track_index])

func _on_track_finished():
    """Auto-advance dans la playlist"""
    if is_playing and not playlist.is_empty():
        next_track()
```

### Usage

```gdscript
## Dans une scène
func _ready():
    # Changer de musique
    MusicManager.change_music(preload("res://audio/music/level1.ogg"))

func _on_boss_fight_start():
    # Crossfade rapide vers musique de boss
    MusicManager.change_music(preload("res://audio/music/boss.ogg"), 0.5)

func _on_pause():
    # Baisser le volume au lieu de stop
    var tween = create_tween()
    tween.tween_property(MusicManager.current_player, "volume_db", -20.0, 0.3)

func _on_resume():
    var tween = create_tween()
    tween.tween_property(MusicManager.current_player, "volume_db", 0.0, 0.3)
```

---

## Section 4 : SFXPool — Gestion des effets sonores

Créer un `AudioStreamPlayer` à chaque tir = instanciation coûteuse. Solution : pool de players pré-instanciés.

### Principe

- 8-16 players créés au boot
- `play()` cherche un player disponible (pas en train de jouer)
- Si tous occupés, prendre le plus ancien
- Throttle : même son ne peut pas jouer deux fois en < 80ms

### Code complet

```gdscript
## sfx_pool.gd — Autoload
extends Node

@export var pool_size: int = 12

var _pool: Array[AudioStreamPlayer] = []
var _last_played: Dictionary = {}  # stream path → timestamp
var _throttle_ms: int = 80

func _ready():
    # Créer le pool
    for i in pool_size:
        var player = AudioStreamPlayer.new()
        player.bus = "SFX"
        add_child(player)
        _pool.append(player)

func play(stream: AudioStream, pitch_scale: float = 1.0, volume_db: float = 0.0):
    """Joue un son avec pitch et volume custom"""
    if not stream:
        return

    # Throttle check
    var stream_path = stream.resource_path
    var now = Time.get_ticks_msec()
    if _last_played.has(stream_path):
        var last = _last_played[stream_path]
        if now - last < _throttle_ms:
            return  # Trop tôt

    _last_played[stream_path] = now

    # Trouver un player disponible
    var player = _get_available_player()
    player.stream = stream
    player.pitch_scale = pitch_scale
    player.volume_db = volume_db
    player.play()

func play_random(stream: AudioStream):
    """Joue un son avec variation aléatoire"""
    var pitch = randf_range(0.9, 1.1)
    var volume = randf_range(-2.0, 0.0)
    play(stream, pitch, volume)

func _get_available_player() -> AudioStreamPlayer:
    """Retourne le premier player libre, ou le plus ancien si tous occupés"""
    # Chercher un player libre
    for player in _pool:
        if not player.playing:
            return player

    # Tous occupés : prendre le premier (le plus ancien)
    return _pool[0]
```

### Usage

```gdscript
## Dans un personnage
func _on_jump():
    SFXPool.play_random(preload("res://audio/sfx/jump.wav"))

func _on_shoot():
    SFXPool.play(preload("res://audio/sfx/laser.wav"), 1.0, -3.0)

func _on_hurt():
    # Pitch plus bas = son plus grave = lourdeur
    SFXPool.play(preload("res://audio/sfx/hurt.wav"), 0.8)
```

> **Throttle avancé** : Si tu as besoin d'un throttle plus sophistiqué (par position, par type d'action), voir `/godot-behavior` Section 7.

---

## Section 5 : Audio Spatial 2D

Les `AudioStreamPlayer2D` ont du panning et de l'attenuation selon la distance à la caméra.

### Propriétés clés

| Propriété | Effet | Valeur recommandée |
|-----------|-------|--------------------|
| **max_distance** | Distance au-delà de laquelle le son est inaudible | 500-2000 selon le type de son |
| **attenuation** | Courbe de falloff. 1.0 = linéaire, 2.0 = inverse square (réaliste) | 2.0 |
| **panning_strength** | Intensité du panning stéréo. 0 = mono, 1 = full stereo | 0.7-1.0 |

### Pattern — Zone audio (ambiance positionnelle)

Une cascade, un feu de camp, une machine bruyante.

```gdscript
## zone_audio.gd — Attacher à une Area2D
extends Area2D

@export var audio_stream: AudioStream
@export var max_volume_db: float = 0.0
@export var fade_duration: float = 1.0

@onready var player: AudioStreamPlayer2D = $AudioStreamPlayer2D

func _ready():
    player.stream = audio_stream
    player.max_distance = 500.0
    player.attenuation = 2.0
    player.volume_db = -80.0  # Start muted
    player.autoplay = true

    body_entered.connect(_on_body_entered)
    body_exited.connect(_on_body_exited)

func _on_body_entered(body: Node2D):
    if body.is_in_group("player"):
        var tween = create_tween()
        tween.tween_property(player, "volume_db", max_volume_db, fade_duration)

func _on_body_exited(body: Node2D):
    if body.is_in_group("player"):
        var tween = create_tween()
        tween.tween_property(player, "volume_db", -80.0, fade_duration)
```

### Pattern — Footsteps positionnels

Les pas sont joués à la position du personnage, pas en global.

```gdscript
## character.gd
@onready var footstep_player: AudioStreamPlayer2D = $FootstepPlayer

var footstep_sounds: Array[AudioStream] = [
    preload("res://audio/sfx/step_01.wav"),
    preload("res://audio/sfx/step_02.wav"),
    preload("res://audio/sfx/step_03.wav")
]

func _play_footstep():
    """Appelé par AnimationPlayer ou Timer"""
    footstep_player.stream = footstep_sounds.pick_random()
    footstep_player.pitch_scale = randf_range(0.9, 1.1)
    footstep_player.play()
```

---

## Section 6 : Volume Settings UI

Contrôler le volume des bus via des sliders. Persister les settings dans un fichier.

### Conversion dB ↔ linear

Les sliders vont de 0.0 à 1.0 (linéaire), mais `AudioServer` attend des dB.

| Linear | dB |
|--------|----|
| 1.0 | 0 dB |
| 0.5 | ~-6 dB |
| 0.1 | ~-20 dB |
| 0.0 | -INF (silence) |

Godot fournit : `linear_to_db(value)` et `db_to_linear(db)`

### Code complet

```gdscript
## volume_settings.gd — Attacher à un panneau de settings
extends Control

@onready var master_slider: HSlider = $VBoxContainer/MasterSlider
@onready var music_slider: HSlider = $VBoxContainer/MusicSlider
@onready var sfx_slider: HSlider = $VBoxContainer/SFXSlider

var config: ConfigFile

func _ready():
    config = ConfigFile.new()

    # Charger les settings
    _load_settings()

    # Connecter les sliders
    master_slider.value_changed.connect(_on_master_changed)
    music_slider.value_changed.connect(_on_music_changed)
    sfx_slider.value_changed.connect(_on_sfx_changed)

func _on_master_changed(value: float):
    _set_bus_volume("Master", value)
    _save_settings()

func _on_music_changed(value: float):
    _set_bus_volume("Music", value)
    _save_settings()

func _on_sfx_changed(value: float):
    _set_bus_volume("SFX", value)
    _save_settings()

func _set_bus_volume(bus_name: String, linear_value: float):
    var bus_idx = AudioServer.get_bus_index(bus_name)
    if bus_idx == -1:
        return

    # Clamper à 0.01 pour éviter -INF
    linear_value = maxf(linear_value, 0.01)
    var db = linear_to_db(linear_value)
    AudioServer.set_bus_volume_db(bus_idx, db)

func _save_settings():
    config.set_value("audio", "master", master_slider.value)
    config.set_value("audio", "music", music_slider.value)
    config.set_value("audio", "sfx", sfx_slider.value)
    config.save("user://settings.cfg")

func _load_settings():
    var err = config.load("user://settings.cfg")
    if err != OK:
        # Defaults
        master_slider.value = 1.0
        music_slider.value = 0.8
        sfx_slider.value = 1.0
        return

    master_slider.value = config.get_value("audio", "master", 1.0)
    music_slider.value = config.get_value("audio", "music", 0.8)
    sfx_slider.value = config.get_value("audio", "sfx", 1.0)

    # Appliquer
    _set_bus_volume("Master", master_slider.value)
    _set_bus_volume("Music", music_slider.value)
    _set_bus_volume("SFX", sfx_slider.value)
```

### Pattern — Mute toggle

```gdscript
## Dans le même script
@onready var mute_button: CheckButton = $MuteButton

func _ready():
    mute_button.toggled.connect(_on_mute_toggled)

func _on_mute_toggled(muted: bool):
    var master_idx = AudioServer.get_bus_index("Master")
    AudioServer.set_bus_mute(master_idx, muted)
```

---

## Section 7 : Audio Effects

Godot permet d'ajouter des effets sur les bus (comme un plugin VST).

### Effets utiles

| Effet | Usage | Bus typique |
|-------|-------|-------------|
| **AudioEffectCompressor** | Anti-clipping, même volume perçu | Master |
| **AudioEffectReverb** | Sensation d'espace (cave, église) | Bus dédié "Reverb" |
| **AudioEffectDelay** | Écho, depth | SFX ou bus dédié |
| **AudioEffectLowPassFilter** | Effet "underwater", pause menu | Master (dynamic) |
| **AudioEffectEQ** | Ajuster les fréquences (basses, mediums, aigus) | Master ou Music |

### Pattern — Compression sur Master

Évite que le mix ne sature.

```gdscript
## Dans un script de setup audio
func _ready():
    var master_idx = AudioServer.get_bus_index("Master")

    var compressor = AudioEffectCompressor.new()
    compressor.threshold = -6.0  # Commence à compresser au-dessus de -6 dB
    compressor.ratio = 4.0  # 4:1 = pour 4 dB au-dessus du threshold, n'en laisse passer que 1
    compressor.gain = 3.0  # Boost après compression
    compressor.attack_us = 20.0  # Réactivité rapide
    compressor.release_ms = 100.0

    AudioServer.add_bus_effect(master_idx, compressor)
```

### Pattern — Reverb sur un bus dédié

Plutôt que d'ajouter la reverb directement sur SFX, créer un bus "Reverb" et envoyer une partie du signal vers lui (send).

```gdscript
## Setup via code
func _ready():
    # Créer le bus Reverb
    var reverb_idx = AudioServer.bus_count
    AudioServer.add_bus(reverb_idx)
    AudioServer.set_bus_name(reverb_idx, "Reverb")
    AudioServer.set_bus_send(reverb_idx, "Master")
    AudioServer.set_bus_volume_db(reverb_idx, -10.0)  # Moins fort que le dry

    # Ajouter l'effet
    var reverb = AudioEffectReverb.new()
    reverb.room_size = 0.8
    reverb.damping = 0.5
    reverb.spread = 0.7
    reverb.wet = 1.0
    reverb.dry = 0.0  # Pas de dry sur ce bus, seulement wet
    AudioServer.add_bus_effect(reverb_idx, reverb)

    # Pour envoyer un son vers la reverb, utiliser send sur le player
    # (Non supporté via GDScript, doit être fait dans l'éditeur ou via un bus intermédiaire)
```

> **Note** : Le routing avancé (send auxiliaire) est plus facile via l'éditeur. Créer le bus, ajouter l'effet, et router les sons dedans.

### Pattern — LowPass dynamique (pause menu)

Effet "underwater" ou "muffled" quand le jeu est en pause.

```gdscript
## Dans le script de pause
func _ready():
    var master_idx = AudioServer.get_bus_index("Master")

    var lowpass = AudioEffectLowPassFilter.new()
    lowpass.cutoff_hz = 20000.0  # Pas d'effet par défaut
    lowpass.resonance = 1.0
    AudioServer.add_bus_effect(master_idx, lowpass)

func _on_pause():
    # Baisser le cutoff progressivement
    var lowpass = AudioServer.get_bus_effect(AudioServer.get_bus_index("Master"), 0) as AudioEffectLowPassFilter

    var tween = create_tween()
    tween.tween_property(lowpass, "cutoff_hz", 800.0, 0.3)

func _on_resume():
    var lowpass = AudioServer.get_bus_effect(AudioServer.get_bus_index("Master"), 0) as AudioEffectLowPassFilter

    var tween = create_tween()
    tween.tween_property(lowpass, "cutoff_hz", 20000.0, 0.3)
```

---

## Section 8 : Recettes complètes

### Setup audio complet — Platformer

Un personnage avec jump/land/hurt, ennemis, ambiances.

```gdscript
## audio_setup.gd — Autoload ou script de boot
extends Node

func _ready():
    # Bus layout déjà configuré dans l'éditeur
    # Compression sur Master
    var master_idx = AudioServer.get_bus_index("Master")
    var compressor = AudioEffectCompressor.new()
    compressor.threshold = -6.0
    compressor.ratio = 4.0
    compressor.gain = 3.0
    AudioServer.add_bus_effect(master_idx, compressor)

    # Charger les settings audio
    VolumeSettings.load_settings()

    # Lancer la musique de menu
    MusicManager.change_music(preload("res://audio/music/menu.ogg"))
```

```gdscript
## player.gd
var jump_sound = preload("res://audio/sfx/jump.wav")
var land_sound = preload("res://audio/sfx/land.wav")
var hurt_sound = preload("res://audio/sfx/hurt.wav")

func _on_jump():
    SFXPool.play_random(jump_sound)

func _on_landed(fall_speed: float):
    var pitch = clampf(1.0 + fall_speed / 1000.0, 0.8, 1.2)
    SFXPool.play(land_sound, pitch)

func _on_hurt():
    SFXPool.play(hurt_sound, 0.8, -3.0)
```

```gdscript
## level.gd
func _ready():
    # Changer de musique selon la zone
    MusicManager.change_music(preload("res://audio/music/level1.ogg"))

    # Ambiance vent (zone audio)
    # Les Area2D avec zone_audio.gd gèrent le fade automatiquement
```

### Setup audio complet — RPG top-down

Musique par zone, sons de pas selon le terrain, dialog blips.

```gdscript
## music_zones.gd — Attacher à des Area2D
extends Area2D

@export var music_stream: AudioStream

func _ready():
    body_entered.connect(_on_player_entered)

func _on_player_entered(body: Node2D):
    if body.is_in_group("player"):
        MusicManager.change_music(music_stream, 2.0)  # Crossfade long
```

```gdscript
## footsteps_terrain.gd
var footstep_grass = [...]
var footstep_stone = [...]
var footstep_wood = [...]

var current_terrain: String = "grass"

func _play_footstep():
    var sounds = footstep_grass
    match current_terrain:
        "stone": sounds = footstep_stone
        "wood": sounds = footstep_wood

    footstep_player.stream = sounds.pick_random()
    footstep_player.pitch_scale = randf_range(0.9, 1.1)
    footstep_player.play()
```

```gdscript
## dialog_blip.gd — Son par caractère de texte
var blip_sound = preload("res://audio/sfx/blip.wav")

func _on_character_revealed():
    # Pitch selon le personnage (aigu pour enfant, grave pour adulte)
    var pitch = 1.0
    match character_id:
        "hero": pitch = 1.0
        "child": pitch = 1.3
        "villain": pitch = 0.8

    SFXPool.play(blip_sound, pitch, -6.0)
```

---

## Section 9 : Checklist Review

Avant de valider le système audio :

| Pattern | Question | Fix si non |
|---------|----------|------------|
| **Bus layout** | Au moins Master / Music / SFX configurés ? | Créer les bus dans l'éditeur |
| **Musique** | Joue sur le bus "Music" ? | Assigner le bon bus au player |
| **SFX** | Pool créé (pas d'instanciation runtime) ? | Créer SFXPool autoload |
| **Volume settings** | Persiste via ConfigFile ? | Ajouter `_save_settings()` / `_load_settings()` |
| **Throttle** | Même son ne spam pas ? | Vérifier le throttle dans SFXPool |
| **Spatial audio** | `max_distance` configuré sur AudioStreamPlayer2D ? | Set max_distance (500-2000) |
| **Formats** | WAV pour SFX courts, OGG pour musique ? | Convertir les fichiers |
| **Compression** | Effet compressor sur Master ? | Ajouter AudioEffectCompressor |
| **Crossfade** | Utilise deux players pour la musique ? | Implémenter MusicManager pattern |
| **Pas de runtime instantiate** | Tous les players créés au boot ? | Refactorer en pool |

---

## Section 10 : Pièges courants

| Piège | Symptôme | Solution |
|-------|----------|----------|
| AudioStreamPlayer sans bus assigné | Tout passe sur Master, contrôle impossible | Assigner le bon bus ("Music", "SFX", etc.) |
| `linear_to_db(0.0)` = -INF | Crash ou silence définitif | Clamper slider min à 0.01 ou utiliser mute |
| Trop de players simultanés | Audio crackle, distortion | Limiter via pool + polyphony (~12 max) |
| OGG pour SFX courts | Latence au premier play | Utiliser WAV pour SFX < 2s |
| Volume saved en dB | Settings illisibles (-6.02059...) | Sauvegarder en linear (0-1), convertir au load |
| AudioStreamPlayer2D sans `max_distance` | Son audible partout, pas d'attenuation | Set `max_distance` (ex: 1000) |
| Crossfade sans kill du tween précédent | Tweens empilés, volume incohérent | `tween = create_tween()` crée un nouveau tween auto-kill |
| Instancier un player à chaque SFX | GC spam, lag | Utiliser un pool de players pré-instanciés |
| Pas de throttle sur les SFX | Mitrailleuse de sons, saturation | Throttle 80ms dans SFXPool |
| Settings audio pas appliqués au boot | Volume reset à chaque lancement | Appeler `_load_settings()` dans `_ready()` |

---

## See also

| Skill | Quand l'utiliser |
|-------|-----------------|
| `/godot-juice` | Game feel (screenshake, freeze frame, squash & stretch) — l'audio est 50% du juice |
| `/godot-behavior` | Sound design UI (hover, focus, pressed) — Section 7 détaille le throttle |
| `/godot-composition` | Structure UI pour les sliders de volume |
| `/godot-debug` | Si l'audio cause des bugs (crackle, silence, lag) |

| Recette | Contenu marinade |
|---------|-----------------|
| `recipes/audio_layering.md` | Bus layout recipes, 3-layer impact, pitch variation, music ducking values, silence as sound |
