neteja tidy a source/core/system i audio amb fixes d'arrel

This commit is contained in:
2026-05-14 21:02:43 +02:00
parent dc622c7bae
commit 0ee117135c
11 changed files with 145 additions and 115 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
// 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.c"
#include "external/stb_vorbis.h"
// 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 altres headers.
#undef L
+3 -2
View File
@@ -1,5 +1,6 @@
#pragma once
#include <cmath> // Para lround
#include <cstdint> // Para int8_t, uint8_t
#include <string> // Para string
#include <utility> // Para move
@@ -59,8 +60,8 @@ class Audio {
// --- 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 auto toPercent(float volume) -> int {
return static_cast<int>(std::lround(volume * 100.0F));
}
static constexpr auto fromPercent(int percent) -> float {
return static_cast<float>(percent) / 100.0F;
+85 -62
View File
@@ -12,7 +12,7 @@
#include <vector> // Para std::vector
#define STB_VORBIS_HEADER_ONLY
#include "external/stb_vorbis.c" // Para stb_vorbis_open_memory i streaming
#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<Uint8[], SDLFreeDeleter>` — zero size
@@ -26,14 +26,14 @@ struct SDLFreeDeleter {
};
// --- Public Enums ---
enum JA_Channel_state {
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 {
enum JA_Music_state : std::uint8_t {
JA_MUSIC_INVALID,
JA_MUSIC_PLAYING,
JA_MUSIC_PAUSED,
@@ -42,7 +42,7 @@ enum JA_Music_state {
};
// --- Struct Definitions ---
enum {
enum : std::uint8_t {
JA_MAX_SIMULTANEOUS_CHANNELS = 20,
JA_MAX_GROUPS = 2
};
@@ -57,10 +57,10 @@ struct JA_Sound_t {
struct JA_Channel_t {
JA_Sound_t* sound{nullptr};
SDL_AudioStream* stream{nullptr};
int pos{0};
int times{0};
int group{0};
SDL_AudioStream* stream{nullptr};
JA_Channel_state state{JA_CHANNEL_FREE};
};
@@ -199,63 +199,86 @@ inline void JA_PreFillOutgoing(JA_Music_t* music, int duration_ms) {
// --- Core Functions ---
// 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<Uint64>(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<float>(elapsed) / static_cast<float>(outgoing_music.fade.duration_ms);
SDL_SetAudioStreamGain(outgoing_music.stream, outgoing_music.fade.initial_volume * (1.0F - percent));
}
// Fade-in de la música entrant (segona meitat d'un crossfade).
inline void JA_UpdateIncomingFade() {
if (!incoming_fade.active) {
return;
}
const Uint64 elapsed = SDL_GetTicks() - incoming_fade.start_time;
if (elapsed >= static_cast<Uint64>(incoming_fade.duration_ms)) {
incoming_fade.active = false;
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
return;
}
const float percent = static_cast<float>(elapsed) / static_cast<float>(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();
}
}
// 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);
}
return;
}
if (static_cast<Uint32>(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() {
// --- Outgoing music fade-out (crossfade o fade-out a silencio) ---
if ((outgoing_music.stream != nullptr) && outgoing_music.fade.active) {
Uint64 now = SDL_GetTicks();
Uint64 elapsed = now - outgoing_music.fade.start_time;
if (elapsed >= (Uint64)outgoing_music.fade.duration_ms) {
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 != nullptr) && current_music->state == JA_MUSIC_PLAYING) {
// Fade-in (parte de un crossfade)
if (incoming_fade.active) {
Uint64 now = SDL_GetTicks();
Uint64 elapsed = now - incoming_fade.start_time;
if (elapsed >= (Uint64)incoming_fade.duration_ms) {
incoming_fade.active = false;
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
} else {
float percent = (float)elapsed / (float)incoming_fade.duration_ms;
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * percent);
}
}
// 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();
}
}
// --- Sound channels ---
if (JA_soundEnabled) {
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) {
if (channels[i].state == JA_CHANNEL_PLAYING) {
if (channels[i].times != 0) {
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) {
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer.get(), channels[i].sound->length);
if (channels[i].times > 0) {
channels[i].times--;
}
}
} else {
if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) {
JA_StopChannel(i);
}
}
}
}
}
JA_UpdateOutgoingFade();
JA_UpdateCurrentMusic();
JA_UpdateSoundChannels();
}
inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) {
@@ -586,7 +609,7 @@ inline void JA_EnableMusic(const bool value) {
inline auto JA_LoadSound(uint8_t* buffer, uint32_t size) -> JA_Sound_t* {
auto sound = std::make_unique<JA_Sound_t>();
Uint8* raw = nullptr;
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &raw, &sound->length)) {
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;
}
+23 -18
View File
@@ -1,9 +1,10 @@
#include "core/system/director.h"
#include <SDL3/SDL.h>
#include <errno.h> // for errno, EEXIST, EACCES, ENAMETOO...
#include <stdio.h> // for printf, perror
#include <string.h> // for strcmp
#include <cerrno> // for errno, EEXIST, EACCES, ENAMETOO...
#include <cstdio> // for printf, perror
#include <cstring> // for strcmp
#ifndef __EMSCRIPTEN__
#include <sys/stat.h> // for mkdir, stat, S_IRWXU
#include <unistd.h> // for getuid
@@ -40,7 +41,7 @@
// Constructor
Director::Director(int argc, const char *argv[]) {
std::cout << "Game start" << std::endl;
std::cout << "Game start" << '\n';
// Inicializa variables
section = new section_t();
section->name = SECTION_PROG_LOGO;
@@ -97,7 +98,7 @@ Director::Director(int argc, const char *argv[]) {
const std::string pack_path = executablePath + "resources.pack";
#endif
if (!ResourceHelper::initializeResourceSystem(pack_path, enable_fallback)) {
std::cerr << "Fatal: resource system init failed (missing resources.pack?)" << std::endl;
std::cerr << "Fatal: resource system init failed (missing resources.pack?)" << '\n';
exit(EXIT_FAILURE);
}
}
@@ -197,7 +198,7 @@ Director::~Director() {
ResourceHelper::shutdownResourceSystem();
std::cout << "\nBye!" << std::endl;
std::cout << "\nBye!" << '\n';
}
// Inicializa el objeto input
@@ -256,14 +257,14 @@ void Director::initJailAudio() {
}
// Arranca SDL y crea la ventana
bool Director::initSDL() {
auto Director::initSDL() -> bool {
// Indicador de éxito
bool success = true;
// Inicializa SDL
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) {
if (Options::settings.console) {
std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl;
std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << '\n';
}
success = false;
} else {
@@ -278,18 +279,18 @@ bool Director::initSDL() {
0);
if (window == nullptr) {
if (Options::settings.console) {
std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << '\n';
}
success = false;
} else {
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
// Crea un renderizador para la ventana
renderer = SDL_CreateRenderer(window, NULL);
renderer = SDL_CreateRenderer(window, nullptr);
if (renderer == nullptr) {
if (Options::settings.console) {
std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << '\n';
}
success = false;
} else {
@@ -315,17 +316,17 @@ bool Director::initSDL() {
}
if (Options::settings.console) {
std::cout << std::endl;
std::cout << '\n';
}
return success;
}
// Crea el indice de ficheros
bool Director::setFileList() {
auto Director::setFileList() -> bool {
#ifdef MACOS_BUNDLE
const std::string prefix = "/../Resources";
#else
const std::string prefix = "";
const std::string prefix;
#endif
// Ficheros de configuración
@@ -488,7 +489,7 @@ void Director::createSystemFolder(const std::string &folder) {
// En Emscripten no necesitamos crear carpetas (MEMFS las crea automáticamente)
(void)folder;
#else
struct stat st = {0};
struct stat st = {.st_dev = 0};
if (stat(systemFolder.c_str(), &st) == -1) {
errno = 0;
#ifdef _WIN32
@@ -537,10 +538,14 @@ void Director::handleSectionTransition() {
case SECTION_PROG_GAME:
targetSection = ActiveSection::Game;
break;
default:
break;
}
// Si no ha cambiado, no hay nada que hacer
if (targetSection == activeSection) return;
if (targetSection == activeSection) {
return;
}
// Destruye la sección anterior
logo.reset();
@@ -571,7 +576,7 @@ void Director::handleSectionTransition() {
}
// Ejecuta un frame del juego
SDL_AppResult Director::iterate() {
auto Director::iterate() -> SDL_AppResult {
#ifdef __EMSCRIPTEN__
// En WASM no se puede salir: reinicia al logo
if (section->name == SECTION_PROG_QUIT) {
@@ -611,7 +616,7 @@ SDL_AppResult Director::iterate() {
}
// Procesa un evento
SDL_AppResult Director::handleEvent(SDL_Event *event) {
auto Director::handleEvent(SDL_Event *event) -> SDL_AppResult {
#ifndef __EMSCRIPTEN__
// Evento de salida de la aplicación
if (event->type == SDL_EVENT_QUIT) {
+10 -9
View File
@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <cstdint> // for uint8_t
#include <memory>
#include <string> // for string, basic_string
class Game;
@@ -11,7 +12,7 @@ class Title;
struct section_t;
// Secciones activas del Director
enum class ActiveSection { None,
enum class ActiveSection : std::uint8_t { None,
Logo,
Intro,
Title,
@@ -36,19 +37,19 @@ class Director {
std::string systemFolder; // Carpeta del sistema donde guardar datos
// Inicializa jail_audio
void initJailAudio();
static void initJailAudio();
// Arranca SDL y crea la ventana
bool initSDL();
auto initSDL() -> bool;
// Inicializa el objeto input
void initInput();
static void initInput();
// Crea el indice de ficheros
bool setFileList();
auto setFileList() -> bool;
// Comprueba los parametros del programa
void checkProgramArguments(int argc, const char *argv[]);
static void checkProgramArguments(int argc, const char *argv[]);
// Crea la carpeta del sistema donde guardar datos
void createSystemFolder(const std::string &folder);
@@ -64,11 +65,11 @@ class Director {
~Director();
Director(const Director &) = delete;
Director &operator=(const Director &) = delete;
auto operator=(const Director &) -> Director & = delete;
// Ejecuta un frame del juego
SDL_AppResult iterate();
auto iterate() -> SDL_AppResult;
// Procesa un evento
SDL_AppResult handleEvent(SDL_Event *event);
auto handleEvent(SDL_Event *event) -> SDL_AppResult;
};
+16 -16
View File
@@ -266,16 +266,16 @@ class Game {
void loadMedia();
// Carga el fichero de puntos
bool loadScoreFile();
auto loadScoreFile() -> bool;
// Carga el fichero de datos para la demo
bool loadDemoFile();
auto loadDemoFile() -> bool;
// Guarda el fichero de puntos
bool saveScoreFile();
auto saveScoreFile() -> bool;
// Guarda el fichero de datos para la demo
bool saveDemoFile();
auto saveDemoFile() -> bool;
// Inicializa las formaciones enemigas
void initEnemyFormations();
@@ -299,7 +299,7 @@ class Game {
void updateHiScore();
// Transforma un valor numérico en una cadena de 6 cifras
std::string updateScoreText(Uint32 num);
auto updateScoreText(Uint32 num) -> std::string;
// Pinta el marcador en pantalla usando un objeto texto
void renderScoreBoard();
@@ -326,7 +326,7 @@ class Game {
void renderBalloons();
// Crea un globo nuevo en el vector de globos
Uint8 createBalloon(float x, int y, Uint8 kind, float velx, float speed, Uint16 stoppedcounter);
auto createBalloon(float x, int y, Uint8 kind, float velx, float speed, Uint16 stoppedcounter) -> Uint8;
// Crea una PowerBall
void createPowerBall();
@@ -359,7 +359,7 @@ class Game {
void freeBalloons();
// Comprueba la colisión entre el jugador y los globos activos
bool checkPlayerBalloonCollision(Player *player);
auto checkPlayerBalloonCollision(Player *player) -> bool;
// Comprueba la colisión entre el jugador y los items
void checkPlayerItemCollision(Player *player);
@@ -386,7 +386,7 @@ class Game {
void renderItems();
// Devuelve un item en función del azar
Uint8 dropItem();
auto dropItem() -> Uint8;
// Crea un objeto item
void createItem(Uint8 kind, float x, float y);
@@ -422,13 +422,13 @@ class Game {
void evaluateAndSetMenace();
// Obtiene el valor de la variable
Uint8 getMenace();
auto getMenace() -> Uint8;
// Establece el valor de la variable
void setTimeStopped(bool value);
// Obtiene el valor de la variable
bool isTimeStopped();
auto isTimeStopped() -> bool;
// Establece el valor de la variable
void setTimeStoppedCounter(Uint16 value);
@@ -470,7 +470,7 @@ class Game {
void updateDeathShake();
// Indica si el efecto de agitación intensa está activo
bool isDeathShaking();
auto isDeathShaking() -> bool;
// Actualiza la secuencia de muerte del jugador
void updateDeathSequence();
@@ -494,10 +494,10 @@ class Game {
void enterGameOverScreen();
// Indica si se puede crear una powerball
bool canPowerBallBeCreated();
auto canPowerBallBeCreated() -> bool;
// Calcula el poder actual de los globos en pantalla
int calculateScreenPower();
auto calculateScreenPower() -> int;
// Inicializa las variables que contienen puntos de ruta para mover objetos
void initPaths();
@@ -509,7 +509,7 @@ class Game {
void updateHelper();
// Comprueba si todos los jugadores han muerto
bool allPlayersAreDead();
auto allPlayersAreDead() -> bool;
// Elimina todos los objetos contenidos en vectores
void deleteAllVectorObjects();
@@ -525,7 +525,7 @@ class Game {
~Game();
Game(const Game &) = delete;
Game &operator=(const Game &) = delete;
auto operator=(const Game &) -> Game & = delete;
// Bucle para el juego
void run();
@@ -534,7 +534,7 @@ class Game {
void iterate();
// Indica si el juego ha terminado
bool hasFinished() const;
[[nodiscard]] auto hasFinished() const -> bool;
// Procesa un evento
void handleEvent(const SDL_Event *event);
+3 -3
View File
@@ -46,7 +46,7 @@ class Instructions {
~Instructions();
Instructions(const Instructions &) = delete;
Instructions &operator=(const Instructions &) = delete;
auto operator=(const Instructions &) -> Instructions & = delete;
// Bucle principal
void run(mode_e mode);
@@ -64,8 +64,8 @@ class Instructions {
void checkEvents();
// Indica si las instrucciones han terminado
bool hasFinished() const;
[[nodiscard]] auto hasFinished() const -> bool;
// Indica si se ha solicitado salir de la aplicación
bool isQuitRequested() const;
[[nodiscard]] auto isQuitRequested() const -> bool;
};
+1 -1
View File
@@ -48,7 +48,7 @@ class Intro {
~Intro();
Intro(const Intro &) = delete;
Intro &operator=(const Intro &) = delete;
auto operator=(const Intro &) -> Intro & = delete;
// Bucle principal
void run();
+1 -1
View File
@@ -43,7 +43,7 @@ class Logo {
~Logo();
Logo(const Logo &) = delete;
Logo &operator=(const Logo &) = delete;
auto operator=(const Logo &) -> Logo & = delete;
// Bucle principal
void run();
+2 -2
View File
@@ -129,7 +129,7 @@ class Title {
void runDemoGame();
// Modifica las opciones para los controles de los jugadores
bool updatePlayerInputs(int numPlayer);
auto updatePlayerInputs(int numPlayer) -> bool;
// Crea el mosaico de fondo del titulo
void createTiledBackground();
@@ -148,7 +148,7 @@ class Title {
~Title();
Title(const Title &) = delete;
Title &operator=(const Title &) = delete;
auto operator=(const Title &) -> Title & = delete;
// Bucle para el titulo del juego
void run();