Basic audio manager setup.

This commit is contained in:
2026-09-18 07:22:53 +02:00
parent f6f8e99e6b
commit 6b76ad6874
2 changed files with 159 additions and 0 deletions
+148
View File
@@ -0,0 +1,148 @@
package game
import rl "vendor:raylib"
Fade_State :: enum {
None,
In,
Out,
}
// setup sound IDs so gameplay code never
// touches raw Raylib sound handles
Sound_ID :: enum {
Ball_Collision,
Score_Goal,
UI_Select,
UI_Confirm,
}
Audio_Manager :: struct {
sounds: map[Sound_ID]rl.Sound,
music: rl.Music,
music_loaded: bool,
sfx_vol: f32, // User SFX volume setting
user_music_vol: f32, // User Music volume setting
current_music_vol: f32, // Live interpolated output volume
// Runtime Fade State
fade_state: Fade_State,
fade_elapsed_sec: f32,
fade_duration_sec: f32,
}
// ------------------------------------------------------------------------------
// Lifecycle procs
// ------------------------------------------------------------------------------
audio_init :: proc(audio: ^Audio_Manager) {
rl.InitAudioDevice()
audio.sounds = make(map[Sound_ID]rl.Sound)
// Load SFX
audio.sfx_vol = 0.5
audio.sounds[.UI_Select] = rl.LoadSound("assets/audio/ui/select.ogg")
audio.sounds[.Ball_Collision] = rl.LoadSound("assets/audio/gameplay/ballhit.ogg")
// Load Music Stream
audio.user_music_vol = 0.1
audio.current_music_vol = 0.0
audio.fade_state = .None
audio.music = rl.LoadMusicStream("assets/audio/gameplay/music/track_1.wav")
audio.music_loaded = true
rl.SetMusicVolume(audio.music, audio.current_music_vol)
}
audio_update :: proc(audio: ^Audio_Manager, dt: f32) {
if !audio.music_loaded do return
// Process Fade if the audio state machine requires
if audio.fade_state != .None {
audio.fade_elapsed_sec += dt
// convert elapsed seconds over target seconds into a 0.0 -> 1.0 percentage
progress_pct := clamp(audio.fade_elapsed_sec / audio.fade_duration_sec, 0.0, 1.0)
#partial switch audio.fade_state {
case .In:
audio.current_music_vol = rl.Lerp(0.0, audio.user_music_vol, progress_pct)
// progress is 100% or more stop fading in music
if progress_pct >= 1.0 do audio.fade_state = .None
case .Out:
audio.current_music_vol = rl.Lerp(audio.user_music_vol, 0.0, progress_pct)
// progress is 100% or more stop fading out music and stop the stream
if progress_pct >= 1.0 {
audio.fade_state = .None
rl.StopMusicStream(audio.music)
}
}
} else {
// not fading, output matches the user setting directly
audio.current_music_vol = audio.user_music_vol
}
rl.SetMusicVolume(audio.music, audio.current_music_vol)
// keep audio stream buffer fed
if rl.IsMusicStreamPlaying(audio.music) {
rl.UpdateMusicStream(audio.music)
}
}
audio_manager_quit :: proc(audio: ^Audio_Manager) {
for _, sound in audio.sounds {
rl.UnloadSound(sound)
}
delete(audio.sounds)
rl.UnloadMusicStream(audio.music)
rl.CloseAudioDevice()
}
// ------------------------------------------------------------------------------
// Utilities
// ------------------------------------------------------------------------------
audio_play_music :: proc(audio: ^Audio_Manager) {
if audio.music_loaded {
// Only trigger start if it isn't playing already
if !rl.IsMusicStreamPlaying(audio.music) {
rl.PlayMusicStream(audio.music)
}
}
}
audio_stop_music :: proc(audio: ^Audio_Manager) {
if audio.music_loaded && rl.IsMusicStreamPlaying(audio.music) {
rl.StopMusicStream(audio.music)
}
}
audio_play_sfx :: proc(audio: ^Audio_Manager, id: Sound_ID) {
if sound, ok := audio.sounds[id]; ok {
rl.SetSoundVolume(sound, audio.sfx_vol)
rl.PlaySound(sound)
}
}
// ------------------------------------------------------------------------------
// Audio Fading
// ------------------------------------------------------------------------------
audio_fade_in :: proc(audio: ^Audio_Manager, duration: f32 = 1.0) {
if !audio.music_loaded do return
if !rl.IsMusicStreamPlaying(audio.music) {
rl.PlayMusicStream(audio.music)
}
audio.fade_state = .In
audio.fade_duration_sec = max(0.001, duration)
audio.fade_elapsed_sec = 0.0
audio.current_music_vol = 0.0
rl.SetMusicVolume(audio.music, 0.0)
}
audio_fade_out :: proc(audio: ^Audio_Manager, duration: f32 = 1.0) {
if !audio.music_loaded || !rl.IsMusicStreamPlaying(audio.music) do return
audio.fade_state = .Out
audio.fade_duration_sec = max(0.001, duration)
audio.fade_elapsed_sec = 0.0
}
+11
View File
@@ -13,6 +13,7 @@ Game :: struct {
scene: Scene,
overlay_scene: Maybe(Scene),
should_close: bool,
audio: Audio_Manager,
}
// ------------------------------------------------------------------------------
@@ -22,6 +23,9 @@ game_init :: proc(game: ^Game) {
rl.InitWindow(WINDOW_W, WINDOW_H, WINDOW_TITLE)
rl.SetTargetFPS(60)
// init any global managers
audio_init(&game.audio)
// allocate some event capacity to stop early dynamic reallocations
GLOBAL_EVENTS = make([dynamic]Event, 0, 16)
@@ -43,6 +47,9 @@ game_update :: proc(game: ^Game) {
scene_update(&game.scene, game, input, dt)
}
// background music buffer streaming
audio_update(&game.audio, dt)
// flush any events triggered in this loop
flush_events()
}
@@ -72,6 +79,10 @@ game_render :: proc(game: ^Game) {
game_quit :: proc(game: ^Game) {
delete(GLOBAL_EVENTS)
rl.UnloadRenderTexture(game.viewport)
// cleanup global managers
audio_manager_quit(&game.audio)
rl.CloseWindow()
}