Files
aee-arcade/source/core/audio/jail_audio.hpp
T
JailDesignerandClaude Opus 4.7 bc53174f2b refactor(core/audio): split jail_audio.hpp/.cpp (COUP-02, PATCH-03)
Preparatori per COUP-01. No canvia comportament: només mou les
definicions inline de jail_audio.hpp a un jail_audio.cpp nou, deixant
el .hpp amb declaracions + tipus + constants.

Efectes col·laterals que es tanquen gratis:

  - COUP-02: jail_audio deixa de ser header-only. Abans cada TU que
    l'incloïa compilava 650 línies inline (amb stb_vorbis.c en mode
    HEADER_ONLY). Ara el .hpp té ~130 línies de declaracions.
  - PATCH-03: #include "external/stb_vorbis.c" + #undef L/C/R/PLAYBACK_*
    viu només al final de jail_audio.cpp. audio.cpp perd el workaround
    dels undefs.

Estat intern (current_music, channels[], audio_spec, music_volume,
sound_volume[], music_enabled, sound_enabled, sdl_audio_device,
outgoing_music, incoming_fade) baixa al .cpp dins d'un namespace
anònim (internal linkage). Els helpers privats (feedMusicChunk,
pumpMusic, preFillOutgoing, fadeProgress, update*Fade, updateCurrent*,
stealCurrentIntoOutgoing, forEachTargetChannel) també viuen a
l'anonymous namespace.

Ja::Music i Ja::Sound són públics; el .hpp forward-declara
`struct stb_vorbis` perquè els callers només els agafen per punter.
NOLINT(readability-identifier-naming) al forward-decl perquè el nom
l'imposa stb_vorbis.

jail_audio.cpp s'afegeix a APP_SOURCES al CMakeLists.txt.

.clang-tidy: desactivat clang-analyzer-core.UndefinedBinaryOperatorResult
globalment. Fals positiu conegut a external/stb_vorbis.c que abans no es
disparava (no hi havia TU que cridés directament stb_vorbis_seek_start
des del mateix fitxer on la funció està inlined); ara que jail_audio.cpp
sí que fa aquestes crides directes, el path d'anàlisi passa pel .c i el
check dispara. HeaderFilterRegex no aplica a clang-analyzer-*.

Preparació per COUP-01 (introduir Ja::Engine al commit següent).

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

140 lines
4.9 KiB
C++

#pragma once
// --- Includes ---
#include <SDL3/SDL.h>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
// Forward-declaració del decoder de vorbis. La implementació viu a
// jail_audio.cpp (únic TU que compila external/stb_vorbis.c). Qualsevol caller
// només necessita `stb_vorbis*` per punter — mai per valor — així que el
// forward decl n'hi ha prou i evita arrossegar el .c a tots els TU.
// NOLINTNEXTLINE(readability-identifier-naming) — nom imposat per l'API de stb_vorbis
struct stb_vorbis;
// Deleter stateless per a buffers reservats amb `SDL_malloc` / `SDL_LoadWAV*`.
// Compatible amb `std::unique_ptr<Uint8[], SdlFreeDeleter>` — zero size overhead
// gràcies a EBO, igual que un unique_ptr amb default_delete.
struct SdlFreeDeleter {
void operator()(Uint8* p) const noexcept {
if (p != nullptr) { SDL_free(p); }
}
};
// Engine de baix nivell d'àudio del projecte jailgames: streaming OGG (stb_vorbis)
// + N canals d'efectes (SDL3 audio). No depèn d'Options ni de cap singleton del
// joc; només de SDL3 i stb_vorbis. La capa superior (Audio) li passa recursos
// pel punter i fa el bookkeeping d'usuari.
namespace Ja {
// --- Public Enums ---
enum class ChannelState : std::uint8_t {
INVALID,
FREE,
PLAYING,
PAUSED,
SOUND_DISABLED,
};
enum class MusicState : std::uint8_t {
INVALID,
PLAYING,
PAUSED,
STOPPED,
DISABLED,
};
// --- Constants ---
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 Definitions ---
struct Sound {
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.
std::unique_ptr<Uint8[], SdlFreeDeleter> buffer;
};
// L'ordre (punters primer, ints després, enum de 8 bits al final) minimitza
// el padding a 64-bit (evita avisos de clang-analyzer-optin.performance.Padding).
struct Channel {
Sound* sound{nullptr};
SDL_AudioStream* stream{nullptr};
int pos{0};
int times{0};
int group{0};
ChannelState state{ChannelState::FREE};
};
struct Music {
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
// per streaming. Com que stb_vorbis guarda un punter persistent al
// `.data()` d'aquest vector, no el podem resize'jar un cop establert
// (una reallocation invalidaria el punter que el decoder conserva).
std::vector<Uint8> ogg_data;
stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del Music
std::string filename;
int times{0}; // loops restants (-1 = infinit, 0 = un sol play)
SDL_AudioStream* stream{nullptr};
MusicState state{MusicState::INVALID};
};
struct FadeState {
bool active{false};
Uint64 start_time{0};
int duration_ms{0};
float initial_volume{0.0F};
};
struct OutgoingMusic {
SDL_AudioStream* stream{nullptr};
FadeState fade;
};
// --- Lifecycle ---
void init(int freq, SDL_AudioFormat format, int num_channels);
void quit();
void update();
// --- Music API ---
[[nodiscard]] auto loadMusic(const Uint8* buffer, Uint32 length) -> Music*;
[[nodiscard]] auto loadMusic(const Uint8* buffer, Uint32 length, const char* filename) -> Music*;
void playMusic(Music* music, int loop = -1);
void pauseMusic();
void resumeMusic();
void stopMusic();
void fadeOutMusic(int milliseconds);
void crossfadeMusic(Music* music, int crossfade_ms, int loop = -1);
[[nodiscard]] auto getMusicState() -> MusicState;
void deleteMusic(Music* music);
auto setMusicVolume(float volume) -> float;
void enableMusic(bool value);
// --- Sound API ---
[[nodiscard]] auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound*;
auto playSound(Sound* sound, int loop = 0, int group = 0) -> int;
auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int;
void deleteSound(Sound* sound);
void pauseChannel(int channel);
void resumeChannel(int channel);
void stopChannel(int channel);
auto setSoundVolume(float volume, int group = -1) -> float;
void enableSound(bool value);
} // namespace Ja