feat(title): música title.ogg amb fade-out, SFX player_start al START i bombament continu del stream sortint per evitar talls quan SDL drena a 2x
This commit is contained in:
1 parent
36fc496226
commit
921e10bbb3
10 files changed
+109
-1
No files matched your search
@@ -127,11 +127,44 @@ namespace Ja {
|
||||
void Engine::updateOutgoingFade() {
|
||||
if (outgoing_music_.stream == nullptr || !outgoing_music_.fade.active) { return; }
|
||||
|
||||
// Mentre la fosa està activa, mantenim el stream amb una reserva
|
||||
// de samples per davant del cursor (mateix patró que pumpMusic
|
||||
// per al current_music_). Així el stream no es buida ni quan SDL
|
||||
// drena més ràpid del previst en haver sounds bound a la mateixa
|
||||
// device. Si l'OGG arriba a EOF, rebobina (la fosa pot ser més
|
||||
// llarga que la pista).
|
||||
if (outgoing_music_.music != nullptr && outgoing_music_.music->vorbis != nullptr) {
|
||||
const Music& music = *outgoing_music_.music;
|
||||
const int BYTES_PER_SECOND = music.spec.freq * music.spec.channels * MUSIC_BYTES_PER_SAMPLE;
|
||||
const int LOW_WATER = static_cast<int>(MUSIC_LOW_WATER_SECONDS * static_cast<float>(BYTES_PER_SECOND));
|
||||
while (SDL_GetAudioStreamAvailable(outgoing_music_.stream) < LOW_WATER) {
|
||||
short chunk[MUSIC_CHUNK_SHORTS];
|
||||
const int SAMPLES = stb_vorbis_get_samples_short_interleaved(
|
||||
music.vorbis,
|
||||
music.spec.channels,
|
||||
static_cast<short*>(chunk),
|
||||
MUSIC_CHUNK_SHORTS);
|
||||
if (SAMPLES <= 0) {
|
||||
stb_vorbis_seek_start(music.vorbis);
|
||||
continue;
|
||||
}
|
||||
const int BYTES = SAMPLES * music.spec.channels * MUSIC_BYTES_PER_SAMPLE;
|
||||
SDL_PutAudioStreamData(outgoing_music_.stream, static_cast<const void*>(chunk), BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
const float PROGRESS = fadeProgress(outgoing_music_.fade);
|
||||
if (PROGRESS >= 1.0F) {
|
||||
SDL_DestroyAudioStream(outgoing_music_.stream);
|
||||
outgoing_music_.stream = nullptr;
|
||||
outgoing_music_.fade.active = false;
|
||||
// Deixem el Vorbis del Music original en un estat conegut per
|
||||
// a la pròxima reproducció. (playMusic també fa seek_start,
|
||||
// però fer-ho ací evita estats intermedis si algú consulta.)
|
||||
if (outgoing_music_.music != nullptr && outgoing_music_.music->vorbis != nullptr) {
|
||||
stb_vorbis_seek_start(outgoing_music_.music->vorbis);
|
||||
}
|
||||
outgoing_music_.music = nullptr;
|
||||
} else {
|
||||
SDL_SetAudioStreamGain(outgoing_music_.stream, outgoing_music_.fade.initial_volume * (1.0F - PROGRESS));
|
||||
}
|
||||
@@ -191,7 +224,14 @@ namespace Ja {
|
||||
}
|
||||
|
||||
preFillOutgoing(current_music_, duration_ms);
|
||||
|
||||
outgoing_music_.stream = current_music_->stream;
|
||||
// Guardem la referència al Music perquè updateOutgoingFade puga
|
||||
// seguir bombant Vorbis cap al stream durant tota la fosa. NO fem
|
||||
// seek_start ací: la decompressió ha de continuar des d'on estava
|
||||
// perquè el so siga continu. El seek_start es farà quan la fosa
|
||||
// acabe (o quan playMusic la interrompi via stopMusic).
|
||||
outgoing_music_.music = current_music_;
|
||||
outgoing_music_.fade = {
|
||||
.active = true,
|
||||
.start_time = SDL_GetTicks(),
|
||||
@@ -200,7 +240,6 @@ namespace Ja {
|
||||
};
|
||||
current_music_->stream = nullptr;
|
||||
current_music_->state = MusicState::STOPPED;
|
||||
if (current_music_->vorbis != nullptr) { stb_vorbis_seek_start(current_music_->vorbis); }
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
@@ -265,6 +304,10 @@ namespace Ja {
|
||||
SDL_DestroyAudioStream(outgoing_music_.stream);
|
||||
outgoing_music_.stream = nullptr;
|
||||
outgoing_music_.fade.active = false;
|
||||
if (outgoing_music_.music != nullptr && outgoing_music_.music->vorbis != nullptr) {
|
||||
stb_vorbis_seek_start(outgoing_music_.music->vorbis);
|
||||
}
|
||||
outgoing_music_.music = nullptr;
|
||||
}
|
||||
incoming_fade_.active = false;
|
||||
|
||||
|
||||
@@ -125,6 +125,13 @@ namespace Ja {
|
||||
|
||||
struct OutgoingMusic {
|
||||
SDL_AudioStream* stream{nullptr};
|
||||
// Referència al Music original perquè updateOutgoingFade puga
|
||||
// continuar descomprimint des de Vorbis cap al stream durant
|
||||
// tota la fosa. Sense això, només tenim el pre-fill puntual i
|
||||
// SDL drena el stream més ràpid del previst quan hi ha sounds
|
||||
// bound a la mateixa device (~2x), buidant-lo a meitat del
|
||||
// fade i sentint-se com un tall sec.
|
||||
Music* music{nullptr};
|
||||
FadeState fade;
|
||||
};
|
||||
|
||||
|
||||
@@ -153,6 +153,9 @@ Director::Director() {
|
||||
Resource::Cache::get().loadMusic("room_intro.ogg", "data/music/room_intro.ogg");
|
||||
Resource::Cache::get().loadMusic("playing.ogg", "data/music/playing.ogg");
|
||||
Resource::Cache::get().loadMusic("room_outro.ogg", "data/music/room_outro.ogg");
|
||||
// Música de la TITLE: arrenca al constructor de l'escena i es fa
|
||||
// fade-out quan l'escena entra a STARTING.
|
||||
Resource::Cache::get().loadMusic("title.ogg", "data/music/title.ogg");
|
||||
|
||||
// Sons del menú de servei. Els noms venen de `data/ui.yaml` (amb
|
||||
// fallback a `Defaults::UI::MENU_*_SOUND`); el ServiceMenu els
|
||||
@@ -161,6 +164,10 @@ Director::Director() {
|
||||
const auto& menu_accept = UiConfig::get().menuAcceptSound();
|
||||
Resource::Cache::get().loadSound(menu_select, "data/sounds/" + menu_select);
|
||||
Resource::Cache::get().loadSound(menu_accept, "data/sounds/" + menu_accept);
|
||||
// So del START a la TITLE: el dispara TitleScene quan un jugador
|
||||
// prem START (vore enterStarting i el reset del segon jugador).
|
||||
const auto& player_start = UiConfig::get().playerStartSound();
|
||||
Resource::Cache::get().loadSound(player_start, "data/sounds/" + player_start);
|
||||
|
||||
// Catàleg de presets d'efectes per als sons. Es carrega abans d'Audio
|
||||
// perquè els primers playSoundWith* (al començament del joc) ja
|
||||
|
||||
@@ -106,6 +106,7 @@ UiConfig::UiConfig()
|
||||
title_starting_blink_period_ms_(Defaults::UI::Title::STARTING_BLINK_PERIOD_MS),
|
||||
title_fade_out_ms_(Defaults::UI::Title::FADE_OUT_MS),
|
||||
title_fade_out_max_block_(Defaults::UI::Title::FADE_OUT_MAX_BLOCK),
|
||||
title_music_fade_out_ms_(Defaults::UI::Title::MUSIC_FADE_OUT_MS),
|
||||
title_heat_haze_{
|
||||
.enabled = Defaults::UI::Title::HeatHaze::ENABLED,
|
||||
.amplitude_px = Defaults::UI::Title::HeatHaze::AMPLITUDE_PX,
|
||||
@@ -114,6 +115,7 @@ UiConfig::UiConfig()
|
||||
},
|
||||
menu_select_sound_(Defaults::UI::MENU_SELECT_SOUND),
|
||||
menu_accept_sound_(Defaults::UI::MENU_ACCEPT_SOUND),
|
||||
player_start_sound_(Defaults::UI::PLAYER_START_SOUND),
|
||||
hud_font_{.name = Defaults::UI::Font::HUD_NAME, .gif = Defaults::UI::Font::HUD_GIF, .fnt = Defaults::UI::Font::HUD_FNT, .kerning = Defaults::UI::Font::HUD_KERNING},
|
||||
placeholder_font_{.name = Defaults::UI::Font::PLACEHOLDER_NAME, .gif = Defaults::UI::Font::PLACEHOLDER_GIF, .fnt = Defaults::UI::Font::PLACEHOLDER_FNT, .kerning = Defaults::UI::Font::PLACEHOLDER_KERNING},
|
||||
room_text_font_{.name = Defaults::UI::Font::ROOM_TEXT_NAME, .gif = Defaults::UI::Font::ROOM_TEXT_GIF, .fnt = Defaults::UI::Font::ROOM_TEXT_FNT, .kerning = Defaults::UI::Font::ROOM_TEXT_KERNING},
|
||||
@@ -218,6 +220,7 @@ void UiConfig::load(const std::string& file_path) {
|
||||
const auto& s = yaml["sounds"];
|
||||
readField(s, "menu_select", menu_select_sound_);
|
||||
readField(s, "menu_accept", menu_accept_sound_);
|
||||
readField(s, "player_start", player_start_sound_);
|
||||
}
|
||||
|
||||
if (yaml.contains("title")) {
|
||||
@@ -232,6 +235,7 @@ void UiConfig::load(const std::string& file_path) {
|
||||
readField(t, "starting_blink_period_ms", title_starting_blink_period_ms_);
|
||||
readField(t, "fade_out_ms", title_fade_out_ms_);
|
||||
readField(t, "fade_out_max_block", title_fade_out_max_block_);
|
||||
readField(t, "music_fade_out_ms", title_music_fade_out_ms_);
|
||||
if (t.contains("heat_haze")) {
|
||||
const auto& hh = t["heat_haze"];
|
||||
readField(hh, "enabled", title_heat_haze_.enabled);
|
||||
|
||||
@@ -99,6 +99,7 @@ class UiConfig {
|
||||
[[nodiscard]] auto titleStartingBlinkPeriodMs() const -> int { return title_starting_blink_period_ms_; }
|
||||
[[nodiscard]] auto titleFadeOutMs() const -> int { return title_fade_out_ms_; }
|
||||
[[nodiscard]] auto titleFadeOutMaxBlock() const -> int { return title_fade_out_max_block_; }
|
||||
[[nodiscard]] auto titleMusicFadeOutMs() const -> int { return title_music_fade_out_ms_; }
|
||||
[[nodiscard]] auto titleHeatHaze() const -> const TitleHeatHaze& { return title_heat_haze_; }
|
||||
|
||||
// Corbes d'easing triades al YAML. Si el nom no és conegut, s'usa
|
||||
@@ -116,6 +117,7 @@ class UiConfig {
|
||||
// fitxers viuen a `data/sounds/`).
|
||||
[[nodiscard]] auto menuSelectSound() const -> const std::string& { return menu_select_sound_; }
|
||||
[[nodiscard]] auto menuAcceptSound() const -> const std::string& { return menu_accept_sound_; }
|
||||
[[nodiscard]] auto playerStartSound() const -> const std::string& { return player_start_sound_; }
|
||||
|
||||
// Fonts de UI.
|
||||
[[nodiscard]] auto hudFont() const -> const Font& { return hud_font_; }
|
||||
@@ -175,10 +177,12 @@ class UiConfig {
|
||||
int title_starting_blink_period_ms_;
|
||||
int title_fade_out_ms_;
|
||||
int title_fade_out_max_block_;
|
||||
int title_music_fade_out_ms_;
|
||||
TitleHeatHaze title_heat_haze_;
|
||||
|
||||
std::string menu_select_sound_;
|
||||
std::string menu_accept_sound_;
|
||||
std::string player_start_sound_;
|
||||
|
||||
Font hud_font_;
|
||||
Font placeholder_font_;
|
||||
|
||||
Reference in new issue
Block a user