From 4da00d81c21143e2f75fbe4665b2961a775b00a6 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 12 Apr 2026 17:12:52 +0200 Subject: [PATCH] violat jail_audio amb JA_CrossfadeMusic i globals a structs fix: fade_initial_volume --- data/zones/zones.yaml | 2 +- source/core/audio/audio.cpp | 32 ++++---- source/core/audio/audio.hpp | 17 ++-- source/core/audio/jail_audio.hpp | 129 ++++++++++++++++++++++++++----- source/game/scenes/game.cpp | 2 +- 5 files changed, 138 insertions(+), 44 deletions(-) diff --git a/data/zones/zones.yaml b/data/zones/zones.yaml index 1dd8ed0..bfef7c5 100644 --- a/data/zones/zones.yaml +++ b/data/zones/zones.yaml @@ -16,7 +16,7 @@ zones: - name: forest tileSetFile: forest.gif - music: 574070_KUVO_Farewell_to_school.ogg + music: 574071_EA_DTV.ogg bgColor: 35 - name: sewers diff --git a/source/core/audio/audio.cpp b/source/core/audio/audio.cpp index 8ed2f1b..b740884 100644 --- a/source/core/audio/audio.cpp +++ b/source/core/audio/audio.cpp @@ -38,10 +38,15 @@ Audio::~Audio() { // Método principal void Audio::update() { JA_Update(); + + // Sincronizar estado: detectar cuando la música se para (ej. fade-out completado) + if (instance && instance->music_.state == MusicState::PLAYING && JA_GetMusicState() != JA_MUSIC_PLAYING) { + instance->music_.state = MusicState::STOPPED; + } } -// Reproduce la música -void Audio::playMusic(const std::string& name, const int loop) { +// Reproduce la música (con crossfade opcional) +void Audio::playMusic(const std::string& name, const int loop, const int crossfade_ms) { bool new_loop = (loop != 0); // Si ya está sonando exactamente la misma pista y mismo modo loop, no hacemos nada @@ -51,20 +56,19 @@ void Audio::playMusic(const std::string& name, const int loop) { // Intentar obtener recurso; si falla, no tocar estado auto* resource = Resource::Cache::get()->getMusic(name); - if (resource == nullptr) { - // manejo de error opcional - return; + if (resource == nullptr) return; + + if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) { + // Crossfade: fade-out de la pista actual + fade-in de la nueva + JA_CrossfadeMusic(resource, crossfade_ms, loop); + } else { + // Cambio inmediato + if (music_.state == MusicState::PLAYING) { + JA_StopMusic(); + } + JA_PlayMusic(resource, loop); } - // Si hay algo reproduciéndose, detenerlo primero (si el backend lo requiere) - if (music_.state == MusicState::PLAYING) { - JA_StopMusic(); // sustituir por la función de stop real del API si tiene otro nombre - } - - // Llamada al motor para reproducir la nueva pista - JA_PlayMusic(resource, loop); - - // Actualizar estado y metadatos después de iniciar con éxito music_.name = name; music_.loop = new_loop; music_.state = MusicState::PLAYING; diff --git a/source/core/audio/audio.hpp b/source/core/audio/audio.hpp index bfc32c4..7933147 100644 --- a/source/core/audio/audio.hpp +++ b/source/core/audio/audio.hpp @@ -21,9 +21,10 @@ class Audio { }; // --- Constantes --- - static constexpr float MAX_VOLUME = 1.0F; // Volumen máximo - static constexpr float MIN_VOLUME = 0.0F; // Volumen mínimo - static constexpr int FREQUENCY = 48000; // Frecuencia de audio + static constexpr float MAX_VOLUME = 1.0F; // Volumen máximo + static constexpr float MIN_VOLUME = 0.0F; // Volumen mínimo + static constexpr int FREQUENCY = 48000; // Frecuencia de audio + static constexpr int DEFAULT_CROSSFADE_MS = 1500; // Duración del crossfade por defecto (ms) // --- Singleton --- static void init(); // Inicializa el objeto Audio @@ -35,11 +36,11 @@ class Audio { static void update(); // Actualización del sistema de audio // --- Control de música --- - void playMusic(const std::string& name, int loop = -1); // Reproducir música en bucle - void pauseMusic(); // Pausar reproducción de música - void resumeMusic(); // Continua la música pausada - void stopMusic(); // Detener completamente la música - void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música + void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música (con crossfade opcional) + void pauseMusic(); // Pausar reproducción de música + void resumeMusic(); // Continua la música pausada + void stopMusic(); // Detener completamente la música + void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música // --- Control de sonidos --- void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre diff --git a/source/core/audio/jail_audio.hpp b/source/core/audio/jail_audio.hpp index 3ef686c..5241578 100644 --- a/source/core/audio/jail_audio.hpp +++ b/source/core/audio/jail_audio.hpp @@ -66,33 +66,61 @@ inline bool JA_musicEnabled{true}; inline bool JA_soundEnabled{true}; inline SDL_AudioDeviceID sdlAudioDevice{0}; -inline bool fading{false}; -inline int fade_start_time{0}; -inline int fade_duration{0}; -inline float fade_initial_volume{0.0f}; // Corregido de 'int' a 'float' +// --- Crossfade / Fade State --- +struct JA_FadeState { + bool active{false}; + Uint64 start_time{0}; + int duration_ms{0}; + float initial_volume{0.0f}; +}; + +struct JA_OutgoingMusic { + SDL_AudioStream* stream{nullptr}; + JA_FadeState fade; +}; + +inline JA_OutgoingMusic outgoing_music; +inline JA_FadeState incoming_fade; // --- Forward Declarations --- inline void JA_StopMusic(); inline void JA_StopChannel(const int channel); inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0); +inline void JA_CrossfadeMusic(JA_Music_t* music, int crossfade_ms, int loop = -1); // --- Core Functions --- inline void JA_Update() { + // --- Outgoing music fade-out (crossfade o fade-out a silencio) --- + if (outgoing_music.stream && outgoing_music.fade.active) { + Uint64 now = SDL_GetTicks(); + Uint64 elapsed = now - outgoing_music.fade.start_time; + if (elapsed >= (Uint64)outgoing_music.fade.duration_ms) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + outgoing_music.fade.active = false; + } else { + float percent = (float)elapsed / (float)outgoing_music.fade.duration_ms; + SDL_SetAudioStreamGain(outgoing_music.stream, outgoing_music.fade.initial_volume * (1.0f - percent)); + } + } + + // --- Current music --- if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) { - if (fading) { - int time = SDL_GetTicks(); - if (time > (fade_start_time + fade_duration)) { - fading = false; - JA_StopMusic(); - return; + // Fade-in (parte de un crossfade) + if (incoming_fade.active) { + Uint64 now = SDL_GetTicks(); + Uint64 elapsed = now - incoming_fade.start_time; + if (elapsed >= (Uint64)incoming_fade.duration_ms) { + incoming_fade.active = false; + SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); } else { - const int time_passed = time - fade_start_time; - const float percent = (float)time_passed / (float)fade_duration; - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * (1.0 - percent)); + float percent = (float)elapsed / (float)incoming_fade.duration_ms; + SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * percent); } } + // Buffering de musica en loop if (current_music->times != 0) { if ((Uint32)SDL_GetAudioStreamAvailable(current_music->stream) < (current_music->length / 2)) { SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length); @@ -103,6 +131,7 @@ inline void JA_Update() { } } + // --- Sound channels --- if (JA_soundEnabled) { for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) if (channels[i].state == JA_CHANNEL_PLAYING) { @@ -132,7 +161,11 @@ inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_ } inline void JA_Quit() { - if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice + if (outgoing_music.stream) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + } + if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); sdlAudioDevice = 0; } @@ -233,6 +266,14 @@ inline void JA_ResumeMusic() { } inline void JA_StopMusic() { + // Limpiar outgoing crossfade si existe + if (outgoing_music.stream) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + outgoing_music.fade.active = false; + } + incoming_fade.active = false; + if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return; current_music->pos = 0; @@ -241,17 +282,65 @@ inline void JA_StopMusic() { SDL_DestroyAudioStream(current_music->stream); current_music->stream = nullptr; } - // No liberamos filename aquí, se debería liberar en JA_DeleteMusic } inline void JA_FadeOutMusic(const int milliseconds) { if (!JA_musicEnabled) return; - if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return; + if (!current_music || current_music->state != JA_MUSIC_PLAYING) return; - fading = true; - fade_start_time = SDL_GetTicks(); - fade_duration = milliseconds; - fade_initial_volume = JA_musicVolume; + // Destruir outgoing anterior si existe + if (outgoing_music.stream) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + } + + // Robar el stream del current_music al outgoing + outgoing_music.stream = current_music->stream; + outgoing_music.fade = {true, SDL_GetTicks(), milliseconds, JA_musicVolume}; + + // Dejar current_music sin stream (ya lo tiene outgoing) + current_music->stream = nullptr; + current_music->pos = 0; + current_music->state = JA_MUSIC_STOPPED; + incoming_fade.active = false; +} + +inline void JA_CrossfadeMusic(JA_Music_t* music, const int crossfade_ms, const int loop) { + if (!JA_musicEnabled || !music) return; + + // Destruir outgoing anterior si existe (crossfade durante crossfade) + if (outgoing_music.stream) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + outgoing_music.fade.active = false; + } + + // Robar el stream de la musica actual al outgoing para el fade-out + if (current_music && current_music->state == JA_MUSIC_PLAYING && current_music->stream) { + outgoing_music.stream = current_music->stream; + outgoing_music.fade = {true, SDL_GetTicks(), crossfade_ms, JA_musicVolume}; + current_music->stream = nullptr; + current_music->state = JA_MUSIC_STOPPED; + } + + // Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente) + current_music = music; + current_music->pos = 0; + current_music->state = JA_MUSIC_PLAYING; + current_music->times = loop; + + current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec); + if (!current_music->stream) { + SDL_Log("Failed to create audio stream for crossfade!"); + current_music->state = JA_MUSIC_STOPPED; + return; + } + SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length); + SDL_SetAudioStreamGain(current_music->stream, 0.0f); + SDL_BindAudioStream(sdlAudioDevice, current_music->stream); + + // Configurar fade-in + incoming_fade = {true, SDL_GetTicks(), crossfade_ms, 0.0f}; } inline JA_Music_state JA_GetMusicState() { diff --git a/source/game/scenes/game.cpp b/source/game/scenes/game.cpp index a6e3423..7167bd1 100644 --- a/source/game/scenes/game.cpp +++ b/source/game/scenes/game.cpp @@ -1024,7 +1024,7 @@ void Game::updateMusicForRoom() { // Reproducir si no coincide la pista actual o si está parada if (Audio::get()->getCurrentMusicName() != target || Audio::get()->getMusicState() == Audio::MusicState::STOPPED) { - Audio::get()->playMusic(target); + Audio::get()->playMusic(target, -1, Audio::DEFAULT_CROSSFADE_MS); } }