diff --git a/source/core/audio/audio.cpp b/source/core/audio/audio.cpp index c77c3b1..3aca318 100644 --- a/source/core/audio/audio.cpp +++ b/source/core/audio/audio.cpp @@ -194,6 +194,14 @@ void Audio::setOnMusicEnded(std::function callback) { if (engine_) { engine_->setOnMusicEnded(std::move(callback)); } } +// Resol el nom contra el cache de recursos i retorna la durada pre-calculada +// al `loadMusic`. 0 si la pista no existeix — així el caller pot decidir +// fallback (p. ex. usar un timeout fix) sense haver de propagar errors. +auto Audio::getMusicDurationMs(const std::string& name) -> int { + auto* music = AudioResource::getMusic(name); + return (music != nullptr) ? music->duration_ms : 0; +} + // 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 50171d8..9c4c387 100644 --- a/source/core/audio/audio.hpp +++ b/source/core/audio/audio.hpp @@ -121,6 +121,11 @@ class Audio { [[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; } [[nodiscard]] static auto getMusicState() -> MusicState; // Estat real consultat a Ja:: [[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; } + // Durada de la pista resolta per nom (mil·lisegons). 0 si la pista no + // existeix al cache de recursos o si el seu header OGG no permet + // calcular-la. Pensat per a clients que necessiten un timeline + // determinista (p. ex. RoomFsm) sense dependre de callbacks de fi. + [[nodiscard]] static auto getMusicDurationMs(const std::string& name) -> int; private: // --- Tipus anidats --- diff --git a/source/core/audio/jail_audio.cpp b/source/core/audio/jail_audio.cpp index 5ad4fb0..9c9a3ac 100644 --- a/source/core/audio/jail_audio.cpp +++ b/source/core/audio/jail_audio.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -564,6 +565,15 @@ namespace Ja { music->spec.channels = static_cast(INFO.channels); music->spec.freq = static_cast(INFO.sample_rate); music->spec.format = SDL_AUDIO_S16; + // Pre-càlcul de la durada en ms a partir del header. stb_vorbis ja + // ha decodificat la informació necessària a `stb_vorbis_open_memory`; + // aquesta consulta no descodifica àudio, només llig el comptador + // de samples. Si el sample_rate fos 0 (header malmès) deixem + // duration_ms a 0. + if (INFO.sample_rate > 0) { + const auto SAMPLES = stb_vorbis_stream_length_in_samples(music->vorbis); + music->duration_ms = static_cast((static_cast(SAMPLES) * 1000ULL) / INFO.sample_rate); + } music->state = MusicState::STOPPED; return music.release(); diff --git a/source/core/audio/jail_audio.hpp b/source/core/audio/jail_audio.hpp index 85f9900..aba0ba9 100644 --- a/source/core/audio/jail_audio.hpp +++ b/source/core/audio/jail_audio.hpp @@ -112,6 +112,12 @@ namespace Ja { std::string filename; int times{0}; // loops restants (-1 = infinit, 0 = un sol play) + // Durada total de la pista en mil·lisegons, mesurada via + // `stb_vorbis_stream_length_in_samples / sample_rate` al + // `loadMusic`. 0 si el càlcul no és possible (header malmès). + // L'usen consumidors que necessiten un timeline pre-calculat — + // p. ex. la FSM de sala — sense dependre de callbacks de fi. + int duration_ms{0}; SDL_AudioStream* stream{nullptr}; MusicState state{MusicState::INVALID}; }; diff --git a/source/core/rendering/transitions/fade_to_black.cpp b/source/core/rendering/transitions/fade_to_black.cpp index 3904bcd..0925adc 100644 --- a/source/core/rendering/transitions/fade_to_black.cpp +++ b/source/core/rendering/transitions/fade_to_black.cpp @@ -8,9 +8,10 @@ namespace Transitions { - void FadeToBlack::begin(int duration_ms) { + void FadeToBlack::begin(int duration_ms, Direction direction) { duration_ms_ = duration_ms; elapsed_ms_ = 0; + direction_ = direction; } void FadeToBlack::tick(int delta_ms) { @@ -29,7 +30,11 @@ namespace Transitions { // Alpha lineal — sense corba quadràtica (això és prerogativa del // dissolve mosaic, on volem que la mascara fosca arribe tard // perquè es vegen els blocs). Per al fade net, lineal 0..255. - const auto ALPHA = static_cast(std::round(255.0F * P)); + // Direcció FADE_IN inverteix la rampa: el negre comença opac i + // s'esvaeix cap a transparent, deixant veure l'escena pintada + // darrere abans de `render()`. + const float ALPHA_F = (direction_ == Direction::FADE_IN) ? (1.0F - P) : P; + const auto ALPHA = static_cast(std::round(255.0F * ALPHA_F)); if (ALPHA == 0) { return; } auto& screen = Screen::get(); diff --git a/source/core/rendering/transitions/fade_to_black.hpp b/source/core/rendering/transitions/fade_to_black.hpp index 59c6d4f..880eea4 100644 --- a/source/core/rendering/transitions/fade_to_black.hpp +++ b/source/core/rendering/transitions/fade_to_black.hpp @@ -1,37 +1,51 @@ #pragma once +#include + namespace Transitions { - // Fade-out lineal a negre. A diferència de `Mosaic`, no rebina cap - // píxel; només posa una capa negra amb alpha creixent (0 → 255) al - // damunt del canvas. És la transició "neta" per defecte; ideal per a - // sortides on no convé l'estètica chunky de l'arcade dissolve. + // Fade lineal contra negre. A diferència de `Mosaic`, no rebina cap + // píxel; només posa una capa negra al damunt del canvas amb alpha + // que evoluciona linealment al llarg de la durada. És la transició + // "neta" per defecte; ideal per a entrades/sortides on no convé + // l'estètica chunky de l'arcade dissolve. + // + // La direcció controla el sentit de l'alpha: + // FADE_OUT: alpha 0 → 255 (món visible → negre opac) + // FADE_IN: alpha 255 → 0 (negre opac → món visible) + // En tots dos casos `progress()` segueix sent monotònic 0 → 1 + // (mateixa semàntica per al caller); només el render inverteix. // // Mateixa interfície que `Mosaic` (begin/tick/render/isActive/ // isFinished/progress) perquè els llocs de crida puguen escollir-ne // una o l'altra sense canviar el patró d'ús. class FadeToBlack { public: + enum class Direction : std::uint8_t { FADE_OUT, + FADE_IN }; + FadeToBlack() = default; // Arrenca un fade nou. Si ja n'hi havia un actiu, el reemplaça. // `duration_ms <= 0` deixa l'efecte inactiu. - void begin(int duration_ms); + void begin(int duration_ms, Direction direction = Direction::FADE_OUT); void tick(int delta_ms); [[nodiscard]] auto isActive() const -> bool { return duration_ms_ > 0 && elapsed_ms_ < duration_ms_; } [[nodiscard]] auto isFinished() const -> bool { return duration_ms_ > 0 && elapsed_ms_ >= duration_ms_; } [[nodiscard]] auto progress() const -> float; + [[nodiscard]] auto direction() const -> Direction { return direction_; } - // Pinta la capa negra amb alpha proporcional al `progress()`. - // Cridar després del render normal de l'escena, abans de - // `Screen::present()`. + // Pinta la capa negra amb alpha derivat del `progress()` i la + // direcció. Cridar després del render normal de l'escena, + // abans de `Screen::present()`. void render() const; private: int duration_ms_{0}; int elapsed_ms_{0}; + Direction direction_{Direction::FADE_OUT}; }; } // namespace Transitions