From dee00a8de7a6c9ad978e5df786a7317ece9172d5 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 20 Apr 2026 12:05:28 +0200 Subject: [PATCH] refactor(core/audio): helper stealCurrentIntoOutgoing (DUP-04) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fadeOutMusic i crossfadeMusic repetien el bloc "destruir outgoing anterior + preFillOutgoing + robar stream + reset state+vorbis". Es treu a stealCurrentIntoOutgoing(duration_ms) i els dos callers deleguen. Com a efecte secundari, homogeneïtza la neteja: abans fadeOutMusic no posava outgoing.fade.active = false en destruir l'anterior i crossfadeMusic sí. L'helper fa sempre les dues coses. El comportament observable no canvia perquè la fade s'assigna completament just després, però elimina una inconsistència latent. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/audit-core-audio.md | 2 +- source/core/audio/jail_audio.hpp | 83 +++++++++++++------------------- 2 files changed, 35 insertions(+), 50 deletions(-) diff --git a/docs/audit-core-audio.md b/docs/audit-core-audio.md index 50cc73b..79cd1c6 100644 --- a/docs/audit-core-audio.md +++ b/docs/audit-core-audio.md @@ -33,7 +33,7 @@ Los IDs se mantienen estables una vez asignados. Cada entrada apunta al(los) fic | DUP-01 | `audio.cpp:58-101` | Dos `playMusic` (nombre/puntero) repiten el bloque crossfade/stop+play | Menor | DONE | `playMusicInternal(Ja::Music*, loop, crossfade_ms)` privat; els dos overloads fan gating + name-handling i deleguen el dispatch | | DUP-02 | `audio.cpp:172-185` | `setSoundVolume`/`setMusicVolume` idéntica estructura de clamp+gate+producto | Menor | DONE | `effectiveVolume(volume, channel_enabled)` privat; els dos setters deleguen | | DUP-03 | `jail_audio.hpp:196-222` | `updateOutgoingFade`/`updateIncomingFade` comparten la curva lineal | Menor | DONE | `fadeProgress(FadeState&)` concentra la corba en un sol lloc; també cobreix `duration_ms <= 0` com a 1.0 | -| DUP-04 | `jail_audio.hpp:391-471` | `fadeOutMusic`/`crossfadeMusic` repiten "destruir outgoing + pre-fill + robar stream + reset vorbis" | Importante | PENDING | Función privada `stealCurrentIntoOutgoing(int ms)` | +| DUP-04 | `jail_audio.hpp:391-471` | `fadeOutMusic`/`crossfadeMusic` repiten "destruir outgoing + pre-fill + robar stream + reset vorbis" | Importante | DONE | `stealCurrentIntoOutgoing(duration_ms)` concentra el robatori; `fadeOutMusic` i `crossfadeMusic` hi deleguen. També homogeneïtza `outgoing.fade.active = false` en la destrucció (abans era inconsistent) | | DUP-05 | `jail_audio.hpp:575-631` | `pauseChannel`/`resumeChannel`/`stopChannel` repiten dispatch `channel==-1 / [0,MAX)` | Menor | PENDING | Helper `forEachTargetChannel(int, fn)` | ## 2. Hardcoding diff --git a/source/core/audio/jail_audio.hpp b/source/core/audio/jail_audio.hpp index 9039d0b..bf8d2c6 100644 --- a/source/core/audio/jail_audio.hpp +++ b/source/core/audio/jail_audio.hpp @@ -400,63 +400,48 @@ namespace Ja { } } - inline void fadeOutMusic(const int milliseconds) { - if (!music_enabled) { return; } - if (current_music == nullptr || current_music->state != MusicState::PLAYING) { return; } - - // Destruir outgoing anterior si existe - if (outgoing_music.stream != nullptr) { - SDL_DestroyAudioStream(outgoing_music.stream); - outgoing_music.stream = nullptr; - } - - // Pre-omplim l'stream amb `milliseconds` de so: un cop robat, ja no - // tindrà accés al vorbis decoder i només podrà drenar el que tinga. - preFillOutgoing(current_music, milliseconds); - - // Robar el stream del current_music al outgoing - outgoing_music.stream = current_music->stream; - outgoing_music.fade = { - .active = true, - .start_time = SDL_GetTicks(), - .duration_ms = milliseconds, - .initial_volume = music_volume, - }; - - // Dejar current_music sin stream (ya lo tiene outgoing) - current_music->stream = nullptr; - current_music->state = MusicState::STOPPED; - if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); } - incoming_fade.active = false; - } - - inline void crossfadeMusic(Music* music, const int crossfade_ms, const int loop) { - if (!music_enabled || music == nullptr || music->vorbis == nullptr) { return; } - - // Destruir outgoing anterior si existe (crossfade durante crossfade) + // Roba l'stream de la música actual al slot outgoing per a un fade-out de + // `duration_ms` ms. Destrueix qualsevol outgoing anterior. L'stream robat + // ja no es pot alimentar (perd l'accés al vorbis decoder), per això cal + // preFillOutgoing abans del robatori perquè tingui so suficient fins que + // acabi el fade. No-op si no hi ha música sonant amb stream vàlid. + inline void stealCurrentIntoOutgoing(const int duration_ms) { if (outgoing_music.stream != nullptr) { SDL_DestroyAudioStream(outgoing_music.stream); outgoing_music.stream = nullptr; outgoing_music.fade.active = false; } - // Robar el stream de la musica actual al outgoing para el fade-out. - // Pre-omplim amb `crossfade_ms` de so perquè no es quede en silenci - // abans d'acabar el fade (l'stream robat ja no pot alimentar-se). - if (current_music != nullptr && current_music->state == MusicState::PLAYING && current_music->stream != nullptr) { - preFillOutgoing(current_music, crossfade_ms); - outgoing_music.stream = current_music->stream; - outgoing_music.fade = { - .active = true, - .start_time = SDL_GetTicks(), - .duration_ms = crossfade_ms, - .initial_volume = music_volume, - }; - current_music->stream = nullptr; - current_music->state = MusicState::STOPPED; - if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); } + if (current_music == nullptr || current_music->state != MusicState::PLAYING || current_music->stream == nullptr) { + return; } + preFillOutgoing(current_music, duration_ms); + outgoing_music.stream = current_music->stream; + outgoing_music.fade = { + .active = true, + .start_time = SDL_GetTicks(), + .duration_ms = duration_ms, + .initial_volume = music_volume, + }; + current_music->stream = nullptr; + current_music->state = MusicState::STOPPED; + if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); } + } + + inline void fadeOutMusic(const int milliseconds) { + if (!music_enabled) { return; } + if (current_music == nullptr || current_music->state != MusicState::PLAYING) { return; } + + stealCurrentIntoOutgoing(milliseconds); + incoming_fade.active = false; + } + + inline void crossfadeMusic(Music* music, const int crossfade_ms, const int loop) { + if (!music_enabled || music == nullptr || music->vorbis == nullptr) { return; } + + stealCurrentIntoOutgoing(crossfade_ms); + // Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente) current_music = music; current_music->state = MusicState::PLAYING;