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:
commit
17301aff66
5 files changed
+347
-270
No files matched your search
@@ -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
@@ -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
@@ -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
|
||||
};
|
||||
@@ -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(¤t_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(¤t_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
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user