From 1ce0d9c56cfe03a6d1bdeb44cf987fde1ac7e145 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sat, 16 May 2026 14:15:25 +0200 Subject: [PATCH] refactor: JA_* a namespace Ja:: (estil aee_arcade) --- source/core/audio/audio.cpp | 54 +- source/core/audio/audio.hpp | 9 +- source/core/audio/audio_adapter.cpp | 4 +- source/core/audio/audio_adapter.hpp | 14 +- source/core/audio/jail_audio.hpp | 1224 ++++++++++------------ source/core/resources/resource_cache.cpp | 16 +- source/core/resources/resource_cache.hpp | 4 +- source/core/resources/resource_types.hpp | 23 +- source/game/scenes/banner_scene.hpp | 2 +- source/game/scenes/credits_scene.cpp | 2 +- source/game/scenes/secreta_scene.hpp | 2 +- source/game/scenes/slides_scene.cpp | 2 +- source/game/scenes/slides_scene.hpp | 2 +- source/game/scenes/timeline.hpp | 2 +- source/main.cpp | 4 +- 15 files changed, 645 insertions(+), 719 deletions(-) diff --git a/source/core/audio/audio.cpp b/source/core/audio/audio.cpp index da5800b..2efd9ff 100644 --- a/source/core/audio/audio.cpp +++ b/source/core/audio/audio.cpp @@ -40,15 +40,15 @@ Audio::Audio() { initSDLAudio(); } // Destructor Audio::~Audio() { - JA_Quit(); + Ja::quit(); } // Método principal void Audio::update() { - JA_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) { + if (instance && instance->music_.state == MusicState::PLAYING && Ja::getMusicState() != Ja::MusicState::PLAYING) { instance->music_.state = MusicState::STOPPED; } } @@ -72,12 +72,12 @@ void Audio::playMusic(const std::string& name, const int loop, const int crossfa } if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) { - JA_CrossfadeMusic(resource, crossfade_ms, loop); + Ja::crossfadeMusic(resource, crossfade_ms, loop); } else { if (music_.state == MusicState::PLAYING) { - JA_StopMusic(); + Ja::stopMusic(); } - JA_PlayMusic(resource, loop); + Ja::playMusic(resource, loop); } music_.name = name; @@ -86,18 +86,18 @@ void Audio::playMusic(const std::string& name, const int loop, const int crossfa } // Reproduce la música por puntero (con crossfade opcional) -void Audio::playMusic(JA_Music_t* music, const int loop, const int crossfade_ms) { +void Audio::playMusic(Ja::Music* music, const int loop, const int crossfade_ms) { if (!music_enabled_ || music == nullptr) { return; } if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) { - JA_CrossfadeMusic(music, crossfade_ms, loop); + Ja::crossfadeMusic(music, crossfade_ms, loop); } else { if (music_.state == MusicState::PLAYING) { - JA_StopMusic(); + Ja::stopMusic(); } - JA_PlayMusic(music, loop); + Ja::playMusic(music, loop); } music_.name.clear(); // nom desconegut quan es passa per punter @@ -108,7 +108,7 @@ void Audio::playMusic(JA_Music_t* music, const int loop, const int crossfade_ms) // Pausa la música void Audio::pauseMusic() { if (music_enabled_ && music_.state == MusicState::PLAYING) { - JA_PauseMusic(); + Ja::pauseMusic(); music_.state = MusicState::PAUSED; } } @@ -116,7 +116,7 @@ void Audio::pauseMusic() { // Continua la música pausada void Audio::resumeMusic() { if (music_enabled_ && music_.state == MusicState::PAUSED) { - JA_ResumeMusic(); + Ja::resumeMusic(); music_.state = MusicState::PLAYING; } } @@ -124,7 +124,7 @@ void Audio::resumeMusic() { // Detiene la música void Audio::stopMusic() { if (music_enabled_) { - JA_StopMusic(); + Ja::stopMusic(); music_.state = MusicState::STOPPED; } } @@ -132,42 +132,42 @@ void Audio::stopMusic() { // Reproduce un sonido por nombre void Audio::playSound(const std::string& name, Group group) const { if (sound_enabled_) { - JA_PlaySound(AudioResource::getSound(name), 0, static_cast(group)); + Ja::playSound(AudioResource::getSound(name), 0, static_cast(group)); } } // Reproduce un sonido por puntero directo -void Audio::playSound(JA_Sound_t* sound, Group group) const { +void Audio::playSound(Ja::Sound* sound, Group group) const { if (sound_enabled_ && sound != nullptr) { - JA_PlaySound(sound, 0, static_cast(group)); + Ja::playSound(sound, 0, static_cast(group)); } } // Detiene todos los sonidos void Audio::stopAllSounds() const { if (sound_enabled_) { - JA_StopChannel(-1); + Ja::stopChannel(-1); } } // Realiza un fundido de salida de la música void Audio::fadeOutMusic(int milliseconds) const { if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) { - JA_FadeOutMusic(milliseconds); + Ja::fadeOutMusic(milliseconds); } } // Consulta directamente el estado real de la música en jailaudio auto Audio::getRealMusicState() -> MusicState { - JA_Music_state ja_state = JA_GetMusicState(); + Ja::MusicState ja_state = Ja::getMusicState(); switch (ja_state) { - case JA_MUSIC_PLAYING: + case Ja::MusicState::PLAYING: return MusicState::PLAYING; - case JA_MUSIC_PAUSED: + case Ja::MusicState::PAUSED: return MusicState::PAUSED; - case JA_MUSIC_STOPPED: - case JA_MUSIC_INVALID: - case JA_MUSIC_DISABLED: + case Ja::MusicState::STOPPED: + case Ja::MusicState::INVALID: + case Ja::MusicState::DISABLED: default: return MusicState::STOPPED; } @@ -178,7 +178,7 @@ void Audio::setSoundVolume(float sound_volume, Group group) const { sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME); const bool active = enabled_ && sound_enabled_; const float CONVERTED_VOLUME = active ? sound_volume * Options::audio.volume : 0.0F; - JA_SetSoundVolume(CONVERTED_VOLUME, static_cast(group)); + Ja::setSoundVolume(CONVERTED_VOLUME, static_cast(group)); } // Establece el volumen de la música (float 0.0..1.0) @@ -186,7 +186,7 @@ void Audio::setMusicVolume(float music_volume) const { music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME); const bool active = enabled_ && music_enabled_; const float CONVERTED_VOLUME = active ? music_volume * Options::audio.volume : 0.0F; - JA_SetMusicVolume(CONVERTED_VOLUME); + Ja::setMusicVolume(CONVERTED_VOLUME); } // Aplica la configuración @@ -207,7 +207,7 @@ void Audio::initSDLAudio() { if (!SDL_Init(SDL_INIT_AUDIO)) { std::cout << "SDL_AUDIO could not initialize! SDL Error: " << SDL_GetError() << '\n'; } else { - JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2); + Ja::init(FREQUENCY, SDL_AUDIO_S16LE, 2); enable(Options::audio.enabled); } } diff --git a/source/core/audio/audio.hpp b/source/core/audio/audio.hpp index ae795a1..3b76c00 100644 --- a/source/core/audio/audio.hpp +++ b/source/core/audio/audio.hpp @@ -6,6 +6,11 @@ #include // Para string #include // Para move +namespace Ja { + struct Music; + struct Sound; +} // namespace Ja + // --- Clase Audio: gestor de audio (singleton) --- // Implementació canònica, byte-idèntica entre projectes. // Els volums es manegen internament com a float 0.0–1.0; la capa de @@ -45,7 +50,7 @@ class Audio { // --- Control de música --- void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional) - void playMusic(struct JA_Music_t* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional) + void playMusic(Ja::Music* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (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 @@ -53,7 +58,7 @@ class Audio { // --- Control de sonidos --- void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre - void playSound(struct JA_Sound_t* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero + void playSound(Ja::Sound* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero void stopAllSounds() const; // Detener todos los sonidos // --- Control de volumen (API interna: float 0.0..1.0) --- diff --git a/source/core/audio/audio_adapter.cpp b/source/core/audio/audio_adapter.cpp index 23a6b5c..287bb46 100644 --- a/source/core/audio/audio_adapter.cpp +++ b/source/core/audio/audio_adapter.cpp @@ -4,11 +4,11 @@ namespace AudioResource { - auto getMusic(const std::string& name) -> JA_Music_t* { + auto getMusic(const std::string& name) -> Ja::Music* { return Resource::Cache::get()->getMusic(name); } - auto getSound(const std::string& name) -> JA_Sound_t* { + auto getSound(const std::string& name) -> Ja::Sound* { return Resource::Cache::get()->getSound(name); } diff --git a/source/core/audio/audio_adapter.hpp b/source/core/audio/audio_adapter.hpp index 9c95864..7feda5a 100644 --- a/source/core/audio/audio_adapter.hpp +++ b/source/core/audio/audio_adapter.hpp @@ -1,17 +1,19 @@ #pragma once // --- Audio Resource Adapter --- -// Aquest fitxer exposa una interfície comuna a Audio per obtenir JA_Music_t* / -// JA_Sound_t* per nom. Cada projecte la implementa en audio_adapter.cpp +// Aquest fitxer exposa una interfície comuna a Audio per obtenir Ja::Music* / +// Ja::Sound* per nom. Cada projecte la implementa en audio_adapter.cpp // delegant al seu singleton de recursos (Resource::get(), Resource::Cache::get(), // etc.). Això permet que audio.hpp/audio.cpp siguin idèntics entre projectes. #include // Para string -struct JA_Music_t; -struct JA_Sound_t; +namespace Ja { + struct Music; + struct Sound; +} // namespace Ja namespace AudioResource { - auto getMusic(const std::string& name) -> JA_Music_t*; - auto getSound(const std::string& name) -> JA_Sound_t*; + auto getMusic(const std::string& name) -> Ja::Music*; + auto getSound(const std::string& name) -> Ja::Sound*; } // namespace AudioResource diff --git a/source/core/audio/jail_audio.hpp b/source/core/audio/jail_audio.hpp index edb3ee2..276841f 100644 --- a/source/core/audio/jail_audio.hpp +++ b/source/core/audio/jail_audio.hpp @@ -3,779 +3,697 @@ // --- Includes --- #include -#include // Para std::fill -#include // Para uint32_t, uint8_t -#include // Para NULL, fseek, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET -#include // Para free, malloc -#include // Para std::cout -#include // Para std::unique_ptr -#include // Para std::string -#include // Para std::vector +#include +#include +#include +#include +#include +#include +#include +#include #define STB_VORBIS_HEADER_ONLY -#include "external/stb_vorbis.c" // NOLINT(bugprone-suspicious-include): stb header-only library +// NOLINTNEXTLINE(bugprone-suspicious-include) — stb_vorbis és single-file: la macro de dalt limita a només-declaracions. +#include "external/stb_vorbis.c" // Para stb_vorbis_open_memory i streaming // Deleter stateless per a buffers reservats amb `SDL_malloc` / `SDL_LoadWAV*`. -// Compatible amb `std::unique_ptr` — zero size +// Compatible amb `std::unique_ptr` — zero size // overhead gràcies a EBO, igual que un unique_ptr amb default_delete. -struct SDLFreeDeleter { +struct SdlFreeDeleter { void operator()(Uint8* p) const noexcept { - if (p != nullptr) { - SDL_free(p); - } + if (p != nullptr) { SDL_free(p); } } }; -// --- Public Enums --- -enum JA_Channel_state : std::uint8_t { - JA_CHANNEL_INVALID, - JA_CHANNEL_FREE, - JA_CHANNEL_PLAYING, - JA_CHANNEL_PAUSED, - JA_SOUND_DISABLED, -}; -enum JA_Music_state : std::uint8_t { - JA_MUSIC_INVALID, - JA_MUSIC_PLAYING, - JA_MUSIC_PAUSED, - JA_MUSIC_STOPPED, - JA_MUSIC_DISABLED, -}; +namespace Ja { -// --- Struct Definitions --- -enum : std::uint8_t { - JA_MAX_SIMULTANEOUS_CHANNELS = 20, - JA_MAX_GROUPS = 2 -}; + // --- Public Enums --- + enum class ChannelState : std::uint8_t { + INVALID, + FREE, + PLAYING, + PAUSED, + DISABLED, + }; -struct JA_Sound_t { - SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; - Uint32 length{0}; - // Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV - // via SDL_malloc; el deleter `SDLFreeDeleter` allibera amb SDL_free. - std::unique_ptr buffer; -}; + enum class MusicState : std::uint8_t { + INVALID, + PLAYING, + PAUSED, + STOPPED, + DISABLED, + }; -struct JA_Channel_t { - JA_Sound_t* sound{nullptr}; - SDL_AudioStream* stream{nullptr}; - int pos{0}; - int times{0}; - int group{0}; - JA_Channel_state state{JA_CHANNEL_FREE}; -}; + // --- Constants --- + inline constexpr int MAX_SIMULTANEOUS_CHANNELS = 20; + inline constexpr int MAX_GROUPS = 2; + inline constexpr SDL_AudioSpec DEFAULT_SPEC{.format = SDL_AUDIO_S16, .channels = 2, .freq = 48000}; -struct JA_Music_t { - SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; + // --- Struct Definitions --- + struct Sound { + SDL_AudioSpec spec{DEFAULT_SPEC}; + Uint32 length{0}; + // Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV + // via SDL_malloc; el deleter `SdlFreeDeleter` allibera amb SDL_free. + std::unique_ptr buffer; + }; - // OGG comprimit en memòria. Propietat nostra; es copia des del buffer - // d'entrada una sola vegada en JA_LoadMusic i es descomprimix en chunks - // per streaming. Com que stb_vorbis guarda un punter persistent al - // `.data()` d'aquest vector, no el podem resize'jar un cop establert - // (una reallocation invalidaria el punter que el decoder conserva). - std::vector ogg_data; - stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del JA_Music_t + // L'ordre (punters primer, ints després, enum de 8 bits al final) minimitza + // el padding a 64-bit (evita avisos de clang-analyzer-optin.performance.Padding). + struct Channel { + Sound* sound{nullptr}; + SDL_AudioStream* stream{nullptr}; + int pos{0}; + int times{0}; + int group{0}; + ChannelState state{ChannelState::FREE}; + }; - std::string filename; + struct Music { + SDL_AudioSpec spec{DEFAULT_SPEC}; - int times{0}; // loops restants (-1 = infinit, 0 = un sol play) - SDL_AudioStream* stream{nullptr}; - JA_Music_state state{JA_MUSIC_INVALID}; -}; + // OGG comprimit en memòria. Propietat nostra; es copia des del buffer + // d'entrada una sola vegada en loadMusic i es descomprimix en chunks + // per streaming. Com que stb_vorbis guarda un punter persistent al + // `.data()` d'aquest vector, no el podem resize'jar un cop establert + // (una reallocation invalidaria el punter que el decoder conserva). + std::vector ogg_data; + stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del Music -// --- Internal Global State (inline, C++17) --- + std::string filename; -inline JA_Music_t* current_music{nullptr}; -inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS]; + int times{0}; // loops restants (-1 = infinit, 0 = un sol play) + SDL_AudioStream* stream{nullptr}; + MusicState state{MusicState::INVALID}; + }; -inline SDL_AudioSpec JA_audioSpec{SDL_AUDIO_S16, 2, 48000}; -inline float JA_musicVolume{1.0F}; -inline float JA_soundVolume[JA_MAX_GROUPS]; -inline bool JA_musicEnabled{true}; -inline bool JA_soundEnabled{true}; -inline SDL_AudioDeviceID sdlAudioDevice{0}; + struct FadeState { + bool active{false}; + Uint64 start_time{0}; + int duration_ms{0}; + float initial_volume{0.0F}; + }; -// --- Crossfade / Fade State --- -struct JA_FadeState { - bool active{false}; - Uint64 start_time{0}; - int duration_ms{0}; - float initial_volume{0.0F}; -}; + struct OutgoingMusic { + SDL_AudioStream* stream{nullptr}; + FadeState fade; + }; -struct JA_OutgoingMusic { - SDL_AudioStream* stream{nullptr}; - JA_FadeState fade; -}; + // --- Internal Global State (inline, C++17) --- + inline Music* current_music{nullptr}; + inline Channel channels[MAX_SIMULTANEOUS_CHANNELS]; -inline JA_OutgoingMusic outgoing_music; -inline JA_FadeState incoming_fade; + inline SDL_AudioSpec audio_spec{DEFAULT_SPEC}; + inline float music_volume{1.0F}; + inline float sound_volume[MAX_GROUPS]; + inline bool music_enabled{true}; + inline bool sound_enabled{true}; + inline SDL_AudioDeviceID sdl_audio_device{0}; -// --- Forward Declarations --- -inline void JA_StopMusic(); -inline void JA_StopChannel(int channel); -inline auto JA_PlaySoundOnChannel(JA_Sound_t* sound, int channel, int loop = 0, int group = 0) -> int; -inline void JA_CrossfadeMusic(JA_Music_t* music, int crossfade_ms, int loop = -1); + inline OutgoingMusic outgoing_music; + inline FadeState incoming_fade; -// --- Music streaming internals --- -// Bytes-per-sample per canal (sempre s16) -static constexpr int JA_MUSIC_BYTES_PER_SAMPLE = 2; -// Quants shorts decodifiquem per crida a get_samples_short_interleaved. -// 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz. -static constexpr int JA_MUSIC_CHUNK_SHORTS = 8192; -// Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a -// l'SDL_AudioStream per absorbir jitter de frame i evitar underruns. -static constexpr float JA_MUSIC_LOW_WATER_SECONDS = 0.5F; + // --- Forward Declarations --- + inline void stopMusic(); + inline void stopChannel(int channel); + inline auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int; + inline void crossfadeMusic(Music* music, int crossfade_ms, int loop = -1); -// Decodifica un chunk del vorbis i el volca a l'stream. Retorna samples -// decodificats per canal (0 = EOF de l'stream vorbis). -inline auto JA_FeedMusicChunk(JA_Music_t* music) -> int { - if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { - return 0; + // --- Music streaming internals --- + // Bytes-per-sample per canal (sempre s16) + inline constexpr int MUSIC_BYTES_PER_SAMPLE = 2; + // Quants shorts decodifiquem per crida a get_samples_short_interleaved. + // 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz. + inline constexpr int MUSIC_CHUNK_SHORTS = 8192; + // Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a + // l'SDL_AudioStream per absorbir jitter de frame i evitar underruns. + inline constexpr float MUSIC_LOW_WATER_SECONDS = 0.5F; + + // Decodifica un chunk del vorbis i el volca a l'stream. Retorna samples + // decodificats per canal (0 = EOF de l'stream vorbis). + inline auto feedMusicChunk(Music* music) -> int { + if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { return 0; } + + short chunk[MUSIC_CHUNK_SHORTS]; + const int NUM_CHANNELS = music->spec.channels; + const int SAMPLES_PER_CHANNEL = stb_vorbis_get_samples_short_interleaved( + music->vorbis, + NUM_CHANNELS, + chunk, + MUSIC_CHUNK_SHORTS); + if (SAMPLES_PER_CHANNEL <= 0) { return 0; } + + const int BYTES = SAMPLES_PER_CHANNEL * NUM_CHANNELS * MUSIC_BYTES_PER_SAMPLE; + SDL_PutAudioStreamData(music->stream, chunk, BYTES); + return SAMPLES_PER_CHANNEL; } - short chunk[JA_MUSIC_CHUNK_SHORTS]; - const int num_channels = music->spec.channels; - const int samples_per_channel = stb_vorbis_get_samples_short_interleaved( - music->vorbis, - num_channels, - chunk, - JA_MUSIC_CHUNK_SHORTS); - if (samples_per_channel <= 0) { - return 0; - } + // Reompli l'stream fins que tinga ≥ MUSIC_LOW_WATER_SECONDS bufferats. + // En arribar a EOF del vorbis, aplica el loop (times) o deixa drenar. + inline void pumpMusic(Music* music) { + if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { return; } - const int bytes = samples_per_channel * num_channels * JA_MUSIC_BYTES_PER_SAMPLE; - SDL_PutAudioStreamData(music->stream, chunk, bytes); - return samples_per_channel; -} + const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE; + const int LOW_WATER_BYTES = static_cast(MUSIC_LOW_WATER_SECONDS * static_cast(BYTES_PER_SECOND)); -// Reompli l'stream fins que tinga ≥ JA_MUSIC_LOW_WATER_SECONDS bufferats. -// En arribar a EOF del vorbis, aplica el loop (times) o deixa drenar. -inline void JA_PumpMusic(JA_Music_t* music) { - if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { - return; - } + while (SDL_GetAudioStreamAvailable(music->stream) < LOW_WATER_BYTES) { + const int DECODED = feedMusicChunk(music); + if (DECODED > 0) { continue; } - const int bytes_per_second = music->spec.freq * music->spec.channels * JA_MUSIC_BYTES_PER_SAMPLE; - const int low_water_bytes = static_cast(JA_MUSIC_LOW_WATER_SECONDS * static_cast(bytes_per_second)); - - while (SDL_GetAudioStreamAvailable(music->stream) < low_water_bytes) { - const int decoded = JA_FeedMusicChunk(music); - if (decoded > 0) { - continue; - } - - // EOF: si queden loops, rebobinar; si no, tallar i deixar drenar. - if (music->times != 0) { - stb_vorbis_seek_start(music->vorbis); - if (music->times > 0) { - music->times--; + // EOF: si queden loops, rebobinar; si no, tallar i deixar drenar. + if (music->times != 0) { + stb_vorbis_seek_start(music->vorbis); + if (music->times > 0) { music->times--; } + } else { + break; } - } else { - break; } } -} -// Pre-carrega `duration_ms` de so dins l'stream actual abans que l'stream -// siga robat per outgoing_music (crossfade o fade-out). Imprescindible amb -// streaming: l'stream robat no es pot re-alimentar perquè perd la referència -// al seu vorbis decoder. No aplica loop — si el vorbis s'esgota abans, parem. -inline void JA_PreFillOutgoing(JA_Music_t* music, int duration_ms) { - if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { - return; - } + // Pre-carrega `duration_ms` de so dins l'stream actual abans que l'stream + // siga robat per outgoing_music (crossfade o fade-out). Imprescindible amb + // streaming: l'stream robat no es pot re-alimentar perquè perd la referència + // al seu vorbis decoder. No aplica loop — si el vorbis s'esgota abans, parem. + inline void preFillOutgoing(Music* music, int duration_ms) { + if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { return; } - const int bytes_per_second = music->spec.freq * music->spec.channels * JA_MUSIC_BYTES_PER_SAMPLE; - const int needed_bytes = static_cast((static_cast(duration_ms) * bytes_per_second) / 1000); + const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE; + const int NEEDED_BYTES = static_cast((static_cast(duration_ms) * BYTES_PER_SECOND) / 1000); - while (SDL_GetAudioStreamAvailable(music->stream) < needed_bytes) { - const int decoded = JA_FeedMusicChunk(music); - if (decoded <= 0) { - break; // EOF: deixem drenar el que hi haja + while (SDL_GetAudioStreamAvailable(music->stream) < NEEDED_BYTES) { + const int DECODED = feedMusicChunk(music); + if (DECODED <= 0) { break; } // EOF: deixem drenar el que hi haja } } -} -// --- Core Functions --- + // --- update() helpers --- + inline void updateOutgoingFade() { + if ((outgoing_music.stream == nullptr) || !outgoing_music.fade.active) { return; } -inline void JA_Update() { - // --- Outgoing music fade-out (crossfade o fade-out a silencio) --- - if ((outgoing_music.stream != nullptr) && outgoing_music.fade.active) { - Uint64 now = SDL_GetTicks(); - Uint64 elapsed = now - outgoing_music.fade.start_time; - if (elapsed >= (Uint64)outgoing_music.fade.duration_ms) { + const Uint64 NOW = SDL_GetTicks(); + const Uint64 ELAPSED = NOW - outgoing_music.fade.start_time; + if (ELAPSED >= static_cast(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)); + const float PERCENT = static_cast(ELAPSED) / static_cast(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 != nullptr) && current_music->state == JA_MUSIC_PLAYING) { - // 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 { - float percent = (float)elapsed / (float)incoming_fade.duration_ms; - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * percent); - } + inline void updateIncomingFade() { + if (!incoming_fade.active) { return; } + + const Uint64 NOW = SDL_GetTicks(); + const Uint64 ELAPSED = NOW - incoming_fade.start_time; + if (ELAPSED >= static_cast(incoming_fade.duration_ms)) { + incoming_fade.active = false; + SDL_SetAudioStreamGain(current_music->stream, music_volume); + } else { + const float PERCENT = static_cast(ELAPSED) / static_cast(incoming_fade.duration_ms); + SDL_SetAudioStreamGain(current_music->stream, music_volume * PERCENT); } + } + + inline void updateCurrentMusic() { + if (!music_enabled || (current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; } + + updateIncomingFade(); // Streaming: rellenem l'stream fins al low-water-mark i parem si el // vorbis s'ha esgotat i no queden loops. - JA_PumpMusic(current_music); + pumpMusic(current_music); if (current_music->times == 0 && SDL_GetAudioStreamAvailable(current_music->stream) == 0) { - JA_StopMusic(); + stopMusic(); } } - // --- Sound channels --- - if (JA_soundEnabled) { - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) { - if (channels[i].state == JA_CHANNEL_PLAYING) { - if (channels[i].times != 0) { - if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) { - SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer.get(), channels[i].sound->length); - if (channels[i].times > 0) { - channels[i].times--; - } - } - } else { - if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) { - JA_StopChannel(i); - } + inline void updateSoundChannels() { + if (!sound_enabled) { return; } + + for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; ++i) { + auto& ch = channels[i]; + if (ch.state != ChannelState::PLAYING) { continue; } + + if (ch.times != 0) { + if (static_cast(SDL_GetAudioStreamAvailable(ch.stream)) < (ch.sound->length / 2)) { + SDL_PutAudioStreamData(ch.stream, ch.sound->buffer.get(), ch.sound->length); + if (ch.times > 0) { ch.times--; } } + } else { + if (SDL_GetAudioStreamAvailable(ch.stream) == 0) { stopChannel(i); } } } } -} -inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) { - JA_audioSpec = {.format = format, .channels = num_channels, .freq = freq}; - if (sdlAudioDevice != 0U) { - SDL_CloseAudioDevice(sdlAudioDevice); - } - sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec); - if (sdlAudioDevice == 0) { - std::cout << "Failed to initialize SDL audio!" << '\n'; - } - for (auto& channel : channels) { - channel.state = JA_CHANNEL_FREE; - } - std::fill(std::begin(JA_soundVolume), std::end(JA_soundVolume), 0.5F); -} - -inline void JA_Quit() { - if (outgoing_music.stream != nullptr) { - SDL_DestroyAudioStream(outgoing_music.stream); - outgoing_music.stream = nullptr; - } - if (sdlAudioDevice != 0U) { - SDL_CloseAudioDevice(sdlAudioDevice); - } - sdlAudioDevice = 0; -} - -// --- Music Functions --- - -inline auto JA_LoadMusic(const Uint8* buffer, Uint32 length) -> JA_Music_t* { - if ((buffer == nullptr) || length == 0) { - return nullptr; + inline void update() { + updateOutgoingFade(); + updateCurrentMusic(); + updateSoundChannels(); } - // Allocem el JA_Music_t primer per aprofitar el seu `std::vector` - // com a propietari del OGG comprimit. stb_vorbis guarda un punter - // persistent al buffer; com que ací no el resize'jem, el .data() és - // estable durant tot el cicle de vida del music. - auto* music = new JA_Music_t(); - music->ogg_data.assign(buffer, buffer + length); - - int vorbis_error = 0; - music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(), - static_cast(length), - &vorbis_error, - nullptr); - if (music->vorbis == nullptr) { - std::cout << "JA_LoadMusic: stb_vorbis_open_memory failed (error " << vorbis_error << ")" << '\n'; - delete music; - return nullptr; + inline void init(int freq, SDL_AudioFormat format, int num_channels) { + audio_spec = {.format = format, .channels = num_channels, .freq = freq}; + if (sdl_audio_device != 0) { SDL_CloseAudioDevice(sdl_audio_device); } + sdl_audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec); + if (sdl_audio_device == 0) { std::cout << "Failed to initialize SDL audio!" << '\n'; } + for (auto& ch : channels) { ch.state = ChannelState::FREE; } + std::fill(std::begin(sound_volume), std::end(sound_volume), 0.5F); } - const stb_vorbis_info info = stb_vorbis_get_info(music->vorbis); - music->spec.channels = info.channels; - music->spec.freq = static_cast(info.sample_rate); - music->spec.format = SDL_AUDIO_S16; - music->state = JA_MUSIC_STOPPED; - - return music; -} - -// Overload amb filename — els callers l'usen per poder comparar la música -// en curs amb JA_GetMusicFilename() i no rearrancar-la si ja és la mateixa. -inline auto JA_LoadMusic(Uint8* buffer, Uint32 length, const char* filename) -> JA_Music_t* { - JA_Music_t* music = JA_LoadMusic(static_cast(buffer), length); - if ((music != nullptr) && (filename != nullptr)) { - music->filename = filename; - } - return music; -} - -inline auto JA_LoadMusic(const char* filename) -> JA_Music_t* { - // Carreguem primer el arxiu en memòria i després el descomprimim. - FILE* f = fopen(filename, "rb"); - if (f == nullptr) { - return nullptr; - } - fseek(f, 0, SEEK_END); - long fsize = ftell(f); - fseek(f, 0, SEEK_SET); - if (fsize <= 0) { - fclose(f); - return nullptr; - } - auto* buffer = static_cast(malloc(fsize + 1)); - if (buffer == nullptr) { - fclose(f); - return nullptr; - } - if (fread(buffer, fsize, 1, f) != 1) { - fclose(f); - free(buffer); - return nullptr; - } - fclose(f); - - JA_Music_t* music = JA_LoadMusic(static_cast(buffer), static_cast(fsize)); - if (music != nullptr) { - music->filename = filename; + inline void quit() { + if (outgoing_music.stream != nullptr) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + } + if (sdl_audio_device != 0) { SDL_CloseAudioDevice(sdl_audio_device); } + sdl_audio_device = 0; } - free(buffer); + // --- Music Functions --- + inline auto loadMusic(const Uint8* buffer, Uint32 length) -> Music* { + if ((buffer == nullptr) || length == 0) { return nullptr; } - return music; -} + // Allocem el Music primer per aprofitar el seu `std::vector` + // com a propietari del OGG comprimit. stb_vorbis guarda un punter + // persistent al buffer; com que ací no el resize'jem, el .data() és + // estable durant tot el cicle de vida del music. + auto* music = new Music(); + music->ogg_data.assign(buffer, buffer + length); -inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) { - if (!JA_musicEnabled || (music == nullptr) || (music->vorbis == nullptr)) { - return; + int vorbis_error = 0; + music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(), + static_cast(length), + &vorbis_error, + nullptr); + if (music->vorbis == nullptr) { + std::cout << "loadMusic: stb_vorbis_open_memory failed (error " << vorbis_error << ")" << '\n'; + delete music; + return nullptr; + } + + const stb_vorbis_info INFO = stb_vorbis_get_info(music->vorbis); + music->spec.channels = INFO.channels; + music->spec.freq = static_cast(INFO.sample_rate); + music->spec.format = SDL_AUDIO_S16; + music->state = MusicState::STOPPED; + + return music; } - JA_StopMusic(); - - current_music = music; - current_music->state = JA_MUSIC_PLAYING; - current_music->times = loop; - - // Rebobinem l'stream de vorbis al principi. Cobreix tant play-per-primera- - // vegada com replays/canvis de track que tornen a la mateixa pista. - stb_vorbis_seek_start(current_music->vorbis); - - current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec); - if (current_music->stream == nullptr) { - std::cout << "Failed to create audio stream!" << '\n'; - current_music->state = JA_MUSIC_STOPPED; - return; - } - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); - - // Pre-cargem el buffer abans de bindejar per evitar un underrun inicial. - JA_PumpMusic(current_music); - - if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) { - std::cout << "[ERROR] SDL_BindAudioStream failed!" << '\n'; - } -} - -inline auto JA_GetMusicFilename(const JA_Music_t* music = nullptr) -> const char* { - if (music == nullptr) { - music = current_music; - } - if ((music == nullptr) || music->filename.empty()) { - return nullptr; - } - return music->filename.c_str(); -} - -inline void JA_PauseMusic() { - if (!JA_musicEnabled) { - return; - } - if ((current_music == nullptr) || current_music->state != JA_MUSIC_PLAYING) { - return; + // Overload amb filename — els callers l'usen per poder comparar la música + // en curs amb getMusicFilename() i no rearrancar-la si ja és la mateixa. + inline auto loadMusic(Uint8* buffer, Uint32 length, const char* filename) -> Music* { + Music* music = loadMusic(static_cast(buffer), length); + if ((music != nullptr) && (filename != nullptr)) { music->filename = filename; } + return music; } - current_music->state = JA_MUSIC_PAUSED; - SDL_UnbindAudioStream(current_music->stream); -} + inline auto loadMusic(const char* filename) -> Music* { + // Carreguem primer el arxiu en memòria i després el descomprimim. + FILE* f = std::fopen(filename, "rb"); + if (f == nullptr) { return nullptr; } + std::fseek(f, 0, SEEK_END); + const long FSIZE = std::ftell(f); + std::fseek(f, 0, SEEK_SET); + if (FSIZE <= 0) { + std::fclose(f); + return nullptr; + } + auto* buffer = static_cast(std::malloc(static_cast(FSIZE) + 1)); + if (buffer == nullptr) { + std::fclose(f); + return nullptr; + } + if (std::fread(buffer, FSIZE, 1, f) != 1) { + std::fclose(f); + std::free(buffer); + return nullptr; + } + std::fclose(f); -inline void JA_ResumeMusic() { - if (!JA_musicEnabled) { - return; - } - if ((current_music == nullptr) || current_music->state != JA_MUSIC_PAUSED) { - return; + Music* music = loadMusic(static_cast(buffer), static_cast(FSIZE)); + if (music != nullptr) { music->filename = filename; } + + std::free(buffer); + + return music; } - current_music->state = JA_MUSIC_PLAYING; - SDL_BindAudioStream(sdlAudioDevice, current_music->stream); -} + inline void playMusic(Music* music, int loop = -1) { + if (!music_enabled || (music == nullptr) || (music->vorbis == nullptr)) { return; } -inline void JA_StopMusic() { - // Limpiar outgoing crossfade si existe - if (outgoing_music.stream != nullptr) { - SDL_DestroyAudioStream(outgoing_music.stream); - outgoing_music.stream = nullptr; - outgoing_music.fade.active = false; - } - incoming_fade.active = false; + stopMusic(); - if ((current_music == nullptr) || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) { - return; - } + current_music = music; + current_music->state = MusicState::PLAYING; + current_music->times = loop; - current_music->state = JA_MUSIC_STOPPED; - if (current_music->stream != nullptr) { - SDL_DestroyAudioStream(current_music->stream); - current_music->stream = nullptr; - } - // Deixem el handle de vorbis viu — es tanca en JA_DeleteMusic. - // Rebobinem perquè un futur JA_PlayMusic comence des del principi. - if (current_music->vorbis != nullptr) { + // Rebobinem l'stream de vorbis al principi. Cobreix tant play-per-primera- + // vegada com replays/canvis de track que tornen a la mateixa pista. stb_vorbis_seek_start(current_music->vorbis); - } -} -inline void JA_FadeOutMusic(const int milliseconds) { - if (!JA_musicEnabled) { - return; - } - if ((current_music == nullptr) || current_music->state != JA_MUSIC_PLAYING) { - return; + current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &audio_spec); + if (current_music->stream == nullptr) { + std::cout << "Failed to create audio stream!" << '\n'; + current_music->state = MusicState::STOPPED; + return; + } + SDL_SetAudioStreamGain(current_music->stream, music_volume); + + // Pre-cargem el buffer abans de bindejar per evitar un underrun inicial. + pumpMusic(current_music); + + if (!SDL_BindAudioStream(sdl_audio_device, current_music->stream)) { + std::cout << "[ERROR] SDL_BindAudioStream failed!" << '\n'; + } } - // Destruir outgoing anterior si existe - if (outgoing_music.stream != nullptr) { - SDL_DestroyAudioStream(outgoing_music.stream); - outgoing_music.stream = nullptr; + inline auto getMusicFilename(const Music* music = nullptr) -> const char* { + if (music == nullptr) { music = current_music; } + if ((music == nullptr) || music->filename.empty()) { return nullptr; } + return music->filename.c_str(); } - // Pre-omplim l'stream amb `milliseconds` de so: un cop robat, ja no - // tindrà accés al vorbis decoder i només podrà drenar el que tinga. - JA_PreFillOutgoing(current_music, milliseconds); + inline void pauseMusic() { + if (!music_enabled) { return; } + if ((current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; } - // Robar el stream del current_music al outgoing - outgoing_music.stream = current_music->stream; - outgoing_music.fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = milliseconds, .initial_volume = JA_musicVolume}; - - // Dejar current_music sin stream (ya lo tiene outgoing) - current_music->stream = nullptr; - current_music->state = JA_MUSIC_STOPPED; - if (current_music->vorbis != nullptr) { - stb_vorbis_seek_start(current_music->vorbis); - } - incoming_fade.active = false; -} - -inline void JA_CrossfadeMusic(JA_Music_t* music, const int crossfade_ms, const int loop) { - if (!JA_musicEnabled || (music == nullptr) || (music->vorbis == nullptr)) { - return; + current_music->state = MusicState::PAUSED; + SDL_UnbindAudioStream(current_music->stream); } - // Destruir outgoing anterior si existe (crossfade durante crossfade) - if (outgoing_music.stream != nullptr) { - SDL_DestroyAudioStream(outgoing_music.stream); - outgoing_music.stream = nullptr; - outgoing_music.fade.active = false; + inline void resumeMusic() { + if (!music_enabled) { return; } + if ((current_music == nullptr) || current_music->state != MusicState::PAUSED) { return; } + + current_music->state = MusicState::PLAYING; + SDL_BindAudioStream(sdl_audio_device, current_music->stream); } - // Robar el stream de la musica actual al outgoing para el fade-out. - // Pre-omplim amb `crossfade_ms` de so perquè no es quede en silenci - // abans d'acabar el fade (l'stream robat ja no pot alimentar-se). - if ((current_music != nullptr) && current_music->state == JA_MUSIC_PLAYING && (current_music->stream != nullptr)) { - JA_PreFillOutgoing(current_music, crossfade_ms); - outgoing_music.stream = current_music->stream; - outgoing_music.fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = crossfade_ms, .initial_volume = JA_musicVolume}; - current_music->stream = nullptr; - current_music->state = JA_MUSIC_STOPPED; + inline void stopMusic() { + // Limpiar outgoing crossfade si existe + if (outgoing_music.stream != nullptr) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + outgoing_music.fade.active = false; + } + incoming_fade.active = false; + + if ((current_music == nullptr) || current_music->state == MusicState::INVALID || current_music->state == MusicState::STOPPED) { return; } + + current_music->state = MusicState::STOPPED; + if (current_music->stream != nullptr) { + SDL_DestroyAudioStream(current_music->stream); + current_music->stream = nullptr; + } + // Deixem el handle de vorbis viu — es tanca en deleteMusic. + // Rebobinem perquè un futur playMusic comence des del principi. if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); } } - // Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente) - current_music = music; - current_music->state = JA_MUSIC_PLAYING; - current_music->times = loop; + inline void fadeOutMusic(int milliseconds) { + if (!music_enabled) { return; } + if ((current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; } - stb_vorbis_seek_start(current_music->vorbis); - current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec); - if (current_music->stream == nullptr) { - std::cout << "Failed to create audio stream for crossfade!" << '\n'; - current_music->state = JA_MUSIC_STOPPED; - return; - } - SDL_SetAudioStreamGain(current_music->stream, 0.0F); - JA_PumpMusic(current_music); // pre-carrega abans de bindejar - SDL_BindAudioStream(sdlAudioDevice, current_music->stream); - - // Configurar fade-in - incoming_fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = crossfade_ms, .initial_volume = 0.0F}; -} - -inline auto JA_GetMusicState() -> JA_Music_state { - if (!JA_musicEnabled) { - return JA_MUSIC_DISABLED; - } - if (current_music == nullptr) { - return JA_MUSIC_INVALID; - } - - return current_music->state; -} - -inline void JA_DeleteMusic(JA_Music_t* music) { - if (music == nullptr) { - return; - } - if (current_music == music) { - JA_StopMusic(); - current_music = nullptr; - } - if (music->stream != nullptr) { - SDL_DestroyAudioStream(music->stream); - } - if (music->vorbis != nullptr) { - stb_vorbis_close(music->vorbis); - } - // ogg_data (std::vector) i filename (std::string) s'alliberen sols - // al destructor de JA_Music_t. - delete music; -} - -inline auto JA_SetMusicVolume(float volume) -> float { - JA_musicVolume = SDL_clamp(volume, 0.0F, 1.0F); - if ((current_music != nullptr) && (current_music->stream != nullptr)) { - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); - } - return JA_musicVolume; -} - -inline void JA_SetMusicPosition(float /*value*/) { - // No implementat amb el backend de streaming. -} - -inline auto JA_GetMusicPosition() -> float { - return 0.0F; -} - -inline void JA_EnableMusic(const bool value) { - if (!value && (current_music != nullptr) && (current_music->state == JA_MUSIC_PLAYING)) { - JA_StopMusic(); - } - JA_musicEnabled = value; -} - -// --- Sound Functions --- - -inline auto JA_LoadSound(uint8_t* buffer, uint32_t size) -> JA_Sound_t* { - auto sound = std::make_unique(); - Uint8* raw = nullptr; - if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), true, &sound->spec, &raw, &sound->length)) { - std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n'; - return nullptr; - } - sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer - return sound.release(); -} - -inline auto JA_LoadSound(const char* filename) -> JA_Sound_t* { - auto sound = std::make_unique(); - Uint8* raw = nullptr; - if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) { - std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n'; - return nullptr; - } - sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer - return sound.release(); -} - -inline auto JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) -> int { - if (!JA_soundEnabled || (sound == nullptr)) { - return -1; - } - - int channel = 0; - while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; } - if (channel == JA_MAX_SIMULTANEOUS_CHANNELS) { - // No hay canal libre, reemplazamos el primero - channel = 0; - } - - return JA_PlaySoundOnChannel(sound, channel, loop, group); -} - -inline auto JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop, const int group) -> int { - if (!JA_soundEnabled || (sound == nullptr)) { - return -1; - } - if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) { - return -1; - } - - JA_StopChannel(channel); - - channels[channel].sound = sound; - channels[channel].times = loop; - channels[channel].pos = 0; - channels[channel].group = group; - channels[channel].state = JA_CHANNEL_PLAYING; - channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec); - - if (channels[channel].stream == nullptr) { - std::cout << "Failed to create audio stream for sound!" << '\n'; - channels[channel].state = JA_CHANNEL_FREE; - return -1; - } - - SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer.get(), channels[channel].sound->length); - SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]); - SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); - - return channel; -} - -inline void JA_DeleteSound(JA_Sound_t* sound) { - if (sound == nullptr) { - return; - } - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { - if (channels[i].sound == sound) { - JA_StopChannel(i); + // Destruir outgoing anterior si existe + if (outgoing_music.stream != nullptr) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; } - } - // buffer es destrueix automàticament via RAII (SDLFreeDeleter). - delete sound; -} -inline void JA_PauseChannel(const int channel) { - if (!JA_soundEnabled) { - return; + // Pre-omplim l'stream amb `milliseconds` de so: un cop robat, ja no + // tindrà accés al vorbis decoder i només podrà drenar el que tinga. + preFillOutgoing(current_music, milliseconds); + + // Robar el stream del current_music al outgoing + outgoing_music.stream = current_music->stream; + outgoing_music.fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = milliseconds, .initial_volume = music_volume}; + + // Dejar current_music sin stream (ya lo tiene outgoing) + current_music->stream = nullptr; + current_music->state = MusicState::STOPPED; + if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); } + incoming_fade.active = false; } - if (channel == -1) { - for (auto& ch : channels) { - if (ch.state == JA_CHANNEL_PLAYING) { - ch.state = JA_CHANNEL_PAUSED; - SDL_UnbindAudioStream(ch.stream); - } + inline void crossfadeMusic(Music* music, int crossfade_ms, int loop) { + if (!music_enabled || (music == nullptr) || (music->vorbis == nullptr)) { return; } + + // Destruir outgoing anterior si existe (crossfade durante crossfade) + if (outgoing_music.stream != nullptr) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + outgoing_music.fade.active = false; } - } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { - if (channels[channel].state == JA_CHANNEL_PLAYING) { - channels[channel].state = JA_CHANNEL_PAUSED; - SDL_UnbindAudioStream(channels[channel].stream); - } - } -} -inline void JA_ResumeChannel(const int channel) { - if (!JA_soundEnabled) { - return; + // Robar el stream de la musica actual al outgoing para el fade-out. + // Pre-omplim amb `crossfade_ms` de so perquè no es quede en silenci + // abans d'acabar el fade (l'stream robat ja no pot alimentar-se). + if ((current_music != nullptr) && current_music->state == MusicState::PLAYING && (current_music->stream != nullptr)) { + preFillOutgoing(current_music, crossfade_ms); + outgoing_music.stream = current_music->stream; + outgoing_music.fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = crossfade_ms, .initial_volume = music_volume}; + current_music->stream = nullptr; + current_music->state = MusicState::STOPPED; + if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); } + } + + // Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente) + current_music = music; + current_music->state = MusicState::PLAYING; + current_music->times = loop; + + stb_vorbis_seek_start(current_music->vorbis); + current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &audio_spec); + if (current_music->stream == nullptr) { + std::cout << "Failed to create audio stream for crossfade!" << '\n'; + current_music->state = MusicState::STOPPED; + return; + } + SDL_SetAudioStreamGain(current_music->stream, 0.0F); + pumpMusic(current_music); // pre-carrega abans de bindejar + SDL_BindAudioStream(sdl_audio_device, current_music->stream); + + // Configurar fade-in + incoming_fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = crossfade_ms, .initial_volume = 0.0F}; } - if (channel == -1) { - for (auto& ch : channels) { - if (ch.state == JA_CHANNEL_PAUSED) { - ch.state = JA_CHANNEL_PLAYING; - SDL_BindAudioStream(sdlAudioDevice, ch.stream); - } - } - } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { - if (channels[channel].state == JA_CHANNEL_PAUSED) { - channels[channel].state = JA_CHANNEL_PLAYING; - SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); - } - } -} + inline auto getMusicState() -> MusicState { + if (!music_enabled) { return MusicState::DISABLED; } + if (current_music == nullptr) { return MusicState::INVALID; } -inline void JA_StopChannel(const int channel) { - if (channel == -1) { - for (auto& ch : channels) { - if (ch.state != JA_CHANNEL_FREE) { - if (ch.stream != nullptr) { - SDL_DestroyAudioStream(ch.stream); + return current_music->state; + } + + inline void deleteMusic(Music* music) { + if (music == nullptr) { return; } + if (current_music == music) { + stopMusic(); + current_music = nullptr; + } + if (music->stream != nullptr) { SDL_DestroyAudioStream(music->stream); } + if (music->vorbis != nullptr) { stb_vorbis_close(music->vorbis); } + // ogg_data (std::vector) i filename (std::string) s'alliberen sols + // al destructor de Music. + delete music; + } + + inline auto setMusicVolume(float volume) -> float { + music_volume = SDL_clamp(volume, 0.0F, 1.0F); + if ((current_music != nullptr) && (current_music->stream != nullptr)) { + SDL_SetAudioStreamGain(current_music->stream, music_volume); + } + return music_volume; + } + + inline void setMusicPosition(float /*value*/) { + // No implementat amb el backend de streaming. + } + + inline auto getMusicPosition() -> float { + return 0.0F; + } + + inline void enableMusic(bool value) { + if (!value && (current_music != nullptr) && (current_music->state == MusicState::PLAYING)) { stopMusic(); } + music_enabled = value; + } + + // --- Sound Functions --- + inline auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound* { + auto sound = std::make_unique(); + Uint8* raw = nullptr; + if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), true, &sound->spec, &raw, &sound->length)) { + std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n'; + return nullptr; + } + sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer + return sound.release(); + } + + inline auto loadSound(const char* filename) -> Sound* { + auto sound = std::make_unique(); + Uint8* raw = nullptr; + if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) { + std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n'; + return nullptr; + } + sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer + return sound.release(); + } + + inline auto playSound(Sound* sound, int loop = 0, int group = 0) -> int { + if (!sound_enabled || (sound == nullptr)) { return -1; } + + int channel = 0; + while (channel < MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != ChannelState::FREE) { channel++; } + if (channel == MAX_SIMULTANEOUS_CHANNELS) { + // No hi ha canal lliure, reemplacem el primer + channel = 0; + } + + return playSoundOnChannel(sound, channel, loop, group); + } + + inline auto playSoundOnChannel(Sound* sound, int channel, int loop, int group) -> int { + if (!sound_enabled || (sound == nullptr)) { return -1; } + if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return -1; } + + stopChannel(channel); + + channels[channel].sound = sound; + channels[channel].times = loop; + channels[channel].pos = 0; + channels[channel].group = group; + channels[channel].state = ChannelState::PLAYING; + channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &audio_spec); + + if (channels[channel].stream == nullptr) { + std::cout << "Failed to create audio stream for sound!" << '\n'; + channels[channel].state = ChannelState::FREE; + return -1; + } + + SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer.get(), channels[channel].sound->length); + SDL_SetAudioStreamGain(channels[channel].stream, sound_volume[group]); + SDL_BindAudioStream(sdl_audio_device, channels[channel].stream); + + return channel; + } + + inline void deleteSound(Sound* sound) { + if (sound == nullptr) { return; } + for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; i++) { + if (channels[i].sound == sound) { stopChannel(i); } + } + // buffer es destrueix automàticament via RAII (SdlFreeDeleter). + delete sound; + } + + inline void pauseChannel(int channel) { + if (!sound_enabled) { return; } + + if (channel == -1) { + for (auto& ch : channels) { + if (ch.state == ChannelState::PLAYING) { + ch.state = ChannelState::PAUSED; + SDL_UnbindAudioStream(ch.stream); } - ch.stream = nullptr; - ch.state = JA_CHANNEL_FREE; - ch.pos = 0; - ch.sound = nullptr; + } + } else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) { + if (channels[channel].state == ChannelState::PLAYING) { + channels[channel].state = ChannelState::PAUSED; + SDL_UnbindAudioStream(channels[channel].stream); } } - } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { - if (channels[channel].state != JA_CHANNEL_FREE) { - if (channels[channel].stream != nullptr) { - SDL_DestroyAudioStream(channels[channel].stream); + } + + inline void resumeChannel(int channel) { + if (!sound_enabled) { return; } + + if (channel == -1) { + for (auto& ch : channels) { + if (ch.state == ChannelState::PAUSED) { + ch.state = ChannelState::PLAYING; + SDL_BindAudioStream(sdl_audio_device, ch.stream); + } + } + } else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) { + if (channels[channel].state == ChannelState::PAUSED) { + channels[channel].state = ChannelState::PLAYING; + SDL_BindAudioStream(sdl_audio_device, channels[channel].stream); } - channels[channel].stream = nullptr; - channels[channel].state = JA_CHANNEL_FREE; - channels[channel].pos = 0; - channels[channel].sound = nullptr; } } -} -inline auto JA_GetChannelState(const int channel) -> JA_Channel_state { - if (!JA_soundEnabled) { - return JA_SOUND_DISABLED; - } - if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) { - return JA_CHANNEL_INVALID; + inline void stopChannel(int channel) { + if (channel == -1) { + for (auto& ch : channels) { + if (ch.state != ChannelState::FREE) { + if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); } + ch.stream = nullptr; + ch.state = ChannelState::FREE; + ch.pos = 0; + ch.sound = nullptr; + } + } + } else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) { + if (channels[channel].state != ChannelState::FREE) { + if (channels[channel].stream != nullptr) { SDL_DestroyAudioStream(channels[channel].stream); } + channels[channel].stream = nullptr; + channels[channel].state = ChannelState::FREE; + channels[channel].pos = 0; + channels[channel].sound = nullptr; + } + } } - return channels[channel].state; -} + inline auto getChannelState(int channel) -> ChannelState { + if (!sound_enabled) { return ChannelState::DISABLED; } + if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return ChannelState::INVALID; } -inline auto JA_SetSoundVolume(float volume, const int group = -1) -> float { - const float v = SDL_clamp(volume, 0.0F, 1.0F); - - if (group == -1) { - std::fill(std::begin(JA_soundVolume), std::end(JA_soundVolume), v); - } else if (group >= 0 && group < JA_MAX_GROUPS) { - JA_soundVolume[group] = v; - } else { - return v; + return channels[channel].state; } - // Aplicar volum als canals actius. - for (auto& channel : channels) { - if ((channel.state == JA_CHANNEL_PLAYING) || (channel.state == JA_CHANNEL_PAUSED)) { - if (group == -1 || channel.group == group) { - if (channel.stream != nullptr) { - SDL_SetAudioStreamGain(channel.stream, JA_soundVolume[channel.group]); + inline auto setSoundVolume(float volume, int group = -1) -> float { + const float V = SDL_clamp(volume, 0.0F, 1.0F); + + if (group == -1) { + std::fill(std::begin(sound_volume), std::end(sound_volume), V); + } else if (group >= 0 && group < MAX_GROUPS) { + sound_volume[group] = V; + } else { + return V; + } + + // Aplicar volum als canals actius. + for (auto& ch : channels) { + if ((ch.state == ChannelState::PLAYING) || (ch.state == ChannelState::PAUSED)) { + if (group == -1 || ch.group == group) { + if (ch.stream != nullptr) { + SDL_SetAudioStreamGain(ch.stream, sound_volume[ch.group]); + } } } } + return V; } - return v; -} -inline void JA_EnableSound(const bool value) { - if (!value) { - JA_StopChannel(-1); + inline void enableSound(bool value) { + if (!value) { + stopChannel(-1); + } + sound_enabled = value; } - JA_soundEnabled = value; -} -inline auto JA_SetVolume(float volume) -> float { - float v = JA_SetMusicVolume(volume); - JA_SetSoundVolume(v, -1); - return v; -} + inline auto setVolume(float volume) -> float { + const float V = setMusicVolume(volume); + setSoundVolume(V, -1); + return V; + } + +} // namespace Ja diff --git a/source/core/resources/resource_cache.cpp b/source/core/resources/resource_cache.cpp index 681c813..14a0be9 100644 --- a/source/core/resources/resource_cache.cpp +++ b/source/core/resources/resource_cache.cpp @@ -33,7 +33,7 @@ namespace Resource { } } // namespace - auto Cache::getMusic(const std::string& name) -> JA_Music_t* { + auto Cache::getMusic(const std::string& name) -> Ja::Music* { auto it = std::ranges::find_if(musics_, [&](const auto& m) { return m.name == name; }); if (it != musics_.end()) { return it->music.get(); @@ -42,7 +42,7 @@ namespace Resource { throw std::runtime_error("Music not found: " + name); } - auto Cache::getSound(const std::string& name) -> JA_Sound_t* { + auto Cache::getSound(const std::string& name) -> Ja::Sound* { auto it = std::ranges::find_if(sounds_, [&](const auto& s) { return s.name == name; }); if (it != sounds_.end()) { return it->sound.get(); @@ -192,12 +192,12 @@ namespace Resource { std::cerr << "Resource::Cache: no s'ha pogut llegir " << path << '\n'; return; } - JA_Music_t* music = JA_LoadMusic(bytes.data(), static_cast(bytes.size()), path.c_str()); + Ja::Music* music = Ja::loadMusic(bytes.data(), static_cast(bytes.size()), path.c_str()); if (music == nullptr) { - std::cerr << "Resource::Cache: JA_LoadMusic ha fallat per " << path << '\n'; + std::cerr << "Resource::Cache: Ja::loadMusic ha fallat per " << path << '\n'; return; } - musics_.push_back(MusicResource{.name = name, .music = std::unique_ptr(music)}); + musics_.push_back(MusicResource{.name = name, .music = std::unique_ptr(music)}); ++loaded_count_; std::cout << " [music ] " << name << '\n'; } @@ -213,12 +213,12 @@ namespace Resource { std::cerr << "Resource::Cache: no s'ha pogut llegir " << path << '\n'; return; } - JA_Sound_t* sound = JA_LoadSound(bytes.data(), static_cast(bytes.size())); + Ja::Sound* sound = Ja::loadSound(bytes.data(), static_cast(bytes.size())); if (sound == nullptr) { - std::cerr << "Resource::Cache: JA_LoadSound ha fallat per " << path << '\n'; + std::cerr << "Resource::Cache: Ja::loadSound ha fallat per " << path << '\n'; return; } - sounds_.push_back(SoundResource{.name = name, .sound = std::unique_ptr(sound)}); + sounds_.push_back(SoundResource{.name = name, .sound = std::unique_ptr(sound)}); ++loaded_count_; std::cout << " [sound ] " << name << '\n'; } diff --git a/source/core/resources/resource_cache.hpp b/source/core/resources/resource_cache.hpp index 8f26959..28f1a80 100644 --- a/source/core/resources/resource_cache.hpp +++ b/source/core/resources/resource_cache.hpp @@ -27,8 +27,8 @@ namespace Resource { auto operator=(const Cache&) -> Cache& = delete; // Getters: throw runtime_error si el nom no existeix al cache. - auto getMusic(const std::string& name) -> JA_Music_t*; - auto getSound(const std::string& name) -> JA_Sound_t*; + auto getMusic(const std::string& name) -> Ja::Music*; + auto getSound(const std::string& name) -> Ja::Sound*; auto getSurfacePixels(const std::string& name) -> const std::vector&; auto getPaletteBytes(const std::string& name) -> const std::vector&; auto getTextFile(const std::string& name) -> const std::vector&; diff --git a/source/core/resources/resource_types.hpp b/source/core/resources/resource_types.hpp index 92727f9..77330fe 100644 --- a/source/core/resources/resource_types.hpp +++ b/source/core/resources/resource_types.hpp @@ -8,38 +8,39 @@ #include // Forward declarations to keep this header light. -struct JA_Music_t; -struct JA_Sound_t; - -void JA_DeleteMusic(JA_Music_t* music); -void JA_DeleteSound(JA_Sound_t* sound); +namespace Ja { + struct Music; + struct Sound; + void deleteMusic(Music* music); + void deleteSound(Sound* sound); +} // namespace Ja namespace Resource { struct MusicDeleter { - void operator()(JA_Music_t* music) const noexcept { + void operator()(Ja::Music* music) const noexcept { if (music != nullptr) { - JA_DeleteMusic(music); + Ja::deleteMusic(music); } } }; struct SoundDeleter { - void operator()(JA_Sound_t* sound) const noexcept { + void operator()(Ja::Sound* sound) const noexcept { if (sound != nullptr) { - JA_DeleteSound(sound); + Ja::deleteSound(sound); } } }; struct MusicResource { std::string name; - std::unique_ptr music; + std::unique_ptr music; }; struct SoundResource { std::string name; - std::unique_ptr sound; + std::unique_ptr sound; }; // Una entrada BITMAP descodifica un GIF i emmagatzema els seus diff --git a/source/game/scenes/banner_scene.hpp b/source/game/scenes/banner_scene.hpp index a56d272..8940e3b 100644 --- a/source/game/scenes/banner_scene.hpp +++ b/source/game/scenes/banner_scene.hpp @@ -15,7 +15,7 @@ namespace scenes { // 2. Pinta títol, subtítol i número de piràmide segons info::ctx.num_piramide. // 3. Fade-in de paleta. // 4. Mostra ~5s o fins que es polse una tecla. - // 5. JA_FadeOutMusic(250) + fade-out de paleta. + // 5. Ja::fadeOutMusic(250) + fade-out de paleta. // 6. Retorna nextState=0 per a entrar al ModuleGame. // // Registrat al SceneRegistry amb state_keys 2..5 (els num_piramide on diff --git a/source/game/scenes/credits_scene.cpp b/source/game/scenes/credits_scene.cpp index 83a460b..b0084ce 100644 --- a/source/game/scenes/credits_scene.cpp +++ b/source/game/scenes/credits_scene.cpp @@ -44,7 +44,7 @@ namespace scenes { // previ ("music/final.ogg"). Si l'escena s'arrenca directament (test // amb piramide_inicial=8) no hi ha res que heretar, així que // arranquem la mateixa pista només si no sona res. Inocu en el - // flux normal: JA_MUSIC_PLAYING fa que no la tornem a tocar. + // flux normal: Ja::MusicState::PLAYING fa que no la tornem a tocar. if (Audio::getRealMusicState() != Audio::MusicState::PLAYING) { playMusic("music/final.ogg"); } diff --git a/source/game/scenes/secreta_scene.hpp b/source/game/scenes/secreta_scene.hpp index ed976b1..775b040 100644 --- a/source/game/scenes/secreta_scene.hpp +++ b/source/game/scenes/secreta_scene.hpp @@ -22,7 +22,7 @@ namespace scenes { // i pinta un revelat horitzontal (~1.6 s + ~1.6 s de pausa). // 5. "Red pulse": anima els colors 253/254 incrementant el canal R // de 12 a 62 durant ~1 s (+ ~1 s de pausa). - // 6. FadeOut + JA_FadeOutMusic(250). + // 6. FadeOut + Ja::fadeOutMusic(250). // 7. Retorna nextState=0 per entrar al ModuleGame amb num_piramide=6. // // Registrada al SceneRegistry amb state_key = 6. diff --git a/source/game/scenes/slides_scene.cpp b/source/game/scenes/slides_scene.cpp index e354ba9..7031206 100644 --- a/source/game/scenes/slides_scene.cpp +++ b/source/game/scenes/slides_scene.cpp @@ -102,7 +102,7 @@ namespace scenes { void SlidesScene::tick(int delta_ms) { // Skip: qualsevol tecla salta directament al fade final. Per fidelitat // al vell doSlides, el skip NO atura la música explícitament — només - // el final natural crida JA_FadeOutMusic (beginFinalFade() distingeix). + // el final natural crida Ja::fadeOutMusic (beginFinalFade() distingeix). if (!skip_triggered_ && JI_AnyKey()) { skip_triggered_ = true; if (num_piramide_at_start_ != 7) { diff --git a/source/game/scenes/slides_scene.hpp b/source/game/scenes/slides_scene.hpp index 58b04f9..678d6cc 100644 --- a/source/game/scenes/slides_scene.hpp +++ b/source/game/scenes/slides_scene.hpp @@ -26,7 +26,7 @@ namespace scenes { // → FadeOut2 + clear + reset paleta // → Slide3Enter (1600 ms scroll dreta→centre) // → Slide3Hold (4600 ms) - // → FadeFinal (JA_FadeOutMusic si num_piramide != 7 + fade paleta) + // → FadeFinal (Ja::fadeOutMusic si num_piramide != 7 + fade paleta) // → Done // // Qualsevol tecla salta directament a FadeFinal (sense cortar la música diff --git a/source/game/scenes/timeline.hpp b/source/game/scenes/timeline.hpp index 2fd9ee3..22ccf79 100644 --- a/source/game/scenes/timeline.hpp +++ b/source/game/scenes/timeline.hpp @@ -12,7 +12,7 @@ namespace scenes { // .once([this] { JD8_ClearScreen(0); fade_.startFadeTo(pal); }) // .step(5000) // espera pura // .step(1000, [this](float p) { /*...*/ }) // animat amb progress - // .once([this] { JA_FadeOutMusic(250); }); + // .once([this] { Ja::fadeOutMusic(250); }); // // `tick(delta_ms)` avança el temps. Els passos one-shot s'executen al // moment d'entrar-hi i avancen immediatament. Els passos amb duració diff --git a/source/main.cpp b/source/main.cpp index 5b23e10..ecf0742 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -92,7 +92,7 @@ auto SDL_AppInit(void** /*appstate*/, int /*argc*/, char* /*argv*/[]) -> SDL_App JG_Init(); Screen::init(); JD8_Init(); - Audio::init(); // crida internament JA_Init i aplica Options::audio + Audio::init(); // crida internament Ja::init i aplica Options::audio Overlay::init(); Menu::init(); @@ -146,7 +146,7 @@ void SDL_AppQuit(void* /*appstate*/, SDL_AppResult /*result*/) { Overlay::destroy(); Resource::Cache::destroy(); Resource::List::destroy(); - Audio::destroy(); // el destructor del singleton crida JA_Quit + Audio::destroy(); // el destructor del singleton crida Ja::quit JD8_Quit(); Screen::destroy(); JG_Finalize();