From be95b8afabb4448b65caeb84db149f565ae98274 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sat, 16 May 2026 17:56:46 +0200 Subject: [PATCH] refactor jail_audio: namespace Ja, enum class, tipus sense prefix JA_ --- source/core/audio/audio.cpp | 68 +- source/core/audio/audio.hpp | 26 +- source/core/audio/audio_adapter.cpp | 4 +- source/core/audio/audio_adapter.hpp | 14 +- source/core/audio/jail_audio.hpp | 1333 ++++++++++++--------------- source/core/resources/resource.cpp | 14 +- source/core/resources/resource.h | 14 +- source/core/system/director.cpp | 2 +- source/game/game.cpp | 6 +- source/game/game.h | 38 +- source/game/scenes/intro.h | 6 +- source/game/scenes/title.h | 10 +- source/game/ui/menu.cpp | 28 +- source/game/ui/menu.h | 10 +- 14 files changed, 741 insertions(+), 832 deletions(-) diff --git a/source/core/audio/audio.cpp b/source/core/audio/audio.cpp index aebaaa5..aa3d521 100644 --- a/source/core/audio/audio.cpp +++ b/source/core/audio/audio.cpp @@ -20,7 +20,7 @@ // clang-format on #include "core/audio/audio_adapter.hpp" // Para AudioResource::getMusic/getSound -#include "core/audio/jail_audio.hpp" // Para JA_* +#include "core/audio/jail_audio.hpp" // Para Ja namespace #include "game/options.hpp" // Para Options::audio // Singleton @@ -43,15 +43,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 != nullptr) && instance->music_.state == MusicState::PLAYING && JA_GetMusicState() != JA_MUSIC_PLAYING) { + if (instance != nullptr && instance->music_.state == MusicState::PLAYING && Ja::getMusicState() != Ja::MusicState::PLAYING) { instance->music_.state = MusicState::STOPPED; } } @@ -65,22 +65,18 @@ void Audio::playMusic(const std::string& name, const int loop, const int crossfa return; } - if (!music_enabled_) { - return; - } + if (!music_enabled_) { return; } auto* resource = AudioResource::getMusic(name); - if (resource == nullptr) { - return; - } + if (resource == nullptr) { return; } 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; @@ -89,18 +85,16 @@ 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) { - if (!music_enabled_ || music == nullptr) { - return; - } +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 @@ -111,7 +105,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; } } @@ -119,7 +113,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; } } @@ -127,7 +121,7 @@ void Audio::resumeMusic() { // Detiene la música void Audio::stopMusic() { if (music_enabled_) { - JA_StopMusic(); + Ja::stopMusic(); music_.state = MusicState::STOPPED; } } @@ -135,42 +129,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; } @@ -181,7 +175,7 @@ void Audio::setSoundVolume(float sound_volume, Group group) const { if (sound_enabled_) { sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME); const float CONVERTED_VOLUME = sound_volume * Options::audio.volume; - JA_SetSoundVolume(CONVERTED_VOLUME, static_cast(group)); + Ja::setSoundVolume(CONVERTED_VOLUME, static_cast(group)); } } @@ -190,7 +184,7 @@ void Audio::setMusicVolume(float music_volume) const { if (music_enabled_) { music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME); const float CONVERTED_VOLUME = music_volume * Options::audio.volume; - JA_SetMusicVolume(CONVERTED_VOLUME); + Ja::setMusicVolume(CONVERTED_VOLUME); } } @@ -212,7 +206,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 21cb5d2..be490d4 100644 --- a/source/core/audio/audio.hpp +++ b/source/core/audio/audio.hpp @@ -1,8 +1,14 @@ #pragma once -#include // Para lround +#include // Para std::lround #include // Para int8_t, uint8_t #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. @@ -41,17 +47,17 @@ class Audio { static void update(); // Actualización del sistema de 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 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 por nombre (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 + 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 - void playSound(struct JA_Sound_t* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero - void stopAllSounds() const; // Detener todos los sonidos + void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre + 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) --- void setSoundVolume(float volume, Group group = Group::ALL) const; // Ajustar volumen de efectos diff --git a/source/core/audio/audio_adapter.cpp b/source/core/audio/audio_adapter.cpp index ab2ec76..b400f34 100644 --- a/source/core/audio/audio_adapter.cpp +++ b/source/core/audio/audio_adapter.cpp @@ -3,11 +3,11 @@ #include "core/resources/resource.h" namespace AudioResource { - auto getMusic(const std::string& name) -> JA_Music_t* { + auto getMusic(const std::string& name) -> Ja::Music* { return Resource::get()->getMusic(name); } - auto getSound(const std::string& name) -> JA_Sound_t* { + auto getSound(const std::string& name) -> Ja::Sound* { return Resource::get()->getSound(name); } } // namespace AudioResource 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 fd2ebf2..3e4739b 100644 --- a/source/core/audio/jail_audio.hpp +++ b/source/core/audio/jail_audio.hpp @@ -2,802 +2,697 @@ // --- Includes --- #include -#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.h" // 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; } + } + } + + // 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 * 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 = feedMusicChunk(music); + if (DECODED <= 0) { break; } // EOF: deixem drenar el que hi haja + } + } + + // --- update() helpers --- + inline void updateOutgoingFade() { + if ((outgoing_music.stream == nullptr) || !outgoing_music.fade.active) { return; } + + 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 { - break; + 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)); } } -} -// 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; - } + inline void updateIncomingFade() { + if (!incoming_fade.active) { 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); - - while (SDL_GetAudioStreamAvailable(music->stream) < needed_bytes) { - const int decoded = JA_FeedMusicChunk(music); - if (decoded <= 0) { - break; // EOF: deixem drenar el que hi haja + 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); } } -} -// --- Core Functions --- + inline void updateCurrentMusic() { + if (!music_enabled || (current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; } -// Fade-out de la música sortint (crossfade o fade-out a silenci). En acabar -// destrueix l'AudioStream sortint. -inline void JA_UpdateOutgoingFade() { - if ((outgoing_music.stream == nullptr) || !outgoing_music.fade.active) { - return; - } - const Uint64 elapsed = SDL_GetTicks() - 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; - return; - } - 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)); -} + updateIncomingFade(); -// Fade-in de la música entrant (segona meitat d'un crossfade). -inline void JA_UpdateIncomingFade() { - if (!incoming_fade.active) { - return; + // Streaming: rellenem l'stream fins al low-water-mark i parem si el + // vorbis s'ha esgotat i no queden loops. + pumpMusic(current_music); + if (current_music->times == 0 && SDL_GetAudioStreamAvailable(current_music->stream) == 0) { + stopMusic(); + } } - const Uint64 elapsed = SDL_GetTicks() - incoming_fade.start_time; - if (elapsed >= static_cast(incoming_fade.duration_ms)) { + + 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 update() { + updateOutgoingFade(); + updateCurrentMusic(); + updateSoundChannels(); + } + + 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::ranges::fill(sound_volume, 0.5F); + } + + 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; + } + + // --- Music Functions --- + inline auto loadMusic(const Uint8* buffer, Uint32 length) -> Music* { + if ((buffer == nullptr) || length == 0) { return nullptr; } + + // 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); + + 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; + } + + // 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; + } + + 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); + + Music* music = loadMusic(static_cast(buffer), static_cast(FSIZE)); + if (music != nullptr) { music->filename = filename; } + + std::free(buffer); + + return music; + } + + inline void playMusic(Music* music, int loop = -1) { + if (!music_enabled || (music == nullptr) || (music->vorbis == nullptr)) { return; } + + stopMusic(); + + current_music = music; + current_music->state = MusicState::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, &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'; + } + } + + 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(); + } + + inline void pauseMusic() { + if (!music_enabled) { return; } + if ((current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; } + + current_music->state = MusicState::PAUSED; + SDL_UnbindAudioStream(current_music->stream); + } + + 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); + } + + 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; - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); - return; - } - const float percent = static_cast(elapsed) / static_cast(incoming_fade.duration_ms); - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * percent); -} -// Manté l'stream de la música activa alimentat i para la reproducció si -// el vorbis s'ha esgotat i no queden loops. -inline void JA_UpdateCurrentMusic() { - if (!JA_musicEnabled || current_music == nullptr || current_music->state != JA_MUSIC_PLAYING) { - return; - } - JA_UpdateIncomingFade(); - JA_PumpMusic(current_music); - if (current_music->times == 0 && SDL_GetAudioStreamAvailable(current_music->stream) == 0) { - JA_StopMusic(); - } -} + if ((current_music == nullptr) || current_music->state == MusicState::INVALID || current_music->state == MusicState::STOPPED) { return; } -// Avança l'estat d'un sol canal de so: alimenta més samples mentre quedin -// loops; si ja no queden i l'stream s'ha buidat, atura el canal. -inline void JA_UpdateSoundChannel(int channel_index) { - JA_Channel_t& ch = channels[channel_index]; - if (ch.state != JA_CHANNEL_PLAYING) { - return; - } - if (ch.times == 0) { - if (SDL_GetAudioStreamAvailable(ch.stream) == 0) { - JA_StopChannel(channel_index); + current_music->state = MusicState::STOPPED; + if (current_music->stream != nullptr) { + SDL_DestroyAudioStream(current_music->stream); + current_music->stream = nullptr; } - return; - } - 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--; - } - } -} - -// Avança tots els canals de so actius. -inline void JA_UpdateSoundChannels() { - if (!JA_soundEnabled) { - return; - } - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) { - JA_UpdateSoundChannel(i); - } -} - -inline void JA_Update() { - JA_UpdateOutgoingFade(); - JA_UpdateCurrentMusic(); - JA_UpdateSoundChannels(); -} - -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; - } - for (float& i : JA_soundVolume) { - i = 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; - } - - // 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 err = 0; - music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(), - static_cast(length), - &err, - nullptr); - if (music->vorbis == nullptr) { - std::cout << "JA_LoadMusic: stb_vorbis_open_memory failed (error " << err << ")" << '\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 = 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); - 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; - } - - free(buffer); - - return music; -} - -inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) { - if (!JA_musicEnabled || (music == nullptr) || (music->vorbis == nullptr)) { - return; - } - - 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; - } - - current_music->state = JA_MUSIC_PAUSED; - SDL_UnbindAudioStream(current_music->stream); -} - -inline void JA_ResumeMusic() { - if (!JA_musicEnabled) { - return; - } - if ((current_music == nullptr) || current_music->state != JA_MUSIC_PAUSED) { - return; - } - - current_music->state = JA_MUSIC_PLAYING; - SDL_BindAudioStream(sdlAudioDevice, current_music->stream); -} - -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; - - if ((current_music == nullptr) || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) { - return; - } - - 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) { - 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; - } - - // Destruir outgoing anterior si existe - if (outgoing_music.stream != nullptr) { - SDL_DestroyAudioStream(outgoing_music.stream); - outgoing_music.stream = nullptr; - } - - // 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); - - // 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; - } - - // 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; - } - - // 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; + // 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& channel : channels) { - if (channel.state == JA_CHANNEL_PLAYING) { - channel.state = JA_CHANNEL_PAUSED; - SDL_UnbindAudioStream(channel.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& channel : channels) { - if (channel.state == JA_CHANNEL_PAUSED) { - channel.state = JA_CHANNEL_PLAYING; - SDL_BindAudioStream(sdlAudioDevice, channel.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& channel : channels) { - if (channel.state != JA_CHANNEL_FREE) { - if (channel.stream != nullptr) { - SDL_DestroyAudioStream(channel.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); } - channel.stream = nullptr; - channel.state = JA_CHANNEL_FREE; - channel.pos = 0; - channel.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; - } - - return channels[channel].state; -} - -inline auto JA_SetSoundVolume(float volume, const int group = -1) -> float { - const float v = SDL_clamp(volume, 0.0F, 1.0F); - - if (group == -1) { - for (float& i : JA_soundVolume) { - i = v; + 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; + } } - } else if (group >= 0 && group < JA_MAX_GROUPS) { - JA_soundVolume[group] = v; - } else { - return v; } - // 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 getChannelState(int channel) -> ChannelState { + if (!sound_enabled) { return ChannelState::DISABLED; } + if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return ChannelState::INVALID; } + + return channels[channel].state; + } + + inline auto setSoundVolume(float volume, int group = -1) -> float { + const float V = SDL_clamp(volume, 0.0F, 1.0F); + + if (group == -1) { + std::ranges::fill(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.cpp b/source/core/resources/resource.cpp index 82cb534..6f77bd8 100644 --- a/source/core/resources/resource.cpp +++ b/source/core/resources/resource.cpp @@ -64,12 +64,12 @@ Resource::~Resource() { textures_.clear(); for (auto &[name, s] : sounds_) { - JA_DeleteSound(s); + Ja::deleteSound(s); } sounds_.clear(); for (auto &[name, m] : musics_) { - JA_DeleteMusic(m); + Ja::deleteMusic(m); } musics_.clear(); } @@ -103,14 +103,14 @@ void Resource::preloadResources() { break; } case Asset::Type::SOUND: { - JA_Sound_t *s = JA_LoadSound(bytes.data(), (uint32_t)bytes.size()); + Ja::Sound *s = Ja::loadSound(bytes.data(), (uint32_t)bytes.size()); if (s != nullptr) { sounds_[BASE_NAME] = s; } break; } case Asset::Type::MUSIC: { - JA_Music_t *m = JA_LoadMusic(bytes.data(), (Uint32)bytes.size()); + Ja::Music *m = Ja::loadMusic(bytes.data(), (Uint32)bytes.size()); if (m != nullptr) { musics_[BASE_NAME] = m; } @@ -185,7 +185,7 @@ void Resource::preloadFonts() { // Pass 2b: construye los Menu (dependen de Text+sonidos cargados antes) // // NOTA: Menu::loadFromBytes aún llama internamente a asset->get() y Text/ -// JA_LoadSound por path. Funciona en modo fallback; en pack estricto requiere +// Ja::loadSound por path. Funciona en modo fallback; en pack estricto requiere // que Menu se adapte a cargar desde ResourceHelper. Migración pendiente. void Resource::preloadMenus() { const auto &items = Asset::get()->getAll(); @@ -218,7 +218,7 @@ auto Resource::getTexture(const std::string &name) -> Texture * { return it->second; } -auto Resource::getSound(const std::string &name) -> JA_Sound_t * { +auto Resource::getSound(const std::string &name) -> Ja::Sound * { auto it = sounds_.find(name); if (it == sounds_.end()) { std::cerr << "Resource::getSound: missing " << name << '\n'; @@ -227,7 +227,7 @@ auto Resource::getSound(const std::string &name) -> JA_Sound_t * { return it->second; } -auto Resource::getMusic(const std::string &name) -> JA_Music_t * { +auto Resource::getMusic(const std::string &name) -> Ja::Music * { auto it = musics_.find(name); if (it == musics_.end()) { std::cerr << "Resource::getMusic: missing " << name << '\n'; diff --git a/source/core/resources/resource.h b/source/core/resources/resource.h index 0f8e5be..e41d428 100644 --- a/source/core/resources/resource.h +++ b/source/core/resources/resource.h @@ -10,8 +10,10 @@ class Menu; class Text; class Texture; -struct JA_Music_t; -struct JA_Sound_t; +namespace Ja { + struct Music; + struct Sound; +} // namespace Ja // Precarga y posee todos los recursos del juego durante toda la vida de la app. // Singleton inicializado desde Director; las escenas consultan handles via get*(). @@ -22,8 +24,8 @@ class Resource { static auto get() -> Resource *; auto getTexture(const std::string &name) -> Texture *; - auto getSound(const std::string &name) -> JA_Sound_t *; - auto getMusic(const std::string &name) -> JA_Music_t *; + auto getSound(const std::string &name) -> Ja::Sound *; + auto getMusic(const std::string &name) -> Ja::Music *; auto getAnimationLines(const std::string &name) -> std::vector &; auto getText(const std::string &name) -> Text *; // name sin extensión: "smb2", "nokia2", ... auto getMenu(const std::string &name) -> Menu *; // name sin extensión: "title", "options", ... @@ -44,8 +46,8 @@ class Resource { SDL_Renderer *renderer_; std::unordered_map textures_; - std::unordered_map sounds_; - std::unordered_map musics_; + std::unordered_map sounds_; + std::unordered_map musics_; std::unordered_map> animation_lines_; std::unordered_map texts_; std::unordered_map menus_; diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index f25df00..e6a4d4d 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -168,7 +168,7 @@ Director::~Director() { Options::saveToFile(); // Libera las secciones primero: sus destructores tocan audio/render SDL - // (p.ej. Intro::~Intro llama a JA_DeleteMusic) y deben ejecutarse antes + // (p.ej. Intro::~Intro llama a Ja::deleteMusic) y deben ejecutarse antes // de SDL_Quit(). logo_.reset(); intro_.reset(); diff --git a/source/game/game.cpp b/source/game/game.cpp index 1425613..be8dfc6 100644 --- a/source/game/game.cpp +++ b/source/game/game.cpp @@ -27,7 +27,9 @@ #include "game/entities/player.h" // for Player #include "game/options.hpp" // for Options #include "game/ui/menu.h" // for Menu -struct JA_Sound_t; +namespace Ja { + struct Sound; +} // namespace Ja namespace { // Constantes geométricas y temporales compartidas por los helpers de initEnemyFormations @@ -1521,7 +1523,7 @@ void Game::updateDeath() { // Hace sonar aleatoriamente uno de los 4 sonidos de burbujas if (!demo_.enabled) { const Uint8 INDEX = rand() % 4; - JA_Sound_t *sound[4] = {bubble1_sound_, bubble2_sound_, bubble3_sound_, bubble4_sound_}; + Ja::Sound *sound[4] = {bubble1_sound_, bubble2_sound_, bubble3_sound_, bubble4_sound_}; Audio::get()->playSound(sound[INDEX]); } } diff --git a/source/game/game.h b/source/game/game.h index 7ec38e3..8b55db2 100644 --- a/source/game/game.h +++ b/source/game/game.h @@ -18,8 +18,10 @@ class SmartSprite; class Sprite; class Text; class Texture; -struct JA_Music_t; -struct JA_Sound_t; +namespace Ja { + struct Music; + struct Sound; +} // namespace Ja // Clase Game class Game { @@ -315,23 +317,23 @@ class Game { Sprite *game_over_sprite_; // Sprite para dibujar los graficos del game over Sprite *game_over_end_sprite_; // Sprite para dibujar los graficos del game over de acabar el juego - JA_Sound_t *balloon_sound_; // Sonido para la explosión del globo - JA_Sound_t *bullet_sound_; // Sonido para los disparos - JA_Sound_t *player_collision_sound_; // Sonido para la colisión del jugador con un enemigo - JA_Sound_t *hi_score_sound_; // Sonido para cuando se alcanza la máxima puntuación - JA_Sound_t *item_drop_sound_; // Sonido para cuando se genera un item - JA_Sound_t *item_pick_up_sound_; // Sonido para cuando se recoge un item - JA_Sound_t *coffee_out_sound_; // Sonido para cuando el jugador pierde el café al recibir un impacto - JA_Sound_t *stage_change_sound_; // Sonido para cuando se cambia de fase - JA_Sound_t *bubble1_sound_; // Sonido para cuando el jugador muere - JA_Sound_t *bubble2_sound_; // Sonido para cuando el jugador muere - JA_Sound_t *bubble3_sound_; // Sonido para cuando el jugador muere - JA_Sound_t *bubble4_sound_; // Sonido para cuando el jugador muere - JA_Sound_t *clock_sound_; // Sonido para cuando se detiene el tiempo con el item reloj - JA_Sound_t *power_ball_sound_; // Sonido para cuando se explota una Power Ball - JA_Sound_t *coffee_machine_sound_; // Sonido para cuando la máquina de café toca el suelo + Ja::Sound *balloon_sound_; // Sonido para la explosión del globo + Ja::Sound *bullet_sound_; // Sonido para los disparos + Ja::Sound *player_collision_sound_; // Sonido para la colisión del jugador con un enemigo + Ja::Sound *hi_score_sound_; // Sonido para cuando se alcanza la máxima puntuación + Ja::Sound *item_drop_sound_; // Sonido para cuando se genera un item + Ja::Sound *item_pick_up_sound_; // Sonido para cuando se recoge un item + Ja::Sound *coffee_out_sound_; // Sonido para cuando el jugador pierde el café al recibir un impacto + Ja::Sound *stage_change_sound_; // Sonido para cuando se cambia de fase + Ja::Sound *bubble1_sound_; // Sonido para cuando el jugador muere + Ja::Sound *bubble2_sound_; // Sonido para cuando el jugador muere + Ja::Sound *bubble3_sound_; // Sonido para cuando el jugador muere + Ja::Sound *bubble4_sound_; // Sonido para cuando el jugador muere + Ja::Sound *clock_sound_; // Sonido para cuando se detiene el tiempo con el item reloj + Ja::Sound *power_ball_sound_; // Sonido para cuando se explota una Power Ball + Ja::Sound *coffee_machine_sound_; // Sonido para cuando la máquina de café toca el suelo - JA_Music_t *game_music_; // Musica de fondo + Ja::Music *game_music_; // Musica de fondo // Variables int num_players_; // Numero de jugadores diff --git a/source/game/scenes/intro.h b/source/game/scenes/intro.h index 01bee16..99ac45f 100644 --- a/source/game/scenes/intro.h +++ b/source/game/scenes/intro.h @@ -7,8 +7,10 @@ class SmartSprite; class Text; class Texture; class Writer; -struct JA_Music_t; struct Section; +namespace Ja { + struct Music; +} // namespace Ja // Clase Intro class Intro { @@ -36,7 +38,7 @@ class Intro { // Variables Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa Uint8 ticks_speed_; // Velocidad a la que se repiten los bucles del programa - JA_Music_t *music_; // Musica para la intro + Ja::Music *music_; // Musica para la intro int scene_; // Indica que escena está activa void update(); // Actualiza las variables del objeto diff --git a/source/game/scenes/title.h b/source/game/scenes/title.h index 5bf3446..09b6c15 100644 --- a/source/game/scenes/title.h +++ b/source/game/scenes/title.h @@ -15,8 +15,10 @@ class SmartSprite; class Sprite; class Text; class Texture; -struct JA_Music_t; -struct JA_Sound_t; +namespace Ja { + struct Music; + struct Sound; +} // namespace Ja class Title { public: @@ -70,8 +72,8 @@ class Title { Fade *fade_; // Objeto para realizar fundidos en pantalla // Variables - JA_Music_t *title_music_; // Musica para el titulo - JA_Sound_t *crash_sound_; // Sonido con el impacto del título + Ja::Music *title_music_; // Musica para el titulo + Ja::Sound *crash_sound_; // Sonido con el impacto del título int background_counter_; // Temporizador para el fondo de tiles de la pantalla de titulo int counter_; // Temporizador para la pantalla de titulo Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa diff --git a/source/game/ui/menu.cpp b/source/game/ui/menu.cpp index 06e1527..5a7c26b 100644 --- a/source/game/ui/menu.cpp +++ b/source/game/ui/menu.cpp @@ -6,7 +6,7 @@ #include // for basic_stringstream #include "core/audio/audio.hpp" // for Audio::get (playSound) -#include "core/audio/jail_audio.hpp" // for JA_LoadSound, JA_DeleteSound (propietat local) +#include "core/audio/jail_audio.hpp" // for Ja::loadSound, Ja::deleteSound (propietat local) #include "core/input/input.h" // for Input, REPEAT_FALSE, InputAction #include "core/rendering/text.h" // for Text #include "core/resources/asset.h" // for Asset @@ -40,7 +40,7 @@ Menu::Menu(SDL_Renderer *renderer, const std::string &file) center_x_ = 0; center_y_ = 0; widest_item_ = 0; - default_action_when_cancel = 0; + default_action_when_cancel_ = 0; // Selector selector_.origin_y = 0; @@ -73,15 +73,15 @@ Menu::~Menu() { renderer_ = nullptr; if (sound_move_ != nullptr) { - JA_DeleteSound(sound_move_); + Ja::deleteSound(sound_move_); } if (sound_accept_ != nullptr) { - JA_DeleteSound(sound_accept_); + Ja::deleteSound(sound_accept_); } if (sound_cancel_ != nullptr) { - JA_DeleteSound(sound_cancel_); + Ja::deleteSound(sound_cancel_); } delete text_; @@ -229,21 +229,21 @@ auto Menu::setVars(const std::string &var, const std::string &value) -> bool { else if (var == "sound_cancel") { auto bytes = ResourceHelper::loadFile(Asset::get()->get(value)); if (!bytes.empty()) { - sound_cancel_ = JA_LoadSound(bytes.data(), (uint32_t)bytes.size()); + sound_cancel_ = Ja::loadSound(bytes.data(), (uint32_t)bytes.size()); } } else if (var == "sound_accept") { auto bytes = ResourceHelper::loadFile(Asset::get()->get(value)); if (!bytes.empty()) { - sound_accept_ = JA_LoadSound(bytes.data(), (uint32_t)bytes.size()); + sound_accept_ = Ja::loadSound(bytes.data(), (uint32_t)bytes.size()); } } else if (var == "sound_move") { auto bytes = ResourceHelper::loadFile(Asset::get()->get(value)); if (!bytes.empty()) { - sound_move_ = JA_LoadSound(bytes.data(), (uint32_t)bytes.size()); + sound_move_ = Ja::loadSound(bytes.data(), (uint32_t)bytes.size()); } } @@ -324,7 +324,7 @@ auto Menu::setVars(const std::string &var, const std::string &value) -> bool { } else if (var == "defaultActionWhenCancel") { - default_action_when_cancel = std::stoi(value); + default_action_when_cancel_ = std::stoi(value); } else if (var.empty()) { @@ -341,15 +341,15 @@ auto Menu::setVars(const std::string &var, const std::string &value) -> bool { void Menu::loadAudioFile(const std::string &file, int sound) { switch (sound) { case SOUND_ACCEPT: - sound_accept_ = JA_LoadSound(file.c_str()); + sound_accept_ = Ja::loadSound(file.c_str()); break; case SOUND_CANCEL: - sound_cancel_ = JA_LoadSound(file.c_str()); + sound_cancel_ = Ja::loadSound(file.c_str()); break; case SOUND_MOVE: - sound_move_ = JA_LoadSound(file.c_str()); + sound_move_ = Ja::loadSound(file.c_str()); break; default: @@ -770,7 +770,7 @@ void Menu::setItemCaption(int index, const std::string &text) { // Establece el indice del itemm que se usará por defecto al cancelar el menu void Menu::setDefaultActionWhenCancel(int item) { - default_action_when_cancel = item; + default_action_when_cancel_ = item; } // Gestiona la entrada de teclado y mando durante el menu @@ -797,7 +797,7 @@ void Menu::checkInput() { } if (Input::get()->checkInput(CANCEL, REPEAT_FALSE)) { - item_selected_ = default_action_when_cancel; + item_selected_ = default_action_when_cancel_; if (sound_cancel_ != nullptr) { Audio::get()->playSound(sound_cancel_); } diff --git a/source/game/ui/menu.h b/source/game/ui/menu.h index 6dfc923..820c6a6 100644 --- a/source/game/ui/menu.h +++ b/source/game/ui/menu.h @@ -8,7 +8,9 @@ #include "utils/utils.h" // for Color class Text; -struct JA_Sound_t; +namespace Ja { + struct Sound; +} // namespace Ja // Tipos de fondos para el menú constexpr int MENU_BACKGROUND_TRANSPARENT = 0; @@ -140,9 +142,9 @@ class Menu { bool is_centered_on_y_; // Variable para saber si el menu debe estar centrado respecto a un punto en el eje Y bool are_elements_centered_on_x_; // Variable para saber si los elementos van centrados en el eje X int widest_item_; // Anchura del elemento más ancho - JA_Sound_t *sound_accept_; // Sonido al aceptar o elegir una opción del menu - JA_Sound_t *sound_cancel_; // Sonido al cancelar el menu - JA_Sound_t *sound_move_; // Sonido al mover el selector + Ja::Sound *sound_accept_; // Sonido al aceptar o elegir una opción del menu + Ja::Sound *sound_cancel_; // Sonido al cancelar el menu + Ja::Sound *sound_move_; // Sonido al mover el selector Color color_greyed_; // Color para los elementos agrisados Rectangle rect_bg_; // Rectangulo de fondo del menu std::vector items_; // Estructura para cada elemento del menu