Encapsula l'estat del motor a Ja::Engine — classe amb RAII (ctor obre SDL_AudioDevice, dtor el tanca) i `assert` al ctor contra instàncies concurrents. Tots els globals del namespace anònim del commit 1 (current_music, channels[], audio_spec, music_volume, sound_volume[], music_enabled, sound_enabled, sdl_audio_device, outgoing_music, incoming_fade) es converteixen en membres privats amb sufix `_`. Els helpers que toquen estat (stealCurrentIntoOutgoing, updateOutgoing/IncomingFade, updateCurrent*, forEachTargetChannel) passen a mètodes privats. Els stateless (feedMusicChunk, pumpMusic, preFillOutgoing, fadeProgress) es queden al namespace anònim del .cpp. Engine::active() static accessor: registrat al ctor, desregistrat al dtor. Els deleters Ja::deleteMusic/deleteSound consulten Engine::active() per parar canals/música abans d'alliberar recursos; si active() és nullptr (shutdown-order invertit), fan cleanup local silenciós. Això resol CONV-02 (engine ja és classe) i tanca COUP-01. Compat amb els callers: les free functions Ja::init/quit/update, Ja::playMusic, Ja::pauseMusic, Ja::resumeMusic, Ja::stopMusic, Ja::fadeOutMusic, Ja::crossfadeMusic, Ja::getMusicState, Ja::setMusicVolume, Ja::enableMusic, Ja::playSound, Ja::playSoundOnChannel, Ja::pauseChannel, Ja::resumeChannel, Ja::stopChannel, Ja::setSoundVolume i Ja::enableSound es mantenen com a thin forwarders contra Engine::active(). audio.cpp no canvia en aquest commit; les seves crides Ja:: continuen funcionant. Al commit 3 Audio agafarà ownership directe via std::unique_ptr<Engine> i aquests forwarders desapareixeran. Ja::init() crea un std::unique_ptr<Engine> propietari dins d'un namespace anònim del .cpp (encara no és Audio qui posseeix — això és el commit 3). Ja::loadMusic/loadSound/deleteMusic/deleteSound es queden com a free functions permanents (no depenen del motor per construir). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
221 lines
8.5 KiB
C++
221 lines
8.5 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); }
|
|
}
|
|
};
|
|
|
|
// Motor 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 que l'Engine s'iniciï i
|
|
// com a valor inicial de Sound/Music. L'spec real d'ús l'imposa el ctor
|
|
// d'Engine, alimentat des de Defaults::Audio via Audio.
|
|
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;
|
|
};
|
|
|
|
// --- Engine ---
|
|
// Encapsula tot l'estat que abans vivia com a globals inline. Un sol Engine
|
|
// viu per procés (enforceat via assert al ctor contra `active_`). El ctor
|
|
// obre el device SDL; el dtor el tanca (RAII). Els deleters
|
|
// `Ja::deleteMusic`/`Ja::deleteSound` accedeixen al motor actiu via
|
|
// `Engine::active()` per parar canals abans d'alliberar.
|
|
class Engine {
|
|
public:
|
|
Engine(int freq, SDL_AudioFormat format, int num_channels);
|
|
~Engine();
|
|
Engine(const Engine&) = delete;
|
|
auto operator=(const Engine&) -> Engine& = delete;
|
|
Engine(Engine&&) = delete;
|
|
auto operator=(Engine&&) -> Engine& = delete;
|
|
|
|
// Retorna el motor actiu o nullptr si cap ha estat construït. L'usen
|
|
// els deleters de recursos perquè no els arriba cap referència directa.
|
|
[[nodiscard]] static auto active() noexcept -> Engine*;
|
|
|
|
void update();
|
|
|
|
// --- Música ---
|
|
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() const -> MusicState;
|
|
auto setMusicVolume(float volume) -> float;
|
|
void enableMusic(bool value);
|
|
// Notifica al motor que un Music s'està destruint: si és el current_music
|
|
// s'atura abans que els seus recursos (stream/vorbis) deixin de ser vàlids.
|
|
void onMusicDeleted(const Music* music);
|
|
|
|
// --- So ---
|
|
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 pauseChannel(int channel);
|
|
void resumeChannel(int channel);
|
|
void stopChannel(int channel);
|
|
auto setSoundVolume(float volume, int group = -1) -> float;
|
|
void enableSound(bool value);
|
|
// Notifica al motor que un Sound s'està destruint: els canals que el
|
|
// referenciïn es paren abans d'alliberar el buffer.
|
|
void onSoundDeleted(const Sound* sound);
|
|
|
|
private:
|
|
void stealCurrentIntoOutgoing(int duration_ms);
|
|
void updateOutgoingFade();
|
|
void updateIncomingFade();
|
|
void updateCurrentMusic();
|
|
void updateSoundChannels();
|
|
|
|
template <typename Fn>
|
|
void forEachTargetChannel(int channel, Fn&& fn);
|
|
|
|
Music* current_music_{nullptr};
|
|
Channel channels_[MAX_SIMULTANEOUS_CHANNELS]{};
|
|
SDL_AudioSpec audio_spec_{DEFAULT_SPEC};
|
|
float music_volume_{1.0F};
|
|
float sound_volume_[MAX_GROUPS]{};
|
|
bool music_enabled_{true};
|
|
bool sound_enabled_{true};
|
|
SDL_AudioDeviceID sdl_audio_device_{0};
|
|
OutgoingMusic outgoing_music_;
|
|
FadeState incoming_fade_;
|
|
|
|
// NOLINTNEXTLINE(readability-identifier-naming) — convenció projecte: private static amb sufix _
|
|
static Engine* active_;
|
|
};
|
|
|
|
// --- Lifecycle forwarders ---
|
|
// Compat amb els callers actuals. Creen/destrueixen l'Engine únic via
|
|
// make_unique emmagatzemat al propi .cpp. Al commit següent Audio agafarà
|
|
// ownership directe i aquests forwarders desapareixeran per a la capa de
|
|
// control; els de música/so romanen només el temps de la migració.
|
|
void init(int freq, SDL_AudioFormat format, int num_channels);
|
|
void quit();
|
|
void update();
|
|
|
|
// --- Music API forwarders (temporal, eliminats al commit 3) ---
|
|
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;
|
|
auto setMusicVolume(float volume) -> float;
|
|
void enableMusic(bool value);
|
|
|
|
// --- Sound API forwarders (temporal, eliminats al commit 3) ---
|
|
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 pauseChannel(int channel);
|
|
void resumeChannel(int channel);
|
|
void stopChannel(int channel);
|
|
auto setSoundVolume(float volume, int group = -1) -> float;
|
|
void enableSound(bool value);
|
|
|
|
// --- Factories i destructors (permanents) ---
|
|
// No depenen de l'estat del motor: loadMusic/loadSound només construeixen
|
|
// objectes, deleteMusic/deleteSound consulten Engine::active() per parar
|
|
// canals abans d'alliberar (si el motor encara viu).
|
|
[[nodiscard]] auto loadMusic(const Uint8* buffer, Uint32 length) -> Music*;
|
|
[[nodiscard]] auto loadMusic(const Uint8* buffer, Uint32 length, const char* filename) -> Music*;
|
|
void deleteMusic(Music* music);
|
|
[[nodiscard]] auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound*;
|
|
void deleteSound(Sound* sound);
|
|
|
|
} // namespace Ja
|