From d68e5159e15cd6b2f5bc82e5b8f248e8e002c262 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Mon, 1 Apr 2024 17:54:42 +0100 Subject: [PATCH] Feature: Allow base sounds set to be changed mid-game. (#12399) --- src/mixer.cpp | 18 ++++++++++++++++++ src/mixer.h | 1 + src/settings_gui.cpp | 10 ++-------- src/sound.cpp | 28 ++++++++++++++++++++++++++++ src/sound_func.h | 2 ++ 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/mixer.cpp b/src/mixer.cpp index 3b30f3d25d..bcfde64a01 100644 --- a/src/mixer.cpp +++ b/src/mixer.cpp @@ -35,6 +35,7 @@ struct MixerChannel { }; static std::atomic _active_channels; +static std::atomic _stop_channels; static MixerChannel _channels[8]; static uint32_t _play_rate = 11025; static uint32_t _max_size = UINT_MAX; @@ -108,6 +109,16 @@ static void MxCloseChannel(uint8_t channel_index) _active_channels.fetch_and(~(1 << channel_index), std::memory_order_release); } +/** + * Close all mixer channels. + * This signals to the mixer that each channel should be closed even if it has not played all remaining samples. + * This is safe (and designed) to be called from the main thread. + */ +void MxCloseAllChannels() +{ + _stop_channels.fetch_or(~0, std::memory_order_release); +} + void MxMixSamples(void *buffer, uint samples) { PerformanceMeasurer framerate(PFE_SOUND); @@ -126,6 +137,12 @@ void MxMixSamples(void *buffer, uint samples) if (_music_stream) _music_stream((int16_t*)buffer, samples); } + /* Check if any channels should be stopped. */ + uint8_t stop = _stop_channels.load(std::memory_order_acquire); + for (uint8_t idx : SetBitIterator(stop)) { + MxCloseChannel(idx); + } + /* Apply simple x^3 scaling to master effect volume. This increases the * perceived difference in loudness to better match expectations. effect_vol * is expected to be in the range 0-127 hence the division by 127 * 127 to @@ -200,6 +217,7 @@ void MxSetChannelVolume(MixerChannel *mc, uint volume, float pan) void MxActivateChannel(MixerChannel *mc) { uint8_t channel_index = mc - _channels; + _stop_channels.fetch_and(~(1 << channel_index), std::memory_order_release); _active_channels.fetch_or((1 << channel_index), std::memory_order_release); } diff --git a/src/mixer.h b/src/mixer.h index ad94f5440c..3fbcbc4bc8 100644 --- a/src/mixer.h +++ b/src/mixer.h @@ -27,6 +27,7 @@ MixerChannel *MxAllocateChannel(); void MxSetChannelRawSrc(MixerChannel *mc, int8_t *mem, size_t size, uint rate, bool is16bit); void MxSetChannelVolume(MixerChannel *mc, uint volume, float pan); void MxActivateChannel(MixerChannel*); +void MxCloseAllChannels(); uint32_t MxSetMusicSource(MxStreamCallback music_callback); diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 2043373dc6..bdcc5e5fcd 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -47,6 +47,7 @@ #include "network/network_survey.h" #include "video/video_driver.hpp" #include "social_integration.h" +#include "sound_func.h" #include "safeguards.h" @@ -940,13 +941,7 @@ struct GameOptionsWindow : Window { break; case WID_GO_BASE_SFX_DROPDOWN: - if (_game_mode == GM_MENU) { - auto set = BaseSounds::GetSet(index); - BaseSounds::ini_set = set->name; - BaseSounds::SetSet(set); - this->reload = true; - this->InvalidateData(); - } + ChangeSoundSet(index); break; case WID_GO_BASE_MUSIC_DROPDOWN: @@ -982,7 +977,6 @@ struct GameOptionsWindow : Window { #endif /* HAS_TRUETYPE_FONT */ this->SetWidgetDisabledState(WID_GO_BASE_GRF_DROPDOWN, _game_mode != GM_MENU); - this->SetWidgetDisabledState(WID_GO_BASE_SFX_DROPDOWN, _game_mode != GM_MENU); this->SetWidgetDisabledState(WID_GO_BASE_GRF_PARAMETERS, BaseGraphics::GetUsedSet() == nullptr || !BaseGraphics::GetUsedSet()->IsConfigurable()); diff --git a/src/sound.cpp b/src/sound.cpp index 78b4cc80de..02bdf82d04 100644 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -233,6 +233,34 @@ void SndCopyToPool() } } +/** + * Change the configured sound set and reset sounds. + * @param index Index of sound set to switch to. + */ +void ChangeSoundSet(int index) +{ + if (BaseSounds::GetIndexOfUsedSet() == index) return; + + auto set = BaseSounds::GetSet(index); + BaseSounds::ini_set = set->name; + BaseSounds::SetSet(set); + + MxCloseAllChannels(); + InitializeSound(); + + /* Replace baseset sounds in the pool with the updated original sounds. This is safe to do as + * any sound still playing owns its sample data. */ + for (uint i = 0; i < ORIGINAL_SAMPLE_COUNT; i++) { + SoundEntry *sound = GetSound(i); + /* GRF Container 0 means the sound comes from the baseset, and isn't overridden by NewGRF. */ + if (sound == nullptr || sound->grf_container_ver != 0) continue; + + *sound = _original_sounds[_sound_idx[i]]; + sound->volume = _sound_base_vol[i]; + sound->priority = 0; + } +} + /** * Decide 'where' (between left and right speaker) to play the sound effect. * Note: Callers must determine if sound effects are enabled. This plays a sound regardless of the setting. diff --git a/src/sound_func.h b/src/sound_func.h index b378dbdfe2..164df9fc24 100644 --- a/src/sound_func.h +++ b/src/sound_func.h @@ -14,6 +14,8 @@ #include "vehicle_type.h" #include "tile_type.h" +void ChangeSoundSet(int index); + void SndPlayTileFx(SoundID sound, TileIndex tile); void SndPlayVehicleFx(SoundID sound, const Vehicle *v); void SndPlayFx(SoundID sound);