From c0accd25e28ccddb5d68196b73e6092a598ac43d Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 14 Apr 2026 08:32:49 +0200 Subject: [PATCH] streaming de audio --- CMakeLists.txt | 1 - source/audio.cpp | 16 +- source/external/jail_audio.cpp | 477 ---------------------------- source/external/jail_audio.h | 43 --- source/external/jail_audio.hpp | 555 +++++++++++++++++++++++++++++++++ source/resource.cpp | 2 +- 6 files changed, 571 insertions(+), 523 deletions(-) delete mode 100644 source/external/jail_audio.cpp delete mode 100644 source/external/jail_audio.h create mode 100644 source/external/jail_audio.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ecdeec7..9230352 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,6 @@ set(APP_SOURCES # Fuentes de librerías de terceros set(EXTERNAL_SOURCES - source/external/jail_audio.cpp source/external/json.hpp source/external/gif.cpp ) diff --git a/source/audio.cpp b/source/audio.cpp index e9c9604..681ddbf 100644 --- a/source/audio.cpp +++ b/source/audio.cpp @@ -4,7 +4,21 @@ #include // Para clamp -#include "external/jail_audio.h" // Para JA_FadeOutMusic, JA_Init, JA_PauseM... +// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp). +// clang-format off +#undef STB_VORBIS_HEADER_ONLY +#include "external/stb_vorbis.h" +// stb_vorbis.h filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem +// perquè xocarien amb noms de paràmetres de plantilla en json.hpp i altres. +#undef L +#undef C +#undef R +#undef PLAYBACK_MONO +#undef PLAYBACK_LEFT +#undef PLAYBACK_RIGHT +// clang-format on + +#include "external/jail_audio.hpp" // Para JA_FadeOutMusic, JA_Init, JA_PauseM... #include "options.hpp" // Para AudioOptions, audio, MusicOptions #include "resource.hpp" // Para Resource #include "ui/logger.hpp" // Para logger diff --git a/source/external/jail_audio.cpp b/source/external/jail_audio.cpp deleted file mode 100644 index e8c2c3d..0000000 --- a/source/external/jail_audio.cpp +++ /dev/null @@ -1,477 +0,0 @@ -#ifndef JA_USESDLMIXER -#include "jail_audio.h" - -#include // Para SDL_AudioFormat, SDL_BindAudioStream, SDL_SetAudioStreamGain, SDL_PutAudioStreamData, SDL_DestroyAudioStream, SDL_GetAudioStreamAvailable, Uint8, SDL_CreateAudioStream, SDL_UnbindAudioStream, Uint32, SDL_CloseAudioDevice, SDL_GetTicks, SDL_Log, SDL_free, SDL_AudioSpec, SDL_AudioStream, SDL_IOFromMem, SDL_LoadWAV, SDL_LoadWAV_IO, SDL_OpenAudioDevice, SDL_clamp, SDL_malloc, SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, SDL_AudioDeviceID, SDL_memcpy -#include // Para uint32_t, uint8_t -#include // Para NULL, fseek, printf, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET -#include // Para free, malloc -#include // Para strcpy, strlen - -#include "stb_vorbis.h" // Para stb_vorbis_decode_memory - -#define JA_MAX_SIMULTANEOUS_CHANNELS 20 -#define JA_MAX_GROUPS 2 - -struct JA_Sound_t -{ - SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 }; - Uint32 length { 0 }; - Uint8 *buffer { NULL }; -}; - -struct JA_Channel_t -{ - JA_Sound_t *sound { nullptr }; - int pos { 0 }; - int times { 0 }; - int group { 0 }; - SDL_AudioStream *stream { nullptr }; - JA_Channel_state state { JA_CHANNEL_FREE }; -}; - -struct JA_Music_t -{ - SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 }; - Uint32 length { 0 }; - Uint8 *buffer { nullptr }; - char *filename { nullptr }; - - int pos { 0 }; - int times { 0 }; - SDL_AudioStream *stream { nullptr }; - JA_Music_state state { JA_MUSIC_INVALID }; -}; - -JA_Music_t *current_music { nullptr }; -JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS]; - -SDL_AudioSpec JA_audioSpec { SDL_AUDIO_S16, 2, 48000 }; -float JA_musicVolume { 1.0f }; -float JA_soundVolume[JA_MAX_GROUPS]; -bool JA_musicEnabled { true }; -bool JA_soundEnabled { true }; -SDL_AudioDeviceID sdlAudioDevice { 0 }; -//SDL_TimerID JA_timerID { 0 }; - -bool fading = false; -int fade_start_time; -int fade_duration; -int fade_initial_volume; - - -void JA_Update() -{ - if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) - { - if (fading) { - int time = SDL_GetTicks(); - if (time > (fade_start_time+fade_duration)) { - fading = false; - JA_StopMusic(); - return; - } else { - const int time_passed = time - fade_start_time; - const float percent = (float)time_passed / (float)fade_duration; - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume*(1.0 - percent)); - } - } - - if (current_music->times != 0) - { - if ((Uint32)SDL_GetAudioStreamAvailable(current_music->stream) < (current_music->length/2)) { - SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length); - } - if (current_music->times>0) current_music->times--; - } - else - { - if (SDL_GetAudioStreamAvailable(current_music->stream) == 0) JA_StopMusic(); - } - } - - 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, channels[i].sound->length); - if (channels[i].times>0) channels[i].times--; - } - } - else - { - if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) JA_StopChannel(i); - } - } - - } - - return; -} - -void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) -{ - #ifdef DEBUG - SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG); - #endif - - JA_audioSpec = {format, num_channels, freq }; - if (!sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); - sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec); - if (sdlAudioDevice==0) SDL_Log("Failed to initialize SDL audio!"); - for (int i=0; ilength = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2; - - music->spec.channels = chan; - music->spec.freq = samplerate; - music->spec.format = SDL_AUDIO_S16; - music->buffer = (Uint8*)SDL_malloc(music->length); - SDL_memcpy(music->buffer, output, music->length); - free(output); - music->pos = 0; - music->state = JA_MUSIC_STOPPED; - - return music; -} - -JA_Music_t *JA_LoadMusic(const char* filename) -{ - // [RZC 28/08/22] Carreguem primer el arxiu en memòria i després el descomprimim. Es algo més rapid. - FILE *f = fopen(filename, "rb"); - fseek(f, 0, SEEK_END); - long fsize = ftell(f); - fseek(f, 0, SEEK_SET); - Uint8 *buffer = (Uint8*)malloc(fsize + 1); - if (fread(buffer, fsize, 1, f)!=1) return NULL; - fclose(f); - - JA_Music_t *music = JA_LoadMusic(buffer, fsize); - music->filename = (char*)malloc(strlen(filename)+1); - strcpy(music->filename, filename); - - free(buffer); - - return music; -} - -void JA_PlayMusic(JA_Music_t *music, const int loop) -{ - if (!JA_musicEnabled) return; - - JA_StopMusic(); - - current_music = music; - current_music->pos = 0; - current_music->state = JA_MUSIC_PLAYING; - current_music->times = loop; - - current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec); - if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length)) printf("[ERROR] SDL_PutAudioStreamData failed!\n"); - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); - if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n"); - //SDL_ResumeAudioStreamDevice(current_music->stream); -} - -char *JA_GetMusicFilename(JA_Music_t *music) -{ - if (!music) music = current_music; - return music->filename; -} - -void JA_PauseMusic() -{ - if (!JA_musicEnabled) return; - if (!current_music || current_music->state == JA_MUSIC_INVALID) return; - - current_music->state = JA_MUSIC_PAUSED; - //SDL_PauseAudioStreamDevice(current_music->stream); - SDL_UnbindAudioStream(current_music->stream); -} - -void JA_ResumeMusic() -{ - if (!JA_musicEnabled) return; - if (!current_music || current_music->state == JA_MUSIC_INVALID) return; - - current_music->state = JA_MUSIC_PLAYING; - //SDL_ResumeAudioStreamDevice(current_music->stream); - SDL_BindAudioStream(sdlAudioDevice, current_music->stream); -} - -void JA_StopMusic() -{ - if (!JA_musicEnabled) return; - if (!current_music || current_music->state == JA_MUSIC_INVALID) return; - - current_music->pos = 0; - current_music->state = JA_MUSIC_STOPPED; - //SDL_PauseAudioStreamDevice(current_music->stream); - SDL_DestroyAudioStream(current_music->stream); - current_music->stream = nullptr; - free(current_music->filename); - current_music->filename = nullptr; -} - -void JA_FadeOutMusic(const int milliseconds) -{ - if (!JA_musicEnabled) return; - if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return; - - fading = true; - fade_start_time = SDL_GetTicks(); - fade_duration = milliseconds; - fade_initial_volume = JA_musicVolume; -} - -JA_Music_state JA_GetMusicState() -{ - if (!JA_musicEnabled) return JA_MUSIC_DISABLED; - if (!current_music) return JA_MUSIC_INVALID; - - return current_music->state; -} - -void JA_DeleteMusic(JA_Music_t *music) -{ - if (current_music == music) current_music = nullptr; - SDL_free(music->buffer); - if (music->stream) SDL_DestroyAudioStream(music->stream); - delete music; -} - -float JA_SetMusicVolume(float volume) -{ - JA_musicVolume = SDL_clamp( volume, 0.0f, 1.0f ); - if (current_music) SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); - return JA_musicVolume; -} - -void JA_SetMusicPosition(float value) -{ - if (!current_music) return; - current_music->pos = value * current_music->spec.freq; -} - -float JA_GetMusicPosition() -{ - if (!current_music) return 0; - return float(current_music->pos)/float(current_music->spec.freq); -} - -void JA_EnableMusic(const bool value) -{ - if ( !value && current_music && (current_music->state==JA_MUSIC_PLAYING) ) JA_StopMusic(); - - JA_musicEnabled = value; -} - - - - - -JA_Sound_t *JA_NewSound(Uint8* buffer, Uint32 length) -{ - JA_Sound_t *sound = new JA_Sound_t(); - sound->buffer = buffer; - sound->length = length; - return sound; -} - -JA_Sound_t *JA_LoadSound(uint8_t* buffer, uint32_t size) -{ - JA_Sound_t *sound = new JA_Sound_t(); - SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size),1, &sound->spec, &sound->buffer, &sound->length); - - return sound; -} - -JA_Sound_t *JA_LoadSound(const char* filename) -{ - JA_Sound_t *sound = new JA_Sound_t(); - SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length); - - return sound; -} - -int JA_PlaySound(JA_Sound_t *sound, const int loop, const int group) -{ - if (!JA_soundEnabled) return -1; - - int channel = 0; - while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; } - if (channel == JA_MAX_SIMULTANEOUS_CHANNELS) channel = 0; - JA_StopChannel(channel); - - channels[channel].sound = sound; - channels[channel].times = loop; - channels[channel].pos = 0; - channels[channel].state = JA_CHANNEL_PLAYING; - channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec); - SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length); - SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]); - SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); - - return channel; -} - -int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop, const int group) -{ - if (!JA_soundEnabled) 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].state = JA_CHANNEL_PLAYING; - channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec); - SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length); - SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]); - SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); - - return channel; -} - -void JA_DeleteSound(JA_Sound_t *sound) -{ - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { - if (channels[i].sound == sound) JA_StopChannel(i); - } - SDL_free(sound->buffer); - delete sound; -} - -void JA_PauseChannel(const int channel) -{ - if (!JA_soundEnabled) return; - - if (channel == -1) - { - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) - if (channels[i].state == JA_CHANNEL_PLAYING) - { - channels[i].state = JA_CHANNEL_PAUSED; - //SDL_PauseAudioStreamDevice(channels[i].stream); - SDL_UnbindAudioStream(channels[i].stream); - } - } - else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) - { - if (channels[channel].state == JA_CHANNEL_PLAYING) - { - channels[channel].state = JA_CHANNEL_PAUSED; - //SDL_PauseAudioStreamDevice(channels[channel].stream); - SDL_UnbindAudioStream(channels[channel].stream); - } - } -} - -void JA_ResumeChannel(const int channel) -{ - if (!JA_soundEnabled) return; - - if (channel == -1) - { - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) - if (channels[i].state == JA_CHANNEL_PAUSED) - { - channels[i].state = JA_CHANNEL_PLAYING; - //SDL_ResumeAudioStreamDevice(channels[i].stream); - SDL_BindAudioStream(sdlAudioDevice, channels[i].stream); - } - } - else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) - { - if (channels[channel].state == JA_CHANNEL_PAUSED) - { - channels[channel].state = JA_CHANNEL_PLAYING; - //SDL_ResumeAudioStreamDevice(channels[channel].stream); - SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); - } - } -} - -void JA_StopChannel(const int channel) -{ - if (!JA_soundEnabled) return; - - if (channel == -1) - { - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { - if (channels[i].state != JA_CHANNEL_FREE) SDL_DestroyAudioStream(channels[i].stream); - channels[i].stream = nullptr; - channels[i].state = JA_CHANNEL_FREE; - channels[i].pos = 0; - channels[i].sound = NULL; - } - } - else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) - { - if (channels[channel].state != JA_CHANNEL_FREE) SDL_DestroyAudioStream(channels[channel].stream); - channels[channel].stream = nullptr; - channels[channel].state = JA_CHANNEL_FREE; - channels[channel].pos = 0; - channels[channel].sound = NULL; - } -} - -JA_Channel_state JA_GetChannelState(const int channel) -{ - if (!JA_soundEnabled) return JA_SOUND_DISABLED; - - if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return JA_CHANNEL_INVALID; - - return channels[channel].state; -} - -float JA_SetSoundVolume(float volume, const int group) -{ - const float v = SDL_clamp( volume, 0.0f, 1.0f ); - for (int i = 0; i < JA_MAX_GROUPS; ++i) { - if (group==-1 || group==i) JA_soundVolume[i]=v; - } - - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) - if ( ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) && - ((group==-1) || (channels[i].group==group)) ) - SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume[i]); - - return v; -} - -void JA_EnableSound(const bool value) -{ - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) - { - if (channels[i].state == JA_CHANNEL_PLAYING) JA_StopChannel(i); - } - JA_soundEnabled = value; -} - -float JA_SetVolume(float volume) -{ - JA_SetSoundVolume(JA_SetMusicVolume(volume) / 2.0f); - - return JA_musicVolume; -} - -#endif \ No newline at end of file diff --git a/source/external/jail_audio.h b/source/external/jail_audio.h deleted file mode 100644 index 716b7f9..0000000 --- a/source/external/jail_audio.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once -#include - -enum JA_Channel_state { JA_CHANNEL_INVALID, JA_CHANNEL_FREE, JA_CHANNEL_PLAYING, JA_CHANNEL_PAUSED, JA_SOUND_DISABLED }; -enum JA_Music_state { JA_MUSIC_INVALID, JA_MUSIC_PLAYING, JA_MUSIC_PAUSED, JA_MUSIC_STOPPED, JA_MUSIC_DISABLED }; - -struct JA_Sound_t; -struct JA_Music_t; - -void JA_Update(); - -void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels); -void JA_Quit(); - -JA_Music_t *JA_LoadMusic(const char* filename); -JA_Music_t *JA_LoadMusic(Uint8* buffer, Uint32 length); -void JA_PlayMusic(JA_Music_t *music, const int loop = -1); -char *JA_GetMusicFilename(JA_Music_t *music = nullptr); -void JA_PauseMusic(); -void JA_ResumeMusic(); -void JA_StopMusic(); -void JA_FadeOutMusic(const int milliseconds); -JA_Music_state JA_GetMusicState(); -void JA_DeleteMusic(JA_Music_t *music); -float JA_SetMusicVolume(float volume); -void JA_SetMusicPosition(float value); -float JA_GetMusicPosition(); -void JA_EnableMusic(const bool value); - -JA_Sound_t *JA_NewSound(Uint8* buffer, Uint32 length); -JA_Sound_t *JA_LoadSound(Uint8* buffer, Uint32 length); -JA_Sound_t *JA_LoadSound(const char* filename); -int JA_PlaySound(JA_Sound_t *sound, const int loop = 0, const int group=0); -int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop = 0, const int group=0); -void JA_PauseChannel(const int channel); -void JA_ResumeChannel(const int channel); -void JA_StopChannel(const int channel); -JA_Channel_state JA_GetChannelState(const int channel); -void JA_DeleteSound(JA_Sound_t *sound); -float JA_SetSoundVolume(float volume, const int group=0); -void JA_EnableSound(const bool value); - -float JA_SetVolume(float volume); diff --git a/source/external/jail_audio.hpp b/source/external/jail_audio.hpp new file mode 100644 index 0000000..3a5fcaf --- /dev/null +++ b/source/external/jail_audio.hpp @@ -0,0 +1,555 @@ +#pragma once + +// --- Includes --- +#include +#include // Para uint32_t, uint8_t +#include // Para NULL, fseek, printf, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET +#include // Para free, malloc +#include // Para strcpy, strlen + +#define STB_VORBIS_HEADER_ONLY +#include "external/stb_vorbis.h" // Para stb_vorbis_decode_memory + +// --- Public Enums --- +enum JA_Channel_state { JA_CHANNEL_INVALID, + JA_CHANNEL_FREE, + JA_CHANNEL_PLAYING, + JA_CHANNEL_PAUSED, + JA_SOUND_DISABLED }; +enum JA_Music_state { JA_MUSIC_INVALID, + JA_MUSIC_PLAYING, + JA_MUSIC_PAUSED, + JA_MUSIC_STOPPED, + JA_MUSIC_DISABLED }; + +// --- Struct Definitions --- +#define JA_MAX_SIMULTANEOUS_CHANNELS 20 +#define JA_MAX_GROUPS 2 + +struct JA_Sound_t { + SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; + Uint32 length{0}; + Uint8* buffer{NULL}; +}; + +struct JA_Channel_t { + JA_Sound_t* sound{nullptr}; + int pos{0}; + int times{0}; + int group{0}; + SDL_AudioStream* stream{nullptr}; + JA_Channel_state state{JA_CHANNEL_FREE}; +}; + +struct JA_Music_t { + SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; + + // OGG comprimit en memòria. Propietat nostra; es copia des del fitxer una + // sola vegada en JA_LoadMusic i es descomprimix en chunks per streaming. + Uint8* ogg_data{nullptr}; + Uint32 ogg_length{0}; + stb_vorbis* vorbis{nullptr}; // Handle del decoder, viu tot el cicle del JA_Music_t + + char* filename{nullptr}; + + int times{0}; // Loops restants (-1 = infinit, 0 = un sol play) + SDL_AudioStream* stream{nullptr}; + JA_Music_state state{JA_MUSIC_INVALID}; +}; + +// --- Internal Global State --- +// Marcado 'inline' (C++17) para asegurar una única instancia. + +inline JA_Music_t* current_music{nullptr}; +inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS]; + +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}; + +inline bool fading{false}; +inline int fade_start_time{0}; +inline int fade_duration{0}; +inline float fade_initial_volume{0.0f}; // Corregido de 'int' a 'float' + +// --- Forward Declarations --- +inline void JA_StopMusic(); +inline void JA_StopChannel(const int channel); +inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0); + +// --- 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; + +// Decodifica un chunk del vorbis i el volca a l'stream. Retorna samples +// decodificats per canal (0 = EOF de l'stream vorbis). +inline int JA_FeedMusicChunk(JA_Music_t* music) { + if (!music || !music->vorbis || !music->stream) return 0; + + short chunk[JA_MUSIC_CHUNK_SHORTS]; + const int channels = music->spec.channels; + const int samples_per_channel = stb_vorbis_get_samples_short_interleaved( + music->vorbis, + channels, + chunk, + JA_MUSIC_CHUNK_SHORTS); + if (samples_per_channel <= 0) return 0; + + const int bytes = samples_per_channel * channels * JA_MUSIC_BYTES_PER_SAMPLE; + SDL_PutAudioStreamData(music->stream, chunk, bytes); + return samples_per_channel; +} + +// 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 || !music->vorbis || !music->stream) return; + + 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--; + } else { + break; + } + } +} + +// --- Core Functions --- + +inline void JA_Update() { + if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) { + if (fading) { + int time = SDL_GetTicks(); + if (time > (fade_start_time + fade_duration)) { + fading = false; + JA_StopMusic(); + return; + } else { + const int time_passed = time - fade_start_time; + const float percent = (float)time_passed / (float)fade_duration; + SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * (1.0 - percent)); + } + } + + // 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); + if (current_music->times == 0 && SDL_GetAudioStreamAvailable(current_music->stream) == 0) { + JA_StopMusic(); + } + } + + 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, 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 JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) { +#ifdef _DEBUG + SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG); +#endif + + JA_audioSpec = {format, num_channels, freq}; + if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice + sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec); + if (sdlAudioDevice == 0) SDL_Log("Failed to initialize SDL audio!"); + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE; + for (int i = 0; i < JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5f; +} + +inline void JA_Quit() { + if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice + sdlAudioDevice = 0; +} + +// --- Music Functions --- + +inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) { + if (!buffer || length == 0) return nullptr; + + // Còpia del OGG comprimit: stb_vorbis llig de forma persistent aquesta + // memòria mentre el handle estiga viu, així que hem de posseir-la nosaltres. + Uint8* ogg_copy = static_cast(SDL_malloc(length)); + if (!ogg_copy) return nullptr; + SDL_memcpy(ogg_copy, buffer, length); + + int error = 0; + stb_vorbis* vorbis = stb_vorbis_open_memory(ogg_copy, static_cast(length), &error, nullptr); + if (!vorbis) { + SDL_free(ogg_copy); + SDL_Log("JA_LoadMusic: stb_vorbis_open_memory failed (error %d)", error); + return nullptr; + } + + auto* music = new JA_Music_t(); + music->ogg_data = ogg_copy; + music->ogg_length = length; + music->vorbis = vorbis; + + const stb_vorbis_info info = stb_vorbis_get_info(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; +} + +inline JA_Music_t* JA_LoadMusic(const char* filename) { + // [RZC 28/08/22] Carreguem primer el arxiu en memòria i després el descomprimim. Es algo més rapid. + FILE* f = fopen(filename, "rb"); + if (!f) return NULL; // Añadida comprobación de apertura + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); + auto* buffer = static_cast(malloc(fsize + 1)); + if (!buffer) { // Añadida comprobación de malloc + fclose(f); + return NULL; + } + if (fread(buffer, fsize, 1, f) != 1) { + fclose(f); + free(buffer); + return NULL; + } + fclose(f); + + JA_Music_t* music = JA_LoadMusic(buffer, fsize); + if (music) { // Comprobar que JA_LoadMusic tuvo éxito + music->filename = static_cast(malloc(strlen(filename) + 1)); + if (music->filename) { + strcpy(music->filename, filename); + } + } + + free(buffer); + + return music; +} + +inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) { + if (!JA_musicEnabled || !music || !music->vorbis) 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) { + SDL_Log("Failed to create audio stream!"); + 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)) printf("[ERROR] SDL_BindAudioStream failed!\n"); +} + +inline char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) { + if (!music) music = current_music; + if (!music) return nullptr; // Añadida comprobación + return music->filename; +} + +inline void JA_PauseMusic() { + if (!JA_musicEnabled) return; + if (!current_music || current_music->state != JA_MUSIC_PLAYING) return; // Comprobación mejorada + + current_music->state = JA_MUSIC_PAUSED; + SDL_UnbindAudioStream(current_music->stream); +} + +inline void JA_ResumeMusic() { + if (!JA_musicEnabled) return; + if (!current_music || current_music->state != JA_MUSIC_PAUSED) return; // Comprobación mejorada + + current_music->state = JA_MUSIC_PLAYING; + SDL_BindAudioStream(sdlAudioDevice, current_music->stream); +} + +inline void JA_StopMusic() { + if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return; + + current_music->state = JA_MUSIC_STOPPED; + if (current_music->stream) { + 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) { + stb_vorbis_seek_start(current_music->vorbis); + } + // No liberem filename aquí; es fa en JA_DeleteMusic. +} + +inline void JA_FadeOutMusic(const int milliseconds) { + if (!JA_musicEnabled) return; + if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return; + + fading = true; + fade_start_time = SDL_GetTicks(); + fade_duration = milliseconds; + fade_initial_volume = JA_musicVolume; +} + +inline JA_Music_state JA_GetMusicState() { + if (!JA_musicEnabled) return JA_MUSIC_DISABLED; + if (!current_music) return JA_MUSIC_INVALID; + + return current_music->state; +} + +inline void JA_DeleteMusic(JA_Music_t* music) { + if (!music) return; + if (current_music == music) { + JA_StopMusic(); + current_music = nullptr; + } + if (music->stream) SDL_DestroyAudioStream(music->stream); + if (music->vorbis) stb_vorbis_close(music->vorbis); + SDL_free(music->ogg_data); + free(music->filename); // filename es libera aquí + delete music; +} + +inline float JA_SetMusicVolume(float volume) { + JA_musicVolume = SDL_clamp(volume, 0.0f, 1.0f); + if (current_music && current_music->stream) { + SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); + } + return JA_musicVolume; +} + +inline void JA_SetMusicPosition(float /*value*/) { + // No implementat amb el backend de streaming. Mai va arribar a usar-se + // en el codi existent, així que es manté com a stub. +} + +inline float JA_GetMusicPosition() { + // Veure nota a JA_SetMusicPosition. + return 0.0f; +} + +inline void JA_EnableMusic(const bool value) { + if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic(); + + JA_musicEnabled = value; +} + +// --- Sound Functions --- + +inline JA_Sound_t* JA_NewSound(Uint8* buffer, Uint32 length) { + JA_Sound_t* sound = new JA_Sound_t(); + sound->buffer = buffer; + sound->length = length; + // Nota: spec se queda con los valores por defecto. + return sound; +} + +inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) { + JA_Sound_t* sound = new JA_Sound_t(); + if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &sound->buffer, &sound->length)) { + SDL_Log("Failed to load WAV from memory: %s", SDL_GetError()); + delete sound; + return nullptr; + } + return sound; +} + +inline JA_Sound_t* JA_LoadSound(const char* filename) { + JA_Sound_t* sound = new JA_Sound_t(); + if (!SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length)) { + SDL_Log("Failed to load WAV file: %s", SDL_GetError()); + delete sound; + return nullptr; + } + return sound; +} + +inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) { + if (!JA_soundEnabled || !sound) 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 int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop, const int group) { + if (!JA_soundEnabled || !sound) return -1; + if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1; + + JA_StopChannel(channel); // Detiene y limpia el canal si estaba en uso + + channels[channel].sound = sound; + channels[channel].times = loop; + channels[channel].pos = 0; + channels[channel].group = group; // Asignar grupo + channels[channel].state = JA_CHANNEL_PLAYING; + channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec); + + if (!channels[channel].stream) { + SDL_Log("Failed to create audio stream for sound!"); + channels[channel].state = JA_CHANNEL_FREE; + return -1; + } + + SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, 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) return; + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { + if (channels[i].sound == sound) JA_StopChannel(i); + } + SDL_free(sound->buffer); + delete sound; +} + +inline void JA_PauseChannel(const int channel) { + if (!JA_soundEnabled) return; + + if (channel == -1) { + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) + if (channels[i].state == JA_CHANNEL_PLAYING) { + channels[i].state = JA_CHANNEL_PAUSED; + SDL_UnbindAudioStream(channels[i].stream); + } + } 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; + + if (channel == -1) { + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) + if (channels[i].state == JA_CHANNEL_PAUSED) { + channels[i].state = JA_CHANNEL_PLAYING; + SDL_BindAudioStream(sdlAudioDevice, channels[i].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 void JA_StopChannel(const int channel) { + if (channel == -1) { + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { + if (channels[i].state != JA_CHANNEL_FREE) { + if (channels[i].stream) SDL_DestroyAudioStream(channels[i].stream); + channels[i].stream = nullptr; + channels[i].state = JA_CHANNEL_FREE; + channels[i].pos = 0; + channels[i].sound = NULL; + } + } + } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { + if (channels[channel].state != JA_CHANNEL_FREE) { + if (channels[channel].stream) SDL_DestroyAudioStream(channels[channel].stream); + channels[channel].stream = nullptr; + channels[channel].state = JA_CHANNEL_FREE; + channels[channel].pos = 0; + channels[channel].sound = NULL; + } + } +} + +inline JA_Channel_state JA_GetChannelState(const int channel) { + if (!JA_soundEnabled) return JA_SOUND_DISABLED; + if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return JA_CHANNEL_INVALID; + + return channels[channel].state; +} + +inline float JA_SetSoundVolume(float volume, const int group = -1) // -1 para todos los grupos +{ + const float v = SDL_clamp(volume, 0.0f, 1.0f); + + if (group == -1) { + for (int i = 0; i < JA_MAX_GROUPS; ++i) { + JA_soundVolume[i] = v; + } + } else if (group >= 0 && group < JA_MAX_GROUPS) { + JA_soundVolume[group] = v; + } else { + return v; // Grupo inválido + } + + // Aplicar volumen a canales activos + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { + if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) { + if (group == -1 || channels[i].group == group) { + if (channels[i].stream) { + SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume[channels[i].group]); + } + } + } + } + return v; +} + +inline void JA_EnableSound(const bool value) { + if (!value) { + JA_StopChannel(-1); // Detener todos los canales + } + JA_soundEnabled = value; +} + +inline float JA_SetVolume(float volume) { + float v = JA_SetMusicVolume(volume); + JA_SetSoundVolume(v, -1); // Aplicar a todos los grupos de sonido + return v; +} diff --git a/source/resource.cpp b/source/resource.cpp index 93d06c0..20558ac 100644 --- a/source/resource.cpp +++ b/source/resource.cpp @@ -13,7 +13,7 @@ #include "asset.hpp" // Para Asset #include "color.hpp" // Para Color, NO_COLOR_MOD -#include "external/jail_audio.h" // Para JA_LoadMusic, JA_LoadSound, JA_DeleteMusic, JA_DeleteSound +#include "external/jail_audio.hpp" // Para JA_LoadMusic, JA_LoadSound, JA_DeleteMusic, JA_DeleteSound #include "lang.hpp" // Para getText #include "param.hpp" // Para Param, param, ParamPlayer, ParamResource, ParamGame #include "resource_helper.hpp" // Para loadFile