From 99d490180ce1dcdd5bb0ed449c78c9d6e09ad4d4 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 7 May 2022 13:24:04 +0100 Subject: [PATCH] Settings: Add Resampler Makes the resampler algorithm configurable from the settings menu. --- CMake/Definitions.cmake | 3 + CMakeLists.txt | 16 +++++ Source/gamemenu.cpp | 1 + Source/options.cpp | 134 +++++++++++++++++++++++++++++++++++----- Source/options.h | 50 +++++++++++++-- Source/sound_defs.hpp | 4 +- Source/sound_stubs.cpp | 1 + Source/utils/aulib.hpp | 18 ++++-- 8 files changed, 201 insertions(+), 26 deletions(-) diff --git a/CMake/Definitions.cmake b/CMake/Definitions.cmake index 207815f27..bc9a48183 100644 --- a/CMake/Definitions.cmake +++ b/CMake/Definitions.cmake @@ -15,6 +15,8 @@ foreach( GPERF_HEAP_FIRST_GAME_ITERATION STREAM_ALL_AUDIO PACKET_ENCRYPTION + DEVILUTIONX_RESAMPLER_SPEEX + DEVILUTIONX_RESAMPLER_SDL ) if(${def_name}) list(APPEND DEVILUTIONX_DEFINITIONS ${def_name}) @@ -82,6 +84,7 @@ foreach( JOY_BUTTON_START JOY_BUTTON_BACK REMAP_KEYBOARD_KEYS + DEVILUTIONX_DEFAULT_RESAMPLER ) if(DEFINED ${def_name}) list(APPEND DEVILUTIONX_DEFINITIONS ${def_name}=${${def_name}}) diff --git a/CMakeLists.txt b/CMakeLists.txt index a2802337b..caac7465f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,9 @@ mark_as_advanced(DISABLE_STREAMING_SOUNDS) option(STREAM_ALL_AUDIO "Stream all the audio. For extremely RAM-constrained platforms.") mark_as_advanced(STREAM_ALL_AUDIO) +option(DEVILUTIONX_RESAMPLER_SPEEX "Build with Speex resampler" ON) +option(DEVILUTIONX_RESAMPLER_SDL "Build with SDL resampler" ON) + if(TSAN) set(ASAN OFF) endif() @@ -154,6 +157,19 @@ if(NONET) set(PACKET_ENCRYPTION OFF) endif() +if(USE_SDL1) + set(DEVILUTIONX_RESAMPLER_SDL OFF) +endif() +if(DEVILUTIONX_RESAMPLER_SPEEX) + list(APPEND _resamplers Speex) +endif() +if(DEVILUTIONX_RESAMPLER_SDL) + list(APPEND _resamplers SDL) +endif() +list(GET _resamplers 0 _default_resampler) +set(DEVILUTIONX_DEFAULT_RESAMPLER ${_default_resampler} CACHE STRING "Default resampler") +set_property(CACHE DEVILUTIONX_DEFAULT_RESAMPLER PROPERTY STRINGS ${_resamplers}) + find_program(CCACHE_PROGRAM ccache) if(CCACHE_PROGRAM) set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") diff --git a/Source/gamemenu.cpp b/Source/gamemenu.cpp index 993ee17c0..02b8ea356 100644 --- a/Source/gamemenu.cpp +++ b/Source/gamemenu.cpp @@ -13,6 +13,7 @@ #include "options.h" #include "pfile.h" #include "sound.h" +#include "sound_defs.hpp" #include "utils/language.h" namespace devilution { diff --git a/Source/options.cpp b/Source/options.cpp index 604abe28b..4d8d3e3c1 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -64,6 +64,15 @@ constexpr OptionEntryFlags OnlyIfSupportsWindowed = OptionEntryFlags::Invisible; constexpr OptionEntryFlags OnlyIfSupportsWindowed = OptionEntryFlags::None; #endif +constexpr size_t NumResamplers = +#ifdef DEVILUTIONX_RESAMPLER_SPEEX + 1 + +#endif +#ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER + 1 + +#endif + 0; + std::string GetIniPath() { auto path = paths::ConfigPath() + std::string("diablo.ini"); @@ -145,9 +154,11 @@ float GetIniFloat(const char *sectionName, const char *keyName, float defaultVal return (float)GetIni().GetDoubleValue(sectionName, keyName, defaultValue); } -bool GetIniValue(const char *sectionName, const char *keyName, char *string, int stringSize, const char *defaultString = "") +bool GetIniValue(string_view sectionName, string_view keyName, char *string, int stringSize, const char *defaultString = "") { - const char *value = GetIni().GetValue(sectionName, keyName); + std::string sectionNameStr { sectionName }; + std::string keyNameStr { keyName }; + const char *value = GetIni().GetValue(sectionNameStr.c_str(), keyNameStr.c_str()); if (value == nullptr) { CopyUtf8(string, defaultString, stringSize); return false; @@ -186,11 +197,14 @@ void SetIniValue(const char *keyname, const char *valuename, float value) GetIni().SetDoubleValue(keyname, valuename, value, nullptr, true); } -void SetIniValue(const char *sectionName, const char *keyName, const char *value) +void SetIniValue(string_view sectionName, string_view keyName, string_view value) { - IniChangedChecker changedChecker(sectionName, keyName); + std::string sectionNameStr { sectionName }; + std::string keyNameStr { keyName }; + std::string valueStr { value }; + IniChangedChecker changedChecker(sectionNameStr.c_str(), keyNameStr.c_str()); auto &ini = GetIni(); - ini.SetValue(sectionName, keyName, value, nullptr, true); + ini.SetValue(sectionNameStr.c_str(), keyNameStr.c_str(), valueStr.c_str(), nullptr, true); } void SetIniValue(const char *keyname, const char *valuename, const std::vector &stringValues) @@ -612,19 +626,13 @@ AudioOptions::AudioOptions() , sampleRate("Sample Rate", OptionEntryFlags::CantChangeInGame, N_("Sample Rate"), N_("Output sample rate (Hz)."), DEFAULT_AUDIO_SAMPLE_RATE, { 22050, 44100, 48000 }) , channels("Channels", OptionEntryFlags::CantChangeInGame, N_("Channels"), N_("Number of output channels."), DEFAULT_AUDIO_CHANNELS, { 1, 2 }) , bufferSize("Buffer Size", OptionEntryFlags::CantChangeInGame, N_("Buffer Size"), N_("Buffer size (number of frames per channel)."), DEFAULT_AUDIO_BUFFER_SIZE, { 1024, 2048, 5120 }) - , resamplingQuality("Resampling Quality", - OptionEntryFlags::CantChangeInGame | -#ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER - OptionEntryFlags::Invisible, -#else - OptionEntryFlags::None, -#endif - N_("Resampling Quality"), N_("Quality of the resampler, from 0 (lowest) to 10 (highest)."), DEFAULT_AUDIO_RESAMPLING_QUALITY, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) + , resamplingQuality("Resampling Quality", OptionEntryFlags::CantChangeInGame, N_("Resampling Quality"), N_("Quality of the resampler, from 0 (lowest) to 10 (highest)."), DEFAULT_AUDIO_RESAMPLING_QUALITY, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) { sampleRate.SetValueChangedCallback(OptionAudioChanged); channels.SetValueChangedCallback(OptionAudioChanged); bufferSize.SetValueChangedCallback(OptionAudioChanged); resamplingQuality.SetValueChangedCallback(OptionAudioChanged); + resampler.SetValueChangedCallback(OptionAudioChanged); } std::vector AudioOptions::GetEntries() { @@ -637,6 +645,7 @@ std::vector AudioOptions::GetEntries() &sampleRate, &channels, &bufferSize, + &resampler, &resamplingQuality, }; } @@ -737,6 +746,67 @@ void OptionEntryResolution::SetActiveListIndex(size_t index) NotifyValueChanged(); } +OptionEntryResampler::OptionEntryResampler() + : OptionEntryListBase("Resampler", OptionEntryFlags::CantChangeInGame + // When there are exactly 2 options there is no submenu, so we need to recreate the UI + // to reflect the change in the "Resampling quality" setting visibility. + | (NumResamplers == 2 ? OptionEntryFlags::RecreateUI : OptionEntryFlags::None), + N_("Resampler"), N_("Audio resampler")) +{ +} +void OptionEntryResampler::LoadFromIni(string_view category) +{ + char resamplerStr[32]; + if (GetIniValue(category, key, resamplerStr, sizeof(resamplerStr))) { + std::optional resampler = ResamplerFromString(resamplerStr); + if (resampler) { + resampler_ = *resampler; + UpdateDependentOptions(); + return; + } + } + resampler_ = Resampler::DEVILUTIONX_DEFAULT_RESAMPLER; + UpdateDependentOptions(); +} + +void OptionEntryResampler::SaveToIni(string_view category) const +{ + SetIniValue(category, key, ResamplerToString(resampler_)); +} + +size_t OptionEntryResampler::GetListSize() const +{ + return NumResamplers; +} + +string_view OptionEntryResampler::GetListDescription(size_t index) const +{ + return ResamplerToString(static_cast(index)); +} + +size_t OptionEntryResampler::GetActiveListIndex() const +{ + return static_cast(resampler_); +} + +void OptionEntryResampler::SetActiveListIndex(size_t index) +{ + resampler_ = static_cast(index); + UpdateDependentOptions(); + NotifyValueChanged(); +} + +void OptionEntryResampler::UpdateDependentOptions() const +{ +#ifdef DEVILUTIONX_RESAMPLER_SPEEX + if (resampler_ == Resampler::Speex) { + sgOptions.Audio.resamplingQuality.flags &= ~OptionEntryFlags::Invisible; + } else { + sgOptions.Audio.resamplingQuality.flags |= OptionEntryFlags::Invisible; + } +#endif +} + GraphicsOptions::GraphicsOptions() : OptionCategoryBase("Graphics", N_("Graphics"), N_("Graphics Settings")) , fullscreen("Fullscreen", OnlyIfSupportsWindowed | OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Fullscreen"), N_("Display the game in windowed or fullscreen mode."), true) @@ -922,7 +992,7 @@ OptionEntryLanguageCode::OptionEntryLanguageCode() } void OptionEntryLanguageCode::LoadFromIni(string_view category) { - if (GetIniValue(category.data(), key.data(), szCode, sizeof(szCode))) { + if (GetIniValue(category, key, szCode, sizeof(szCode))) { if (HasTranslation(szCode)) { // User preferred language is available return; @@ -965,7 +1035,7 @@ void OptionEntryLanguageCode::LoadFromIni(string_view category) } void OptionEntryLanguageCode::SaveToIni(string_view category) const { - SetIniValue(category.data(), key.data(), szCode); + SetIniValue(category, key, szCode); } void OptionEntryLanguageCode::CheckLanguagesAreInitialized() const @@ -1245,4 +1315,38 @@ uint32_t KeymapperOptions::KeyForAction(string_view actionName) const return DVL_VK_INVALID; } +namespace { +constexpr char ResamplerSpeex[] = "Speex"; +constexpr char ResamplerSDL[] = "SDL"; +} // namespace + +string_view ResamplerToString(Resampler resampler) +{ + switch (resampler) { +#ifdef DEVILUTIONX_RESAMPLER_SPEEX + case Resampler::Speex: + return ResamplerSpeex; +#endif +#ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER + case Resampler::SDL: + return ResamplerSDL; +#endif + default: + return ""; + } +} + +std::optional ResamplerFromString(string_view resampler) +{ +#ifdef DEVILUTIONX_RESAMPLER_SPEEX + if (resampler == ResamplerSpeex) + return Resampler::Speex; +#endif +#ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER + if (resampler == ResamplerSDL) + return Resampler::SDL; +#endif + return std::nullopt; +} + } // namespace devilution diff --git a/Source/options.h b/Source/options.h index 3fe36d211..148b673da 100644 --- a/Source/options.h +++ b/Source/options.h @@ -7,7 +7,9 @@ #include #include "pack.h" +#include "sound_defs.hpp" #include "utils/enum_traits.h" +#include "utils/stdcompat/optional.hpp" #include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -41,6 +43,18 @@ enum class ScalingQuality { AnisotropicFiltering, }; +enum class Resampler { +#ifdef DEVILUTIONX_RESAMPLER_SPEEX + Speex = 0, +#endif +#ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER + SDL, +#endif +}; + +string_view ResamplerToString(Resampler resampler); +std::optional ResamplerFromString(string_view resampler); + enum class OptionEntryType { Boolean, List, @@ -72,8 +86,8 @@ use_enum_as_flags(OptionEntryFlags); class OptionEntryBase { public: OptionEntryBase(string_view key, OptionEntryFlags flags, string_view name, string_view description) - : key(key) - , flags(flags) + : flags(flags) + , key(key) , name(name) , description(description) { @@ -89,9 +103,10 @@ public: virtual void LoadFromIni(string_view category) = 0; virtual void SaveToIni(string_view category) const = 0; + OptionEntryFlags flags; + protected: string_view key; - OptionEntryFlags flags; string_view name; string_view description; void NotifyValueChanged(); @@ -301,6 +316,29 @@ private: void CheckResolutionsAreInitialized() const; }; +class OptionEntryResampler : public OptionEntryListBase { +public: + OptionEntryResampler(); + + void LoadFromIni(string_view category) override; + void SaveToIni(string_view category) const override; + + [[nodiscard]] size_t GetListSize() const override; + [[nodiscard]] string_view GetListDescription(size_t index) const override; + [[nodiscard]] size_t GetActiveListIndex() const override; + void SetActiveListIndex(size_t index) override; + + Resampler operator*() const + { + return resampler_; + } + +private: + void UpdateDependentOptions() const; + + Resampler resampler_; +}; + struct OptionCategoryBase { OptionCategoryBase(string_view key, string_view name, string_view description); @@ -366,13 +404,15 @@ struct AudioOptions : OptionCategoryBase { /** @brief Picking up items emits the items pickup sound. */ OptionEntryBoolean itemPickupSound; - /** @brief Output sample rate (Hz) */ + /** @brief Output sample rate (Hz). */ OptionEntryInt sampleRate; /** @brief The number of output channels (1 or 2) */ OptionEntryInt channels; /** @brief Buffer size (number of frames per channel) */ OptionEntryInt bufferSize; - /** @brief Quality of the resampler, from 0 (lowest) to 10 (highest) */ + /** @brief Resampler implementation. */ + OptionEntryResampler resampler; + /** @brief Quality of the resampler, from 0 (lowest) to 10 (highest). Available for the speex resampler only. */ OptionEntryInt resamplingQuality; }; diff --git a/Source/sound_defs.hpp b/Source/sound_defs.hpp index c0909220d..d8e1b9261 100644 --- a/Source/sound_defs.hpp +++ b/Source/sound_defs.hpp @@ -2,6 +2,8 @@ #include +#include "utils/stdcompat/string_view.hpp" + #define VOLUME_MIN -1600 #define VOLUME_MAX 0 #define VOLUME_STEPS 64 @@ -12,6 +14,6 @@ #define PAN_MIN -6400 #define PAN_MAX 6400 -#if SDL_VERSION_ATLEAST(2, 0, 7) +#if SDL_VERSION_ATLEAST(2, 0, 7) && defined(DEVILUTIONX_RESAMPLER_SDL) #define DVL_AULIB_SUPPORTS_SDL_RESAMPLER #endif diff --git a/Source/sound_stubs.cpp b/Source/sound_stubs.cpp index 9e11a0f8b..492ed39c2 100644 --- a/Source/sound_stubs.cpp +++ b/Source/sound_stubs.cpp @@ -6,6 +6,7 @@ namespace devilution { bool gbSndInited; bool gbMusicOn; bool gbSoundOn; +_music_id sgnMusicTrack = NUM_MUSIC; // Disable clang-format here because our config says: // AllowShortFunctionsOnASingleLine: None diff --git a/Source/utils/aulib.hpp b/Source/utils/aulib.hpp index c4a7a7e91..959072a34 100644 --- a/Source/utils/aulib.hpp +++ b/Source/utils/aulib.hpp @@ -5,10 +5,12 @@ #include +#ifdef DEVILUTIONX_RESAMPLER_SPEEX +#include +#endif + #ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER #include -#else -#include #endif #include "options.h" @@ -17,11 +19,17 @@ namespace devilution { inline std::unique_ptr CreateAulibResampler() { + switch (*sgOptions.Audio.resampler) { +#ifdef DEVILUTIONX_RESAMPLER_SPEEX + case Resampler::Speex: + return std::make_unique(*sgOptions.Audio.resamplingQuality); +#endif #ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER - return std::make_unique(); -#else - return std::make_unique(*sgOptions.Audio.resamplingQuality); + case Resampler::SDL: + return std::make_unique(); #endif + } + return nullptr; } } // namespace devilution