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>
140 lines
4.9 KiB
C++
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
|