Files
aee-arcade/source/core/audio/audio.cpp
T
JailDesignerandClaude Opus 4.7 a917f6660a fix(core/audio): recuperar el nom del Ja::Music::filename (PATCH-02)
L'overload playMusic(Ja::Music*, ...) buidava music_.name a cegues, així
que getCurrentMusicName() mentia després d'aquest camí tot i que el
Ja::Music que li passaves podia portar filename (Ja::loadMusic té un
overload que l'accepta).

Ara copiem music->filename — si el caller va usar l'overload amb
filename, el nom es conserva; si no, queda buit i el contracte és
explícit al comentari.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 12:17:32 +02:00

241 lines
8.6 KiB
C++

#include "core/audio/audio.hpp"
#include <SDL3/SDL.h> // Para SDL_GetError, SDL_Init
#include <algorithm> // Para clamp
#include <iostream> // Para std::cout
// Implementació de stb_vorbis (ha d'anar ABANS d'incloure jail_audio.hpp).
// clang-format off
#undef STB_VORBIS_HEADER_ONLY
// NOLINTNEXTLINE(bugprone-suspicious-include)
#include "external/stb_vorbis.c"
// stb_vorbis.c filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem
// perquè xocarien amb noms de paràmetres de plantilla en altres headers.
#undef L
#undef C
#undef R
#undef PLAYBACK_MONO
#undef PLAYBACK_LEFT
#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 "utils/defaults.hpp" // Para 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;
// 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
void Audio::destroy() { Audio::instance.reset(); }
// Obtiene la instancia
auto Audio::get() -> Audio* { return Audio::instance.get(); }
// Constructor
Audio::Audio(const Config& config)
: config_(config) { initSDLAudio(); }
// Destructor
Audio::~Audio() {
Ja::quit();
}
// Método 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();
}
// Reproduce la música por nombre (con 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 (getMusicState() == MusicState::PLAYING && music_.name == name && music_.loop == NEW_LOOP) {
return;
}
if (!music_enabled_) { return; }
auto* resource = AudioResource::getMusic(name);
if (resource == nullptr) { return; }
playMusicInternal(resource, loop, crossfade_ms);
music_.name = name;
}
// Reproduce la música por puntero (con crossfade opcional)
void Audio::playMusic(Ja::Music* music, const int loop, const int crossfade_ms) {
if (!music_enabled_ || music == nullptr) { return; }
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 (CURRENTLY_PLAYING) {
Ja::stopMusic();
}
Ja::playMusic(music, loop);
}
music_.loop = (loop != 0);
}
// 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_ && getMusicState() == MusicState::PLAYING) {
Ja::pauseMusic();
}
}
// 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_ && getMusicState() == MusicState::PAUSED) {
Ja::resumeMusic();
}
}
// 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();
}
}
// Reproduce un sonido por nombre
// 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
// 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
// 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
// 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 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:
return MusicState::PAUSED;
case Ja::MusicState::STOPPED:
case Ja::MusicState::INVALID:
case Ja::MusicState::DISABLED:
default:
return MusicState::STOPPED;
}
}
// 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)
// 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)
// 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)
void Audio::applySettings(const Config& config) {
config_ = config;
sound_enabled_ = config_.sound_enabled;
music_enabled_ = config_.music_enabled;
enable(config_.enabled);
}
// Estableix l'estat general
void Audio::enable(bool value) {
enabled_ = value;
setSoundVolume(enabled_ ? config_.sound_volume : MIN_VOLUME);
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';
} else {
Ja::init(Defaults::Audio::FREQUENCY, Defaults::Audio::FORMAT, Defaults::Audio::CHANNELS);
sound_enabled_ = config_.sound_enabled;
music_enabled_ = config_.music_enabled;
enable(config_.enabled);
}
}