From 2ec6fd23b6086d2857ded25cca82e66e1b28bd53 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 20 Apr 2026 13:31:47 +0200 Subject: [PATCH] feat(core/audio): music-ended callback (CONV-03) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ja::Engine exposa setOnMusicEnded(std::function). Quan updateCurrentMusic detecta que la pista actual ha acabat de drenar (times == 0 && stream buit) s'atura primer via stopMusic i després s'invoca el callback — així un eventual playMusic des del callback pot començar amb l'engine en estat net. Audio proxeia amb setOnMusicEnded. Contracte documentat: s'executa al mateix thread que Audio::update (render loop); no fer operacions blocants. Cap consumidor actualment; només exposa l'API per quan calgui. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/audio/audio.cpp | 8 ++++++++ source/core/audio/audio.hpp | 12 +++++++----- source/core/audio/jail_audio.cpp | 8 ++++++++ source/core/audio/jail_audio.hpp | 8 ++++++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/source/core/audio/audio.cpp b/source/core/audio/audio.cpp index 9bcd52f..e9904ca 100644 --- a/source/core/audio/audio.cpp +++ b/source/core/audio/audio.cpp @@ -144,6 +144,14 @@ void Audio::fadeOutMusic(int milliseconds) { } } +// Registra un callback que el motor dispararà quan la pista actual acabi de +// drenar (times == 0 + stream buit). S'executa al mateix thread que +// Audio::update (render loop); els consumidors no poden fer I/O blocant. +// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_ +void Audio::setOnMusicEnded(std::function callback) { + if (engine_) { engine_->setOnMusicEnded(std::move(callback)); } +} + // 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). diff --git a/source/core/audio/audio.hpp b/source/core/audio/audio.hpp index f64254a..09e8fed 100644 --- a/source/core/audio/audio.hpp +++ b/source/core/audio/audio.hpp @@ -1,10 +1,11 @@ #pragma once -#include // Per a std::lround -#include // Per a int8_t, uint8_t -#include // Per a std::unique_ptr -#include // Per a string -#include // Per a move +#include // Per a std::lround +#include // Per a int8_t, uint8_t +#include // Per a std::function +#include // Per a std::unique_ptr +#include // Per a string +#include // Per a move // Forward-declares per no incloure core/audio/jail_audio.hpp al header. Els // tres símbols (Music/Sound per al punter que exposa la API i Engine per al @@ -74,6 +75,7 @@ class Audio { void resumeMusic(); // Continua la música pausada void stopMusic(); // Aturar completament la música void fadeOutMusic(int milliseconds); // Fosa de sortida de la música (muta globals de Ja) + void setOnMusicEnded(std::function callback); // Callback disparat quan la pista actual acaba de drenar (CONV-03) // --- Control de sons --- void playSound(const std::string& name, Group group = Group::GAME); // Reproduir so puntual per nom (muta globals de Ja) diff --git a/source/core/audio/jail_audio.cpp b/source/core/audio/jail_audio.cpp index d02a945..292bb67 100644 --- a/source/core/audio/jail_audio.cpp +++ b/source/core/audio/jail_audio.cpp @@ -151,7 +151,11 @@ namespace Ja { pumpMusic(current_music_); if (current_music_->times == 0 && SDL_GetAudioStreamAvailable(current_music_->stream) == 0) { + // La pista ha acabat de drenar naturalment. L'aturem primer (deixa + // l'engine en estat consistent) i llavors invoquem el callback; + // així un eventual playMusic des del callback comença net. stopMusic(); + if (on_music_ended_) { on_music_ended_(); } } } @@ -319,6 +323,10 @@ namespace Ja { return music_volume_; } + void Engine::setOnMusicEnded(std::function callback) { + on_music_ended_ = std::move(callback); + } + void Engine::onMusicDeleted(const Music* music) { if (music == nullptr) { return; } if (current_music_ == music) { diff --git a/source/core/audio/jail_audio.hpp b/source/core/audio/jail_audio.hpp index 4571448..7a88879 100644 --- a/source/core/audio/jail_audio.hpp +++ b/source/core/audio/jail_audio.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -133,6 +134,12 @@ namespace Ja { void crossfadeMusic(Music* music, int crossfade_ms, int loop = -1); [[nodiscard]] auto getMusicState() const -> MusicState; auto setMusicVolume(float volume) -> float; + // Registra un callback que es disparà quan la música actual acabi de + // drenar naturalment (times == 0 + stream buit). Es crida DESPRÉS de + // stopMusic, així que el callback pot invocar playMusic sense córrer. + // S'executa al mateix thread que Engine::update (render loop); no fer + // operacions blocants. + void setOnMusicEnded(std::function callback); // 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); @@ -166,6 +173,7 @@ namespace Ja { SDL_AudioDeviceID sdl_audio_device_{0}; OutgoingMusic outgoing_music_; FadeState incoming_fade_; + std::function on_music_ended_; // NOLINTNEXTLINE(readability-identifier-naming) — convenció projecte: private static amb sufix _ static Engine* active_;