Auditoria de core/audio: neteja general aplicada (rama audit/core-audio)

17 ítems DONE (HC-01..HC-05 exc. HC-04 WONTFIX, DUP-01..DUP-05, PATCH-01/02/04,
INCONS-01/02/03/05-parcial, COUP-03, DEAD-01..05) + 1 parcial (INCONS-05).

Detalls i resolucions: docs/audit-core-audio.md.
This commit is contained in:
JailDesigner committed 2026-04-20 13:37:45 +02:00
commit 17301aff66
5 files changed
+347 -270

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
# Auditoría `core/audio/`
Rama: `audit/core-audio`
Estado global: neteja aplicada. 17 ítems `DONE` (+1 parcial), 4 `WONTFIX`, 4 `DEFERRED`. Els `DEFERRED` apunten al refactor pendent d'encapsular `Ja::*` en classe (COUP-01), que arrossegarà COUP-02, PATCH-03, PATCH-05, CONV-02, CONV-03 i la meitat restant d'INCONS-05.
Estados posibles:
- `PENDING` — identificado, sin tocar
- `WIP` — diff propuesto, pendiente de aplicar
- `DONE` — aplicado y commiteado
- `WONTFIX` — se decide no tocar (justificación en la nota)
- `DEFERRED` — se hará en fase posterior
Los IDs se mantienen estables una vez asignados. Cada entrada apunta al(los) fichero(s) y líneas afectados en la rama de origen (`main` en el momento del clone).
---
## Top 5 prioritario
| ID | Título | Estado |
|----|--------|--------|
| COUP-01 | Encapsular globals `Ja::` en clase (engine sin estado global) | DEFERRED |
| HC-02 | Formato/canales/freq hardcodeado en 3+ sitios | DONE |
| INCONS-02 | Doble `MusicState` + `getRealMusicState` como cache paralelo | DONE |
| HC-01 | Parámetros de audio no configurables (viola convención del proyecto) | DONE |
| PATCH-01 | `toggleEnabled/toggleSound/toggleMusic` no reaplican volumen | DONE |
---
## 1. Duplicación
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|----|---------------|----------|-----------|--------|-------|
| DUP-01 | `audio.cpp:58-101` | Dos `playMusic` (nombre/puntero) repiten el bloque crossfade/stop+play | Menor | DONE | `playMusicInternal(Ja::Music*, loop, crossfade_ms)` privat; els dos overloads fan gating + name-handling i deleguen el dispatch |
| DUP-02 | `audio.cpp:172-185` | `setSoundVolume`/`setMusicVolume` idéntica estructura de clamp+gate+producto | Menor | DONE | `effectiveVolume(volume, channel_enabled)` privat; els dos setters deleguen |
| DUP-03 | `jail_audio.hpp:196-222` | `updateOutgoingFade`/`updateIncomingFade` comparten la curva lineal | Menor | DONE | `fadeProgress(FadeState&)` concentra la corba en un sol lloc; també cobreix `duration_ms <= 0` com a 1.0 |
| DUP-04 | `jail_audio.hpp:391-471` | `fadeOutMusic`/`crossfadeMusic` repiten "destruir outgoing + pre-fill + robar stream + reset vorbis" | Importante | DONE | `stealCurrentIntoOutgoing(duration_ms)` concentra el robatori; `fadeOutMusic` i `crossfadeMusic` hi deleguen. També homogeneïtza `outgoing.fade.active = false` en la destrucció (abans era inconsistent) |
| DUP-05 | `jail_audio.hpp:575-631` | `pauseChannel`/`resumeChannel`/`stopChannel` repiten dispatch `channel==-1 / [0,MAX)` | Menor | DONE | Template `forEachTargetChannel(channel, fn)`; cada funció només descriu què fa per canal |
## 2. Hardcoding
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|----|---------------|----------|-----------|--------|-------|
| HC-01 | `audio.hpp:53-55` | `VOLUME_STEP`, `FREQUENCY`, `DEFAULT_CROSSFADE_MS` viven en la clase, no en `Defaults::Audio` | Importante | DONE | Movidos a `Defaults::Audio::{VOLUME_STEP, FREQUENCY, CROSSFADE_MS}` en `utils/defaults.hpp` |
| HC-02 | `jail_audio.hpp:57,76,110` + `audio.cpp:208` | `{SDL_AUDIO_S16, 2, 48000}` literal en 3 sitios; `S16LE` vs `S16` inconsistente | Importante | DONE | `Defaults::Audio::{FORMAT,CHANNELS,FREQUENCY}` al call-site d'`Audio::initSDLAudio`; `Ja::DEFAULT_SPEC` com a fallback local perquè l'engine no depengui del projecte |
| HC-03 | `jail_audio.hpp:53-54` | `MAX_SIMULTANEOUS_CHANNELS=20`, `MAX_GROUPS=2` fijos sin justificación | Menor | DONE | `static_assert` a `audio.cpp` protegeix `Audio::Group ↔ Ja::MAX_GROUPS`. No es mou a Defaults — el motor és independent del projecte per disseny |
| HC-04 | `jail_audio.hpp:131,134` | `MUSIC_CHUNK_SHORTS=8192`, `MUSIC_LOW_WATER_SECONDS=0.5F` tuning hidden | Menor | WONTFIX | Són tuning intern del streaming (vorbis + SDL stream). Ja són `inline constexpr` amb comentaris; moure-les a Defaults trencaria la independència del motor (mateix criteri que HC-03) |
| HC-05 | `jail_audio.hpp:266` | `fill(sound_volume, 0.5F)` valor mágico que se sobreescribe enseguida | Menor | DONE | Eliminada la línia. El `inline float[]` zero-inicialitza i `Audio::initSDLAudio` sobreescriu immediatament via `enable()` |
## 3. Parches e improvisaciones
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|----|---------------|----------|-----------|--------|-------|
| PATCH-01 | `audio.hpp:98,105,111` | `toggleEnabled/toggleSound/toggleMusic` sólo flipan el flag; no reaplican volúmenes | Importante | DONE | `toggle*()` i variants sense bool passen per `enable[Sound|Music](bool)`. Aquestes funcions ara reapliquen el volum cap a `Ja::` perquè els canals actius responguin al toggle |
| PATCH-02 | `audio.cpp:98` | `music_.name.clear()` en overload por puntero → `getCurrentMusicName()` miente | Importante | DONE | `music_.name = music->filename`. Si `Ja::Music` va ser carregat amb filename, `getCurrentMusicName()` ara retorna el nom correcte; si no, queda buit (contracte explícit) |
| PATCH-03 | `audio.cpp:14-21` | `#undef L,C,R,PLAYBACK_*` workaround para macros filtradas por stb_vorbis | Importante | WONTFIX | Documentado; aislar stb_vorbis en TU propio sería más invasivo — se hará con COUP-01 |
| PATCH-04 | `jail_audio.hpp:501-507` | `setMusicPosition`/`getMusicPosition` stubs no-op | Importante | DONE | Eliminats (veure DEAD-01) |
| PATCH-05 | `jail_audio.hpp:532-535` | "No hay canal libre → canal 0" robo de voz arbitrario | Importante | DEFERRED | Política de voice-stealing es una feature; se documenta |
## 4. Acoplamiento innecesario
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|----|---------------|----------|-----------|--------|-------|
| COUP-01 | `jail_audio.hpp:107-118` | `inline` globals en namespace: estado del engine sin encapsulación | Crítico | DEFERRED | Refactor grande; se hará en fase posterior |
| COUP-02 | `jail_audio.hpp` entero | Engine header-only: 678 líneas compilándose en cada TU | Importante | DEFERRED | Depende de COUP-01 |
| COUP-03 | `audio.cpp:3`, `jail_audio.hpp:8` | `#include <iostream>` sólo para `std::cout` de errores | Importante | DONE | `<iostream>` → `<cstdio>` als dos fitxers. `jail_audio.hpp` ja no arrossega iostream a cada TU que l'inclou. Missatges prefixats `Ja::` / `Audio:` (convenció `Module: message` del projecte) |
| COUP-04 | `audio.cpp:43-45`, `208` | `Ja::init` en `initSDLAudio` vs `Ja::quit` en destructor (asimetría) | Menor | WONTFIX | Convive con singleton; se revisará con COUP-01 |
| COUP-05 | `audio.hpp:71,79` | API pública expone `Ja::Music*`/`Ja::Sound*` | Menor | WONTFIX | Forward-declare ya mitiga; evita doble lookup por nombre |
## 5. Inconsistencias internas
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|----|---------------|----------|-----------|--------|-------|
| INCONS-01 | `audio.cpp:128,135,142,149,172,180` | Métodos `const` que mutan globals de `Ja` (const miente) | Importante | DONE | `const` tret de 6 mètodes (`playSound` x2, `stopAllSounds`, `fadeOutMusic`, `setSoundVolume`, `setMusicVolume`). `effectiveVolume` es queda `const` (pur sobre membres). Cap caller extern via `const Audio*` |
| INCONS-02 | `audio.hpp:44-48` + `jail_audio.hpp:44-50` | Dos `MusicState` paralelos + `getRealMusicState` como puente | Importante | DONE | Eliminat `music_.state` (cache) i `getRealMusicState`. `getMusicState()` consulta Ja::* directament i projecta INVALID/DISABLED → STOPPED. La font de veritat passa a ser Ja. `update()` ja no necessita sincronització |
| INCONS-03 | `audio.cpp` varios | Comentarios mezclan catalán y español | Menor | DONE | Tots els comentaris en castellà passats a català (incloent-hi els dels enums, constants, `--- Secció ---`, includes `Per a ...`, variables membre). Resta els comentaris en català que ja hi eren |
| INCONS-04 | `audio.hpp:70,78` | `playMusic` `loop=-1` vs `playSound` `loop=0` | Menor | WONTFIX | Convención razonable (música loop / sonido one-shot); documentar |
| INCONS-05 | `audio.hpp:102-105` | 4 formas de enable/disable sound (API redundante) + divergencia con `Ja::enableSound` | Importante | DONE (parcial) | 4 variants no-arg eliminades. La divergència amb `Ja::enableSound/enableMusic` es manté: Audio silencia per gain, Ja pararia els canals; la decisió es posposa a COUP-01 |
## 6. Código muerto
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|----|---------------|----------|-----------|--------|-------|
| DEAD-01 | `jail_audio.hpp:501-507` | `setMusicPosition`/`getMusicPosition` stubs | Importante | DONE | Eliminats; cap caller |
| DEAD-02 | `jail_audio.hpp:671-675` | `Ja::setVolume` master no usado desde `Audio` | Menor | DONE | Eliminat; Audio fa el gate per canal |
| DEAD-03 | `jail_audio.hpp:346-350` | `getMusicFilename` presunto unused | Menor | DONE | Eliminat; `Ja::Music::filename` s'accedeix directe (PATCH-02) |
| DEAD-04 | `jail_audio.hpp:633-638` | `getChannelState` sin getter público en `Audio` | Menor | DONE | Eliminat |
| DEAD-05 | `audio.hpp:102-103` | `enableSound()`/`disableSound()` sin argumento (duplican la versión con bool) | Menor | DONE | Eliminades (veure INCONS-05) |
## 7. Violaciones de convenciones
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|----|---------------|----------|-----------|--------|-------|
| CONV-01 | varios | Parámetros de audio fuera de `Defaults`/`config.yaml` | Importante | DONE (parcial) | Els que encaixen (`VOLUME_STEP`, `FREQUENCY`, `CROSSFADE_MS`, `FORMAT`, `CHANNELS`) ja són a `Defaults::Audio` (HC-01, HC-02). La resta són tuning intern del motor i resten a `jail_audio.hpp` per preservar-ne la independència |
| CONV-02 | `jail_audio.hpp` | Engine no es clase (namespace + globals), viola OOP | Importante | DEFERRED | Cubierto por COUP-01 |
| CONV-03 | `jail_audio.hpp` | No hay callback "music ended"; polling desde `update()` | Menor | DEFERRED | Requiere COUP-01 |
+92 -65
View File
@@ -1,9 +1,9 @@
#include "core/audio/audio.hpp"
#include <SDL3/SDL.h> // Para SDL_GetError, SDL_Init
#include <SDL3/SDL.h> // Per a SDL_GetError, SDL_Init
#include <algorithm> // Para clamp
#include <iostream> // Para std::cout
#include <algorithm> // Per a clamp
#include <cstdio> // Per a std::fprintf
// Implementació de stb_vorbis (ha d'anar ABANS d'incloure jail_audio.hpp).
// clang-format off
@@ -20,8 +20,15 @@
#undef PLAYBACK_RIGHT
// clang-format on
#include "core/audio/audio_adapter.hpp" // Para AudioResource::getMusic/getSound
#include "core/audio/jail_audio.hpp" // Para Ja::* (engine jailgames)
#include "core/audio/audio_adapter.hpp" // Per a AudioResource::getMusic/getSound
#include "core/audio/jail_audio.hpp" // Per a Ja::* (motor jailgames)
#include "utils/defaults.hpp" // Per a Defaults::Audio::FREQUENCY
// Invariant compile-time: tots els valors d'Audio::Group han de cabre als slots
// de volum per grup que manté l'engine. Si s'afegeix una nova entrada a Group
// i no s'incrementa Ja::MAX_GROUPS, aquest assert falla abans de compilar.
static_assert(static_cast<int>(Audio::Group::INTERFACE) < Ja::MAX_GROUPS,
"Audio::Group té més entrades que slots té Ja::MAX_GROUPS");
// Singleton
std::unique_ptr<Audio> Audio::instance;
@@ -29,10 +36,10 @@ std::unique_ptr<Audio> Audio::instance;
// Inicialitza la instància única del singleton amb la configuració rebuda
void Audio::init(const Config& config) { Audio::instance = std::unique_ptr<Audio>(new Audio(config)); }
// Libera la instancia
// Allibera la instància
void Audio::destroy() { Audio::instance.reset(); }
// Obtiene la instancia
// Obté la instància
auto Audio::get() -> Audio* { return Audio::instance.get(); }
// Constructor
@@ -44,22 +51,18 @@ Audio::~Audio() {
Ja::quit();
}
// Método principal
// Mètode principal: l'estat de la música el manté Ja (única font de veritat),
// per tant no cal cap sincronització aquí.
void Audio::update() {
Ja::update();
// Sincronitzar estat: detectar quan la música s'ha parat (p. ex. fade-out completat)
if (instance && instance->music_.state == MusicState::PLAYING && Ja::getMusicState() != Ja::MusicState::PLAYING) {
instance->music_.state = MusicState::STOPPED;
}
}
// Reproduce la música por nombre (con crossfade opcional)
// Reprodueix la música per nom (amb crossfade opcional)
void Audio::playMusic(const std::string& name, const int loop, const int crossfade_ms) {
const bool NEW_LOOP = (loop != 0);
// Si ja sona exactament la mateixa pista i mateix mode loop, no fem res
if (music_.state == MusicState::PLAYING && music_.name == name && music_.loop == NEW_LOOP) {
if (getMusicState() == MusicState::PLAYING && music_.name == name && music_.loop == NEW_LOOP) {
return;
}
@@ -68,94 +71,100 @@ void Audio::playMusic(const std::string& name, const int loop, const int crossfa
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);
}
playMusicInternal(resource, loop, crossfade_ms);
music_.name = name;
music_.loop = NEW_LOOP;
music_.state = MusicState::PLAYING;
}
// Reproduce la música por puntero (con crossfade opcional)
// Reprodueix la música per punter (amb crossfade opcional)
void Audio::playMusic(Ja::Music* music, const int loop, const int crossfade_ms) {
if (!music_enabled_ || music == nullptr) { return; }
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
playMusicInternal(music, loop, crossfade_ms);
// Si el Ja::Music es va crear amb filename (loadMusic amb 3 arguments), el
// recuperem perquè getCurrentMusicName() no menteixi. Si no, music_.name
// queda buit — el contracte d'aquest overload no garanteix el nom.
music_.name = music->filename;
}
// Camí comú dels dos overloads: fa el dispatch crossfade vs stop+play i
// actualitza el loop cachejat. Els callers s'encarreguen del gating
// (music_enabled_, nullptr, same-track early return) i del nom. L'estat el
// manté Ja (Ja::playMusic posa PLAYING al Ja::Music* corresponent).
void Audio::playMusicInternal(Ja::Music* music, const int loop, const int crossfade_ms) {
const bool CURRENTLY_PLAYING = (getMusicState() == MusicState::PLAYING);
if (crossfade_ms > 0 && CURRENTLY_PLAYING) {
Ja::crossfadeMusic(music, crossfade_ms, loop);
} else {
if (music_.state == MusicState::PLAYING) {
if (CURRENTLY_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 (l'estat el transiciona Ja::pauseMusic)
// NOLINTNEXTLINE(readability-make-member-function-const) — muta Ja::current_music->state
void Audio::pauseMusic() {
if (music_enabled_ && music_.state == MusicState::PLAYING) {
if (music_enabled_ && getMusicState() == MusicState::PLAYING) {
Ja::pauseMusic();
music_.state = MusicState::PAUSED;
}
}
// Continua la música pausada
// Continua la música pausada (l'estat el transiciona Ja::resumeMusic)
// NOLINTNEXTLINE(readability-make-member-function-const) — muta Ja::current_music->state
void Audio::resumeMusic() {
if (music_enabled_ && music_.state == MusicState::PAUSED) {
if (music_enabled_ && getMusicState() == MusicState::PAUSED) {
Ja::resumeMusic();
music_.state = MusicState::PLAYING;
}
}
// Detiene la música
// Detiene la música (l'estat el transiciona Ja::stopMusic)
// NOLINTNEXTLINE(readability-make-member-function-const) — muta Ja::current_music/outgoing_music
void Audio::stopMusic() {
if (music_enabled_) {
Ja::stopMusic();
music_.state = MusicState::STOPPED;
}
}
// Reproduce un sonido por nombre
void Audio::playSound(const std::string& name, Group group) const {
// Reprodueix un so per nom
// NOLINTNEXTLINE(readability-make-member-function-const) — muta globals de Ja::channels
void Audio::playSound(const std::string& name, Group group) {
if (sound_enabled_) {
Ja::playSound(AudioResource::getSound(name), 0, static_cast<int>(group));
}
}
// Reproduce un sonido por puntero directo
void Audio::playSound(Ja::Sound* sound, Group group) const {
// Reprodueix un so per punter directe
// NOLINTNEXTLINE(readability-make-member-function-const) — muta globals de Ja::channels
void Audio::playSound(Ja::Sound* sound, Group group) {
if (sound_enabled_ && sound != nullptr) {
Ja::playSound(sound, 0, static_cast<int>(group));
}
}
// Detiene todos los sonidos
void Audio::stopAllSounds() const {
// Atura tots els sons
// NOLINTNEXTLINE(readability-make-member-function-const) — muta globals de Ja::channels
void Audio::stopAllSounds() {
if (sound_enabled_) {
Ja::stopChannel(-1);
}
}
// Realiza un fundido de salida de la música
void Audio::fadeOutMusic(int milliseconds) const {
if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) {
// Fa una fosa de sortida de la música
// NOLINTNEXTLINE(readability-make-member-function-const) — muta Ja::outgoing_music/incoming_fade
void Audio::fadeOutMusic(int milliseconds) {
if (music_enabled_ && getMusicState() == MusicState::PLAYING) {
Ja::fadeOutMusic(milliseconds);
}
}
// Consulta directament l'estat real de la música a jail_audio
auto Audio::getRealMusicState() -> MusicState {
const Ja::MusicState JA_STATE = Ja::getMusicState();
switch (JA_STATE) {
// Consulta directament l'estat a Ja i el projecta al subconjunt d'estats que
// exposa Audio (INVALID/DISABLED de Ja col·lapsen a STOPPED — la capa d'usuari
// només vol saber si està sonant, pausat o parat).
auto Audio::getMusicState() -> MusicState {
switch (Ja::getMusicState()) {
case Ja::MusicState::PLAYING:
return MusicState::PLAYING;
case Ja::MusicState::PAUSED:
@@ -168,23 +177,27 @@ auto Audio::getRealMusicState() -> MusicState {
}
}
// Aplica el gate master (enabled_) + el gate del canal (sound/music_enabled_)
// i retorna el volum escalat pel master config_.volume. 0 si algun gate està
// tancat. Així els dos setters comparteixen la mateixa política.
auto Audio::effectiveVolume(float volume, bool channel_enabled) const -> float {
volume = std::clamp(volume, MIN_VOLUME, MAX_VOLUME);
return (enabled_ && channel_enabled) ? volume * config_.volume : 0.0F;
}
// Estableix el volum dels sons (float 0.0..1.0)
void Audio::setSoundVolume(float sound_volume, Group group) const {
sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME);
const bool ACTIVE = enabled_ && sound_enabled_;
const float CONVERTED_VOLUME = ACTIVE ? sound_volume * config_.volume : 0.0F;
Ja::setSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
// NOLINTNEXTLINE(readability-make-member-function-const) — muta Ja::sound_volume[] i canals actius
void Audio::setSoundVolume(float sound_volume, Group group) {
Ja::setSoundVolume(effectiveVolume(sound_volume, sound_enabled_), static_cast<int>(group));
}
// Estableix el volum de la música (float 0.0..1.0)
void Audio::setMusicVolume(float music_volume) const {
music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME);
const bool ACTIVE = enabled_ && music_enabled_;
const float CONVERTED_VOLUME = ACTIVE ? music_volume * config_.volume : 0.0F;
Ja::setMusicVolume(CONVERTED_VOLUME);
// NOLINTNEXTLINE(readability-make-member-function-const) — muta Ja::music_volume i stream actual
void Audio::setMusicVolume(float music_volume) {
Ja::setMusicVolume(effectiveVolume(music_volume, music_enabled_));
}
// Aplica una nova configuració (substitueix la cache i reaplica enables/volums)
// Aplica una nova configuració (substitueix la config cachejada i reaplica enables/volums)
void Audio::applySettings(const Config& config) {
config_ = config;
sound_enabled_ = config_.sound_enabled;
@@ -200,12 +213,26 @@ void Audio::enable(bool value) {
setMusicVolume(enabled_ ? config_.music_volume : MIN_VOLUME);
}
// Estableix l'estat dels sons i reaplica el volum perquè els canals actius
// responguin a l'instant (evita que el toggle només surti efecte al pròxim
// setSoundVolume explícit).
void Audio::enableSound(bool value) {
sound_enabled_ = value;
setSoundVolume(config_.sound_volume);
}
// Estableix l'estat de la música i reaplica el volum per la mateixa raó.
void Audio::enableMusic(bool value) {
music_enabled_ = value;
setMusicVolume(config_.music_volume);
}
// Inicialitza SDL Audio
void Audio::initSDLAudio() {
if (!SDL_Init(SDL_INIT_AUDIO)) {
std::cout << "SDL_AUDIO could not initialize! SDL Error: " << SDL_GetError() << '\n';
std::fprintf(stderr, "Audio: SDL_AUDIO could not initialize! SDL Error: %s\n", SDL_GetError());
} else {
Ja::init(FREQUENCY, SDL_AUDIO_S16LE, 2);
Ja::init(Defaults::Audio::FREQUENCY, Defaults::Audio::FORMAT, Defaults::Audio::CHANNELS);
sound_enabled_ = config_.sound_enabled;
music_enabled_ = config_.music_enabled;
enable(config_.enabled);
+56 -63
View File
@@ -1,10 +1,10 @@
#pragma once
#include <cmath> // Para std::lround
#include <cstdint> // Para int8_t, uint8_t
#include <memory> // Para std::unique_ptr
#include <string> // Para string
#include <utility> // Para move
#include <cmath> // Per a std::lround
#include <cstdint> // Per a int8_t, uint8_t
#include <memory> // Per a std::unique_ptr
#include <string> // Per a string
#include <utility> // Per a move
// Forward-declares per no incloure core/audio/jail_audio.hpp al header (conté
// l'engine complet de streaming amb stb_vorbis).
@@ -13,7 +13,7 @@ namespace Ja {
struct Sound;
} // namespace Ja
// --- Clase Audio: gestor de audio (singleton) ---
// --- Classe Audio: gestor d'àudio (singleton) ---
// Port del subsistema d'àudio del projecte ../aee, desacoblat d'Options:
// la configuració entra per la struct Audio::Config a init()/applySettings(),
// en lloc de llegir directament Options::audio. Això deixa audio.cpp independent
@@ -36,52 +36,49 @@ class Audio {
// --- Enums ---
enum class Group : std::int8_t {
ALL = -1, // Todos los grupos
GAME = 0, // Sonidos del juego
INTERFACE = 1 // Sonidos de la interfaz
ALL = -1, // Tots els grups
GAME = 0, // Sons del joc
INTERFACE = 1 // Sons de la interfície
};
enum class MusicState : std::uint8_t {
PLAYING, // Reproduciendo música
PLAYING, // Reproduint música
PAUSED, // Música pausada
STOPPED, // Música detenida
STOPPED, // Música aturada
};
// --- Constantes ---
static constexpr float MAX_VOLUME = 1.0F; // Volumen máximo (float 0..1)
static constexpr float MIN_VOLUME = 0.0F; // Volumen mínimo (float 0..1)
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)
// --- Constants ---
static constexpr float MAX_VOLUME = 1.0F; // Volum màxim (float 0..1)
static constexpr float MIN_VOLUME = 0.0F; // Volum mínim (float 0..1)
// --- Singleton ---
static void init(const Config& config); // Inicialitza amb la configuració rebuda
static void destroy(); // Libera el objeto Audio
static auto get() -> Audio*; // Obtiene el puntero al objeto Audio
static void destroy(); // Allibera l'objecte Audio
static auto get() -> Audio*; // Obté el punter a l'objecte Audio
~Audio(); // Destructor (públic per a std::unique_ptr)
Audio(const Audio&) = delete; // Evitar copia
Audio(const Audio&) = delete; // Evitar còpia
Audio(Audio&&) = delete;
auto operator=(const Audio&) -> Audio& = delete; // Evitar asignación
auto operator=(const Audio&) -> Audio& = delete; // Evitar assignació
auto operator=(Audio&&) -> Audio& = delete;
static void update(); // Actualización del sistema de audio
static void update(); // Actualització del sistema d'àudio
// --- Control de música ---
void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional)
void playMusic(Ja::Music* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional)
void pauseMusic(); // Pausar reproducción de música
void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproduir música per nom (amb crossfade opcional)
void playMusic(Ja::Music* music, int loop = -1, int crossfade_ms = 0); // Reproduir música per punter (amb crossfade opcional)
void pauseMusic(); // Pausar la reproducció de música
void resumeMusic(); // Continua la música pausada
void stopMusic(); // Detener completamente la música
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
void stopMusic(); // Aturar completament la música
void fadeOutMusic(int milliseconds); // Fosa de sortida de la música (muta globals de Ja)
// --- Control de sonidos ---
void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre
void playSound(Ja::Sound* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero
void stopAllSounds() const; // Detener todos los sonidos
// --- Control de sons ---
void playSound(const std::string& name, Group group = Group::GAME); // Reproduir so puntual per nom (muta globals de Ja)
void playSound(Ja::Sound* sound, Group group = Group::GAME); // Reproduir so puntual per punter (muta globals de Ja)
void stopAllSounds(); // Aturar tots els sons (muta globals de Ja)
// --- 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
// --- Control de volum (API interna: float 0.0..1.0) ---
void setSoundVolume(float volume, Group group = Group::ALL); // Ajusta el volum dels efectes
void setMusicVolume(float volume); // Ajusta el volum de la música
// --- Helpers de conversió per a la capa de presentació ---
// UI (menús, notificacions) manega enters 0..100; internament viu float 0..1.
@@ -93,49 +90,45 @@ class Audio {
return static_cast<float>(percent) / 100.0F;
}
// --- Configuración general ---
void enable(bool value); // Establecer estado general
void toggleEnabled() { enabled_ = !enabled_; } // Alternar estado general
void applySettings(const Config& config); // Aplica una nova configuració
// --- Configuració general ---
void enable(bool value); // Estableix l'estat general (reaplica volums)
void toggleEnabled() { enable(!enabled_); } // Alterna l'estat general (reaplica volums)
void applySettings(const Config& config); // Aplica una nova configuració
// --- Configuración de sonidos ---
void enableSound() { sound_enabled_ = true; } // Habilitar sonidos
void disableSound() { sound_enabled_ = false; } // Deshabilitar sonidos
void enableSound(bool value) { sound_enabled_ = value; } // Establecer estado de sonidos
void toggleSound() { sound_enabled_ = !sound_enabled_; } // Alternar estado de sonidos
// --- Configuració de sons ---
void enableSound(bool value); // Estableix l'estat dels sons (reaplica volum)
void toggleSound() { enableSound(!sound_enabled_); } // Alterna l'estat dels sons (reaplica volum)
// --- Configuración de música ---
void enableMusic() { music_enabled_ = true; } // Habilitar música
void disableMusic() { music_enabled_ = false; } // Deshabilitar 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
// --- Configuració de música ---
void enableMusic(bool value); // Estableix l'estat de la música (reaplica volum)
void toggleMusic() { enableMusic(!music_enabled_); } // Alterna l'estat de la música (reaplica volum)
// --- Consultas de estado ---
// --- Consultes d'estat ---
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
[[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; }
[[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; }
[[nodiscard]] auto getMusicState() const -> MusicState { return music_.state; }
[[nodiscard]] static auto getRealMusicState() -> MusicState;
[[nodiscard]] static auto getMusicState() -> MusicState; // Estat real consultat a Ja::
[[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; }
private:
// --- Tipos anidados ---
// --- Tipus anidats ---
struct Music {
MusicState state{MusicState::STOPPED}; // Estado actual de la música
std::string name; // Última pista de música reproducida
bool loop{false}; // Indica si se reproduce en bucle
std::string name; // Última pista de música reproduïda (buida si es va passar per punter sense filename)
bool loop{false}; // Si el play actual és en bucle
};
// --- Métodos ---
explicit Audio(const Config& config); // Constructor privat: rep la config
void initSDLAudio(); // Inicializa SDL Audio
// --- Mètodes ---
explicit Audio(const Config& config); // Constructor privat: rep la config
void initSDLAudio(); // Inicialitza SDL Audio
void playMusicInternal(Ja::Music* music, int loop, int crossfade_ms); // Camí comú dels dos overloads de playMusic
[[nodiscard]] auto effectiveVolume(float volume, bool channel_enabled) const -> float; // Gate master+channel: 0 si algun està off, clamp 0..1 altrament
// --- Variables miembro ---
// --- Variables membre ---
static std::unique_ptr<Audio> instance; // Instància única d'Audio
Config config_{}; // Configuració injectada (volums, enables)
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
Music music_; // Estat de la música
bool enabled_{true}; // Estat general de l'àudio
bool sound_enabled_{true}; // Estat dels efectes de so
bool music_enabled_{true}; // Estat de la música
};
+99 -142
View File
@@ -5,7 +5,7 @@
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <cstdio>
#include <iterator>
#include <memory>
#include <string>
@@ -53,8 +53,13 @@ namespace Ja {
inline constexpr int MAX_SIMULTANEOUS_CHANNELS = 20;
inline constexpr int MAX_GROUPS = 2;
// Spec de fallback del dispositiu. S'aplica abans de Ja::init() i com a valor
// inicial de Sound/Music. L'spec real d'ús l'imposa Ja::init(), que la capa
// Audio alimenta des de Defaults::Audio — única font de veritat del projecte.
inline constexpr SDL_AudioSpec DEFAULT_SPEC{SDL_AUDIO_S16, 2, 48000};
struct Sound {
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
SDL_AudioSpec spec{DEFAULT_SPEC};
Uint32 length{0};
// Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV
// via SDL_malloc; el deleter `SdlFreeDeleter` allibera amb SDL_free.
@@ -73,7 +78,7 @@ namespace Ja {
};
struct Music {
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
SDL_AudioSpec spec{DEFAULT_SPEC};
// OGG comprimit en memòria. Propietat nostra; es copia des del buffer
// d'entrada una sola vegada en loadMusic i es descomprimix en chunks
@@ -107,7 +112,7 @@ namespace Ja {
inline Music* current_music{nullptr};
inline Channel channels[MAX_SIMULTANEOUS_CHANNELS];
inline SDL_AudioSpec audio_spec{SDL_AUDIO_S16, 2, 48000};
inline SDL_AudioSpec audio_spec{DEFAULT_SPEC};
inline float music_volume{1.0F};
inline float sound_volume[MAX_GROUPS];
inline bool music_enabled{true};
@@ -192,18 +197,27 @@ namespace Ja {
// --- Core Functions ---
// Retorna el progrés lineal [0..1] d'un fade. 1.0 vol dir completat. Única
// font de la corba del fade: si es vol canviar a logarítmica/quadràtica,
// s'edita aquí i afecta fade-in i fade-out alhora.
inline auto fadeProgress(const FadeState& fade) -> float {
if (fade.duration_ms <= 0) { return 1.0F; }
const Uint64 ELAPSED = SDL_GetTicks() - fade.start_time;
if (ELAPSED >= static_cast<Uint64>(fade.duration_ms)) { return 1.0F; }
return static_cast<float>(ELAPSED) / static_cast<float>(fade.duration_ms);
}
// Avança el fade-out de l'outgoing music (crossfade o fade-out a silenci).
inline void 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)) {
const float PROGRESS = fadeProgress(outgoing_music.fade);
if (PROGRESS >= 1.0F) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
outgoing_music.fade.active = false;
} else {
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));
SDL_SetAudioStreamGain(outgoing_music.stream, outgoing_music.fade.initial_volume * (1.0F - PROGRESS));
}
}
@@ -211,13 +225,12 @@ namespace Ja {
inline void updateIncomingFade() {
if (!incoming_fade.active) { return; }
const Uint64 ELAPSED = SDL_GetTicks() - incoming_fade.start_time;
if (ELAPSED >= static_cast<Uint64>(incoming_fade.duration_ms)) {
const float PROGRESS = fadeProgress(incoming_fade);
if (PROGRESS >= 1.0F) {
incoming_fade.active = false;
SDL_SetAudioStreamGain(current_music->stream, music_volume);
} else {
const float PERCENT = static_cast<float>(ELAPSED) / static_cast<float>(incoming_fade.duration_ms);
SDL_SetAudioStreamGain(current_music->stream, music_volume * PERCENT);
SDL_SetAudioStreamGain(current_music->stream, music_volume * PROGRESS);
}
}
@@ -261,9 +274,8 @@ namespace Ja {
audio_spec = {.format = format, .channels = num_channels, .freq = freq};
if (sdl_audio_device != 0) { SDL_CloseAudioDevice(sdl_audio_device); }
sdl_audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec);
if (sdl_audio_device == 0) { std::cout << "Failed to initialize SDL audio!" << '\n'; }
if (sdl_audio_device == 0) { std::fprintf(stderr, "Ja: Failed to initialize SDL audio!\n"); }
for (auto& channel : channels) { channel.state = ChannelState::FREE; }
std::ranges::fill(sound_volume, 0.5F);
}
inline void quit() {
@@ -293,7 +305,7 @@ namespace Ja {
&vorbis_error,
nullptr);
if (music->vorbis == nullptr) {
std::cout << "loadMusic: stb_vorbis_open_memory failed (error " << vorbis_error << ")" << '\n';
std::fprintf(stderr, "Ja::loadMusic: stb_vorbis_open_memory failed (error %d)\n", vorbis_error);
return nullptr;
}
@@ -306,8 +318,9 @@ namespace Ja {
return music.release();
}
// Overload amb filename — els callers l'usen per poder comparar la música
// en curs amb getMusicFilename() i no rearrancar-la si ja és la mateixa.
// Overload amb filename. Resource::Cache l'usa per registrar el path dins
// del propi Ja::Music (camp `filename`); la capa Audio l'usa per recuperar
// el nom després d'un playMusic(Ja::Music*, ...) — veure PATCH-02.
inline auto loadMusic(const Uint8* buffer, Uint32 length, const char* filename) -> Music* {
Music* music = loadMusic(buffer, length);
if (music != nullptr && filename != nullptr) { music->filename = filename; }
@@ -329,7 +342,7 @@ namespace Ja {
current_music->stream = SDL_CreateAudioStream(&current_music->spec, &audio_spec);
if (current_music->stream == nullptr) {
std::cout << "Failed to create audio stream!" << '\n';
std::fprintf(stderr, "Ja::playMusic: Failed to create audio stream!\n");
current_music->state = MusicState::STOPPED;
return;
}
@@ -339,16 +352,10 @@ namespace Ja {
pumpMusic(current_music);
if (!SDL_BindAudioStream(sdl_audio_device, current_music->stream)) {
std::cout << "[ERROR] SDL_BindAudioStream failed!" << '\n';
std::fprintf(stderr, "Ja::playMusic: SDL_BindAudioStream failed!\n");
}
}
inline auto getMusicFilename(const Music* music = nullptr) -> const char* {
if (music == nullptr) { music = current_music; }
if (music == nullptr || music->filename.empty()) { return nullptr; }
return music->filename.c_str();
}
inline void pauseMusic() {
if (!music_enabled) { return; }
if (current_music == nullptr || current_music->state != MusicState::PLAYING) { return; }
@@ -388,63 +395,48 @@ namespace Ja {
}
}
inline void fadeOutMusic(const int milliseconds) {
if (!music_enabled) { return; }
if (current_music == nullptr || current_music->state != MusicState::PLAYING) { return; }
// Destruir outgoing anterior si existe
if (outgoing_music.stream != nullptr) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
}
// Pre-omplim l'stream amb `milliseconds` de so: un cop robat, ja no
// tindrà accés al vorbis decoder i només podrà drenar el que tinga.
preFillOutgoing(current_music, milliseconds);
// Robar el stream del current_music al outgoing
outgoing_music.stream = current_music->stream;
outgoing_music.fade = {
.active = true,
.start_time = SDL_GetTicks(),
.duration_ms = milliseconds,
.initial_volume = music_volume,
};
// Dejar current_music sin stream (ya lo tiene outgoing)
current_music->stream = nullptr;
current_music->state = MusicState::STOPPED;
if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); }
incoming_fade.active = false;
}
inline void crossfadeMusic(Music* music, const int crossfade_ms, const int loop) {
if (!music_enabled || music == nullptr || music->vorbis == nullptr) { return; }
// Destruir outgoing anterior si existe (crossfade durante crossfade)
// Roba l'stream de la música actual al slot outgoing per a un fade-out de
// `duration_ms` ms. Destrueix qualsevol outgoing anterior. L'stream robat
// ja no es pot alimentar (perd l'accés al vorbis decoder), per això cal
// preFillOutgoing abans del robatori perquè tingui so suficient fins que
// acabi el fade. No-op si no hi ha música sonant amb stream vàlid.
inline void stealCurrentIntoOutgoing(const int duration_ms) {
if (outgoing_music.stream != nullptr) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
outgoing_music.fade.active = false;
}
// Robar el stream de la musica actual al outgoing para el fade-out.
// Pre-omplim amb `crossfade_ms` de so perquè no es quede en silenci
// abans d'acabar el fade (l'stream robat ja no pot alimentar-se).
if (current_music != nullptr && current_music->state == MusicState::PLAYING && current_music->stream != nullptr) {
preFillOutgoing(current_music, crossfade_ms);
outgoing_music.stream = current_music->stream;
outgoing_music.fade = {
.active = true,
.start_time = SDL_GetTicks(),
.duration_ms = crossfade_ms,
.initial_volume = music_volume,
};
current_music->stream = nullptr;
current_music->state = MusicState::STOPPED;
if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); }
if (current_music == nullptr || current_music->state != MusicState::PLAYING || current_music->stream == nullptr) {
return;
}
preFillOutgoing(current_music, duration_ms);
outgoing_music.stream = current_music->stream;
outgoing_music.fade = {
.active = true,
.start_time = SDL_GetTicks(),
.duration_ms = duration_ms,
.initial_volume = music_volume,
};
current_music->stream = nullptr;
current_music->state = MusicState::STOPPED;
if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); }
}
inline void fadeOutMusic(const int milliseconds) {
if (!music_enabled) { return; }
if (current_music == nullptr || current_music->state != MusicState::PLAYING) { return; }
stealCurrentIntoOutgoing(milliseconds);
incoming_fade.active = false;
}
inline void crossfadeMusic(Music* music, const int crossfade_ms, const int loop) {
if (!music_enabled || music == nullptr || music->vorbis == nullptr) { return; }
stealCurrentIntoOutgoing(crossfade_ms);
// Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente)
current_music = music;
current_music->state = MusicState::PLAYING;
@@ -453,7 +445,7 @@ namespace Ja {
stb_vorbis_seek_start(current_music->vorbis);
current_music->stream = SDL_CreateAudioStream(&current_music->spec, &audio_spec);
if (current_music->stream == nullptr) {
std::cout << "Failed to create audio stream for crossfade!" << '\n';
std::fprintf(stderr, "Ja::crossfadeMusic: Failed to create audio stream!\n");
current_music->state = MusicState::STOPPED;
return;
}
@@ -498,14 +490,6 @@ namespace Ja {
return music_volume;
}
inline void setMusicPosition(float /*value*/) {
// No implementat amb el backend de streaming.
}
inline auto getMusicPosition() -> float {
return 0.0F;
}
inline void enableMusic(const bool value) {
if (!value && current_music != nullptr && (current_music->state == MusicState::PLAYING)) { stopMusic(); }
music_enabled = value;
@@ -517,7 +501,7 @@ namespace Ja {
auto sound = std::make_unique<Sound>();
Uint8* raw = nullptr;
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), true, &sound->spec, &raw, &sound->length)) {
std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n';
std::fprintf(stderr, "Ja::loadSound: Failed to load WAV from memory: %s\n", SDL_GetError());
return nullptr;
}
sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
@@ -551,7 +535,7 @@ namespace Ja {
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &audio_spec);
if (channels[channel].stream == nullptr) {
std::cout << "Failed to create audio stream for sound!" << '\n';
std::fprintf(stderr, "Ja::playSoundOnChannel: Failed to create audio stream!\n");
channels[channel].state = ChannelState::FREE;
return -1;
}
@@ -572,69 +556,48 @@ namespace Ja {
delete sound;
}
// Aplica `fn` als canals objectiu: tots si channel == -1, o només el d'índex
// vàlid si està dins [0, MAX_SIMULTANEOUS_CHANNELS). Centralitza el dispatch
// "tots vs un" que pauseChannel/resumeChannel/stopChannel repetien.
template <typename Fn>
inline void forEachTargetChannel(const int channel, Fn&& fn) {
if (channel == -1) {
for (auto& ch : channels) { fn(ch); }
} else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) {
fn(channels[channel]);
}
}
inline void pauseChannel(const int channel) {
if (!sound_enabled) { return; }
if (channel == -1) {
for (auto& ch : channels) {
if (ch.state == ChannelState::PLAYING) {
ch.state = ChannelState::PAUSED;
SDL_UnbindAudioStream(ch.stream);
}
forEachTargetChannel(channel, [](Channel& ch) {
if (ch.state == ChannelState::PLAYING) {
ch.state = ChannelState::PAUSED;
SDL_UnbindAudioStream(ch.stream);
}
} else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) {
if (channels[channel].state == ChannelState::PLAYING) {
channels[channel].state = ChannelState::PAUSED;
SDL_UnbindAudioStream(channels[channel].stream);
}
}
});
}
inline void resumeChannel(const int channel) {
if (!sound_enabled) { return; }
if (channel == -1) {
for (auto& ch : channels) {
if (ch.state == ChannelState::PAUSED) {
ch.state = ChannelState::PLAYING;
SDL_BindAudioStream(sdl_audio_device, ch.stream);
}
forEachTargetChannel(channel, [](Channel& ch) {
if (ch.state == ChannelState::PAUSED) {
ch.state = ChannelState::PLAYING;
SDL_BindAudioStream(sdl_audio_device, ch.stream);
}
} else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) {
if (channels[channel].state == ChannelState::PAUSED) {
channels[channel].state = ChannelState::PLAYING;
SDL_BindAudioStream(sdl_audio_device, channels[channel].stream);
}
}
});
}
inline void stopChannel(const int channel) {
if (channel == -1) {
for (auto& ch : channels) {
if (ch.state != ChannelState::FREE) {
if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); }
ch.stream = nullptr;
ch.state = ChannelState::FREE;
ch.pos = 0;
ch.sound = nullptr;
}
forEachTargetChannel(channel, [](Channel& ch) {
if (ch.state != ChannelState::FREE) {
if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); }
ch.stream = nullptr;
ch.state = ChannelState::FREE;
ch.pos = 0;
ch.sound = nullptr;
}
} else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) {
if (channels[channel].state != ChannelState::FREE) {
if (channels[channel].stream != nullptr) { SDL_DestroyAudioStream(channels[channel].stream); }
channels[channel].stream = nullptr;
channels[channel].state = ChannelState::FREE;
channels[channel].pos = 0;
channels[channel].sound = nullptr;
}
}
}
inline auto getChannelState(const int channel) -> ChannelState {
if (!sound_enabled) { return ChannelState::SOUND_DISABLED; }
if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return ChannelState::INVALID; }
return channels[channel].state;
});
}
inline auto setSoundVolume(float volume, const int group = -1) -> float {
@@ -668,10 +631,4 @@ namespace Ja {
sound_enabled = value;
}
inline auto setVolume(float volume) -> float {
const float V = setMusicVolume(volume);
setSoundVolume(V, -1);
return V;
}
} // namespace Ja
+5
View File
@@ -65,6 +65,11 @@ namespace Defaults {
constexpr float MUSIC_VOLUME = 0.8F;
constexpr bool SOUND_ENABLED = true;
constexpr float SOUND_VOLUME = 1.0F;
constexpr float VOLUME_STEP = 0.05F; // Pas estàndard per a la UI (5%).
constexpr int FREQUENCY = 48000; // Freqüència de mostreig del dispositiu (Hz).
constexpr int CROSSFADE_MS = 1500; // Durada per defecte del crossfade entre pistes (ms).
constexpr SDL_AudioFormat FORMAT = SDL_AUDIO_S16; // Format PCM 16-bit signat natiu.
constexpr int CHANNELS = 2; // Nombre de canals de sortida (estèreo).
} // namespace Audio
namespace KeysGame {