normalitzat Audio

This commit is contained in:
2026-04-18 11:43:13 +02:00
parent 561028ff04
commit 3390d01ef6
12 changed files with 441 additions and 207 deletions

View File

@@ -44,6 +44,7 @@ set(APP_SOURCES
# --- core/audio --- # --- core/audio ---
source/core/audio/audio.cpp source/core/audio/audio.cpp
source/core/audio/audio_adapter.cpp
# --- core/input --- # --- core/input ---
source/core/input/define_buttons.cpp source/core/input/define_buttons.cpp

View File

@@ -1,6 +1,6 @@
#include "core/audio/audio.hpp" #include "core/audio/audio.hpp"
#include <SDL3/SDL.h> // Para SDL_LogInfo, SDL_LogCategory, SDL_G... #include <SDL3/SDL.h> // Para SDL_GetError, SDL_Init
#include <algorithm> // Para clamp #include <algorithm> // Para clamp
#include <iostream> // Para std::cout #include <iostream> // Para std::cout
@@ -8,9 +8,9 @@
// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp). // Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp).
// clang-format off // clang-format off
#undef STB_VORBIS_HEADER_ONLY #undef STB_VORBIS_HEADER_ONLY
#include "external/stb_vorbis.h" #include "external/stb_vorbis.c"
// stb_vorbis.h filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem // stb_vorbis.c 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. // perquè xocarien amb noms de paràmetres de plantilla en altres headers.
#undef L #undef L
#undef C #undef C
#undef R #undef R
@@ -19,9 +19,9 @@
#undef PLAYBACK_RIGHT #undef PLAYBACK_RIGHT
// clang-format on // clang-format on
#include "core/audio/jail_audio.hpp" // Para JA_FadeOutMusic, JA_Init, JA_PauseM... #include "core/audio/audio_adapter.hpp" // Para AudioResource::getMusic/getSound
#include "core/resources/resource.hpp" // Para Resource #include "core/audio/jail_audio.hpp" // Para JA_*
#include "game/options.hpp" // Para AudioOptions, audio, MusicOptions #include "game/options.hpp" // Para Options::audio
// Singleton // Singleton
Audio* Audio::instance = nullptr; Audio* Audio::instance = nullptr;
@@ -30,7 +30,10 @@ Audio* Audio::instance = nullptr;
void Audio::init() { Audio::instance = new Audio(); } void Audio::init() { Audio::instance = new Audio(); }
// Libera la instancia // Libera la instancia
void Audio::destroy() { delete Audio::instance; } void Audio::destroy() {
delete Audio::instance;
Audio::instance = nullptr;
}
// Obtiene la instancia // Obtiene la instancia
auto Audio::get() -> Audio* { return Audio::instance; } auto Audio::get() -> Audio* { return Audio::instance; }
@@ -46,17 +49,57 @@ Audio::~Audio() {
// Método principal // Método principal
void Audio::update() { void Audio::update() {
JA_Update(); JA_Update();
// Sincronizar estado: detectar cuando la música se para (ej. fade-out completado)
if (instance && instance->music_.state == MusicState::PLAYING && JA_GetMusicState() != JA_MUSIC_PLAYING) {
instance->music_.state = MusicState::STOPPED;
}
} }
// Reproduce la música // Reproduce la música por nombre (con crossfade opcional)
void Audio::playMusic(const std::string& name, const int loop) { void Audio::playMusic(const std::string& name, const int loop, const int crossfade_ms) {
music_.name = name; bool new_loop = (loop != 0);
music_.loop = (loop != 0);
if (music_enabled_ && music_.state != MusicState::PLAYING) { // Si ya está sonando exactamente la misma pista y mismo modo loop, no hacemos nada
JA_PlayMusic(Resource::get()->getMusic(name), loop); if (music_.state == MusicState::PLAYING && music_.name == name && music_.loop == new_loop) {
music_.state = MusicState::PLAYING; return;
} }
if (!music_enabled_) return;
auto* resource = AudioResource::getMusic(name);
if (resource == nullptr) return;
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
JA_CrossfadeMusic(resource, crossfade_ms, loop);
} else {
if (music_.state == MusicState::PLAYING) {
JA_StopMusic();
}
JA_PlayMusic(resource, loop);
}
music_.name = name;
music_.loop = new_loop;
music_.state = MusicState::PLAYING;
}
// 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;
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
JA_CrossfadeMusic(music, crossfade_ms, loop);
} else {
if (music_.state == MusicState::PLAYING) {
JA_StopMusic();
}
JA_PlayMusic(music, loop);
}
music_.name.clear(); // nom desconegut quan es passa per punter
music_.loop = (loop != 0);
music_.state = MusicState::PLAYING;
} }
// Pausa la música // Pausa la música
@@ -83,10 +126,17 @@ void Audio::stopMusic() {
} }
} }
// Reproduce un sonido // Reproduce un sonido por nombre
void Audio::playSound(const std::string& name, Group group) const { void Audio::playSound(const std::string& name, Group group) const {
if (sound_enabled_) { if (sound_enabled_) {
JA_PlaySound(Resource::get()->getSound(name), 0, static_cast<int>(group)); JA_PlaySound(AudioResource::getSound(name), 0, static_cast<int>(group));
}
}
// Reproduce un sonido por puntero directo
void Audio::playSound(JA_Sound_t* sound, Group group) const {
if (sound_enabled_ && sound != nullptr) {
JA_PlaySound(sound, 0, static_cast<int>(group));
} }
} }
@@ -120,20 +170,20 @@ auto Audio::getRealMusicState() -> MusicState {
} }
} }
// Establece el volumen de los sonidos // Establece el volumen de los sonidos (float 0.0..1.0)
void Audio::setSoundVolume(int sound_volume, Group group) const { void Audio::setSoundVolume(float sound_volume, Group group) const {
if (sound_enabled_) { if (sound_enabled_) {
sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME); sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME);
const float CONVERTED_VOLUME = (sound_volume / 100.0F) * (Options::audio.volume / 100.0F); const float CONVERTED_VOLUME = sound_volume * Options::audio.volume;
JA_SetSoundVolume(CONVERTED_VOLUME, static_cast<int>(group)); JA_SetSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
} }
} }
// Establece el volumen de la música // Establece el volumen de la música (float 0.0..1.0)
void Audio::setMusicVolume(int music_volume) const { void Audio::setMusicVolume(float music_volume) const {
if (music_enabled_) { if (music_enabled_) {
music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME); music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME);
const float CONVERTED_VOLUME = (music_volume / 100.0F) * (Options::audio.volume / 100.0F); const float CONVERTED_VOLUME = music_volume * Options::audio.volume;
JA_SetMusicVolume(CONVERTED_VOLUME); JA_SetMusicVolume(CONVERTED_VOLUME);
} }
} }
@@ -159,4 +209,4 @@ void Audio::initSDLAudio() {
JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2); JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2);
enable(Options::audio.enabled); enable(Options::audio.enabled);
} }
} }

