Add sound effects, ambient music and volume mixing (0.5.0)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Denis Defolie
2026-10-11 22:57:37 +02:00
parent dbf513e686
commit 2d7001105d
40 changed files with 665 additions and 26 deletions
+94 -12
View File
@@ -1,26 +1,108 @@
extends Node
## Lecture des voix, dictees, musiques et effets sonores, y compris l'audio
## provisoire des dictees. Aucun fichier audio n'est encore fourni (etape
## "Graphismes et audio provisoires", a venir) : play_dictation se degrade
## silencieusement (avertissement journalise) si le fichier est absent,
## plutot que de faire planter le jeu.
## Lecture des voix (dictees), des effets sonores et de la musique d'ambiance.
## Les sons viennent d'identifiants resolus par SoundCatalog dans un
## repertoire fixe. Un fichier absent se degrade silencieusement
## (avertissement journalise) plutot que de faire planter le jeu. La musique
## baisse pendant qu'une dictee est lue, pour ne jamais couvrir la voix.
const DICTATION_AUDIO_DIR := "res://assets/audio/dictations"
const MUSIC_DUCK_DB := -12.0
const MUSIC_BASE_DB := -6.0
var _player: AudioStreamPlayer
var _voice_player: AudioStreamPlayer
var _music_player: AudioStreamPlayer
var _effect_players: Array[AudioStreamPlayer] = []
var _current_music_id := ""
var _stream_cache: Dictionary = {}
func _ready() -> void:
_player = AudioStreamPlayer.new()
add_child(_player)
AudioBuses.ensure()
_voice_player = AudioStreamPlayer.new()
_voice_player.finished.connect(_on_voice_finished)
add_child(_voice_player)
_music_player = AudioStreamPlayer.new()
_music_player.bus = AudioBuses.MUSIC
_music_player.volume_db = MUSIC_BASE_DB
add_child(_music_player)
for _i in range(4):
var player := AudioStreamPlayer.new()
player.bus = AudioBuses.EFFECTS
add_child(player)
_effect_players.append(player)
# Un clic sur n'importe quel bouton du jeu produit le meme petit son.
get_tree().node_added.connect(_on_node_added)
func play_dictation(audio_id: String) -> void:
if audio_id.is_empty():
return
var path := "%s/%s.ogg" % [DICTATION_AUDIO_DIR, audio_id]
var path := "%s/%s.ogg" % ["res://assets/audio/dictations", audio_id]
if not ResourceLoader.exists(path):
push_warning("Audio de dictee introuvable (pas encore genere) : %s" % path)
return
_player.stream = load(path)
_player.play()
_voice_player.stream = load(path)
_voice_player.play()
_music_player.volume_db = MUSIC_BASE_DB + MUSIC_DUCK_DB
func play_sfx(sfx_id: String) -> void:
var stream := _load_stream(SoundCatalog.sfx_path(sfx_id))
if stream == null:
return
for player in _effect_players:
if not player.playing:
player.stream = stream
player.play()
return
# Tous les lecteurs sont occupes : on coupe le premier plutot que d'ignorer.
_effect_players[0].stream = stream
_effect_players[0].play()
## Lance la musique d'ambiance en boucle. Sans effet si c'est deja celle-ci.
func play_music(music_id: String) -> void:
if music_id == _current_music_id and _music_player.playing:
return
var stream := _load_stream(SoundCatalog.music_path(music_id))
if stream == null:
return
if stream is AudioStreamOggVorbis:
(stream as AudioStreamOggVorbis).loop = true
_current_music_id = music_id
_music_player.stream = stream
_music_player.play()
func current_music_id() -> String:
return _current_music_id
func stop_music() -> void:
_current_music_id = ""
_music_player.stop()
func _load_stream(path: String) -> AudioStream:
if path.is_empty():
return null
if _stream_cache.has(path):
return _stream_cache[path]
if not ResourceLoader.exists(path):
push_warning("Son introuvable : %s" % path)
return null
var stream: AudioStream = load(path)
_stream_cache[path] = stream
return stream
func _on_voice_finished() -> void:
_music_player.volume_db = MUSIC_BASE_DB
func _on_node_added(node: Node) -> void:
var button := node as BaseButton
if button != null and not button.has_meta("silent"):
button.pressed.connect(play_sfx.bind("click"))
+25 -1
View File
@@ -1,5 +1,6 @@
extends Node
## Volume, taille du texte et animations. Les reglages sont ceux de l'appareil
## Volumes (general, musique, effets), taille du texte et animations. Les
## reglages sont ceux de l'appareil
## (user://settings.json), pas ceux d'un profil. Les valeurs sont nettoyees par
## AppSettings a chaque lecture ; un fichier absent ou abime redonne les valeurs
## par defaut sans erreur. Le volume agit sur le bus principal ; la taille du
@@ -11,6 +12,8 @@ const SETTINGS_PATH := "user://settings.json"
var animations_enabled := true
var volume := 0.8
var music_volume := 0.5
var effects_volume := 0.8
var text_scale_index := 0
var _base_font_sizes: Dictionary = {}
@@ -36,6 +39,16 @@ func set_volume(value: float) -> void:
_commit()
func set_music_volume(value: float) -> void:
music_volume = clampf(value, 0.0, 1.0)
_commit()
func set_effects_volume(value: float) -> void:
effects_volume = clampf(value, 0.0, 1.0)
_commit()
func set_text_scale_index(index: int) -> void:
text_scale_index = clampi(index, 0, AppSettings.TEXT_SCALES.size() - 1)
_commit()
@@ -54,6 +67,8 @@ func load_from_disk() -> void:
raw = JSON.parse_string(file.get_as_text())
var clean := AppSettings.sanitize(raw)
volume = clean["volume"]
music_volume = clean["music_volume"]
effects_volume = clean["effects_volume"]
text_scale_index = clean["text_scale_index"]
animations_enabled = clean["animations_enabled"]
@@ -62,6 +77,8 @@ func load_from_disk() -> void:
func save_to_disk() -> bool:
var data := AppSettings.defaults()
data["volume"] = volume
data["music_volume"] = music_volume
data["effects_volume"] = effects_volume
data["text_scale_index"] = text_scale_index
data["animations_enabled"] = animations_enabled
var tmp_path := SETTINGS_PATH + ".tmp"
@@ -79,7 +96,14 @@ func save_to_disk() -> bool:
## Applique les reglages au moteur : volume du bus principal et tailles du
## theme global.
func apply() -> void:
AudioBuses.ensure()
AudioServer.set_bus_volume_db(0, AppSettings.volume_db(volume))
AudioServer.set_bus_volume_db(
AudioServer.get_bus_index(AudioBuses.MUSIC), AppSettings.volume_db(music_volume)
)
AudioServer.set_bus_volume_db(
AudioServer.get_bus_index(AudioBuses.EFFECTS), AppSettings.volume_db(effects_volume)
)
_apply_text_scale()