diff --git a/Source/options.cpp b/Source/options.cpp index 5f8c1d8a5..03e0c1473 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -638,9 +638,11 @@ AudioOptions::AudioOptions() bufferSize.SetValueChangedCallback(OptionAudioChanged); resamplingQuality.SetValueChangedCallback(OptionAudioChanged); resampler.SetValueChangedCallback(OptionAudioChanged); + device.SetValueChangedCallback(OptionAudioChanged); } std::vector AudioOptions::GetEntries() { + // clang-format off return { &soundVolume, &musicVolume, @@ -652,7 +654,11 @@ std::vector AudioOptions::GetEntries() &bufferSize, &resampler, &resamplingQuality, +#if SDL_VERSION_ATLEAST(2, 0, 0) + &device, +#endif }; + // clang-format on } OptionEntryResolution::OptionEntryResolution() @@ -812,6 +818,74 @@ void OptionEntryResampler::UpdateDependentOptions() const #endif } +OptionEntryAudioDevice::OptionEntryAudioDevice() + : OptionEntryListBase("Device", OptionEntryFlags::CantChangeInGame, N_("Device"), N_("Audio device")) +{ +} +void OptionEntryAudioDevice::LoadFromIni(string_view category) +{ + char deviceStr[100]; + GetIniValue(category, key, deviceStr, sizeof(deviceStr), ""); + deviceName_ = deviceStr; +} + +void OptionEntryAudioDevice::SaveToIni(string_view category) const +{ +#if SDL_VERSION_ATLEAST(2, 0, 0) + SetIniValue(category, key, deviceName_); +#endif +} + +size_t OptionEntryAudioDevice::GetListSize() const +{ +#if SDL_VERSION_ATLEAST(2, 0, 0) + return SDL_GetNumAudioDevices(false) + 1; +#else + return 1; +#endif +} + +string_view OptionEntryAudioDevice::GetListDescription(size_t index) const +{ + constexpr size_t MaxWidth = 500; + + string_view deviceName = GetDeviceName(index); + if (deviceName.empty()) + return "System Default"; + + while (GetLineWidth(deviceName, GameFont24, 1) > MaxWidth) { + size_t lastSymbolIndex = FindLastUtf8Symbols(deviceName); + deviceName = string_view(deviceName.data(), lastSymbolIndex); + } + + return deviceName; +} + +size_t OptionEntryAudioDevice::GetActiveListIndex() const +{ + for (size_t i = 0; i < GetListSize(); i++) { + string_view deviceName = GetDeviceName(i); + if (deviceName == deviceName_) + return i; + } + return 0; +} + +void OptionEntryAudioDevice::SetActiveListIndex(size_t index) +{ + deviceName_ = std::string { GetDeviceName(index) }; + NotifyValueChanged(); +} + +string_view OptionEntryAudioDevice::GetDeviceName(size_t index) const +{ +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (index != 0) + return SDL_GetAudioDeviceName(index - 1, false); +#endif + return ""; +} + 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) diff --git a/Source/options.h b/Source/options.h index 148b673da..68b10aeb2 100644 --- a/Source/options.h +++ b/Source/options.h @@ -339,6 +339,34 @@ private: Resampler resampler_; }; +class OptionEntryAudioDevice : public OptionEntryListBase { +public: + OptionEntryAudioDevice(); + + 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; + + std::string operator*() const + { + for (size_t i = 0; i < GetListSize(); i++) { + string_view deviceName = GetDeviceName(i); + if (deviceName == deviceName_) + return deviceName_; + } + return ""; + } + +private: + string_view GetDeviceName(size_t index) const; + + std::string deviceName_; +}; + struct OptionCategoryBase { OptionCategoryBase(string_view key, string_view name, string_view description); @@ -414,6 +442,8 @@ struct AudioOptions : OptionCategoryBase { OptionEntryResampler resampler; /** @brief Quality of the resampler, from 0 (lowest) to 10 (highest). Available for the speex resampler only. */ OptionEntryInt resamplingQuality; + /** @brief Audio device. */ + OptionEntryAudioDevice device; }; struct GraphicsOptions : OptionCategoryBase { diff --git a/Source/sound.cpp b/Source/sound.cpp index f856442bf..ea571e6db 100644 --- a/Source/sound.cpp +++ b/Source/sound.cpp @@ -201,7 +201,7 @@ void snd_init() // Initialize the SDL_audiolib library. Set the output sample rate to // 22kHz, the audio format to 16-bit signed, use 2 output channels // (stereo), and a 2KiB output buffer. - if (!Aulib::init(*sgOptions.Audio.sampleRate, AUDIO_S16, *sgOptions.Audio.channels, *sgOptions.Audio.bufferSize)) { + if (!Aulib::init(*sgOptions.Audio.sampleRate, AUDIO_S16, *sgOptions.Audio.channels, *sgOptions.Audio.bufferSize, *sgOptions.Audio.device)) { LogError(LogCategory::Audio, "Failed to initialize audio (Aulib::init): {}", SDL_GetError()); return; }