View File

@@ -1,111 +1,114 @@
#pragma once #pragma once
#include <cstdint> // Para int8_t, uint8_t
#include <string> // Para string #include <string> // Para string
#include <utility> // Para move #include <utility> // Para move
// --- Clase Audio: gestor de audio (singleton) --- // --- Clase Audio: gestor de audio (singleton) ---
// Implementació canònica, byte-idèntica entre projectes.
// Els volums es manegen internament com a float 0.01.0; la capa de
// presentació (menús, notificacions) usa les helpers toPercent/fromPercent
// per mostrar 0100 a l'usuari.
class Audio { class Audio {
public: public:
// --- Enums --- // --- Enums ---
enum class Group : int { enum class Group : std::int8_t {
ALL = -1, // Todos los grupos ALL = -1, // Todos los grupos
GAME = 0, // Sonidos del juego GAME = 0, // Sonidos del juego
INTERFACE = 1 // Sonidos de la interfaz INTERFACE = 1 // Sonidos de la interfaz
}; };
enum class MusicState { enum class MusicState : std::uint8_t {
PLAYING, // Reproduciendo música PLAYING, // Reproduciendo música
PAUSED, // Música pausada PAUSED, // Música pausada
STOPPED, // Música detenida STOPPED, // Música detenida
}; };
// --- Constantes --- // --- Constantes ---
static constexpr int MAX_VOLUME = 100; // Volumen máximo static constexpr float MAX_VOLUME = 1.0F; // Volumen máximo (float 0..1)
static constexpr int MIN_VOLUME = 0; // Volumen mínimo static constexpr float MIN_VOLUME = 0.0F; // Volumen mínimo (float 0..1)
static constexpr int FREQUENCY = 48000; // Frecuencia de audio static constexpr float VOLUME_STEP = 0.05F; // Pas estàndard per a UI (5%)
static constexpr int FREQUENCY = 48000; // Frecuencia de audio
static constexpr int DEFAULT_CROSSFADE_MS = 1500; // Duració del crossfade per defecte (ms)
// --- Métodos de singleton --- // --- Singleton ---
static void init(); // Inicializa el objeto Audio static void init(); // Inicializa el objeto Audio
static void destroy(); // Libera el objeto Audio static void destroy(); // Libera el objeto Audio
static auto get() -> Audio*; // Obtiene el puntero al objeto Audio static auto get() -> Audio*; // Obtiene el puntero al objeto Audio
Audio(const Audio&) = delete; // Evitar copia Audio(const Audio&) = delete; // Evitar copia
auto operator=(const Audio&) -> Audio& = delete; // Evitar asignación auto operator=(const Audio&) -> Audio& = delete; // Evitar asignación
// --- Método principal --- static void update(); // Actualización del sistema de audio
static void update();
// --- Control de Música --- // --- Control de música ---
void playMusic(const std::string& name, int loop = -1); // Reproducir música en bucle void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional)
void pauseMusic(); // Pausar reproducción de música void playMusic(struct JA_Music_t* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional)
void resumeMusic(); // Continua la música pausada void pauseMusic(); // Pausar reproducción de música
void stopMusic(); // Detener completamente la música void resumeMusic(); // Continua la música pausada
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música void stopMusic(); // Detener completamente la música
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
// --- Control de Sonidos --- // --- Control de sonidos ---
void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre
void stopAllSounds() const; // Detener todos los sonidos void playSound(struct JA_Sound_t* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero
void stopAllSounds() const; // Detener todos los sonidos
// --- Configuración General --- // --- Control de volumen (API interna: float 0.0..1.0) ---
void setSoundVolume(float volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
void setMusicVolume(float volume) const; // Ajustar volumen de música
// --- Helpers de conversió per a la capa de presentació ---
// UI (menús, notificacions) manega enters 0..100; internament viu float 0..1.
static constexpr auto toPercent(float volume) -> int {
return static_cast<int>(volume * 100.0F + 0.5F);
}
static constexpr auto fromPercent(int percent) -> float {
return static_cast<float>(percent) / 100.0F;
}
// --- Configuración general ---
void enable(bool value); // Establecer estado general void enable(bool value); // Establecer estado general
void toggleEnabled() { enabled_ = !enabled_; } // Alternar estado general void toggleEnabled() { enabled_ = !enabled_; } // Alternar estado general
void applySettings(); // Aplica la configuración void applySettings(); // Aplica la configuración
// --- Configuración de Sonidos --- // --- Configuración de sonidos ---
void enableSound() { sound_enabled_ = true; } // Habilitar sonidos void enableSound() { sound_enabled_ = true; } // Habilitar sonidos
void disableSound() { sound_enabled_ = false; } // Deshabilitar sonidos void disableSound() { sound_enabled_ = false; } // Deshabilitar sonidos
void enableSound(bool value) { sound_enabled_ = value; } // Establecer estado de sonidos void enableSound(bool value) { sound_enabled_ = value; } // Establecer estado de sonidos
void toggleSound() { sound_enabled_ = !sound_enabled_; } // Alternar estado de sonidos void toggleSound() { sound_enabled_ = !sound_enabled_; } // Alternar estado de sonidos
// --- Configuración de Música --- // --- Configuración de música ---
void enableMusic() { music_enabled_ = true; } // Habilitar música void enableMusic() { music_enabled_ = true; } // Habilitar música
void disableMusic() { music_enabled_ = false; } // Deshabilitar música void disableMusic() { music_enabled_ = false; } // Deshabilitar música
void enableMusic(bool value) { music_enabled_ = value; } // Establecer estado de música void enableMusic(bool value) { music_enabled_ = value; } // Establecer estado de música
void toggleMusic() { music_enabled_ = !music_enabled_; } // Alternar estado de música void toggleMusic() { music_enabled_ = !music_enabled_; } // Alternar estado de música
// --- Control de Volumen --- // --- Consultas de estado ---
void setSoundVolume(int volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
void setMusicVolume(int volume) const; // Ajustar volumen de música
// --- Getters para debug ---
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; } [[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
[[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; } [[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; }
[[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; } [[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; }
[[nodiscard]] auto getMusicState() const -> MusicState { return music_.state; } [[nodiscard]] auto getMusicState() const -> MusicState { return music_.state; }
[[nodiscard]] static auto getRealMusicState() -> MusicState; // Consulta directamente a jailaudio [[nodiscard]] static auto getRealMusicState() -> MusicState;
[[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; } [[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; }
private: private:
// --- Estructuras privadas --- // --- Tipos anidados ---
struct Music { struct Music {
MusicState state; // Estado actual de la música (reproduciendo, detenido, en pausa) MusicState state{MusicState::STOPPED}; // Estado actual de la música
std::string name; // Última pista de música reproducida std::string name; // Última pista de música reproducida
bool loop; // Indica si la última pista de música se debe reproducir en bucle bool loop{false}; // Indica si se reproduce en bucle
// Constructor para inicializar la música con valores predeterminados
Music()
: state(MusicState::STOPPED),
loop(false) {}
// Constructor para inicializar con valores específicos
Music(MusicState init_state, std::string init_name, bool init_loop)
: state(init_state),
name(std::move(init_name)),
loop(init_loop) {}
}; };
// --- Variables de estado --- // --- Métodos ---
Music music_; // Estado de la música Audio(); // Constructor privado
bool enabled_ = true; // Estado general del audio ~Audio(); // Destructor privado
bool sound_enabled_ = true; // Estado de los efectos de sonido
bool music_enabled_ = true; // Estado de la música
// --- Métodos internos ---
void initSDLAudio(); // Inicializa SDL Audio void initSDLAudio(); // Inicializa SDL Audio
// --- Constructores y destructor privados (singleton) --- // --- Variables miembro ---
Audio(); // Constructor privado
~Audio(); // Destructor privado
// --- Instancia singleton ---
static Audio* instance; // Instancia única de Audio static Audio* instance; // Instancia única de Audio
};
Music music_; // Estado de la música
bool enabled_{true}; // Estado general del audio
bool sound_enabled_{true}; // Estado de los efectos de sonido
bool music_enabled_{true}; // Estado de la música
};

View File

@@ -0,0 +1,13 @@
#include "core/audio/audio_adapter.hpp"
#include "core/resources/resource.hpp"
namespace AudioResource {
JA_Music_t* getMusic(const std::string& name) {
return Resource::get()->getMusic(name);
}
JA_Sound_t* getSound(const std::string& name) {
return Resource::get()->getSound(name);
}
} // namespace AudioResource

View File

@@ -0,0 +1,17 @@
#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
// 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 <string> // Para string
struct JA_Music_t;
struct JA_Sound_t;
namespace AudioResource {
JA_Music_t* getMusic(const std::string& name);
JA_Sound_t* getSound(const std::string& name);
} // namespace AudioResource

View File

@@ -3,26 +3,41 @@
// --- Includes --- // --- Includes ---
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <stdint.h> // Para uint32_t, uint8_t #include <stdint.h> // Para uint32_t, uint8_t
#include <stdio.h> // Para NULL, fseek, printf, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET #include <stdio.h> // Para NULL, fseek, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET
#include <stdlib.h> // Para free, malloc #include <stdlib.h> // Para free, malloc
#include <string.h> // Para strcpy, strlen
#include <iostream> // Para std::cout #include <iostream> // Para std::cout
#include <memory> // Para std::unique_ptr
#include <string> // Para std::string
#include <vector> // Para std::vector
#define STB_VORBIS_HEADER_ONLY #define STB_VORBIS_HEADER_ONLY
#include "external/stb_vorbis.h" // Para stb_vorbis_decode_memory #include "external/stb_vorbis.c" // Para stb_vorbis_open_memory i streaming
// Deleter stateless per a buffers reservats amb `SDL_malloc` / `SDL_LoadWAV*`.
// Compatible amb `std::unique_ptr<Uint8[], SDLFreeDeleter>` — zero size
// overhead gràcies a EBO, igual que un unique_ptr amb default_delete.
struct SDLFreeDeleter {
void operator()(Uint8* p) const noexcept {
if (p) SDL_free(p);
}
};
// --- Public Enums --- // --- Public Enums ---
enum JA_Channel_state { JA_CHANNEL_INVALID, enum JA_Channel_state {
JA_CHANNEL_INVALID,
JA_CHANNEL_FREE, JA_CHANNEL_FREE,
JA_CHANNEL_PLAYING, JA_CHANNEL_PLAYING,
JA_CHANNEL_PAUSED, JA_CHANNEL_PAUSED,
JA_SOUND_DISABLED }; JA_SOUND_DISABLED,
enum JA_Music_state { JA_MUSIC_INVALID, };
enum JA_Music_state {
JA_MUSIC_INVALID,
JA_MUSIC_PLAYING, JA_MUSIC_PLAYING,
JA_MUSIC_PAUSED, JA_MUSIC_PAUSED,
JA_MUSIC_STOPPED, JA_MUSIC_STOPPED,
JA_MUSIC_DISABLED }; JA_MUSIC_DISABLED,
};
// --- Struct Definitions --- // --- Struct Definitions ---
#define JA_MAX_SIMULTANEOUS_CHANNELS 20 #define JA_MAX_SIMULTANEOUS_CHANNELS 20
@@ -31,7 +46,9 @@ enum JA_Music_state { JA_MUSIC_INVALID,
struct JA_Sound_t { struct JA_Sound_t {
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
Uint32 length{0}; Uint32 length{0};
Uint8* buffer{NULL}; // Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV
// via SDL_malloc; el deleter `SDLFreeDeleter` allibera amb SDL_free.
std::unique_ptr<Uint8[], SDLFreeDeleter> buffer;
}; };
struct JA_Channel_t { struct JA_Channel_t {
@@ -46,21 +63,22 @@ struct JA_Channel_t {
struct JA_Music_t { struct JA_Music_t {
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
// OGG comprimit en memòria. Propietat nostra; es copia des del fitxer una // OGG comprimit en memòria. Propietat nostra; es copia des del buffer
// sola vegada en JA_LoadMusic i es descomprimix en chunks per streaming. // d'entrada una sola vegada en JA_LoadMusic i es descomprimix en chunks
Uint8* ogg_data{nullptr}; // per streaming. Com que stb_vorbis guarda un punter persistent al
Uint32 ogg_length{0}; // `.data()` d'aquest vector, no el podem resize'jar un cop establert
stb_vorbis* vorbis{nullptr}; // Handle del decoder, viu tot el cicle del JA_Music_t // (una reallocation invalidaria el punter que el decoder conserva).
std::vector<Uint8> ogg_data;
stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del JA_Music_t
char* filename{nullptr}; std::string filename;
int times{0}; // Loops restants (-1 = infinit, 0 = un sol play) int times{0}; // loops restants (-1 = infinit, 0 = un sol play)
SDL_AudioStream* stream{nullptr}; SDL_AudioStream* stream{nullptr};
JA_Music_state state{JA_MUSIC_INVALID}; JA_Music_state state{JA_MUSIC_INVALID};
}; };
// --- Internal Global State --- // --- Internal Global State (inline, C++17) ---
// Marcado 'inline' (C++17) para asegurar una única instancia.
inline JA_Music_t* current_music{nullptr}; inline JA_Music_t* current_music{nullptr};
inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS]; inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS];
@@ -72,15 +90,27 @@ inline bool JA_musicEnabled{true};
inline bool JA_soundEnabled{true}; inline bool JA_soundEnabled{true};
inline SDL_AudioDeviceID sdlAudioDevice{0}; inline SDL_AudioDeviceID sdlAudioDevice{0};
inline bool fading{false}; // --- Crossfade / Fade State ---
inline int fade_start_time{0}; struct JA_FadeState {
inline int fade_duration{0}; bool active{false};
inline float fade_initial_volume{0.0f}; // Corregido de 'int' a 'float' Uint64 start_time{0};
int duration_ms{0};
float initial_volume{0.0f};
};
struct JA_OutgoingMusic {
SDL_AudioStream* stream{nullptr};
JA_FadeState fade;
};
inline JA_OutgoingMusic outgoing_music;
inline JA_FadeState incoming_fade;
// --- Forward Declarations --- // --- Forward Declarations ---
inline void JA_StopMusic(); inline void JA_StopMusic();
inline void JA_StopChannel(const int channel); inline void JA_StopChannel(const int channel);
inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0); inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0);
inline void JA_CrossfadeMusic(JA_Music_t* music, int crossfade_ms, int loop = -1);
// --- Music streaming internals --- // --- Music streaming internals ---
// Bytes-per-sample per canal (sempre s16) // Bytes-per-sample per canal (sempre s16)
@@ -98,15 +128,15 @@ inline int JA_FeedMusicChunk(JA_Music_t* music) {
if (!music || !music->vorbis || !music->stream) return 0; if (!music || !music->vorbis || !music->stream) return 0;
short chunk[JA_MUSIC_CHUNK_SHORTS]; short chunk[JA_MUSIC_CHUNK_SHORTS];
const int channels = music->spec.channels; const int num_channels = music->spec.channels;
const int samples_per_channel = stb_vorbis_get_samples_short_interleaved( const int samples_per_channel = stb_vorbis_get_samples_short_interleaved(
music->vorbis, music->vorbis,
channels, num_channels,
chunk, chunk,
JA_MUSIC_CHUNK_SHORTS); JA_MUSIC_CHUNK_SHORTS);
if (samples_per_channel <= 0) return 0; if (samples_per_channel <= 0) return 0;
const int bytes = samples_per_channel * channels * JA_MUSIC_BYTES_PER_SAMPLE; const int bytes = samples_per_channel * num_channels * JA_MUSIC_BYTES_PER_SAMPLE;
SDL_PutAudioStreamData(music->stream, chunk, bytes); SDL_PutAudioStreamData(music->stream, chunk, bytes);
return samples_per_channel; return samples_per_channel;
} }
@@ -133,20 +163,51 @@ inline void JA_PumpMusic(JA_Music_t* music) {
} }
} }
// 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 || !music->vorbis || !music->stream) return;
const int bytes_per_second = music->spec.freq * music->spec.channels * JA_MUSIC_BYTES_PER_SAMPLE;
const int needed_bytes = static_cast<int>((static_cast<int64_t>(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
}
}
// --- Core Functions --- // --- Core Functions ---
inline void JA_Update() { inline void JA_Update() {
// --- Outgoing music fade-out (crossfade o fade-out a silencio) ---
if (outgoing_music.stream && outgoing_music.fade.active) {
Uint64 now = SDL_GetTicks();
Uint64 elapsed = now - outgoing_music.fade.start_time;
if (elapsed >= (Uint64)outgoing_music.fade.duration_ms) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
outgoing_music.fade.active = false;
} else {
float percent = (float)elapsed / (float)outgoing_music.fade.duration_ms;
SDL_SetAudioStreamGain(outgoing_music.stream, outgoing_music.fade.initial_volume * (1.0f - percent));
}
}
// --- Current music ---
if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) { if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) {
if (fading) { // Fade-in (parte de un crossfade)
int time = SDL_GetTicks(); if (incoming_fade.active) {
if (time > (fade_start_time + fade_duration)) { Uint64 now = SDL_GetTicks();
fading = false; Uint64 elapsed = now - incoming_fade.start_time;
JA_StopMusic(); if (elapsed >= (Uint64)incoming_fade.duration_ms) {
return; incoming_fade.active = false;
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
} else { } else {
const int time_passed = time - fade_start_time; float percent = (float)elapsed / (float)incoming_fade.duration_ms;
const float percent = (float)time_passed / (float)fade_duration; SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * percent);
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * (1.0 - percent));
} }
} }
@@ -158,12 +219,13 @@ inline void JA_Update() {
} }
} }
// --- Sound channels ---
if (JA_soundEnabled) { if (JA_soundEnabled) {
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i)
if (channels[i].state == JA_CHANNEL_PLAYING) { if (channels[i].state == JA_CHANNEL_PLAYING) {
if (channels[i].times != 0) { if (channels[i].times != 0) {
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) { 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); SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer.get(), channels[i].sound->length);
if (channels[i].times > 0) channels[i].times--; if (channels[i].times > 0) channels[i].times--;
} }
} else { } else {
@@ -174,12 +236,8 @@ inline void JA_Update() {
} }
inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) { 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}; JA_audioSpec = {format, num_channels, freq};
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec); sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec);
if (sdlAudioDevice == 0) std::cout << "Failed to initialize SDL audio!" << '\n'; if (sdlAudioDevice == 0) std::cout << "Failed to initialize SDL audio!" << '\n';
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE; for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE;
@@ -187,7 +245,11 @@ inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_
} }
inline void JA_Quit() { inline void JA_Quit() {
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice if (outgoing_music.stream) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
}
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
sdlAudioDevice = 0; sdlAudioDevice = 0;
} }
@@ -196,26 +258,25 @@ inline void JA_Quit() {
inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) { inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
if (!buffer || length == 0) return nullptr; if (!buffer || length == 0) return nullptr;
// Còpia del OGG comprimit: stb_vorbis llig de forma persistent aquesta // Allocem el JA_Music_t primer per aprofitar el seu `std::vector<Uint8>`
// memòria mentre el handle estiga viu, així que hem de posseir-la nosaltres. // com a propietari del OGG comprimit. stb_vorbis guarda un punter
Uint8* ogg_copy = static_cast<Uint8*>(SDL_malloc(length)); // persistent al buffer; com que ací no el resize'jem, el .data() és
if (!ogg_copy) return nullptr; // estable durant tot el cicle de vida del music.
SDL_memcpy(ogg_copy, buffer, length); auto* music = new JA_Music_t();
music->ogg_data.assign(buffer, buffer + length);
int error = 0; int error = 0;
stb_vorbis* vorbis = stb_vorbis_open_memory(ogg_copy, static_cast<int>(length), &error, nullptr); music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(),
if (!vorbis) { static_cast<int>(length),
SDL_free(ogg_copy); &error,
nullptr);
if (!music->vorbis) {
std::cout << "JA_LoadMusic: stb_vorbis_open_memory failed (error " << error << ")" << '\n'; std::cout << "JA_LoadMusic: stb_vorbis_open_memory failed (error " << error << ")" << '\n';
delete music;
return nullptr; return nullptr;
} }
auto* music = new JA_Music_t(); const stb_vorbis_info info = stb_vorbis_get_info(music->vorbis);
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.channels = info.channels;
music->spec.freq = static_cast<int>(info.sample_rate); music->spec.freq = static_cast<int>(info.sample_rate);
music->spec.format = SDL_AUDIO_S16; music->spec.format = SDL_AUDIO_S16;
@@ -224,31 +285,36 @@ inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
return music; 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 JA_Music_t* JA_LoadMusic(Uint8* buffer, Uint32 length, const char* filename) {
JA_Music_t* music = JA_LoadMusic(static_cast<const Uint8*>(buffer), length);
if (music && filename) music->filename = filename;
return music;
}
inline JA_Music_t* JA_LoadMusic(const char* filename) { 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. // Carreguem primer el arxiu en memòria i després el descomprimim.
FILE* f = fopen(filename, "rb"); FILE* f = fopen(filename, "rb");
if (!f) return NULL; // Añadida comprobación de apertura if (!f) return nullptr;
fseek(f, 0, SEEK_END); fseek(f, 0, SEEK_END);
long fsize = ftell(f); long fsize = ftell(f);
fseek(f, 0, SEEK_SET); fseek(f, 0, SEEK_SET);
auto* buffer = static_cast<Uint8*>(malloc(fsize + 1)); auto* buffer = static_cast<Uint8*>(malloc(fsize + 1));
if (!buffer) { // Añadida comprobación de malloc if (!buffer) {
fclose(f); fclose(f);
return NULL; return nullptr;
} }
if (fread(buffer, fsize, 1, f) != 1) { if (fread(buffer, fsize, 1, f) != 1) {
fclose(f); fclose(f);
free(buffer); free(buffer);
return NULL; return nullptr;
} }
fclose(f); fclose(f);
JA_Music_t* music = JA_LoadMusic(buffer, fsize); JA_Music_t* music = JA_LoadMusic(static_cast<const Uint8*>(buffer), static_cast<Uint32>(fsize));
if (music) { // Comprobar que JA_LoadMusic tuvo éxito if (music) {
music->filename = static_cast<char*>(malloc(strlen(filename) + 1)); music->filename = filename;
if (music->filename) {
strcpy(music->filename, filename);
}
} }
free(buffer); free(buffer);
@@ -280,18 +346,20 @@ inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
// Pre-cargem el buffer abans de bindejar per evitar un underrun inicial. // Pre-cargem el buffer abans de bindejar per evitar un underrun inicial.
JA_PumpMusic(current_music); JA_PumpMusic(current_music);
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n"); if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) {
std::cout << "[ERROR] SDL_BindAudioStream failed!" << '\n';
}
} }
inline char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) { inline const char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) {
if (!music) music = current_music; if (!music) music = current_music;
if (!music) return nullptr; // Añadida comprobación if (!music || music->filename.empty()) return nullptr;
return music->filename; return music->filename.c_str();
} }
inline void JA_PauseMusic() { inline void JA_PauseMusic() {
if (!JA_musicEnabled) return; if (!JA_musicEnabled) return;
if (!current_music || current_music->state != JA_MUSIC_PLAYING) return; // Comprobación mejorada if (!current_music || current_music->state != JA_MUSIC_PLAYING) return;
current_music->state = JA_MUSIC_PAUSED; current_music->state = JA_MUSIC_PAUSED;
SDL_UnbindAudioStream(current_music->stream); SDL_UnbindAudioStream(current_music->stream);
@@ -299,13 +367,21 @@ inline void JA_PauseMusic() {
inline void JA_ResumeMusic() { inline void JA_ResumeMusic() {
if (!JA_musicEnabled) return; if (!JA_musicEnabled) return;
if (!current_music || current_music->state != JA_MUSIC_PAUSED) return; // Comprobación mejorada if (!current_music || current_music->state != JA_MUSIC_PAUSED) return;
current_music->state = JA_MUSIC_PLAYING; current_music->state = JA_MUSIC_PLAYING;
SDL_BindAudioStream(sdlAudioDevice, current_music->stream); SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
} }
inline void JA_StopMusic() { inline void JA_StopMusic() {
// Limpiar outgoing crossfade si existe
if (outgoing_music.stream) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
outgoing_music.fade.active = false;
}
incoming_fade.active = false;
if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return; if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return;
current_music->state = JA_MUSIC_STOPPED; current_music->state = JA_MUSIC_STOPPED;
@@ -318,17 +394,73 @@ inline void JA_StopMusic() {
if (current_music->vorbis) { if (current_music->vorbis) {
stb_vorbis_seek_start(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) { inline void JA_FadeOutMusic(const int milliseconds) {
if (!JA_musicEnabled) return; if (!JA_musicEnabled) return;
if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return; if (!current_music || current_music->state != JA_MUSIC_PLAYING) return;
fading = true; // Destruir outgoing anterior si existe
fade_start_time = SDL_GetTicks(); if (outgoing_music.stream) {
fade_duration = milliseconds; SDL_DestroyAudioStream(outgoing_music.stream);
fade_initial_volume = JA_musicVolume; 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 = {true, SDL_GetTicks(), milliseconds, 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) 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 || !music->vorbis) return;
// Destruir outgoing anterior si existe (crossfade durante crossfade)
if (outgoing_music.stream) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
outgoing_music.fade.active = false;
}
// Robar el stream de la musica actual al outgoing para el fade-out.
// 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 && current_music->state == JA_MUSIC_PLAYING && current_music->stream) {
JA_PreFillOutgoing(current_music, crossfade_ms);
outgoing_music.stream = current_music->stream;
outgoing_music.fade = {true, SDL_GetTicks(), crossfade_ms, JA_musicVolume};
current_music->stream = nullptr;
current_music->state = JA_MUSIC_STOPPED;
if (current_music->vorbis) 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;
stb_vorbis_seek_start(current_music->vorbis);
current_music->stream = SDL_CreateAudioStream(&current_music->spec, &JA_audioSpec);
if (!current_music->stream) {
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 = {true, SDL_GetTicks(), crossfade_ms, 0.0f};
} }
inline JA_Music_state JA_GetMusicState() { inline JA_Music_state JA_GetMusicState() {
@@ -346,8 +478,8 @@ inline void JA_DeleteMusic(JA_Music_t* music) {
} }
if (music->stream) SDL_DestroyAudioStream(music->stream); if (music->stream) SDL_DestroyAudioStream(music->stream);
if (music->vorbis) stb_vorbis_close(music->vorbis); if (music->vorbis) stb_vorbis_close(music->vorbis);
SDL_free(music->ogg_data); // ogg_data (std::vector) i filename (std::string) s'alliberen sols
free(music->filename); // filename es libera aquí // al destructor de JA_Music_t.
delete music; delete music;
} }
@@ -360,49 +492,40 @@ inline float JA_SetMusicVolume(float volume) {
} }
inline void JA_SetMusicPosition(float /*value*/) { inline void JA_SetMusicPosition(float /*value*/) {
// No implementat amb el backend de streaming. Mai va arribar a usar-se // No implementat amb el backend de streaming.
// en el codi existent, així que es manté com a stub.
} }
inline float JA_GetMusicPosition() { inline float JA_GetMusicPosition() {
// Veure nota a JA_SetMusicPosition.
return 0.0f; return 0.0f;
} }
inline void JA_EnableMusic(const bool value) { inline void JA_EnableMusic(const bool value) {
if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic(); if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic();
JA_musicEnabled = value; JA_musicEnabled = value;
} }
// --- Sound Functions --- // --- 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) { inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) {
JA_Sound_t* sound = new JA_Sound_t(); auto sound = std::make_unique<JA_Sound_t>();
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &sound->buffer, &sound->length)) { Uint8* raw = nullptr;
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &raw, &sound->length)) {
std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n'; std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n';
delete sound;
return nullptr; return nullptr;
} }
return sound; sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
return sound.release();
} }
inline JA_Sound_t* JA_LoadSound(const char* filename) { inline JA_Sound_t* JA_LoadSound(const char* filename) {
JA_Sound_t* sound = new JA_Sound_t(); auto sound = std::make_unique<JA_Sound_t>();
if (!SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length)) { Uint8* raw = nullptr;
if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) {
std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n'; std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n';
delete sound;
return nullptr; return nullptr;
} }
return sound; sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
return sound.release();
} }
inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) { inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) {
@@ -422,12 +545,12 @@ inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int
if (!JA_soundEnabled || !sound) return -1; if (!JA_soundEnabled || !sound) return -1;
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1; if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1;
JA_StopChannel(channel); // Detiene y limpia el canal si estaba en uso JA_StopChannel(channel);
channels[channel].sound = sound; channels[channel].sound = sound;
channels[channel].times = loop; channels[channel].times = loop;
channels[channel].pos = 0; channels[channel].pos = 0;
channels[channel].group = group; // Asignar grupo channels[channel].group = group;
channels[channel].state = JA_CHANNEL_PLAYING; channels[channel].state = JA_CHANNEL_PLAYING;
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec); channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
@@ -437,7 +560,7 @@ inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int
return -1; return -1;
} }
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length); SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer.get(), channels[channel].sound->length);
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]); SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
@@ -449,7 +572,7 @@ inline void JA_DeleteSound(JA_Sound_t* sound) {
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
if (channels[i].sound == sound) JA_StopChannel(i); if (channels[i].sound == sound) JA_StopChannel(i);
} }
SDL_free(sound->buffer); // buffer es destrueix automàticament via RAII (SDLFreeDeleter).
delete sound; delete sound;
} }
@@ -495,7 +618,7 @@ inline void JA_StopChannel(const int channel) {
channels[i].stream = nullptr; channels[i].stream = nullptr;
channels[i].state = JA_CHANNEL_FREE; channels[i].state = JA_CHANNEL_FREE;
channels[i].pos = 0; channels[i].pos = 0;
channels[i].sound = NULL; channels[i].sound = nullptr;
} }
} }
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
@@ -504,7 +627,7 @@ inline void JA_StopChannel(const int channel) {
channels[channel].stream = nullptr; channels[channel].stream = nullptr;
channels[channel].state = JA_CHANNEL_FREE; channels[channel].state = JA_CHANNEL_FREE;
channels[channel].pos = 0; channels[channel].pos = 0;
channels[channel].sound = NULL; channels[channel].sound = nullptr;
} }
} }
} }
@@ -516,8 +639,7 @@ inline JA_Channel_state JA_GetChannelState(const int channel) {
return channels[channel].state; return channels[channel].state;
} }
inline float JA_SetSoundVolume(float volume, const int group = -1) // -1 para todos los grupos inline float JA_SetSoundVolume(float volume, const int group = -1) {
{
const float v = SDL_clamp(volume, 0.0f, 1.0f); const float v = SDL_clamp(volume, 0.0f, 1.0f);
if (group == -1) { if (group == -1) {
@@ -527,10 +649,10 @@ inline float JA_SetSoundVolume(float volume, const int group = -1) // -1 para t
} else if (group >= 0 && group < JA_MAX_GROUPS) { } else if (group >= 0 && group < JA_MAX_GROUPS) {
JA_soundVolume[group] = v; JA_soundVolume[group] = v;
} else { } else {
return v; // Grupo inválido return v;
} }
// Aplicar volumen a canales activos // Aplicar volum als canals actius.
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) { if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) {
if (group == -1 || channels[i].group == group) { if (group == -1 || channels[i].group == group) {
@@ -545,13 +667,13 @@ inline float JA_SetSoundVolume(float volume, const int group = -1) // -1 para t
inline void JA_EnableSound(const bool value) { inline void JA_EnableSound(const bool value) {
if (!value) { if (!value) {
JA_StopChannel(-1); // Detener todos los canales JA_StopChannel(-1);
} }
JA_soundEnabled = value; JA_soundEnabled = value;
} }
inline float JA_SetVolume(float volume) { inline float JA_SetVolume(float volume) {
float v = JA_SetMusicVolume(volume); float v = JA_SetMusicVolume(volume);
JA_SetSoundVolume(v, -1); // Aplicar a todos los grupos de sonido JA_SetSoundVolume(v, -1);
return v; return v;
} }

View File

@@ -195,17 +195,17 @@ namespace Defaults::Video {
namespace Defaults::Music { namespace Defaults::Music {
constexpr bool ENABLED = true; constexpr bool ENABLED = true;
constexpr int VOLUME = 100; constexpr float VOLUME = 0.8F;
} // namespace Defaults::Music } // namespace Defaults::Music
namespace Defaults::Sound { namespace Defaults::Sound {
constexpr bool ENABLED = true; constexpr bool ENABLED = true;
constexpr int VOLUME = 100; constexpr float VOLUME = 1.0F;
} // namespace Defaults::Sound } // namespace Defaults::Sound
namespace Defaults::Audio { namespace Defaults::Audio {
constexpr bool ENABLED = true; constexpr bool ENABLED = true;
constexpr int VOLUME = 100; constexpr float VOLUME = 1.0F;
} // namespace Defaults::Audio } // namespace Defaults::Audio
namespace Defaults::Settings { namespace Defaults::Settings {

View File

@@ -459,7 +459,7 @@ namespace Options {
} }
if (aud.contains("volume")) { if (aud.contains("volume")) {
try { try {
audio.volume = std::clamp(aud["volume"].get_value<int>(), 0, 100); audio.volume = std::clamp(aud["volume"].get_value<float>(), 0.0F, 1.0F);
} catch (...) {} } catch (...) {}
} }
if (aud.contains("music")) { if (aud.contains("music")) {
@@ -471,7 +471,7 @@ namespace Options {
} }
if (mus.contains("volume")) { if (mus.contains("volume")) {
try { try {
audio.music.volume = std::clamp(mus["volume"].get_value<int>(), 0, 100); audio.music.volume = std::clamp(mus["volume"].get_value<float>(), 0.0F, 1.0F);
} catch (...) {} } catch (...) {}
} }
} }
@@ -484,7 +484,7 @@ namespace Options {
} }
if (snd.contains("volume")) { if (snd.contains("volume")) {
try { try {
audio.sound.volume = std::clamp(snd["volume"].get_value<int>(), 0, 100); audio.sound.volume = std::clamp(snd["volume"].get_value<float>(), 0.0F, 1.0F);
} catch (...) {} } catch (...) {}
} }
} }

View File

@@ -94,19 +94,19 @@ namespace Options {
struct Music { struct Music {
bool enabled = Defaults::Music::ENABLED; // Indica si la música suena o no bool enabled = Defaults::Music::ENABLED; // Indica si la música suena o no
int volume = Defaults::Music::VOLUME; // Volumen de la música float volume = Defaults::Music::VOLUME; // Volumen de la música (0.0..1.0)
}; };
struct Sound { struct Sound {
bool enabled = Defaults::Sound::ENABLED; // Indica si los sonidos suenan o no bool enabled = Defaults::Sound::ENABLED; // Indica si los sonidos suenan o no
int volume = Defaults::Sound::VOLUME; // Volumen de los sonidos float volume = Defaults::Sound::VOLUME; // Volumen de los sonidos (0.0..1.0)
}; };
struct Audio { struct Audio {
Music music; // Opciones para la música Music music; // Opciones para la música
Sound sound; // Opciones para los efectos de sonido Sound sound; // Opciones para los efectos de sonido
bool enabled = Defaults::Audio::ENABLED; // Indica si el audio está activo o no bool enabled = Defaults::Audio::ENABLED; // Indica si el audio está activo o no
int volume = Defaults::Audio::VOLUME; // Volumen general del audio float volume = Defaults::Audio::VOLUME; // Volumen general del audio (0.0..1.0)
}; };
struct Loading { struct Loading {

View File

@@ -106,6 +106,40 @@ class IntOption : public MenuOption {
int min_value_, max_value_, step_value_; int min_value_, max_value_, step_value_;
}; };
// VolumeOption: emmagatzema un float 0.0..1.0 però es mostra/edita com a int 0..100.
// Pensat per als sliders de volum que l'usuari veu com percentatge però que
// internament viuen en float (API unificada del motor d'àudio).
class VolumeOption : public MenuOption {
public:
VolumeOption(const std::string& cap, ServiceMenu::SettingsGroup grp, float* var, int step_percent = 5)
: MenuOption(cap, grp),
linked_variable_(var),
step_value_(step_percent) {}
[[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::ADJUST; }
[[nodiscard]] auto getValueAsString() const -> std::string override {
int pct = static_cast<int>(*linked_variable_ * 100.0F + 0.5F);
return std::to_string(pct);
}
void adjustValue(bool adjust_up) override {
int current = static_cast<int>(*linked_variable_ * 100.0F + 0.5F);
int new_value = std::clamp(current + (adjust_up ? step_value_ : -step_value_), 0, 100);
*linked_variable_ = static_cast<float>(new_value) / 100.0F;
}
auto getMaxValueWidth(Text* text_renderer) const -> int override {
int max_width = 0;
for (int value = 0; value <= 100; value += step_value_) {
int width = text_renderer->length(std::to_string(value), -2);
max_width = std::max(max_width, width);
}
return max_width;
}
private:
float* linked_variable_;
int step_value_;
};
class ListOption : public MenuOption { class ListOption : public MenuOption {
public: public:
ListOption(const std::string& cap, ServiceMenu::SettingsGroup grp, std::vector<std::string> values, std::function<std::string()> current_value_getter, std::function<void(const std::string&)> new_value_setter) ListOption(const std::string& cap, ServiceMenu::SettingsGroup grp, std::vector<std::string> values, std::function<std::string()> current_value_getter, std::function<void(const std::string&)> new_value_setter)

View File

@@ -490,28 +490,22 @@ void ServiceMenu::initializeOptions() {
SettingsGroup::AUDIO, SettingsGroup::AUDIO,
&Options::audio.enabled)); &Options::audio.enabled));
options_.push_back(std::make_unique<IntOption>( options_.push_back(std::make_unique<VolumeOption>(
Lang::getText("[SERVICE_MENU] MAIN_VOLUME"), Lang::getText("[SERVICE_MENU] MAIN_VOLUME"),
SettingsGroup::AUDIO, SettingsGroup::AUDIO,
&Options::audio.volume, &Options::audio.volume,
0,
100,
5)); 5));
options_.push_back(std::make_unique<IntOption>( options_.push_back(std::make_unique<VolumeOption>(
Lang::getText("[SERVICE_MENU] MUSIC_VOLUME"), Lang::getText("[SERVICE_MENU] MUSIC_VOLUME"),
SettingsGroup::AUDIO, SettingsGroup::AUDIO,
&Options::audio.music.volume, &Options::audio.music.volume,
0,
100,
5)); 5));
options_.push_back(std::make_unique<IntOption>( options_.push_back(std::make_unique<VolumeOption>(
Lang::getText("[SERVICE_MENU] SFX_VOLUME"), Lang::getText("[SERVICE_MENU] SFX_VOLUME"),
SettingsGroup::AUDIO, SettingsGroup::AUDIO,
&Options::audio.sound.volume, &Options::audio.sound.volume,
0,
100,
5)); 5));
// SETTINGS // SETTINGS