Browse Source

Settings: Add Resampler

Makes the resampler algorithm configurable from the settings menu.
pull/4544/merge
Gleb Mazovetskiy 4 years ago
parent
commit
99d490180c
  1. 3
      CMake/Definitions.cmake
  2. 16
      CMakeLists.txt
  3. 1
      Source/gamemenu.cpp
  4. 134
      Source/options.cpp
  5. 50
      Source/options.h
  6. 4
      Source/sound_defs.hpp
  7. 1
      Source/sound_stubs.cpp
  8. 18
      Source/utils/aulib.hpp

3
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}})

16
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}")

1
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 {

134
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<std::string> &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<OptionEntryBase *> AudioOptions::GetEntries()
{
@ -637,6 +645,7 @@ std::vector<OptionEntryBase *> 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> 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<Resampler>(index));
}
size_t OptionEntryResampler::GetActiveListIndex() const
{
return static_cast<size_t>(resampler_);
}
void OptionEntryResampler::SetActiveListIndex(size_t index)
{
resampler_ = static_cast<Resampler>(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<Resampler> 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

50
Source/options.h

@ -7,7 +7,9 @@
#include <SDL_version.h>
#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<Resampler> 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<std::uint32_t> sampleRate;
/** @brief The number of output channels (1 or 2) */
OptionEntryInt<std::uint8_t> channels;
/** @brief Buffer size (number of frames per channel) */
OptionEntryInt<std::uint32_t> 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<std::uint8_t> resamplingQuality;
};

4
Source/sound_defs.hpp

@ -2,6 +2,8 @@
#include <SDL_version.h>
#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

1
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

18
Source/utils/aulib.hpp

@ -5,10 +5,12 @@
#include <Aulib/Stream.h>
#ifdef DEVILUTIONX_RESAMPLER_SPEEX
#include <Aulib/ResamplerSpeex.h>
#endif
#ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER
#include <Aulib/ResamplerSdl.h>
#else
#include <Aulib/ResamplerSpeex.h>
#endif
#include "options.h"
@ -17,11 +19,17 @@ namespace devilution {
inline std::unique_ptr<Aulib::Resampler> CreateAulibResampler()
{
switch (*sgOptions.Audio.resampler) {
#ifdef DEVILUTIONX_RESAMPLER_SPEEX
case Resampler::Speex:
return std::make_unique<Aulib::ResamplerSpeex>(*sgOptions.Audio.resamplingQuality);
#endif
#ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER
return std::make_unique<Aulib::ResamplerSdl>();
#else
return std::make_unique<Aulib::ResamplerSpeex>(*sgOptions.Audio.resamplingQuality);
case Resampler::SDL:
return std::make_unique<Aulib::ResamplerSdl>();
#endif
}
return nullptr;
}
} // namespace devilution

Loading…
Cancel
Save