From 459c604b4ab9495541542c2b0119f19a7f2fbe02 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sat, 25 Jul 2020 20:25:46 +0300 Subject: [PATCH 01/56] Everything --- .../FreeSurround/FreeSurroundDecoder.h | 4 + .../source/FreeSurroundDecoder.cpp | 4 +- Source/Android/jni/MainAndroid.cpp | 5 + Source/Core/AudioCommon/AlsaSoundStream.cpp | 5 - Source/Core/AudioCommon/AlsaSoundStream.h | 3 +- Source/Core/AudioCommon/AudioCommon.cpp | 246 ++++- Source/Core/AudioCommon/AudioCommon.h | 17 +- Source/Core/AudioCommon/AudioSpeedCounter.cpp | 238 +++++ Source/Core/AudioCommon/AudioSpeedCounter.h | 65 ++ Source/Core/AudioCommon/AudioStretcher.cpp | 109 +- Source/Core/AudioCommon/AudioStretcher.h | 16 +- Source/Core/AudioCommon/CMakeLists.txt | 4 + Source/Core/AudioCommon/CubebStream.cpp | 30 +- Source/Core/AudioCommon/Mixer.cpp | 881 +++++++++++++--- Source/Core/AudioCommon/Mixer.h | 161 ++- Source/Core/AudioCommon/NullSoundStream.cpp | 12 - Source/Core/AudioCommon/NullSoundStream.h | 7 +- Source/Core/AudioCommon/OpenALStream.cpp | 31 +- Source/Core/AudioCommon/OpenALStream.h | 4 +- Source/Core/AudioCommon/OpenSLESStream.h | 2 +- Source/Core/AudioCommon/PulseAudioStream.h | 2 +- Source/Core/AudioCommon/SoundStream.cpp | 10 + Source/Core/AudioCommon/SoundStream.h | 12 +- Source/Core/AudioCommon/SurroundDecoder.cpp | 61 +- Source/Core/AudioCommon/SurroundDecoder.h | 10 +- Source/Core/AudioCommon/WASAPIStream.cpp | 979 +++++++++++++++--- Source/Core/AudioCommon/WASAPIStream.h | 55 +- Source/Core/Common/FixedSizeQueue.h | 23 +- Source/Core/Core/Config/MainSettings.cpp | 5 +- Source/Core/Core/Config/MainSettings.h | 4 +- .../Core/ConfigLoaders/IsSettingSaveable.cpp | 3 +- Source/Core/Core/ConfigManager.cpp | 18 +- Source/Core/Core/ConfigManager.h | 12 +- Source/Core/Core/HW/AudioInterface.cpp | 27 +- Source/Core/Core/HW/AudioInterface.h | 10 +- Source/Core/Core/HW/DSP.cpp | 12 +- Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp | 2 +- Source/Core/Core/HW/DSPHLE/UCodes/AX.h | 2 +- Source/Core/Core/HW/DVD/DVDInterface.cpp | 23 +- Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp | 2 +- Source/Core/Core/HW/GCPadEmu.cpp | 4 - Source/Core/Core/HW/SystemTimers.cpp | 12 +- Source/Core/Core/HW/VideoInterface.cpp | 9 +- Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp | 2 +- Source/Core/Core/HW/WiimoteEmu/Speaker.cpp | 105 +- Source/Core/Core/HW/WiimoteEmu/Speaker.h | 8 +- Source/Core/Core/Host.h | 1 + Source/Core/Core/HotkeyManager.h | 1 + Source/Core/Core/IOS/USB/USB_KBD.cpp | 2 +- Source/Core/DolphinNoGUI/MainNoGUI.cpp | 6 + .../Config/Mapping/MappingIndicator.cpp | 5 +- .../Config/Mapping/MappingIndicator.h | 5 +- .../Config/Mapping/MappingWidget.cpp | 2 +- .../Config/Mapping/MappingWindow.cpp | 3 + Source/Core/DolphinQt/Host.cpp | 16 + Source/Core/DolphinQt/Host.h | 3 + Source/Core/DolphinQt/HotkeyScheduler.cpp | 8 +- Source/Core/DolphinQt/HotkeyScheduler.h | 1 + Source/Core/DolphinQt/MainWindow.h | 1 + Source/Core/DolphinQt/Settings.cpp | 11 + Source/Core/DolphinQt/Settings.h | 3 + Source/Core/DolphinQt/Settings/AudioPane.cpp | 341 +++++- Source/Core/DolphinQt/Settings/AudioPane.h | 16 +- .../Core/DolphinQt/Settings/GeneralPane.cpp | 13 +- .../Core/DolphinQt/Settings/InterfacePane.h | 1 + .../ControlReference/ControlReference.cpp | 215 +++- .../ControlReference/ControlReference.h | 32 +- .../ControlReference/ExpressionParser.h | 3 + .../ControlReference/FunctionExpression.h | 2 + .../ControlGroup/AnalogStick.cpp | 2 +- .../ControlGroup/ControlGroup.cpp | 17 +- .../ControllerEmu/ControlGroup/ControlGroup.h | 7 +- .../ControllerEmu/ControlGroup/Cursor.h | 22 +- .../ControlGroup/IMUGyroscope.cpp | 2 +- .../ControllerEmu/ControlGroup/Tilt.cpp | 2 +- .../ControllerEmu/Setting/NumericSetting.h | 8 +- .../InputCommon/ControllerEmu/StickGate.cpp | 17 +- .../InputCommon/ControllerEmu/StickGate.h | 2 +- .../ControllerInterface.cpp | 6 +- .../ControllerInterface/CoreDevice.h | 46 +- .../DualShockUDPClient/DualShockUDPClient.cpp | 3 + .../Quartz/QuartzKeyboardAndMouse.h | 9 + .../Wiimote/WiimoteController.cpp | 19 +- .../ControllerInterface/XInput/XInput.cpp | 5 + .../ControllerInterface/Xlib/XInput2.h | 13 + Source/DSPTool/StubHost.cpp | 4 + Source/UnitTests/StubHost.cpp | 4 + 87 files changed, 3357 insertions(+), 778 deletions(-) create mode 100644 Source/Core/AudioCommon/AudioSpeedCounter.cpp create mode 100644 Source/Core/AudioCommon/AudioSpeedCounter.h create mode 100644 Source/Core/AudioCommon/SoundStream.cpp diff --git a/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h b/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h index 0aa51d57396d..34a93964cc57 100644 --- a/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h +++ b/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h @@ -15,6 +15,10 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, // USA. +// Dolphin: taken from https://hydrogenaud.io/index.php?topic=52235.0 but heavily modified +// A more up to date version is available here: +// https://github.com/kodi-adsp/adsp.freesurround/blob/master/src/FreeSurroundDecoder.cpp + #ifndef FREESURROUND_DECODER_H #define FREESURROUND_DECODER_H #include "KissFFTR.h" diff --git a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp index b65734a5cb27..b5db168f8fa2 100644 --- a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp +++ b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp @@ -72,8 +72,8 @@ void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, set_center_image(1); set_front_separation(1); set_rear_separation(1); - set_low_cutoff(40.0f / samplerate * 2); - set_high_cutoff(90.0f / samplerate * 2); + set_low_cutoff(40.0f / (samplerate / 2.f)); + set_high_cutoff(90.0f / (samplerate / 2.f)); set_bass_redirection(false); initialized = true; diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 1b698d2a17f4..231142bec229 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -151,6 +151,11 @@ bool Host_RendererHasFocus() return true; } +bool Host_RendererHasFullFocus() +{ + return true; +} + bool Host_RendererIsFullscreen() { return false; diff --git a/Source/Core/AudioCommon/AlsaSoundStream.cpp b/Source/Core/AudioCommon/AlsaSoundStream.cpp index 9c52efc2699f..1e290858be74 100644 --- a/Source/Core/AudioCommon/AlsaSoundStream.cpp +++ b/Source/Core/AudioCommon/AlsaSoundStream.cpp @@ -42,11 +42,6 @@ bool AlsaSound::Init() return true; } -void AlsaSound::Update() -{ - // don't need to do anything here. -} - // Called on audio thread. void AlsaSound::SoundLoop() { diff --git a/Source/Core/AudioCommon/AlsaSoundStream.h b/Source/Core/AudioCommon/AlsaSoundStream.h index 8cfde375d3e1..ecb7d3214458 100644 --- a/Source/Core/AudioCommon/AlsaSoundStream.h +++ b/Source/Core/AudioCommon/AlsaSoundStream.h @@ -25,10 +25,9 @@ class AlsaSound final : public SoundStream bool Init() override; void SoundLoop() override; - void Update() override; bool SetRunning(bool running) override; - static bool isValid() { return true; } + static bool IsValid() { return true; } private: // maximum number of frames the buffer can hold diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index e6a9e679ff61..4ae84647463e 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -14,11 +14,20 @@ #include "Common/Common.h" #include "Common/FileUtil.h" #include "Common/Logging/Log.h" +#include "Common/ScopeGuard.h" #include "Core/ConfigManager.h" +#ifdef _WIN32 +#include +#include +#include +#endif + // This shouldn't be a global, at least not here. std::unique_ptr g_sound_stream; +std::mutex g_sound_stream_mutex; + namespace AudioCommon { static bool s_audio_dump_start = false; @@ -29,29 +38,26 @@ constexpr int AUDIO_VOLUME_MAX = 100; static std::unique_ptr CreateSoundStreamForBackend(std::string_view backend) { + // We only check IsValid on backends that are only available on some platforms if (backend == BACKEND_CUBEB) return std::make_unique(); - else if (backend == BACKEND_OPENAL && OpenALStream::isValid()) + else if (backend == BACKEND_OPENAL && OpenALStream::IsValid()) return std::make_unique(); else if (backend == BACKEND_NULLSOUND) return std::make_unique(); - else if (backend == BACKEND_ALSA && AlsaSound::isValid()) + else if (backend == BACKEND_ALSA && AlsaSound::IsValid()) return std::make_unique(); - else if (backend == BACKEND_PULSEAUDIO && PulseAudio::isValid()) + else if (backend == BACKEND_PULSEAUDIO && PulseAudio::IsValid()) return std::make_unique(); - else if (backend == BACKEND_OPENSLES && OpenSLESStream::isValid()) + else if (backend == BACKEND_OPENSLES && OpenSLESStream::IsValid()) return std::make_unique(); - else if (backend == BACKEND_WASAPI && WASAPIStream::isValid()) + else if (backend == BACKEND_WASAPI && WASAPIStream::IsValid()) return std::make_unique(); return {}; } void InitSoundStream() { - std::string backend = SConfig::GetInstance().sBackend; - g_sound_stream = CreateSoundStreamForBackend(backend); - - if (!g_sound_stream) { WARN_LOG_FMT(AUDIO, "Unknown backend {}, using {} instead.", backend, GetDefaultSoundBackend()); backend = GetDefaultSoundBackend(); @@ -71,6 +77,34 @@ void PostInitSoundStream() { // This needs to be called after AudioInterface::Init where input sample rates are set UpdateSoundStream(); + std::lock_guard guard(g_sound_stream_mutex); + + std::string backend = SConfig::GetInstance().sBackend; + g_sound_stream = CreateSoundStreamForBackend(backend); + + if (!g_sound_stream) + { + WARN_LOG(AUDIO, "Unknown backend %s, using %s instead", backend.c_str(), + GetDefaultSoundBackend().c_str()); + backend = GetDefaultSoundBackend(); + g_sound_stream = CreateSoundStreamForBackend(GetDefaultSoundBackend()); + } + + if (!g_sound_stream || !g_sound_stream->Init()) + { + WARN_LOG(AUDIO, "Could not initialize backend %s, using %s instead", backend.c_str(), + BACKEND_NULLSOUND); + g_sound_stream = std::make_unique(); + g_sound_stream->Init(); + } + } + + UpdateSoundStreamSettings(true); + // This can fail, but we don't really care as it just won't produce any sounds, + // also the user might be able to fix it up by changing his device settings + // and pausing and unpausing the emulation. + // Note that when we start a game, this is called here, but then it's called + // with false and true again, so basically the backend is restarted SetSoundStreamRunning(true); if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_start) @@ -85,7 +119,10 @@ void ShutdownSoundStream() StopAudioDump(); SetSoundStreamRunning(false); - g_sound_stream.reset(); + { + std::lock_guard guard(g_sound_stream_mutex); + g_sound_stream.reset(); + } INFO_LOG_FMT(AUDIO, "Done shutting down sound stream"); } @@ -115,15 +152,15 @@ std::vector GetSoundBackends() backends.emplace_back(BACKEND_NULLSOUND); backends.emplace_back(BACKEND_CUBEB); - if (AlsaSound::isValid()) + if (AlsaSound::IsValid()) backends.emplace_back(BACKEND_ALSA); - if (PulseAudio::isValid()) + if (PulseAudio::IsValid()) backends.emplace_back(BACKEND_PULSEAUDIO); - if (OpenALStream::isValid()) + if (OpenALStream::IsValid()) backends.emplace_back(BACKEND_OPENAL); - if (OpenSLESStream::isValid()) + if (OpenSLESStream::IsValid()) backends.emplace_back(BACKEND_OPENSLES); - if (WASAPIStream::isValid()) + if (WASAPIStream::IsValid()) backends.emplace_back(BACKEND_WASAPI); return backends; @@ -131,54 +168,192 @@ std::vector GetSoundBackends() bool SupportsDPL2Decoder(std::string_view backend) { -#ifndef __APPLE__ if (backend == BACKEND_OPENAL) return true; -#endif if (backend == BACKEND_CUBEB) return true; if (backend == BACKEND_PULSEAUDIO) return true; + if (backend == BACKEND_WASAPI) + return true; return false; } bool SupportsLatencyControl(std::string_view backend) { - return backend == BACKEND_OPENAL || backend == BACKEND_WASAPI; + // TODO: we should ask the backends whether they support this +#ifdef _WIN32 + // Cubeb only supports custom latency on Windows + return backend == BACKEND_OPENAL || backend == BACKEND_WASAPI || backend == BACKEND_CUBEB; +#else + return false; +#endif } bool SupportsVolumeChanges(std::string_view backend) { - // FIXME: this one should ask the backend whether it supports it. - // but getting the backend from string etc. is probably - // too much just to enable/disable a stupid slider... - return backend == BACKEND_CUBEB || backend == BACKEND_OPENAL || backend == BACKEND_WASAPI; + // TODO: we should ask the backends whether they support this + return backend == BACKEND_CUBEB || backend == BACKEND_OPENAL || backend == BACKEND_WASAPI || + BACKEND_OPENSLES; } -void UpdateSoundStream() +bool SupportsRuntimeSettingsChanges() { + std::lock_guard guard(g_sound_stream_mutex); + if (g_sound_stream) + { + return g_sound_stream->SupportsRuntimeSettingsChanges(); + } + return false; +} + +unsigned long GetDefaultSampleRate() +{ + return 48000ul; +} + +unsigned long GetMaxSupportedLatency() +{ + return (Mixer::MAX_SAMPLES * 1000) / (54000000.0 / 1124.0); +} + +unsigned long GetUserTargetLatency() +{ + // iLatency is not unsigned + unsigned long target_latency = std::max(SConfig::GetInstance().iLatency, 0); + return std::min(target_latency, AudioCommon::GetMaxSupportedLatency()); +} + +unsigned long GetOSMixerSampleRate() +{ +#ifdef _WIN32 + HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (result != RPC_E_CHANGED_MODE && FAILED(result)) + { + ERROR_LOG(AUDIO, "Failed to initialize the COM library"); + return 0; + } + Common::ScopeGuard uninit([result] { + if (SUCCEEDED(result)) + CoUninitialize(); + }); + + IMMDeviceEnumerator* enumerator = nullptr; + IMMDevice* device = nullptr; + IAudioClient* audio_client = nullptr; + + result = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)); + + if (result != S_OK) + { + _com_error err(result); + std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); + ERROR_LOG(AUDIO, "Failed to create MMDeviceEnumerator: (%s)", error.c_str()); + return 0; + } + + result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + + if (result != S_OK) + { + _com_error err(result); + std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); + ERROR_LOG(AUDIO, "Failed to obtain default endpoint: (%s)", error.c_str()); + enumerator->Release(); + return 0; + } + + result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, + reinterpret_cast(&audio_client)); + + if (result != S_OK) + { + _com_error err(result); + std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); + ERROR_LOG(AUDIO, "Failed to reactivate IAudioClient: (%s)", error.c_str()); + device->Release(); + enumerator->Release(); + return 0; + } + + WAVEFORMATEX* mixer_format; + result = audio_client->GetMixFormat(&mixer_format); + + DWORD sample_rate; + + if (result != S_OK) + { + _com_error err(result); + std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); + ERROR_LOG(AUDIO, "Failed to retrieve the mixer format: (%s)", error.c_str()); + // Return the default Dolphin sample rate, hoping it would work + sample_rate = GetDefaultSampleRate(); + } + else + { + sample_rate = mixer_format->nSamplesPerSec; + CoTaskMemFree(mixer_format); + } + + device->Release(); + audio_client->Release(); + enumerator->Release(); + + return sample_rate; +#else + // TODO: implement on other OSes + return GetDefaultSampleRate(); +#endif +} + +void UpdateSoundStreamSettings(bool volume_only) +{ + // This can be called from any threads, needs to be protected + std::lock_guard guard(g_sound_stream_mutex); if (g_sound_stream) { int volume = SConfig::GetInstance().m_IsMuted ? 0 : SConfig::GetInstance().m_Volume; g_sound_stream->SetVolume(volume); + + if (!volume_only) + { + // Some backends will be able to apply changes in settings at runtime + g_sound_stream->OnSettingsChanged(); + } } } -void SetSoundStreamRunning(bool running) +std::mutex g_sound_stream_running_mutex; + +bool SetSoundStreamRunning(bool running, bool send_error) { + // This can be called by the main thread while a previous + // SetRunning() might still be waiting to finish, so we need to protect it + // as most backends could even crash + std::lock_guard guard(g_sound_stream_running_mutex); + if (!g_sound_stream) - return; + return true; + // Safeguard against calling "SetRunning" with a value equal to the previous one, + // which is not supported on some backends (could crash) if (s_sound_stream_running == running) - return; - s_sound_stream_running = running; + return true; if (g_sound_stream->SetRunning(running)) - return; - if (running) + { + s_sound_stream_running = running; + return true; + } + if (send_error) + { + if (running) ERROR_LOG_FMT(AUDIO, "Error starting stream."); - else + else ERROR_LOG_FMT(AUDIO, "Error stopping stream."); + } + return false; } void SendAIBuffer(const short* samples, unsigned int num_samples) @@ -195,9 +370,12 @@ void SendAIBuffer(const short* samples, unsigned int num_samples) if (pMixer && samples) { - pMixer->PushSamples(samples, num_samples); + pMixer->PushDMASamples(samples, num_samples); } + // Update is called here because DMA are submitted with a lower frequency + // than DVD/streaming samples. Call it even if s_sound_stream_running + // is false as some backends try to re-initialize here g_sound_stream->Update(); } @@ -228,7 +406,7 @@ void IncreaseVolume(unsigned short offset) currentVolume += offset; if (currentVolume > AUDIO_VOLUME_MAX) currentVolume = AUDIO_VOLUME_MAX; - UpdateSoundStream(); + UpdateSoundStreamSettings(true); } void DecreaseVolume(unsigned short offset) @@ -238,13 +416,13 @@ void DecreaseVolume(unsigned short offset) currentVolume -= offset; if (currentVolume < AUDIO_VOLUME_MIN) currentVolume = AUDIO_VOLUME_MIN; - UpdateSoundStream(); + UpdateSoundStreamSettings(true); } void ToggleMuteVolume() { bool& isMuted = SConfig::GetInstance().m_IsMuted; isMuted = !isMuted; - UpdateSoundStream(); + UpdateSoundStreamSettings(true); } } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/AudioCommon.h b/Source/Core/AudioCommon/AudioCommon.h index e30c0acfbe7a..8ac75a8ab0f5 100644 --- a/Source/Core/AudioCommon/AudioCommon.h +++ b/Source/Core/AudioCommon/AudioCommon.h @@ -27,8 +27,21 @@ DPL2Quality GetDefaultDPL2Quality(); bool SupportsDPL2Decoder(std::string_view backend); bool SupportsLatencyControl(std::string_view backend); bool SupportsVolumeChanges(std::string_view backend); -void UpdateSoundStream(); -void SetSoundStreamRunning(bool running); +bool SupportsRuntimeSettingsChanges(); +// Default "Dolphin" output and internal mixer sample rate +unsigned long GetDefaultSampleRate(); +// Returns the min buffer time length it can hold (in ms), our backends can't have a latency +// higher than this, we'd ask for more than we can give. This depends on the current game DMA +// and DVD sample rate, but let's theorize the worse case (GC 48kHz mode: ~48043Hz). +// Of course we shouldn't use anything above half of what this returns +unsigned long GetMaxSupportedLatency(); +// Already clamped by GetMaxSupportedLatency(). Can return 0 +unsigned long GetUserTargetLatency(); +// Returns the OS mixer sample rate (based on the currently used audio device) +unsigned long GetOSMixerSampleRate(); +// Either volume only, or any type of more advanced settings (e.g. Latency, DPLII) +void UpdateSoundStreamSettings(bool volume_only); +bool SetSoundStreamRunning(bool running, bool send_error = false); void SendAIBuffer(const short* samples, unsigned int num_samples); void StartAudioDump(); void StopAudioDump(); diff --git a/Source/Core/AudioCommon/AudioSpeedCounter.cpp b/Source/Core/AudioCommon/AudioSpeedCounter.cpp new file mode 100644 index 000000000000..9095c6ff5b00 --- /dev/null +++ b/Source/Core/AudioCommon/AudioSpeedCounter.cpp @@ -0,0 +1,238 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "AudioCommon/AudioSpeedCounter.h" + +#include "Common/Timer.h" + +AudioSpeedCounter::AudioSpeedCounter(double average_time, double ticks_per_sec, + double ticks_per_upd) + : m_average_time(average_time), m_ticks_per_sec(ticks_per_sec), m_ticks_per_upd(ticks_per_upd) +{ + m_ticks_per_sec = std::max(m_ticks_per_sec, 1.0); + m_ticks_per_upd = std::max(m_ticks_per_upd, 1.0); + OnSettingsChanged(); + + m_last_time = GetTime(); +} + +u64 AudioSpeedCounter::GetTime() const +{ + return Common::Timer::GetTimeUs(); +} +double AudioSpeedCounter::GetTimeDelta(u64 old_time) const +{ + u64 time = GetTime(); + u64 delta = time - old_time; + return double(delta / TIME_CONVERSION); +} +double AudioSpeedCounter::GetTimeDeltaAndUpdateOldTime(std::atomic& old_time) const +{ + u64 time = GetTime(); + u64 delta = time - old_time; + old_time = time; + return double(delta / TIME_CONVERSION); +} + +void AudioSpeedCounter::OnSettingsChanged() +{ + double prev_target_delta = m_target_delta; + m_target_delta = m_ticks_per_upd / m_ticks_per_sec; + // Keep the previous speed by adjusting the last deltas + double relative_change = m_target_delta / prev_target_delta; + for (int i = 0; i < m_last_deltas.size(); ++i) + { + m_last_deltas[i] *= relative_change; + } +} + +void AudioSpeedCounter::SetAverageTime(double average_time) +{ + m_average_time = average_time; + OnSettingsChanged(); +} +void AudioSpeedCounter::SetTicksPerSecond(double ticks_per_sec) +{ + m_ticks_per_sec = std::max(ticks_per_sec, 1.0); + OnSettingsChanged(); +} + +void AudioSpeedCounter::Start(bool simulate_full_speed) +{ + m_last_time = GetTime(); + + m_last_deltas.clear(); + if (simulate_full_speed) + { + m_cached_last_delta = m_target_delta; + size_t size = std::max(size_t(m_average_time / m_target_delta), size_t(1)); + m_last_deltas.resize(size, m_target_delta); + } + else + { + m_cached_last_delta = -1.0; + } +} +void AudioSpeedCounter::Update(double elapsed_ticks) +{ + if (elapsed_ticks != m_ticks_per_upd) + { + m_ticks_per_upd = std::max(elapsed_ticks, 1.0); + OnSettingsChanged(); + } + + double delta = GetTimeDeltaAndUpdateOldTime(m_last_time); + // If this ended up being 0 it would be ignored + m_cached_last_delta = delta; + double total_delta = delta; + + // Remove the oldest time deltas if they are older than the max average time + for (int i = (int)m_last_deltas.size() - 1; i >= 0; --i) + { + total_delta += m_last_deltas[i]; + if (total_delta > m_average_time) + { + i = (int)m_last_deltas.size() - 1 - i; + m_last_deltas.erase(m_last_deltas.begin(), m_last_deltas.end() - i); + break; + } + } + + m_last_deltas.push_back(delta); +} + +double AudioSpeedCounter::GetLastSpeed(bool& in_out_predict, bool simulate_full_speed) const +{ + if (in_out_predict) + { + double delta = GetTimeDelta(m_last_time); + // If it's currently late for a new update + if (delta > m_target_delta) + { + return m_target_delta / delta; + } + in_out_predict = false; + } + + double cached_last_delta = m_cached_last_delta; + if (cached_last_delta > 0.0) + { + return m_target_delta / cached_last_delta; + } + return simulate_full_speed ? 1.0 : 0.0; +} + +double AudioSpeedCounter::GetAverageSpeed(bool predict, bool simulate_full_speed, + double max_average_time) const +{ + double total_delta = 0.0; + u32 num = 0; + + if (predict) + { + double delta = GetTimeDelta(m_last_time); + // If it's currently late for a new update + if (delta > m_target_delta) + { + total_delta += delta; + ++num; + } + } + + for (int i = (int)m_last_deltas.size() - 1; i >= 0; --i) + { + total_delta += m_last_deltas[i]; + ++num; + + // Accept the last one even if it was over the limit (we always keep one last delta in anyway) + if (max_average_time >= 0.0 && total_delta > max_average_time) + { + break; + } + } + + if (num == 0) + { + return simulate_full_speed ? 1.0 : 0.0; + } + + return double(m_target_delta / (total_delta / num)); +} + +double AudioSpeedCounter::GetCachedAverageSpeed(bool alternative_speed, bool predict, + bool simulate_full_speed) const +{ + double total_delta = alternative_speed ? m_alternative_cached_average : m_cached_average; + s32 num = alternative_speed ? m_alternative_cached_average_num : m_cached_average_num; + + if (predict) + { + double delta = GetTimeDelta(m_last_time); + // If it's currently late for a new update + if (delta > m_target_delta) + { + // Take away some importance from the average, otherwise it would always have the same weight. + // Theoretically we should remove the oldest deltas but we can't here. Keep at least 1 like + // in GetAverageSpeed() + s32 times_over = delta / m_target_delta; + total_delta *= double(std::max(num - times_over, 1)) / std::max(num, 1); + num = std::max(num - times_over, 1); + + total_delta += delta; + ++num; + } + } + if (num == 0) + { + return simulate_full_speed ? 1.0 : 0.0; + } + + return double(m_target_delta / (total_delta / num)); +} + +void AudioSpeedCounter::CacheAverageSpeed(bool alternative_speed, double max_average_time) +{ + double total_delta = 0.0; + u32 num = 0; + for (int i = (int)m_last_deltas.size() - 1; i >= 0; --i) + { + total_delta += m_last_deltas[i]; + ++num; + + // Accept the last one even if it was over the limit (we always keep one last delta in anyway) + if (max_average_time >= 0.0 && total_delta > max_average_time) + { + break; + } + } + + if (alternative_speed) + { + m_alternative_cached_average = total_delta; + m_alternative_cached_average_num = num; + } + else + { + m_cached_average = total_delta; + m_cached_average_num = num; + } +} + +void AudioSpeedCounter::SetPaused(bool paused) +{ + if (paused != m_is_paused) + { + m_is_paused = paused; + if (m_is_paused) + { + m_last_paused_time = GetTime(); + } + else + { + s64 time = GetTime(); + s64 delta = time - m_last_paused_time; + m_last_time += delta; + } + } +} diff --git a/Source/Core/AudioCommon/AudioSpeedCounter.h b/Source/Core/AudioCommon/AudioSpeedCounter.h new file mode 100644 index 000000000000..cc75701936df --- /dev/null +++ b/Source/Core/AudioCommon/AudioSpeedCounter.h @@ -0,0 +1,65 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" + +// Helper to calculate the ratio between (audio) input and output, in seconds. +// Ticks represents the num of samples, while updates represent samples pushes. +struct AudioSpeedCounter +{ + AudioSpeedCounter(double average_time, double ticks_per_sec, double ticks_per_upd); + + // Start does not need to be called. Can be used as re-start + void Start(bool simulate_full_speed = false); + // if elapsed_ticks changes, the m_last_deltas array is adjusted to keep the same average speed + void Update(double elapsed_ticks); + // Current (last calculated). Thread safe + double GetLastSpeed(bool& in_out_predict, bool simulate_full_speed = false) const; + // Use max_average_time to get a more "precise" estimation. Not thread safe + double GetAverageSpeed(bool predict = false, bool simulate_full_speed = false, + double max_average_time = -1.0) const; + // Thread safe + double GetCachedAverageSpeed(bool alternative_speed = false, bool predict = false, + bool simulate_full_speed = false) const; + void CacheAverageSpeed(bool alternative_speed = false, double max_average_time = -1.0); + double GetAverageTime() const { return m_average_time; } + double GetTargetDelta() const { return m_target_delta; } + void SetPaused(bool paused); + bool IsPaused() { return m_is_paused; } + // Sets the length time to count the speed for + void SetAverageTime(double average_time); + void SetTicksPerSecond(double ticks_per_sec); + +private: + // Time passed between every update + std::deque m_last_deltas; + std::atomic m_last_time; + std::atomic m_cached_average{0.0}; + std::atomic m_alternative_cached_average{0.0}; + std::atomic m_cached_average_num{0}; + std::atomic m_alternative_cached_average_num{0}; + std::atomic m_cached_last_delta{-1.0}; + // The max time we keep deltas for + double m_average_time; + double m_ticks_per_upd; + double m_ticks_per_sec; + // The expected time elapsed between two updates + double m_target_delta = 1.0; + u64 m_last_paused_time = 0; + std::atomic m_is_paused{false}; + + void OnSettingsChanged(); + + // Us + u64 GetTime() const; + // Seconds + double GetTimeDelta(u64 old_time) const; + double GetTimeDeltaAndUpdateOldTime(std::atomic& old_time) const; + + static constexpr double TIME_CONVERSION = 1000000.0; +}; diff --git a/Source/Core/AudioCommon/AudioStretcher.cpp b/Source/Core/AudioCommon/AudioStretcher.cpp index d0ab13473ce8..b302c9405ce2 100644 --- a/Source/Core/AudioCommon/AudioStretcher.cpp +++ b/Source/Core/AudioCommon/AudioStretcher.cpp @@ -2,70 +2,46 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include -#include -#include - #include "AudioCommon/AudioStretcher.h" -#include "Common/Logging/Log.h" -#include "Core/ConfigManager.h" namespace AudioCommon { -AudioStretcher::AudioStretcher(unsigned int sample_rate) : m_sample_rate(sample_rate) +AudioStretcher::AudioStretcher(u32 sample_rate) : m_sample_rate(sample_rate) { m_sound_touch.setChannels(2); - m_sound_touch.setSampleRate(sample_rate); - m_sound_touch.setPitch(1.0); - m_sound_touch.setTempo(1.0); + m_sound_touch.setSampleRate(m_sample_rate); + m_sound_touch.setSetting(SETTING_SEQUENCE_MS, 62); + m_sound_touch.setSetting(SETTING_SEEKWINDOW_MS, 28); + // Unfortunately the AA filter is only applied on the sample rate transposer, which we don't use, + // it just adds overhead and loss of quality. AA is not be used by the tempo stretcher + m_sound_touch.setSetting(SETTING_USE_AA_FILTER, 0); } void AudioStretcher::Clear() { m_sound_touch.clear(); + m_last_stretched_sample[0] = 0; + m_last_stretched_sample[1] = 0; + m_stretch_ratio = 1.0; + m_stretch_ratio_sum = 0.0; + m_stretch_ratio_count = 0; } -void AudioStretcher::ProcessSamples(const short* in, unsigned int num_in, unsigned int num_out) +void AudioStretcher::PushSamples(const s16* in, u32 num_in) { - const double time_delta = static_cast(num_out) / m_sample_rate; // seconds - - // We were given actual_samples number of samples, and num_samples were requested from us. - double current_ratio = static_cast(num_in) / static_cast(num_out); - - const double max_latency = SConfig::GetInstance().m_audio_stretch_max_latency; - const double max_backlog = m_sample_rate * max_latency / 1000.0 / m_stretch_ratio; - const double backlog_fullness = m_sound_touch.numSamples() / max_backlog; - if (backlog_fullness > 5.0) + uint prev_processed_samples = m_sound_touch.numSamples(); + m_sound_touch.putSamples(in, num_in); + // Some samples have been processed, reset our tempo average counter + if (m_sound_touch.numSamples() != prev_processed_samples) { - // Too many samples in backlog: Don't push anymore on - num_in = 0; + m_stretch_ratio_sum = 0.0; + m_stretch_ratio_count = 0; } - - // We ideally want the backlog to be about 50% full. - // This gives some headroom both ways to prevent underflow and overflow. - // We tweak current_ratio to encourage this. - constexpr double tweak_time_scale = 0.5; // seconds - current_ratio *= 1.0 + 2.0 * (backlog_fullness - 0.5) * (time_delta / tweak_time_scale); - - // This low-pass filter smoothes out variance in the calculated stretch ratio. - // The time-scale determines how responsive this filter is. - constexpr double lpf_time_scale = 1.0; // seconds - const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale); - m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio); - - // Place a lower limit of 10% speed. When a game boots up, there will be - // many silence samples. These do not need to be timestretched. - m_stretch_ratio = std::max(m_stretch_ratio, 0.1); - m_sound_touch.setTempo(m_stretch_ratio); - - DEBUG_LOG_FMT(AUDIO, "Audio stretching: samples:{}/{} ratio:{} backlog:{} gain: {}", num_in, - num_out, m_stretch_ratio, backlog_fullness, lpf_gain); - - m_sound_touch.putSamples(in, num_in); } -void AudioStretcher::GetStretchedSamples(short* out, unsigned int num_out) +void AudioStretcher::GetStretchedSamples(s16* out, u32 num_out) { + // This won't return any samples a lot of times as they are processed in batches const size_t samples_received = m_sound_touch.receiveSamples(out, num_out); if (samples_received != 0) @@ -75,11 +51,52 @@ void AudioStretcher::GetStretchedSamples(short* out, unsigned int num_out) } // Perform padding if we've run out of samples. - for (size_t i = samples_received; i < num_out; i++) + for (size_t i = samples_received; i < num_out; ++i) { out[i * 2 + 0] = m_last_stretched_sample[0]; out[i * 2 + 1] = m_last_stretched_sample[1]; } } +// Call this before ProcessSamples() +void AudioStretcher::SetTempo(double tempo, bool reset) +{ + // SoutchTouch doesn't constantly produce output samples, + // it does it in batches when it has enough samples, but at that time, + // it only considers the "last" set tempo, which won't include + // the oscillations in speed we had from the last calculated batch, + // so to avoid missing that information, we use the average of all + // the tempos set between batches productions. + // Note that the tempo influences the number of samples needed to + // produce a batch. + if (reset) + { + m_stretch_ratio_sum = 0.0; + m_stretch_ratio_count = 0; + } + m_stretch_ratio_sum += tempo; + ++m_stretch_ratio_count; + m_stretch_ratio = m_stretch_ratio_sum / m_stretch_ratio_count; + m_sound_touch.setTempo(m_stretch_ratio); +} + +void AudioStretcher::SetSampleRate(u32 sample_rate) +{ + m_sample_rate = sample_rate; + m_sound_touch.setSampleRate(m_sample_rate); +} + +// Returns the samples of "processed" samples waiting to be read +double AudioStretcher::GetProcessedLatency() const +{ + return m_sound_touch.numSamples() / double(m_sample_rate); +} + +// This returns the smallest amount of samples (as time) that will be processed in a batch. +// We can't have a latency lower than this as these samples are suddenly added +double AudioStretcher::GetAcceptableLatency() const +{ + return m_sound_touch.getSetting(SETTING_NOMINAL_OUTPUT_SEQUENCE) / double(m_sample_rate); +} + } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/AudioStretcher.h b/Source/Core/AudioCommon/AudioStretcher.h index d83f852b7f07..50cdbdc27ac2 100644 --- a/Source/Core/AudioCommon/AudioStretcher.h +++ b/Source/Core/AudioCommon/AudioStretcher.h @@ -13,16 +13,22 @@ namespace AudioCommon class AudioStretcher { public: - explicit AudioStretcher(unsigned int sample_rate); - void ProcessSamples(const short* in, unsigned int num_in, unsigned int num_out); - void GetStretchedSamples(short* out, unsigned int num_out); + explicit AudioStretcher(u32 sample_rate); + void PushSamples(const s16* in, u32 num_in); + void GetStretchedSamples(s16* out, u32 num_out); void Clear(); + void SetTempo(double tempo, bool reset = false); + void SetSampleRate(u32 sample_rate); + double GetProcessedLatency() const; + double GetAcceptableLatency() const; private: - unsigned int m_sample_rate; - std::array m_last_stretched_sample = {}; + u32 m_sample_rate; + std::array m_last_stretched_sample = {}; soundtouch::SoundTouch m_sound_touch; double m_stretch_ratio = 1.0; + double m_stretch_ratio_sum = 0.0; + u32 m_stretch_ratio_count = 0; }; } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/CMakeLists.txt b/Source/Core/AudioCommon/CMakeLists.txt index dfa20f61949d..01d4b36fded7 100644 --- a/Source/Core/AudioCommon/CMakeLists.txt +++ b/Source/Core/AudioCommon/CMakeLists.txt @@ -1,6 +1,8 @@ add_library(audiocommon AudioCommon.cpp AudioCommon.h + AudioSpeedCounter.cpp + AudioSpeedCounter.h AudioStretcher.cpp AudioStretcher.h CubebStream.cpp @@ -14,6 +16,8 @@ add_library(audiocommon SurroundDecoder.h NullSoundStream.cpp NullSoundStream.h + SoundStream.cpp + SoundStream.h WaveFile.cpp WaveFile.h ) diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index db8b1411b4ac..599323686e43 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -6,11 +6,13 @@ #include "AudioCommon/CubebStream.h" #include "AudioCommon/CubebUtils.h" +#include "AudioCommon/AudioCommon.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/Thread.h" #include "Core/ConfigManager.h" +//To review: this is not true... surround min samples depend on the sample rate. Also this doesn't depend on sample rate... // ~10 ms - needs to be at least 240 for surround constexpr u32 BUFFER_SAMPLES = 512; @@ -37,6 +39,10 @@ bool CubebStream::Init() if (!m_ctx) return false; + m_mixer->SetSampleRate(SConfig::GetInstance().bUseOSMixerSampleRate ? + AudioCommon::GetOSMixerSampleRate() : + AudioCommon::GetDefaultSampleRate()); + m_stereo = !SConfig::GetInstance().ShouldUseDPL2Decoder(); cubeb_stream_params params; @@ -54,14 +60,34 @@ bool CubebStream::Init() params.layout = CUBEB_LAYOUT_3F2_LFE; } + // In samples u32 minimum_latency = 0; if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) ERROR_LOG_FMT(AUDIO, "Error getting minimum latency"); INFO_LOG_FMT(AUDIO, "Minimum latency: {} frames", minimum_latency); + u32 final_latency; + +#ifdef _WIN32 + uint32_t target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; + // WASAPI supports up to 5000ms but let's clamp to 500ms + uint32_t max_latency = 500 / 1000.0 * params.rate; + final_latency = std::clamp(target_latency, minimum_latency, max_latency); +#else + // TODO: implement on other platforms that I couldn't test (they might fail to initialize). + // Max supported by cubeb is 96000 and min is 1 (in samples) + final_latency = minimum_latency; +#endif + if (!m_stereo) + { + final_latency = std::max(BUFFER_SAMPLES, final_latency); + } + + INFO_LOG(AUDIO, "Latency: %u frames", final_latency); + return cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, - nullptr, ¶ms, std::max(BUFFER_SAMPLES, minimum_latency), - DataCallback, StateCallback, this) == CUBEB_OK; + nullptr, ¶ms, final_latency, DataCallback, StateCallback, + this) == CUBEB_OK; } bool CubebStream::SetRunning(bool running) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 56a5d865ea32..3bd2aa080bc2 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -2,8 +2,7 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include "AudioCommon/Mixer.h" -#include "AudioCommon/Enums.h" +#include "AudioCommon/Mixer.h" //To try to delete some includes and see if it builds #include #include @@ -11,51 +10,82 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" -#include "Common/Logging/Log.h" +#include "Common/MathUtil.h" #include "Common/Swap.h" +#include "Core/Core.h" +#include #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" -static u32 DPL2QualityToFrameBlockSize(AudioCommon::DPL2Quality quality) +#include "Common/Logging/Log.h" //To delete and all the uses (or make debug log) +#include "VideoCommon/OnScreenDisplay.h" //To delete and all the uses +//#pragma optimize("", off) //To delete + +Mixer::Mixer(u32 sample_rate) + : m_sample_rate(sample_rate), m_stretcher(sample_rate), + m_surround_decoder(sample_rate, Config::Get(Config::MAIN_DPL2_QUALITY)) { - switch (quality) - { - case AudioCommon::DPL2Quality::Lowest: - return 512; - case AudioCommon::DPL2Quality::Low: - return 1024; - case AudioCommon::DPL2Quality::Highest: - return 4096; - default: - return 2048; - } + m_scratch_buffer.reserve(MAX_SAMPLES * NC); + m_dma_speed.Start(true); + + INFO_LOG(AUDIO, "Mixer is initialized"); + + m_on_state_changed_handle = Core::AddOnStateChangedCallback([this](Core::State state) { + if (state == Core::State::Paused) + SetPaused(true); + else if (state == Core::State::Running) + SetPaused(false); + }); } -Mixer::Mixer(unsigned int BackendSampleRate) - : m_sampleRate(BackendSampleRate), m_stretcher(BackendSampleRate), - m_surround_decoder(BackendSampleRate, - DPL2QualityToFrameBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY))) +Mixer::~Mixer() { INFO_LOG_FMT(AUDIO_INTERFACE, "Mixer is initialized"); + Core::RemoveOnStateChangedCallback(m_on_state_changed_handle); } -Mixer::~Mixer() +void Mixer::SetPaused(bool paused) { + if (!m_dma_speed.IsPaused() && !paused) + { + // This happens on game start. The backend has already started reading actually + m_dma_speed.Start(true); + } + m_dma_speed.SetPaused(paused); } void Mixer::DoState(PointerWrap& p) { m_dma_mixer.DoState(p); m_streaming_mixer.DoState(p); - m_wiimote_speaker_mixer.DoState(p); + m_wiimote_speaker_mixer[0].DoState(p); + m_wiimote_speaker_mixer[1].DoState(p); + m_wiimote_speaker_mixer[2].DoState(p); + m_wiimote_speaker_mixer[3].DoState(p); + + if (p.GetMode() == PointerWrap::MODE_READ) + { + m_dma_speed.SetTicksPerSecond(m_dma_mixer.GetInputSampleRate()); + // We could reset a few things here but it would require too much thread synchronization + } } -// Executed from sound stream thread -unsigned int Mixer::MixerFifo::Mix(short* samples, unsigned int numSamples, - bool consider_framelimit) +void Mixer::SetSampleRate(u32 sample_rate) { - unsigned int currentSample = 0; + m_sample_rate = sample_rate; + m_stretcher.SetSampleRate(m_sample_rate); + m_surround_decoder.SetSampleRate(m_sample_rate); + //To review: is this thread safe between the game and emu thread? + if (!m_dma_speed.IsPaused()) + m_dma_speed.Start(true); + //To do: reset m_fract, and also DPLII. Add method to reset DPLII +} +// render num_samples sample pairs to samples[] +// advance indexR with sample position +// returns the new number of samples mixed (not the ones played backwards) +u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) +{ // Cache access in non-volatile variable // This is the only function changing the read value, so it's safe to // cache it locally although it's written here. @@ -66,232 +96,646 @@ unsigned int Mixer::MixerFifo::Mix(short* samples, unsigned int numSamples, u32 indexR = m_indexR.load(); u32 indexW = m_indexW.load(); - // render numleft sample pairs to samples[] - // advance indexR with sample position - // remember fractional offset - - float emulationspeed = SConfig::GetInstance().m_EmulationSpeed; - float aid_sample_rate = static_cast(m_input_sample_rate); - if (consider_framelimit && emulationspeed > 0.0f) - { - float numLeft = static_cast(((indexW - indexR) & INDEX_MASK) / 2); + // The rate can be any, unfortunately we don't apply an anti aliasing filer, which means + // we might get aliasing at higher rates (unless our mixer sample rate is very high) + double rate = (m_input_sample_rate * (stretching ? 1.0 : m_mixer->GetCurrentSpeed())) / + m_mixer->m_sample_rate; - u32 low_waterwark = m_input_sample_rate * SConfig::GetInstance().iTimingVariance / 1000; - low_waterwark = std::min(low_waterwark, MAX_SAMPLES / 2); + s32 lVolume = m_lVolume.load(); + s32 rVolume = m_rVolume.load(); - m_numLeftI = (numLeft + m_numLeftI * (CONTROL_AVG - 1)) / CONTROL_AVG; - float offset = (m_numLeftI - low_waterwark) * CONTROL_FACTOR; - if (offset > MAX_FREQ_SHIFT) - offset = MAX_FREQ_SHIFT; - if (offset < -MAX_FREQ_SHIFT) - offset = -MAX_FREQ_SHIFT; + s16 s[NC]; // Padding samples + s[0] = m_last_output_samples[1]; + s[1] = m_last_output_samples[1]; - aid_sample_rate = (aid_sample_rate + offset) * emulationspeed; + // Actual number of samples written (not padded nor backwards played) + u32 actual_samples_count = + CubicInterpolation(samples, num_samples, rate, indexR, indexW, s[0], s[1], lVolume, rVolume); + m_last_output_samples[0] = s[0]; + m_last_output_samples[1] = s[1]; + if (actual_samples_count != num_samples) + { + if (actual_samples_count > 0) + { + // We reserved some samples for the interpolation so start from the opposite direction + // to make the first backwards interpolated sample be as close as possible to the + // second last forward played one (indexR will be increased again before reading) + m_backwards_indexR = indexR + INTERP_SAMPLES * NC; + m_backwards_fract = 1.0 - m_fract; + } + // We have run out of samples, so set indexR to the max allowed so that there won't be any + // further attempts at reading it, when indexW is finally increased we will resume reading, + // acknowledging that indexR had already been increased for the next time, as fract would be + // set to -1. We could also have ignored changing it here, and set a variable like: + // m_finished_samples, but that would have very little benefits (except more fract consistency) + // to the cost of additional code branches. Setting indesR to indexW also makes them + // stay more aligned (not really necessary) setting fract to -1 so it resets itself helps + // perfect ratios to maintain quality after a small drop in speed which would have ruined fract + // being always 0. + // Try to pull back indexR to place it so that the next sample that will be interpolated + // will be just one sample "behind" the last one played forwards + indexR = indexW - INTERP_SAMPLES * NC; + m_fract = -1.0; } - const u32 ratio = (u32)(65536.0f * aid_sample_rate / (float)m_mixer->m_sampleRate); - - s32 lvolume = m_LVolume.load(); - s32 rvolume = m_RVolume.load(); + // Padding (mix the remaining samples with the our last sample in case we didn't have enough) + // indexR is always "INTERP_SAMPLES" away from indexW, + // so pad by the current indexR, which was the biggest influence + // against the cubic interpolation of this frame - // TODO: consider a higher-quality resampling algorithm. - for (; currentSample < numSamples * 2 && ((indexW - indexR) & INDEX_MASK) > 2; currentSample += 2) + s32 behind_samples = num_samples - actual_samples_count; + // This might sound bad if we are constantly missing a few samples, but that should never happen, + // and we couldn't predict it anyway (meaning we should start playing backwards as soon as we can) + if (behind_samples > 0 && m_constantly_pushed && !stretching) { - u32 indexR2 = indexR + 2; // next sample - - s16 l1 = Common::swap16(m_buffer[indexR & INDEX_MASK]); // current - s16 l2 = Common::swap16(m_buffer[indexR2 & INDEX_MASK]); // next - int sampleL = ((l1 << 16) + (l2 - l1) * (u16)m_frac) >> 16; - sampleL = (sampleL * lvolume) >> 8; - sampleL += samples[currentSample + 1]; - samples[currentSample + 1] = std::clamp(sampleL, -32767, 32767); - - s16 r1 = Common::swap16(m_buffer[(indexR + 1) & INDEX_MASK]); // current - s16 r2 = Common::swap16(m_buffer[(indexR2 + 1) & INDEX_MASK]); // next - int sampleR = ((r1 << 16) + (r2 - r1) * (u16)m_frac) >> 16; - sampleR = (sampleR * rvolume) >> 8; - sampleR += samples[currentSample]; - samples[currentSample] = std::clamp(sampleR, -32767, 32767); + rate = m_input_sample_rate / m_mixer->m_sample_rate; //To review (this should actually follow the rate but with no prediction...) + s16* back_samples = samples + actual_samples_count * NC; + // I've been thinking of this a lot and this is the bast way to deal with it. + // If we don't have enough samples to mix the number of samples requested, we play back all samples starting from the last mixed one + // (until we get new samples or until we still have old samples to read). + // There are other similar ways to deal with this: + // -Only play backwards if the number of missing samples is greater than "n", otherwise do padding. + // That's wrong because n is arbitrary and the required length would need to depend on the current sound wave period + // -Play backwards half of the missing required samples, then play back forwards the other half. + // The good thing of this approach is that it blends well when it toggles the direction and normal playback resumes, + // but unfortunately, it's arbitrary as if we had a backend latency of "0", we'd mix 1 sample per time, so + // switching between playing old samples back and forward wouldn't be possible, unless we abstracted latency (the number of required out samples) + // from the code and cached a series of variables (e.g. play back 15ms of sound, then play these forwards again). + // This might work out better but is long to implement, but that 15ms of sound would again be an arbitrary number which might sound better or worse + // depending on the current sound waves + // Once we have run out of old samples to play, this will output silence. Alternatively, we could have played them forwards again, but the likelihood of running out of samples with the + // current buffer size is extremely rare: not worth considering. + // --------------------- + // I don't think constantly changing direction between backward and forward when we are out + // of samples can ever work. I've been trying. the 2 problems are: + // -if it's not latency agnostic, you'd play back half of the missing samples backwards, and + // then from the point you reached, play the same set forwards again (so that if emulation + // resumes, the last played sample would be the matching the first new sample) so the above + // would be fine, except the way it sounds depends on your current backend latency, and if you + // have a very low backend latency, you'd never have enough time to let a few sound waves play + // smoothly without inverting the playback direction again + // -if it is latency agnostic, then we'd + // play a pre-decided number of samples backwards, and then the same number forwards again, over + // one or more audio frames. This would keep ping-ponging until we don't have any new samples + // again, this would be all right in some case, but then, when you actually receive the new + // samples from the DMA, you are going to have to interrupt the ping point in a random place, + // and that defeats the purpose of doing the ping-pong in the first place unless you wait for + // the ping-pong cycle to be finished, but that would introduce latency which is insane if you + // are already not at full speed the only remaining solution i can think of is to cross fade but + // from my tests it doesn't seem to be necessary, cracklings aren't bad enough, they aren't + // really disturbing, they are very low, and it's not like dolphin didn't have them before. + // Also in general, ping ponging wouldn't give you enough time to appreciate the sounds, + // they would constantly be interrupted. The only solution to crackling would be cross fade? + CubicInterpolation(back_samples, behind_samples, rate, m_backwards_indexR, indexW, s[0], s[1], + lVolume, rVolume, false); + } + // If we are not constantly pushed, we always append some silent samples at the end of our buffer, + // so there won't be any need of panning until new samples are pushed + else if (behind_samples > 0 && (m_constantly_pushed || m_currently_pushed)) + { + if (indexW > 8) OSD::AddMessage("Behind samples: " + std::to_string(behind_samples), 0U); - m_frac += ratio; - indexR += 2 * (u16)(m_frac >> 16); - m_frac &= 0xffff; + //To review: should padding play the last played sample or try to predict what the next one would have been? We will resume from the previous indexR as we have lost m_fract here + //To review: if we re-enable padding on wii mote forever, make sure it's disabled once we disconnect it + unsigned int current_sample = actual_samples_count * 2; + for (; current_sample < num_samples * 2; current_sample += 2) + { + samples[current_sample + 0] = + std::clamp(m_last_output_samples[0] + samples[current_sample + 0], -SHRT_MAX, SHRT_MAX); + samples[current_sample + 1] = + std::clamp(m_last_output_samples[1] + samples[current_sample + 1], -SHRT_MAX, SHRT_MAX); + } } + //if (indexW > 8 && !m_constantly_pushed) OSD::AddMessage("last sample: " + std::to_string(m_last_output_samples[0]), 0U); - // Actual number of samples written to the buffer without padding. - unsigned int actual_sample_count = currentSample / 2; + m_indexR.store(indexR); + + return actual_samples_count; +} - // Padding - short s[2]; - s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]); - s[1] = Common::swap16(m_buffer[(indexR - 2) & INDEX_MASK]); - s[0] = (s[0] * rvolume) >> 8; - s[1] = (s[1] * lvolume) >> 8; - for (; currentSample < numSamples * 2; currentSample += 2) +// Sounds better than linear interpolation +u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double rate, u32& indexR, + u32 indexW, s16& l_s, s16& r_s, s32 lVolume, s32 rVolume, + bool forwards) +{ + // Coefficients copied from SoundTouch Copyright © Olli Parviainen 2001-2019 + constexpr float coeffs[] = + { -0.5f, 1.0f, -0.5f, 0.0f, + 1.5f, -2.5f, 0.0f, 1.0f, + -1.5f, 2.0f, 0.5f, 0.0f, + 0.5f, -0.5f, 0.0f, 0.0f }; + + u32 available_samples = SamplesDifference(indexW, indexR); + s8 direction = forwards ? 1 : -1; + double& fract = forwards ? m_fract : m_backwards_fract; + s16* interpolation_buffer = m_mixer->m_interpolation_buffer.data(); + + u32 requested_samples = u32(rate * num_samples) * NC + NC; // Increase by 1 for imprecisions + u32 readable_samples = forwards ? available_samples : (MAX_SAMPLES * NC); + u32 samples_to_read = std::min(requested_samples + (INTERP_SAMPLES * NC), readable_samples); + u32 first_indexR = GetNextIndexR(indexR); + u32 last_indexR = first_indexR + samples_to_read * direction; + // Do the swaps once instead of processing them for each iteration below + for (u32 k = first_indexR; k != last_indexR + direction * NC; k += direction * NC) { - int sampleR = std::clamp(s[0] + samples[currentSample + 0], -32767, 32767); - int sampleL = std::clamp(s[1] + samples[currentSample + 1], -32767, 32767); + interpolation_buffer[k + 0 & INDEX_MASK] = Common::swap16(m_buffer[k + 0 & INDEX_MASK]); + interpolation_buffer[k + 1 & INDEX_MASK] = Common::swap16(m_buffer[k + 1 & INDEX_MASK]); + } - samples[currentSample + 0] = sampleR; - samples[currentSample + 1] = sampleL; + // fract requested to be reset so sure it will be 0 in the first cycle + if (fract < 0.0 && num_samples > 0 && (!forwards || available_samples > INTERP_SAMPLES * NC)) + { + fract = -rate; } - // Flush cached variable - m_indexR.store(indexR); + u32 i = 0; + u32 next_available_samples = available_samples; + // Stop 3 (INTERP_SAMPLES) samples from the end as we need to interpolate with them. + while (i < num_samples && (!forwards || (next_available_samples > INTERP_SAMPLES * NC && + next_available_samples <= available_samples))) + { + // If rate is 1 it would like there was no interpolation. + // If rate is 0 fract won't never make a whole so it's basically like padding. + // If rate is a recurring decimal fract often ends up being 0.99999 due to + // loss of precision, but that is absolutely fine, it implies no quality loss. + // Fract imprecisions are the reason we don't pre-calculate the number of iterations + fract += rate; // Update fraction position + u32 whole = u32(fract); // Update whole position + fract -= whole; + // Increase indexR before reading it, not after like before. The old code had 3 problems: + // - IndexR was increased after being read, so if the rate was very high, + // it could go over indexW. This would require a flag to be fixed + // - In our first iteration, we would use the last fract calculated from the previous + // interpolation (last audio frame), meaning that it would have been based on the old + // rate, not the new one. Of course, over time the errors would cancel themselves out, + // but in the immediate, it was wrong (less reactive to changes in speed) + // - When suddenly changing playback direction, the indexR would have been moved to the + // next position to read in the opposite direction + // The only "problems" with the new code is that in the very first iteration after a fract + // reset, fract won't be increased. It also denaturalizes the progress of indexR if we + // run out of samples, but that would sound bad anyway. We also can't really tell + // how many samples we will have available until we have found out our next play rate + //To review last condition above, how often does it happen? How bad it is? How could we improve on that? The wii mote speaker might be more sensible to that + indexR += NC * whole * direction; + + available_samples = next_available_samples; + next_available_samples = + SamplesDifference(indexW, indexR); + + const float x2 = float(fract); // x + const float x1 = x2 * x2; // x^2 + const float x0 = x1 * x2; // x^3 + + float y0 = coeffs[0] * x0 + coeffs[1] * x1 + coeffs[2] * x2 + coeffs[3]; + float y1 = coeffs[4] * x0 + coeffs[5] * x1 + coeffs[6] * x2 + coeffs[7]; + float y2 = coeffs[8] * x0 + coeffs[9] * x1 + coeffs[10] * x2 + coeffs[11]; + float y3 = coeffs[12] * x0 + coeffs[13] * x1 + coeffs[14] * x2 + coeffs[15]; + + // The first and last sample act as control points, the middle ones have more importance. + // The very first and last samples might never directly be used but it shouldn't be a problem. + // Theoretically we could linearly interpolate between the last 2 samples + // and trade latency with a small hit in quality, but it is not worth it. + // We could have ignored the direction while playing backwards, and just read + // indexR over our actually play direction, but I thought that was "wrong" + float l_s_f = y0 * interpolation_buffer[indexR & INDEX_MASK] + + y1 * interpolation_buffer[(indexR + 2 * direction) & INDEX_MASK] + + y2 * interpolation_buffer[(indexR + 4 * direction) & INDEX_MASK] + + y3 * interpolation_buffer[(indexR + 6 * direction) & INDEX_MASK]; + float r_s_f = y0 * interpolation_buffer[(indexR + 1) & INDEX_MASK] + + y1 * interpolation_buffer[(indexR + 2 * direction + 1) & INDEX_MASK] + + y2 * interpolation_buffer[(indexR + 4 * direction + 1) & INDEX_MASK] + + y3 * interpolation_buffer[(indexR + 6 * direction + 1) & INDEX_MASK]; + //To test left and right on HW source + + l_s = (s16(std::round(l_s_f)) * lVolume) >> 8; + r_s = (s16(std::round(r_s_f)) * rVolume) >> 8; + // Clamp after adding to current sample, as if the cubic interpolation produced a sample over + // the limits and the current sample has the opposite sign, then we'd keep the excess value, + // while if it had the same sign, it would have been clamped anyway. + // We don't clamp to SHRT_MIN to keep the center at 0, though audio devices can take SHRT_MIN + samples[i * NC + 0] = std::clamp(samples[i * NC + 0] + l_s, -SHRT_MAX, SHRT_MAX); + samples[i * NC + 1] = std::clamp(samples[i * NC + 1] + r_s, -SHRT_MAX, SHRT_MAX); + + ++i; + } - return actual_sample_count; + return i; } -unsigned int Mixer::Mix(short* samples, unsigned int num_samples) +u32 Mixer::Mix(s16* samples, u32 num_samples) { - if (!samples) + // We can't mix if the emulation is paused as m_dma_speed would return wrong speeds + if (!samples || num_samples == 0 || m_dma_speed.IsPaused()) return 0; - memset(samples, 0, num_samples * 2 * sizeof(short)); + bool stretching = SConfig::GetInstance().m_audio_stretch; + + double emulation_speed = SConfig::GetInstance().m_EmulationSpeed; + bool frame_limiter = emulation_speed > 0.0 && !Core::GetIsThrottlerTempDisabled(); + + // backend latency in seconds + double time_delta = double(num_samples) / m_sample_rate; - if (SConfig::GetInstance().m_audio_stretch) + double average_actual_speed = m_dma_speed.GetCachedAverageSpeed(false, true, true); + bool predicting = true; + // Set predicting to false if we are not predicting (meaning the last samples push isn't late) + double actual_speed = m_dma_speed.GetLastSpeed(predicting, true); + //INFO_LOG(AUDIO, "dma_mixer current speed: %lf", average_actual_speed); + + double stretching_target_speed = 1.0; + //To review: keep the previous target speed while we calculate the new one (to get the correct indexR)? Have it double buffered. Also, maintain in when pass in between stretching and not + double& target_speed = stretching ? stretching_target_speed : m_target_speed; + target_speed = emulation_speed; + if (stretching) + { + m_target_speed = 1.0; + } + + // What we likely want to do is check the emulation speed difference between the target speed and the averaged emulation speed, + // but then use the last frame emulation speed for more accuracy (and clamping it to the target speed) + static double FallbackDelta = 0.0; //0.005 or before 0.1 + // Only do if we are actually going slower, if we are going faster, it's likely to be a slight incorrection of the actual speed (because it's averaged). + // Given that we ignore the frames where we went faster, overall we will consume less audio samples that what we have received, causing less padding. + // This avoids missing (or padded) samples when we can't reach full speed. The delta is relative to the emulator target speed. + // OLD: (emulation_speed - average_actual_speed > FallbackDelta) + // Unfortunately this doesn't work when unpausing (or starting?) the emulation, so we'll need to + // work around that. + // When using the last 3 frames (?) averaged speed, this causes heavier crackling (and fluctuation), but overall the + // sound is much more accurate. + // When using the very last frame, the sound works much better and has less crackling and skips, at the cost of fluctuating in pitch a bit, + // just like the emulation itself is doing. + // Using the amount of samples in the buffer to calculate the emulation speed doesn't seem to be accurate at all because of so many timing/order fluctuations + // NonStretchCorrectionTolerance: we don't want to suddenly snap in and out of actual FPS speed, but only when we have confirmed the slow down is not due to a hiccup + // Small variances are already handled by the mixing, so we don't want to take care of these cases + + if (!frame_limiter) + { + m_time_behind_target_speed = 0.0; //To maybe find a way to maintain this during unlimited frame rate? + m_time_below_target_speed_growing = false; + //To review: this can start over 30 in the first frames of game... + target_speed = m_dma_speed.GetCachedAverageSpeed(true, true, true); + m_time_at_custom_speed = m_time_at_custom_speed + time_delta; + } + //To avoid divisions all over, also, just do these things at the top every frame... + // actual_speed varies by about 0.5% every audio frame so we need to go back and forth and then when we have lost enough, + // we will start using the average actual speed. To come back at full emulation speed, we check for how long we had been + // running at full speed, we don't wait for m_time_behind_target_speed to come back to 0 because that will never happen, + // what's lost is lost (though recovery could still happen if for some reason we had cycles imprecisions within a frame???) + // Stop using emulation speed, start using actual speed for audio playback, if we fell behind enough + // Filter out small inaccuracies (samples aren't submitted with perfect timing) + else if (actual_speed / emulation_speed < 1.0 - FallbackDelta) + { + // Note: when not able to reach the target speed before m_time_below_target_speed_growing triggers, you might hear crackles due to (probably) having finished the samples + //To review: this happens more often at higher backend latencies, smooth it over time? Ignore first missed frame? + // If we fell behind of m_time_behind_target_speed seconds of samples, start using the actual emulation speed + m_time_behind_target_speed += time_delta * (1.0 - (actual_speed / emulation_speed)); + if (SConfig::GetInstance().m_audio_emu_speed_tolerance >= 0 && + m_time_behind_target_speed > SConfig::GetInstance().m_audio_emu_speed_tolerance / 1000.0) + { + if (!m_time_below_target_speed_growing) + OSD::AddMessage("m_time_below_target_speed_growing = true", 2000U); + //m_time_behind_target_speed = SConfig::GetInstance().NonStretchCorrectionTolerance; + m_time_below_target_speed_growing = true; //To rename + } + } + else + { + //To review: the time to recover is too small? When we have constant small (or big) dips with 100% speed in between, + //we immediately recover speed but then take some time to fall back... Alternatively we could have a "high attention" phase immediately after having recovered full speed + double gain_time_delta = time_delta * (1.0 - (actual_speed / emulation_speed)); + m_time_behind_target_speed = std::max(m_time_behind_target_speed + gain_time_delta, 0.0); + // Time at target speed (to tell if we have recovered full speed) + //To review: this might not work perfectly with iTimingVariance which recovers speed after a small drop in speed + static double FallbackDelta2 = 0.001; + if (average_actual_speed >= emulation_speed - (FallbackDelta2 * emulation_speed)) + { + if (m_time_below_target_speed_growing) + OSD::AddMessage("m_time_below_target_speed_growing = false", 2000U, OSD::Color::GREEN); + m_time_below_target_speed_growing = false; + m_time_behind_target_speed = 0; + } + } + if (m_time_below_target_speed_growing) + { + //To review min. Also, maybe just use the same speed the frame_limiter would above? + target_speed = frame_limiter ? std::min(average_actual_speed, emulation_speed) : average_actual_speed; + INFO_LOG(AUDIO, " actual_speed: %f average_actual_speed: %f", actual_speed, average_actual_speed); + m_time_at_custom_speed = m_time_at_custom_speed + time_delta; + } + else if (frame_limiter) { - unsigned int available_samples = - std::min(m_dma_mixer.AvailableSamples(), m_streaming_mixer.AvailableSamples()); + m_time_at_custom_speed = 0.0; + } - m_scratch_buffer.fill(0); + //To test on GC with weird sample rates again. And with DVD Streaming + //To test latency/buffer buildup (auto adjustment/sync) when we pass from stretching to not stretching. If they are always on the edge... We'll need to slow it down. Have a mirrored version of max_latency (min)? + //To review: this should be done per mixer + double latency; + // The target latency used to be iTimingVariance if stretching was off and + // m_audio_stretch_max_latency if it was on. In the first case, the reason was to cache enough + // samples to be able to withstand small hangs and changes in speed, but our new approach is to + // play samples backwards when we are out of new ones so these are never problems. + double max_latency = Config::Get(Config::MAIN_AUDIO_MIXER_LATENCY) / 1000.0; //To read as low times as possible + //To double or disable when speed is unlimited (or if we can't reach target speed), to avoid constant fluctuations (the average speed will compensate temporary imprecisions anyway) + if (!frame_limiter || m_time_below_target_speed_growing) + { + static double mult = 1.0; + max_latency *= mult; + } + double catch_up_speed; + double target_latency; + max_latency = 100000000000; //To delete - m_dma_mixer.Mix(m_scratch_buffer.data(), available_samples, false); - m_streaming_mixer.Mix(m_scratch_buffer.data(), available_samples, false); - m_wiimote_speaker_mixer.Mix(m_scratch_buffer.data(), available_samples, false); + if (stretching) + { + // If we are reading samples at a slower speed than what they are being pushed, the stretcher + // would keep stacking them forever, so we need to speed up. Given that samples are produced + // in batches, we need to account for a minimum accepted latency. The stretcher latency is + // already post mix. Normal mixers latency does not exist as they are all processed + // immediately. Not that this isn't the whole stretcher latency, there is also the unprocessed + // part which we can't control + latency = m_stretcher.GetProcessedLatency(); + double acceptable_latency = m_stretcher.GetAcceptableLatency() - time_delta; + target_latency = acceptable_latency + max_latency * 0.5; + max_latency += acceptable_latency; + // When we are pitch correcting it's harder to hear the change so correct it faster + catch_up_speed = 1.25; + } + else + { + // Latency should be based on how many samples left we will have after the mixer has run + // (predicted), not before. Otherwise if there is a sudden change of speed in between mixes, + // or if the samples pushes and reads are done with very different timings, the latency won't + // be stable at all and we will end up constantly adjusting it towards a value that makes no + // sense. Also, this way we can target a latency of 0 + double rate = m_dma_mixer.GetInputSampleRate() * target_speed / m_sample_rate; + s32 post_mix_samples = m_dma_mixer.NumSamples(); + post_mix_samples -= num_samples * rate + INTERP_SAMPLES; + latency = std::max(post_mix_samples, 0) / m_dma_mixer.GetInputSampleRate(); + // This isn't big enough to notice but it is enough to make a difference and recover latency + catch_up_speed = 1.015; //To review: this will be very slow if we are going at 0.1 speed already, maybe consider adding it up? + target_latency = max_latency * 0.5; + } + //INFO_LOG(AUDIO, "latency: %lf", latency); + + // Instead of constantly adjusting the playback speed to be as close as possible to the target + // latency as we did before (which lowers quality due to fluctuations), we now have a latency + // tolerance, and while it is self adjusting when it goes too low, we need to make sure it + // doesn't go too high. So when it goes over the limit, we speed up the playback by a very small + // amount, almost unnoticeable, until we will have reached the target latency again. + // The only downside of having a variable latency is in music games, where you need to + // press a button when you hear a sound + if (latency > (m_latency_catching_up ? target_latency : max_latency)) + { + m_latency_catching_up = true; + target_speed *= catch_up_speed; + OSD::AddMessage("Reached max latency", 0U); + } + else + { + m_latency_catching_up = false; + } - if (!m_is_stretching) + if (stretching) + { + if (!m_stretching) { m_stretcher.Clear(); - m_is_stretching = true; + m_stretching = true; } - m_stretcher.ProcessSamples(m_scratch_buffer.data(), available_samples, num_samples); + // Reset the average inside if we are predicting the audio speed, as we need it as up to date + // as possible + m_stretcher.SetTempo(target_speed, predicting); + + u32 available_samples = std::min(m_dma_mixer.AvailableSamples(), m_streaming_mixer.AvailableSamples()); + for (u8 i = 0; i < 4; ++i) + { + if (m_wiimote_speaker_mixer[i].IsCurrentlyPushed()) + { + available_samples = + std::min(available_samples, m_wiimote_speaker_mixer[i].AvailableSamples()); + } + } + + bool scratch_buffer_equal_samples = m_scratch_buffer.data() == samples; + + // If the input and output sample rates difference is too high, available_samples might over the max + m_scratch_buffer.reserve(available_samples * NC); + if (scratch_buffer_equal_samples) + { + samples = m_scratch_buffer.data(); // m_scratch_buffer might have been re-allocated + memset(samples, 0, std::max(available_samples, num_samples) * NC * sizeof(samples[0])); + } + else + { + memset(samples, 0, num_samples * NC * sizeof(samples[0])); + memset(m_scratch_buffer.data(), 0, available_samples * NC * sizeof(m_scratch_buffer[0])); + } + + m_dma_mixer.Mix(m_scratch_buffer.data(), available_samples, true); + m_streaming_mixer.Mix(m_scratch_buffer.data(), available_samples, true); + m_wiimote_speaker_mixer[0].Mix(m_scratch_buffer.data(), available_samples, true); + m_wiimote_speaker_mixer[1].Mix(m_scratch_buffer.data(), available_samples, true); + m_wiimote_speaker_mixer[2].Mix(m_scratch_buffer.data(), available_samples, true); + m_wiimote_speaker_mixer[3].Mix(m_scratch_buffer.data(), available_samples, true); + + m_stretcher.PushSamples(m_scratch_buffer.data(), available_samples); m_stretcher.GetStretchedSamples(samples, num_samples); } else { - m_dma_mixer.Mix(samples, num_samples, true); - m_streaming_mixer.Mix(samples, num_samples, true); - m_wiimote_speaker_mixer.Mix(samples, num_samples, true); - m_is_stretching = false; + memset(samples, 0, num_samples * NC * sizeof(samples[0])); + + m_dma_mixer.Mix(samples, num_samples, false); + m_streaming_mixer.Mix(samples, num_samples, false); + m_wiimote_speaker_mixer[0].Mix(samples, num_samples, false); + m_wiimote_speaker_mixer[1].Mix(samples, num_samples, false); + m_wiimote_speaker_mixer[2].Mix(samples, num_samples, false); + m_wiimote_speaker_mixer[3].Mix(samples, num_samples, false); + + m_stretching = false; } return num_samples; } -unsigned int Mixer::MixSurround(float* samples, unsigned int num_samples) +u32 Mixer::MixSurround(float* samples, u32 num_samples) { - if (!num_samples) - return 0; - - memset(samples, 0, num_samples * SURROUND_CHANNELS * sizeof(float)); - - size_t needed_frames = m_surround_decoder.QueryFramesNeededForSurroundOutput(num_samples); - - // Mix() may also use m_scratch_buffer internally, but is safe because it alternates reads - // and writes. - size_t available_frames = Mix(m_scratch_buffer.data(), static_cast(needed_frames)); - if (available_frames != needed_frames) + memset(samples, 0, num_samples * SURROUND_CHANNELS * sizeof(samples[0])); + + //To clear on settings changed (num_samples): m_surround_decoder.Clear() (thread safe?) + //To fix QuerySamplesNeededForSurroundOutput doesn't work with num_samples 0 or small + size_t needed_samples = m_surround_decoder.QuerySamplesNeededForSurroundOutput(num_samples); + + // If we set our latency too high, we might need more samples than we have, + // as the surround decoder can only accept exactly "needed_samples" + m_scratch_buffer.reserve(needed_samples * NC); + + // Time stretching can be applied before decoding 5.1, it should be fine theoretically (untested). + // Mix() may also use m_scratch_buffer internally, but is safe because we alternate reads + // and writes. It returns the actual number of computed samples, which might be less + // than the required ones, but as long as it computed something (it's not just all padding) + // then we should use it for surround, otherwise these sounds would be missed + size_t available_samples = Mix(m_scratch_buffer.data(), u32(needed_samples)); + //To fix: we can't reach this at lower latencies? Or with stretching on + if (available_samples != needed_samples) { ERROR_LOG_FMT(AUDIO, "Error decoding surround frames."); return 0; } - m_surround_decoder.PutFrames(m_scratch_buffer.data(), needed_frames); - m_surround_decoder.ReceiveFrames(samples, num_samples); + m_surround_decoder.PushSamples(m_scratch_buffer.data(), needed_samples); + m_surround_decoder.GetDecodedSamples(samples, num_samples); return num_samples; } -void Mixer::MixerFifo::PushSamples(const short* samples, unsigned int num_samples) +void Mixer::MixerFifo::PushSamples(const s16* samples, u32 num_samples) { + if (!samples || num_samples == 0) // nullptr can happen + return; + // Cache access in non-volatile variable // indexR isn't allowed to cache in the audio throttling loop as it // needs to get updates to not deadlock. u32 indexW = m_indexW.load(); - // Check if we have enough free space - // indexW == m_indexR results in empty buffer, so indexR must always be smaller than indexW - if (num_samples * 2 + ((indexW - m_indexR.load()) & INDEX_MASK) >= MAX_SAMPLES * 2) - return; + u32 fifo_samples = SamplesDifference(indexW, m_indexR.load()); + + // Check if we have enough free space. Accepting new samples if we haven't played back current + // ones wouldn't make sense, it's cheaper to lose the new ones. + // indexW == indexR results in empty buffer, so indexR must always be smaller than indexW. + // Checking if we received 0 samples to early out is not worth it as it only happens on startup. + if (num_samples * NC + fifo_samples > MAX_SAMPLES * NC) + { + static bool PrintRunOutOfSamples = false; + if (PrintRunOutOfSamples) + OSD::AddMessage("Run out of samples", 100, OSD::Color::RED); + //WARN_LOG(AUDIO, "Too many samples: %i", num_samples * NC + fifo_samples - (MAX_SAMPLES * NC)); + // Fallback to the max we can currently take + num_samples = MAX_SAMPLES - fifo_samples / NC; + } // AyuanX: Actual re-sampling work has been moved to sound thread // to alleviate the workload on main thread // and we simply store raw data here to make fast mem copy - int over_bytes = num_samples * 4 - (MAX_SAMPLES * 2 - (indexW & INDEX_MASK)) * sizeof(short); + constexpr u32 size = sizeof(s16); + int over_bytes = (num_samples * NC - (MAX_SAMPLES * NC - (indexW & INDEX_MASK))) * size; + if (over_bytes > 0) { - memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * 4 - over_bytes); - memcpy(&m_buffer[0], samples + (num_samples * 4 - over_bytes) / sizeof(short), over_bytes); + auto bytes = num_samples * NC * size - over_bytes; + memcpy(&m_buffer[indexW & INDEX_MASK], samples, bytes); + memcpy(&m_buffer[0], samples + bytes / size, over_bytes); } else { - memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * 4); + memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * NC * size); } - m_indexW.fetch_add(num_samples * 2); + m_indexW.fetch_add(num_samples * NC); } -void Mixer::PushSamples(const short* samples, unsigned int num_samples) +void Mixer::PushDMASamples(const s16* samples, u32 num_samples) { + assert(num_samples > 0); //To delete + // Use the DMA samples to determine the emulation speed, the Streaming/DVD + // samples submissions are more frequent, so we could get more precision from + // them, but it's really not necessary. Also, DVD is not really used on Wii + // so it could potentially be disabled for Wii in the future. + // Note that emulation speed can vary a lot, between frame but also between a frame, + // as a frame might take very little, there will be a long sleep before the next one + // starts. And if there is a speed drop, Dolphin will try to catch up (rebounce) to + // reach 100% speed within iTimingVariance milliseconds. + // For these two reasons, checking individual DMA submission times makes little sense, + // it's best to take the average of at least iTimingVariance ms (40 by default) + // We could also rely on an external, more reliable way of calculating the emulation speed (like vblank) but they might be less accurate and I wanted to keep everything here. + // Note that the speed at the beginning and very end of the emulation is wrong because we are receiving silent samples at random rates (depends on your CPU speed). + // No sound will be there to be played anyway so the playback speed does not matter there. + // When hitting a breakpoint or stopping the process in any other way, the timer here will keep running, + // so we could easily either clamp the results within an accepted range, or compare it against another timer (e.g. from the mixer thread), + // and see if that had a massive time between the last 2 calls, and if so, scale the second from the first + m_dma_speed.Update(num_samples); + m_dma_speed.CacheAverageSpeed(false); //To test different lengths + // This average will be slightly outdated when retrieved later as m_time_at_custom_speed + // could have increased in the meanwhile, but it's ok, the error is small enough + m_dma_speed.CacheAverageSpeed(true, m_time_at_custom_speed); + + bool PrintPushedSamples = true; //To delete + if (PrintPushedSamples) INFO_LOG(AUDIO, "dma_mixer added samples: %u, speed: %lf", num_samples, m_dma_speed.GetCachedAverageSpeed()); m_dma_mixer.PushSamples(samples, num_samples); - int sample_rate = m_dma_mixer.GetInputSampleRate(); + + int sample_rate = m_dma_mixer.GetRoundedInputSampleRate(); if (m_log_dsp_audio) m_wave_writer_dsp.AddStereoSamplesBE(samples, num_samples, sample_rate); } -void Mixer::PushStreamingSamples(const short* samples, unsigned int num_samples) +void Mixer::PushStreamingSamples(const s16* samples, u32 num_samples) { + bool PrintPushedSamples = false; //To delete + if (PrintPushedSamples) INFO_LOG(AUDIO, "streaming_mixer added samples: %u", num_samples); m_streaming_mixer.PushSamples(samples, num_samples); - int sample_rate = m_streaming_mixer.GetInputSampleRate(); + + //To review: this mixer varies a lot in num_samples? + // Check whether the wii mote speaker mixers have finished pushing. We do it + // from this mixer as it's the one with the higher update frequency. + // Yes, it's a bit of a hack but it works fine + double time_delta = double(num_samples) / m_dma_mixer.GetInputSampleRate(); + m_wiimote_speaker_mixer[0].UpdatePush(-time_delta); + m_wiimote_speaker_mixer[1].UpdatePush(-time_delta); + m_wiimote_speaker_mixer[2].UpdatePush(-time_delta); + m_wiimote_speaker_mixer[3].UpdatePush(-time_delta); + + int sample_rate = m_streaming_mixer.GetRoundedInputSampleRate(); if (m_log_dtk_audio) m_wave_writer_dtk.AddStereoSamplesBE(samples, num_samples, sample_rate); } -void Mixer::PushWiimoteSpeakerSamples(const short* samples, unsigned int num_samples, - unsigned int sample_rate) +void Mixer::PushWiimoteSpeakerSamples(u8 index, const s16* samples, u32 num_samples, + u32 sample_rate) { - short samples_stereo[MAX_SAMPLES * 2]; + num_samples = std::min(num_samples, MAX_SAMPLES); - if (num_samples < MAX_SAMPLES) - { - m_wiimote_speaker_mixer.SetInputSampleRate(sample_rate); + m_wiimote_speaker_mixer[index].SetInputSampleRate(sample_rate); - for (unsigned int i = 0; i < num_samples; ++i) - { - samples_stereo[i * 2] = Common::swap16(samples[i]); - samples_stereo[i * 2 + 1] = Common::swap16(samples[i]); - } + m_wiimote_speaker_mixer[index].UpdatePush(double(num_samples) / sample_rate); - m_wiimote_speaker_mixer.PushSamples(samples_stereo, num_samples); + for (u32 i = 0; i < num_samples; ++i) + { + // Wii mote speaker samples are mono and aren't swapped like the other, so we pre-swap them + m_conversion_buffer[i * NC] = Common::swap16(samples[i]); + m_conversion_buffer[i * NC + 1] = m_conversion_buffer[i * NC]; } + + m_wiimote_speaker_mixer[index].PushSamples(m_conversion_buffer, num_samples); } -void Mixer::SetDMAInputSampleRate(unsigned int rate) +void Mixer::SetDMAInputSampleRate(double rate) { m_dma_mixer.SetInputSampleRate(rate); + m_dma_speed.SetTicksPerSecond(rate); } -void Mixer::SetStreamInputSampleRate(unsigned int rate) +void Mixer::SetStreamingInputSampleRate(double rate) { m_streaming_mixer.SetInputSampleRate(rate); } -void Mixer::SetStreamingVolume(unsigned int lvolume, unsigned int rvolume) +void Mixer::SetStreamingVolume(u32 lVolume, u32 rVolume) { - m_streaming_mixer.SetVolume(lvolume, rvolume); + m_streaming_mixer.SetVolume(lVolume, rVolume); } -void Mixer::SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume) +void Mixer::SetWiimoteSpeakerVolume(u8 index, u32 lVolume, u32 rVolume) { - m_wiimote_speaker_mixer.SetVolume(lvolume, rvolume); + m_wiimote_speaker_mixer[index].SetVolume(lVolume, rVolume); } void Mixer::StartLogDTKAudio(const std::string& filename) { if (!m_log_dtk_audio) { - bool success = m_wave_writer_dtk.Start(filename, m_streaming_mixer.GetInputSampleRate()); + bool success = m_wave_writer_dtk.Start(filename, m_streaming_mixer.GetRoundedInputSampleRate()); if (success) { m_log_dtk_audio = true; @@ -328,7 +772,7 @@ void Mixer::StartLogDSPAudio(const std::string& filename) { if (!m_log_dsp_audio) { - bool success = m_wave_writer_dsp.Start(filename, m_dma_mixer.GetInputSampleRate()); + bool success = m_wave_writer_dsp.Start(filename, m_dma_mixer.GetRoundedInputSampleRate()); if (success) { m_log_dsp_audio = true; @@ -364,30 +808,147 @@ void Mixer::StopLogDSPAudio() void Mixer::MixerFifo::DoState(PointerWrap& p) { p.Do(m_input_sample_rate); - p.Do(m_LVolume); - p.Do(m_RVolume); + p.Do(m_lVolume); + p.Do(m_rVolume); + + if (p.GetMode() == PointerWrap::MODE_READ) + { + // Restore fract to a neutral value + m_fract = -1.0; + } } -void Mixer::MixerFifo::SetInputSampleRate(unsigned int rate) +void Mixer::MixerFifo::SetInputSampleRate(double rate) { + // We should theoretically play all the current samples at the old sample rate, + // but the reality of that happening on real hardware when there are non zero + // samples is pretty low if not impossible m_input_sample_rate = rate; + //To review: this happens every frame it's called for the wii mote speaker + //To review: if in and out are the same but fract has been polluted by a small change in speed once, should we reset it? + // If we passed from an input sample rate different from the output one to + // one that is identical, "fract" would permanently stay offsetted, which we don't really want. + // This is likely to only happen before the game has actually played any sounds anyway + if (m_constantly_pushed) + m_fract = -1.0; } -unsigned int Mixer::MixerFifo::GetInputSampleRate() const +double Mixer::MixerFifo::GetInputSampleRate() const { return m_input_sample_rate; } +// For places that don't support floating point sample rates +u32 Mixer::MixerFifo::GetRoundedInputSampleRate() const +{ + return round(m_input_sample_rate); +} -void Mixer::MixerFifo::SetVolume(unsigned int lvolume, unsigned int rvolume) +void Mixer::MixerFifo::SetVolume(u32 lVolume, u32 rVolume) { - m_LVolume.store(lvolume + (lvolume >> 7)); - m_RVolume.store(rvolume + (rvolume >> 7)); + m_lVolume.store(lVolume + (lVolume >> 7)); + m_rVolume.store(rVolume + (rVolume >> 7)); } -unsigned int Mixer::MixerFifo::AvailableSamples() const +u32 Mixer::MixerFifo::AvailableSamples() const { - unsigned int samples_in_fifo = ((m_indexW.load() - m_indexR.load()) & INDEX_MASK) / 2; - if (samples_in_fifo <= 1) - return 0; // Mixer::MixerFifo::Mix always keeps one sample in the buffer. - return (samples_in_fifo - 1) * m_mixer->m_sampleRate / m_input_sample_rate; + u32 fifo_samples = NumSamples(); + // Mixer::MixerFifo::Mix always keeps some sample in the buffer, we want to ignore them + if (fifo_samples <= INTERP_SAMPLES) + return 0; + return (fifo_samples - INTERP_SAMPLES) * m_mixer->m_sample_rate / m_input_sample_rate; +} + +u32 Mixer::MixerFifo::NumSamples() const +{ + return SamplesDifference(m_indexW.load(), m_indexR.load()) / NC; +} + +u32 Mixer::MixerFifo::SamplesDifference(u32 indexW, u32 indexR) const +{ + // We can't have more than MAX_SAMPLES, if we do, we loop over + u32 diff = indexW - GetNextIndexR(indexR); + u32 normalized_diff = diff & INDEX_MASK; + return normalized_diff == 0u ? (diff == 0u ? 0u : (MAX_SAMPLES * NC)) : normalized_diff; +} + +u32 Mixer::MixerFifo::GetNextIndexR(u32 indexR) const +{ + double rate = (m_input_sample_rate * m_mixer->GetCurrentSpeed()) / m_mixer->m_sample_rate; + return indexR + (m_fract >= 0.0 ? NC * u32(m_fract + rate) : 0.0); +} + +void Mixer::MixerFifo::UpdatePush(double time) +{ + bool currently_pushed; + + // Stop this mixer if it hasn't been pushed within the expected time + if (time >= 0.0) + { + m_last_push_timer = std::max(m_last_push_timer, time); + currently_pushed = m_last_push_timer > 0.0; + } + // Make sure at least 2 updates elapse before disabling "m_currently_pushed", + // as if the negative update time is sporadic but larger, it might stop before + // enough time has actually elapsed + else if (m_last_push_timer > 0.0) + { + m_last_push_timer += time; + currently_pushed = true; + } + else + { + currently_pushed = false; + } + + if (m_currently_pushed != currently_pushed) + { + m_currently_pushed = currently_pushed; + if (m_currently_pushed) + { + //To do more here? e.g. save the last playable backwards sample... (maybe, not easy to do) + //To now increase indexR by 1 or 3 or 4? Otherwise it would be slightly delayed, but I guess it's fine as it would be the same case even if we didn't add samples... + //Make sure the first read sample (index) will be the first pushed one + //m_indexR.fetch_add((INTERP_SAMPLES + 1) * NC); + //To review: add some silent samples to make sure we don't run out of samples. The target latency precisely. + // When stretching, we don't exactly need to add a latency on top to make sure it won't run out of samples, + // but we do to keep consistency (actually, don't do it! It add useless latency but what was the other problem??? Loss of sync between mixers? Just being useless?) + //To review: this works originally but then it breaks after enabling and disabling stretching??? + //To review: make sure the post mix latency is correct. We can't know how many samples the backend will read and we can't know how many more samples we will receive before the next + //backend read, but we approximate... It should be fine + // Note that this wouldn't work if the backend/surround (mix) latency (requested samples) was too high, as you'd run out of samples in the first mix after this, + // as this possibly started towards the end of 2 mix calls, and it hasn't build up enough samples. 3 alternative solutions: + // -Find a way to always push silent wii mote speaker samples when the wii mote is not pushing any (e.g. from the DVD Stream push) + // -Slow down the first mix of this any time we start pushing, to the point where it would only exactly play the number of samples available, + // to not incur in padding for the next audio frames + // -Predict the next mix accuracy (by calling queryNofFra...) and setting that as min latency, maybe also check how long it has elapsed since the last mix call, + // to know whether we'd manage to fill it up already with the next wii mote speaker samples pushes (assuming the minimum length of a wii mote speaker push is like 0.2ms). + // Then using the min between the backend latency and the target latency. This might result in going over the max latency so it would speed up the sound speed + // The wii mote pushes 40 samples at 6000Hz, which means 2.5 submissions per frame at 60fps. After pressing the input responsible for triggering samples, at least 2 batches of samples will be pushed in the next frame, though the mixing could happen at any time... + // On original HW, might have been turned off on the last sample, meaning that if the sample wasn't 0, it wouldn't have made any additional sounds + //To review: could we use the: Wii Mote Speaker enabled/disabled information in any way? + if (!SConfig::GetInstance().m_audio_stretch) + { + u32 num_samples = + (Config::Get(Config::MAIN_AUDIO_MIXER_LATENCY) / 1000.0) * 0.5 * m_input_sample_rate; + num_samples = std::min(num_samples, MAX_SAMPLES); + memset(m_mixer->m_conversion_buffer, 0, + num_samples * NC * sizeof(m_mixer->m_conversion_buffer[0])); + PushSamples(m_mixer->m_conversion_buffer, num_samples); + } + } + else + { + //To do this: increase indexW by 4 more every time, with 4 samples of silence, if when new + //samples are pushed, we haven't finished reading (indexR), then we take out the last 4 samples + //(doesn't work with sound stretching). Or save a bool which will increase indexR by 4 + + OSD::AddMessage("last sample: " + std::to_string(m_buffer[(m_indexW - 2) & INDEX_MASK]), 0U); + + constexpr u32 num_samples = INTERP_SAMPLES + 1; + // Add enough samples of silence to make sure when it has finished reading it won't stop on a + // non zero sample (which would break padding) + s16 silent_samples[num_samples * NC]{}; + PushSamples(silent_samples, num_samples); + } + } } diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index b38ab7f47a98..3ffa365125a6 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -6,8 +6,10 @@ #include #include +#include #include "AudioCommon/AudioStretcher.h" +#include "AudioCommon/AudioSpeedCounter.h" #include "AudioCommon/SurroundDecoder.h" #include "AudioCommon/WaveFile.h" #include "Common/CommonTypes.h" @@ -17,25 +19,27 @@ class PointerWrap; class Mixer final { public: - explicit Mixer(unsigned int BackendSampleRate); + explicit Mixer(u32 sample_rate); ~Mixer(); void DoState(PointerWrap& p); - // Called from audio threads - unsigned int Mix(short* samples, unsigned int numSamples); - unsigned int MixSurround(float* samples, unsigned int num_samples); - - // Called from main thread - void PushSamples(const short* samples, unsigned int num_samples); - void PushStreamingSamples(const short* samples, unsigned int num_samples); - void PushWiimoteSpeakerSamples(const short* samples, unsigned int num_samples, - unsigned int sample_rate); - unsigned int GetSampleRate() const { return m_sampleRate; } - void SetDMAInputSampleRate(unsigned int rate); - void SetStreamInputSampleRate(unsigned int rate); - void SetStreamingVolume(unsigned int lvolume, unsigned int rvolume); - void SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume); + void SetPaused(bool paused); + + // Called from audio threads: + u32 Mix(s16* samples, u32 num_samples); + u32 MixSurround(float* samples, u32 num_samples); + + // Called from main thread: + void PushDMASamples(const s16* samples, u32 num_samples); + void PushStreamingSamples(const s16* samples, u32 num_samples); + void PushWiimoteSpeakerSamples(u8 index, const s16* samples, u32 num_samples, u32 sample_rate); + + void SetDMAInputSampleRate(double rate); + void SetStreamingInputSampleRate(double rate); + + void SetStreamingVolume(u32 lVolume, u32 rVolume); + void SetWiimoteSpeakerVolume(u8 index, u32 lVolume, u32 rVolume); void StartLogDTKAudio(const std::string& filename); void StopLogDTKAudio(); @@ -43,54 +47,122 @@ class Mixer final void StartLogDSPAudio(const std::string& filename); void StopLogDSPAudio(); - float GetCurrentSpeed() const { return m_speed.load(); } - void UpdateSpeed(float val) { m_speed.store(val); } + // Only call from main thread and when the mix is not running + void SetSampleRate(u32 sample_rate); + + u32 GetSampleRate() const { return m_sample_rate; } + double GetCurrentSpeed() const { return m_target_speed; } + + // 512ms at 32kHz and ~341ms at 48kHz (on Wii, GC has similar values). + // Make sure this is a power of 2 for INDEX_MASK to work, + // if not change all the "& INDEX_MASK" to "% (MAX_SAMPLES * NC)". + // It's important that this is high enough to allow for enough backwards + // samples to be played in a frame dip. It doesn't make much sense that + // this is independent from the sample rate, but it's fine + static constexpr u32 MAX_SAMPLES = 16384; private: - static constexpr u32 MAX_SAMPLES = 1024 * 4; // 128 ms - static constexpr u32 INDEX_MASK = MAX_SAMPLES * 2 - 1; - static constexpr int MAX_FREQ_SHIFT = 200; // Per 32000 Hz - static constexpr float CONTROL_FACTOR = 0.2f; - static constexpr u32 CONTROL_AVG = 32; // In freq_shift per FIFO size offset + // Number of channels the mixer has. Shortened because it is constantly used. + // The main reason we made this is to allow for faster conversion to another number + // of channels, as most of the code is channel agnostic + static constexpr u32 NC = 2u; + static constexpr u32 SURROUND_CHANNELS = 6u; + + static constexpr u32 INDEX_MASK = MAX_SAMPLES * NC - 1; - const unsigned int SURROUND_CHANNELS = 6; + // Interpolation reserved/required samples + static constexpr u8 INTERP_SAMPLES = 3u; + // A single mixer in/out buffer. The original alignment they might have had on real HW isn't kept class MixerFifo final { public: - MixerFifo(Mixer* mixer, unsigned sample_rate) : m_mixer(mixer), m_input_sample_rate(sample_rate) + MixerFifo(Mixer* mixer, unsigned sample_rate, bool constantly_pushed = true) + : m_mixer(mixer), m_input_sample_rate(sample_rate), m_constantly_pushed(constantly_pushed) { } void DoState(PointerWrap& p); - void PushSamples(const short* samples, unsigned int num_samples); - unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true); - void SetInputSampleRate(unsigned int rate); - unsigned int GetInputSampleRate() const; - void SetVolume(unsigned int lvolume, unsigned int rvolume); - unsigned int AvailableSamples() const; + void PushSamples(const s16* samples, u32 num_samples); + // Returns the actual mixed samples num, pads the rest with the last sample. + // Executed from sound stream thread + u32 Mix(s16* samples, u32 num_samples, bool stretching = false); + void SetInputSampleRate(double rate); + double GetInputSampleRate() const; + u32 GetRoundedInputSampleRate() const; + bool IsCurrentlyPushed() const { return m_currently_pushed; } + // Expects values from 0 to 255 + void SetVolume(u32 lVolume, u32 rVolume); + // Returns the max number of samples we will be able to mix. + // This is not precise due to the interpolation fract, but it's close enough + u32 AvailableSamples() const; + u32 NumSamples() const; + u32 SamplesDifference(u32 indexW, u32 indexR) const; + u32 GetNextIndexR(u32 indexR) const; + void UpdatePush(double time); + + // Returns the actual number of samples written. Outputs the last played sample for padding + u32 CubicInterpolation(s16* samples, u32 num_samples, double rate, u32& indexR, u32 indexW, + s16& l_s, s16& r_s, s32 lVolume, s32 rVolume, bool forwards = true); private: Mixer* m_mixer; - unsigned m_input_sample_rate; - std::array m_buffer{}; - std::atomic m_indexW{0}; + std::atomic m_input_sample_rate; + // Ring input buffer directly from emulated HW. In big endians. + // Even indexes are left channel, same as the output + std::array m_buffer{}; + + // Write (how many have been written - 1, index of the last one to have been written) + // Start from max so even indices will be left channel and odd right + // Start from INTERP_SAMPLES + 1 so that we gradually blend into the initial samples + std::atomic m_indexW{(INTERP_SAMPLES + 1) * NC}; + //To describe + // Read (how many have been read - 1, index of last one to have been read) std::atomic m_indexR{0}; - // Volume ranges from 0-256 - std::atomic m_LVolume{256}; - std::atomic m_RVolume{256}; - float m_numLeftI = 0.0f; - u32 m_frac = 0; + // If their difference is 0, we will have read all the samples (or there are none left anyway). + // We could still re-read the current indexR to get the last sample value (0 if never written). + // This is because we don't want to increase indexR over indexW to tell that we have finished reading + // the pushed samples, it would be confusing. + // indexR is the last read (fully consumed, we moved over) value, while indexW is the last written + 1 (the next one to be written), + // so if they are the same, we have read over the last written. (???) + u32 m_backwards_indexR = 0; + // Volume range is 0-256 + std::atomic m_lVolume{256}; + std::atomic m_rVolume{256}; + s16 m_last_output_samples[NC]{}; + double m_fract = -1.0; + double m_backwards_fract = -1.0; + //To review: if this was off, that mixer won't gather a buffer/latency, and it would always be on the brink of playback + //Don't do any padding for the wiimote speaker, because samples are only submitted when played, + //so the last one might not be 0, which would end up offsetting every other sample forever + // Ignores simple sound stretching and padding and backwards playing + bool m_constantly_pushed; + std::atomic m_currently_pushed{false}; + double m_last_push_timer = -1.0; }; MixerFifo m_dma_mixer{this, 32000}; MixerFifo m_streaming_mixer{this, 48000}; - MixerFifo m_wiimote_speaker_mixer{this, 3000}; - unsigned int m_sampleRate; + // There is no way of knowing when the wii mote speakers will have finished pushing samples + MixerFifo m_wiimote_speaker_mixer[4]{ + {this, 3000, false}, {this, 3000, false}, {this, 3000, false}, {this, 3000, false}}; + u32 m_sample_rate; + + std::vector m_scratch_buffer; + std::array m_interpolation_buffer; + s16 m_conversion_buffer[MAX_SAMPLES * NC]; + + int m_on_state_changed_handle = -1; + + // Target emulation speed, but it can fallback to the actual emulation speed + double m_target_speed = 1.0; //To make atomic (m_fract as well???) (make sure they aren't used twice in a line if so) + double m_time_behind_target_speed = 0.0; + bool m_time_below_target_speed_growing = false; //To rename? + std::atomic m_time_at_custom_speed{0.0}; + bool m_latency_catching_up = false; - bool m_is_stretching = false; + bool m_stretching = false; AudioCommon::AudioStretcher m_stretcher; AudioCommon::SurroundDecoder m_surround_decoder; - std::array m_scratch_buffer; WaveFileWriter m_wave_writer_dtk; WaveFileWriter m_wave_writer_dsp; @@ -98,6 +170,7 @@ class Mixer final bool m_log_dtk_audio = false; bool m_log_dsp_audio = false; - // Current rate of emulation (1.0 = 100% speed) - std::atomic m_speed{0.0f}; + // Average of the last 0.425 seconds, Start at the most + // common sample rate and pushed samples num per batch + AudioSpeedCounter m_dma_speed{0.425, 32000, 560}; }; diff --git a/Source/Core/AudioCommon/NullSoundStream.cpp b/Source/Core/AudioCommon/NullSoundStream.cpp index c27e407a7853..aec488f56071 100644 --- a/Source/Core/AudioCommon/NullSoundStream.cpp +++ b/Source/Core/AudioCommon/NullSoundStream.cpp @@ -4,10 +4,6 @@ #include "AudioCommon/NullSoundStream.h" -void NullSound::SoundLoop() -{ -} - bool NullSound::Init() { return true; @@ -17,11 +13,3 @@ bool NullSound::SetRunning(bool running) { return true; } - -void NullSound::SetVolume(int volume) -{ -} - -void NullSound::Update() -{ -} diff --git a/Source/Core/AudioCommon/NullSoundStream.h b/Source/Core/AudioCommon/NullSoundStream.h index 4bcc0c26824b..4161705c053d 100644 --- a/Source/Core/AudioCommon/NullSoundStream.h +++ b/Source/Core/AudioCommon/NullSoundStream.h @@ -6,14 +6,11 @@ #include "AudioCommon/SoundStream.h" +// Sound mixer is still created and samples are still pushed to it, +// but they aren't outputtted class NullSound final : public SoundStream { public: bool Init() override; - void SoundLoop() override; bool SetRunning(bool running) override; - void SetVolume(int volume) override; - void Update() override; - - static bool isValid() { return true; } }; diff --git a/Source/Core/AudioCommon/OpenALStream.cpp b/Source/Core/AudioCommon/OpenALStream.cpp index ed38b1e3e29b..6f876e2d24c7 100644 --- a/Source/Core/AudioCommon/OpenALStream.cpp +++ b/Source/Core/AudioCommon/OpenALStream.cpp @@ -10,6 +10,7 @@ #include #include "AudioCommon/OpenALStream.h" +#include "AudioCommon/AudioCommon.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/Thread.h" @@ -84,7 +85,7 @@ static bool InitLibrary() return true; } -bool OpenALStream::isValid() +bool OpenALStream::IsValid() { return InitLibrary(); } @@ -118,6 +119,10 @@ bool OpenALStream::Init() return false; } + m_mixer->SetSampleRate(SConfig::GetInstance().bUseOSMixerSampleRate ? + AudioCommon::GetOSMixerSampleRate() : + AudioCommon::GetDefaultSampleRate()); + palcMakeContextCurrent(context); m_run_thread.Set(); m_thread = std::thread(&OpenALStream::SoundLoop, this); @@ -126,9 +131,8 @@ bool OpenALStream::Init() OpenALStream::~OpenALStream() { - m_run_thread.Clear(); // kick the thread if it's waiting - m_sound_sync_event.Set(); + m_run_thread.Clear(); m_thread.join(); @@ -158,7 +162,6 @@ void OpenALStream::SetVolume(int volume) void OpenALStream::Update() { - m_sound_sync_event.Set(); } bool OpenALStream::SetRunning(bool running) @@ -230,16 +233,11 @@ void OpenALStream::SoundLoop() u32 frequency = m_mixer->GetSampleRate(); - u32 frames_per_buffer; // Can't have zero samples per buffer - if (SConfig::GetInstance().iLatency > 0) - { - frames_per_buffer = frequency / 1000 * SConfig::GetInstance().iLatency / OAL_BUFFERS; - } - else - { - frames_per_buffer = frequency / 1000 * 1 / OAL_BUFFERS; - } + unsigned int target_latency = std::max(AudioCommon::GetUserTargetLatency(), 1ul); + + u32 frames_per_buffer; + frames_per_buffer = frequency / 1000 * target_latency / OAL_BUFFERS; if (frames_per_buffer > OAL_MAX_FRAMES) { @@ -277,7 +275,8 @@ void OpenALStream::SoundLoop() while (m_run_thread.IsSet()) { - // Block until we have a free buffer + // TODO: WaitForMultipleObjects to stop the thread immediately or wait, like in WASAPIStream.cpp + // Block until we have a free buffer. We have clamped our latency to the length of this sleep int num_buffers_processed; palGetSourcei(m_source, AL_BUFFERS_PROCESSED, &num_buffers_processed); if (num_buffers_queued == OAL_BUFFERS && !num_buffers_processed) @@ -286,7 +285,7 @@ void OpenALStream::SoundLoop() continue; } - // Remove the Buffer from the Queue. + // Remove the Buffer from the Queue if (num_buffers_processed) { std::array unqueued_buffer_ids; @@ -351,7 +350,7 @@ void OpenALStream::SoundLoop() err = CheckALError("buffering data"); if (err == AL_INVALID_ENUM) { - // 5.1 is not supported by the host, fallback to stereo + // 5.1 is not supported by the host, fallback to stereo in the next audio frame WARN_LOG_FMT( AUDIO, "Unable to set 5.1 surround mode. Updating OpenAL Soft might fix this issue."); use_surround = false; diff --git a/Source/Core/AudioCommon/OpenALStream.h b/Source/Core/AudioCommon/OpenALStream.h index ab83fb892eb9..dbc146d3fd1d 100644 --- a/Source/Core/AudioCommon/OpenALStream.h +++ b/Source/Core/AudioCommon/OpenALStream.h @@ -62,14 +62,12 @@ class OpenALStream final : public SoundStream bool SetRunning(bool running) override; void Update() override; - static bool isValid(); + static bool IsValid(); private: std::thread m_thread; Common::Flag m_run_thread; - Common::Event m_sound_sync_event; - std::vector m_realtime_buffer; std::array m_buffers; ALuint m_source; diff --git a/Source/Core/AudioCommon/OpenSLESStream.h b/Source/Core/AudioCommon/OpenSLESStream.h index 8a76781dbf07..c7877688aa0f 100644 --- a/Source/Core/AudioCommon/OpenSLESStream.h +++ b/Source/Core/AudioCommon/OpenSLESStream.h @@ -17,7 +17,7 @@ class OpenSLESStream final : public SoundStream bool Init() override; bool SetRunning(bool running) override { return running; } void SetVolume(int volume) override; - static bool isValid() { return true; } + static bool IsValid() { return true; } private: std::thread thread; diff --git a/Source/Core/AudioCommon/PulseAudioStream.h b/Source/Core/AudioCommon/PulseAudioStream.h index 3ff3043d3240..ee3a99f813b6 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.h +++ b/Source/Core/AudioCommon/PulseAudioStream.h @@ -22,7 +22,7 @@ class PulseAudio final : public SoundStream bool Init() override; bool SetRunning(bool running) override { return running; } - static bool isValid() { return true; } + static bool IsValid() { return true; } void StateCallback(pa_context* c); void WriteCallback(pa_stream* s, size_t length); void UnderflowCallback(pa_stream* s); diff --git a/Source/Core/AudioCommon/SoundStream.cpp b/Source/Core/AudioCommon/SoundStream.cpp new file mode 100644 index 000000000000..4a53b11489f7 --- /dev/null +++ b/Source/Core/AudioCommon/SoundStream.cpp @@ -0,0 +1,10 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "AudioCommon/SoundStream.h" +#include "AudioCommon/AudioCommon.h" + +SoundStream::SoundStream() : m_mixer(new Mixer(AudioCommon::GetDefaultSampleRate())) +{ +} diff --git a/Source/Core/AudioCommon/SoundStream.h b/Source/Core/AudioCommon/SoundStream.h index b5f40dea151c..a1a766e4a781 100644 --- a/Source/Core/AudioCommon/SoundStream.h +++ b/Source/Core/AudioCommon/SoundStream.h @@ -9,19 +9,27 @@ #include "AudioCommon/Mixer.h" #include "Common/CommonTypes.h" +class Mixer; + +// Abstract class for different sound backends. +// Needs to be created and destroyed within the same thread class SoundStream { protected: std::unique_ptr m_mixer; public: - SoundStream() : m_mixer(new Mixer(48000)) {} + SoundStream(); virtual ~SoundStream() {} - static bool isValid() { return false; } + static bool IsValid() { return false; } Mixer* GetMixer() const { return m_mixer.get(); } virtual bool Init() { return false; } virtual void SetVolume(int) {} virtual void SoundLoop() {} virtual void Update() {} + virtual bool SupportsRuntimeSettingsChanges() const { return false; } + virtual void OnSettingsChanged() {} + // Can be called by the main thread or the emulator/video thread, + // never concurrently. Only call this through AudioCommons virtual bool SetRunning(bool running) { return false; } }; diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 8ded0ca5e87d..84db0e367ef5 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -12,11 +12,28 @@ namespace AudioCommon constexpr size_t STEREO_CHANNELS = 2; constexpr size_t SURROUND_CHANNELS = 6; -SurroundDecoder::SurroundDecoder(u32 sample_rate, u32 frame_block_size) - : m_sample_rate(sample_rate), m_frame_block_size(frame_block_size) +// Quality (higher quality also means more latency) +static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) +{ + switch (quality) + { + case DPL2Quality::Lowest: + return 512; + case DPL2Quality::Low: + return 1024; + case DPL2Quality::Highest: + return 4096; + default: // AudioCommon::DPL2Quality::High + return 2048; + } +} + +SurroundDecoder::SurroundDecoder(u32 sample_rate, DPL2Quality quality) + : m_sample_rate(sample_rate), m_frame_block_size(DPL2QualityToFrameBlockSize(quality, sample_rate)) { m_fsdecoder = std::make_unique(); m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate); + //To review (make config and remove comment, also, move to re-init): m_fsdecoder->set_bass_redirection(false); } SurroundDecoder::~SurroundDecoder() = default; @@ -27,33 +44,39 @@ void SurroundDecoder::Clear() m_decoded_fifo.clear(); } +void SurroundDecoder::SetSampleRate(u32 sample_rate) +{ + //To finish, also set DPL2QualityToFrameBlockSize and fix it by sample rate + // With "FreeSurroundDecoder" there is no need of setting the sample_rate, it's only used +} + // Currently only 6 channels are supported. -size_t SurroundDecoder::QueryFramesNeededForSurroundOutput(const size_t output_frames) const +size_t SurroundDecoder::QuerySamplesNeededForSurroundOutput(const size_t output_samples) const { - if (m_decoded_fifo.size() < output_frames * SURROUND_CHANNELS) + if (m_decoded_fifo.size() < output_samples * SURROUND_CHANNELS) { - // Output stereo frames needed to have at least the desired number of surround frames - size_t frames_needed = output_frames - m_decoded_fifo.size() / SURROUND_CHANNELS; - return frames_needed + m_frame_block_size - frames_needed % m_frame_block_size; + // Output stereo samples needed to have at least the desired number of surround samples + size_t samples_needed = output_samples - m_decoded_fifo.size() / SURROUND_CHANNELS; + return samples_needed + m_frame_block_size - samples_needed % m_frame_block_size; } return 0; } // Receive and decode samples -void SurroundDecoder::PutFrames(const short* in, const size_t num_frames_in) +void SurroundDecoder::PushSamples(const s16* in, const size_t num_samples) { // Maybe check if it is really power-of-2? - s64 remaining_frames = static_cast(num_frames_in); - size_t frame_index = 0; + s64 remaining_samples = static_cast(num_samples); + size_t sample_index = 0; - while (remaining_frames > 0) + while (remaining_samples > 0) { // Convert to float for (size_t i = 0, end = m_frame_block_size * STEREO_CHANNELS; i < end; ++i) { - m_float_conversion_buffer[i] = in[i + frame_index * STEREO_CHANNELS] / - static_cast(std::numeric_limits::max()); + m_float_conversion_buffer[i] = in[i + sample_index * STEREO_CHANNELS] / + static_cast(std::numeric_limits::max()); } // Decode @@ -70,20 +93,22 @@ void SurroundDecoder::PutFrames(const short* in, const size_t num_frames_in) m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 0]); // LEFTFRONT m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 2]); // RIGHTFRONT m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 1]); // CENTREFRONT + // The LFE channel is disabled in the surround decoder, as most people + // have their own low pass crossover m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 5]); // sub/lfe m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 3]); // LEFTREAR m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 4]); // RIGHTREAR } - remaining_frames = remaining_frames - static_cast(m_frame_block_size); - frame_index = frame_index + m_frame_block_size; + remaining_samples = remaining_samples - static_cast(m_frame_block_size); + sample_index = sample_index + m_frame_block_size; } } -void SurroundDecoder::ReceiveFrames(float* out, const size_t num_frames_out) +void SurroundDecoder::GetDecodedSamples(float* out, const size_t num_samples) { - // Copy to output array with desired num_frames_out - for (size_t i = 0, num_samples_output = num_frames_out * SURROUND_CHANNELS; + // Copy to output array with desired num_samples + for (size_t i = 0, num_samples_output = num_samples * SURROUND_CHANNELS; i < num_samples_output; ++i) { out[i] = m_decoded_fifo.pop_front(); diff --git a/Source/Core/AudioCommon/SurroundDecoder.h b/Source/Core/AudioCommon/SurroundDecoder.h index 76f16db51842..d8913b112e64 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.h +++ b/Source/Core/AudioCommon/SurroundDecoder.h @@ -7,6 +7,7 @@ #include #include +#include "AudioCommon/Enums.h" #include "Common/CommonTypes.h" #include "Common/FixedSizeQueue.h" @@ -17,11 +18,12 @@ namespace AudioCommon class SurroundDecoder { public: - explicit SurroundDecoder(u32 sample_rate, u32 frame_block_size); + explicit SurroundDecoder(u32 sample_rate, DPL2Quality quality); ~SurroundDecoder(); - size_t QueryFramesNeededForSurroundOutput(const size_t output_frames) const; - void PutFrames(const short* in, const size_t num_frames_in); - void ReceiveFrames(float* out, const size_t num_frames_out); + size_t QuerySamplesNeededForSurroundOutput(const size_t output_samples) const; + void SetSampleRate(u32 sample_rate); + void PushSamples(const s16* in, const size_t num_samples); + void GetDecodedSamples(float* out, const size_t num_samples); void Clear(); private: diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index a22d36912143..e99bfb4f3d88 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -10,343 +10,986 @@ #include #include #include -#include #include -#include +#include // clang-format on -#include - -#include "Common/Assert.h" +#include "AudioCommon/AudioCommon.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" #include "Common/Thread.h" #include "Core/ConfigManager.h" #include "VideoCommon/OnScreenDisplay.h" -using Microsoft::WRL::ComPtr; +#define STEREO_CHANNELS 2 +#define SURROUND_CHANNELS 6 +// Name that the config string we read from will have if it wants the default device/endpoint to be +// used. This is better than an empty string as some devices might theoretically not have a name +#define DEFAULT_DEVICE_NAME "default" -WASAPIStream::WASAPIStream() +// Helper to avoid having to call CoUninitialize() on multiple return cases +class AutoCoInit { - if (SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) - m_coinitialize.activate(); - - m_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - m_format.Format.nChannels = 2; - m_format.Format.nSamplesPerSec = GetMixer()->GetSampleRate(); - m_format.Format.nAvgBytesPerSec = m_format.Format.nSamplesPerSec * 4; - m_format.Format.nBlockAlign = 4; - m_format.Format.wBitsPerSample = 16; - m_format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - m_format.Samples.wValidBitsPerSample = m_format.Format.wBitsPerSample; - m_format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; - m_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; -} +public: + AutoCoInit() + { + // COINIT_MULTITHREADED seems to be the better choice here given we handle multithread access + // safely ourself and WASAPI is made for MTA. It should also perform better. + // Note that calls to this from the main thread will return RPC_E_CHANGED_MODE + // as some external library is calling CoInitialize with COINIT_APARTMENTTHREADED + // without uninitializing it. We keep going + result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + // It's very rare for this to happen so we don't append the consequences of the error + if (result != RPC_E_CHANGED_MODE && FAILED(result)) + ERROR_LOG(AUDIO, "WASAPI: Failed to initialize the COM library"); + } + ~AutoCoInit() + { + if (SUCCEEDED(result)) + CoUninitialize(); + } -WASAPIStream::~WASAPIStream() + bool Succeeded() const { return result == RPC_E_CHANGED_MODE || SUCCEEDED(result); } + +private: + HRESULT result; +}; + +// Helper to print a failed message on screen if we fail to start (and isn't retrying) +class AutoPrintMsg { - m_running.store(false, std::memory_order_relaxed); - if (m_thread.joinable()) - m_thread.join(); -} +public: + AutoPrintMsg(WASAPIStream* WASAPI_stream) : m_WASAPI_stream(WASAPI_stream) {} + ~AutoPrintMsg() + { + if (!m_WASAPI_stream->IsRunning() && !m_WASAPI_stream->ShouldRestart()) + { + std::string message = + "WASAPI failed to start, the current audio device likely does not support 2.0 16-bit " + + std::to_string(m_WASAPI_stream->GetMixer()->GetSampleRate()) + + "Hz PCM audio." + "\nWASAPI exclusive mode (event driven) won't work"; + + ERROR_LOG(AUDIO, message.c_str()); + OSD::AddMessage(message, 6000U, OSD::Color::RED); + } + } + +private: + WASAPIStream* m_WASAPI_stream; +}; -bool WASAPIStream::isValid() +// The implementation of MSCOM was copied from MSDN. I doubt it's needed +class CustomNotificationClient : public IMMNotificationClient { - return true; -} +public: + CustomNotificationClient(WASAPIStream* stream) : m_ref_count(1), m_stream(stream) {} + + ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&m_ref_count); } + ULONG STDMETHODCALLTYPE Release() + { + ULONG ref_count = InterlockedDecrement(&m_ref_count); + if (0 == ref_count) + { + delete this; + } + return ref_count; + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface) + { + if (__uuidof(IUnknown) == riid) + { + AddRef(); + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IMMNotificationClient) == riid) + { + AddRef(); + *ppvInterface = (IMMNotificationClient*)this; + } + else + { + *ppvInterface = nullptr; + return E_NOINTERFACE; + } + return S_OK; + } -static bool HandleWinAPI(std::string_view message, HRESULT result) + HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) + { + if ((flow == EDataFlow::eRender || flow == EDataFlow::eAll) && m_stream->IsRunning() && + m_stream->IsUsingDefaultDevice()) + { + // Trigger a restart so it will resume from the new default device + m_stream->OnSettingsChanged(); + } + return S_OK; + } + + // We already handle these events: if a stream is running and affected, it will be restarted. + // We probably couldn't know from here if the stream will fail to keep running, + // so we couldn't tell whether it needs restarting or not + HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id) { return S_OK; } + HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) + { + return S_OK; + } + + LONG m_ref_count; + WASAPIStream* m_stream; +}; + +static bool HandleWinAPI(std::string message, HRESULT result) { - if (FAILED(result)) + if (result != S_OK) { - std::string error; + _com_error err(result); + std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); switch (result) { case AUDCLNT_E_DEVICE_IN_USE: - error = "Audio endpoint already in use!"; + error = "Audio endpoint already in use"; break; - default: - error = TStrToUTF8(_com_error(result).ErrorMessage()).c_str(); + case AUDCLNT_E_RESOURCES_INVALIDATED: + error = "Audio resource invalidated (e.g. by a device removal)"; + break; + case AUDCLNT_E_DEVICE_INVALIDATED: + error = "Audio endpoint invalidated (e.g. by a change of settings)"; break; } - ERROR_LOG_FMT(AUDIO, "WASAPI: {}: {}", message, error); + ERROR_LOG(AUDIO, "WASAPI: %s (%s)", message.c_str(), error.c_str()); } return SUCCEEDED(result); } -static void ForEachNamedDevice(const std::function, std::string)>& callback) +WASAPIStream::WASAPIStream() { - HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - // RPC_E_CHANGED_MODE means that thread has COM already initialized with a different threading - // model. We don't necessarily need multithreaded model here, so don't treat this as an error - if (result != RPC_E_CHANGED_MODE && !HandleWinAPI("Failed to call CoInitialize", result)) - return; + // Just to avoid moving AutoCoInit to the header file + m_aci = std::make_unique(); +} - wil::unique_couninitialize_call cleanup; - if (FAILED(result)) - cleanup.release(); // CoUninitialize must be matched with each successful CoInitialize call, so - // don't call it if initialize fails +WASAPIStream::~WASAPIStream() +{ + // As long as this is wrapped around AudioCommon, this won't ever trigger + if (m_running) + SetRunning(false); + + if (m_enumerator) + { + m_enumerator->Release(); + if (m_notification_client) + { + // Ignore the result, we can't do much about it + m_enumerator->UnregisterEndpointNotificationCallback(m_notification_client); + m_notification_client->Release(); + } + } +} + +std::vector WASAPIStream::GetAvailableDevices() +{ + AutoCoInit aci; + if (!aci.Succeeded()) + return {}; - ComPtr enumerator; + IMMDeviceEnumerator* enumerator = nullptr; - result = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(enumerator.GetAddressOf())); + HRESULT result = + CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)); if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) - return; + return {}; - ComPtr devices; - result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, devices.GetAddressOf()); + std::vector device_names; + IMMDeviceCollection* devices; + result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices); if (!HandleWinAPI("Failed to get available devices", result)) - return; + { + enumerator->Release(); + return {}; + } UINT count; devices->GetCount(&count); - for (u32 i = 0; i < count; i++) + for (u32 i = 0; i < count; ++i) { - ComPtr device; - devices->Item(i, device.GetAddressOf()); + IMMDevice* device; + devices->Item(i, &device); if (!HandleWinAPI("Failed to get device " + std::to_string(i), result)) continue; - ComPtr device_properties; + IPropertyStore* device_properties; - result = device->OpenPropertyStore(STGM_READ, device_properties.GetAddressOf()); + result = device->OpenPropertyStore(STGM_READ, &device_properties); - if (!HandleWinAPI("Failed to initialize IPropertyStore", result)) + if (!HandleWinAPI("Failed to get IPropertyStore", result)) continue; - wil::unique_prop_variant device_name; - device_properties->GetValue(PKEY_Device_FriendlyName, device_name.addressof()); + PROPVARIANT device_name; + PropVariantInit(&device_name); - if (!callback(std::move(device), TStrToUTF8(device_name.pwszVal))) - break; + device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); + + device_names.push_back(TStrToUTF8(device_name.pwszVal)); + + if (device_names.back() == DEFAULT_DEVICE_NAME) + { + WARN_LOG(AUDIO, "WASAPI: Device names called \"%s\" are not supported", DEFAULT_DEVICE_NAME); + } + + PropVariantClear(&device_name); + + device_properties->Release(); + device_properties = nullptr; } + + devices->Release(); + enumerator->Release(); + + return device_names; } -std::vector WASAPIStream::GetAvailableDevices() +IMMDevice* WASAPIStream::GetDeviceByName(const std::string& name) { - std::vector device_names; + AutoCoInit aci; + if (!aci.Succeeded()) + return nullptr; - ForEachNamedDevice([&device_names](ComPtr, std::string n) { - device_names.push_back(std::move(n)); - return true; - }); + IMMDeviceEnumerator* enumerator = nullptr; - return device_names; + HRESULT result = + CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)); + + if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) + return nullptr; + + IMMDeviceCollection* devices; + result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices); + + if (!HandleWinAPI("Failed to get available devices", result)) + { + enumerator->Release(); + return {}; + } + + UINT count; + devices->GetCount(&count); + + for (u32 i = 0; i < count; ++i) + { + IMMDevice* device; + devices->Item(i, &device); + if (!HandleWinAPI("Failed to get device " + std::to_string(i), result)) + continue; + + IPropertyStore* device_properties; + + result = device->OpenPropertyStore(STGM_READ, &device_properties); + + if (!HandleWinAPI("Failed to get IPropertyStore", result)) + continue; + + PROPVARIANT device_name; + PropVariantInit(&device_name); + + device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); + + device_properties->Release(); + device_properties = nullptr; + + if (TStrToUTF8(device_name.pwszVal) == name) + { + PropVariantClear(&device_name); + devices->Release(); + enumerator->Release(); + return device; + } + + PropVariantClear(&device_name); + } + + devices->Release(); + enumerator->Release(); + + return nullptr; } -ComPtr WASAPIStream::GetDeviceByName(std::string_view name) +std::vector WASAPIStream::GetSelectedDeviceSampleRates() { - ComPtr device; + std::vector device_sample_rates; + + AutoCoInit aci; + if (!aci.Succeeded()) + return device_sample_rates; + + IMMDeviceEnumerator* enumerator = nullptr; + IMMDevice* device = nullptr; + IAudioClient* audio_client = nullptr; + + HRESULT result = + CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)); + + if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) + return device_sample_rates; + + if (SConfig::GetInstance().sWASAPIDevice == DEFAULT_DEVICE_NAME) + { + result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + } + else + { + device = GetDeviceByName(SConfig::GetInstance().sWASAPIDevice); + result = S_OK; - ForEachNamedDevice([&device, &name](ComPtr d, std::string n) { - if (n == name) + if (!device) { - device = std::move(d); - return false; + ERROR_LOG(AUDIO, "WASAPI: Can't find device '%s', falling back to default", + SConfig::GetInstance().sWASAPIDevice.c_str()); + result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + } + } + + if (!HandleWinAPI("Failed to get default endpoint", result)) + { + enumerator->Release(); + return device_sample_rates; + } + + // Note from Microsoft: In Win 8, the first use of IAudioClient to access the audio device + // should be on the STA thread. Calls from an MTA thread may result in undefined behavior. + // We haven't implemented that, but cubeb said they never found the problem, but it could be + // exclusive to exclisve mode, or it could have been patched in newer Win 8 versions + result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, + reinterpret_cast(&audio_client)); + + if (!HandleWinAPI("Failed to reactivate IAudioClient", result)) + { + device->Release(); + enumerator->Release(); + return device_sample_rates; + } + + // Anything below 48kHz should not be used, but I've added 44.1kHz in case + // a device doesn't support 48kHz. Any new value can be added here + unsigned long worst_to_best_sample_rates[5] = {44100, 48000, 96000, 192000, 384000}; + s32 i = ARRAYSIZE(worst_to_best_sample_rates) - 1; + + WAVEFORMATEXTENSIBLE format; + format.Format.nChannels = STEREO_CHANNELS; + format.Format.wBitsPerSample = 16; + format.Format.nBlockAlign = format.Format.nChannels * format.Format.wBitsPerSample / 8; + format.Samples.wValidBitsPerSample = format.Format.wBitsPerSample; + format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + + while (i >= 0) + { + format.Format.nSamplesPerSec = worst_to_best_sample_rates[i]; + format.Format.nAvgBytesPerSec = format.Format.nSamplesPerSec * format.Format.nBlockAlign; + + format.Format.wFormatTag = WAVE_FORMAT_PCM; + format.Format.cbSize = 0; + // Microsoft specified this needs to be called twice, once with WAVEFORMATEX + // and once with WAVEFORMATEXTENSIBLE + result = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, + (const WAVEFORMATEX*)&format, nullptr); + + if (!SUCCEEDED(result)) + { + --i; + continue; } + + format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + result = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, + (const WAVEFORMATEX*)&format, nullptr); + + if (!SUCCEEDED(result)) + { + --i; + continue; + } + + device_sample_rates.push_back(worst_to_best_sample_rates[i]); + --i; + } + + HandleWinAPI("Found no supported device formats, will default to " + + std::to_string(AudioCommon::GetDefaultSampleRate()) + + " whether supported or not", result); + + device->Release(); + audio_client->Release(); + enumerator->Release(); + + return device_sample_rates; +} + +bool WASAPIStream::SetRestartFromResult(HRESULT result) +{ + // This can be triggered by changing the "Spacial sound" settings or the sample rate + if (result == AUDCLNT_E_DEVICE_INVALIDATED || result == AUDCLNT_E_RESOURCES_INVALIDATED) + { + WARN_LOG(AUDIO, "WASAPI: The audio device has either been removed or a setting has recently " + "changed and Windows is recomputing it. Restarting WASAPI"); + // Doing this here is definitely a hack, the correct way would be to use IMMNotificationClient + // but it seems more complicated to use, relying on errors is just simpler + m_should_restart = true; return true; - }); + } - return device; + return false; } bool WASAPIStream::Init() { - ASSERT(m_enumerator == nullptr); - HRESULT result = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(m_enumerator.GetAddressOf())); + // Both CLSCTX_INPROC_SERVER or CLSCTX_ALL should be fine for Dolphin + HRESULT result = + CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), reinterpret_cast(&m_enumerator)); if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) return false; + m_notification_client = new CustomNotificationClient(this); + result = m_enumerator->RegisterEndpointNotificationCallback(m_notification_client); + if (FAILED(result)) + { + m_notification_client->Release(); + m_notification_client = nullptr; + } + return true; } bool WASAPIStream::SetRunning(bool running) { + assert(running != m_running); + + // Can be called by multiple threads (main and emulation), we need to initialize COM + AutoCoInit aci; + if (!aci.Succeeded()) + return false; + + // Keep trying if we fail before this + m_should_restart = false; + if (running) { - ComPtr device; + unsigned long sample_rate = 0; + + // Make sure the selected mode is still supported (ignored if we are using the default device) + char* pEnd = nullptr; + unsigned long user_preferred_sample_rate = + std::strtoull(SConfig::GetInstance().sWASAPIDeviceSampleRate.c_str(), &pEnd, 10); + if (SConfig::GetInstance().sWASAPIDevice != DEFAULT_DEVICE_NAME && pEnd != nullptr && + user_preferred_sample_rate != 0) + { + std::vector selected_device_sample_rates = + WASAPIStream::GetSelectedDeviceSampleRates(); + for (unsigned long selected_device_sample_rate : selected_device_sample_rates) + { + if (selected_device_sample_rate == user_preferred_sample_rate) + { + sample_rate = user_preferred_sample_rate; + break; + } + } + } + + // Recreate the mixer with our preferred sample rate + if (sample_rate > 0) + { + GetMixer()->SetSampleRate(sample_rate); + } + + m_surround = SConfig::GetInstance().ShouldUseDPL2Decoder(); + m_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + m_format.Format.nChannels = m_surround ? SURROUND_CHANNELS : STEREO_CHANNELS; + m_format.Format.nSamplesPerSec = GetMixer()->GetSampleRate(); + // We could use 24 bit (in 32 bit containers) when using surround, as the surround mixer gives + // us a float between -1 and 1, but it was originally mixed from 16 bit source anyway, so there + // likely won't be any loss in quality. The other advantage would be that we wouldn't need to + // use an intermediary array as we could just use the surround array, but again, + // that's not worth it. The only advantage would be when the volume isn't set to 100% + m_format.Format.wBitsPerSample = 16; + m_format.Format.nBlockAlign = m_format.Format.nChannels * m_format.Format.wBitsPerSample / 8; + m_format.Format.nAvgBytesPerSec = m_format.Format.nSamplesPerSec * m_format.Format.nBlockAlign; + m_format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + m_format.Samples.wValidBitsPerSample = m_format.Format.wBitsPerSample; + m_format.dwChannelMask = m_surround ? (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT ): + (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT ); + // Some sound cards might support KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, which would avoid a + // conversion when using DPLII, though I didn't think it was worth implementing + m_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + + IMMDevice* device = nullptr; + + AutoPrintMsg apm(this); HRESULT result; - if (SConfig::GetInstance().sWASAPIDevice == "default") + if (SConfig::GetInstance().sWASAPIDevice == DEFAULT_DEVICE_NAME) { - result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.GetAddressOf()); + result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + m_using_default_device = true; } else { - result = S_OK; device = GetDeviceByName(SConfig::GetInstance().sWASAPIDevice); - + result = S_OK; + m_using_default_device = false; + + // Whether this is the first time WASAPI starts running or not, + // we will fallback on the default device if the custom one is not found. + // This might happen if the device got removed (WASAPI would trigger m_should_restart). + // An alternative would be to NOT fallback on the default device if a custom one + // is specified, especially in case the specified one was previously available but then + // just got disconnected, but overall, this should be fine if (!device) { - ERROR_LOG_FMT(AUDIO, "Can't find device '{}', falling back to default", - SConfig::GetInstance().sWASAPIDevice); - result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.GetAddressOf()); + ERROR_LOG(AUDIO, "WASAPI: Can't find device '%s', falling back to default", + SConfig::GetInstance().sWASAPIDevice.c_str()); + result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + m_using_default_device = true; } } - if (!HandleWinAPI("Failed to obtain default endpoint", result)) + if (!HandleWinAPI("Failed to get default endpoint", result)) return false; - // Show a friendly name in the log - ComPtr device_properties; + // Show a friendly name in the log (no other use) + IPropertyStore* device_properties; + result = device->OpenPropertyStore(STGM_READ, &device_properties); - result = device->OpenPropertyStore(STGM_READ, device_properties.GetAddressOf()); + if (HandleWinAPI("Failed to get IPropertyStore", result)) + { + PROPVARIANT device_name; + PropVariantInit(&device_name); - if (!HandleWinAPI("Failed to initialize IPropertyStore", result)) - return false; + device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); - wil::unique_prop_variant device_name; - device_properties->GetValue(PKEY_Device_FriendlyName, device_name.addressof()); + INFO_LOG(AUDIO, "WASAPI: Using Audio Device '%s'", TStrToUTF8(device_name.pwszVal).c_str()); - INFO_LOG_FMT(AUDIO, "Using audio endpoint '{}'", TStrToUTF8(device_name.pwszVal)); + PropVariantClear(&device_name); - ComPtr audio_client; + device_properties->Release(); + device_properties = nullptr; + } - // Get IAudioDevice + // TODO: Microsoft says we need to create and destroy IAudioClient and all the pointers we get + // from it in the same thread. Unfortunately, due to the way Dolphin is done, this isn't easy, + // as the audio backend can be started and stopped (SetRunning()) from the main and emu threads. + // We can't move all of this between Init() and the destructor as if we paused the game, WASAPI + // would keep exclusive access of the device. I've also tried to move all of this in SoundLoop() + // (m_thread), but m_need_data_event failed to trigger. + // Let's just hope it works fine without it, at worse it will cause a small leak, but this + // has always been the case. + // We don't need IAudioClient3, it doesn't add anything for exclusive mode. result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, - reinterpret_cast(audio_client.GetAddressOf())); + reinterpret_cast(&m_audio_client)); - if (!HandleWinAPI("Failed to activate IAudioClient", result)) + if (SetRestartFromResult(result) || !HandleWinAPI("Failed to activate IAudioClient", result)) + { + device->Release(); return false; - + } + + // Clamp by the minimum supported device period (latency). + // Microsoft said it would automatically be clamped but setting + // device_period to 1 or 0 failed the IAudioClient::Initialize. + // WASAPI exclusive mode supports up to 5000ms, but let's clamp it, + // to 500ms and the mixer buffer length REFERENCE_TIME device_period = 0; + result = m_audio_client->GetDevicePeriod(nullptr, &device_period); + device_period = std::clamp((REFERENCE_TIME)AudioCommon::GetUserTargetLatency() * 10000, + device_period, (REFERENCE_TIME)500 * 10000); - result = audio_client->GetDevicePeriod(nullptr, &device_period); + if (SetRestartFromResult(result) || !HandleWinAPI("Failed to get device period", result)) + { + device->Release(); + m_audio_client->Release(); + m_audio_client = nullptr; + return false; + } - device_period += SConfig::GetInstance().iLatency * (10000 / m_format.Format.nChannels); - INFO_LOG_FMT(AUDIO, "Audio period set to {}", device_period); + DWORD stream_flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; - if (!HandleWinAPI("Failed to obtain device period", result)) - return false; + // We don't need a GUID as we only have one audio session at a time + result = m_audio_client->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, stream_flags, device_period, + device_period, &m_format.Format, nullptr); - result = audio_client->Initialize( - AUDCLNT_SHAREMODE_EXCLUSIVE, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, device_period, - device_period, reinterpret_cast(&m_format), nullptr); + // Fallback to stereo if surround 5.1 is not supported + if (m_surround && result == AUDCLNT_E_UNSUPPORTED_FORMAT) + { + WARN_LOG(AUDIO, "WASAPI: Your current audio device doesn't support 5.1 16-bit %uHz" + " PCM audio. Will fallback to 2.0 16-bit", + GetMixer()->GetSampleRate()); + + m_surround = false; + m_format.Format.nChannels = STEREO_CHANNELS; + m_format.Format.nBlockAlign = m_format.Format.nChannels * m_format.Format.wBitsPerSample / 8; + m_format.Format.nAvgBytesPerSec = + m_format.Format.nSamplesPerSec * m_format.Format.nBlockAlign; + m_format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + + result = m_audio_client->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, stream_flags, device_period, + device_period, &m_format.Format, nullptr); + } + // TODO: just like we fallback here above if we don't support surround, we should add support for + // more devices (e.g. bitrate, channels, format) by falling back as well if (result == AUDCLNT_E_UNSUPPORTED_FORMAT) { - OSD::AddMessage("Your current audio device doesn't support 16-bit 48000 hz PCM audio. WASAPI " - "exclusive mode won't work.", - 6000U); + WARN_LOG(AUDIO, "WASAPI: Your current audio device doesn't support 2.0 16-bit %uHz" + " PCM audio. Exclusive mode (event driven) won't work", + GetMixer()->GetSampleRate()); + + device->Release(); + m_audio_client->Release(); + m_audio_client = nullptr; return false; } + // Standard procedure from Microsoft documentation in case we receive this error if (result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { - result = audio_client->GetBufferSize(&m_frames_in_buffer); + result = m_audio_client->GetBufferSize(&m_frames_in_buffer); + m_audio_client->Release(); + m_audio_client = nullptr; - if (!HandleWinAPI("Failed to get aligned buffer size", result)) + if (SetRestartFromResult(result) || + !HandleWinAPI("Failed to get buffer size from IAudioClient", result)) + { + device->Release(); return false; + } - // Get IAudioDevice result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, - reinterpret_cast(audio_client.ReleaseAndGetAddressOf())); + reinterpret_cast(&m_audio_client)); - if (!HandleWinAPI("Failed to reactivate IAudioClient", result)) + if (SetRestartFromResult(result) || + !HandleWinAPI("Failed to reactivate IAudioClient", result)) + { + device->Release(); return false; + } + + WARN_LOG(AUDIO, "WASAPI: Your current audio device doesn't support the latency (period)" + " you specified. Falling back to default device latency"); - device_period = - static_cast( - 10000.0 * 1000 * m_frames_in_buffer / m_format.Format.nSamplesPerSec + 0.5) + - SConfig::GetInstance().iLatency * 10000; + // m_frames_in_buffer should already "include" our previously attempted latency, + // so we don't need to multiply by our target latency again + device_period = (REFERENCE_TIME)( + (10000.0 * 1000 / m_format.Format.nSamplesPerSec * m_frames_in_buffer) + 0.5); - result = audio_client->Initialize( - AUDCLNT_SHAREMODE_EXCLUSIVE, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, device_period, - device_period, reinterpret_cast(&m_format), nullptr); + // This should always work so we don't need to warn the user that WASAPI failed again + result = m_audio_client->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, stream_flags, device_period, + device_period, &m_format.Format, nullptr); } - if (!HandleWinAPI("Failed to initialize IAudioClient", result)) + // Show this even if we failed, just for information to the user + INFO_LOG(AUDIO, "WASAPI: Sample Rate: %u, Channels: %u, Audio Period: %ims", + m_format.Format.nSamplesPerSec, m_format.Format.nChannels, device_period / 10000); + + if (SetRestartFromResult(result) || !HandleWinAPI("Failed to initialize IAudioClient", result)) + { + device->Release(); + m_audio_client->Release(); + m_audio_client = nullptr; return false; + } - result = audio_client->GetBufferSize(&m_frames_in_buffer); + result = m_audio_client->GetBufferSize(&m_frames_in_buffer); - if (!HandleWinAPI("Failed to get buffer size from IAudioClient", result)) + if (SetRestartFromResult(result) || + !HandleWinAPI("Failed to get buffer size from IAudioClient", result)) + { + device->Release(); + m_audio_client->Release(); + m_audio_client = nullptr; return false; + } - ComPtr audio_renderer; + result = m_audio_client->GetService(__uuidof(IAudioRenderClient), + reinterpret_cast(&m_audio_renderer)); - result = audio_client->GetService(IID_PPV_ARGS(audio_renderer.GetAddressOf())); + if (SetRestartFromResult(result) || + !HandleWinAPI("Failed to get IAudioRenderClient from IAudioClient", result)) + { + device->Release(); + m_audio_client->Release(); + m_audio_client = nullptr; + return false; + } - if (!HandleWinAPI("Failed to get IAudioRenderClient from IAudioClient", result)) + m_need_data_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + m_audio_client->SetEventHandle(m_need_data_event); + + result = m_audio_client->Start(); + + if (SetRestartFromResult(result) || !HandleWinAPI("Failed to start IAudioClient", result)) + { + device->Release(); + m_audio_renderer->Release(); + m_audio_renderer = nullptr; + m_audio_client->Release(); + m_audio_client = nullptr; + CloseHandle(m_need_data_event); + m_need_data_event = nullptr; return false; + } - wil::unique_event_nothrow need_data_event; - need_data_event.create(); + // We don't really care if this fails, we only use this to make sure the we didn't miss any + // output periods, but if we can't guarantee that WASAPI will work anyway + result = m_audio_client->GetService(__uuidof(IAudioClock), + reinterpret_cast(&m_audio_clock)); + if (SUCCEEDED(result)) + { + // This should always be identical to m_format.Format.nSamplesPerSec, but I can't guarantee + u64 device_frequency = 0; + m_audio_clock->GetFrequency(&device_frequency); - audio_client->SetEventHandle(need_data_event.get()); + if (device_frequency != m_format.Format.nSamplesPerSec) + { + // m_audio_clock->GetPosition would return unreliable values + WARN_LOG(AUDIO, "WASAPI: The device frequence is different from our sample rate, we can't" + " keep them in sync. WASAPI will still work"); + m_audio_clock->Release(); + m_audio_clock = nullptr; + } + } - result = audio_client->Start(); + // Again, if this fails, we can go on, we just won't know the volume of the application + // the user has set through the Windows mixer + IAudioSessionManager* audio_session_manager = nullptr; + result = device->Activate(__uuidof(IAudioSessionManager), CLSCTX_INPROC_SERVER, nullptr, + reinterpret_cast(&audio_session_manager)); + if (SUCCEEDED(result)) + { + result = audio_session_manager->GetSimpleAudioVolume(nullptr, false, &m_simple_audio_volume); + audio_session_manager->Release(); + } - if (!HandleWinAPI("Failed to get IAudioRenderClient from IAudioClient", result)) - return false; + m_running = true; - INFO_LOG_FMT(AUDIO, "WASAPI: Successfully initialized!"); + device->Release(); - // "Commit" audio client and audio renderer now - m_audio_client = std::move(audio_client); - m_audio_renderer = std::move(audio_renderer); - m_need_data_event = std::move(need_data_event); + NOTICE_LOG(AUDIO, "WASAPI: Successfully initialized"); - m_running.store(true, std::memory_order_relaxed); - m_thread = std::thread(&WASAPIStream::SoundLoop, this); + m_stop_thread_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + m_thread = std::thread([this] { SoundLoop(); }); } else { - m_running.store(false, std::memory_order_relaxed); + m_running = false; + + SetEvent(m_stop_thread_event); - if (m_thread.joinable()) - m_thread.join(); + m_thread.join(); + + m_surround = false; + m_using_default_device = false; - m_need_data_event.reset(); - m_audio_renderer.Reset(); - m_audio_client.Reset(); + // As long as we don't call SetRunning(false) while m_running was already false, + // there is no need to check pointers here + m_audio_renderer->Release(); + m_audio_renderer = nullptr; + m_audio_client->Release(); + m_audio_client = nullptr; + if (m_audio_clock) + { + m_audio_clock->Release(); + m_audio_clock = nullptr; + } + if (m_simple_audio_volume) + { + m_simple_audio_volume->Release(); + m_simple_audio_volume = nullptr; + } + CloseHandle(m_need_data_event); + m_need_data_event = nullptr; + CloseHandle(m_stop_thread_event); + m_stop_thread_event = nullptr; } return true; } +void WASAPIStream::Update() +{ + // TODO: move this out of the game (main) thread, restarting WASAPI should not lock the game + // thread, but as of now there isn't any other constant and safe access point to restart + + // If the sound loop failed for some reason, re-initialize ASAPI to resume playback + if (m_should_restart) + { + m_should_restart = false; + if (m_running) + { + // We need to pass through AudioCommon as it has a mutex and + // to make sure s_sound_stream_running is updated + if (AudioCommon::SetSoundStreamRunning(false, false)) + { + // m_should_restart is triggered when the device is currently + // invalidated, and it will stay for a while, so this new call + // to SetRunning(true) might fail, but if it fails some + // specific reasons, it will set m_should_restart true again. + // A Sleep(10) call also seemed to fix the problem but it's hacky. + AudioCommon::SetSoundStreamRunning(true, false); + } + } + else + { + AudioCommon::SetSoundStreamRunning(true, false); + } + } +} + void WASAPIStream::SoundLoop() { Common::SetCurrentThreadName("WASAPI Handler"); BYTE* data; - m_audio_renderer->GetBuffer(m_frames_in_buffer, &data); - m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, AUDCLNT_BUFFERFLAGS_SILENT); + // New thread, we need to initialize COM + AutoCoInit aci; + if (!aci.Succeeded()) + { + m_should_restart = true; + return; + } + + // I'm not sure what this is for, but it doesn't work without it. Maybe to init thread access + if (SUCCEEDED(m_audio_renderer->GetBuffer(m_frames_in_buffer, &data))) + { + m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, AUDCLNT_BUFFERFLAGS_SILENT); + } + + u64 submitted_samples = 0; - while (m_running.load(std::memory_order_relaxed)) + while (true) { - WaitForSingleObject(m_need_data_event.get(), 1000); + // "stop" has greater priority than "data" event (if both triggered at once) + HANDLE handles[2] = {m_stop_thread_event, m_need_data_event}; + DWORD r = WaitForMultipleObjects(2, handles, false, INFINITE); + + if (r == WAIT_OBJECT_0) + { + break; + } + else if (r != WAIT_OBJECT_0 + 1) + { + // We don't set m_should_restart to true here because + // we can't guarantee it wouldn't constantly fail again + ERROR_LOG(AUDIO, "WASAPI: WaitForMultipleObjects failed. Stopping sound playback." + " Pausing and unpausing the emulation might fix the problem"); + break; + } - m_audio_renderer->GetBuffer(m_frames_in_buffer, &data); + // We could as well override the SoundStream::SetVolume(int) function + float volume = SConfig::GetInstance().m_IsMuted ? 0.f : SConfig::GetInstance().m_Volume / 100.f; + if (m_simple_audio_volume) + { + float master_volume = 1.f; + BOOL mute = false; + // Ignore them in case they fail + m_simple_audio_volume->GetMasterVolume(&master_volume); + m_simple_audio_volume->GetMute(&mute); + volume *= mute ? 0.f : master_volume; + } - s16* audio_data = reinterpret_cast(data); - GetMixer()->Mix(audio_data, m_frames_in_buffer); + HRESULT result = m_audio_renderer->GetBuffer(m_frames_in_buffer, &data); - const SConfig& config = SConfig::GetInstance(); - const bool is_muted = config.m_IsMuted || config.m_Volume == 0; - const bool need_volume_adjustment = config.m_Volume != 100 && !is_muted; + if (SetRestartFromResult(result) || + !HandleWinAPI("Failed to get buffer from IAudioClient. Stopping sound playback." + " Pausing and unpausing the emulation might fix the problem", result)) + { + // We don't set m_should_restart to true in the second case here + // because we can't guarantee it wouldn't constantly fail again + break; + } - if (need_volume_adjustment) + if (m_surround) { - const float volume = config.m_Volume / 100.0f; + m_surround_samples.resize(m_frames_in_buffer * SURROUND_CHANNELS); + unsigned int mixed_samples = + GetMixer()->MixSurround(m_surround_samples.data(), m_frames_in_buffer); - for (u32 i = 0; i < m_frames_in_buffer * 2; i++) - *audio_data++ *= volume; + // From float(32) (-1 to 1) to signed short(16) + for (u32 i = 0; i < mixed_samples * SURROUND_CHANNELS; ++i) + { + // std::numeric_limits::min is actually smaller than max, like every signed min, + // so we don't need to clamp to -max (or min) if the float is clamped to -1. + // We assume the volume won't be more than 1, or we'd lose some amplifications. + // It would be better to round the result value, but the chance of that making + // a difference is not worth the performance hit + reinterpret_cast(data)[i] = + static_cast(std::clamp(m_surround_samples[i] * volume, -1.f, 1.f) * + std::numeric_limits::max()); + } + } + // From signed short(16) to signed short(16) + else + { + GetMixer()->Mix(reinterpret_cast(data), m_frames_in_buffer); + + for (u32 i = 0; i < m_frames_in_buffer * STEREO_CHANNELS; ++i) + { + reinterpret_cast(data)[i] = + static_cast(reinterpret_cast(data)[i] * volume); + } } - m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, is_muted ? AUDCLNT_BUFFERFLAGS_SILENT : 0); + // If the device period was set too low, the above computations might take longer + // that the latency, so we'd lose this audio frame + m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, 0); + submitted_samples += m_frames_in_buffer; + m_wait_next_desync_check -= m_frames_in_buffer; + + // When pausing and unpausing the process (e.g. a breakpoint) or toggling fullscreen + // WASAPI would fall out of sync without letting us know, and start outputting frames + // that had not been calculated yet, so garbage, thus crackling. This seems to be + // a simple and effective fix (not sure they are any official/better fixes) + if (m_audio_clock && m_wait_next_desync_check <= 0) + { + u64 device_samples_position; + // This should roughly return the "sample" the audio device is currently playing, + // if it's playing the last one we submitted already, then something is wrong, + // we likely have missed a period, so we'd better restart WASAPI + result = m_audio_clock->GetPosition(&device_samples_position, nullptr); + + if (SUCCEEDED(result) && device_samples_position >= submitted_samples) + { + WARN_LOG(AUDIO, "WASAPI: Fell out of sync with the device period as we could not compute" + " samples in time. " + "Restarting WASAPI to avoid crackling. If this keeps happening," + " increase your backend latency"); + + // Don't check more than roughly once per second, as this could trigger a lot otherwise + m_wait_next_desync_check = m_format.Format.nSamplesPerSec; + + m_should_restart = true; + break; + } + } } } diff --git a/Source/Core/AudioCommon/WASAPIStream.h b/Source/Core/AudioCommon/WASAPIStream.h index 640d0155fcdf..7c5c68800c61 100644 --- a/Source/Core/AudioCommon/WASAPIStream.h +++ b/Source/Core/AudioCommon/WASAPIStream.h @@ -5,28 +5,25 @@ #pragma once #ifdef _WIN32 - -// clang-format off #include #include -#include -#include -// clang-format on +#endif #include #include #include #include -#include #include "AudioCommon/SoundStream.h" struct IAudioClient; struct IAudioRenderClient; +struct IAudioClock; +struct ISimpleAudioVolume; struct IMMDevice; struct IMMDeviceEnumerator; - -#endif +class CustomNotificationClient; +class AutoCoInit; class WASAPIStream final : public SoundStream { @@ -36,25 +33,47 @@ class WASAPIStream final : public SoundStream ~WASAPIStream(); bool Init() override; bool SetRunning(bool running) override; + void Update() override; void SoundLoop() override; - static bool isValid(); + bool IsRunning() const { return m_running; } + bool ShouldRestart() const { return m_should_restart; } + bool IsUsingDefaultDevice() const { return m_using_default_device; } + + bool SupportsRuntimeSettingsChanges() const override { return true; } + void OnSettingsChanged() override { m_should_restart = true; } + + static bool IsValid() { return true; } static std::vector GetAvailableDevices(); - static Microsoft::WRL::ComPtr GetDeviceByName(std::string_view name); + static IMMDevice* GetDeviceByName(const std::string& name); + // Returns the user selected device supported sample rates at 16 bit and 2 channels, + // so it ignores 24 bit or support for 5 channels. If we are starting up WASAPI with DPL2 on, + // it will try these sample rates anyway, or fallback to the dolphin default one, + // but generally if a device supports a sample rate, it supports it with any bitrate/channels. + // It doesn't just return any device supported sample rate, just the ones we care for in Dolphin + static std::vector GetSelectedDeviceSampleRates(); private: + bool SetRestartFromResult(HRESULT result); + u32 m_frames_in_buffer = 0; std::atomic m_running = false; + std::atomic m_should_restart = false; std::thread m_thread; - // CoUninitialize must be called after all WASAPI COM objects have been destroyed, - // therefore this member must be located before them, as first class fields are destructed last - wil::unique_couninitialize_call m_coinitialize{false}; - - Microsoft::WRL::ComPtr m_enumerator; - Microsoft::WRL::ComPtr m_audio_client; - Microsoft::WRL::ComPtr m_audio_renderer; - wil::unique_event_nothrow m_need_data_event; + IAudioClient* m_audio_client = nullptr; + IAudioRenderClient* m_audio_renderer = nullptr; + IAudioClock* m_audio_clock = nullptr; + ISimpleAudioVolume* m_simple_audio_volume = nullptr; + IMMDeviceEnumerator* m_enumerator = nullptr; + HANDLE m_need_data_event = nullptr; + HANDLE m_stop_thread_event = nullptr; WAVEFORMATEXTENSIBLE m_format; + std::unique_ptr m_aci; + CustomNotificationClient* m_notification_client = nullptr; + bool m_surround = false; + bool m_using_default_device = false; + std::vector m_surround_samples; + s64 m_wait_next_desync_check = 0; #endif // _WIN32 }; diff --git a/Source/Core/Common/FixedSizeQueue.h b/Source/Core/Common/FixedSizeQueue.h index 889cbe2ee7c1..80ccd2422683 100644 --- a/Source/Core/Common/FixedSizeQueue.h +++ b/Source/Core/Common/FixedSizeQueue.h @@ -14,7 +14,7 @@ // // Not fully featured, no safety checking yet. Add features as needed. -template +template class FixedSizeQueue { public: @@ -59,6 +59,7 @@ class FixedSizeQueue void pop() { + assert(count > 0); if constexpr (!std::is_trivial_v) storage[head] = {}; @@ -73,15 +74,27 @@ class FixedSizeQueue return temp; } + T& operator[](size_t index) + { + assert(index < count); + return storage[(head + index) % N]; + } + const T& operator[](size_t index) const + { + assert(index < count); + return storage[(head + index) % N]; + } + T& front() noexcept { return storage[head]; } const T& front() const noexcept { return storage[head]; } size_t size() const noexcept { return count; } + size_t max_size() const noexcept { return N; } bool empty() const noexcept { return size() == 0; } private: std::array storage; - int head = 0; - int tail = 0; - // Sacrifice 4 bytes for a simpler implementation. may optimize away in the future. - int count = 0; + unsigned int head = 0; + unsigned int tail = 0; + // Not necessary but avoids a lot of calculations + unsigned int count = 0; }; diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index bfc6d8f5a5f8..6175e7fcc1e4 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -38,9 +38,10 @@ const Info MAIN_OVERRIDE_REGION_SETTINGS{{System::Main, "Core", "OverrideR const Info MAIN_DPL2_DECODER{{System::Main, "Core", "DPL2Decoder"}, false}; const Info MAIN_DPL2_QUALITY{{System::Main, "Core", "DPL2Quality"}, AudioCommon::GetDefaultDPL2Quality()}; -const Info MAIN_AUDIO_LATENCY{{System::Main, "Core", "AudioLatency"}, 20}; +// TODO: rename "AudioBackendLatency", it's confusing +const Info MAIN_AUDIO_BACKEND_LATENCY{{System::Main, "Core", "AudioLatency"}, 20}; +const Info MAIN_AUDIO_MIXER_LATENCY{{System::Main, "Core", "AudioMixerTargetLatency"}, 40}; const Info MAIN_AUDIO_STRETCH{{System::Main, "Core", "AudioStretch"}, false}; -const Info MAIN_AUDIO_STRETCH_LATENCY{{System::Main, "Core", "AudioStretchMaxLatency"}, 80}; const Info MAIN_MEMCARD_A_PATH{{System::Main, "Core", "MemcardAPath"}, ""}; const Info MAIN_MEMCARD_B_PATH{{System::Main, "Core", "MemcardBPath"}, ""}; const Info MAIN_AGP_CART_A_PATH{{System::Main, "Core", "AgpCartAPath"}, ""}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 9de328aa427d..7a1b84783f56 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -39,9 +39,9 @@ extern const Info MAIN_GC_LANGUAGE; extern const Info MAIN_OVERRIDE_REGION_SETTINGS; extern const Info MAIN_DPL2_DECODER; extern const Info MAIN_DPL2_QUALITY; -extern const Info MAIN_AUDIO_LATENCY; +extern const Info MAIN_AUDIO_BACKEND_LATENCY; +extern const Info MAIN_AUDIO_MIXER_LATENCY; extern const Info MAIN_AUDIO_STRETCH; -extern const Info MAIN_AUDIO_STRETCH_LATENCY; extern const Info MAIN_MEMCARD_A_PATH; extern const Info MAIN_MEMCARD_B_PATH; extern const Info MAIN_AGP_CART_A_PATH; diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index fcb5eb0f4e19..4fc1a82ff4d3 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -34,7 +34,7 @@ bool IsSettingSaveable(const Config::Location& config_location) } } - static constexpr std::array s_setting_saveable = { + static constexpr std::array s_setting_saveable = { // Main.Core &Config::MAIN_DEFAULT_ISO.GetLocation(), @@ -44,6 +44,7 @@ bool IsSettingSaveable(const Config::Location& config_location) &Config::MAIN_ALLOW_SD_WRITES.GetLocation(), &Config::MAIN_DPL2_DECODER.GetLocation(), &Config::MAIN_DPL2_QUALITY.GetLocation(), + &Config::MAIN_AUDIO_MIXER_LATENCY.location, &Config::MAIN_RAM_OVERRIDE_ENABLE.GetLocation(), &Config::MAIN_MEM1_SIZE.GetLocation(), &Config::MAIN_MEM2_SIZE.GetLocation(), diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 029a345016f9..4d5aeb52aa2c 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -147,6 +147,7 @@ void SConfig::SaveInterfaceSettings(IniFile& ini) interface->Set("ConfirmStop", bConfirmStop); interface->Set("HideCursor", bHideCursor); + interface->Set("LockCursor", bLockCursor); interface->Set("LanguageCode", m_InterfaceLanguage); interface->Set("ExtendedFPSInfo", m_InterfaceExtendedFPSInfo); interface->Set("ShowActiveTitle", m_show_active_title); @@ -220,8 +221,9 @@ void SConfig::SaveCoreSettings(IniFile& ini) core->Set("OverrideRegionSettings", bOverrideRegionSettings); core->Set("DPL2Decoder", bDPL2Decoder); core->Set("AudioLatency", iLatency); + core->Set("UseOSMixerSampleRate", &bUseOSMixerSampleRate); core->Set("AudioStretch", m_audio_stretch); - core->Set("AudioStretchMaxLatency", m_audio_stretch_max_latency); + core->Set("AudioEmuSpeedTolerance", &m_audio_emu_speed_tolerance); core->Set("AgpCartAPath", m_strGbaCartA); core->Set("AgpCartBPath", m_strGbaCartB); core->Set("SlotA", m_EXIDevice[0]); @@ -279,6 +281,7 @@ void SConfig::SaveDSPSettings(IniFile& ini) #ifdef _WIN32 dsp->Set("WASAPIDevice", sWASAPIDevice); + dsp->Set("WASAPIDeviceSampleRate", sWASAPIDeviceSampleRate); #endif } @@ -401,6 +404,7 @@ void SConfig::LoadInterfaceSettings(IniFile& ini) interface->Get("ConfirmStop", &bConfirmStop, true); interface->Get("HideCursor", &bHideCursor, false); + interface->Get("LockCursor", &bLockCursor, false); interface->Get("LanguageCode", &m_InterfaceLanguage, ""); interface->Get("ExtendedFPSInfo", &m_InterfaceExtendedFPSInfo, false); interface->Get("ShowActiveTitle", &m_show_active_title, true); @@ -478,8 +482,9 @@ void SConfig::LoadCoreSettings(IniFile& ini) core->Get("OverrideRegionSettings", &bOverrideRegionSettings, false); core->Get("DPL2Decoder", &bDPL2Decoder, false); core->Get("AudioLatency", &iLatency, 20); + core->Get("UseOSMixerSampleRate", &bUseOSMixerSampleRate, false); core->Get("AudioStretch", &m_audio_stretch, false); - core->Get("AudioStretchMaxLatency", &m_audio_stretch_max_latency, 80); + core->Get("AudioEmuSpeedTolerance", &m_audio_emu_speed_tolerance, 10); core->Get("AgpCartAPath", &m_strGbaCartA); core->Get("AgpCartBPath", &m_strGbaCartB); core->Get("SlotA", (int*)&m_EXIDevice[0], ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER); @@ -549,6 +554,7 @@ void SConfig::LoadDSPSettings(IniFile& ini) #ifdef _WIN32 dsp->Get("WASAPIDevice", &sWASAPIDevice, "default"); + dsp->Get("WASAPIDeviceSampleRate", &sWASAPIDeviceSampleRate, "0"); #endif m_IsMuted = false; @@ -763,7 +769,13 @@ void SConfig::LoadDefaults() bDPL2Decoder = false; iLatency = 20; m_audio_stretch = false; - m_audio_stretch_max_latency = 80; + m_audio_emu_speed_tolerance = 10; + bUsePanicHandlers = true; + bOnScreenDisplayMessages = true; + + m_analytics_id = ""; + m_analytics_enabled = false; + m_analytics_permission_asked = false; bLoopFifoReplay = true; diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 38c6abad74ba..7f7cb0e42899 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -52,7 +52,7 @@ struct BootParameters; #define BACKEND_NULLSOUND _trans("No Audio Output") #define BACKEND_ALSA "ALSA" #define BACKEND_CUBEB "Cubeb" -#define BACKEND_OPENAL "OpenAL" +#define BACKEND_OPENAL _trans("OpenAL (Deprecated)") #define BACKEND_PULSEAUDIO "Pulse" #define BACKEND_OPENSLES "OpenSLES" #define BACKEND_WASAPI _trans("WASAPI (Exclusive Mode)") @@ -113,7 +113,8 @@ struct SConfig bool bAccurateNaNs = false; bool bDisableICache = false; - int iTimingVariance = 40; // in milli secounds + // 40 ms for one frame on 25 fps games + int iTimingVariance = 40; bool bCPUThread = true; bool bDSPThread = false; bool bDSPHLE = true; @@ -124,9 +125,11 @@ struct SConfig bool bCopyWiiSaveNetplay = true; bool bDPL2Decoder = false; + // Don't access directly, use AudioInterface::GetUserTargetLatency() int iLatency = 20; + bool bUseOSMixerSampleRate = false; bool m_audio_stretch = false; - int m_audio_stretch_max_latency = 80; + int m_audio_emu_speed_tolerance = 10; bool bRunCompareServer = false; bool bRunCompareClient = false; @@ -150,6 +153,8 @@ struct SConfig // Interface settings bool bConfirmStop = false; bool bHideCursor = false; + bool bUsePanicHandlers = true; + bool bOnScreenDisplayMessages = true; std::string theme_name; // Bluetooth passthrough mode settings @@ -304,6 +309,7 @@ struct SConfig #ifdef _WIN32 // WSAPI settings std::string sWASAPIDevice; + std::string sWASAPIDeviceSampleRate; #endif // Input settings diff --git a/Source/Core/Core/HW/AudioInterface.cpp b/Source/Core/Core/HW/AudioInterface.cpp index 82f1418368a0..bfbb44393ec2 100644 --- a/Source/Core/Core/HW/AudioInterface.cpp +++ b/Source/Core/Core/HW/AudioInterface.cpp @@ -13,7 +13,7 @@ Here is a nice ascii overview of audio flow affected by this file: (DVD)---->[Drive I/F]---->[SRC]---->[Counter] Notes: -Output at "48KHz" is actually 48043Hz. +Output at "48KHz" is actually ~48043Hz on GC. Sample counter counts streaming stereo samples after upsampling. [DAC] causes [AI I/F] to read from RAM at rate selected by AIDFR. Each [SRC] will upsample a 32KHz source, or pass through the 48KHz @@ -113,8 +113,8 @@ static u32 s_interrupt_timing = 0; static u64 s_last_cpu_time = 0; static u64 s_cpu_cycles_per_sample = 0xFFFFFFFFFFFULL; -static u32 s_ais_sample_rate = 48000; -static u32 s_aid_sample_rate = 32000; +static double s_ais_sample_rate = 48000.0; +static double s_aid_sample_rate = 32000.0; void DoState(PointerWrap& p) { @@ -181,7 +181,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) s_control.AIINTVLD = tmp_ai_ctrl.AIINTVLD; } - // Set frequency of streaming audio + // Set frequency of streaming (DVD) audio if (tmp_ai_ctrl.AISFR != s_control.AISFR) { // AISFR rates below are intentionally inverted wrt yagcd @@ -189,7 +189,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) tmp_ai_ctrl.AISFR ? "48khz" : "32khz"); s_control.AISFR = tmp_ai_ctrl.AISFR; s_ais_sample_rate = tmp_ai_ctrl.AISFR ? Get48KHzSampleRate() : Get32KHzSampleRate(); - g_sound_stream->GetMixer()->SetStreamInputSampleRate(s_ais_sample_rate); + g_sound_stream->GetMixer()->SetStreamingInputSampleRate(s_ais_sample_rate); s_cpu_cycles_per_sample = SystemTimers::GetTicksPerSecond() / s_ais_sample_rate; } // Set frequency of DMA @@ -300,24 +300,22 @@ bool IsPlaying() return (s_control.PSTAT == 1); } -u32 GetAIDSampleRate() +double GetAIDSampleRate() { return s_aid_sample_rate; } - -u32 GetAISSampleRate() +double GetAISSampleRate() { return s_ais_sample_rate; } -u32 Get32KHzSampleRate() +double Get32KHzSampleRate() { - return SConfig::GetInstance().bWii ? 32000 : 32029; + return SConfig::GetInstance().bWii ? 32000.0 : (36000000.0 / 1124.0); } - -u32 Get48KHzSampleRate() +double Get48KHzSampleRate() { - return SConfig::GetInstance().bWii ? 48000 : 48043; + return SConfig::GetInstance().bWii ? 48000.0 : (54000000.0 / 1124.0); } static void Update(u64 userdata, s64 cycles_late) @@ -325,9 +323,10 @@ static void Update(u64 userdata, s64 cycles_late) if (s_control.PSTAT) { const u64 diff = CoreTiming::GetTicks() - s_last_cpu_time; - if (diff > s_cpu_cycles_per_sample) + if (diff >= s_cpu_cycles_per_sample) { const u32 samples = static_cast(diff / s_cpu_cycles_per_sample); + // We re-multiply samples by s_cpu_cycles_per_sample to account for imprecisions s_last_cpu_time += samples * s_cpu_cycles_per_sample; IncreaseSampleCount(samples); } diff --git a/Source/Core/Core/HW/AudioInterface.h b/Source/Core/Core/HW/AudioInterface.h index 92d15b1f2b86..b5d8ed545185 100644 --- a/Source/Core/Core/HW/AudioInterface.h +++ b/Source/Core/Core/HW/AudioInterface.h @@ -23,12 +23,12 @@ bool IsPlaying(); void RegisterMMIO(MMIO::Mapping* mmio, u32 base); -// Get the audio rates (48000 or 32000 only) -u32 GetAIDSampleRate(); -u32 GetAISSampleRate(); +// Get the audio rates (48000 and 32000 for Wii, or ~48043 and ~32029 for GC) +double GetAIDSampleRate(); +double GetAISSampleRate(); -u32 Get32KHzSampleRate(); -u32 Get48KHzSampleRate(); +double Get32KHzSampleRate(); +double Get48KHzSampleRate(); void GenerateAISInterrupt(); diff --git a/Source/Core/Core/HW/DSP.cpp b/Source/Core/Core/HW/DSP.cpp index 26d07c464305..b342cd08da93 100644 --- a/Source/Core/Core/HW/DSP.cpp +++ b/Source/Core/Core/HW/DSP.cpp @@ -396,7 +396,8 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) AudioCommon::SendAIBuffer((short*)address, s_audioDMA.AudioDMAControl.NumBlocks * 8); // TODO: need hardware tests for the timing of this interrupt. - // Sky Crawlers crashes at boot if this is scheduled less than 87 cycles in the future. + // "The Sky Crawlers: Innocent Aces" crashes at boot if this is scheduled less than 87 + // cycles in the future. // Other Namco games crash too, see issue 9509. For now we will just push it to 200 cycles CoreTiming::ScheduleEvent(200, s_et_GenerateDSPInterrupt, INT_AID); } @@ -465,7 +466,8 @@ void UpdateDSPSlice(int cycles) } } -// This happens at 4 khz, since 32 bytes at 4khz = 4 bytes at 32 khz (16bit stereo pcm) +// This happens at 4kHz, since 32 bytes at 4kHz = 4 bytes at 32kHz (16bit stereo pcm). +// At the beginning and end of emulation, this happens at unreliable rates void UpdateAudioDMA() { static short zero_samples[8 * 2] = {0}; @@ -488,6 +490,12 @@ void UpdateAudioDMA() if (s_audioDMA.remaining_blocks_count != 0) { + // Similarly to DVD/Streaming audio, instead of sending a different number of samples each + // submission, we just change the frequency of the submission to match the sample rate. This + // causes more imprecision on GC, where sample rates aren't integer, but it should mostly be + // fine. The Mixer (Mixer.cpp) might assume the above is true, so if you change the sample + // submitted per submission, make sure you review the Mixer as well. + // We make the samples ready as soon as possible void* address = Memory::GetPointer(s_audioDMA.SourceAddress); AudioCommon::SendAIBuffer((short*)address, s_audioDMA.AudioDMAControl.NumBlocks * 8); diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp index b0c272ab6096..2ea621f812be 100644 --- a/Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp +++ b/Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp @@ -343,7 +343,7 @@ AXMixControl AXUCode::ConvertMixerControl(u32 mixer_control) if (mixer_control & 0x2000) ret |= MIX_AUXB_S_RAMP; - // TODO: 0x4000 is used for Dolby Pro 2 sound mixing + // TODO: 0x4000 is used for DPLII (Dolby) sound mixing } return (AXMixControl)ret; diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/AX.h b/Source/Core/Core/HW/DSPHLE/UCodes/AX.h index 494d3f4dde45..a5d3b1c2f4ba 100644 --- a/Source/Core/Core/HW/DSPHLE/UCodes/AX.h +++ b/Source/Core/Core/HW/DSPHLE/UCodes/AX.h @@ -8,7 +8,7 @@ // * Depop support // * ITD support // * Polyphase sample interpolation support (not very useful) -// * Dolby Pro 2 mixing with recent AX versions +// * DPLII (Dolby) mixing with recent AX versions #pragma once diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index dd9a2c0ccfc8..b9f844c6afa6 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -300,10 +300,20 @@ static void DTKStreamingCallback(DIInterruptType interrupt_type, const std::vect { // TODO: Should we use GetAISSampleRate instead of a fixed 48 KHz? The audio mixer is using // GetAISSampleRate. (This doesn't affect any actual games, since they all set it to 48 KHz.) - const u32 sample_rate = AudioInterface::Get48KHzSampleRate(); + const double sample_rate = AudioInterface::Get48KHzSampleRate(); + + //To review: here we used to just hardcode 48Khz, ignoring wether we are wii or gc (and also avoiding it being double). old comment: + // This determines which audio data to read next. + // Similarly to DMA audio, instead of sending a different number of samples each submission, + // we just change the frequency of the submission to match the sample rate. This causes more + // imprecision on GC, where sample rates aren't integer, but it should mostly be fine. + // The Mixer (Mixer.cpp) might assume the above is true, so if you change the samples submitted + // per submission, make sure you review the Mixer as well. // Determine which audio data to read next. - const u32 maximum_samples = sample_rate / 2000 * 7; // 3.5 ms of samples + // 168 samples (3.5ms at 48kHz/5.25ms at 32kHz on Wii, similar numbers on GC) + const u32 maximum_samples = sample_rate / 2000 * 7; + u64 read_offset = 0; u32 read_length = 0; @@ -332,7 +342,10 @@ static void DTKStreamingCallback(DIInterruptType interrupt_type, const std::vect } // Read the next chunk of audio data asynchronously. - s64 ticks_to_dtk = SystemTimers::GetTicksPerSecond() * s64(s_pending_samples) / sample_rate; + // With exact floating points values, GC sample rates multiply to precise + // integer numbers, no need to have a remainder or keep this as a double + s64 ticks_to_dtk = + (SystemTimers::GetTicksPerSecond() / AudioInterface::GetAISSampleRate()) * s_pending_samples; ticks_to_dtk -= cycles_late; if (read_length > 0) { @@ -772,7 +785,7 @@ static bool CheckReadPreconditions() return true; } -// Iff false is returned, ScheduleEvent must be used to finish executing the command +// If false is returned, ScheduleEvent must be used to finish executing the command static bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length, u32 output_length, const DiscIO::Partition& partition, ReplyType reply_type, DIInterruptType* interrupt_type) @@ -1301,7 +1314,7 @@ void SetDriveError(DriveError error) void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late, const std::vector& data) { - // The data parameter contains the requested data iff this was called from DVDThread, and is + // The data parameter contains the requested data if this was called from DVDThread, and is // empty otherwise. DVDThread is the only source of ReplyType::NoReply and ReplyType::DTK. u32 transfer_size = 0; diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp index 99177d21a2a4..22cb2821d40e 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp @@ -320,7 +320,7 @@ void CEXIIPL::TransferByte(u8& data) if (!m_command.is_write()) { u32 dev_addr = DEV_ADDR_CURSOR(ROM); - // Technically we should descramble here iff descrambling logic is enabled. + // Technically we should descramble here if descrambling logic is enabled. // At the moment, we pre-decrypt the whole thing and // ignore the "enabled" bit - see CEXIIPL::CEXIIPL data = m_rom[dev_addr]; diff --git a/Source/Core/Core/HW/GCPadEmu.cpp b/Source/Core/Core/HW/GCPadEmu.cpp index 1ad70afac91f..fdeff8ac1141 100644 --- a/Source/Core/Core/HW/GCPadEmu.cpp +++ b/Source/Core/Core/HW/GCPadEmu.cpp @@ -194,10 +194,6 @@ void GCPad::LoadDefaults(const ControllerInterface& ciface) m_buttons->SetControlExpression(5, "Return"); #endif - // stick modifiers to 50 % - m_main_stick->controls[4]->control_ref->range = 0.5f; - m_c_stick->controls[4]->control_ref->range = 0.5f; - // D-Pad m_dpad->SetControlExpression(0, "T"); // Up m_dpad->SetControlExpression(1, "G"); // Down diff --git a/Source/Core/Core/HW/SystemTimers.cpp b/Source/Core/Core/HW/SystemTimers.cpp index e3ea958da5de..8bc0db6ccdf4 100644 --- a/Source/Core/Core/HW/SystemTimers.cpp +++ b/Source/Core/Core/HW/SystemTimers.cpp @@ -85,8 +85,6 @@ u32 s_cpu_core_clock = 486000000u; // 486 mhz (its not 485, stop bugging me!) // These two are badly educated guesses. // Feel free to experiment. Set them in Init below. -// This is a fixed value, don't change it -int s_audio_dma_period; // This is completely arbitrary. If we find that we need lower latency, // we can just increase this number. int s_ipc_hle_period; @@ -115,7 +113,7 @@ void DSPCallback(u64 userdata, s64 cyclesLate) void AudioDMACallback(u64 userdata, s64 cyclesLate) { - int period = s_cpu_core_clock / (AudioInterface::GetAIDSampleRate() * 4 / 32); + s64 period = s_cpu_core_clock / AudioInterface::GetAIDSampleRate() * 32 / 4; DSP::UpdateAudioDMA(); // Push audio to speakers. CoreTiming::ScheduleEvent(period - cyclesLate, et_AudioDMA); } @@ -301,8 +299,10 @@ void Init() s_ipc_hle_period = GetTicksPerSecond() / freq; } - // System internal sample rate is fixed at 32KHz * 4 (16bit Stereo) / 32 bytes DMA - s_audio_dma_period = s_cpu_core_clock / (AudioInterface::GetAIDSampleRate() * 4 / 32); + // System internal sample rate is fixed at 32KHz * 4 (16bit Stereo) / 32 bytes DMA. + // With exact floating points values, GC sample rates multiply to precise + // integer numbers, no need to have a remainder or keep this as a double + int audio_dma_period = s_cpu_core_clock / AudioInterface::GetAIDSampleRate() * 32 / 4; Common::Timer::IncreaseResolution(); // store and convert localtime at boot to timebase ticks @@ -331,7 +331,7 @@ void Init() CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerHalfLine(), et_VI); CoreTiming::ScheduleEvent(0, et_DSP); - CoreTiming::ScheduleEvent(s_audio_dma_period, et_AudioDMA); + CoreTiming::ScheduleEvent(audio_dma_period, et_AudioDMA); CoreTiming::ScheduleEvent(0, et_Throttle, Common::Timer::GetTimeUs()); CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerField(), et_PatchEngine); diff --git a/Source/Core/Core/HW/VideoInterface.cpp b/Source/Core/Core/HW/VideoInterface.cpp index 0bcdd8df88b8..2b039da87859 100644 --- a/Source/Core/Core/HW/VideoInterface.cpp +++ b/Source/Core/Core/HW/VideoInterface.cpp @@ -28,6 +28,8 @@ #include "DiscIO/Enums.h" +#include "InputCommon/ControlReference/ControlReference.h" + #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoConfig.h" @@ -867,7 +869,12 @@ void Update(u64 ticks) if (s_half_line_of_next_si_poll == s_half_line_count) { - Core::UpdateInputGate(!SConfig::GetInstance().m_BackgroundInput); + // If we wanted to add an option to not block the mouse input when it's + // responsable for capturing focus on the game window, put it as the last param, + // though it would break the code to prevent mouse clicks from being accepted + // after we lost focus (more flags could be added for that) + InputReference::UpdateInputGate(!SConfig::GetInstance().m_BackgroundInput, + SConfig::GetInstance().bLockCursor, true); SerialInterface::UpdateDevices(); s_half_line_of_next_si_poll += 2 * SerialInterface::GetPollXLines(); } diff --git a/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp b/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp index 43bf5e0ce9bf..dbd627b83818 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp @@ -228,7 +228,7 @@ WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g, void EmulatePoint(MotionState* state, ControllerEmu::Cursor* ir_group, float time_elapsed) { - const auto cursor = ir_group->GetState(true); + const auto cursor = ir_group->GetState(time_elapsed, false); if (!cursor.IsVisible()) { diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp index 43b7d1810efc..bd68dff2fd4f 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp @@ -22,6 +22,7 @@ #include "AudioCommon/WaveFile.h" #include "Common/FileUtil.h" #endif +//#pragma optimize("", off) //To delete namespace WiimoteEmu { @@ -33,30 +34,20 @@ static const s32 yamaha_difflookup[] = {1, 3, 5, 7, 9, 11, 13, 15, static const s32 yamaha_indexscale[] = {230, 230, 230, 230, 307, 409, 512, 614, 230, 230, 230, 230, 307, 409, 512, 614}; -static s16 av_clip16(s32 a) +static double av_clip16(double a) { - if ((a + 32768) & ~65535) - return (a >> 31) ^ 32767; - else - return a; -} - -static s32 av_clip(s32 a, s32 amin, s32 amax) -{ - if (a < amin) - return amin; - else if (a > amax) - return amax; + if ((s32(a) + 32768) & ~65535) + return (s32(a) >> 31) ^ 32767; else return a; } static s16 adpcm_yamaha_expand_nibble(ADPCMState& s, u8 nibble) { - s.predictor += (s.step * yamaha_difflookup[nibble]) / 8; + s.predictor += (s.step * yamaha_difflookup[nibble]) / 8.0; s.predictor = av_clip16(s.predictor); - s.step = (s.step * yamaha_indexscale[nibble]) >> 8; - s.step = av_clip(s.step, 127, 24576); + s.step = (s.step * yamaha_indexscale[nibble]) / 256.0; + s.step = std::clamp(s.step, 127.0, 24576.0); return s.predictor; } @@ -73,14 +64,11 @@ void stopdamnwav() void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan) { - // TODO: should we still process samples for the decoder state? - if (!SConfig::GetInstance().m_WiimoteEnableSpeaker) - return; - if (reg_data.sample_rate == 0 || length == 0) return; - // Even if volume is zero we process samples to maintain proper decoder state. + // Even if volume is zero or WiimoteEnableSpeaker is off we process samples to maintain proper + // decoder state // TODO consider using static max size instead of new std::unique_ptr samples(new s16[length * 2]); @@ -103,12 +91,38 @@ void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan) } else if (reg_data.format == SpeakerLogic::DATA_FORMAT_ADPCM) { + //\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b 8 Starts with sometimes + //€€€€€€€€€€€€€€€€€€€€ 128 Ends with sometimes + // I'm not exactly sure how the real wii mote resets its Yamaha ADPCM decoder state, + // but every time a new samples batch is started, if playing the same sound, it's exactly the same as the one before, + // so the decoder should not be maintained between samples. + // testing NSMBW, the data it sends is always the same again, except this time is is shifted differently every time, there is some padding at the beginning, always different in number and value + // Unfortunately we aren't aware of a "new sound started/stopped" + // event. If we open the wii menu while a sound from a game was playing, it will keep playing. + // On real HW it might have been reset with a timer, or it would naturally reset itself with some more accurate math, + // or it might have some unknown flag in the data which tells to reset it, or it knew the length of a sound, + // and with a counter, every time a num of samples have played, it resets itself. + // There definitely is a "sound index" as otherwise the samples would stop playing when entering the wii menu, so we need to get that and know when a new one has started + // This just sounds different than from real HW, the easiest way to tell the difference is + // open the wii home button menu, go to the wii mote settings, and turn down the volume to 1, then change it to 0, it will only make a beeeeep noise, while on the real wii mote it makes a smooth relaxing sound + // 4 bit Yamaha ADPCM (same as dreamcast) for (int i = 0; i < length; ++i) { - samples[i * 2] = adpcm_yamaha_expand_nibble(adpcm_state, (data[i] >> 4) & 0xf); + samples[i * 2] = adpcm_yamaha_expand_nibble(adpcm_state, data[i] >> 4); samples[i * 2 + 1] = adpcm_yamaha_expand_nibble(adpcm_state, data[i] & 0xf); } + //for (int i = length; i > 0; --i) + //{ + // samples[(length - i) * 2] = adpcm_yamaha_expand_nibble(adpcm_state, data[i] & 0xf); + // samples[(length - i) * 2 + 1] = adpcm_yamaha_expand_nibble(adpcm_state, data[i] >> 4); + //} + //for (int n = length; n > 0; n--) + //{ + // u8 v = bytestream2_get_byteu(&gb); + // *samples++ = adpcm_yamaha_expand_nibble(&c->status, v & 0x0F); + // *samples++ = adpcm_yamaha_expand_nibble(&c->status, v >> 4); + //} // Following details from http://wiibrew.org/wiki/Wiimote#Speaker sample_rate_dividend = 6000000; @@ -127,27 +141,30 @@ void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan) volume_divisor = reg_data.volume; } - // SetWiimoteSpeakerVolume expects values from 0 to 255. - // Multiply by 256, floor to int, and clamp to 255 for a uniformly mapped conversion. - const double volume = float(reg_data.volume) * 256.f / volume_divisor; - - // This used the "Constant Power Pan Law", but it is undesirable - // if the pan is 0, and it implied that the loudness of a wiimote speaker - // is equal to the loudness of your device speakers, which isn't true at all. - // This way, if the pan is 0, it's like if it is not there. - // We should play the samples from the wiimote at the native volume they came with, - // because you can lower their volume from the Wii settings and because they are - // already extremely low quality, so any additional quality loss isn't welcome. - speaker_pan = std::clamp(speaker_pan, -1.f, 1.f); - const u32 l_volume = std::min(u32(std::min(1.f - speaker_pan, 1.f) * volume), 255u); - const u32 r_volume = std::min(u32(std::min(1.f + speaker_pan, 1.f) * volume), 255u); - - g_sound_stream->GetMixer()->SetWiimoteSpeakerVolume(l_volume, r_volume); - - // ADPCM sample rate is thought to be x2.(3000 x2 = 6000). - const unsigned int sample_rate = sample_rate_dividend / reg_data.sample_rate; - g_sound_stream->GetMixer()->PushWiimoteSpeakerSamples(samples.get(), sample_length, - sample_rate * 2); + if (SConfig::GetInstance().m_WiimoteEnableSpeaker) + { + // SetWiimoteSpeakerVolume expects values from 0 to 255. + // Multiply by 256, floor to int, and clamp to 255 for a uniformly mapped conversion. + const double volume = float(reg_data.volume) * 256.f / volume_divisor; + + // This used the "Constant Power Pan Law", but it is undesiderable + // if the pan is 0, and it implied that the loudness of a wiimote speaker + // is equal to the loudness of your device speakers, which isn't true at all. + // This way, if the pan is 0, it's like if it is not there. + // We should play the samples from the wiimote at the native volume they came with, + // because you can lower their volume from the wii settings, and because they are + // already extremely low quality, so any additional quality loss isn't welcome. + speaker_pan = std::clamp(speaker_pan, -1.f, 1.f); + u32 l_volume = std::min(u32(std::min(1.f - speaker_pan, 1.f) * volume), 255u); + u32 r_volume = std::min(u32(std::min(1.f + speaker_pan, 1.f) * volume), 255u); + + g_sound_stream->GetMixer()->SetWiimoteSpeakerVolume(m_index, l_volume, r_volume); + + // ADPCM sample rate is thought to be x2 (3000 x2 = 6000). + const unsigned int sample_rate = sample_rate_dividend / reg_data.sample_rate; + g_sound_stream->GetMixer()->PushWiimoteSpeakerSamples(m_index, samples.get(), sample_length, + sample_rate * 2); + } #ifdef WIIMOTE_SPEAKER_DUMP static int num = 0; @@ -177,7 +194,7 @@ void SpeakerLogic::Reset() reg_data = {}; // Yamaha ADPCM state initialize - adpcm_state.predictor = 0; + adpcm_state.predictor = 0; //To make sure it's called (what about when the speaker is muted or disabled) adpcm_state.step = 127; } diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.h b/Source/Core/Core/HW/WiimoteEmu/Speaker.h index d746dc249827..0fb576a379e6 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.h +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.h @@ -13,7 +13,8 @@ namespace WiimoteEmu { struct ADPCMState { - s32 predictor, step; + double predictor; + double step; }; class Wiimote; @@ -31,7 +32,7 @@ class SpeakerLogic : public I2CSlave void DoState(PointerWrap& p); private: - // Pan is -1.0 to +1.0 + // Pan is -1.0 (L) to +1.0 (R) void SpeakerData(const u8* data, int length, float speaker_pan); // TODO: enum class @@ -72,6 +73,9 @@ class SpeakerLogic : public I2CSlave ADPCMState adpcm_state; ControllerEmu::SettingValue m_speaker_pan_setting; + + // Wiimote index, 0-3 + u8 m_index; }; } // namespace WiimoteEmu diff --git a/Source/Core/Core/Host.h b/Source/Core/Core/Host.h index ec539a01d2de..580d7eae64d9 100644 --- a/Source/Core/Core/Host.h +++ b/Source/Core/Core/Host.h @@ -36,6 +36,7 @@ enum class HostMessageID std::vector Host_GetPreferredLocales(); bool Host_UIBlocksControllerState(); bool Host_RendererHasFocus(); +bool Host_RendererHasFullFocus(); bool Host_RendererIsFullscreen(); void Host_Message(HostMessageID id); diff --git a/Source/Core/Core/HotkeyManager.h b/Source/Core/Core/HotkeyManager.h index 8bce8f86fe3c..66732f7d24ce 100644 --- a/Source/Core/Core/HotkeyManager.h +++ b/Source/Core/Core/HotkeyManager.h @@ -29,6 +29,7 @@ enum Hotkey HK_FULLSCREEN, HK_SCREENSHOT, HK_EXIT, + HK_UNLOCK_CURSOR, HK_ACTIVATE_CHAT, HK_REQUEST_GOLF_CONTROL, diff --git a/Source/Core/Core/IOS/USB/USB_KBD.cpp b/Source/Core/Core/IOS/USB/USB_KBD.cpp index 9cb8ed0b4e4b..6f2ab1f2f2c0 100644 --- a/Source/Core/Core/IOS/USB/USB_KBD.cpp +++ b/Source/Core/Core/IOS/USB/USB_KBD.cpp @@ -212,7 +212,7 @@ std::optional USB_KBD::Write(const ReadWriteRequest& request) std::optional USB_KBD::IOCtl(const IOCtlRequest& request) { if (SConfig::GetInstance().m_WiiKeyboard && !Core::WantsDeterminism() && - ControlReference::GetInputGate() && !m_message_queue.empty()) + InputReference::GetInputGate() && !m_message_queue.empty()) { Memory::CopyToEmu(request.buffer_out, &m_message_queue.front(), sizeof(MessageData)); m_message_queue.pop(); diff --git a/Source/Core/DolphinNoGUI/MainNoGUI.cpp b/Source/Core/DolphinNoGUI/MainNoGUI.cpp index 7c64a5cbeb24..95f11388830f 100644 --- a/Source/Core/DolphinNoGUI/MainNoGUI.cpp +++ b/Source/Core/DolphinNoGUI/MainNoGUI.cpp @@ -98,6 +98,12 @@ bool Host_RendererHasFocus() return s_platform->IsWindowFocused(); } +bool Host_RendererHasFullFocus() +{ + // Mouse capturing isn't implemented + return s_platform->IsWindowFocused(); +} + bool Host_RendererIsFullscreen() { return s_platform->IsWindowFullscreen(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp index b07d61c8c011..0f1563dfe136 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp @@ -16,6 +16,9 @@ #include #include +#include "Core/Core.h" +#include "Core/HW/Wiimote.h" + #include "Common/MathUtil.h" #include "InputCommon/ControlReference/ControlReference.h" @@ -293,7 +296,7 @@ void MappingIndicator::paintEvent(QPaintEvent*) void CursorIndicator::Draw() { - const auto adj_coord = m_cursor_group.GetState(true); + const auto adj_coord = m_cursor_group.GetState(0.f, true); DrawReshapableInput(m_cursor_group, CURSOR_TV_COLOR, adj_coord.IsVisible() ? diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h index 7e3fedfe7505..45ce4cbfb6d7 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h @@ -106,7 +106,10 @@ class TiltIndicator : public ReshapableInputIndicator class CursorIndicator : public ReshapableInputIndicator { public: - explicit CursorIndicator(ControllerEmu::Cursor& cursor) : m_cursor_group(cursor) {} + explicit CursorIndicator(ControllerEmu::Cursor& cursor) : m_cursor_group(cursor) + { + m_cursor_group.ResetState(true); + } private: void Draw() override; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp index 01512d0cc317..230d66a29111 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp @@ -202,7 +202,7 @@ MappingWidget::CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingB if (setting.IsSimpleValue()) setting.SetExpressionFromValue(); - IOWindow io(this, GetController(), &setting.GetInputReference(), IOWindow::Type::Input); + IOWindow io(this, GetController(), &setting.GetInputReference(), IOWindow::Type::InputSetting); io.exec(); setting.SimplifyIfPossible(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index ac6230cec476..4b9ca314ce6e 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -445,6 +445,9 @@ void MappingWindow::SetMappingType(MappingWindow::Type type) return; } + // Automatically hide the tab bar if its redundant (we only have one tab) + m_tab_widget->setTabBarAutoHide(true); + widget->LoadSettings(); m_config = widget->GetConfig(); diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index fdf0309b2615..b6a419571b4d 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -66,6 +66,11 @@ bool Host::GetRenderFocus() return m_render_focus; } +bool Host::GetRenderFullFocus() +{ + return m_render_full_focus; +} + void Host::SetRenderFocus(bool focus) { m_render_focus = focus; @@ -76,6 +81,11 @@ void Host::SetRenderFocus(bool focus) }); } +void Host::SetRenderFullFocus(bool focus) +{ + m_render_full_focus = focus; +} + bool Host::GetRenderFullscreen() { return m_render_fullscreen; @@ -131,6 +141,11 @@ bool Host_RendererHasFocus() return Host::GetInstance()->GetRenderFocus(); } +bool Host_RendererHasFullFocus() +{ + return Host::GetInstance()->GetRenderFullFocus(); +} + bool Host_RendererIsFullscreen() { return Host::GetInstance()->GetRenderFullscreen(); @@ -170,6 +185,7 @@ void Host_RequestRenderWindowSize(int w, int h) bool Host_UIBlocksControllerState() { + // TODO: shouldn't this call WantCaptureKeyboard and "controller" as well? return ImGui::GetCurrentContext() && ImGui::GetIO().WantCaptureKeyboard; } diff --git a/Source/Core/DolphinQt/Host.h b/Source/Core/DolphinQt/Host.h index 090e620336a9..33051627a7c4 100644 --- a/Source/Core/DolphinQt/Host.h +++ b/Source/Core/DolphinQt/Host.h @@ -23,10 +23,12 @@ class Host final : public QObject static Host* GetInstance(); bool GetRenderFocus(); + bool GetRenderFullFocus(); bool GetRenderFullscreen(); void SetRenderHandle(void* handle); void SetRenderFocus(bool focus); + void SetRenderFullFocus(bool focus); void SetRenderFullscreen(bool fullscreen); void ResizeSurface(int new_width, int new_height); void RequestNotifyMapLoaded(); @@ -43,5 +45,6 @@ class Host final : public QObject std::atomic m_render_handle{nullptr}; std::atomic m_render_focus{false}; + std::atomic m_render_full_focus{false}; std::atomic m_render_fullscreen{false}; }; diff --git a/Source/Core/DolphinQt/HotkeyScheduler.cpp b/Source/Core/DolphinQt/HotkeyScheduler.cpp index 30bc61aec0f9..b8df1c0b7d34 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt/HotkeyScheduler.cpp @@ -156,12 +156,12 @@ void HotkeyScheduler::Run() if (Core::GetState() != Core::State::Stopping) { // Obey window focus (config permitting) before checking hotkeys. - Core::UpdateInputGate(Config::Get(Config::MAIN_FOCUSED_HOTKEYS)); + InputReference::UpdateInputGate(Config::Get(Config::MAIN_FOCUSED_HOTKEYS), false, true); HotkeyManagerEmu::GetStatus(); // Everything else on the host thread (controller config dialog) should always get input. - ControlReference::SetInputGate(true); + InputReference::SetInputGateOpen(); if (!Core::IsRunningAndStarted()) continue; @@ -213,6 +213,10 @@ void HotkeyScheduler::Run() if (IsHotkey(HK_EXIT)) emit ExitHotkey(); + // Unlock Cursor + if (IsHotkey(HK_UNLOCK_CURSOR)) + emit UnlockCursor(); + auto& settings = Settings::Instance(); // Toggle Chat diff --git a/Source/Core/DolphinQt/HotkeyScheduler.h b/Source/Core/DolphinQt/HotkeyScheduler.h index 4717eb44920b..8ae02301ef06 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.h +++ b/Source/Core/DolphinQt/HotkeyScheduler.h @@ -27,6 +27,7 @@ class HotkeyScheduler : public QObject void ChangeDisc(); void ExitHotkey(); + void UnlockCursor(); void ActivateChat(); void RequestGolfControl(); void FullScreenHotkey(); diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index 60653412bbc1..79383b354179 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -108,6 +108,7 @@ class MainWindow final : public QMainWindow void SetFullScreenResolution(bool fullscreen); void FullScreen(); + void UnlockCursor(); void ScreenShot(); void CreateComponents(); diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index cc92a67c38bd..0456b268f99b 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -232,6 +232,17 @@ bool Settings::GetHideCursor() const return SConfig::GetInstance().bHideCursor; } +void Settings::SetLockCursor(bool lock_cursor) +{ + SConfig::GetInstance().bLockCursor = lock_cursor; + emit LockCursorChanged(); +} + +bool Settings::GetLockCursor() const +{ + return SConfig::GetInstance().bLockCursor; +} + void Settings::SetKeepWindowOnTop(bool top) { if (IsKeepWindowOnTopEnabled() == top) diff --git a/Source/Core/DolphinQt/Settings.h b/Source/Core/DolphinQt/Settings.h index 63396c3e7695..22cb72666637 100644 --- a/Source/Core/DolphinQt/Settings.h +++ b/Source/Core/DolphinQt/Settings.h @@ -96,6 +96,8 @@ class Settings final : public QObject // Graphics void SetHideCursor(bool hide_cursor); bool GetHideCursor() const; + void SetLockCursor(bool lock_cursor); + bool GetLockCursor() const; void SetKeepWindowOnTop(bool top); bool IsKeepWindowOnTopEnabled() const; @@ -164,6 +166,7 @@ class Settings final : public QObject void MetadataRefreshCompleted(); void AutoRefreshToggled(bool enabled); void HideCursorChanged(); + void LockCursorChanged(); void KeepWindowOnTopChanged(bool top); void VolumeChanged(int volume); void NANDRefresh(); diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index f616b6bcf5ac..39b27aa67e3b 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -30,6 +31,9 @@ AudioPane::AudioPane() { + m_running = false; + m_ignore_save_settings = false; + CheckNeedForLatencyControl(); CreateWidgets(); LoadSettings(); @@ -68,6 +72,9 @@ void AudioPane::CreateWidgets() m_volume_slider->setMinimum(0); m_volume_slider->setMaximum(100); + m_volume_slider->setToolTip(tr("Setting volume to values different from 100 might slightly" + " degrade sound quality.")); + m_volume_indicator->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); m_volume_indicator->setFixedWidth(QFontMetrics(font()).boundingRect(tr("%1 %").arg(100)).width()); @@ -86,13 +93,30 @@ void AudioPane::CreateWidgets() m_latency_label = new QLabel(tr("Latency:")); m_latency_spin = new QSpinBox(); m_latency_spin->setMinimum(0); - m_latency_spin->setMaximum(200); - m_latency_spin->setToolTip(tr("Sets the latency (in ms). Higher values may reduce audio " - "crackling. Certain backends only.")); + m_latency_spin->setMaximum(std::min((int)AudioCommon::GetMaxSupportedLatency(), 200)); + //To review: is this not visible? + //To specify that it's the Target/min. A good one is from 3 to 40. It might not work on systems with not many CPU cores + m_latency_spin->setToolTip(tr("Target latency (in ms). Higher values may reduce audio" + " crackling.\nCertain backends only. Values above 20ms are not suggested")); } + m_use_os_sample_rate = new QCheckBox(tr("Use OS Mixer sample rate")); + + std::string useOSSampleRateTooltip = "Directly mixes at the current OS mixer sample rate. As opposed to " + std::to_string(AudioCommon::GetDefaultSampleRate()) + + "Hz. This will make resamplings from" + " 32000Hz sources more accurate,\npossibly improving" + " audio quality at the cost of performance. It won't adjust if you change it after starting. Also if you use 44.1, it will be better" + "\nIf unsure, leave off."; + m_use_os_sample_rate->setToolTip(tr(useOSSampleRateTooltip.c_str())); + + //To review: this might create an empty space when hidden + m_use_os_sample_rate->setHidden(true); + m_dolby_pro_logic->setToolTip( - tr("Enables Dolby Pro Logic II emulation using 5.1 surround. Certain backends only.")); + tr("Enables Dolby Pro Logic II emulation using 5.1 surround.\nCertain backends and DPS " + "emulation engines only.\nAutomatically disabled if not supported by your audio device." + "\nAs on the original hardware, games will still be stereo,\nbut DPLII will" + " extrapolate information to output 5.1 channels.\nIf unsure, leave off. When changing at runtime, you will lose samples. You need to enable it in game for GC or in menu of Wii")); auto* dolby_quality_layout = new QHBoxLayout; @@ -125,35 +149,55 @@ void AudioPane::CreateWidgets() #ifdef _WIN32 m_wasapi_device_label = new QLabel(tr("Device:")); + m_wasapi_device_sample_rate_label = new QLabel(tr("Device Sample Rate:")); m_wasapi_device_combo = new QComboBox; + m_wasapi_device_sample_rate_combo = new QComboBox; + + //To use AudioCommon::GetDefaultSampleRate(). Don't select anything below 48kHz! + m_wasapi_device_sample_rate_combo->setToolTip( + tr("This will not only output at the specified sample rate, \nit will also run the internal " + "mixer at that same sample rate.\nSelecting anything above 48kHz will have very minimal " + "advanced stretching won't work if you don't see a high (or low?) enough backend latency?" + "improvements on sound quality, at the cost of performance.\nAdvantages will only be seen on 32kHz games or if you unlimit the FPS.\nThis is for 2 channel 16bit, surround will try to use the same or fallback to 48000")); backend_layout->addRow(m_wasapi_device_label, m_wasapi_device_combo); + backend_layout->addRow(m_wasapi_device_sample_rate_label, m_wasapi_device_sample_rate_combo); #endif + backend_layout->addWidget(m_use_os_sample_rate); backend_layout->addRow(m_dolby_pro_logic); backend_layout->addRow(m_dolby_quality_label); backend_layout->addRow(dolby_quality_layout); backend_layout->addRow(m_dolby_quality_latency_label); - auto* stretching_box = new QGroupBox(tr("Audio Stretching Settings")); - auto* stretching_layout = new QGridLayout; + auto* game_audio_box = new QGroupBox(tr("Game Audio Settings")); + auto* game_audio_layout = new QGridLayout; m_stretching_enable = new QCheckBox(tr("Enable Audio Stretching")); - m_stretching_buffer_slider = new QSlider(Qt::Horizontal); - m_stretching_buffer_indicator = new QLabel(); - m_stretching_buffer_label = new QLabel(tr("Buffer Size:")); - stretching_box->setLayout(stretching_layout); - - m_stretching_buffer_slider->setMinimum(5); - m_stretching_buffer_slider->setMaximum(300); - - m_stretching_enable->setToolTip(tr("Enables stretching of the audio to match emulation speed.")); - m_stretching_buffer_slider->setToolTip(tr("Size of stretch buffer in milliseconds. " - "Values too low may cause audio crackling.")); - - stretching_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); - stretching_layout->addWidget(m_stretching_buffer_label, 1, 0); - stretching_layout->addWidget(m_stretching_buffer_slider, 1, 1); - stretching_layout->addWidget(m_stretching_buffer_indicator, 1, 2); + m_emu_speed_tolerance_slider = new QSlider(Qt::Horizontal); + m_emu_speed_tolerance_indicator = new QLabel(); + m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); + game_audio_box->setLayout(game_audio_layout); + + m_emu_speed_tolerance_slider->setMinimum(-1); + m_emu_speed_tolerance_slider->setMaximum(300); + m_emu_speed_tolerance_slider->setToolTip( + tr("Time (ms) we need to fall behind the emulation for sound to start slowing down. If set " + "too high, sound will crackle when we slow down or stutter, if set too low, sound might " + "lose quality if you have constant small stutters. Leave around 15ms if unsure. Slide all " + "the way left to disable. Below 10 is unsuggested unless you always want to enabled")); + + m_stretching_enable->setToolTip( + tr("Enables stretching of the audio to match emulation speed (pitch correction)." + "\nA backend latency of at least 32ms is suggested to avoid" + "\nloss of quality. Having None will cause audio to stop if you dip FPS, but it will be " + "of max quality other times. Simple add an internal buffer/latency to have some samples " + "left for frame dips and if the audio playback loses alignment")); + + game_audio_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); + //game_audio_layout->addWidget(m_stretching_enable, 0, 1); //To review + game_audio_layout->addWidget(m_emu_speed_tolerance_label, 1, 0); + game_audio_layout->addWidget(m_emu_speed_tolerance_slider, 1, 1); + game_audio_layout->addWidget(m_emu_speed_tolerance_indicator, 1, 2); m_main_layout = new QGridLayout; @@ -164,7 +208,7 @@ void AudioPane::CreateWidgets() m_main_layout->addWidget(dsp_box, 0, 0); m_main_layout->addWidget(volume_box, 0, 1, -1, 1); m_main_layout->addWidget(backend_box, 1, 0); - m_main_layout->addWidget(stretching_box, 2, 0); + m_main_layout->addWidget(game_audio_box, 2, 0); setLayout(m_main_layout); } @@ -179,7 +223,8 @@ void AudioPane::ConnectWidgets() connect(m_latency_spin, qOverload(&QSpinBox::valueChanged), this, &AudioPane::SaveSettings); } - connect(m_stretching_buffer_slider, &QSlider::valueChanged, this, &AudioPane::SaveSettings); + connect(m_emu_speed_tolerance_slider, &QSlider::valueChanged, this, &AudioPane::SaveSettings); + connect(m_use_os_sample_rate, &QCheckBox::toggled, this, &AudioPane::SaveSettings); connect(m_dolby_pro_logic, &QCheckBox::toggled, this, &AudioPane::SaveSettings); connect(m_dolby_quality_slider, &QSlider::valueChanged, this, &AudioPane::SaveSettings); connect(m_stretching_enable, &QCheckBox::toggled, this, &AudioPane::SaveSettings); @@ -190,6 +235,8 @@ void AudioPane::ConnectWidgets() #ifdef _WIN32 connect(m_wasapi_device_combo, qOverload(&QComboBox::currentIndexChanged), this, &AudioPane::SaveSettings); + connect(m_wasapi_device_sample_rate_combo, qOverload(&QComboBox::currentIndexChanged), this, + &AudioPane::SaveSettings); #endif } @@ -209,19 +256,23 @@ void AudioPane::LoadSettings() } // Backend + m_ignore_save_settings = true; const auto current = SConfig::GetInstance().sBackend; bool selection_set = false; + m_backend_combo->clear(); for (const auto& backend : AudioCommon::GetSoundBackends()) { m_backend_combo->addItem(tr(backend.c_str()), QVariant(QString::fromStdString(backend))); if (backend == current) { + //To specify that WASAPI (or any) might not work on all devices and stops other sounds m_backend_combo->setCurrentIndex(m_backend_combo->count() - 1); selection_set = true; } } if (!selection_set) m_backend_combo->setCurrentIndex(-1); + m_ignore_save_settings = false; OnBackendChanged(); @@ -230,25 +281,40 @@ void AudioPane::LoadSettings() // DPL2 m_dolby_pro_logic->setChecked(SConfig::GetInstance().bDPL2Decoder); + m_ignore_save_settings = true; m_dolby_quality_slider->setValue(int(Config::Get(Config::MAIN_DPL2_QUALITY))); + m_ignore_save_settings = false; m_dolby_quality_latency_label->setText( GetDPL2ApproximateLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); if (AudioCommon::SupportsDPL2Decoder(current) && !m_dsp_hle->isChecked()) { - EnableDolbyQualityWidgets(m_dolby_pro_logic->isChecked()); + EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked() && + !m_running); } // Latency if (m_latency_control_supported) + { + m_ignore_save_settings = true; m_latency_spin->setValue(SConfig::GetInstance().iLatency); + m_ignore_save_settings = false; + } + + // Sample rate + m_use_os_sample_rate->setChecked(SConfig::GetInstance().bUseOSMixerSampleRate); // Stretch m_stretching_enable->setChecked(SConfig::GetInstance().m_audio_stretch); - m_stretching_buffer_slider->setValue(SConfig::GetInstance().m_audio_stretch_max_latency); - m_stretching_buffer_slider->setEnabled(m_stretching_enable->isChecked()); - m_stretching_buffer_indicator->setText(tr("%1 ms").arg(m_stretching_buffer_slider->value())); + m_emu_speed_tolerance_slider->setValue(SConfig::GetInstance().m_audio_emu_speed_tolerance); + if (m_emu_speed_tolerance_slider->value() < 0) + m_emu_speed_tolerance_indicator->setText(tr("Disabled")); + else + m_emu_speed_tolerance_indicator->setText( + tr("%1 ms").arg(m_emu_speed_tolerance_slider->value())); + m_emu_speed_tolerance_indicator->setAlignment(Qt::AlignRight); //To review #ifdef _WIN32 + m_ignore_save_settings = true; if (SConfig::GetInstance().sWASAPIDevice == "default") { m_wasapi_device_combo->setCurrentIndex(0); @@ -257,14 +323,34 @@ void AudioPane::LoadSettings() { m_wasapi_device_combo->setCurrentText( QString::fromStdString(SConfig::GetInstance().sWASAPIDevice)); + // Saved device not found, reset it + if (m_wasapi_device_combo->currentIndex() < 1) + { + SConfig::GetInstance().sWASAPIDevice = "default"; + } } + LoadWASAPIDeviceSampleRate(); + m_ignore_save_settings = false; #endif + + // Call this again to "clamp" values that might not have been accepted + SaveSettings(); } void AudioPane::SaveSettings() { + // Avoids multiple calls to this when we are modifying the widgets + // in a way that would trigger multiple SaveSettings() callbacks + if (m_ignore_save_settings) + { + return; + } + auto& settings = Settings::Instance(); + bool volume_changed = false; + bool backend_setting_changed = false; + // DSP if (SConfig::GetInstance().bDSPHLE != m_dsp_hle->isChecked() || SConfig::GetInstance().m_DSPEnableJIT != m_dsp_lle->isChecked()) @@ -292,42 +378,84 @@ void AudioPane::SaveSettings() { settings.SetVolume(m_volume_slider->value()); OnVolumeChanged(settings.GetVolume()); + + volume_changed = true; } // DPL2 - SConfig::GetInstance().bDPL2Decoder = m_dolby_pro_logic->isChecked(); + if (SConfig::GetInstance().bDPL2Decoder != m_dolby_pro_logic->isChecked()) + { + SConfig::GetInstance().bDPL2Decoder = m_dolby_pro_logic->isChecked(); + backend_setting_changed = true; + } Config::SetBase(Config::MAIN_DPL2_QUALITY, static_cast(m_dolby_quality_slider->value())); m_dolby_quality_latency_label->setText( GetDPL2ApproximateLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) { - EnableDolbyQualityWidgets(m_dolby_pro_logic->isChecked()); + EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && + m_dolby_pro_logic->isChecked() && !m_running); } // Latency - if (m_latency_control_supported) + if (m_latency_control_supported && SConfig::GetInstance().iLatency != m_latency_spin->value()) + { SConfig::GetInstance().iLatency = m_latency_spin->value(); + backend_setting_changed = true; + } + + // Sample rate + SConfig::GetInstance().bUseOSMixerSampleRate = m_use_os_sample_rate->isChecked(); // Stretch SConfig::GetInstance().m_audio_stretch = m_stretching_enable->isChecked(); - SConfig::GetInstance().m_audio_stretch_max_latency = m_stretching_buffer_slider->value(); - m_stretching_buffer_label->setEnabled(m_stretching_enable->isChecked()); - m_stretching_buffer_slider->setEnabled(m_stretching_enable->isChecked()); - m_stretching_buffer_indicator->setEnabled(m_stretching_enable->isChecked()); - m_stretching_buffer_indicator->setText( - tr("%1 ms").arg(SConfig::GetInstance().m_audio_stretch_max_latency)); + SConfig::GetInstance().m_audio_emu_speed_tolerance = m_emu_speed_tolerance_slider->value(); + m_emu_speed_tolerance_indicator->setText( + tr("%1 ms").arg(SConfig::GetInstance().m_audio_emu_speed_tolerance)); #ifdef _WIN32 + // If left at default, Dolphin will automatically + // pick a device and sample rate std::string device = "default"; - if (m_wasapi_device_combo->currentIndex() != 0) + if (m_wasapi_device_combo->currentIndex() > 0) device = m_wasapi_device_combo->currentText().toStdString(); - SConfig::GetInstance().sWASAPIDevice = device; + if (SConfig::GetInstance().sWASAPIDevice != device) + { + assert(device != ""); + SConfig::GetInstance().sWASAPIDevice = device; + + bool is_wasapi = backend == BACKEND_WASAPI; + if (is_wasapi) + { + OnWASAPIDeviceChanged(); + LoadWASAPIDeviceSampleRate(); + backend_setting_changed = true; + } + } + + std::string deviceSampleRate = "0"; + + bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default" && !m_running; + if (m_wasapi_device_sample_rate_combo->currentIndex() > 0 && canSelectDeviceSampleRate) + { + QString qs = m_wasapi_device_sample_rate_combo->currentText(); + qs.chop(2); // Remove "Hz" from the end + deviceSampleRate = qs.toStdString(); + } + + if (!m_running) + { + SConfig::GetInstance().sWASAPIDeviceSampleRate = deviceSampleRate; + } #endif - AudioCommon::UpdateSoundStream(); + if (volume_changed || backend_setting_changed) + { + AudioCommon::UpdateSoundStreamSettings(!backend_setting_changed); + } } void AudioPane::OnDspChanged() @@ -336,18 +464,19 @@ void AudioPane::OnDspChanged() m_dolby_pro_logic->setEnabled(AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()); - EnableDolbyQualityWidgets(AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked() && - m_dolby_pro_logic->isChecked()); + EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked() && + !m_running); } void AudioPane::OnBackendChanged() { const auto backend = SConfig::GetInstance().sBackend; + m_use_os_sample_rate->setEnabled(backend != BACKEND_NULLSOUND); m_dolby_pro_logic->setEnabled(AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()); - EnableDolbyQualityWidgets(AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked() && - m_dolby_pro_logic->isChecked()); + EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked() && + !m_running); if (m_latency_control_supported) { m_latency_label->setEnabled(AudioCommon::SupportsLatencyControl(backend)); @@ -357,15 +486,27 @@ void AudioPane::OnBackendChanged() #ifdef _WIN32 bool is_wasapi = backend == BACKEND_WASAPI; m_wasapi_device_label->setHidden(!is_wasapi); + m_wasapi_device_sample_rate_label->setHidden(!is_wasapi); m_wasapi_device_combo->setHidden(!is_wasapi); + m_wasapi_device_sample_rate_combo->setHidden(!is_wasapi); + + // TODO: implement this in other Operative Systems, + // as of now this is only supported on (all) Windows backends + m_use_os_sample_rate->setHidden(is_wasapi); if (is_wasapi) { + m_ignore_save_settings = true; + m_wasapi_device_combo->clear(); m_wasapi_device_combo->addItem(tr("Default Device")); for (const auto device : WASAPIStream::GetAvailableDevices()) m_wasapi_device_combo->addItem(QString::fromStdString(device)); + + OnWASAPIDeviceChanged(); + + m_ignore_save_settings = false; } #endif @@ -373,39 +514,141 @@ void AudioPane::OnBackendChanged() m_volume_indicator->setEnabled(AudioCommon::SupportsVolumeChanges(backend)); } +#ifdef _WIN32 +void AudioPane::OnWASAPIDeviceChanged() +{ + m_ignore_save_settings = true; + + m_wasapi_device_sample_rate_combo->clear(); + // Don't allow users to select a sample rate for the default device, + // even though that would be possible, the default device can change + // at any time so it wouldn't make sense + bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default" && !m_running; + if (canSelectDeviceSampleRate) + { + m_wasapi_device_sample_rate_combo->setEnabled(true); + m_wasapi_device_sample_rate_label->setEnabled(true); + + std::string default_sample_rate_text = "Default Dolphin Sample Rate (" + + std::to_string(AudioCommon::GetDefaultSampleRate()) + + "Hz)"; + m_wasapi_device_sample_rate_combo->addItem(tr(default_sample_rate_text.c_str())); + + for (const auto deviceSampleRate : WASAPIStream::GetSelectedDeviceSampleRates()) + { + m_wasapi_device_sample_rate_combo->addItem( + QString::number(deviceSampleRate).append(tr("Hz"))); + } + } + else + { + m_wasapi_device_sample_rate_combo->setEnabled(false); + m_wasapi_device_sample_rate_label->setEnabled(false); + std::string sample_rate_text; + if (m_running) + { + sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate + "Hz"; + if (sample_rate_text == "0Hz") + { + sample_rate_text = std::to_string(AudioCommon::GetDefaultSampleRate()) + "Hz"; + } + } + else + { + sample_rate_text = + "Select a Device (" + std::to_string(AudioCommon::GetDefaultSampleRate()) + "Hz)"; + } + m_wasapi_device_sample_rate_combo->addItem(tr(sample_rate_text.c_str())); + } + + m_ignore_save_settings = false; +} + +void AudioPane::LoadWASAPIDeviceSampleRate() +{ + bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default" && !m_running; + if (SConfig::GetInstance().sWASAPIDeviceSampleRate == "0" || !canSelectDeviceSampleRate) + { + m_wasapi_device_sample_rate_combo->setCurrentIndex(0); + SConfig::GetInstance().sWASAPIDeviceSampleRate = "0"; + } + else + { + std::string sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate + "Hz"; + m_wasapi_device_sample_rate_combo->setCurrentText(QString::fromStdString(sample_rate_text)); + // Saved sample rate not found, reset it + if (m_wasapi_device_combo->currentIndex() < 1) + { + SConfig::GetInstance().sWASAPIDeviceSampleRate = "0"; + } + } +} +#endif + void AudioPane::OnEmulationStateChanged(bool running) { + m_running = running; + + const auto backend = SConfig::GetInstance().sBackend; + + bool backend_supports_runtime_changes = AudioCommon::SupportsRuntimeSettingsChanges(); + m_dsp_hle->setEnabled(!running); m_dsp_lle->setEnabled(!running); m_dsp_interpreter->setEnabled(!running); m_backend_label->setEnabled(!running); m_backend_combo->setEnabled(!running); - if (AudioCommon::SupportsDPL2Decoder(SConfig::GetInstance().sBackend) && !m_dsp_hle->isChecked()) + m_use_os_sample_rate->setEnabled(!running && backend != BACKEND_NULLSOUND); + + //To review this and all the m_dsp_hle->isChecked() + if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) { - m_dolby_pro_logic->setEnabled(!running); + bool enable_dolby_pro_logic = !running || backend_supports_runtime_changes; + m_dolby_pro_logic->setEnabled(enable_dolby_pro_logic); + // These settings cannot be changed at runtime EnableDolbyQualityWidgets(!running && m_dolby_pro_logic->isChecked()); } if (m_latency_control_supported && AudioCommon::SupportsLatencyControl(SConfig::GetInstance().sBackend)) { - m_latency_label->setEnabled(!running); - m_latency_spin->setEnabled(!running); + bool enable_latency = (!running || backend_supports_runtime_changes) && + AudioCommon::SupportsLatencyControl(backend); + m_latency_label->setEnabled(enable_latency); + m_latency_spin->setEnabled(enable_latency); } #ifdef _WIN32 - m_wasapi_device_combo->setEnabled(!running); + m_wasapi_device_label->setEnabled(!running || backend_supports_runtime_changes); + m_wasapi_device_combo->setEnabled(!running || backend_supports_runtime_changes); + bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default" && !m_running; + m_wasapi_device_sample_rate_label->setEnabled(!running && canSelectDeviceSampleRate); + m_wasapi_device_sample_rate_combo->setEnabled(!running && canSelectDeviceSampleRate); //To allow changes at runtime now? + bool is_wasapi = backend == BACKEND_WASAPI; + if (is_wasapi) + { + OnWASAPIDeviceChanged(); + if (!running) + { + m_ignore_save_settings = true; + LoadWASAPIDeviceSampleRate(); + m_ignore_save_settings = false; + } + } #endif } void AudioPane::OnVolumeChanged(int volume) { + m_ignore_save_settings = true; m_volume_slider->setValue(volume); + m_ignore_save_settings = false; m_volume_indicator->setText(tr("%1 %").arg(volume)); } void AudioPane::CheckNeedForLatencyControl() { std::vector backends = AudioCommon::GetSoundBackends(); + // Don't show latency related widgets if none of our backends support latency m_latency_control_supported = std::any_of(backends.cbegin(), backends.cend(), AudioCommon::SupportsLatencyControl); } diff --git a/Source/Core/DolphinQt/Settings/AudioPane.h b/Source/Core/DolphinQt/Settings/AudioPane.h index 3348e5e90819..0033db39a1e0 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.h +++ b/Source/Core/DolphinQt/Settings/AudioPane.h @@ -35,6 +35,10 @@ class AudioPane final : public QWidget void OnEmulationStateChanged(bool running); void OnBackendChanged(); +#ifdef _WIN32 + void OnWASAPIDeviceChanged(); + void LoadWASAPIDeviceSampleRate(); + #endif void OnDspChanged(); void OnVolumeChanged(int volume); @@ -45,6 +49,9 @@ class AudioPane final : public QWidget QString GetDPL2ApproximateLatencyLabel(AudioCommon::DPL2Quality value) const; void EnableDolbyQualityWidgets(bool enabled) const; + bool m_running; + bool m_ignore_save_settings; + QGridLayout* m_main_layout; // DSP Engine @@ -67,14 +74,17 @@ class AudioPane final : public QWidget QLabel* m_dolby_quality_latency_label; QLabel* m_latency_label; QSpinBox* m_latency_spin; + QCheckBox* m_use_os_sample_rate; #ifdef _WIN32 QLabel* m_wasapi_device_label; + QLabel* m_wasapi_device_sample_rate_label; QComboBox* m_wasapi_device_combo; + QComboBox* m_wasapi_device_sample_rate_combo; #endif // Audio Stretching QCheckBox* m_stretching_enable; - QLabel* m_stretching_buffer_label; - QSlider* m_stretching_buffer_slider; - QLabel* m_stretching_buffer_indicator; + QLabel* m_emu_speed_tolerance_label; + QSlider* m_emu_speed_tolerance_slider; + QLabel* m_emu_speed_tolerance_indicator; }; diff --git a/Source/Core/DolphinQt/Settings/GeneralPane.cpp b/Source/Core/DolphinQt/Settings/GeneralPane.cpp index 705bdee7ed29..72d980d8543f 100644 --- a/Source/Core/DolphinQt/Settings/GeneralPane.cpp +++ b/Source/Core/DolphinQt/Settings/GeneralPane.cpp @@ -158,11 +158,16 @@ void GeneralPane::CreateBasic() m_combobox_speedlimit = new QComboBox(); m_combobox_speedlimit->addItem(tr("Unlimited")); - for (int i = 10; i <= 200; i += 10) // from 10% to 200% + for (int i = 10; i <= 500; i += 10) // from 10% to 200% { QString str; if (i != 100) - str = QStringLiteral("%1%").arg(i); + { + if (i > 200 && std::fmod((float)i, 50.f) != 0) + continue; + else + str = QStringLiteral("%1%").arg(i); + } else str = tr("%1% (Normal Speed)").arg(i); @@ -354,6 +359,10 @@ void GeneralPane::OnSaveConfig() Config::SetBase(Config::MAIN_AUTO_DISC_CHANGE, m_checkbox_auto_disc_change->isChecked()); Config::SetBaseOrCurrent(Config::MAIN_ENABLE_CHEATS, m_checkbox_cheats->isChecked()); settings.m_EmulationSpeed = m_combobox_speedlimit->currentIndex() * 0.1f; + if (settings.m_EmulationSpeed > 2.f) + { + settings.m_EmulationSpeed = 2.f + (m_combobox_speedlimit->currentIndex() - 20) * 0.5f; + } Settings::Instance().SetFallbackRegion( UpdateFallbackRegionFromIndex(m_combobox_fallback_region->currentIndex())); diff --git a/Source/Core/DolphinQt/Settings/InterfacePane.h b/Source/Core/DolphinQt/Settings/InterfacePane.h index d135de599272..6fde8d25fe89 100644 --- a/Source/Core/DolphinQt/Settings/InterfacePane.h +++ b/Source/Core/DolphinQt/Settings/InterfacePane.h @@ -45,4 +45,5 @@ class InterfacePane final : public QWidget QCheckBox* m_checkbox_show_active_title; QCheckBox* m_checkbox_pause_on_focus_lost; QCheckBox* m_checkbox_hide_mouse; + QCheckBox* m_checkbox_lock_mouse; }; diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp index 671f2f3379a4..db8e85001a18 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.cpp +++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp @@ -4,19 +4,59 @@ #include "InputCommon/ControlReference/ControlReference.h" -using namespace ciface::ExpressionParser; +#include "Core/Host.h" -static thread_local bool tls_input_gate = true; +using namespace ciface::ExpressionParser; -void ControlReference::SetInputGate(bool enable) +// See Control::FocusFlags for more details +enum class InputGateFlags : u8 { - tls_input_gate = enable; -} + // This is always true if the user accepts background input, otherwise + // it's only true when the window has focus, similarly to the state below + HasFocus = 0x01, + // Full focus means the cursor is captured/locked inside the game window. + // Ignored if mouse capturing is off. Implies HasFocus + HasFullFocus = 0x02, + // To ignore some specific inputs temporarily, e.g. mouse down on mouse capture, + // as the user presses mouse down to get full focus. + HadFocus = 0x04, + HadFullFocus = 0x08, + // Given that the input gate is updated at the SI rate, in 60fps + // Wii games it can happen that 2 input gate updates happen between + // emulated Wiimote input updates, so HadFocus would have been + // turned to true already, leaving us unable to block inputs because + // of focus changes. Even with a timer it wouldn't work + // as it would need to depend on emulation speed. + HadHadFocus = 0x10, + HadHadFullFocus = 0x20, + // Fully (force) open, always accept any input. Has priority over any other flag + Ignore = 0x40, + // Accept calls to ignore (block) input when focus, or full focus, is acquired or lost + IgnoreInputOnFocusChanged = 0x80, -bool ControlReference::GetInputGate() -{ - return tls_input_gate; -} + // Most or all input will pass in this case. + // If none of these are true, the gate is fully closed + OpenMask = HasFocus | Ignore, + // All input will pass in this case. Remove these flags to block + FullOpenMask = HasFocus | HasFullFocus | Ignore, + FocusMask = HasFocus | HasFullFocus, + + FocusHistoryMask = HasFocus | HadFocus | HadHadFocus, + FullFocusHistoryMask = HasFullFocus | HadFullFocus | HadHadFullFocus +}; + +// As of now these thread_local variables are only accessed by the host (UI) and the game thread. +// Gate is "open" by default in case we don't bother setting it. +static thread_local InputGateFlags tls_input_gate_flags = InputGateFlags::FullOpenMask; +// This list in never cleaned but unless InputReference(s) are not constantly removed and re-added +// on the fly, it's not a problem. We can't store the blocking in the object instance as it needs +// to be per thread. But in any case, this is safe as we only ever compare values to it. +// An alternative would be to have an ever increasing handle for each newly added InputReference. +// An even better alternative would be to have an helper struct, which contains the std::vector, +// and the helper struct would add itslef to a static (non thread_local) list on construction, +// and remove itself on destruction, meaning that if an InputReference got destroyed, we +// could safely remove all its references from the list in every thread. +static thread_local std::vector tls_blocked_inputs; // // UpdateReference @@ -59,7 +99,9 @@ std::optional ControlReference::SetExpression(std::string expr) return parse_result.description; } -ControlReference::ControlReference() : range(1), m_parsed_expression(nullptr) +ControlReference::ControlReference() + : range(1.0), default_range(1.0), m_parse_status(ParseStatus::EmptyExpression), + m_parsed_expression(nullptr) { } @@ -69,6 +111,10 @@ InputReference::InputReference() : ControlReference() { } +IgnoreGateInputReference::IgnoreGateInputReference() : InputReference() +{ +} + OutputReference::OutputReference() : ControlReference() { } @@ -82,6 +128,140 @@ bool OutputReference::IsInput() const return false; } +void InputReference::SetInputGateOpen() +{ + tls_input_gate_flags = InputGateFlags(u8(tls_input_gate_flags) | u8(InputGateFlags::Ignore)); +} + +bool InputReference::GetInputGate() +{ + return (u8(tls_input_gate_flags) & u8(InputGateFlags::FocusMask)) == + u8(InputGateFlags::FocusMask) || + (u8(tls_input_gate_flags) & u8(InputGateFlags::Ignore)); +} + +void InputReference::UpdateInputGate(bool require_focus, bool require_full_focus, + bool ignore_input_on_focus_changed) +{ + u8 input_gate = 0; + + // If the user accepts background input, the input should also be accepted + // even if an on screen interface is active + if (!require_focus || (Host_RendererHasFocus() && !Host_UIBlocksControllerState())) + { + input_gate |= u8(InputGateFlags::HasFocus); + + if (!require_focus || !require_full_focus || Host_RendererHasFullFocus()) + input_gate |= u8(InputGateFlags::HasFullFocus); + + static_assert(u8(InputGateFlags::HadFocus) == u8(InputGateFlags::HasFocus) << 2); + static_assert(u8(InputGateFlags::HadHadFocus) == u8(InputGateFlags::HadFocus) << 2); + static_assert(u8(InputGateFlags::HadFullFocus) == u8(InputGateFlags::HasFullFocus) << 2); + static_assert(u8(InputGateFlags::HadHadFullFocus) == u8(InputGateFlags::HadFullFocus) << 2); + input_gate |= (u8(tls_input_gate_flags) & u8(InputGateFlags::HasFocus)) << 2; + input_gate |= (u8(tls_input_gate_flags) & u8(InputGateFlags::HadFocus)) << 2; + input_gate |= (u8(tls_input_gate_flags) & u8(InputGateFlags::HasFullFocus)) << 2; + input_gate |= (u8(tls_input_gate_flags) & u8(InputGateFlags::HadFullFocus)) << 2; + + if (require_focus && ignore_input_on_focus_changed) + input_gate |= u8(InputGateFlags::IgnoreInputOnFocusChanged); + } + else + { + // No need to set InputGateFlags::HadFocus or InputGateFlags::HadFullFocus + // as they are only used when passing from not focus to focus + } + tls_input_gate_flags = InputGateFlags(input_gate); +} + +bool InputReference::FilterInput() +{ + if (!m_parsed_expression) + { + return false; + } + + // Theoretically we'd want to check the input flags individually on every function in the + // expression, but given how annoying, slow and useless it would be to do that, we just "sum" + // input flags of all of our functions, the highest priority flags will win (IgnoreFocus). + // If there are no actual inputs in the expression, the default will be returned (RequireFocus) + u8 focus_flags = u8(m_parsed_expression->GetFocusFlags()); + + // Return true even if the gate is blocked in some cases, things like battery level + // always need to pass + if ((u8(tls_input_gate_flags) & u8(InputGateFlags::Ignore)) || + (focus_flags & u8(Device::FocusFlags::IgnoreFocus))) + { + return true; + } + + // This a rare case, it's usually skipped + if ((focus_flags & u8(Device::FocusFlags::IgnoreOnFocusChanged)) && + (u8(tls_input_gate_flags) & u8(InputGateFlags::IgnoreInputOnFocusChanged))) + { + // If we had focus, make sure we still have it, if not, ignore the input. + // We should also check if the focus was actually required from the input gate or if it is + // fake, but it doesn't matter as the flag IgnoreInputOnFocusChanged would not have been on + if ((u8(tls_input_gate_flags) & u8(InputGateFlags::HasFocus)) && !Host_RendererHasFocus()) + return false; + + // Unfortunately on focus loss "ignoring" input wouldn't always work without checking the + // current host focus value. Before this change the game would often react to a mouse click + // that made the window lose focus. That is because of multiple reasons, + // mainly the input gate not being updated enough (only at SI rate) and as a consequence + // some inputs might be read before being blocked by the next SI input gate update. + // Increasing the update rate of the input gate would break the opposite case though, + // (ignoring the mouse click which made the window gain focus) which is more important. + // Loads of things play a role: + // -The update rate of the input gate (SI rate, twice the video frame rate) + // -The update rate of the emulated Wiimote (Wiimote::UPDATE_FREQ: 200Hz) + // -Small delays in window activation and de-activation events from QT/Windows + // -Even if devices are updated at SI rate, the mouse might return a slightly outdated inputs + // This problem doesn't happen on GC, it only happens if an input is bound to an emulated + // Wiimote. + + // If the focus up bits are either 1 or 2, then focus has been acquired + u8 focus_bits = u8(InputGateFlags::FocusHistoryMask) & u8(tls_input_gate_flags); + bool focus_changed = focus_bits != 0 && focus_bits != u8(InputGateFlags::FocusHistoryMask); + u8 full_focus_bits = u8(InputGateFlags::FullFocusHistoryMask) & u8(tls_input_gate_flags); + bool full_focus_changed = + full_focus_bits != 0 && full_focus_bits != u8(InputGateFlags::FullFocusHistoryMask); + + bool is_blocked = false; + std::vector::iterator it = + std::find(tls_blocked_inputs.begin(), tls_blocked_inputs.end(), this); + if (it != tls_blocked_inputs.end()) + { + is_blocked = true; + } + + if ((u8(focus_flags) & u8(Device::FocusFlags::RequireFullFocus)) ? full_focus_changed : + focus_changed) + { + // Block until release if pressed + if (!is_blocked && m_parsed_expression->GetValue() > 0.0) + tls_blocked_inputs.push_back(this); + return false; + } + + if (is_blocked) + { + if (m_parsed_expression->GetValue() > 0.0) + return false; + // Unblock on release. + // We should probably do this if the IgnoreInputOnFocusChanged flag is turned off as well, + // but it's not worth it (never happens for now) + tls_blocked_inputs.erase(it); + } + } + // Exclude this flag from the comparison as it's not actually in InputGateFlags + focus_flags &= ~u8(Device::FocusFlags::IgnoreOnFocusChanged); + + // Remove all the input gate flags from the input flags, if we have no + // "requirements" left, then we can go on + return (focus_flags & ~u8(tls_input_gate_flags)) == 0; +} + // // InputReference :: State // @@ -90,7 +270,15 @@ bool OutputReference::IsInput() const // ControlState InputReference::State(const ControlState ignore) { - if (m_parsed_expression && GetInputGate()) + // Every expression can have a different filter (which depends on the gate) + if (FilterInput()) + return m_parsed_expression->GetValue() * range; + return 0.0; +} + +ControlState IgnoreGateInputReference::State(const ControlState ignore) +{ + if (m_parsed_expression) return m_parsed_expression->GetValue() * range; return 0.0; } @@ -99,9 +287,8 @@ ControlState InputReference::State(const ControlState ignore) // OutputReference :: State // // Set the state of all binded outputs -// overrides ControlReference::State .. combined them so I could make the GUI simple / inputs == -// same as outputs one list -// I was lazy and it works so watever +// overrides ControlReference::State ... combined them so I could make the GUI simple / inputs == +// same as outputs one list. Ignores the input gate for now // ControlState OutputReference::State(const ControlState state) { diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.h b/Source/Core/InputCommon/ControlReference/ControlReference.h index 59a21e8b7db3..264377fd5e81 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.h +++ b/Source/Core/InputCommon/ControlReference/ControlReference.h @@ -10,6 +10,8 @@ #include "InputCommon/ControlReference/ExpressionParser.h" #include "InputCommon/ControllerInterface/CoreDevice.h" +using namespace ciface::Core; + // ControlReference // // These are what you create to actually use the inputs, InputReference or OutputReference. @@ -19,14 +21,9 @@ // when you change a ControlReference's expression, // you must use UpdateReference on it to rebind controls // - class ControlReference { public: - // Note: this is per thread. - static void SetInputGate(bool enable); - static bool GetInputGate(); - virtual ~ControlReference(); virtual ControlState State(const ControlState state = 0) = 0; virtual bool IsInput() const = 0; @@ -42,7 +39,10 @@ class ControlReference // Returns a human-readable error description when the given expression is invalid. std::optional SetExpression(std::string expr); + // State value multiplier ControlState range; + // Useful for resetting settings. Always 1 except for input modifies + ControlState default_range; protected: ControlReference(); @@ -78,11 +78,33 @@ inline ControlState ControlReference::GetState() class InputReference : public ControlReference { public: + // Note: the input gate is per thread. + // Set the gate to be fully open, ignoring focus + static void SetInputGateOpen(); + // Check if the gate is fully open + static bool GetInputGate(); + // This is mostly to cache values for cheaper retrieval and consistency within a frame + static void UpdateInputGate(bool require_focus, bool require_full_focus = false, + bool ignore_input_on_focus_changed = false); + InputReference(); + bool FilterInput(); bool IsInput() const override; ControlState State(const ControlState state) override; }; +// +// IgnoreGateInputReference +// +// Control reference for inputs that should ignore focus, like battery level +// +class IgnoreGateInputReference : public InputReference +{ +public: + IgnoreGateInputReference(); + ControlState State(const ControlState state) override; +}; + // // OutputReference // diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.h b/Source/Core/InputCommon/ControlReference/ExpressionParser.h index 9789e9afe017..9688bc112811 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.h +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.h @@ -13,6 +13,8 @@ namespace ciface::ExpressionParser { +using namespace ciface::Core; + enum TokenType { TOK_WHITESPACE, @@ -166,6 +168,7 @@ class Expression virtual ControlState GetValue() const = 0; virtual void SetValue(ControlState state) = 0; virtual int CountNumControls() const = 0; + virtual Device::FocusFlags GetFocusFlags() const { return Device::FocusFlags::Default; } virtual void UpdateReferences(ControlEnvironment& finder) = 0; }; diff --git a/Source/Core/InputCommon/ControlReference/FunctionExpression.h b/Source/Core/InputCommon/ControlReference/FunctionExpression.h index 3b29baa9ae20..e710cd9495ac 100644 --- a/Source/Core/InputCommon/ControlReference/FunctionExpression.h +++ b/Source/Core/InputCommon/ControlReference/FunctionExpression.h @@ -15,6 +15,7 @@ namespace ciface::ExpressionParser { +// if input > this, then it passes constexpr ControlState CONDITION_THRESHOLD = 0.5; class FunctionExpression : public Expression @@ -32,6 +33,7 @@ class FunctionExpression : public Expression using ArgumentValidation = std::variant; int CountNumControls() const override; + Device::FocusFlags GetFocusFlags() const override; void UpdateReferences(ControlEnvironment& env) override; ArgumentValidation SetArguments(std::vector>&& args); diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp index 881be394711d..ed4f65bc6774 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp @@ -29,7 +29,7 @@ AnalogStick::AnalogStick(const char* const name_, const char* const ui_name_, for (auto& named_direction : named_directions) AddInput(Translate, named_direction); - AddInput(Translate, _trans("Modifier")); + AddInput(Translate, _trans("Modifier"), 0.5); } AnalogStick::ReshapeData AnalogStick::GetReshapableState(bool adjusted) diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp index 286244539259..142fae45fd72 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp @@ -44,7 +44,7 @@ void ControlGroup::AddDeadzoneSetting(SettingValue* value, double maximu // i18n: The percent symbol. _trans("%"), // i18n: Refers to the dead-zone setting of gamepad inputs. - _trans("Input strength to ignore.")}, + _trans("Circular input strength to ignore and remap.")}, 0, 0, maximum_deadzone); } @@ -72,7 +72,7 @@ void ControlGroup::LoadConfig(IniFile::Section* sec, const std::string& defdev, } // range - sec->Get(group + c->name + "/Range", &c->control_ref->range, 100.0); + sec->Get(group + c->name + "/Range", &c->control_ref->range, c->control_ref->default_range * 100.0); c->control_ref->range /= 100; } @@ -153,19 +153,26 @@ void ControlGroup::SetControlExpression(int index, const std::string& expression controls.at(index)->control_ref->SetExpression(expression); } -void ControlGroup::AddInput(Translatability translate, std::string name_) +void ControlGroup::AddInput(Translatability translate, std::string name_, ControlState range) { controls.emplace_back(std::make_unique(translate, std::move(name_))); + controls.back().get()->control_ref->default_range = range; + controls.back().get()->control_ref->range = range; } -void ControlGroup::AddInput(Translatability translate, std::string name_, std::string ui_name_) +void ControlGroup::AddInput(Translatability translate, std::string name_, std::string ui_name_, + ControlState range) { controls.emplace_back(std::make_unique(translate, std::move(name_), std::move(ui_name_))); + controls.back().get()->control_ref->default_range = range; + controls.back().get()->control_ref->range = range; } -void ControlGroup::AddOutput(Translatability translate, std::string name_) +void ControlGroup::AddOutput(Translatability translate, std::string name_, ControlState range) { controls.emplace_back(std::make_unique(translate, std::move(name_))); + controls.back().get()->control_ref->default_range = range; + controls.back().get()->control_ref->range = range; } } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h index 45c2367c101b..390b7f898608 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h @@ -69,9 +69,10 @@ class ControlGroup void SetControlExpression(int index, const std::string& expression); - void AddInput(Translatability translate, std::string name); - void AddInput(Translatability translate, std::string name, std::string ui_name); - void AddOutput(Translatability translate, std::string name); + void AddInput(Translatability translate, std::string name, ControlState range = 1.0); + void AddInput(Translatability translate, std::string name, std::string ui_name, + ControlState range = 1.0); + void AddOutput(Translatability translate, std::string name, ControlState range = 1.0); template void AddSetting(SettingValue* value, const NumericSettingDetails& details, diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h index 562313bb45c3..0c41cd6a7797 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h @@ -12,6 +12,11 @@ namespace ControllerEmu { +// Instead of having two instances of this, we have one with two states, +// one for the UI and one for the game. Otherwise when bringing up the +// input settings, it would also affect the cursor state in the game. +// This is not a problem with other ReshapableInput as they don't +// cache a state class Cursor : public ReshapableInput { public: @@ -28,7 +33,9 @@ class Cursor : public ReshapableInput ReshapeData GetReshapableState(bool adjusted) final override; ControlState GetGateRadiusAtAngle(double ang) const override; - StateData GetState(bool adjusted); + StateData GetState(float absolute_time_elapsed, bool is_ui); + + void ResetState(bool is_ui); // Yaw movement in radians. ControlState GetTotalYaw() const; @@ -47,22 +54,21 @@ class Cursor : public ReshapableInput static constexpr int AUTO_HIDE_MS = 2500; static constexpr double AUTO_HIDE_DEADZONE = 0.001; - // Not adjusted by width/height/center: - StateData m_state; - - // Adjusted: - StateData m_prev_result; + // Not adjusted by width/height/center. + StateData m_state[2]; + StateData m_prev_result[2]; - int m_auto_hide_timer = AUTO_HIDE_MS; + int m_auto_hide_timer[2] = {AUTO_HIDE_MS, AUTO_HIDE_MS}; using Clock = std::chrono::steady_clock; - Clock::time_point m_last_update; + Clock::time_point m_last_update[2]; SettingValue m_yaw_setting; SettingValue m_pitch_setting; SettingValue m_vertical_offset_setting; SettingValue m_relative_setting; + SettingValue m_relative_absolute_time_setting; SettingValue m_autohide_setting; }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp index b5408cfe34eb..c30011335ddd 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp @@ -137,7 +137,7 @@ std::optional IMUGyroscope::GetState() const auto state = GetRawState(); // If the input gate is disabled, miscalibration to zero values would occur. - if (ControlReference::GetInputGate()) + if (InputReference::GetInputGate()) UpdateCalibration(state); state -= m_calibration; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp index 69739bc330d2..d39fcf3a3f14 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp @@ -23,7 +23,7 @@ Tilt::Tilt(const std::string& name_) : ReshapableInput(name_, name_, GroupType:: AddInput(Translate, _trans("Left")); AddInput(Translate, _trans("Right")); - AddInput(Translate, _trans("Modifier")); + AddInput(Translate, _trans("Modifier"), 0.5); AddSetting(&m_max_angle_setting, {_trans("Angle"), diff --git a/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h b/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h index 86bd2e39c34f..25c4ab52c49e 100644 --- a/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h +++ b/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h @@ -165,10 +165,7 @@ class SettingValue public: ValueType GetValue() const { - // Only update dynamic values when the input gate is enabled. - // Otherwise settings will all change to 0 when window focus is lost. - // This is very undesirable for things like battery level or attached extension. - if (!IsSimpleValue() && ControlReference::GetInputGate()) + if (!IsSimpleValue()) m_value = m_input.GetState(); return m_value; @@ -189,7 +186,8 @@ class SettingValue mutable std::atomic m_value = {}; // Unfortunately InputReference's state grabbing is non-const requiring mutable here. - mutable InputReference m_input; + // Use IgnoreGateInputReference so we don't lose the setting when we lose focus. + mutable IgnoreGateInputReference m_input; }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.cpp b/Source/Core/InputCommon/ControllerEmu/StickGate.cpp index 797d19aadfc6..85cf48152f6a 100644 --- a/Source/Core/InputCommon/ControllerEmu/StickGate.cpp +++ b/Source/Core/InputCommon/ControllerEmu/StickGate.cpp @@ -128,7 +128,7 @@ std::optional SquareStickGate::GetIdealCalibrationSampleCount() const ReshapableInput::ReshapableInput(std::string name_, std::string ui_name_, GroupType type_) : ControlGroup(std::move(name_), std::move(ui_name_), type_) { - AddDeadzoneSetting(&m_deadzone_setting, 50); + AddDeadzoneSetting(&m_deadzone_setting, 75); } ControlState ReshapableInput::GetDeadzoneRadiusAtAngle(double angle) const @@ -280,11 +280,14 @@ void ReshapableInput::SaveConfig(IniFile::Section* section, const std::string& d } ReshapableInput::ReshapeData ReshapableInput::Reshape(ControlState x, ControlState y, - ControlState modifier) + ControlState modifier, ControlState clamp) { x -= m_center.x; y -= m_center.y; + // We run this even if both x and y will be zero. + // The angle value will be random but dist will stay 0 + // TODO: make the AtAngle functions work with negative angles: ControlState angle = std::atan2(y, x) + MathUtil::TAU; @@ -308,11 +311,7 @@ ReshapableInput::ReshapeData ReshapableInput::Reshape(ControlState x, ControlSta // This is affected by the modifier's "range" setting which defaults to 50%. if (modifier) { - // TODO: Modifier's range setting gets reset to 100% when the clear button is clicked. - // This causes the modifier to not behave how a user might suspect. - // Retaining the old scale-by-50% behavior until range is fixed to clear to 50%. - dist *= 0.5; - // dist *= modifier; + dist *= modifier; } // Apply deadzone as a percentage of the user-defined calibration shape/size: @@ -321,8 +320,8 @@ ReshapableInput::ReshapeData ReshapableInput::Reshape(ControlState x, ControlSta // Scale to the gate shape/radius: dist *= gate_max_dist; - return {std::clamp(std::cos(angle) * dist, -1.0, 1.0), - std::clamp(std::sin(angle) * dist, -1.0, 1.0)}; + return {std::clamp(std::cos(angle) * dist, -clamp, clamp), + std::clamp(std::sin(angle) * dist, -clamp, clamp)}; } } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.h b/Source/Core/InputCommon/ControllerEmu/StickGate.h index 8a0a0ab100ad..c6f43e8444b2 100644 --- a/Source/Core/InputCommon/ControllerEmu/StickGate.h +++ b/Source/Core/InputCommon/ControllerEmu/StickGate.h @@ -108,7 +108,7 @@ class ReshapableInput : public ControlGroup void SetCenter(ReshapeData center); protected: - ReshapeData Reshape(ControlState x, ControlState y, ControlState modifier = 0.0); + ReshapeData Reshape(ControlState x, ControlState y, ControlState modifier = 0.0, ControlState clamp = 1.0); private: void LoadConfig(IniFile::Section*, const std::string&, const std::string&) override; diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index fe176ce7f797..abd0d86e4383 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -93,6 +93,8 @@ void ControllerInterface::ChangeWindow(void* hwnd) void ControllerInterface::RefreshDevices() { + //if (true) //To finish + // return; if (!m_is_init) return; @@ -265,14 +267,16 @@ void ControllerInterface::RemoveDevice(std::functionUpdateInput(); + should_update_mouse_axis = false; } } diff --git a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h index c368578949de..c0b77839e03d 100644 --- a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h +++ b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h @@ -61,6 +61,40 @@ class Device virtual bool IsMatchingName(std::string_view name) const; }; + // A set of flags we can define to determine whether this control should be + // read (or written), or ignored/blocked, based on our current game app/window focus. + // As of now they are only used by inputs but nothing prevents us from implementing them + // on outputs, and they are not limited to focus, if we came up with new blocking conditions. + // + // These flags are per Control, but they will get summed up with other expressions + // from the same ControlReference. This is because checking them per input/ouput + // would have been too complicated, expensive, and ultimately, useless. + // So they are in order of priority, and some of them are mutually exclusive. + // Users can use function expressions to customize the focus requirements of a + // ControlReference, but we manually hardcode them in some Inputs (e.g. mouse) so + // users don't have to bother in most cases, as it's easy to understand as a concept. + enum class FocusFlags : u8 + { + // The input is only passed if we have focus (or the user accepts + // background input) + RequireFocus = 0x01, + // The input is only passed if we have "full" focus, which means the mouse + // has been locked into the game window. Ignored if mouse locking is off + RequireFullFocus = 0x02, + // Some inputs are able to make you lose or gain focus or full focus, + // for example a mouse click, or the Windows key. When these are pressed + // and there is a window focus change, ignore them for the time being, + // as the user didn't want the application to react + IgnoreOnFocusChanged = 0x04, + // Forces the input to be passed even if we have no focus, + // useful for things like battery level. This is not 0 because it needs + // higher priority over other flags + IgnoreFocus = 0x80, + + // Even expressions that are fixed should return this + Default = RequireFocus + }; + // // Input // @@ -73,15 +107,17 @@ class Device // undesirable behavior in our mapping logic. virtual bool IsDetectable() const { return true; } + virtual FocusFlags GetFocusFlags() const { return FocusFlags::Default; } + // Implementations should return a value from 0.0 to 1.0 across their normal range. // One input should be provided for each "direction". (e.g. 2 for each axis) - // If possible, negative values may be returned in situations where an opposing input is - // activated. (e.g. When an underlying axis, X, is currently negative, "Axis X-", will return a - // positive value and "Axis X+" may return a negative value.) + // If possible, negative values may be returned in situations where an opposing input + // is activated. (e.g. When an underlying axis, X, is currently negative, "Axis X-", + // will return a positive value and "Axis X+" may return a negative value.) // Doing so is solely to allow our input detection logic to better detect false positives. // This is necessary when making use of "FullAnalogSurface" as multiple inputs will be seen - // increasing from 0.0 to 1.0 as a user tries to map just one. The negative values provide a - // view of the underlying axis. (Negative values are clamped off before they reach + // increasing from 0.0 to 1.0 as a user tries to map just one. The negative values provide + // a view of the underlying axis. (Negative values are clamped off before they reach // expression-parser or controller-emu) virtual ControlState GetState() const = 0; diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp index 99767dee4cb3..90288a251d5a 100644 --- a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp @@ -125,6 +125,9 @@ class Device final : public Core::Device bool IsDetectable() const override { return false; } + // We don't need focus to pass the battery level + virtual FocusFlags GetFocusFlags() const { return FocusFlags::IgnoreFocus; } + private: const BatteryState& m_battery; }; diff --git a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h index 740954082be4..4884b26ebd0d 100644 --- a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h +++ b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h @@ -38,6 +38,10 @@ class KeyboardAndMouse : public Core::Device std::string GetName() const override; bool IsDetectable() const override { return false; } ControlState GetState() const override; + Flags GetFlags() const override + { + return (Flags)((u8)Flags::RequiresFocus | (u8)Flags::RequiresFullFocus); + } private: const float& m_axis; @@ -51,6 +55,11 @@ class KeyboardAndMouse : public Core::Device explicit Button(CGMouseButton button) : m_button(button) {} std::string GetName() const override; ControlState GetState() const override; + Flags GetFlags() const override + { + return (Flags)((u8)Flags::RequiresFocus | (u8)Flags::RequiresFullFocus | + (u8)Flags::IgnoreOnFocusAcquired); + } private: CGMouseButton m_button; diff --git a/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp b/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp index eaa56ef44918..9647d68c006b 100644 --- a/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp @@ -15,6 +15,8 @@ namespace ciface::WiimoteController { +using namespace ciface; + static constexpr char SOURCE_NAME[] = "Bluetooth"; static constexpr size_t IR_SENSITIVITY_LEVEL_COUNT = 5; @@ -39,7 +41,7 @@ class Button final : public Core::Device::Input }; // GetState returns value divided by supplied "extent". -template +template class GenericInput : public Core::Device::Input { public: @@ -50,6 +52,8 @@ class GenericInput : public Core::Device::Input bool IsDetectable() const override { return Detectable; } + Core::Device::FocusFlags GetFocusFlags() const override { return TFocusFlags; } + std::string GetName() const override { return m_name; } ControlState GetState() const final override { return ControlState(m_value) / m_extent; } @@ -63,8 +67,8 @@ class GenericInput : public Core::Device::Input template using AnalogInput = GenericInput; -template -using UndetectableAnalogInput = GenericInput; +template +using UndetectableAnalogInput = GenericInput; // GetName() is appended with '-' or '+' based on sign of "extent" value. template @@ -288,10 +292,13 @@ Device::Device(std::unique_ptr wiimote) : m_wiimote(std::m AddInput(new AnalogInput(&m_classic_state.triggers[1], classic_prefix + "R-Analog", 1.f)); // Specialty inputs: - AddInput(new UndetectableAnalogInput(&m_battery, "Battery", 1.f)); - AddInput(new UndetectableAnalogInput( + AddInput(new UndetectableAnalogInput( + &m_battery, "Battery", 1.f)); + AddInput(new UndetectableAnalogInput( &m_extension_number_input, "Attached Extension", WiimoteEmu::ExtensionNumber(1))); - AddInput(new UndetectableAnalogInput(&m_mplus_attached_input, "Attached MotionPlus", 1)); + AddInput(new UndetectableAnalogInput( + &m_mplus_attached_input, "Attached MotionPlus", 1)); AddOutput(new Motor(&m_rumble_level)); } diff --git a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp index 875cce47302c..f45e539ba674 100644 --- a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp +++ b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp @@ -119,6 +119,11 @@ class Battery final : public Core::Device::Input std::string GetName() const override { return "Battery"; } ControlState GetState() const override { return m_level; } bool IsDetectable() const override { return false; } + // We don't need focus to pass the battery level + virtual Core::Device::FocusFlags GetFocusFlags() const + { + return Core::Device::FocusFlags::IgnoreFocus; + } private: const ControlState& m_level; diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h index 71681a3e1054..f2de63cf2bbe 100644 --- a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h @@ -55,6 +55,11 @@ class KeyboardMouse : public Core::Device std::string GetName() const override { return name; } Button(unsigned int index, unsigned int* buttons); ControlState GetState() const override; + Flags GetFlags() const override + { + return (Flags)((u8)Flags::RequiresFocus | (u8)Flags::RequiresFullFocus | + (u8)Flags::IgnoreOnFocusAcquired); + } private: const unsigned int* m_buttons; @@ -69,6 +74,10 @@ class KeyboardMouse : public Core::Device bool IsDetectable() const override { return false; } Cursor(u8 index, bool positive, const float* cursor); ControlState GetState() const override; + Flags GetFlags() const override + { + return (Flags)((u8)Flags::RequiresFocus | (u8)Flags::RequiresFullFocus); + } private: const float* m_cursor; @@ -84,6 +93,10 @@ class KeyboardMouse : public Core::Device bool IsDetectable() const override { return false; } Axis(u8 index, bool positive, const float* axis); ControlState GetState() const override; + Flags GetFlags() const override + { + return (Flags)((u8)Flags::RequiresFocus | (u8)Flags::RequiresFullFocus); + } private: const float* m_axis; diff --git a/Source/DSPTool/StubHost.cpp b/Source/DSPTool/StubHost.cpp index 19c85a642b54..6dce41694bad 100644 --- a/Source/DSPTool/StubHost.cpp +++ b/Source/DSPTool/StubHost.cpp @@ -39,6 +39,10 @@ bool Host_RendererHasFocus() { return false; } +bool Host_RendererHasFullFocus() +{ + return false; +} bool Host_RendererIsFullscreen() { return false; diff --git a/Source/UnitTests/StubHost.cpp b/Source/UnitTests/StubHost.cpp index 4b9db8f03338..7a84a9ece971 100644 --- a/Source/UnitTests/StubHost.cpp +++ b/Source/UnitTests/StubHost.cpp @@ -43,6 +43,10 @@ bool Host_RendererHasFocus() { return false; } +bool Host_RendererHasFullFocus() +{ + return false; +} bool Host_RendererIsFullscreen() { return false; From d151525d277d516ed4b04f6608b82aac8af6f9e9 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sat, 25 Jul 2020 20:41:17 +0300 Subject: [PATCH 02/56] Update AudioPane.cpp UI fixes --- Source/Core/DolphinQt/Settings/AudioPane.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 39b27aa67e3b..071d806e4c28 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -175,11 +175,12 @@ void AudioPane::CreateWidgets() m_stretching_enable = new QCheckBox(tr("Enable Audio Stretching")); m_emu_speed_tolerance_slider = new QSlider(Qt::Horizontal); m_emu_speed_tolerance_indicator = new QLabel(); + m_emu_speed_tolerance_indicator->setAlignment(Qt::AlignRight); m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); game_audio_box->setLayout(game_audio_layout); m_emu_speed_tolerance_slider->setMinimum(-1); - m_emu_speed_tolerance_slider->setMaximum(300); + m_emu_speed_tolerance_slider->setMaximum(100); m_emu_speed_tolerance_slider->setToolTip( tr("Time (ms) we need to fall behind the emulation for sound to start slowing down. If set " "too high, sound will crackle when we slow down or stutter, if set too low, sound might " @@ -194,7 +195,6 @@ void AudioPane::CreateWidgets() "left for frame dips and if the audio playback loses alignment")); game_audio_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); - //game_audio_layout->addWidget(m_stretching_enable, 0, 1); //To review game_audio_layout->addWidget(m_emu_speed_tolerance_label, 1, 0); game_audio_layout->addWidget(m_emu_speed_tolerance_slider, 1, 1); game_audio_layout->addWidget(m_emu_speed_tolerance_indicator, 1, 2); @@ -305,13 +305,14 @@ void AudioPane::LoadSettings() // Stretch m_stretching_enable->setChecked(SConfig::GetInstance().m_audio_stretch); + m_ignore_save_settings = true; m_emu_speed_tolerance_slider->setValue(SConfig::GetInstance().m_audio_emu_speed_tolerance); if (m_emu_speed_tolerance_slider->value() < 0) m_emu_speed_tolerance_indicator->setText(tr("Disabled")); else m_emu_speed_tolerance_indicator->setText( tr("%1 ms").arg(m_emu_speed_tolerance_slider->value())); - m_emu_speed_tolerance_indicator->setAlignment(Qt::AlignRight); //To review + m_ignore_save_settings = false; #ifdef _WIN32 m_ignore_save_settings = true; @@ -411,8 +412,11 @@ void AudioPane::SaveSettings() // Stretch SConfig::GetInstance().m_audio_stretch = m_stretching_enable->isChecked(); SConfig::GetInstance().m_audio_emu_speed_tolerance = m_emu_speed_tolerance_slider->value(); - m_emu_speed_tolerance_indicator->setText( - tr("%1 ms").arg(SConfig::GetInstance().m_audio_emu_speed_tolerance)); + if (m_emu_speed_tolerance_slider->value() < 0) + m_emu_speed_tolerance_indicator->setText(tr("Disabled")); + else + m_emu_speed_tolerance_indicator->setText( + tr("%1 ms").arg(m_emu_speed_tolerance_slider->value())); #ifdef _WIN32 // If left at default, Dolphin will automatically From f817c63f865872cdaf192edb4a9f07f35fff5475 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sat, 25 Jul 2020 21:10:46 +0300 Subject: [PATCH 03/56] Fixed test value --- Source/Core/AudioCommon/Mixer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 3bd2aa080bc2..de0f1b1937dd 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -435,7 +435,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) // m_audio_stretch_max_latency if it was on. In the first case, the reason was to cache enough // samples to be able to withstand small hangs and changes in speed, but our new approach is to // play samples backwards when we are out of new ones so these are never problems. - double max_latency = Config::Get(Config::MAIN_AUDIO_MIXER_LATENCY) / 1000.0; //To read as low times as possible + double max_latency = Config::Get(Config::MAIN_AUDIO_MIXER_LATENCY) / 1000.0; //To read as low times as possible //To rename MAIN_AUDIO_MIXER_MAX_LATENCY //To double or disable when speed is unlimited (or if we can't reach target speed), to avoid constant fluctuations (the average speed will compensate temporary imprecisions anyway) if (!frame_limiter || m_time_below_target_speed_growing) { @@ -444,7 +444,6 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) } double catch_up_speed; double target_latency; - max_latency = 100000000000; //To delete if (stretching) { From 1eeadca732bd321828f02bde5b7d74e9332c1293 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 26 Jul 2020 00:10:29 +0300 Subject: [PATCH 04/56] Fix build errors and warnings --- Source/Core/AudioCommon/AudioCommon.cpp | 5 ++-- Source/Core/AudioCommon/AudioCommon.h | 3 ++ Source/Core/AudioCommon/AudioSpeedCounter.cpp | 28 +++++++++---------- Source/Core/AudioCommon/AudioSpeedCounter.h | 3 +- Source/Core/AudioCommon/AudioStretcher.h | 2 ++ Source/Core/AudioCommon/Mixer.cpp | 9 +++--- Source/Core/AudioCommon/Mixer.h | 2 +- Source/Core/Core/Config/MainSettings.cpp | 2 +- Source/Core/Core/Config/MainSettings.h | 2 +- .../Core/ConfigLoaders/IsSettingSaveable.cpp | 2 +- 10 files changed, 32 insertions(+), 26 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 4ae84647463e..fd57f5958a22 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -27,6 +27,7 @@ std::unique_ptr g_sound_stream; std::mutex g_sound_stream_mutex; +std::mutex g_sound_stream_running_mutex; namespace AudioCommon { @@ -133,7 +134,7 @@ std::string GetDefaultSoundBackend() #if defined ANDROID backend = BACKEND_OPENSLES; #elif defined __linux__ - if (AlsaSound::isValid()) + if (AlsaSound::IsValid()) backend = BACKEND_ALSA; #elif defined(__APPLE__) || defined(_WIN32) backend = BACKEND_CUBEB; @@ -324,8 +325,6 @@ void UpdateSoundStreamSettings(bool volume_only) } } -std::mutex g_sound_stream_running_mutex; - bool SetSoundStreamRunning(bool running, bool send_error) { // This can be called by the main thread while a previous diff --git a/Source/Core/AudioCommon/AudioCommon.h b/Source/Core/AudioCommon/AudioCommon.h index 8ac75a8ab0f5..ece28fbbb0a8 100644 --- a/Source/Core/AudioCommon/AudioCommon.h +++ b/Source/Core/AudioCommon/AudioCommon.h @@ -16,6 +16,9 @@ class Mixer; extern std::unique_ptr g_sound_stream; +extern std::mutex g_sound_stream_mutex; +extern std::mutex g_sound_stream_running_mutex; + namespace AudioCommon { void InitSoundStream(); diff --git a/Source/Core/AudioCommon/AudioSpeedCounter.cpp b/Source/Core/AudioCommon/AudioSpeedCounter.cpp index 9095c6ff5b00..2745c17333ef 100644 --- a/Source/Core/AudioCommon/AudioSpeedCounter.cpp +++ b/Source/Core/AudioCommon/AudioSpeedCounter.cpp @@ -1,4 +1,4 @@ -// Copyright 2008 Dolphin Emulator Project +// Copyright 2020 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. @@ -23,24 +23,24 @@ u64 AudioSpeedCounter::GetTime() const } double AudioSpeedCounter::GetTimeDelta(u64 old_time) const { - u64 time = GetTime(); - u64 delta = time - old_time; + const u64 time = GetTime(); + const u64 delta = time - old_time; return double(delta / TIME_CONVERSION); } double AudioSpeedCounter::GetTimeDeltaAndUpdateOldTime(std::atomic& old_time) const { - u64 time = GetTime(); - u64 delta = time - old_time; + const u64 time = GetTime(); + const u64 delta = time - old_time; old_time = time; return double(delta / TIME_CONVERSION); } void AudioSpeedCounter::OnSettingsChanged() { - double prev_target_delta = m_target_delta; + const double prev_target_delta = m_target_delta; m_target_delta = m_ticks_per_upd / m_ticks_per_sec; // Keep the previous speed by adjusting the last deltas - double relative_change = m_target_delta / prev_target_delta; + const double relative_change = m_target_delta / prev_target_delta; for (int i = 0; i < m_last_deltas.size(); ++i) { m_last_deltas[i] *= relative_change; @@ -66,7 +66,7 @@ void AudioSpeedCounter::Start(bool simulate_full_speed) if (simulate_full_speed) { m_cached_last_delta = m_target_delta; - size_t size = std::max(size_t(m_average_time / m_target_delta), size_t(1)); + const size_t size = std::max(size_t(m_average_time / m_target_delta), size_t(1)); m_last_deltas.resize(size, m_target_delta); } else @@ -82,7 +82,7 @@ void AudioSpeedCounter::Update(double elapsed_ticks) OnSettingsChanged(); } - double delta = GetTimeDeltaAndUpdateOldTime(m_last_time); + const double delta = GetTimeDeltaAndUpdateOldTime(m_last_time); // If this ended up being 0 it would be ignored m_cached_last_delta = delta; double total_delta = delta; @@ -106,7 +106,7 @@ double AudioSpeedCounter::GetLastSpeed(bool& in_out_predict, bool simulate_full_ { if (in_out_predict) { - double delta = GetTimeDelta(m_last_time); + const double delta = GetTimeDelta(m_last_time); // If it's currently late for a new update if (delta > m_target_delta) { @@ -131,7 +131,7 @@ double AudioSpeedCounter::GetAverageSpeed(bool predict, bool simulate_full_speed if (predict) { - double delta = GetTimeDelta(m_last_time); + const double delta = GetTimeDelta(m_last_time); // If it's currently late for a new update if (delta > m_target_delta) { @@ -168,7 +168,7 @@ double AudioSpeedCounter::GetCachedAverageSpeed(bool alternative_speed, bool pre if (predict) { - double delta = GetTimeDelta(m_last_time); + const double delta = GetTimeDelta(m_last_time); // If it's currently late for a new update if (delta > m_target_delta) { @@ -230,8 +230,8 @@ void AudioSpeedCounter::SetPaused(bool paused) } else { - s64 time = GetTime(); - s64 delta = time - m_last_paused_time; + const s64 time = GetTime(); + const s64 delta = time - m_last_paused_time; m_last_time += delta; } } diff --git a/Source/Core/AudioCommon/AudioSpeedCounter.h b/Source/Core/AudioCommon/AudioSpeedCounter.h index cc75701936df..0a481f95eab7 100644 --- a/Source/Core/AudioCommon/AudioSpeedCounter.h +++ b/Source/Core/AudioCommon/AudioSpeedCounter.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "Common/CommonTypes.h" @@ -46,8 +47,8 @@ struct AudioSpeedCounter std::atomic m_cached_last_delta{-1.0}; // The max time we keep deltas for double m_average_time; - double m_ticks_per_upd; double m_ticks_per_sec; + double m_ticks_per_upd; // The expected time elapsed between two updates double m_target_delta = 1.0; u64 m_last_paused_time = 0; diff --git a/Source/Core/AudioCommon/AudioStretcher.h b/Source/Core/AudioCommon/AudioStretcher.h index 50cdbdc27ac2..0de5d2ca24e7 100644 --- a/Source/Core/AudioCommon/AudioStretcher.h +++ b/Source/Core/AudioCommon/AudioStretcher.h @@ -8,6 +8,8 @@ #include +#include "Common/CommonTypes.h" + namespace AudioCommon { class AudioStretcher diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index de0f1b1937dd..53167d44d736 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -5,6 +5,7 @@ #include "AudioCommon/Mixer.h" //To try to delete some includes and see if it builds #include +#include #include #include @@ -238,8 +239,8 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r // Do the swaps once instead of processing them for each iteration below for (u32 k = first_indexR; k != last_indexR + direction * NC; k += direction * NC) { - interpolation_buffer[k + 0 & INDEX_MASK] = Common::swap16(m_buffer[k + 0 & INDEX_MASK]); - interpolation_buffer[k + 1 & INDEX_MASK] = Common::swap16(m_buffer[k + 1 & INDEX_MASK]); + interpolation_buffer[(k + 0) & INDEX_MASK] = Common::swap16(m_buffer[(k + 0) & INDEX_MASK]); + interpolation_buffer[(k + 1) & INDEX_MASK] = Common::swap16(m_buffer[(k + 1) & INDEX_MASK]); } // fract requested to be reset so sure it will be 0 in the first cycle @@ -435,7 +436,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) // m_audio_stretch_max_latency if it was on. In the first case, the reason was to cache enough // samples to be able to withstand small hangs and changes in speed, but our new approach is to // play samples backwards when we are out of new ones so these are never problems. - double max_latency = Config::Get(Config::MAIN_AUDIO_MIXER_LATENCY) / 1000.0; //To read as low times as possible //To rename MAIN_AUDIO_MIXER_MAX_LATENCY + double max_latency = Config::Get(Config::MAIN_AUDIO_MIXER_MAX_LATENCY) / 1000.0; //To read as little times as possible //To double or disable when speed is unlimited (or if we can't reach target speed), to avoid constant fluctuations (the average speed will compensate temporary imprecisions anyway) if (!frame_limiter || m_time_below_target_speed_growing) { @@ -928,7 +929,7 @@ void Mixer::MixerFifo::UpdatePush(double time) if (!SConfig::GetInstance().m_audio_stretch) { u32 num_samples = - (Config::Get(Config::MAIN_AUDIO_MIXER_LATENCY) / 1000.0) * 0.5 * m_input_sample_rate; + (Config::Get(Config::MAIN_AUDIO_MIXER_MAX_LATENCY) / 1000.0) * 0.5 * m_input_sample_rate; num_samples = std::min(num_samples, MAX_SAMPLES); memset(m_mixer->m_conversion_buffer, 0, num_samples * NC * sizeof(m_mixer->m_conversion_buffer[0])); diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 3ffa365125a6..5a03740cdfa1 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -6,7 +6,7 @@ #include #include -#include +#include #include "AudioCommon/AudioStretcher.h" #include "AudioCommon/AudioSpeedCounter.h" diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 6175e7fcc1e4..41264389a9d0 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -40,7 +40,7 @@ const Info MAIN_DPL2_QUALITY{{System::Main, "Core", "D AudioCommon::GetDefaultDPL2Quality()}; // TODO: rename "AudioBackendLatency", it's confusing const Info MAIN_AUDIO_BACKEND_LATENCY{{System::Main, "Core", "AudioLatency"}, 20}; -const Info MAIN_AUDIO_MIXER_LATENCY{{System::Main, "Core", "AudioMixerTargetLatency"}, 40}; +const Info MAIN_AUDIO_MIXER_MAX_LATENCY{{System::Main, "Core", "AudioMixerMaxLatency"}, 40}; const Info MAIN_AUDIO_STRETCH{{System::Main, "Core", "AudioStretch"}, false}; const Info MAIN_MEMCARD_A_PATH{{System::Main, "Core", "MemcardAPath"}, ""}; const Info MAIN_MEMCARD_B_PATH{{System::Main, "Core", "MemcardBPath"}, ""}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 7a1b84783f56..53279186a80d 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -40,7 +40,7 @@ extern const Info MAIN_OVERRIDE_REGION_SETTINGS; extern const Info MAIN_DPL2_DECODER; extern const Info MAIN_DPL2_QUALITY; extern const Info MAIN_AUDIO_BACKEND_LATENCY; -extern const Info MAIN_AUDIO_MIXER_LATENCY; +extern const Info MAIN_AUDIO_MIXER_MAX_LATENCY; extern const Info MAIN_AUDIO_STRETCH; extern const Info MAIN_MEMCARD_A_PATH; extern const Info MAIN_MEMCARD_B_PATH; diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index 4fc1a82ff4d3..a474c56708df 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -44,7 +44,7 @@ bool IsSettingSaveable(const Config::Location& config_location) &Config::MAIN_ALLOW_SD_WRITES.GetLocation(), &Config::MAIN_DPL2_DECODER.GetLocation(), &Config::MAIN_DPL2_QUALITY.GetLocation(), - &Config::MAIN_AUDIO_MIXER_LATENCY.location, + &Config::MAIN_AUDIO_MIXER_MAX_LATENCY.location, &Config::MAIN_RAM_OVERRIDE_ENABLE.GetLocation(), &Config::MAIN_MEM1_SIZE.GetLocation(), &Config::MAIN_MEM2_SIZE.GetLocation(), From f0c9017fa0def1a15dcd591023ed9b6d7954c2d7 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 26 Jul 2020 00:33:37 +0300 Subject: [PATCH 05/56] Fixing --- Source/Core/AudioCommon/Mixer.cpp | 2 +- Source/Core/AudioCommon/SurroundDecoder.cpp | 4 ++- Source/Core/Core/HW/WiimoteEmu/Speaker.cpp | 11 -------- Source/Core/DolphinQt/Settings/AudioPane.cpp | 28 ++++++++++---------- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 53167d44d736..75871bac09a4 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -76,7 +76,7 @@ void Mixer::SetSampleRate(u32 sample_rate) m_sample_rate = sample_rate; m_stretcher.SetSampleRate(m_sample_rate); m_surround_decoder.SetSampleRate(m_sample_rate); - //To review: is this thread safe between the game and emu thread? + //To review: is this thread safe between the game and emu thread? Does it even make sense to do? Not after the first time if (!m_dma_speed.IsPaused()) m_dma_speed.Start(true); //To do: reset m_fract, and also DPLII. Add method to reset DPLII diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 84db0e367ef5..7c90ccc160b0 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -46,8 +46,10 @@ void SurroundDecoder::Clear() void SurroundDecoder::SetSampleRate(u32 sample_rate) { + m_fsdecoder = std::make_unique(); + m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate); //To finish, also set DPL2QualityToFrameBlockSize and fix it by sample rate - // With "FreeSurroundDecoder" there is no need of setting the sample_rate, it's only used + // We can't change the block size after starting unfortunately, old samples will be lost (maybe we can...) } // Currently only 6 channels are supported. diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp index bd68dff2fd4f..c0493eec5dbc 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp @@ -112,17 +112,6 @@ void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan) samples[i * 2] = adpcm_yamaha_expand_nibble(adpcm_state, data[i] >> 4); samples[i * 2 + 1] = adpcm_yamaha_expand_nibble(adpcm_state, data[i] & 0xf); } - //for (int i = length; i > 0; --i) - //{ - // samples[(length - i) * 2] = adpcm_yamaha_expand_nibble(adpcm_state, data[i] & 0xf); - // samples[(length - i) * 2 + 1] = adpcm_yamaha_expand_nibble(adpcm_state, data[i] >> 4); - //} - //for (int n = length; n > 0; n--) - //{ - // u8 v = bytestream2_get_byteu(&gb); - // *samples++ = adpcm_yamaha_expand_nibble(&c->status, v & 0x0F); - // *samples++ = adpcm_yamaha_expand_nibble(&c->status, v >> 4); - //} // Following details from http://wiibrew.org/wiki/Wiimote#Speaker sample_rate_dividend = 6000000; diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 071d806e4c28..de3582213759 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -300,18 +300,20 @@ void AudioPane::LoadSettings() m_ignore_save_settings = false; } + m_ignore_save_settings = true; + // Sample rate m_use_os_sample_rate->setChecked(SConfig::GetInstance().bUseOSMixerSampleRate); // Stretch m_stretching_enable->setChecked(SConfig::GetInstance().m_audio_stretch); - m_ignore_save_settings = true; m_emu_speed_tolerance_slider->setValue(SConfig::GetInstance().m_audio_emu_speed_tolerance); if (m_emu_speed_tolerance_slider->value() < 0) m_emu_speed_tolerance_indicator->setText(tr("Disabled")); else m_emu_speed_tolerance_indicator->setText( tr("%1 ms").arg(m_emu_speed_tolerance_slider->value())); + m_ignore_save_settings = false; #ifdef _WIN32 @@ -442,7 +444,7 @@ void AudioPane::SaveSettings() std::string deviceSampleRate = "0"; - bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default" && !m_running; + bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default"; if (m_wasapi_device_sample_rate_combo->currentIndex() > 0 && canSelectDeviceSampleRate) { QString qs = m_wasapi_device_sample_rate_combo->currentText(); @@ -450,9 +452,10 @@ void AudioPane::SaveSettings() deviceSampleRate = qs.toStdString(); } - if (!m_running) + if (SConfig::GetInstance().sWASAPIDeviceSampleRate != deviceSampleRate) { SConfig::GetInstance().sWASAPIDeviceSampleRate = deviceSampleRate; + backend_setting_changed = true; } #endif @@ -527,7 +530,7 @@ void AudioPane::OnWASAPIDeviceChanged() // Don't allow users to select a sample rate for the default device, // even though that would be possible, the default device can change // at any time so it wouldn't make sense - bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default" && !m_running; + bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default"; if (canSelectDeviceSampleRate) { m_wasapi_device_sample_rate_combo->setEnabled(true); @@ -570,7 +573,7 @@ void AudioPane::OnWASAPIDeviceChanged() void AudioPane::LoadWASAPIDeviceSampleRate() { - bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default" && !m_running; + bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default"; if (SConfig::GetInstance().sWASAPIDeviceSampleRate == "0" || !canSelectDeviceSampleRate) { m_wasapi_device_sample_rate_combo->setCurrentIndex(0); @@ -624,19 +627,16 @@ void AudioPane::OnEmulationStateChanged(bool running) #ifdef _WIN32 m_wasapi_device_label->setEnabled(!running || backend_supports_runtime_changes); m_wasapi_device_combo->setEnabled(!running || backend_supports_runtime_changes); - bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default" && !m_running; - m_wasapi_device_sample_rate_label->setEnabled(!running && canSelectDeviceSampleRate); - m_wasapi_device_sample_rate_combo->setEnabled(!running && canSelectDeviceSampleRate); //To allow changes at runtime now? + bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default"; + m_wasapi_device_sample_rate_label->setEnabled(canSelectDeviceSampleRate); + m_wasapi_device_sample_rate_combo->setEnabled(canSelectDeviceSampleRate); //To allow changes at runtime now? bool is_wasapi = backend == BACKEND_WASAPI; if (is_wasapi) { OnWASAPIDeviceChanged(); - if (!running) - { - m_ignore_save_settings = true; - LoadWASAPIDeviceSampleRate(); - m_ignore_save_settings = false; - } + m_ignore_save_settings = true; + LoadWASAPIDeviceSampleRate(); + m_ignore_save_settings = false; } #endif } From 784334acca199464d13e6a0ace68ce0813fdc743 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 26 Jul 2020 01:52:01 +0300 Subject: [PATCH 06/56] const --- Source/Core/Core/HW/WiimoteEmu/Speaker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp index c0493eec5dbc..64ea46af3561 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp @@ -144,8 +144,8 @@ void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan) // because you can lower their volume from the wii settings, and because they are // already extremely low quality, so any additional quality loss isn't welcome. speaker_pan = std::clamp(speaker_pan, -1.f, 1.f); - u32 l_volume = std::min(u32(std::min(1.f - speaker_pan, 1.f) * volume), 255u); - u32 r_volume = std::min(u32(std::min(1.f + speaker_pan, 1.f) * volume), 255u); + const u32 l_volume = std::min(u32(std::min(1.f - speaker_pan, 1.f) * volume), 255u); + const u32 r_volume = std::min(u32(std::min(1.f + speaker_pan, 1.f) * volume), 255u); g_sound_stream->GetMixer()->SetWiimoteSpeakerVolume(m_index, l_volume, r_volume); From 9ebae28bed23108fab6606546a02b36499fbb714 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 26 Jul 2020 12:01:08 +0300 Subject: [PATCH 07/56] Stuff --- Source/Core/AudioCommon/AudioCommon.cpp | 2 +- Source/Core/AudioCommon/Mixer.cpp | 22 +++------- Source/Core/DolphinQt/Settings/AudioPane.cpp | 44 ++++++++++---------- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index fd57f5958a22..297554523720 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -185,7 +185,7 @@ bool SupportsLatencyControl(std::string_view backend) // TODO: we should ask the backends whether they support this #ifdef _WIN32 // Cubeb only supports custom latency on Windows - return backend == BACKEND_OPENAL || backend == BACKEND_WASAPI || backend == BACKEND_CUBEB; + return backend == BACKEND_OPENAL || backend == BACKEND_WASAPI || backend == BACKEND_CUBEB; //To review: not true #else return false; #endif diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 75871bac09a4..18c8ad25276b 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -47,6 +47,7 @@ Mixer::~Mixer() void Mixer::SetPaused(bool paused) { + //To review: this might not be thread safe? SetPaused is called from the main thread while the DMA is already pushing from the emu thread if (!m_dma_speed.IsPaused() && !paused) { // This happens on game start. The backend has already started reading actually @@ -64,7 +65,7 @@ void Mixer::DoState(PointerWrap& p) m_wiimote_speaker_mixer[2].DoState(p); m_wiimote_speaker_mixer[3].DoState(p); - if (p.GetMode() == PointerWrap::MODE_READ) + if (p.GetMode() == PointerWrap::MODE_READ) //To review, needs to be a state { m_dma_speed.SetTicksPerSecond(m_dma_mixer.GetInputSampleRate()); // We could reset a few things here but it would require too much thread synchronization @@ -76,9 +77,6 @@ void Mixer::SetSampleRate(u32 sample_rate) m_sample_rate = sample_rate; m_stretcher.SetSampleRate(m_sample_rate); m_surround_decoder.SetSampleRate(m_sample_rate); - //To review: is this thread safe between the game and emu thread? Does it even make sense to do? Not after the first time - if (!m_dma_speed.IsPaused()) - m_dma_speed.Start(true); //To do: reset m_fract, and also DPLII. Add method to reset DPLII } @@ -139,11 +137,6 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) m_fract = -1.0; } - // Padding (mix the remaining samples with the our last sample in case we didn't have enough) - // indexR is always "INTERP_SAMPLES" away from indexW, - // so pad by the current indexR, which was the biggest influence - // against the cubic interpolation of this frame - s32 behind_samples = num_samples - actual_samples_count; // This might sound bad if we are constantly missing a few samples, but that should never happen, // and we couldn't predict it anyway (meaning we should start playing backwards as soon as we can) @@ -190,8 +183,9 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) CubicInterpolation(back_samples, behind_samples, rate, m_backwards_indexR, indexW, s[0], s[1], lVolume, rVolume, false); } - // If we are not constantly pushed, we always append some silent samples at the end of our buffer, - // so there won't be any need of panning until new samples are pushed + // Padding (constantly pushing the last sample when we run out to avoid sudden changes in the + // audio wave). This is only needed on mixers that don't constantly push but are currntly pushing, + // as they can't play samples backwards else if (behind_samples > 0 && (m_constantly_pushed || m_currently_pushed)) { if (indexW > 8) OSD::AddMessage("Behind samples: " + std::to_string(behind_samples), 0U); @@ -938,11 +932,7 @@ void Mixer::MixerFifo::UpdatePush(double time) } else { - //To do this: increase indexW by 4 more every time, with 4 samples of silence, if when new - //samples are pushed, we haven't finished reading (indexR), then we take out the last 4 samples - //(doesn't work with sound stretching). Or save a bool which will increase indexR by 4 - - OSD::AddMessage("last sample: " + std::to_string(m_buffer[(m_indexW - 2) & INDEX_MASK]), 0U); + //OSD::AddMessage("last sample: " + std::to_string(m_buffer[(m_indexW - 2) & INDEX_MASK]), 0U); constexpr u32 num_samples = INTERP_SAMPLES + 1; // Add enough samples of silence to make sure when it has finished reading it won't stop on a diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index de3582213759..4555e5d38eb0 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -97,7 +97,7 @@ void AudioPane::CreateWidgets() //To review: is this not visible? //To specify that it's the Target/min. A good one is from 3 to 40. It might not work on systems with not many CPU cores m_latency_spin->setToolTip(tr("Target latency (in ms). Higher values may reduce audio" - " crackling.\nCertain backends only. Values above 20ms are not suggested")); + " crackling.\nCertain backends only. Values above 20ms are not suggested.")); } m_use_os_sample_rate = new QCheckBox(tr("Use OS Mixer sample rate")); @@ -116,7 +116,7 @@ void AudioPane::CreateWidgets() tr("Enables Dolby Pro Logic II emulation using 5.1 surround.\nCertain backends and DPS " "emulation engines only.\nAutomatically disabled if not supported by your audio device." "\nAs on the original hardware, games will still be stereo,\nbut DPLII will" - " extrapolate information to output 5.1 channels.\nIf unsure, leave off. When changing at runtime, you will lose samples. You need to enable it in game for GC or in menu of Wii")); + " extrapolate information to output 5.1 channels.\nIf unsure, leave off. When changing at runtime, you will lose samples. You need to enable it in game for GC or in menu of Wii.")); auto* dolby_quality_layout = new QHBoxLayout; @@ -158,7 +158,7 @@ void AudioPane::CreateWidgets() tr("This will not only output at the specified sample rate, \nit will also run the internal " "mixer at that same sample rate.\nSelecting anything above 48kHz will have very minimal " "advanced stretching won't work if you don't see a high (or low?) enough backend latency?" - "improvements on sound quality, at the cost of performance.\nAdvantages will only be seen on 32kHz games or if you unlimit the FPS.\nThis is for 2 channel 16bit, surround will try to use the same or fallback to 48000")); + "improvements on sound quality, at the cost of performance.\nAdvantages will only be seen on 32kHz games or if you unlimit the FPS.\nThis is for 2 channel 16bit, surround will try to use the same or fallback to 48000.")); backend_layout->addRow(m_wasapi_device_label, m_wasapi_device_combo); backend_layout->addRow(m_wasapi_device_sample_rate_label, m_wasapi_device_sample_rate_combo); @@ -170,34 +170,32 @@ void AudioPane::CreateWidgets() backend_layout->addRow(dolby_quality_layout); backend_layout->addRow(m_dolby_quality_latency_label); - auto* game_audio_box = new QGroupBox(tr("Game Audio Settings")); - auto* game_audio_layout = new QGridLayout; - m_stretching_enable = new QCheckBox(tr("Enable Audio Stretching")); + auto* mixer_box = new QGroupBox(tr("Mixer Settings")); + auto* mixer_layout = new QGridLayout; + m_stretching_enable = new QCheckBox(tr("Audio Stretching")); m_emu_speed_tolerance_slider = new QSlider(Qt::Horizontal); m_emu_speed_tolerance_indicator = new QLabel(); m_emu_speed_tolerance_indicator->setAlignment(Qt::AlignRight); m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); - game_audio_box->setLayout(game_audio_layout); + mixer_box->setLayout(mixer_layout); + + m_stretching_enable->setToolTip( + tr("Enables stretching of the audio (pitch correction) to match the emulation speed")); m_emu_speed_tolerance_slider->setMinimum(-1); m_emu_speed_tolerance_slider->setMaximum(100); m_emu_speed_tolerance_slider->setToolTip( - tr("Time (ms) we need to fall behind the emulation for sound to start slowing down. If set " - "too high, sound will crackle when we slow down or stutter, if set too low, sound might " - "lose quality if you have constant small stutters. Leave around 15ms if unsure. Slide all " - "the way left to disable. Below 10 is unsuggested unless you always want to enabled")); - - m_stretching_enable->setToolTip( - tr("Enables stretching of the audio to match emulation speed (pitch correction)." - "\nA backend latency of at least 32ms is suggested to avoid" - "\nloss of quality. Having None will cause audio to stop if you dip FPS, but it will be " - "of max quality other times. Simple add an internal buffer/latency to have some samples " - "left for frame dips and if the audio playback loses alignment")); + tr("Time(ms) we need to fall behind the emulation for sound to start using the actual " + "emulation speed.\nIf set " + "too high (>40), sound will lose quality when we slow down or stutter.\nIf set too low (<10), " + "sound might " + "lose quality if you have frequent small stutters.\nSet 0 to " + "have it on all the times. Slide all the way left to disable.")); //To find best default - game_audio_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); - game_audio_layout->addWidget(m_emu_speed_tolerance_label, 1, 0); - game_audio_layout->addWidget(m_emu_speed_tolerance_slider, 1, 1); - game_audio_layout->addWidget(m_emu_speed_tolerance_indicator, 1, 2); + mixer_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); + mixer_layout->addWidget(m_emu_speed_tolerance_label, 1, 0); + mixer_layout->addWidget(m_emu_speed_tolerance_slider, 1, 1); + mixer_layout->addWidget(m_emu_speed_tolerance_indicator, 1, 2); m_main_layout = new QGridLayout; @@ -208,7 +206,7 @@ void AudioPane::CreateWidgets() m_main_layout->addWidget(dsp_box, 0, 0); m_main_layout->addWidget(volume_box, 0, 1, -1, 1); m_main_layout->addWidget(backend_box, 1, 0); - m_main_layout->addWidget(game_audio_box, 2, 0); + m_main_layout->addWidget(mixer_box, 2, 0); setLayout(m_main_layout); } From d095b32ec2fe0aa506a41307af2cb68171da5427 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 26 Jul 2020 15:31:17 +0300 Subject: [PATCH 08/56] Surround fixes --- .../source/FreeSurroundDecoder.cpp | 66 +++++---- Source/Core/AudioCommon/AudioCommon.cpp | 2 +- Source/Core/AudioCommon/AudioStretcher.cpp | 15 +- Source/Core/AudioCommon/AudioStretcher.h | 2 +- Source/Core/AudioCommon/CubebStream.cpp | 2 +- Source/Core/AudioCommon/Mixer.cpp | 47 +++++-- Source/Core/AudioCommon/Mixer.h | 7 +- Source/Core/AudioCommon/OpenALStream.cpp | 2 +- Source/Core/AudioCommon/SurroundDecoder.cpp | 129 ++++++++++++------ Source/Core/AudioCommon/SurroundDecoder.h | 23 ++-- Source/Core/AudioCommon/WASAPIStream.cpp | 8 +- Source/Core/Common/FixedSizeQueue.h | 2 +- Source/Core/Core/Config/MainSettings.cpp | 1 + Source/Core/Core/Config/MainSettings.h | 1 + .../Core/ConfigLoaders/IsSettingSaveable.cpp | 3 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 28 ++-- 16 files changed, 212 insertions(+), 126 deletions(-) diff --git a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp index b5db168f8fa2..f77e1974dea5 100644 --- a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp +++ b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp @@ -39,45 +39,43 @@ DPL2FSDecoder::~DPL2FSDecoder() { void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, unsigned int sample_rate) { - if (!initialized) { - setup = chsetup; - N = blsize; - samplerate = sample_rate; + setup = chsetup; + N = blsize; + samplerate = sample_rate; - // Initialize the parameters - wnd = std::vector(N); - inbuf = std::vector(3 * N); - lt = std::vector(N); - rt = std::vector(N); - dst = std::vector(N); - lf = std::vector(N / 2 + 1); - rf = std::vector(N / 2 + 1); - forward = kiss_fftr_alloc(N, 0, 0, 0); - inverse = kiss_fftr_alloc(N, 1, 0, 0); - C = static_cast(chn_alloc[setup].size()); + // Initialize the parameters + wnd = std::vector(N); + inbuf = std::vector(3 * N); + lt = std::vector(N); + rt = std::vector(N); + dst = std::vector(N); + lf = std::vector(N / 2 + 1); + rf = std::vector(N / 2 + 1); + forward = kiss_fftr_alloc(N, 0, 0, 0); + inverse = kiss_fftr_alloc(N, 1, 0, 0); + C = static_cast(chn_alloc[setup].size()); - // Allocate per-channel buffers - outbuf.resize((N + N / 2) * C); - signal.resize(C, std::vector(N)); + // Allocate per-channel buffers + outbuf.resize((N + N / 2) * C); + signal.resize(C, std::vector(N)); - // Init the window function - for (unsigned int k = 0; k < N; k++) - wnd[k] = sqrt(0.5 * (1 - cos(2 * pi * k / N)) / N); + // Init the window function + for (unsigned int k = 0; k < N; k++) + wnd[k] = sqrt(0.5 * (1 - cos(2 * pi * k / N)) / N); - // set default parameters - set_circular_wrap(90); - set_shift(0); - set_depth(1); - set_focus(0); - set_center_image(1); - set_front_separation(1); - set_rear_separation(1); - set_low_cutoff(40.0f / (samplerate / 2.f)); - set_high_cutoff(90.0f / (samplerate / 2.f)); - set_bass_redirection(false); + // set default parameters + set_circular_wrap(90); + set_shift(0); + set_depth(1); + set_focus(0); + set_center_image(1); + set_front_separation(1); + set_rear_separation(1); + set_low_cutoff(40.0f / (samplerate / 2.f)); + set_high_cutoff(90.0f / (samplerate / 2.f)); + set_bass_redirection(false); - initialized = true; - } + initialized = true; } // decode a stereo chunk, produces a multichannel chunk of the same size diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 297554523720..bfebbdb0d842 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -144,7 +144,7 @@ std::string GetDefaultSoundBackend() DPL2Quality GetDefaultDPL2Quality() { - return DPL2Quality::High; + return DPL2Quality::Low; } std::vector GetSoundBackends() diff --git a/Source/Core/AudioCommon/AudioStretcher.cpp b/Source/Core/AudioCommon/AudioStretcher.cpp index b302c9405ce2..bf9f38816b69 100644 --- a/Source/Core/AudioCommon/AudioStretcher.cpp +++ b/Source/Core/AudioCommon/AudioStretcher.cpp @@ -39,10 +39,15 @@ void AudioStretcher::PushSamples(const s16* in, u32 num_in) } } -void AudioStretcher::GetStretchedSamples(s16* out, u32 num_out) +u32 AudioStretcher::GetStretchedSamples(s16* out, u32 num_out, bool pad) { // This won't return any samples a lot of times as they are processed in batches - const size_t samples_received = m_sound_touch.receiveSamples(out, num_out); + u32 samples_received = m_sound_touch.receiveSamples(out, num_out); + + if (!pad) + { + return samples_received; + } if (samples_received != 0) { @@ -50,12 +55,14 @@ void AudioStretcher::GetStretchedSamples(s16* out, u32 num_out) m_last_stretched_sample[1] = out[samples_received * 2 - 1]; } - // Perform padding if we've run out of samples. - for (size_t i = samples_received; i < num_out; ++i) + // Perform padding if we've run out of samples + for (u32 i = samples_received; i < num_out; ++i) { out[i * 2 + 0] = m_last_stretched_sample[0]; out[i * 2 + 1] = m_last_stretched_sample[1]; } + + return num_out; } // Call this before ProcessSamples() diff --git a/Source/Core/AudioCommon/AudioStretcher.h b/Source/Core/AudioCommon/AudioStretcher.h index 0de5d2ca24e7..b6d025ed1587 100644 --- a/Source/Core/AudioCommon/AudioStretcher.h +++ b/Source/Core/AudioCommon/AudioStretcher.h @@ -17,7 +17,7 @@ class AudioStretcher public: explicit AudioStretcher(u32 sample_rate); void PushSamples(const s16* in, u32 num_in); - void GetStretchedSamples(s16* out, u32 num_out); + u32 GetStretchedSamples(s16* out, u32 num_out, bool pad = true); void Clear(); void SetTempo(double tempo, bool reset = false); void SetSampleRate(u32 sample_rate); diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index 599323686e43..a6699ed0ff6e 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -39,7 +39,7 @@ bool CubebStream::Init() if (!m_ctx) return false; - m_mixer->SetSampleRate(SConfig::GetInstance().bUseOSMixerSampleRate ? + m_mixer->UpdateSettings(SConfig::GetInstance().bUseOSMixerSampleRate ? AudioCommon::GetOSMixerSampleRate() : AudioCommon::GetDefaultSampleRate()); diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 18c8ad25276b..e96d292cfd52 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "Common/ChunkFile.h" @@ -14,17 +15,16 @@ #include "Common/MathUtil.h" #include "Common/Swap.h" #include "Core/Core.h" -#include #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Common/Logging/Log.h" //To delete and all the uses (or make debug log) #include "VideoCommon/OnScreenDisplay.h" //To delete and all the uses -//#pragma optimize("", off) //To delete +#pragma optimize("", off) //To delete Mixer::Mixer(u32 sample_rate) : m_sample_rate(sample_rate), m_stretcher(sample_rate), - m_surround_decoder(sample_rate, Config::Get(Config::MAIN_DPL2_QUALITY)) + m_surround_decoder(sample_rate) { m_scratch_buffer.reserve(MAX_SAMPLES * NC); m_dma_speed.Start(true); @@ -72,11 +72,11 @@ void Mixer::DoState(PointerWrap& p) } } -void Mixer::SetSampleRate(u32 sample_rate) +void Mixer::UpdateSettings(u32 sample_rate) { m_sample_rate = sample_rate; m_stretcher.SetSampleRate(m_sample_rate); - m_surround_decoder.SetSampleRate(m_sample_rate); + m_surround_decoder.InitAndSetSampleRate(m_sample_rate); //To do: reset m_fract, and also DPLII. Add method to reset DPLII } @@ -140,7 +140,7 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) s32 behind_samples = num_samples - actual_samples_count; // This might sound bad if we are constantly missing a few samples, but that should never happen, // and we couldn't predict it anyway (meaning we should start playing backwards as soon as we can) - if (behind_samples > 0 && m_constantly_pushed && !stretching) + if (behind_samples > 0 && m_constantly_pushed && !stretching) //To add a setting to disable this? { rate = m_input_sample_rate / m_mixer->m_sample_rate; //To review (this should actually follow the rate but with no prediction...) s16* back_samples = samples + actual_samples_count * NC; @@ -422,9 +422,10 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) m_time_at_custom_speed = 0.0; } - //To test on GC with weird sample rates again. And with DVD Streaming + //To test on GC with weird sample rates again. And with DVD Streaming. Review when entering the galaxy stars view in SMG, it crackles a bit //To test latency/buffer buildup (auto adjustment/sync) when we pass from stretching to not stretching. If they are always on the edge... We'll need to slow it down. Have a mirrored version of max_latency (min)? //To review: this should be done per mixer + //To review, the stretcher can get stuck looping while stopping the process (breakpoint) for like 20 seconds? Only with DPLII? double latency; // The target latency used to be iTimingVariance if stretching was off and // m_audio_stretch_max_latency if it was on. In the first case, the reason was to cache enough @@ -437,6 +438,10 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) static double mult = 1.0; max_latency *= mult; } + //To fix max_latency does NOT work with stretching and DPLII at the same time, just do the min of max latency and out_samples. Make sure this doesn't trigger when we are asked 0 samples? + bool is_surround = m_scratch_buffer.data() == samples; + if (is_surround) + max_latency = std::max(time_delta, max_latency); double catch_up_speed; double target_latency; @@ -540,14 +545,25 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) { memset(samples, 0, num_samples * NC * sizeof(samples[0])); + if (m_stretching) + { + // Play out whatever we had left. Unprocessed samples will be lost. + // Of course this behaves weirdly when toggling stretching every audio frame, + // but it's better than nothing + u32 received_samples = m_stretcher.GetStretchedSamples(samples, num_samples, false); + num_samples -= received_samples; + samples += received_samples * NC; + + if (m_stretcher.GetProcessedLatency() <= 0.0) + m_stretching = false; + } + m_dma_mixer.Mix(samples, num_samples, false); m_streaming_mixer.Mix(samples, num_samples, false); m_wiimote_speaker_mixer[0].Mix(samples, num_samples, false); m_wiimote_speaker_mixer[1].Mix(samples, num_samples, false); m_wiimote_speaker_mixer[2].Mix(samples, num_samples, false); m_wiimote_speaker_mixer[3].Mix(samples, num_samples, false); - - m_stretching = false; } return num_samples; @@ -557,20 +573,27 @@ u32 Mixer::MixSurround(float* samples, u32 num_samples) { memset(samples, 0, num_samples * SURROUND_CHANNELS * sizeof(samples[0])); - //To clear on settings changed (num_samples): m_surround_decoder.Clear() (thread safe?) + //To clear on settings changed (num_samples): m_surround_decoder.Clear() (thread safe?), no, this code just seems wrong? //To fix QuerySamplesNeededForSurroundOutput doesn't work with num_samples 0 or small - size_t needed_samples = m_surround_decoder.QuerySamplesNeededForSurroundOutput(num_samples); + u32 needed_samples = m_surround_decoder.QuerySamplesNeededForSurroundOutput(num_samples); // If we set our latency too high, we might need more samples than we have, // as the surround decoder can only accept exactly "needed_samples" m_scratch_buffer.reserve(needed_samples * NC); + //To have another intermediary buffer here? So we constantly read from mix and when we have enough we put into DPLII. + //We have no alternative as the sound stretcher returns a random number of samples every time, the rest will be padded, + //we can't just ask for 0 and then all of sudden ask for a ton of samples, it won't work. + //We need to put them aside as we go. Also, there is no way of predicting how many samples the mixer will be able to + //produce, nor revert the changes in case it did not produce enough. + //Reduce MAX_BLOCKS_BUFFERED if you do + // Time stretching can be applied before decoding 5.1, it should be fine theoretically (untested). // Mix() may also use m_scratch_buffer internally, but is safe because we alternate reads // and writes. It returns the actual number of computed samples, which might be less // than the required ones, but as long as it computed something (it's not just all padding) // then we should use it for surround, otherwise these sounds would be missed - size_t available_samples = Mix(m_scratch_buffer.data(), u32(needed_samples)); + u32 available_samples = Mix(m_scratch_buffer.data(), needed_samples); //To fix: we can't reach this at lower latencies? Or with stretching on if (available_samples != needed_samples) { diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 5a03740cdfa1..817ff2d97c88 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -47,8 +47,8 @@ class Mixer final void StartLogDSPAudio(const std::string& filename); void StopLogDSPAudio(); - // Only call from main thread and when the mix is not running - void SetSampleRate(u32 sample_rate); + // Only call when the audio thread (Mix()) is not running + void UpdateSettings(u32 sample_rate); u32 GetSampleRate() const { return m_sample_rate; } double GetCurrentSpeed() const { return m_target_speed; } @@ -57,7 +57,7 @@ class Mixer final // Make sure this is a power of 2 for INDEX_MASK to work, // if not change all the "& INDEX_MASK" to "% (MAX_SAMPLES * NC)". // It's important that this is high enough to allow for enough backwards - // samples to be played in a frame dip. It doesn't make much sense that + // samples to be played in during a stutter. It doesn't make much sense that // this is independent from the sample rate, but it's fine static constexpr u32 MAX_SAMPLES = 16384; @@ -147,6 +147,7 @@ class Mixer final {this, 3000, false}, {this, 3000, false}, {this, 3000, false}, {this, 3000, false}}; u32 m_sample_rate; + // the size is always left at 0 std::vector m_scratch_buffer; std::array m_interpolation_buffer; s16 m_conversion_buffer[MAX_SAMPLES * NC]; diff --git a/Source/Core/AudioCommon/OpenALStream.cpp b/Source/Core/AudioCommon/OpenALStream.cpp index 6f876e2d24c7..a9f1ac90153e 100644 --- a/Source/Core/AudioCommon/OpenALStream.cpp +++ b/Source/Core/AudioCommon/OpenALStream.cpp @@ -119,7 +119,7 @@ bool OpenALStream::Init() return false; } - m_mixer->SetSampleRate(SConfig::GetInstance().bUseOSMixerSampleRate ? + m_mixer->UpdateSettings(SConfig::GetInstance().bUseOSMixerSampleRate ? AudioCommon::GetOSMixerSampleRate() : AudioCommon::GetDefaultSampleRate()); diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 7c90ccc160b0..834f307ce21d 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -3,73 +3,119 @@ // Refer to the license.txt file included. #include + #include +#include "Core/Config/MainSettings.h" + +#include "AudioCommon/Enums.h" #include "AudioCommon/SurroundDecoder.h" +#pragma optimize("", off) //To delete namespace AudioCommon { -constexpr size_t STEREO_CHANNELS = 2; -constexpr size_t SURROUND_CHANNELS = 6; +static s32 NearestPowerOfTwo(s32 n) +{ + assert(n > 1); + s32 v = n; + + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; // next power of 2 + + s32 x = v >> 1; // previous power of 2 -// Quality (higher quality also means more latency) + return (v - n) > (n - x) ? x : v; +} + +// Quality (higher quality also means more latency). Needs to be a pow of 2 so we find the closest static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) { + u32 frame_block_time; // ms switch (quality) { case DPL2Quality::Lowest: - return 512; - case DPL2Quality::Low: - return 1024; + frame_block_time = 10; + break; + case DPL2Quality::High: + frame_block_time = 40; + break; + // TODO: review this case, it's too much, FreeSurround said to not go over 20ms. + // The quality/latency trade off is not worth it and it might introduce crackling. case DPL2Quality::Highest: - return 4096; - default: // AudioCommon::DPL2Quality::High - return 2048; + frame_block_time = 80; + break; + case DPL2Quality::Low: + default: + frame_block_time = 20; } + u32 frame_block = std::round(sample_rate * frame_block_time / 1000.0); + return NearestPowerOfTwo(frame_block); } -SurroundDecoder::SurroundDecoder(u32 sample_rate, DPL2Quality quality) - : m_sample_rate(sample_rate), m_frame_block_size(DPL2QualityToFrameBlockSize(quality, sample_rate)) +// Currently only 6 channels are supported. +u32 SurroundDecoder::QuerySamplesNeededForSurroundOutput(u32 output_samples) const +{ + //To review: what would happen if they are ==? + if (output_samples > u32(m_decoded_fifo.size()) / SURROUND_CHANNELS) + { + // Output stereo samples needed to have at least the desired number of surround samples + u32 samples_needed = output_samples - (u32(m_decoded_fifo.size()) / SURROUND_CHANNELS); + return samples_needed + m_frame_block_size - (samples_needed % m_frame_block_size); + } + + return 0; +} + +SurroundDecoder::SurroundDecoder(u32 sample_rate) { m_fsdecoder = std::make_unique(); - m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate); - //To review (make config and remove comment, also, move to re-init): m_fsdecoder->set_bass_redirection(false); + InitAndSetSampleRate(sample_rate); } SurroundDecoder::~SurroundDecoder() = default; -void SurroundDecoder::Clear() +void SurroundDecoder::InitAndSetSampleRate(u32 sample_rate) { - m_fsdecoder->flush(); - m_decoded_fifo.clear(); -} + if (m_sample_rate == sample_rate) + return; + m_sample_rate = sample_rate; -void SurroundDecoder::SetSampleRate(u32 sample_rate) -{ - m_fsdecoder = std::make_unique(); + m_frame_block_size = + DPL2QualityToFrameBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), m_sample_rate); + + // This DPLII quality at this sample rate is not supported, increase the size or lower your + // settings + assert(m_frame_block_size * STEREO_CHANNELS <= m_float_conversion_buffer.max_size()); + assert(m_frame_block_size * SURROUND_CHANNELS * MAX_BLOCKS_BUFFERED <= m_decoded_fifo.max_size()); + + //To review: at max quality 192kHz sound only plays from the left speaker??? Only if we changed the settings first? + //To review: this can crash. It can also crash WASAPI m_audio_clock? + // Re-init. It should keep the samples in the buffer while just changing the settings m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate); - //To finish, also set DPL2QualityToFrameBlockSize and fix it by sample rate - // We can't change the block size after starting unfortunately, old samples will be lost (maybe we can...) + // The LFE channel (bass redirection) is disabled in the surround decoder, as most people + // have their own low pass crossover + m_fsdecoder->set_bass_redirection(Config::Get(Config::MAIN_DPL2_BASS_REDIRECTION)); } -// Currently only 6 channels are supported. -size_t SurroundDecoder::QuerySamplesNeededForSurroundOutput(const size_t output_samples) const +void SurroundDecoder::Clear() { - if (m_decoded_fifo.size() < output_samples * SURROUND_CHANNELS) - { - // Output stereo samples needed to have at least the desired number of surround samples - size_t samples_needed = output_samples - m_decoded_fifo.size() / SURROUND_CHANNELS; - return samples_needed + m_frame_block_size - samples_needed % m_frame_block_size; - } - - return 0; + m_fsdecoder->flush(); + m_decoded_fifo.clear(); } // Receive and decode samples -void SurroundDecoder::PushSamples(const s16* in, const size_t num_samples) +void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) { - // Maybe check if it is really power-of-2? - s64 remaining_samples = static_cast(num_samples); + assert(num_samples % m_frame_block_size == 0); + // We support a max of MAX_BLOCKS_BUFFERED blocks in the buffer, because of m_decoded_fifo, + // just increase if you need. This might trigger if you have very high backend latencies + assert(num_samples <= m_frame_block_size * MAX_BLOCKS_BUFFERED); + u32 remaining_samples = num_samples; size_t sample_index = 0; while (remaining_samples > 0) @@ -78,14 +124,13 @@ void SurroundDecoder::PushSamples(const s16* in, const size_t num_samples) for (size_t i = 0, end = m_frame_block_size * STEREO_CHANNELS; i < end; ++i) { m_float_conversion_buffer[i] = in[i + sample_index * STEREO_CHANNELS] / - static_cast(std::numeric_limits::max()); + float(std::numeric_limits::max()); } // Decode const float* dpl2_fs = m_fsdecoder->decode(m_float_conversion_buffer.data()); // Add to ring buffer and fix channel mapping - // Maybe modify FreeSurround to output the correct mapping? // FreeSurround: // FL | FC | FR | BL | BR | LFE // Most backends: @@ -95,20 +140,19 @@ void SurroundDecoder::PushSamples(const s16* in, const size_t num_samples) m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 0]); // LEFTFRONT m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 2]); // RIGHTFRONT m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 1]); // CENTREFRONT - // The LFE channel is disabled in the surround decoder, as most people - // have their own low pass crossover - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 5]); // sub/lfe + m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 5]); // LFE/SUB m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 3]); // LEFTREAR m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 4]); // RIGHTREAR } - remaining_samples = remaining_samples - static_cast(m_frame_block_size); + remaining_samples = remaining_samples - s32(m_frame_block_size); sample_index = sample_index + m_frame_block_size; } } -void SurroundDecoder::GetDecodedSamples(float* out, const size_t num_samples) +void SurroundDecoder::GetDecodedSamples(float* out, u32 num_samples) { + // TODO: this could be optimized by copying the ring buffer in two parts // Copy to output array with desired num_samples for (size_t i = 0, num_samples_output = num_samples * SURROUND_CHANNELS; i < num_samples_output; ++i) @@ -116,5 +160,4 @@ void SurroundDecoder::GetDecodedSamples(float* out, const size_t num_samples) out[i] = m_decoded_fifo.pop_front(); } } - } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/SurroundDecoder.h b/Source/Core/AudioCommon/SurroundDecoder.h index d8913b112e64..0e20aa0240bd 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.h +++ b/Source/Core/AudioCommon/SurroundDecoder.h @@ -7,7 +7,6 @@ #include #include -#include "AudioCommon/Enums.h" #include "Common/CommonTypes.h" #include "Common/FixedSizeQueue.h" @@ -18,21 +17,27 @@ namespace AudioCommon class SurroundDecoder { public: - explicit SurroundDecoder(u32 sample_rate, DPL2Quality quality); + explicit SurroundDecoder(u32 sample_rate); ~SurroundDecoder(); - size_t QuerySamplesNeededForSurroundOutput(const size_t output_samples) const; - void SetSampleRate(u32 sample_rate); - void PushSamples(const s16* in, const size_t num_samples); - void GetDecodedSamples(float* out, const size_t num_samples); + u32 QuerySamplesNeededForSurroundOutput(u32 output_samples) const; + void InitAndSetSampleRate(u32 sample_rate); + void PushSamples(const s16* in, u32 num_samples); + void GetDecodedSamples(float* out, u32 num_samples); void Clear(); private: - u32 m_sample_rate; + u32 m_sample_rate = 0; u32 m_frame_block_size; + static constexpr u32 STEREO_CHANNELS = 2; + static constexpr u32 SURROUND_CHANNELS = 6; + // Max supported samples rate is about 192kHz at highest block quality (~80ms) + static constexpr u32 MAX_BLOCKS_SIZE = 16384; + static constexpr u32 MAX_BLOCKS_BUFFERED = 8; + std::unique_ptr m_fsdecoder; - std::array m_float_conversion_buffer; - FixedSizeQueue m_decoded_fifo; + std::array m_float_conversion_buffer; + FixedSizeQueue m_decoded_fifo; }; } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index e99bfb4f3d88..7718b6137cf5 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -491,7 +491,7 @@ bool WASAPIStream::SetRunning(bool running) if (running) { - unsigned long sample_rate = 0; + unsigned long sample_rate = AudioCommon::GetDefaultSampleRate(); // Make sure the selected mode is still supported (ignored if we are using the default device) char* pEnd = nullptr; @@ -513,10 +513,7 @@ bool WASAPIStream::SetRunning(bool running) } // Recreate the mixer with our preferred sample rate - if (sample_rate > 0) - { - GetMixer()->SetSampleRate(sample_rate); - } + GetMixer()->UpdateSettings(sample_rate); m_surround = SConfig::GetInstance().ShouldUseDPL2Decoder(); m_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; @@ -817,6 +814,7 @@ bool WASAPIStream::SetRunning(bool running) m_audio_client = nullptr; if (m_audio_clock) { + //To review: as from the comment somewhere, this can crash if closed from another thread... m_audio_clock->Release(); m_audio_clock = nullptr; } diff --git a/Source/Core/Common/FixedSizeQueue.h b/Source/Core/Common/FixedSizeQueue.h index 80ccd2422683..38e817b04309 100644 --- a/Source/Core/Common/FixedSizeQueue.h +++ b/Source/Core/Common/FixedSizeQueue.h @@ -14,7 +14,7 @@ // // Not fully featured, no safety checking yet. Add features as needed. -template +template class FixedSizeQueue { public: diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 41264389a9d0..f3795d4e36a4 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -38,6 +38,7 @@ const Info MAIN_OVERRIDE_REGION_SETTINGS{{System::Main, "Core", "OverrideR const Info MAIN_DPL2_DECODER{{System::Main, "Core", "DPL2Decoder"}, false}; const Info MAIN_DPL2_QUALITY{{System::Main, "Core", "DPL2Quality"}, AudioCommon::GetDefaultDPL2Quality()}; +const Info MAIN_DPL2_BASS_REDIRECTION{{System::Main, "Core", "DPL2BassRedirection"}, false}; // TODO: rename "AudioBackendLatency", it's confusing const Info MAIN_AUDIO_BACKEND_LATENCY{{System::Main, "Core", "AudioLatency"}, 20}; const Info MAIN_AUDIO_MIXER_MAX_LATENCY{{System::Main, "Core", "AudioMixerMaxLatency"}, 40}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 53279186a80d..996d72d76a10 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -39,6 +39,7 @@ extern const Info MAIN_GC_LANGUAGE; extern const Info MAIN_OVERRIDE_REGION_SETTINGS; extern const Info MAIN_DPL2_DECODER; extern const Info MAIN_DPL2_QUALITY; +extern const Info MAIN_DPL2_BASS_REDIRECTION; extern const Info MAIN_AUDIO_BACKEND_LATENCY; extern const Info MAIN_AUDIO_MIXER_MAX_LATENCY; extern const Info MAIN_AUDIO_STRETCH; diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index a474c56708df..20b667f31553 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -34,7 +34,7 @@ bool IsSettingSaveable(const Config::Location& config_location) } } - static constexpr std::array s_setting_saveable = { + static constexpr std::array s_setting_saveable = { // Main.Core &Config::MAIN_DEFAULT_ISO.GetLocation(), @@ -44,6 +44,7 @@ bool IsSettingSaveable(const Config::Location& config_location) &Config::MAIN_ALLOW_SD_WRITES.GetLocation(), &Config::MAIN_DPL2_DECODER.GetLocation(), &Config::MAIN_DPL2_QUALITY.GetLocation(), + &Config::MAIN_DPL2_BASS_REDIRECTION.location, &Config::MAIN_AUDIO_MIXER_MAX_LATENCY.location, &Config::MAIN_RAM_OVERRIDE_ENABLE.GetLocation(), &Config::MAIN_MEM1_SIZE.GetLocation(), diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 4555e5d38eb0..7488b113a643 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -128,7 +128,7 @@ void AudioPane::CreateWidgets() m_dolby_quality_slider->setPageStep(1); m_dolby_quality_slider->setTickPosition(QSlider::TicksBelow); m_dolby_quality_slider->setToolTip( - tr("Quality of the DPLII decoder. Audio latency increases with quality.")); + tr("Quality of the DPLII decoder. Also increases audio latency")); m_dolby_quality_slider->setTracking(true); m_dolby_quality_low_label = new QLabel(GetDPL2QualityLabel(AudioCommon::DPL2Quality::Lowest)); @@ -389,14 +389,21 @@ void AudioPane::SaveSettings() SConfig::GetInstance().bDPL2Decoder = m_dolby_pro_logic->isChecked(); backend_setting_changed = true; } - Config::SetBase(Config::MAIN_DPL2_QUALITY, - static_cast(m_dolby_quality_slider->value())); - m_dolby_quality_latency_label->setText( - GetDPL2ApproximateLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); + if (Config::Get(Config::MAIN_DPL2_QUALITY) != + static_cast(m_dolby_quality_slider->value())) + { + Config::SetBase(Config::MAIN_DPL2_QUALITY, + static_cast(m_dolby_quality_slider->value())); + m_dolby_quality_latency_label->setText( + GetDPL2ApproximateLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); + backend_setting_changed = true; + } if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) { - EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && - m_dolby_pro_logic->isChecked() && !m_running); + bool backend_supports_runtime_changes = AudioCommon::SupportsRuntimeSettingsChanges(); + bool enable_dolby_pro_logic = !m_running || backend_supports_runtime_changes; + EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked() && + enable_dolby_pro_logic); } // Latency @@ -481,7 +488,7 @@ void AudioPane::OnBackendChanged() m_dolby_pro_logic->setEnabled(AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()); EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked() && - !m_running); + !m_running); //To add other m_running checks if (m_latency_control_supported) { m_latency_label->setEnabled(AudioCommon::SupportsLatencyControl(backend)); @@ -610,8 +617,7 @@ void AudioPane::OnEmulationStateChanged(bool running) { bool enable_dolby_pro_logic = !running || backend_supports_runtime_changes; m_dolby_pro_logic->setEnabled(enable_dolby_pro_logic); - // These settings cannot be changed at runtime - EnableDolbyQualityWidgets(!running && m_dolby_pro_logic->isChecked()); + EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); } if (m_latency_control_supported && AudioCommon::SupportsLatencyControl(SConfig::GetInstance().sBackend)) @@ -665,6 +671,7 @@ QString AudioPane::GetDPL2QualityLabel(AudioCommon::DPL2Quality value) const return tr("Low"); case AudioCommon::DPL2Quality::Highest: return tr("Highest"); + case AudioCommon::DPL2Quality::High: default: return tr("High"); } @@ -680,6 +687,7 @@ QString AudioPane::GetDPL2ApproximateLatencyLabel(AudioCommon::DPL2Quality value return tr("Latency: ~20ms"); case AudioCommon::DPL2Quality::Highest: return tr("Latency: ~80ms"); + case AudioCommon::DPL2Quality::High: default: return tr("Latency: ~40ms"); } From eecac16bb546a6fd1d62d02c16cfa059aa205b20 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 26 Jul 2020 16:13:14 +0300 Subject: [PATCH 09/56] Fixed crackling on some loud samples --- Source/Core/AudioCommon/AudioStretcher.cpp | 12 ++++----- Source/Core/AudioCommon/Mixer.cpp | 27 +++++++++++---------- Source/Core/AudioCommon/Mixer.h | 9 ++++--- Source/Core/AudioCommon/SurroundDecoder.cpp | 2 +- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/Source/Core/AudioCommon/AudioStretcher.cpp b/Source/Core/AudioCommon/AudioStretcher.cpp index bf9f38816b69..c572b816e19d 100644 --- a/Source/Core/AudioCommon/AudioStretcher.cpp +++ b/Source/Core/AudioCommon/AudioStretcher.cpp @@ -39,22 +39,22 @@ void AudioStretcher::PushSamples(const s16* in, u32 num_in) } } +// This won't return any samples a lot of times as they are processed in batches u32 AudioStretcher::GetStretchedSamples(s16* out, u32 num_out, bool pad) { - // This won't return any samples a lot of times as they are processed in batches u32 samples_received = m_sound_touch.receiveSamples(out, num_out); - if (!pad) - { - return samples_received; - } - if (samples_received != 0) { m_last_stretched_sample[0] = out[samples_received * 2 - 2]; m_last_stretched_sample[1] = out[samples_received * 2 - 1]; } + if (!pad) + { + return samples_received; + } + // Perform padding if we've run out of samples for (u32 i = samples_received; i < num_out; ++i) { diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index e96d292cfd52..b45e223e4df3 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -20,7 +20,7 @@ #include "Common/Logging/Log.h" //To delete and all the uses (or make debug log) #include "VideoCommon/OnScreenDisplay.h" //To delete and all the uses -#pragma optimize("", off) //To delete +//#pragma optimize("", off) //To delete Mixer::Mixer(u32 sample_rate) : m_sample_rate(sample_rate), m_stretcher(sample_rate), @@ -103,7 +103,7 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) s32 lVolume = m_lVolume.load(); s32 rVolume = m_rVolume.load(); - s16 s[NC]; // Padding samples + s32 s[NC]; // Padding samples s[0] = m_last_output_samples[1]; s[1] = m_last_output_samples[1]; @@ -195,10 +195,10 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) unsigned int current_sample = actual_samples_count * 2; for (; current_sample < num_samples * 2; current_sample += 2) { - samples[current_sample + 0] = - std::clamp(m_last_output_samples[0] + samples[current_sample + 0], -SHRT_MAX, SHRT_MAX); - samples[current_sample + 1] = - std::clamp(m_last_output_samples[1] + samples[current_sample + 1], -SHRT_MAX, SHRT_MAX); + samples[current_sample + 0] = std::clamp( + samples[current_sample + 0] + m_last_output_samples[0], SHRT_MIN, SHRT_MAX); + samples[current_sample + 1] = std::clamp( + samples[current_sample + 1] + m_last_output_samples[1], SHRT_MIN, SHRT_MAX); } } //if (indexW > 8 && !m_constantly_pushed) OSD::AddMessage("last sample: " + std::to_string(m_last_output_samples[0]), 0U); @@ -210,7 +210,7 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) // Sounds better than linear interpolation u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double rate, u32& indexR, - u32 indexW, s16& l_s, s16& r_s, s32 lVolume, s32 rVolume, + u32 indexW, s32& l_s, s32& r_s, s32 lVolume, s32 rVolume, bool forwards) { // Coefficients copied from SoundTouch Copyright © Olli Parviainen 2001-2019 @@ -302,14 +302,14 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r y3 * interpolation_buffer[(indexR + 6 * direction + 1) & INDEX_MASK]; //To test left and right on HW source - l_s = (s16(std::round(l_s_f)) * lVolume) >> 8; - r_s = (s16(std::round(r_s_f)) * rVolume) >> 8; + l_s = (s32(std::round(l_s_f)) * lVolume) >> 8; //To round again post vol? //To use std::rint? + r_s = (s32(std::round(r_s_f)) * rVolume) >> 8; // Clamp after adding to current sample, as if the cubic interpolation produced a sample over // the limits and the current sample has the opposite sign, then we'd keep the excess value, // while if it had the same sign, it would have been clamped anyway. - // We don't clamp to SHRT_MIN to keep the center at 0, though audio devices can take SHRT_MIN - samples[i * NC + 0] = std::clamp(samples[i * NC + 0] + l_s, -SHRT_MAX, SHRT_MAX); - samples[i * NC + 1] = std::clamp(samples[i * NC + 1] + r_s, -SHRT_MAX, SHRT_MAX); + // Instead of clamping to -SHORT_MAX like before to keep 0 centered, we use the full short range + samples[i * NC + 0] = std::clamp(samples[i * NC + 0] + l_s, SHRT_MIN, SHRT_MAX); + samples[i * NC + 1] = std::clamp(samples[i * NC + 1] + r_s, SHRT_MIN, SHRT_MAX); ++i; } @@ -597,7 +597,8 @@ u32 Mixer::MixSurround(float* samples, u32 num_samples) //To fix: we can't reach this at lower latencies? Or with stretching on if (available_samples != needed_samples) { - ERROR_LOG_FMT(AUDIO, "Error decoding surround frames."); + ERROR_LOG_FMT(AUDIO, "Error decoding surround samples"); + // This needs to do padding return 0; } diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 817ff2d97c88..deea0392002d 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -100,9 +100,11 @@ class Mixer final u32 GetNextIndexR(u32 indexR) const; void UpdatePush(double time); - // Returns the actual number of samples written. Outputs the last played sample for padding + // Returns the actual number of samples written. Outputs the last played sample for padding. + // We pass in in/out samples to cache the last calculated value. It's in int32 range because + // cubic interpolation can produce values over int16 and we don't want to lose the extra u32 CubicInterpolation(s16* samples, u32 num_samples, double rate, u32& indexR, u32 indexW, - s16& l_s, s16& r_s, s32 lVolume, s32 rVolume, bool forwards = true); + s32& l_s, s32& r_s, s32 lVolume, s32 rVolume, bool forwards = true); private: Mixer* m_mixer; @@ -128,7 +130,8 @@ class Mixer final // Volume range is 0-256 std::atomic m_lVolume{256}; std::atomic m_rVolume{256}; - s16 m_last_output_samples[NC]{}; + // Se comment on CubicInterpolation() + s32 m_last_output_samples[NC]{}; double m_fract = -1.0; double m_backwards_fract = -1.0; //To review: if this was off, that mixer won't gather a buffer/latency, and it would always be on the brink of playback diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 834f307ce21d..0e68c61c5cca 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -10,7 +10,7 @@ #include "AudioCommon/Enums.h" #include "AudioCommon/SurroundDecoder.h" -#pragma optimize("", off) //To delete +//#pragma optimize("", off) //To delete namespace AudioCommon { From 0c383b46ba25f6a62496e132928c61c8139dc741 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 26 Jul 2020 18:00:39 +0300 Subject: [PATCH 10/56] Build fixes --- Source/Core/AudioCommon/AudioCommon.h | 1 + Source/Core/DolphinQt/Settings/GeneralPane.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Core/AudioCommon/AudioCommon.h b/Source/Core/AudioCommon/AudioCommon.h index ece28fbbb0a8..69955e1a654c 100644 --- a/Source/Core/AudioCommon/AudioCommon.h +++ b/Source/Core/AudioCommon/AudioCommon.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include diff --git a/Source/Core/DolphinQt/Settings/GeneralPane.cpp b/Source/Core/DolphinQt/Settings/GeneralPane.cpp index 72d980d8543f..7672699bdd2c 100644 --- a/Source/Core/DolphinQt/Settings/GeneralPane.cpp +++ b/Source/Core/DolphinQt/Settings/GeneralPane.cpp @@ -4,6 +4,7 @@ #include "DolphinQt/Settings/GeneralPane.h" +#include #include #include @@ -163,7 +164,7 @@ void GeneralPane::CreateBasic() QString str; if (i != 100) { - if (i > 200 && std::fmod((float)i, 50.f) != 0) + if (i > 200 && std::fmod((float)i, 50.f) != 0.f) continue; else str = QStringLiteral("%1%").arg(i); From ad99b5c7991b9cd3e866132e5bca88ed38cc0be6 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 26 Jul 2020 18:14:14 +0300 Subject: [PATCH 11/56] Fixed asserts includes --- Source/Core/AudioCommon/Mixer.cpp | 1 + Source/Core/AudioCommon/SurroundDecoder.cpp | 1 + Source/Core/AudioCommon/WASAPIStream.cpp | 1 + Source/Core/Common/FixedSizeQueue.h | 1 + Source/Core/DolphinQt/Settings/AudioPane.cpp | 2 ++ 5 files changed, 6 insertions(+) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index b45e223e4df3..2bb819d3f882 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -5,6 +5,7 @@ #include "AudioCommon/Mixer.h" //To try to delete some includes and see if it builds #include +#include //To delete #include #include #include diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 0e68c61c5cca..6c69f1268b1f 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -4,6 +4,7 @@ #include +#include #include #include "Core/Config/MainSettings.h" diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 7718b6137cf5..cd026fb31aaf 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -8,6 +8,7 @@ // clang-format off #include +#include #include #include #include diff --git a/Source/Core/Common/FixedSizeQueue.h b/Source/Core/Common/FixedSizeQueue.h index 38e817b04309..4d6b243457f8 100644 --- a/Source/Core/Common/FixedSizeQueue.h +++ b/Source/Core/Common/FixedSizeQueue.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 7488b113a643..1a899ea89568 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -4,6 +4,8 @@ #include "DolphinQt/Settings/AudioPane.h" +#include + #include #include #include From bd4a8c768e9e593fa4bc168b8ee6c4173c65498a Mon Sep 17 00:00:00 2001 From: Filoppi Date: Mon, 27 Jul 2020 00:29:04 +0300 Subject: [PATCH 12/56] Finish audio panel improvements and descriptions. Big improvements to DPLII. Added support to change Cubeb audio settings at runtime, and improved the same thing for WASAPI. Fixed build on linux/mac. Finished input code and cleaned it up. Made FreeSurround safer and fixed support for changing sample rate at runtime. Added GetActualEmulationSpeed() to Core --- .../FreeSurround/FreeSurroundDecoder.h | 4 +- .../include/FreeSurround/KissFFTR.h | 2 +- .../source/FreeSurroundDecoder.cpp | 34 ++-- Externals/FreeSurround/source/KissFFTR.cpp | 15 +- Source/Core/AudioCommon/AudioCommon.cpp | 26 ++- Source/Core/AudioCommon/AudioCommon.h | 9 +- Source/Core/AudioCommon/CubebStream.cpp | 155 +++++++++++------- Source/Core/AudioCommon/CubebStream.h | 8 + Source/Core/AudioCommon/Mixer.cpp | 16 +- Source/Core/AudioCommon/Mixer.h | 3 + Source/Core/AudioCommon/SurroundDecoder.cpp | 14 +- Source/Core/AudioCommon/WASAPIStream.cpp | 22 +-- Source/Core/DolphinQt/Settings/AudioPane.cpp | 106 ++++++------ .../ControlReference/ControlReference.cpp | 6 +- .../ControllerInterface.cpp | 6 +- .../DualShockUDPClient/DualShockUDPClient.cpp | 2 +- .../Quartz/QuartzKeyboardAndMouse.h | 10 +- .../ControllerInterface/Xlib/XInput2.h | 14 +- 18 files changed, 268 insertions(+), 184 deletions(-) diff --git a/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h b/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h index 34a93964cc57..a5bf6eae07ba 100644 --- a/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h +++ b/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h @@ -161,7 +161,9 @@ class DPL2FSDecoder { std::vector lf, rf; // FFT buffers - kiss_fftr_cfg forward, inverse; + // they used to be kiss_fftr_cfg but due to unsafe deletion Dolphin changed them to a char* + char* forward; + char* inverse; // buffers // whether the buffer is currently empty or dirty diff --git a/Externals/FreeSurround/include/FreeSurround/KissFFTR.h b/Externals/FreeSurround/include/FreeSurround/KissFFTR.h index 8c310ca8d116..6a2d335ea4d1 100644 --- a/Externals/FreeSurround/include/FreeSurround/KissFFTR.h +++ b/Externals/FreeSurround/include/FreeSurround/KissFFTR.h @@ -17,7 +17,7 @@ extern "C" { typedef struct kiss_fftr_state *kiss_fftr_cfg; -kiss_fftr_cfg kiss_fftr_alloc(int nfft, int inverse_fft, void *mem, +char* kiss_fftr_alloc(int nfft, int inverse_fft, void *mem, size_t *lenmem); /* nfft must be even diff --git a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp index f77e1974dea5..b70539dd7ff6 100644 --- a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp +++ b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp @@ -28,13 +28,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. DPL2FSDecoder::DPL2FSDecoder() { initialized = false; buffer_empty = true; + forward = nullptr; + inverse = nullptr; } DPL2FSDecoder::~DPL2FSDecoder() { -#pragma warning(suppress : 4150) - delete forward; -#pragma warning(suppress : 4150) - delete inverse; + delete[] forward; + delete[] inverse; } void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, @@ -51,13 +51,19 @@ void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, dst = std::vector(N); lf = std::vector(N / 2 + 1); rf = std::vector(N / 2 + 1); + delete[] forward; + delete[] inverse; forward = kiss_fftr_alloc(N, 0, 0, 0); inverse = kiss_fftr_alloc(N, 1, 0, 0); C = static_cast(chn_alloc[setup].size()); // Allocate per-channel buffers outbuf.resize((N + N / 2) * C); - signal.resize(C, std::vector(N)); + for (unsigned int k = 0; k < signal.size(); k++) + { + signal[k].resize(N); + } + signal.resize(C, signal.size() > 0 ? signal.back() : std::vector(N)); // Init the window function for (unsigned int k = 0; k < N; k++) @@ -83,13 +89,13 @@ void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, float *DPL2FSDecoder::decode(float *input) { if (initialized) { // append incoming data to the end of the input buffer - memcpy(&inbuf[N], &input[0], 8 * N); + memcpy(&inbuf[N], &input[0], sizeof(float) * 2 * N); // process first and second half, overlapped buffered_decode(&inbuf[0]); buffered_decode(&inbuf[N]); // shift last half of the input to the beginning (for overlapping with a // future block) - memcpy(&inbuf[0], &inbuf[2 * N], 4 * N); + memcpy(&inbuf[0], &inbuf[2 * N], sizeof(float) * N); buffer_empty = false; return &outbuf[0]; } @@ -98,8 +104,8 @@ float *DPL2FSDecoder::decode(float *input) { // flush the internal buffers void DPL2FSDecoder::flush() { - memset(&outbuf[0], 0, outbuf.size() * 4); - memset(&inbuf[0], 0, inbuf.size() * 4); + memset(&outbuf[0], 0, outbuf.size() * sizeof(float)); + memset(&inbuf[0], 0, inbuf.size() * sizeof(float)); buffer_empty = true; } @@ -161,8 +167,8 @@ void DPL2FSDecoder::buffered_decode(float *input) { } // map into spectral domain - kiss_fftr(forward, <[0], (kiss_fft_cpx *)&lf[0]); - kiss_fftr(forward, &rt[0], (kiss_fft_cpx *)&rf[0]); + kiss_fftr((kiss_fftr_cfg)forward, <[0], (kiss_fft_cpx *)&lf[0]); + kiss_fftr((kiss_fftr_cfg)forward, &rt[0], (kiss_fft_cpx *)&rf[0]); // compute multichannel output signal in the spectral domain for (unsigned int f = 1; f < N / 2; f++) { @@ -227,13 +233,13 @@ void DPL2FSDecoder::buffered_decode(float *input) { } // shift the last 2/3 to the first 2/3 of the output buffer - memcpy(&outbuf[0], &outbuf[C * N / 2], N * C * 4); + memcpy(&outbuf[0], &outbuf[C * N / 2], N * C * sizeof(float)); // and clear the rest - memset(&outbuf[C * N], 0, C * 4 * N / 2); + memset(&outbuf[C * N], 0, C * sizeof(float) * N / 2); // backtransform each channel and overlap-add for (unsigned int c = 0; c < C; c++) { // back-transform into time domain - kiss_fftri(inverse, (kiss_fft_cpx *)&signal[c][0], &dst[0]); + kiss_fftri((kiss_fftr_cfg)inverse, (kiss_fft_cpx *)&signal[c][0], &dst[0]); // add the result to the last 2/3 of the output buffer, windowed (and // remultiplex) for (unsigned int k = 0; k < N; k++) diff --git a/Externals/FreeSurround/source/KissFFTR.cpp b/Externals/FreeSurround/source/KissFFTR.cpp index 41b13d43d35f..cad56dc0ebd5 100644 --- a/Externals/FreeSurround/source/KissFFTR.cpp +++ b/Externals/FreeSurround/source/KissFFTR.cpp @@ -39,6 +39,8 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FreeSurround/KissFFTR.h" #include "FreeSurround/_KissFFTGuts.h" +#include + struct kiss_fftr_state { kiss_fft_cfg substate; kiss_fft_cpx *tmpbuf; @@ -48,10 +50,11 @@ struct kiss_fftr_state { #endif }; -kiss_fftr_cfg kiss_fftr_alloc(int nfft, int inverse_fft, void *mem, +char* kiss_fftr_alloc(int nfft, int inverse_fft, void *mem, size_t *lenmem) { int i; kiss_fftr_cfg st = NULL; + char* char_st = NULL; size_t subsize = 65536 * 4, memneeded = 0; if (nfft & 1) { @@ -65,10 +68,16 @@ kiss_fftr_cfg kiss_fftr_alloc(int nfft, int inverse_fft, void *mem, sizeof(kiss_fft_cpx) * (nfft * 3 / 2); if (lenmem == NULL) { - st = (kiss_fftr_cfg) new char[memneeded]; + assert(memneeded >= sizeof(kiss_fftr_state)); + char_st = new char[memneeded]; + st = (kiss_fftr_cfg)char_st; } else { if (*lenmem >= memneeded) + { + assert(false); // Support removed by Dolphin st = (kiss_fftr_cfg)mem; + char_st = (char*)st; + } *lenmem = memneeded; } if (!st) @@ -86,7 +95,7 @@ kiss_fftr_cfg kiss_fftr_alloc(int nfft, int inverse_fft, void *mem, phase *= -1; kf_cexp(st->super_twiddles + i, phase); } - return st; + return char_st; } void kiss_fftr(kiss_fftr_cfg st, const kiss_fft_scalar *timedata, diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index bfebbdb0d842..83f12c6a181c 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -184,10 +184,12 @@ bool SupportsLatencyControl(std::string_view backend) { // TODO: we should ask the backends whether they support this #ifdef _WIN32 - // Cubeb only supports custom latency on Windows - return backend == BACKEND_OPENAL || backend == BACKEND_WASAPI || backend == BACKEND_CUBEB; //To review: not true + return backend == BACKEND_OPENAL || backend == BACKEND_WASAPI; #else - return false; + // TODO: test whether cubeb supports latency on Mac OS X and Linux (different internal backends). + // It does NOT support latency on Win 10 (at least on my devices) despite exposing it and + // seemingly supporting it on WASAPI + return backend == BACKEND_CUBEB; #endif } @@ -198,7 +200,7 @@ bool SupportsVolumeChanges(std::string_view backend) BACKEND_OPENSLES; } -bool SupportsRuntimeSettingsChanges() +bool BackendSupportsRuntimeSettingsChanges() { std::lock_guard guard(g_sound_stream_mutex); if (g_sound_stream) @@ -308,16 +310,22 @@ unsigned long GetOSMixerSampleRate() #endif } -void UpdateSoundStreamSettings(bool volume_only) +void UpdateSoundStreamSettings(bool volume_changed, bool settings_changed, bool surround_changed) { // This can be called from any threads, needs to be protected std::lock_guard guard(g_sound_stream_mutex); if (g_sound_stream) { - int volume = SConfig::GetInstance().m_IsMuted ? 0 : SConfig::GetInstance().m_Volume; - g_sound_stream->SetVolume(volume); - - if (!volume_only) + if (volume_changed) + { + int volume = SConfig::GetInstance().m_IsMuted ? 0 : SConfig::GetInstance().m_Volume; + g_sound_stream->SetVolume(volume); + } + if (surround_changed) + { + g_sound_stream->GetMixer()->SetSurroundChanged(); + } + if (settings_changed || surround_changed) { // Some backends will be able to apply changes in settings at runtime g_sound_stream->OnSettingsChanged(); diff --git a/Source/Core/AudioCommon/AudioCommon.h b/Source/Core/AudioCommon/AudioCommon.h index 69955e1a654c..e23aeef3d231 100644 --- a/Source/Core/AudioCommon/AudioCommon.h +++ b/Source/Core/AudioCommon/AudioCommon.h @@ -31,7 +31,7 @@ DPL2Quality GetDefaultDPL2Quality(); bool SupportsDPL2Decoder(std::string_view backend); bool SupportsLatencyControl(std::string_view backend); bool SupportsVolumeChanges(std::string_view backend); -bool SupportsRuntimeSettingsChanges(); +bool BackendSupportsRuntimeSettingsChanges(); // Default "Dolphin" output and internal mixer sample rate unsigned long GetDefaultSampleRate(); // Returns the min buffer time length it can hold (in ms), our backends can't have a latency @@ -43,8 +43,11 @@ unsigned long GetMaxSupportedLatency(); unsigned long GetUserTargetLatency(); // Returns the OS mixer sample rate (based on the currently used audio device) unsigned long GetOSMixerSampleRate(); -// Either volume only, or any type of more advanced settings (e.g. Latency, DPLII) -void UpdateSoundStreamSettings(bool volume_only); +// Either volume only, or other settings that require a backend re-init (e.g. Latency, Device, +// Sample Rate, DPLII Enabled, DPLII Quality), or DPLII Enabled which also requires to clean the +// surround buffer +void UpdateSoundStreamSettings(bool volume_changed, bool settings_changed = false, + bool surround_changed = false); bool SetSoundStreamRunning(bool running, bool send_error = false); void SendAIBuffer(const short* samples, unsigned int num_samples); void StartAudioDump(); diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index a6699ed0ff6e..7322069550f4 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -4,6 +4,8 @@ #include +#include + #include "AudioCommon/CubebStream.h" #include "AudioCommon/CubebUtils.h" #include "AudioCommon/AudioCommon.h" @@ -12,10 +14,6 @@ #include "Common/Thread.h" #include "Core/ConfigManager.h" -//To review: this is not true... surround min samples depend on the sample rate. Also this doesn't depend on sample rate... -// ~10 ms - needs to be at least 240 for surround -constexpr u32 BUFFER_SAMPLES = 512; - long CubebStream::DataCallback(cubeb_stream* stream, void* user_data, const void* /*input_buffer*/, void* output_buffer, long num_frames) { @@ -36,73 +34,118 @@ void CubebStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_sta bool CubebStream::Init() { m_ctx = CubebUtils::GetContext(); - if (!m_ctx) - return false; + return m_ctx != nullptr; +} - m_mixer->UpdateSettings(SConfig::GetInstance().bUseOSMixerSampleRate ? - AudioCommon::GetOSMixerSampleRate() : - AudioCommon::GetDefaultSampleRate()); +bool CubebStream::SetRunning(bool running) +{ + assert(running != m_running); - m_stereo = !SConfig::GetInstance().ShouldUseDPL2Decoder(); + m_should_restart = false; - cubeb_stream_params params; - params.rate = m_mixer->GetSampleRate(); - if (m_stereo) - { - params.channels = 2; - params.format = CUBEB_SAMPLE_S16NE; - params.layout = CUBEB_LAYOUT_STEREO; - } - else + if (running) { - params.channels = 6; - params.format = CUBEB_SAMPLE_FLOAT32NE; - params.layout = CUBEB_LAYOUT_3F2_LFE; - } - - // In samples - u32 minimum_latency = 0; - if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) - ERROR_LOG_FMT(AUDIO, "Error getting minimum latency"); - INFO_LOG_FMT(AUDIO, "Minimum latency: {} frames", minimum_latency); - - u32 final_latency; - + m_mixer->UpdateSettings(SConfig::GetInstance().bUseOSMixerSampleRate ? + AudioCommon::GetOSMixerSampleRate() : + AudioCommon::GetDefaultSampleRate()); + + m_stereo = !SConfig::GetInstance().ShouldUseDPL2Decoder(); + + cubeb_stream_params params; + params.rate = m_mixer->GetSampleRate(); + if (m_stereo) + { + params.channels = 2; + params.format = CUBEB_SAMPLE_S16NE; + params.layout = CUBEB_LAYOUT_STEREO; + } + else + { + params.channels = 6; + params.format = CUBEB_SAMPLE_FLOAT32NE; + params.layout = CUBEB_LAYOUT_3F2_LFE; + } + + // In samples + // Max supported by cubeb is 96000 and min is 1 (in samples) + u32 minimum_latency = 0; + if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) + ERROR_LOG_FMT(AUDIO, "Error getting minimum latency"); + + u32 final_latency; + + uint32_t target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; #ifdef _WIN32 - uint32_t target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; - // WASAPI supports up to 5000ms but let's clamp to 500ms - uint32_t max_latency = 500 / 1000.0 * params.rate; - final_latency = std::clamp(target_latency, minimum_latency, max_latency); + // WASAPI supports up to 5000ms but let's clamp to 500ms + uint32_t max_latency = 500 / 1000.0 * params.rate; + // This doesn't actually seem to work, latency is ignored on Window 10 + final_latency = std::clamp(target_latency, minimum_latency, max_latency); #else - // TODO: implement on other platforms that I couldn't test (they might fail to initialize). - // Max supported by cubeb is 96000 and min is 1 (in samples) - final_latency = minimum_latency; + final_latency = std::clamp(target_latency, minimum_latency, 96000); #endif - if (!m_stereo) + INFO_LOG(AUDIO, "Latency: %u frames", final_latency); + + // It's very hard for cubeb to fail starting so we don't trigger a restart request + if (cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, nullptr, + ¶ms, final_latency, DataCallback, StateCallback, this) == CUBEB_OK) + { + if (cubeb_stream_start(m_stream) == CUBEB_OK) + { + m_running = true; + return true; + } + } + return false; + } + else { - final_latency = std::max(BUFFER_SAMPLES, final_latency); + int result = cubeb_stream_stop(m_stream); + cubeb_stream_destroy(m_stream); + if (result == CUBEB_OK) + m_running = false; + else + ERROR_LOG(AUDIO, "Cubeb failed to stop. Dolphin might crash"); + // Not sure how to proceed here. Destroying cubeb can't fail but stopping it can? + // Does destroy imply stopping? Probably, but is it safe? + return result == CUBEB_OK; } - - INFO_LOG(AUDIO, "Latency: %u frames", final_latency); - - return cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, - nullptr, ¶ms, final_latency, DataCallback, StateCallback, - this) == CUBEB_OK; } -bool CubebStream::SetRunning(bool running) +CubebStream::~CubebStream() { - if (running) - return cubeb_stream_start(m_stream) == CUBEB_OK; - else - return cubeb_stream_stop(m_stream) == CUBEB_OK; + if (m_running) + SetRunning(false); + m_ctx.reset(); } -CubebStream::~CubebStream() +void CubebStream::Update() { - SetRunning(false); - cubeb_stream_destroy(m_stream); - m_ctx.reset(); + // TODO: move this out of the game (main) thread, restarting WASAPI should not lock the game + // thread, but as of now there isn't any other constant and safe access point to restart + + // If the sound loop failed for some reason, re-initialize ASAPI to resume playback + if (m_should_restart) + { + m_should_restart = false; + if (m_running) + { + // We need to pass through AudioCommon as it has a mutex and + // to make sure s_sound_stream_running is updated + if (AudioCommon::SetSoundStreamRunning(false, false)) + { + // m_should_restart is triggered when the device is currently + // invalidated, and it will stay for a while, so this new call + // to SetRunning(true) might fail, but if it fails some + // specific reasons, it will set m_should_restart true again. + // A Sleep(10) call also seemed to fix the problem but it's hacky. + AudioCommon::SetSoundStreamRunning(true, false); + } + } + else + { + AudioCommon::SetSoundStreamRunning(true, false); + } + } } void CubebStream::SetVolume(int volume) diff --git a/Source/Core/AudioCommon/CubebStream.h b/Source/Core/AudioCommon/CubebStream.h index 894d00ac1996..9086f20b9cd2 100644 --- a/Source/Core/AudioCommon/CubebStream.h +++ b/Source/Core/AudioCommon/CubebStream.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -18,8 +19,12 @@ class CubebStream final : public SoundStream ~CubebStream() override; bool Init() override; bool SetRunning(bool running) override; + void Update() override; void SetVolume(int) override; + bool SupportsRuntimeSettingsChanges() const override { return true; } + void OnSettingsChanged() override { m_should_restart = true; } + private: bool m_stereo = false; std::shared_ptr m_ctx; @@ -28,6 +33,9 @@ class CubebStream final : public SoundStream std::vector m_short_buffer; std::vector m_floatstereo_buffer; + std::atomic m_running = false; + std::atomic m_should_restart = false; + static long DataCallback(cubeb_stream* stream, void* user_data, const void* /*input_buffer*/, void* output_buffer, long num_frames); static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 2bb819d3f882..5e1a6f3ef182 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -77,6 +77,11 @@ void Mixer::UpdateSettings(u32 sample_rate) { m_sample_rate = sample_rate; m_stretcher.SetSampleRate(m_sample_rate); + if (m_surround_changed) + { + m_surround_changed = false; + m_surround_decoder.Clear(); + } m_surround_decoder.InitAndSetSampleRate(m_sample_rate); //To do: reset m_fract, and also DPLII. Add method to reset DPLII } @@ -381,6 +386,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) // Filter out small inaccuracies (samples aren't submitted with perfect timing) else if (actual_speed / emulation_speed < 1.0 - FallbackDelta) { + //To review: this keeps trigger and going back to normal on GC due to the uneven sample rates? It also does NOT work with DPLII at the moment // Note: when not able to reach the target speed before m_time_below_target_speed_growing triggers, you might hear crackles due to (probably) having finished the samples //To review: this happens more often at higher backend latencies, smooth it over time? Ignore first missed frame? // If we fell behind of m_time_behind_target_speed seconds of samples, start using the actual emulation speed @@ -423,7 +429,6 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) m_time_at_custom_speed = 0.0; } - //To test on GC with weird sample rates again. And with DVD Streaming. Review when entering the galaxy stars view in SMG, it crackles a bit //To test latency/buffer buildup (auto adjustment/sync) when we pass from stretching to not stretching. If they are always on the edge... We'll need to slow it down. Have a mirrored version of max_latency (min)? //To review: this should be done per mixer //To review, the stretcher can get stuck looping while stopping the process (breakpoint) for like 20 seconds? Only with DPLII? @@ -439,10 +444,9 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) static double mult = 1.0; max_latency *= mult; } - //To fix max_latency does NOT work with stretching and DPLII at the same time, just do the min of max latency and out_samples. Make sure this doesn't trigger when we are asked 0 samples? - bool is_surround = m_scratch_buffer.data() == samples; - if (is_surround) - max_latency = std::max(time_delta, max_latency); + //To fix max_latency does NOT work with stretching and DPLII at the same time, just do the min of max latency and out_samples. Make sure this doesn't trigger when we are asked 0 samples? Or pass in the min block size required for latency + //bool is_surround = m_scratch_buffer.data() == samples; + //max_latency = std::max(time_delta, max_latency); double catch_up_speed; double target_latency; @@ -574,7 +578,7 @@ u32 Mixer::MixSurround(float* samples, u32 num_samples) { memset(samples, 0, num_samples * SURROUND_CHANNELS * sizeof(samples[0])); - //To clear on settings changed (num_samples): m_surround_decoder.Clear() (thread safe?), no, this code just seems wrong? + //To review this code just seems wrong and breaks if we change the sample rate? //To fix QuerySamplesNeededForSurroundOutput doesn't work with num_samples 0 or small u32 needed_samples = m_surround_decoder.QuerySamplesNeededForSurroundOutput(num_samples); diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index deea0392002d..24c41a7d1ff3 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -49,6 +49,8 @@ class Mixer final // Only call when the audio thread (Mix()) is not running void UpdateSettings(u32 sample_rate); + // Useful to clean the surround buffer when we enable/disable it + void SetSurroundChanged() { m_surround_changed = true; } u32 GetSampleRate() const { return m_sample_rate; } double GetCurrentSpeed() const { return m_target_speed; } @@ -165,6 +167,7 @@ class Mixer final bool m_latency_catching_up = false; bool m_stretching = false; + std::atomic m_surround_changed{false}; AudioCommon::AudioStretcher m_stretcher; AudioCommon::SurroundDecoder m_surround_decoder; diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 6c69f1268b1f..bded24971b88 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -58,10 +58,9 @@ static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) return NearestPowerOfTwo(frame_block); } -// Currently only 6 channels are supported. u32 SurroundDecoder::QuerySamplesNeededForSurroundOutput(u32 output_samples) const { - //To review: what would happen if they are ==? + //To review: what would happen if they are ==? How is the output fifo supposed to decide the input amount? if (output_samples > u32(m_decoded_fifo.size()) / SURROUND_CHANNELS) { // Output stereo samples needed to have at least the desired number of surround samples @@ -94,9 +93,8 @@ void SurroundDecoder::InitAndSetSampleRate(u32 sample_rate) assert(m_frame_block_size * STEREO_CHANNELS <= m_float_conversion_buffer.max_size()); assert(m_frame_block_size * SURROUND_CHANNELS * MAX_BLOCKS_BUFFERED <= m_decoded_fifo.max_size()); - //To review: at max quality 192kHz sound only plays from the left speaker??? Only if we changed the settings first? - //To review: this can crash. It can also crash WASAPI m_audio_clock? - // Re-init. It should keep the samples in the buffer while just changing the settings + // Re-init. It should keep the samples in the buffer (and filling the rest with 0) while just + // changing the settings m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate); // The LFE channel (bass redirection) is disabled in the surround decoder, as most people // have their own low pass crossover @@ -124,11 +122,11 @@ void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) // Convert to float for (size_t i = 0, end = m_frame_block_size * STEREO_CHANNELS; i < end; ++i) { - m_float_conversion_buffer[i] = in[i + sample_index * STEREO_CHANNELS] / - float(std::numeric_limits::max()); + m_float_conversion_buffer[i] = + in[i + sample_index * STEREO_CHANNELS] / float(std::numeric_limits::max()); } - // Decode + // Decode. Note that output isn't clamped within -1.f and +1.f const float* dpl2_fs = m_fsdecoder->decode(m_float_conversion_buffer.data()); // Add to ring buffer and fix channel mapping diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index cd026fb31aaf..404cf621d10e 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -7,6 +7,7 @@ #ifdef _WIN32 // clang-format off +#include #include #include #include @@ -487,7 +488,6 @@ bool WASAPIStream::SetRunning(bool running) if (!aci.Succeeded()) return false; - // Keep trying if we fail before this m_should_restart = false; if (running) @@ -815,7 +815,6 @@ bool WASAPIStream::SetRunning(bool running) m_audio_client = nullptr; if (m_audio_clock) { - //To review: as from the comment somewhere, this can crash if closed from another thread... m_audio_clock->Release(); m_audio_clock = nullptr; } @@ -932,17 +931,18 @@ void WASAPIStream::SoundLoop() unsigned int mixed_samples = GetMixer()->MixSurround(m_surround_samples.data(), m_frames_in_buffer); - // From float(32) (-1 to 1) to signed short(16) + // From float(32) (~-1 to ~1 but can go beyond limits) to signed short(16) for (u32 i = 0; i < mixed_samples * SURROUND_CHANNELS; ++i) { - // std::numeric_limits::min is actually smaller than max, like every signed min, - // so we don't need to clamp to -max (or min) if the float is clamped to -1. - // We assume the volume won't be more than 1, or we'd lose some amplifications. - // It would be better to round the result value, but the chance of that making - // a difference is not worth the performance hit - reinterpret_cast(data)[i] = - static_cast(std::clamp(m_surround_samples[i] * volume, -1.f, 1.f) * - std::numeric_limits::max()); + m_surround_samples[i] *= volume * std::numeric_limits::max(); + m_surround_samples[i] = + std::clamp(m_surround_samples[i], float(std::numeric_limits::min()), + float(std::numeric_limits::max())); + + // We assume the volume won't be more than 1, or we'll get some clipping. It would be better + // to round the result, but the chance of that making a difference is not worth the + // performance hit + reinterpret_cast(data)[i] = static_cast(m_surround_samples[i]); } } // From signed short(16) to signed short(16) diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 1a899ea89568..4ee382112ad4 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -74,8 +74,7 @@ void AudioPane::CreateWidgets() m_volume_slider->setMinimum(0); m_volume_slider->setMaximum(100); - m_volume_slider->setToolTip(tr("Setting volume to values different from 100 might slightly" - " degrade sound quality.")); + m_volume_slider->setToolTip(tr("Using this is preferred over the OS mixer volume")); m_volume_indicator->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); m_volume_indicator->setFixedWidth(QFontMetrics(font()).boundingRect(tr("%1 %").arg(100)).width()); @@ -96,29 +95,29 @@ void AudioPane::CreateWidgets() m_latency_spin = new QSpinBox(); m_latency_spin->setMinimum(0); m_latency_spin->setMaximum(std::min((int)AudioCommon::GetMaxSupportedLatency(), 200)); - //To review: is this not visible? - //To specify that it's the Target/min. A good one is from 3 to 40. It might not work on systems with not many CPU cores m_latency_spin->setToolTip(tr("Target latency (in ms). Higher values may reduce audio" " crackling.\nCertain backends only. Values above 20ms are not suggested.")); } m_use_os_sample_rate = new QCheckBox(tr("Use OS Mixer sample rate")); - std::string useOSSampleRateTooltip = "Directly mixes at the current OS mixer sample rate. As opposed to " + std::to_string(AudioCommon::GetDefaultSampleRate()) + - "Hz. This will make resamplings from" - " 32000Hz sources more accurate,\npossibly improving" - " audio quality at the cost of performance. It won't adjust if you change it after starting. Also if you use 44.1, it will be better" - "\nIf unsure, leave off."; - m_use_os_sample_rate->setToolTip(tr(useOSSampleRateTooltip.c_str())); + m_use_os_sample_rate->setToolTip( + tr("Directly mixes at the current OS mixer sample rate as opposed to %1" + "Hz.\nThis will make resampling from 32kHz sources more accurate, possibly improving " + "audio\n" + "quality at the cost of performance. It won't follow changes to your OS setting." + "\nIf unsure, leave off.") + .arg(AudioCommon::GetDefaultSampleRate())); - //To review: this might create an empty space when hidden + // Unfortunately this creates an empty space if added to the widget. We should find a better way m_use_os_sample_rate->setHidden(true); m_dolby_pro_logic->setToolTip( tr("Enables Dolby Pro Logic II emulation using 5.1 surround.\nCertain backends and DPS " "emulation engines only.\nAutomatically disabled if not supported by your audio device." - "\nAs on the original hardware, games will still be stereo,\nbut DPLII will" - " extrapolate information to output 5.1 channels.\nIf unsure, leave off. When changing at runtime, you will lose samples. You need to enable it in game for GC or in menu of Wii.")); + "\nYou need to enable it in the game settings for GC games or in the menu settings on Wii." + "\nThe console will still output 2.0, but the encoder will extract information for 5.1." + "\nIf unsure, leave off.")); auto* dolby_quality_layout = new QHBoxLayout; @@ -155,12 +154,10 @@ void AudioPane::CreateWidgets() m_wasapi_device_combo = new QComboBox; m_wasapi_device_sample_rate_combo = new QComboBox; - //To use AudioCommon::GetDefaultSampleRate(). Don't select anything below 48kHz! + m_wasapi_device_combo->setToolTip(tr("Some devices might not work with WASAPI Exclusive mode")); m_wasapi_device_sample_rate_combo->setToolTip( - tr("This will not only output at the specified sample rate, \nit will also run the internal " - "mixer at that same sample rate.\nSelecting anything above 48kHz will have very minimal " - "advanced stretching won't work if you don't see a high (or low?) enough backend latency?" - "improvements on sound quality, at the cost of performance.\nAdvantages will only be seen on 32kHz games or if you unlimit the FPS.\nThis is for 2 channel 16bit, surround will try to use the same or fallback to 48000.")); + tr("Output (and mix) sample rate.\nAnything above 48kHz will have very minimal " + "improvements to quality at the cost of performance.")); backend_layout->addRow(m_wasapi_device_label, m_wasapi_device_combo); backend_layout->addRow(m_wasapi_device_sample_rate_label, m_wasapi_device_sample_rate_combo); @@ -192,7 +189,7 @@ void AudioPane::CreateWidgets() "too high (>40), sound will lose quality when we slow down or stutter.\nIf set too low (<10), " "sound might " "lose quality if you have frequent small stutters.\nSet 0 to " - "have it on all the times. Slide all the way left to disable.")); //To find best default + "have it on all the times. Slide all the way left to disable.")); //To find best default (and review description at 0) mixer_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); mixer_layout->addWidget(m_emu_speed_tolerance_label, 1, 0); @@ -265,7 +262,6 @@ void AudioPane::LoadSettings() m_backend_combo->addItem(tr(backend.c_str()), QVariant(QString::fromStdString(backend))); if (backend == current) { - //To specify that WASAPI (or any) might not work on all devices and stops other sounds m_backend_combo->setCurrentIndex(m_backend_combo->count() - 1); selection_set = true; } @@ -288,8 +284,7 @@ void AudioPane::LoadSettings() GetDPL2ApproximateLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); if (AudioCommon::SupportsDPL2Decoder(current) && !m_dsp_hle->isChecked()) { - EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked() && - !m_running); + EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); } // Latency @@ -353,6 +348,7 @@ void AudioPane::SaveSettings() bool volume_changed = false; bool backend_setting_changed = false; + bool surround_enabled_changed = false; // DSP if (SConfig::GetInstance().bDSPHLE != m_dsp_hle->isChecked() || @@ -390,6 +386,7 @@ void AudioPane::SaveSettings() { SConfig::GetInstance().bDPL2Decoder = m_dolby_pro_logic->isChecked(); backend_setting_changed = true; + surround_enabled_changed = true; } if (Config::Get(Config::MAIN_DPL2_QUALITY) != static_cast(m_dolby_quality_slider->value())) @@ -398,14 +395,11 @@ void AudioPane::SaveSettings() static_cast(m_dolby_quality_slider->value())); m_dolby_quality_latency_label->setText( GetDPL2ApproximateLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); - backend_setting_changed = true; + backend_setting_changed = true; // Not a mistake } if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) { - bool backend_supports_runtime_changes = AudioCommon::SupportsRuntimeSettingsChanges(); - bool enable_dolby_pro_logic = !m_running || backend_supports_runtime_changes; - EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked() && - enable_dolby_pro_logic); + EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); } // Latency @@ -416,7 +410,11 @@ void AudioPane::SaveSettings() } // Sample rate - SConfig::GetInstance().bUseOSMixerSampleRate = m_use_os_sample_rate->isChecked(); + if (m_use_os_sample_rate->isChecked() != SConfig::GetInstance().bUseOSMixerSampleRate) + { + SConfig::GetInstance().bUseOSMixerSampleRate = m_use_os_sample_rate->isChecked(); + backend_setting_changed = true; + } // Stretch SConfig::GetInstance().m_audio_stretch = m_stretching_enable->isChecked(); @@ -466,10 +464,8 @@ void AudioPane::SaveSettings() } #endif - if (volume_changed || backend_setting_changed) - { - AudioCommon::UpdateSoundStreamSettings(!backend_setting_changed); - } + AudioCommon::UpdateSoundStreamSettings(volume_changed, backend_setting_changed, + surround_enabled_changed); } void AudioPane::OnDspChanged() @@ -478,8 +474,7 @@ void AudioPane::OnDspChanged() m_dolby_pro_logic->setEnabled(AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()); - EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked() && - !m_running); + EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); } void AudioPane::OnBackendChanged() @@ -487,10 +482,11 @@ void AudioPane::OnBackendChanged() const auto backend = SConfig::GetInstance().sBackend; m_use_os_sample_rate->setEnabled(backend != BACKEND_NULLSOUND); + m_dolby_pro_logic->setEnabled(AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()); - EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked() && - !m_running); //To add other m_running checks + EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); + if (m_latency_control_supported) { m_latency_label->setEnabled(AudioCommon::SupportsLatencyControl(backend)); @@ -543,10 +539,8 @@ void AudioPane::OnWASAPIDeviceChanged() m_wasapi_device_sample_rate_combo->setEnabled(true); m_wasapi_device_sample_rate_label->setEnabled(true); - std::string default_sample_rate_text = "Default Dolphin Sample Rate (" + - std::to_string(AudioCommon::GetDefaultSampleRate()) + - "Hz)"; - m_wasapi_device_sample_rate_combo->addItem(tr(default_sample_rate_text.c_str())); + m_wasapi_device_sample_rate_combo->addItem( + tr("Default Dolphin Sample Rate (%1Hz)").arg(AudioCommon::GetDefaultSampleRate())); for (const auto deviceSampleRate : WASAPIStream::GetSelectedDeviceSampleRates()) { @@ -558,21 +552,21 @@ void AudioPane::OnWASAPIDeviceChanged() { m_wasapi_device_sample_rate_combo->setEnabled(false); m_wasapi_device_sample_rate_label->setEnabled(false); - std::string sample_rate_text; if (m_running) { + std::string sample_rate_text; sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate + "Hz"; if (sample_rate_text == "0Hz") { sample_rate_text = std::to_string(AudioCommon::GetDefaultSampleRate()) + "Hz"; } + m_wasapi_device_sample_rate_combo->addItem(tr(sample_rate_text.c_str())); } else { - sample_rate_text = - "Select a Device (" + std::to_string(AudioCommon::GetDefaultSampleRate()) + "Hz)"; + m_wasapi_device_sample_rate_combo->addItem( + tr("Select a Device (%1Hz)").arg(AudioCommon::GetDefaultSampleRate())); } - m_wasapi_device_sample_rate_combo->addItem(tr(sample_rate_text.c_str())); } m_ignore_save_settings = false; @@ -605,37 +599,41 @@ void AudioPane::OnEmulationStateChanged(bool running) const auto backend = SConfig::GetInstance().sBackend; - bool backend_supports_runtime_changes = AudioCommon::SupportsRuntimeSettingsChanges(); + bool supports_current_emulation_state = + !running || AudioCommon::BackendSupportsRuntimeSettingsChanges(); m_dsp_hle->setEnabled(!running); m_dsp_lle->setEnabled(!running); m_dsp_interpreter->setEnabled(!running); m_backend_label->setEnabled(!running); m_backend_combo->setEnabled(!running); - m_use_os_sample_rate->setEnabled(!running && backend != BACKEND_NULLSOUND); - //To review this and all the m_dsp_hle->isChecked() + m_use_os_sample_rate->setEnabled(supports_current_emulation_state && + backend != BACKEND_NULLSOUND); + if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) { - bool enable_dolby_pro_logic = !running || backend_supports_runtime_changes; + // TODO: disable this later if the audio device turned out unable to support surround + bool enable_dolby_pro_logic = supports_current_emulation_state; m_dolby_pro_logic->setEnabled(enable_dolby_pro_logic); EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); } if (m_latency_control_supported && AudioCommon::SupportsLatencyControl(SConfig::GetInstance().sBackend)) { - bool enable_latency = (!running || backend_supports_runtime_changes) && - AudioCommon::SupportsLatencyControl(backend); + bool enable_latency = + supports_current_emulation_state && AudioCommon::SupportsLatencyControl(backend); m_latency_label->setEnabled(enable_latency); m_latency_spin->setEnabled(enable_latency); } #ifdef _WIN32 - m_wasapi_device_label->setEnabled(!running || backend_supports_runtime_changes); - m_wasapi_device_combo->setEnabled(!running || backend_supports_runtime_changes); - bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default"; + m_wasapi_device_label->setEnabled(supports_current_emulation_state); + m_wasapi_device_combo->setEnabled(supports_current_emulation_state); + bool canSelectDeviceSampleRate = + SConfig::GetInstance().sWASAPIDevice != "default" && supports_current_emulation_state; m_wasapi_device_sample_rate_label->setEnabled(canSelectDeviceSampleRate); - m_wasapi_device_sample_rate_combo->setEnabled(canSelectDeviceSampleRate); //To allow changes at runtime now? + m_wasapi_device_sample_rate_combo->setEnabled(canSelectDeviceSampleRate); bool is_wasapi = backend == BACKEND_WASAPI; if (is_wasapi) { diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp index db8e85001a18..ee427d030c55 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.cpp +++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp @@ -4,6 +4,8 @@ #include "InputCommon/ControlReference/ControlReference.h" +#include + #include "Core/Host.h" using namespace ciface::ExpressionParser; @@ -100,8 +102,8 @@ std::optional ControlReference::SetExpression(std::string expr) } ControlReference::ControlReference() - : range(1.0), default_range(1.0), m_parse_status(ParseStatus::EmptyExpression), - m_parsed_expression(nullptr) + : range(1.0), default_range(1.0), m_parsed_expression(nullptr), + m_parse_status(ParseStatus::EmptyExpression) { } diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index abd0d86e4383..a0200eb3ea7b 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -267,16 +267,16 @@ void ControllerInterface::RemoveDevice(std::functionUpdateInput(); - should_update_mouse_axis = false; + should_update_fake_relative_axes = false; } } diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp index 90288a251d5a..78f9830b8531 100644 --- a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp @@ -126,7 +126,7 @@ class Device final : public Core::Device bool IsDetectable() const override { return false; } // We don't need focus to pass the battery level - virtual FocusFlags GetFocusFlags() const { return FocusFlags::IgnoreFocus; } + FocusFlags GetFocusFlags() const override { return FocusFlags::IgnoreFocus; } private: const BatteryState& m_battery; diff --git a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h index 4884b26ebd0d..1024280ebdd5 100644 --- a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h +++ b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h @@ -38,9 +38,9 @@ class KeyboardAndMouse : public Core::Device std::string GetName() const override; bool IsDetectable() const override { return false; } ControlState GetState() const override; - Flags GetFlags() const override + FocusFlags GetFocusFlags() const override { - return (Flags)((u8)Flags::RequiresFocus | (u8)Flags::RequiresFullFocus); + return FocusFlags((u8(FocusFlags::RequireFocus) | u8(FocusFlags::RequireFullFocus))); } private: @@ -55,10 +55,10 @@ class KeyboardAndMouse : public Core::Device explicit Button(CGMouseButton button) : m_button(button) {} std::string GetName() const override; ControlState GetState() const override; - Flags GetFlags() const override + FocusFlags GetFocusFlags() const override { - return (Flags)((u8)Flags::RequiresFocus | (u8)Flags::RequiresFullFocus | - (u8)Flags::IgnoreOnFocusAcquired); + return FocusFlags(u8(FocusFlags::RequireFocus) | u8(FocusFlags::RequireFullFocus) | + u8(FocusFlags::IgnoreOnFocusChanged)); } private: diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h index f2de63cf2bbe..a98d580465a3 100644 --- a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h @@ -55,10 +55,10 @@ class KeyboardMouse : public Core::Device std::string GetName() const override { return name; } Button(unsigned int index, unsigned int* buttons); ControlState GetState() const override; - Flags GetFlags() const override + FocusFlags GetFocusFlags() const override { - return (Flags)((u8)Flags::RequiresFocus | (u8)Flags::RequiresFullFocus | - (u8)Flags::IgnoreOnFocusAcquired); + return FocusFlags(u8(FocusFlags::RequireFocus) | u8(FocusFlags::RequireFullFocus) | + u8(FocusFlags::IgnoreOnFocusChanged)); } private: @@ -74,9 +74,9 @@ class KeyboardMouse : public Core::Device bool IsDetectable() const override { return false; } Cursor(u8 index, bool positive, const float* cursor); ControlState GetState() const override; - Flags GetFlags() const override + FocusFlags GetFocusFlags() const override { - return (Flags)((u8)Flags::RequiresFocus | (u8)Flags::RequiresFullFocus); + return FocusFlags((u8(FocusFlags::RequireFocus) | u8(FocusFlags::RequireFullFocus))); } private: @@ -93,9 +93,9 @@ class KeyboardMouse : public Core::Device bool IsDetectable() const override { return false; } Axis(u8 index, bool positive, const float* axis); ControlState GetState() const override; - Flags GetFlags() const override + FocusFlags GetFocusFlags() const override { - return (Flags)((u8)Flags::RequiresFocus | (u8)Flags::RequiresFullFocus); + return FocusFlags(u8(FocusFlags::RequireFocus) | u8(FocusFlags::RequireFullFocus)); } private: From 873661dc33335d55ed507945a9f33f752983674e Mon Sep 17 00:00:00 2001 From: Filoppi Date: Mon, 27 Jul 2020 02:42:17 +0300 Subject: [PATCH 13/56] Fixed build --- Source/Core/AudioCommon/AudioSpeedCounter.cpp | 2 +- Source/Core/AudioCommon/CubebStream.cpp | 13 +++++++------ Source/Core/AudioCommon/CubebStream.h | 7 ++++++- Source/Core/Core/ConfigManager.cpp | 4 ++-- Source/Core/Core/ConfigManager.h | 2 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 6 +++--- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Source/Core/AudioCommon/AudioSpeedCounter.cpp b/Source/Core/AudioCommon/AudioSpeedCounter.cpp index 2745c17333ef..ff3b527d84b3 100644 --- a/Source/Core/AudioCommon/AudioSpeedCounter.cpp +++ b/Source/Core/AudioCommon/AudioSpeedCounter.cpp @@ -41,7 +41,7 @@ void AudioSpeedCounter::OnSettingsChanged() m_target_delta = m_ticks_per_upd / m_ticks_per_sec; // Keep the previous speed by adjusting the last deltas const double relative_change = m_target_delta / prev_target_delta; - for (int i = 0; i < m_last_deltas.size(); ++i) + for (size_t i = 0; i < m_last_deltas.size(); ++i) { m_last_deltas[i] *= relative_change; } diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index 7322069550f4..ab8b3a199d04 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -41,8 +41,11 @@ bool CubebStream::SetRunning(bool running) { assert(running != m_running); + if (m_settings_changed) + m_settings_changed = false; m_should_restart = false; + //To fix: cubeb stutters on startup (pause/unpause), make sure sure reset is only called if settings have changed if (running) { m_mixer->UpdateSettings(SConfig::GetInstance().bUseOSMixerSampleRate ? @@ -72,16 +75,14 @@ bool CubebStream::SetRunning(bool running) if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) ERROR_LOG_FMT(AUDIO, "Error getting minimum latency"); - u32 final_latency; - - uint32_t target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; + const u32 target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; #ifdef _WIN32 // WASAPI supports up to 5000ms but let's clamp to 500ms - uint32_t max_latency = 500 / 1000.0 * params.rate; + const u32 max_latency = 500 / 1000.0 * params.rate; // This doesn't actually seem to work, latency is ignored on Window 10 - final_latency = std::clamp(target_latency, minimum_latency, max_latency); + const u32 final_latency = std::clamp(target_latency, minimum_latency, max_latency); #else - final_latency = std::clamp(target_latency, minimum_latency, 96000); + const u32 final_latency = std::clamp(target_latency, minimum_latency, 96000u); #endif INFO_LOG(AUDIO, "Latency: %u frames", final_latency); diff --git a/Source/Core/AudioCommon/CubebStream.h b/Source/Core/AudioCommon/CubebStream.h index 9086f20b9cd2..46bc09c7ca79 100644 --- a/Source/Core/AudioCommon/CubebStream.h +++ b/Source/Core/AudioCommon/CubebStream.h @@ -23,7 +23,11 @@ class CubebStream final : public SoundStream void SetVolume(int) override; bool SupportsRuntimeSettingsChanges() const override { return true; } - void OnSettingsChanged() override { m_should_restart = true; } + void OnSettingsChanged() override + { + m_settings_changed = true; + m_should_restart = true; + } private: bool m_stereo = false; @@ -35,6 +39,7 @@ class CubebStream final : public SoundStream std::atomic m_running = false; std::atomic m_should_restart = false; + std::atomic m_settings_changed = false; static long DataCallback(cubeb_stream* stream, void* user_data, const void* /*input_buffer*/, void* output_buffer, long num_frames); diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 4d5aeb52aa2c..5378f46e53f8 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -484,7 +484,7 @@ void SConfig::LoadCoreSettings(IniFile& ini) core->Get("AudioLatency", &iLatency, 20); core->Get("UseOSMixerSampleRate", &bUseOSMixerSampleRate, false); core->Get("AudioStretch", &m_audio_stretch, false); - core->Get("AudioEmuSpeedTolerance", &m_audio_emu_speed_tolerance, 10); + core->Get("AudioEmuSpeedTolerance", &m_audio_emu_speed_tolerance, 20); core->Get("AgpCartAPath", &m_strGbaCartA); core->Get("AgpCartBPath", &m_strGbaCartB); core->Get("SlotA", (int*)&m_EXIDevice[0], ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER); @@ -769,7 +769,7 @@ void SConfig::LoadDefaults() bDPL2Decoder = false; iLatency = 20; m_audio_stretch = false; - m_audio_emu_speed_tolerance = 10; + m_audio_emu_speed_tolerance = 20; bUsePanicHandlers = true; bOnScreenDisplayMessages = true; diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 7f7cb0e42899..1ffbbe3804d6 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -129,7 +129,7 @@ struct SConfig int iLatency = 20; bool bUseOSMixerSampleRate = false; bool m_audio_stretch = false; - int m_audio_emu_speed_tolerance = 10; + int m_audio_emu_speed_tolerance = 20; bool bRunCompareServer = false; bool bRunCompareClient = false; diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 4ee382112ad4..357d34c97850 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -182,14 +182,14 @@ void AudioPane::CreateWidgets() tr("Enables stretching of the audio (pitch correction) to match the emulation speed")); m_emu_speed_tolerance_slider->setMinimum(-1); - m_emu_speed_tolerance_slider->setMaximum(100); + m_emu_speed_tolerance_slider->setMaximum(125); m_emu_speed_tolerance_slider->setToolTip( tr("Time(ms) we need to fall behind the emulation for sound to start using the actual " "emulation speed.\nIf set " - "too high (>40), sound will lose quality when we slow down or stutter.\nIf set too low (<10), " + "too high (>40), sound will play old samples backwards when we slow down or stutter.\nIf set too low (<10), " "sound might " "lose quality if you have frequent small stutters.\nSet 0 to " - "have it on all the times. Slide all the way left to disable.")); //To find best default (and review description at 0) + "have it on all the times. Slide all the way left to disable.")); //To make sure you got the best value... mixer_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); mixer_layout->addWidget(m_emu_speed_tolerance_label, 1, 0); From 1a166bf3062fbeb5093b677e872d631f2af6b118 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Mon, 27 Jul 2020 23:05:48 +0300 Subject: [PATCH 14/56] FIxes: cubeb, mixer, coding standards --- Source/Core/AudioCommon/AudioSpeedCounter.h | 2 +- Source/Core/AudioCommon/CubebStream.cpp | 150 ++++++++++-------- Source/Core/AudioCommon/CubebStream.h | 3 + Source/Core/AudioCommon/Mixer.cpp | 63 +++----- Source/Core/AudioCommon/Mixer.h | 4 +- Source/Core/AudioCommon/OpenALStream.cpp | 7 +- Source/Core/AudioCommon/SurroundDecoder.cpp | 37 ++--- Source/Core/AudioCommon/SurroundDecoder.h | 2 +- Source/Core/AudioCommon/WASAPIStream.cpp | 37 +++-- Source/Core/Common/MathUtil.h | 8 + Source/Core/DolphinQt/Settings/AudioPane.cpp | 29 ++-- Source/Core/DolphinQt/Settings/AudioPane.h | 2 +- .../ControlGroup/ControlGroup.cpp | 3 +- .../InputCommon/ControllerEmu/StickGate.h | 3 +- .../ControllerInterface.cpp | 2 - .../ControllerInterface/CoreDevice.h | 4 +- 16 files changed, 184 insertions(+), 172 deletions(-) diff --git a/Source/Core/AudioCommon/AudioSpeedCounter.h b/Source/Core/AudioCommon/AudioSpeedCounter.h index 0a481f95eab7..1bc4ffc8f5d9 100644 --- a/Source/Core/AudioCommon/AudioSpeedCounter.h +++ b/Source/Core/AudioCommon/AudioSpeedCounter.h @@ -31,7 +31,7 @@ struct AudioSpeedCounter double GetAverageTime() const { return m_average_time; } double GetTargetDelta() const { return m_target_delta; } void SetPaused(bool paused); - bool IsPaused() { return m_is_paused; } + bool IsPaused() const { return m_is_paused; } // Sets the length time to count the speed for void SetAverageTime(double average_time); void SetTicksPerSecond(double ticks_per_sec); diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index ab8b3a199d04..85b445e50d02 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -2,13 +2,14 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include "AudioCommon/CubebStream.h" + #include #include -#include "AudioCommon/CubebStream.h" -#include "AudioCommon/CubebUtils.h" #include "AudioCommon/AudioCommon.h" +#include "AudioCommon/CubebUtils.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/Thread.h" @@ -34,81 +35,99 @@ void CubebStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_sta bool CubebStream::Init() { m_ctx = CubebUtils::GetContext(); - return m_ctx != nullptr; + if (m_ctx) + { + return CreateStream(); + } + return false; } -bool CubebStream::SetRunning(bool running) +// Cubeb settings can be changed while the stream is ongoing so, in that case, +// we want to easily re-create it, but we didn't want to do it every time the emulation +// was paused and unpaused as it causes a small audio stutter +bool CubebStream::CreateStream() { - assert(running != m_running); + m_mixer->UpdateSettings(SConfig::GetInstance().bUseOSMixerSampleRate ? + AudioCommon::GetOSMixerSampleRate() : + AudioCommon::GetDefaultSampleRate()); - if (m_settings_changed) - m_settings_changed = false; - m_should_restart = false; + m_stereo = !SConfig::GetInstance().ShouldUseDPL2Decoder(); - //To fix: cubeb stutters on startup (pause/unpause), make sure sure reset is only called if settings have changed - if (running) + cubeb_stream_params params; + params.rate = m_mixer->GetSampleRate(); + if (m_stereo) { - m_mixer->UpdateSettings(SConfig::GetInstance().bUseOSMixerSampleRate ? - AudioCommon::GetOSMixerSampleRate() : - AudioCommon::GetDefaultSampleRate()); - - m_stereo = !SConfig::GetInstance().ShouldUseDPL2Decoder(); - - cubeb_stream_params params; - params.rate = m_mixer->GetSampleRate(); - if (m_stereo) - { - params.channels = 2; - params.format = CUBEB_SAMPLE_S16NE; - params.layout = CUBEB_LAYOUT_STEREO; - } - else - { - params.channels = 6; - params.format = CUBEB_SAMPLE_FLOAT32NE; - params.layout = CUBEB_LAYOUT_3F2_LFE; - } + params.channels = 2; + params.format = CUBEB_SAMPLE_S16NE; + params.layout = CUBEB_LAYOUT_STEREO; + } + else + { + params.channels = 6; + params.format = CUBEB_SAMPLE_FLOAT32NE; + params.layout = CUBEB_LAYOUT_3F2_LFE; + } - // In samples - // Max supported by cubeb is 96000 and min is 1 (in samples) - u32 minimum_latency = 0; - if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) - ERROR_LOG_FMT(AUDIO, "Error getting minimum latency"); + // In samples + // Max supported by cubeb is 96000 and min is 1 (in samples) + u32 minimum_latency = 0; + if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) + ERROR_LOG_FMT(AUDIO, "Error getting minimum latency"); - const u32 target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; #ifdef _WIN32 - // WASAPI supports up to 5000ms but let's clamp to 500ms - const u32 max_latency = 500 / 1000.0 * params.rate; - // This doesn't actually seem to work, latency is ignored on Window 10 - const u32 final_latency = std::clamp(target_latency, minimum_latency, max_latency); + // Custom latency is ignored in Windows, despite being exposed (always 10ms, WASAPI max is 5000ms) + const u32 final_latency = minimum_latency; #else - const u32 final_latency = std::clamp(target_latency, minimum_latency, 96000u); + const u32 target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; + const u32 final_latency = std::clamp(target_latency, minimum_latency, 96000u); #endif - INFO_LOG(AUDIO, "Latency: %u frames", final_latency); + INFO_LOG(AUDIO, "Latency: %u frames", final_latency); - // It's very hard for cubeb to fail starting so we don't trigger a restart request - if (cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, nullptr, - ¶ms, final_latency, DataCallback, StateCallback, this) == CUBEB_OK) + return cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, + nullptr, ¶ms, final_latency, DataCallback, StateCallback, + this) == CUBEB_OK; +} + +void CubebStream::DestroyStream() +{ + // Can't fail + cubeb_stream_destroy(m_stream); + m_stream = nullptr; +} + +bool CubebStream::SetRunning(bool running) +{ + assert(running != m_running); + + m_should_restart = false; + if (m_settings_changed && running) + { + m_settings_changed = false; + DestroyStream(); + // It's very hard for cubeb to fail starting a stream so we don't trigger a restart request + if (!CreateStream()) { - if (cubeb_stream_start(m_stream) == CUBEB_OK) - { - m_running = true; - return true; - } + return false; + } + } + + if (running) + { + if (cubeb_stream_start(m_stream) == CUBEB_OK) + { + m_running = true; + return true; } return false; } else { - int result = cubeb_stream_stop(m_stream); - cubeb_stream_destroy(m_stream); - if (result == CUBEB_OK) + if (cubeb_stream_stop(m_stream) == CUBEB_OK) + { m_running = false; - else - ERROR_LOG(AUDIO, "Cubeb failed to stop. Dolphin might crash"); - // Not sure how to proceed here. Destroying cubeb can't fail but stopping it can? - // Does destroy imply stopping? Probably, but is it safe? - return result == CUBEB_OK; + return true; + } + return false; } } @@ -116,15 +135,15 @@ CubebStream::~CubebStream() { if (m_running) SetRunning(false); + DestroyStream(); m_ctx.reset(); } void CubebStream::Update() { - // TODO: move this out of the game (main) thread, restarting WASAPI should not lock the game + // TODO: move this out of the game (main) thread, restarting cubeb should not lock the game // thread, but as of now there isn't any other constant and safe access point to restart - // If the sound loop failed for some reason, re-initialize ASAPI to resume playback if (m_should_restart) { m_should_restart = false; @@ -132,19 +151,14 @@ void CubebStream::Update() { // We need to pass through AudioCommon as it has a mutex and // to make sure s_sound_stream_running is updated - if (AudioCommon::SetSoundStreamRunning(false, false)) + if (AudioCommon::SetSoundStreamRunning(false)) { - // m_should_restart is triggered when the device is currently - // invalidated, and it will stay for a while, so this new call - // to SetRunning(true) might fail, but if it fails some - // specific reasons, it will set m_should_restart true again. - // A Sleep(10) call also seemed to fix the problem but it's hacky. - AudioCommon::SetSoundStreamRunning(true, false); + AudioCommon::SetSoundStreamRunning(true); } } else { - AudioCommon::SetSoundStreamRunning(true, false); + AudioCommon::SetSoundStreamRunning(true); } } } diff --git a/Source/Core/AudioCommon/CubebStream.h b/Source/Core/AudioCommon/CubebStream.h index 46bc09c7ca79..1e16f3b3aae1 100644 --- a/Source/Core/AudioCommon/CubebStream.h +++ b/Source/Core/AudioCommon/CubebStream.h @@ -30,6 +30,9 @@ class CubebStream final : public SoundStream } private: + bool CreateStream(); + void DestroyStream(); + bool m_stereo = false; std::shared_ptr m_ctx; cubeb_stream* m_stream = nullptr; diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 5e1a6f3ef182..33908e75cdf4 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -2,18 +2,16 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include "AudioCommon/Mixer.h" //To try to delete some includes and see if it builds +#include "AudioCommon/Mixer.h" #include #include //To delete #include #include -#include #include #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" -#include "Common/MathUtil.h" #include "Common/Swap.h" #include "Core/Core.h" #include "Core/Config/MainSettings.h" @@ -21,7 +19,7 @@ #include "Common/Logging/Log.h" //To delete and all the uses (or make debug log) #include "VideoCommon/OnScreenDisplay.h" //To delete and all the uses -//#pragma optimize("", off) //To delete +#pragma optimize("", off) //To delete Mixer::Mixer(u32 sample_rate) : m_sample_rate(sample_rate), m_stretcher(sample_rate), @@ -48,12 +46,8 @@ Mixer::~Mixer() void Mixer::SetPaused(bool paused) { - //To review: this might not be thread safe? SetPaused is called from the main thread while the DMA is already pushing from the emu thread - if (!m_dma_speed.IsPaused() && !paused) - { - // This happens on game start. The backend has already started reading actually - m_dma_speed.Start(true); - } + // It would be nice to call m_dma_speed.Start(true) if m_dma_speed and paused are false, + // but it doesn't seem to be thread safe (needs more investigation, but it's not necessary) m_dma_speed.SetPaused(paused); } @@ -66,7 +60,7 @@ void Mixer::DoState(PointerWrap& p) m_wiimote_speaker_mixer[2].DoState(p); m_wiimote_speaker_mixer[3].DoState(p); - if (p.GetMode() == PointerWrap::MODE_READ) //To review, needs to be a state + if (p.GetMode() == PointerWrap::MODE_READ) { m_dma_speed.SetTicksPerSecond(m_dma_mixer.GetInputSampleRate()); // We could reset a few things here but it would require too much thread synchronization @@ -83,7 +77,6 @@ void Mixer::UpdateSettings(u32 sample_rate) m_surround_decoder.Clear(); } m_surround_decoder.InitAndSetSampleRate(m_sample_rate); - //To do: reset m_fract, and also DPLII. Add method to reset DPLII } // render num_samples sample pairs to samples[] @@ -145,8 +138,9 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) s32 behind_samples = num_samples - actual_samples_count; // This might sound bad if we are constantly missing a few samples, but that should never happen, - // and we couldn't predict it anyway (meaning we should start playing backwards as soon as we can) - if (behind_samples > 0 && m_constantly_pushed && !stretching) //To add a setting to disable this? + // and we couldn't predict it anyway (we should start playing backwards as soon as we can). + // If required, this could be disabled from a config + if (behind_samples > 0 && m_constantly_pushed && !stretching) { rate = m_input_sample_rate / m_mixer->m_sample_rate; //To review (this should actually follow the rate but with no prediction...) s16* back_samples = samples + actual_samples_count * NC; @@ -190,7 +184,7 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) lVolume, rVolume, false); } // Padding (constantly pushing the last sample when we run out to avoid sudden changes in the - // audio wave). This is only needed on mixers that don't constantly push but are currntly pushing, + // audio wave). This is only needed on mixers that don't constantly push but are currently pushing, // as they can't play samples backwards else if (behind_samples > 0 && (m_constantly_pushed || m_currently_pushed)) { @@ -308,7 +302,7 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r y3 * interpolation_buffer[(indexR + 6 * direction + 1) & INDEX_MASK]; //To test left and right on HW source - l_s = (s32(std::round(l_s_f)) * lVolume) >> 8; //To round again post vol? //To use std::rint? + l_s = (s32(std::round(l_s_f)) * lVolume) >> 8; //To round again post vol? r_s = (s32(std::round(r_s_f)) * rVolume) >> 8; // Clamp after adding to current sample, as if the cubic interpolation produced a sample over // the limits and the current sample has the opposite sign, then we'd keep the excess value, @@ -372,7 +366,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) if (!frame_limiter) { m_time_behind_target_speed = 0.0; //To maybe find a way to maintain this during unlimited frame rate? - m_time_below_target_speed_growing = false; + m_time_behind_target_speed_growing = false; //To review: this can start over 30 in the first frames of game... target_speed = m_dma_speed.GetCachedAverageSpeed(true, true, true); m_time_at_custom_speed = m_time_at_custom_speed + time_delta; @@ -387,17 +381,18 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) else if (actual_speed / emulation_speed < 1.0 - FallbackDelta) { //To review: this keeps trigger and going back to normal on GC due to the uneven sample rates? It also does NOT work with DPLII at the moment - // Note: when not able to reach the target speed before m_time_below_target_speed_growing triggers, you might hear crackles due to (probably) having finished the samples + //To review: what happens when m_audio_emu_speed_tolerance is 0??? + // Note: when not able to reach the target speed before m_time_behind_target_speed_growing triggers, you might hear crackles due to (probably) having finished the samples //To review: this happens more often at higher backend latencies, smooth it over time? Ignore first missed frame? // If we fell behind of m_time_behind_target_speed seconds of samples, start using the actual emulation speed m_time_behind_target_speed += time_delta * (1.0 - (actual_speed / emulation_speed)); if (SConfig::GetInstance().m_audio_emu_speed_tolerance >= 0 && m_time_behind_target_speed > SConfig::GetInstance().m_audio_emu_speed_tolerance / 1000.0) { - if (!m_time_below_target_speed_growing) - OSD::AddMessage("m_time_below_target_speed_growing = true", 2000U); + if (!m_time_behind_target_speed_growing) + OSD::AddMessage("m_time_behind_target_speed_growing = true", 2000U); //m_time_behind_target_speed = SConfig::GetInstance().NonStretchCorrectionTolerance; - m_time_below_target_speed_growing = true; //To rename + m_time_behind_target_speed_growing = true; //To rename } } else @@ -411,13 +406,13 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) static double FallbackDelta2 = 0.001; if (average_actual_speed >= emulation_speed - (FallbackDelta2 * emulation_speed)) { - if (m_time_below_target_speed_growing) - OSD::AddMessage("m_time_below_target_speed_growing = false", 2000U, OSD::Color::GREEN); - m_time_below_target_speed_growing = false; + if (m_time_behind_target_speed_growing) + OSD::AddMessage("m_time_behind_target_speed_growing = false", 2000U, OSD::Color::GREEN); + m_time_behind_target_speed_growing = false; m_time_behind_target_speed = 0; } } - if (m_time_below_target_speed_growing) + if (m_time_behind_target_speed_growing) { //To review min. Also, maybe just use the same speed the frame_limiter would above? target_speed = frame_limiter ? std::min(average_actual_speed, emulation_speed) : average_actual_speed; @@ -439,7 +434,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) // play samples backwards when we are out of new ones so these are never problems. double max_latency = Config::Get(Config::MAIN_AUDIO_MIXER_MAX_LATENCY) / 1000.0; //To read as little times as possible //To double or disable when speed is unlimited (or if we can't reach target speed), to avoid constant fluctuations (the average speed will compensate temporary imprecisions anyway) - if (!frame_limiter || m_time_below_target_speed_growing) + if (!frame_limiter || m_time_behind_target_speed_growing) { static double mult = 1.0; max_latency *= mult; @@ -471,13 +466,13 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) // (predicted), not before. Otherwise if there is a sudden change of speed in between mixes, // or if the samples pushes and reads are done with very different timings, the latency won't // be stable at all and we will end up constantly adjusting it towards a value that makes no - // sense. Also, this way we can target a latency of 0 + // sense. Also, this way we can target a latency of "0" double rate = m_dma_mixer.GetInputSampleRate() * target_speed / m_sample_rate; s32 post_mix_samples = m_dma_mixer.NumSamples(); post_mix_samples -= num_samples * rate + INTERP_SAMPLES; latency = std::max(post_mix_samples, 0) / m_dma_mixer.GetInputSampleRate(); // This isn't big enough to notice but it is enough to make a difference and recover latency - catch_up_speed = 1.015; //To review: this will be very slow if we are going at 0.1 speed already, maybe consider adding it up? + catch_up_speed = 1.015; //To review: this will be very slow if we are going at 0.1 speed already, maybe consider adding it up? Make constexpr? target_latency = max_latency * 0.5; } //INFO_LOG(AUDIO, "latency: %lf", latency); @@ -522,8 +517,8 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) } bool scratch_buffer_equal_samples = m_scratch_buffer.data() == samples; - - // If the input and output sample rates difference is too high, available_samples might over the max + + // If the in and out sample rates difference is too high, available_samples might over the max m_scratch_buffer.reserve(available_samples * NC); if (scratch_buffer_equal_samples) { @@ -833,12 +828,6 @@ void Mixer::MixerFifo::DoState(PointerWrap& p) p.Do(m_input_sample_rate); p.Do(m_lVolume); p.Do(m_rVolume); - - if (p.GetMode() == PointerWrap::MODE_READ) - { - // Restore fract to a neutral value - m_fract = -1.0; - } } void Mixer::MixerFifo::SetInputSampleRate(double rate) @@ -961,7 +950,7 @@ void Mixer::MixerFifo::UpdatePush(double time) } else { - //OSD::AddMessage("last sample: " + std::to_string(m_buffer[(m_indexW - 2) & INDEX_MASK]), 0U); + //OSD::AddMessage("last sample: " + std::to_string(m_buffer[(m_indexW - 1) & INDEX_MASK]), 0U); constexpr u32 num_samples = INTERP_SAMPLES + 1; // Add enough samples of silence to make sure when it has finished reading it won't stop on a diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 24c41a7d1ff3..99438e784980 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -134,6 +134,8 @@ class Mixer final std::atomic m_rVolume{256}; // Se comment on CubicInterpolation() s32 m_last_output_samples[NC]{}; + // It would be nice to reset once in a while, like on do state, or when the in/out sample rates + // change, as it can fall out of alignment, but it would be wrong and the gains would be minimal double m_fract = -1.0; double m_backwards_fract = -1.0; //To review: if this was off, that mixer won't gather a buffer/latency, and it would always be on the brink of playback @@ -162,7 +164,7 @@ class Mixer final // Target emulation speed, but it can fallback to the actual emulation speed double m_target_speed = 1.0; //To make atomic (m_fract as well???) (make sure they aren't used twice in a line if so) double m_time_behind_target_speed = 0.0; - bool m_time_below_target_speed_growing = false; //To rename? + bool m_time_behind_target_speed_growing = false; std::atomic m_time_at_custom_speed{0.0}; bool m_latency_catching_up = false; diff --git a/Source/Core/AudioCommon/OpenALStream.cpp b/Source/Core/AudioCommon/OpenALStream.cpp index a9f1ac90153e..ee0bf03fa76d 100644 --- a/Source/Core/AudioCommon/OpenALStream.cpp +++ b/Source/Core/AudioCommon/OpenALStream.cpp @@ -4,12 +4,13 @@ #ifdef _WIN32 +#include "AudioCommon/OpenALStream.h" + #include #include #include #include -#include "AudioCommon/OpenALStream.h" #include "AudioCommon/AudioCommon.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" @@ -120,8 +121,8 @@ bool OpenALStream::Init() } m_mixer->UpdateSettings(SConfig::GetInstance().bUseOSMixerSampleRate ? - AudioCommon::GetOSMixerSampleRate() : - AudioCommon::GetDefaultSampleRate()); + AudioCommon::GetOSMixerSampleRate() : + AudioCommon::GetDefaultSampleRate()); palcMakeContextCurrent(context); m_run_thread.Set(); diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index bded24971b88..1bc06cba9494 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -7,36 +7,19 @@ #include #include -#include "Core/Config/MainSettings.h" - #include "AudioCommon/Enums.h" #include "AudioCommon/SurroundDecoder.h" -//#pragma optimize("", off) //To delete +#include "Common/MathUtil.h" +#include "Core/Config/MainSettings.h" + +#pragma optimize("", off) //To delete namespace AudioCommon { -static s32 NearestPowerOfTwo(s32 n) -{ - assert(n > 1); - s32 v = n; - - v--; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - v++; // next power of 2 - - s32 x = v >> 1; // previous power of 2 - - return (v - n) > (n - x) ? x : v; -} - // Quality (higher quality also means more latency). Needs to be a pow of 2 so we find the closest static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) { - u32 frame_block_time; // ms + u32 frame_block_time; // ms switch (quality) { case DPL2Quality::Lowest: @@ -54,8 +37,9 @@ static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) default: frame_block_time = 20; } - u32 frame_block = std::round(sample_rate * frame_block_time / 1000.0); - return NearestPowerOfTwo(frame_block); + const u32 frame_block = std::round(sample_rate * frame_block_time / 1000.0); + assert(frame_block > 1); + return MathUtil::NearestPowerOf2(frame_block); } u32 SurroundDecoder::QuerySamplesNeededForSurroundOutput(u32 output_samples) const @@ -110,6 +94,7 @@ void SurroundDecoder::Clear() // Receive and decode samples void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) { + //To make sure assert(num_samples % m_frame_block_size == 0); // We support a max of MAX_BLOCKS_BUFFERED blocks in the buffer, because of m_decoded_fifo, // just increase if you need. This might trigger if you have very high backend latencies @@ -153,8 +138,8 @@ void SurroundDecoder::GetDecodedSamples(float* out, u32 num_samples) { // TODO: this could be optimized by copying the ring buffer in two parts // Copy to output array with desired num_samples - for (size_t i = 0, num_samples_output = num_samples * SURROUND_CHANNELS; - i < num_samples_output; ++i) + for (size_t i = 0, num_samples_output = num_samples * SURROUND_CHANNELS; i < num_samples_output; + ++i) { out[i] = m_decoded_fifo.pop_front(); } diff --git a/Source/Core/AudioCommon/SurroundDecoder.h b/Source/Core/AudioCommon/SurroundDecoder.h index 0e20aa0240bd..b4b91cd1918e 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.h +++ b/Source/Core/AudioCommon/SurroundDecoder.h @@ -14,6 +14,7 @@ class DPL2FSDecoder; namespace AudioCommon { +// GC/Wii encodes sounds for DPLII, so we only support 5.1, anything more wouldn't add anything class SurroundDecoder { public: @@ -39,5 +40,4 @@ class SurroundDecoder std::array m_float_conversion_buffer; FixedSizeQueue m_decoded_fifo; }; - } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 404cf621d10e..16faf152227e 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -432,8 +432,9 @@ std::vector WASAPIStream::GetSelectedDeviceSampleRates() } HandleWinAPI("Found no supported device formats, will default to " + - std::to_string(AudioCommon::GetDefaultSampleRate()) + - " whether supported or not", result); + std::to_string(AudioCommon::GetDefaultSampleRate()) + + " whether supported or not", + result); device->Release(); audio_client->Release(); @@ -530,10 +531,12 @@ bool WASAPIStream::SetRunning(bool running) m_format.Format.nAvgBytesPerSec = m_format.Format.nSamplesPerSec * m_format.Format.nBlockAlign; m_format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); m_format.Samples.wValidBitsPerSample = m_format.Format.wBitsPerSample; + // clang-format off m_format.dwChannelMask = m_surround ? (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT ): (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT ); + // clang-format on // Some sound cards might support KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, which would avoid a // conversion when using DPLII, though I didn't think it was worth implementing m_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; @@ -609,7 +612,7 @@ bool WASAPIStream::SetRunning(bool running) device->Release(); return false; } - + // Clamp by the minimum supported device period (latency). // Microsoft said it would automatically be clamped but setting // device_period to 1 or 0 failed the IAudioClient::Initialize. @@ -637,9 +640,10 @@ bool WASAPIStream::SetRunning(bool running) // Fallback to stereo if surround 5.1 is not supported if (m_surround && result == AUDCLNT_E_UNSUPPORTED_FORMAT) { - WARN_LOG(AUDIO, "WASAPI: Your current audio device doesn't support 5.1 16-bit %uHz" - " PCM audio. Will fallback to 2.0 16-bit", - GetMixer()->GetSampleRate()); + WARN_LOG(AUDIO, + "WASAPI: Your current audio device doesn't support 5.1 16-bit %uHz" + " PCM audio. Will fallback to 2.0 16-bit", + GetMixer()->GetSampleRate()); m_surround = false; m_format.Format.nChannels = STEREO_CHANNELS; @@ -647,18 +651,19 @@ bool WASAPIStream::SetRunning(bool running) m_format.Format.nAvgBytesPerSec = m_format.Format.nSamplesPerSec * m_format.Format.nBlockAlign; m_format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; - + result = m_audio_client->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, stream_flags, device_period, device_period, &m_format.Format, nullptr); } - // TODO: just like we fallback here above if we don't support surround, we should add support for - // more devices (e.g. bitrate, channels, format) by falling back as well + // TODO: just like we fallback here above if we don't support surround, we should add support + // for more devices (e.g. bitrate, channels, format) by falling back as well if (result == AUDCLNT_E_UNSUPPORTED_FORMAT) { - WARN_LOG(AUDIO, "WASAPI: Your current audio device doesn't support 2.0 16-bit %uHz" - " PCM audio. Exclusive mode (event driven) won't work", - GetMixer()->GetSampleRate()); + WARN_LOG(AUDIO, + "WASAPI: Your current audio device doesn't support 2.0 16-bit %uHz" + " PCM audio. Exclusive mode (event driven) won't work", + GetMixer()->GetSampleRate()); device->Release(); m_audio_client->Release(); @@ -837,7 +842,8 @@ void WASAPIStream::Update() // TODO: move this out of the game (main) thread, restarting WASAPI should not lock the game // thread, but as of now there isn't any other constant and safe access point to restart - // If the sound loop failed for some reason, re-initialize ASAPI to resume playback + // If the sound loop failed for some reason, or we changed settings, + // re-initialize WASAPI to resume playback if (m_should_restart) { m_should_restart = false; @@ -918,7 +924,8 @@ void WASAPIStream::SoundLoop() if (SetRestartFromResult(result) || !HandleWinAPI("Failed to get buffer from IAudioClient. Stopping sound playback." - " Pausing and unpausing the emulation might fix the problem", result)) + " Pausing and unpausing the emulation might fix the problem", + result)) { // We don't set m_should_restart to true in the second case here // because we can't guarantee it wouldn't constantly fail again @@ -949,7 +956,7 @@ void WASAPIStream::SoundLoop() else { GetMixer()->Mix(reinterpret_cast(data), m_frames_in_buffer); - + for (u32 i = 0; i < m_frames_in_buffer * STEREO_CHANNELS; ++i) { reinterpret_cast(data)[i] = diff --git a/Source/Core/Common/MathUtil.h b/Source/Core/Common/MathUtil.h index 2aa2b406aba3..21bdf8805660 100644 --- a/Source/Core/Common/MathUtil.h +++ b/Source/Core/Common/MathUtil.h @@ -92,6 +92,14 @@ constexpr u32 NextPowerOf2(u32 value) return value; } +constexpr s32 NearestPowerOf2(s32 value) +{ + const s32 next = NextPowerOf2(value); + const s32 prev = next >> 1; + + return (next - value) > (value - prev) ? prev : next; +} + template struct Rectangle { diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 357d34c97850..71333bcc58a6 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -8,12 +8,12 @@ #include #include -#include #include #include #include #include #include +#include #include #include #include @@ -95,16 +95,17 @@ void AudioPane::CreateWidgets() m_latency_spin = new QSpinBox(); m_latency_spin->setMinimum(0); m_latency_spin->setMaximum(std::min((int)AudioCommon::GetMaxSupportedLatency(), 200)); - m_latency_spin->setToolTip(tr("Target latency (in ms). Higher values may reduce audio" - " crackling.\nCertain backends only. Values above 20ms are not suggested.")); + m_latency_spin->setToolTip( + tr("Target latency (in ms). Higher values may reduce audio" + " crackling.\nCertain backends only. Values above 20ms are not suggested.")); } m_use_os_sample_rate = new QCheckBox(tr("Use OS Mixer sample rate")); m_use_os_sample_rate->setToolTip( tr("Directly mixes at the current OS mixer sample rate as opposed to %1" - "Hz.\nThis will make resampling from 32kHz sources more accurate, possibly improving " - "audio\n" + "Hz.\nThis will make resampling from 32kHz sources more accurate, possibly improving" + " audio\n" "quality at the cost of performance. It won't follow changes to your OS setting." "\nIf unsure, leave off.") .arg(AudioCommon::GetDefaultSampleRate())); @@ -115,7 +116,8 @@ void AudioPane::CreateWidgets() m_dolby_pro_logic->setToolTip( tr("Enables Dolby Pro Logic II emulation using 5.1 surround.\nCertain backends and DPS " "emulation engines only.\nAutomatically disabled if not supported by your audio device." - "\nYou need to enable it in the game settings for GC games or in the menu settings on Wii." + "\nYou need to enable surround from the game settings in GC games or in the menu settings " + "on Wii." "\nThe console will still output 2.0, but the encoder will extract information for 5.1." "\nIf unsure, leave off.")); @@ -178,18 +180,19 @@ void AudioPane::CreateWidgets() m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); mixer_box->setLayout(mixer_layout); - m_stretching_enable->setToolTip( - tr("Enables stretching of the audio (pitch correction) to match the emulation speed")); + m_stretching_enable->setToolTip(tr( + "Enables stretching of the audio (pitch correction) to match the emulation speed.\nIt might " + "incur in a slight loss of quality.\nIt might have undesired side effects with DPLII.")); m_emu_speed_tolerance_slider->setMinimum(-1); m_emu_speed_tolerance_slider->setMaximum(125); m_emu_speed_tolerance_slider->setToolTip( tr("Time(ms) we need to fall behind the emulation for sound to start using the actual " "emulation speed.\nIf set " - "too high (>40), sound will play old samples backwards when we slow down or stutter.\nIf set too low (<10), " - "sound might " - "lose quality if you have frequent small stutters.\nSet 0 to " - "have it on all the times. Slide all the way left to disable.")); //To make sure you got the best value... + "too high (>40), sound will play old samples backwards when we slow down or stutter." + "\nif set too low (<10), sound might lose quality if you have frequent small stutters." + "\nSet 0 to have it on all the times. Slide all the way left to disable.")); + //To make sure you got the best value (and review description at 0) mixer_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); mixer_layout->addWidget(m_emu_speed_tolerance_label, 1, 0); @@ -395,7 +398,7 @@ void AudioPane::SaveSettings() static_cast(m_dolby_quality_slider->value())); m_dolby_quality_latency_label->setText( GetDPL2ApproximateLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); - backend_setting_changed = true; // Not a mistake + backend_setting_changed = true; // Not a mistake } if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) { diff --git a/Source/Core/DolphinQt/Settings/AudioPane.h b/Source/Core/DolphinQt/Settings/AudioPane.h index 0033db39a1e0..fcafcbcc1157 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.h +++ b/Source/Core/DolphinQt/Settings/AudioPane.h @@ -38,7 +38,7 @@ class AudioPane final : public QWidget #ifdef _WIN32 void OnWASAPIDeviceChanged(); void LoadWASAPIDeviceSampleRate(); - #endif +#endif void OnDspChanged(); void OnVolumeChanged(int volume); diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp index 142fae45fd72..6933a16db5dc 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp @@ -72,7 +72,8 @@ void ControlGroup::LoadConfig(IniFile::Section* sec, const std::string& defdev, } // range - sec->Get(group + c->name + "/Range", &c->control_ref->range, c->control_ref->default_range * 100.0); + sec->Get(group + c->name + "/Range", &c->control_ref->range, + c->control_ref->default_range * 100.0); c->control_ref->range /= 100; } diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.h b/Source/Core/InputCommon/ControllerEmu/StickGate.h index c6f43e8444b2..c04a39af9531 100644 --- a/Source/Core/InputCommon/ControllerEmu/StickGate.h +++ b/Source/Core/InputCommon/ControllerEmu/StickGate.h @@ -108,7 +108,8 @@ class ReshapableInput : public ControlGroup void SetCenter(ReshapeData center); protected: - ReshapeData Reshape(ControlState x, ControlState y, ControlState modifier = 0.0, ControlState clamp = 1.0); + ReshapeData Reshape(ControlState x, ControlState y, ControlState modifier = 0.0, + ControlState clamp = 1.0); private: void LoadConfig(IniFile::Section*, const std::string&, const std::string&) override; diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index a0200eb3ea7b..738741f256f6 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -93,8 +93,6 @@ void ControllerInterface::ChangeWindow(void* hwnd) void ControllerInterface::RefreshDevices() { - //if (true) //To finish - // return; if (!m_is_init) return; diff --git a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h index c0b77839e03d..abe0dde17321 100644 --- a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h +++ b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h @@ -67,11 +67,11 @@ class Device // on outputs, and they are not limited to focus, if we came up with new blocking conditions. // // These flags are per Control, but they will get summed up with other expressions - // from the same ControlReference. This is because checking them per input/ouput + // from the same ControlReference. This is because checking them per input/output // would have been too complicated, expensive, and ultimately, useless. // So they are in order of priority, and some of them are mutually exclusive. // Users can use function expressions to customize the focus requirements of a - // ControlReference, but we manually hardcode them in some Inputs (e.g. mouse) so + // ControlReference, but we manually hardcode them in some Inputs (e.g. mouse) so // users don't have to bother in most cases, as it's easy to understand as a concept. enum class FocusFlags : u8 { From fc3ad60f357978e03e244241796e8cfa401435e8 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Mon, 27 Jul 2020 23:32:29 +0300 Subject: [PATCH 15/56] Fixed L and R audio being swapped --- Source/Core/AudioCommon/Mixer.cpp | 40 ++++++++++++++----------------- Source/Core/AudioCommon/Mixer.h | 9 ++++--- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 33908e75cdf4..4359af4dc9af 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -190,7 +190,6 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) { if (indexW > 8) OSD::AddMessage("Behind samples: " + std::to_string(behind_samples), 0U); - //To review: should padding play the last played sample or try to predict what the next one would have been? We will resume from the previous indexR as we have lost m_fract here //To review: if we re-enable padding on wii mote forever, make sure it's disabled once we disconnect it unsigned int current_sample = actual_samples_count * 2; for (; current_sample < num_samples * 2; current_sample += 2) @@ -245,21 +244,21 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r u32 i = 0; u32 next_available_samples = available_samples; - // Stop 3 (INTERP_SAMPLES) samples from the end as we need to interpolate with them. + // Stop 3 (INTERP_SAMPLES) samples from the end as we need to interpolate with them while (i < num_samples && (!forwards || (next_available_samples > INTERP_SAMPLES * NC && - next_available_samples <= available_samples))) + next_available_samples <= available_samples))) { - // If rate is 1 it would like there was no interpolation. + // If rate is 1 it would be like there was no interpolation. // If rate is 0 fract won't never make a whole so it's basically like padding. // If rate is a recurring decimal fract often ends up being 0.99999 due to // loss of precision, but that is absolutely fine, it implies no quality loss. - // Fract imprecisions are the reason we don't pre-calculate the number of iterations - fract += rate; // Update fraction position - u32 whole = u32(fract); // Update whole position + // Fract errors are the reason we don't pre-calculate the number of iterations + fract += rate; // Update fraction position + u32 whole = u32(fract); // Update whole position fract -= whole; // Increase indexR before reading it, not after like before. The old code had 3 problems: // - IndexR was increased after being read, so if the rate was very high, - // it could go over indexW. This would require a flag to be fixed + // it could go over indexW. This would have required a flag to be fixed // - In our first iteration, we would use the last fract calculated from the previous // interpolation (last audio frame), meaning that it would have been based on the old // rate, not the new one. Of course, over time the errors would cancel themselves out, @@ -267,10 +266,8 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r // - When suddenly changing playback direction, the indexR would have been moved to the // next position to read in the opposite direction // The only "problems" with the new code is that in the very first iteration after a fract - // reset, fract won't be increased. It also denaturalizes the progress of indexR if we - // run out of samples, but that would sound bad anyway. We also can't really tell - // how many samples we will have available until we have found out our next play rate - //To review last condition above, how often does it happen? How bad it is? How could we improve on that? The wii mote speaker might be more sensible to that + // reset, fract won't be increased. It also resets the progress of indexR (fract) when we + // run out of samples, but that won't sound any worse indexR += NC * whole * direction; available_samples = next_available_samples; @@ -289,20 +286,19 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r // The first and last sample act as control points, the middle ones have more importance. // The very first and last samples might never directly be used but it shouldn't be a problem. // Theoretically we could linearly interpolate between the last 2 samples - // and trade latency with a small hit in quality, but it is not worth it. + // and trade latency with a small hit in quality, but it's not worth it. // We could have ignored the direction while playing backwards, and just read - // indexR over our actually play direction, but I thought that was "wrong" - float l_s_f = y0 * interpolation_buffer[indexR & INDEX_MASK] + - y1 * interpolation_buffer[(indexR + 2 * direction) & INDEX_MASK] + - y2 * interpolation_buffer[(indexR + 4 * direction) & INDEX_MASK] + - y3 * interpolation_buffer[(indexR + 6 * direction) & INDEX_MASK]; - float r_s_f = y0 * interpolation_buffer[(indexR + 1) & INDEX_MASK] + + // indexR over our actually play direction, but I thought that was wrong and weird + float l_s_f = y0 * interpolation_buffer[(indexR + 1) & INDEX_MASK] + y1 * interpolation_buffer[(indexR + 2 * direction + 1) & INDEX_MASK] + y2 * interpolation_buffer[(indexR + 4 * direction + 1) & INDEX_MASK] + y3 * interpolation_buffer[(indexR + 6 * direction + 1) & INDEX_MASK]; - //To test left and right on HW source + float r_s_f = y0 * interpolation_buffer[indexR & INDEX_MASK] + + y1 * interpolation_buffer[(indexR + 2 * direction) & INDEX_MASK] + + y2 * interpolation_buffer[(indexR + 4 * direction) & INDEX_MASK] + + y3 * interpolation_buffer[(indexR + 6 * direction) & INDEX_MASK]; - l_s = (s32(std::round(l_s_f)) * lVolume) >> 8; //To round again post vol? + l_s = (s32(std::round(l_s_f)) * lVolume) >> 8; r_s = (s32(std::round(r_s_f)) * rVolume) >> 8; // Clamp after adding to current sample, as if the cubic interpolation produced a sample over // the limits and the current sample has the opposite sign, then we'd keep the excess value, @@ -678,7 +674,7 @@ void Mixer::PushDMASamples(const s16* samples, u32 num_samples) // This average will be slightly outdated when retrieved later as m_time_at_custom_speed // could have increased in the meanwhile, but it's ok, the error is small enough m_dma_speed.CacheAverageSpeed(true, m_time_at_custom_speed); - + bool PrintPushedSamples = true; //To delete if (PrintPushedSamples) INFO_LOG(AUDIO, "dma_mixer added samples: %u, speed: %lf", num_samples, m_dma_speed.GetCachedAverageSpeed()); m_dma_mixer.PushSamples(samples, num_samples); diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 99438e784980..51f5f7f26dfd 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -112,15 +112,14 @@ class Mixer final Mixer* m_mixer; std::atomic m_input_sample_rate; // Ring input buffer directly from emulated HW. In big endians. - // Even indexes are left channel, same as the output + // Even indexes are right channel, as opposed to the output std::array m_buffer{}; - // Write (how many have been written - 1, index of the last one to have been written) - // Start from max so even indices will be left channel and odd right + // Write (how many have been written, index of the next one to write) // Start from INTERP_SAMPLES + 1 so that we gradually blend into the initial samples std::atomic m_indexW{(INTERP_SAMPLES + 1) * NC}; - //To describe - // Read (how many have been read - 1, index of last one to have been read) + // Read (how many have been read - 1, index of last one to have been read and that we are + // currently interpolating) std::atomic m_indexR{0}; // If their difference is 0, we will have read all the samples (or there are none left anyway). // We could still re-read the current indexR to get the last sample value (0 if never written). From 221c8715068e7b999134645b6d711c4c436fb9e4 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 28 Jul 2020 02:33:16 +0300 Subject: [PATCH 16/56] Fixes --- Source/Core/AudioCommon/AudioSpeedCounter.cpp | 2 +- Source/Core/AudioCommon/Mixer.cpp | 204 ++++++++---------- Source/Core/AudioCommon/Mixer.h | 8 +- Source/Core/AudioCommon/SurroundDecoder.cpp | 3 +- Source/Core/Core/HW/WiimoteEmu/Speaker.cpp | 57 +++-- Source/Core/DolphinQt/Settings/AudioPane.cpp | 12 +- 6 files changed, 155 insertions(+), 131 deletions(-) diff --git a/Source/Core/AudioCommon/AudioSpeedCounter.cpp b/Source/Core/AudioCommon/AudioSpeedCounter.cpp index ff3b527d84b3..4fedc7c8aa89 100644 --- a/Source/Core/AudioCommon/AudioSpeedCounter.cpp +++ b/Source/Core/AudioCommon/AudioSpeedCounter.cpp @@ -49,8 +49,8 @@ void AudioSpeedCounter::OnSettingsChanged() void AudioSpeedCounter::SetAverageTime(double average_time) { + // Don't immediately delete older deltas, it's not necessary for now m_average_time = average_time; - OnSettingsChanged(); } void AudioSpeedCounter::SetTicksPerSecond(double ticks_per_sec) { diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 4359af4dc9af..084e7e7e1006 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -136,9 +136,13 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) m_fract = -1.0; } + // I can't tell if this is better than padding (silence) when going at about 40% of the target speed + static bool enable_backwards = true; //To review s32 behind_samples = num_samples - actual_samples_count; // This might sound bad if we are constantly missing a few samples, but that should never happen, // and we couldn't predict it anyway (we should start playing backwards as soon as we can). + // We can't play backwards mixers that are not constantly pushed as we don't know when the + // last sound started (we could not, but it's not worth implementing). // If required, this could be disabled from a config if (behind_samples > 0 && m_constantly_pushed && !stretching) { @@ -180,7 +184,7 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) // really disturbing, they are very low, and it's not like dolphin didn't have them before. // Also in general, ping ponging wouldn't give you enough time to appreciate the sounds, // they would constantly be interrupted. The only solution to crackling would be cross fade? - CubicInterpolation(back_samples, behind_samples, rate, m_backwards_indexR, indexW, s[0], s[1], + CubicInterpolation(back_samples, enable_backwards * behind_samples, rate, m_backwards_indexR, indexW, s[0], s[1], lVolume, rVolume, false); } // Padding (constantly pushing the last sample when we run out to avoid sudden changes in the @@ -190,7 +194,7 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) { if (indexW > 8) OSD::AddMessage("Behind samples: " + std::to_string(behind_samples), 0U); - //To review: if we re-enable padding on wii mote forever, make sure it's disabled once we disconnect it + //To review: if we re-enable padding on wii mote forever, make sure it's disabled once we disconnect it. Also, fill with padded samples when starting to push it unsigned int current_sample = actual_samples_count * 2; for (; current_sample < num_samples * 2; current_sample += 2) { @@ -212,7 +216,6 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r u32 indexW, s32& l_s, s32& r_s, s32 lVolume, s32 rVolume, bool forwards) { - // Coefficients copied from SoundTouch Copyright © Olli Parviainen 2001-2019 constexpr float coeffs[] = { -0.5f, 1.0f, -0.5f, 0.0f, 1.5f, -2.5f, 0.0f, 1.0f, @@ -298,6 +301,7 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r y2 * interpolation_buffer[(indexR + 4 * direction) & INDEX_MASK] + y3 * interpolation_buffer[(indexR + 6 * direction) & INDEX_MASK]; + // Could this benefit from multiplying the volume as a float before the round? l_s = (s32(std::round(l_s_f)) * lVolume) >> 8; r_s = (s32(std::round(r_s_f)) * rVolume) >> 8; // Clamp after adding to current sample, as if the cubic interpolation produced a sample over @@ -342,86 +346,86 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) m_target_speed = 1.0; } - // What we likely want to do is check the emulation speed difference between the target speed and the averaged emulation speed, - // but then use the last frame emulation speed for more accuracy (and clamping it to the target speed) - static double FallbackDelta = 0.0; //0.005 or before 0.1 - // Only do if we are actually going slower, if we are going faster, it's likely to be a slight incorrection of the actual speed (because it's averaged). - // Given that we ignore the frames where we went faster, overall we will consume less audio samples that what we have received, causing less padding. - // This avoids missing (or padded) samples when we can't reach full speed. The delta is relative to the emulator target speed. - // OLD: (emulation_speed - average_actual_speed > FallbackDelta) - // Unfortunately this doesn't work when unpausing (or starting?) the emulation, so we'll need to - // work around that. - // When using the last 3 frames (?) averaged speed, this causes heavier crackling (and fluctuation), but overall the - // sound is much more accurate. - // When using the very last frame, the sound works much better and has less crackling and skips, at the cost of fluctuating in pitch a bit, - // just like the emulation itself is doing. - // Using the amount of samples in the buffer to calculate the emulation speed doesn't seem to be accurate at all because of so many timing/order fluctuations - // NonStretchCorrectionTolerance: we don't want to suddenly snap in and out of actual FPS speed, but only when we have confirmed the slow down is not due to a hiccup - // Small variances are already handled by the mixing, so we don't want to take care of these cases - if (!frame_limiter) { - m_time_behind_target_speed = 0.0; //To maybe find a way to maintain this during unlimited frame rate? - m_time_behind_target_speed_growing = false; - //To review: this can start over 30 in the first frames of game... target_speed = m_dma_speed.GetCachedAverageSpeed(true, true, true); m_time_at_custom_speed = m_time_at_custom_speed + time_delta; + // We've managed to reach the target speed so clear the m_behind_target_speed state + if (target_speed >= emulation_speed) + { + m_time_behind_target_speed = 0.0; + m_behind_target_speed = false; + } } - //To avoid divisions all over, also, just do these things at the top every frame... // actual_speed varies by about 0.5% every audio frame so we need to go back and forth and then when we have lost enough, // we will start using the average actual speed. To come back at full emulation speed, we check for how long we had been // running at full speed, we don't wait for m_time_behind_target_speed to come back to 0 because that will never happen, // what's lost is lost (though recovery could still happen if for some reason we had cycles imprecisions within a frame???) // Stop using emulation speed, start using actual speed for audio playback, if we fell behind enough // Filter out small inaccuracies (samples aren't submitted with perfect timing) - else if (actual_speed / emulation_speed < 1.0 - FallbackDelta) - { - //To review: this keeps trigger and going back to normal on GC due to the uneven sample rates? It also does NOT work with DPLII at the moment - //To review: what happens when m_audio_emu_speed_tolerance is 0??? - // Note: when not able to reach the target speed before m_time_behind_target_speed_growing triggers, you might hear crackles due to (probably) having finished the samples - //To review: this happens more often at higher backend latencies, smooth it over time? Ignore first missed frame? - // If we fell behind of m_time_behind_target_speed seconds of samples, start using the actual emulation speed - m_time_behind_target_speed += time_delta * (1.0 - (actual_speed / emulation_speed)); - if (SConfig::GetInstance().m_audio_emu_speed_tolerance >= 0 && - m_time_behind_target_speed > SConfig::GetInstance().m_audio_emu_speed_tolerance / 1000.0) - { - if (!m_time_behind_target_speed_growing) - OSD::AddMessage("m_time_behind_target_speed_growing = true", 2000U); - //m_time_behind_target_speed = SConfig::GetInstance().NonStretchCorrectionTolerance; - m_time_behind_target_speed_growing = true; //To rename - } - } else { - //To review: the time to recover is too small? When we have constant small (or big) dips with 100% speed in between, - //we immediately recover speed but then take some time to fall back... Alternatively we could have a "high attention" phase immediately after having recovered full speed - double gain_time_delta = time_delta * (1.0 - (actual_speed / emulation_speed)); + const double audio_emu_speed_tolerance = + SConfig::GetInstance().m_audio_emu_speed_tolerance / 1000.0; + const bool dynamic_audio_speed_allowed = audio_emu_speed_tolerance >= 0.0; + const bool dynamic_audio_speed_forced = audio_emu_speed_tolerance == 0.0; + + const double gain_time_delta = time_delta * (1.0 - (actual_speed / emulation_speed)); m_time_behind_target_speed = std::max(m_time_behind_target_speed + gain_time_delta, 0.0); - // Time at target speed (to tell if we have recovered full speed) - //To review: this might not work perfectly with iTimingVariance which recovers speed after a small drop in speed + + // What we likely want to do is check the emulation speed difference between the target speed and the averaged emulation speed, + // but then use the last frame emulation speed for more accuracy (and clamping it to the target speed) + static double FallbackDelta = 0.0; //0.005 or before 0.1 //To review and rename both static double FallbackDelta2 = 0.001; - if (average_actual_speed >= emulation_speed - (FallbackDelta2 * emulation_speed)) + // Only do if we are actually going slower, if we are going faster, it's likely to be a slight incorrection of the actual speed (because it's averaged). + // Given that we ignore the frames where we went faster, overall we will consume less audio samples that what we have received, causing less padding. + // This avoids missing (or padded) samples when we can't reach full speed. The delta is relative to the emulator target speed. + // When using the very last frame, the sound works much better and has less crackling and skips, at the cost of fluctuating in pitch a bit, + // just like the emulation itself is doing. + // NonStretchCorrectionTolerance: we don't want to suddenly snap in and out of actual FPS speed, but only when we have confirmed the slow down is not due to a hiccup + // Small variances are already handled by the mixing, so we don't want to take care of these cases + if (actual_speed / emulation_speed < 1.0 - FallbackDelta) { - if (m_time_behind_target_speed_growing) - OSD::AddMessage("m_time_behind_target_speed_growing = false", 2000U, OSD::Color::GREEN); - m_time_behind_target_speed_growing = false; - m_time_behind_target_speed = 0; + //To make sure you got the best m_audio_emu_speed_tolerance value + //To review: this keeps trigger and going back to normal on GC due to the uneven sample rates? It also does NOT work with DPLII at the moment + //To review: how does this sound if we always use actual_speed with sound stretching? + //To review: test at higher backend latencies: smooth it over time? Ignore first missed frame? + // If we fell behind of m_time_behind_target_speed seconds of samples, start using the actual emulation speed + if (m_time_behind_target_speed > audio_emu_speed_tolerance) + { + if (!m_behind_target_speed && audio_emu_speed_tolerance > 0.0) + OSD::AddMessage("m_behind_target_speed = true", 2000U); + m_behind_target_speed = true; + } + } + // Rely on the average speed (and its length) to tell if we have recovered full speed + //To review: this might not work perfectly with iTimingVariance which recovers speed after a small stutter/drop. + else if (average_actual_speed >= emulation_speed - (FallbackDelta2 * emulation_speed)) + { + if (m_behind_target_speed && audio_emu_speed_tolerance > 0.0) + OSD::AddMessage("m_behind_target_speed = false", 2000U, OSD::Color::GREEN); + m_behind_target_speed = false; + // Without this, it would never come back to 0, what's lost is lost + m_time_behind_target_speed = 0.0; + } + + if (dynamic_audio_speed_allowed && (dynamic_audio_speed_forced || m_behind_target_speed)) + { + //To1 review: maybe just use the same speed the frame_limiter would above? When re-enabling the frame_limiter we'll get missing samples + //as we'd use an average too long + static bool use_new_average = true; + target_speed = use_new_average ? m_dma_speed.GetCachedAverageSpeed(true, true, true) : average_actual_speed; + INFO_LOG(AUDIO, " actual_speed: %f average_actual_speed: %f", actual_speed, average_actual_speed); + m_time_at_custom_speed = m_time_at_custom_speed + time_delta; + } + else + { + m_time_at_custom_speed = 0.0; } - } - if (m_time_behind_target_speed_growing) - { - //To review min. Also, maybe just use the same speed the frame_limiter would above? - target_speed = frame_limiter ? std::min(average_actual_speed, emulation_speed) : average_actual_speed; - INFO_LOG(AUDIO, " actual_speed: %f average_actual_speed: %f", actual_speed, average_actual_speed); - m_time_at_custom_speed = m_time_at_custom_speed + time_delta; - } - else if (frame_limiter) - { - m_time_at_custom_speed = 0.0; } - //To test latency/buffer buildup (auto adjustment/sync) when we pass from stretching to not stretching. If they are always on the edge... We'll need to slow it down. Have a mirrored version of max_latency (min)? - //To review: this should be done per mixer + //To1 add a min latency as well for when we can't keep up with the target speed and we are running out of samples, better than playing backwards for the first part + //To1 review: this should be done per mixer //To review, the stretcher can get stuck looping while stopping the process (breakpoint) for like 20 seconds? Only with DPLII? double latency; // The target latency used to be iTimingVariance if stretching was off and @@ -429,8 +433,9 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) // samples to be able to withstand small hangs and changes in speed, but our new approach is to // play samples backwards when we are out of new ones so these are never problems. double max_latency = Config::Get(Config::MAIN_AUDIO_MIXER_MAX_LATENCY) / 1000.0; //To read as little times as possible - //To double or disable when speed is unlimited (or if we can't reach target speed), to avoid constant fluctuations (the average speed will compensate temporary imprecisions anyway) - if (!frame_limiter || m_time_behind_target_speed_growing) + //To1 review: this doesn't seem to be needed when the frame_limiter is disabled (it does reach max latency more often, but it doesn't cause any problems), but when + //we can't reach the target sped, this might sound bad? + if (!frame_limiter || m_behind_target_speed) { static double mult = 1.0; max_latency *= mult; @@ -453,8 +458,8 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) double acceptable_latency = m_stretcher.GetAcceptableLatency() - time_delta; target_latency = acceptable_latency + max_latency * 0.5; max_latency += acceptable_latency; - // When we are pitch correcting it's harder to hear the change so correct it faster - catch_up_speed = 1.25; + // When we are pitch correcting it's harder to hear the change so we correct faster + catch_up_speed = STRETCHING_CATCH_UP_SPEED; } else { @@ -468,7 +473,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) post_mix_samples -= num_samples * rate + INTERP_SAMPLES; latency = std::max(post_mix_samples, 0) / m_dma_mixer.GetInputSampleRate(); // This isn't big enough to notice but it is enough to make a difference and recover latency - catch_up_speed = 1.015; //To review: this will be very slow if we are going at 0.1 speed already, maybe consider adding it up? Make constexpr? + catch_up_speed = NON_STRETCHING_CATCH_UP_SPEED; target_latency = max_latency * 0.5; } //INFO_LOG(AUDIO, "latency: %lf", latency); @@ -483,6 +488,9 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) if (latency > (m_latency_catching_up ? target_latency : max_latency)) { m_latency_catching_up = true; + //To1 review: this will be very slow if we are going at 0.1 speed already. + //If we add instead, we'd hear the difference more when at low speeds, and we'd catch up slower at high speeds. + //Maybe have a minimum of catch_up_speed to apply (e.g. 0.5 speed)? target_speed *= catch_up_speed; OSD::AddMessage("Reached max latency", 0U); } @@ -569,7 +577,7 @@ u32 Mixer::MixSurround(float* samples, u32 num_samples) { memset(samples, 0, num_samples * SURROUND_CHANNELS * sizeof(samples[0])); - //To review this code just seems wrong and breaks if we change the sample rate? + //To1 review this code just seems wrong and breaks if we change the sample rate? //To fix QuerySamplesNeededForSurroundOutput doesn't work with num_samples 0 or small u32 needed_samples = m_surround_decoder.QuerySamplesNeededForSurroundOutput(num_samples); @@ -622,10 +630,6 @@ void Mixer::MixerFifo::PushSamples(const s16* samples, u32 num_samples) // Checking if we received 0 samples to early out is not worth it as it only happens on startup. if (num_samples * NC + fifo_samples > MAX_SAMPLES * NC) { - static bool PrintRunOutOfSamples = false; - if (PrintRunOutOfSamples) - OSD::AddMessage("Run out of samples", 100, OSD::Color::RED); - //WARN_LOG(AUDIO, "Too many samples: %i", num_samples * NC + fifo_samples - (MAX_SAMPLES * NC)); // Fallback to the max we can currently take num_samples = MAX_SAMPLES - fifo_samples / NC; } @@ -652,7 +656,6 @@ void Mixer::MixerFifo::PushSamples(const s16* samples, u32 num_samples) void Mixer::PushDMASamples(const s16* samples, u32 num_samples) { - assert(num_samples > 0); //To delete // Use the DMA samples to determine the emulation speed, the Streaming/DVD // samples submissions are more frequent, so we could get more precision from // them, but it's really not necessary. Also, DVD is not really used on Wii @@ -670,12 +673,12 @@ void Mixer::PushDMASamples(const s16* samples, u32 num_samples) // so we could easily either clamp the results within an accepted range, or compare it against another timer (e.g. from the mixer thread), // and see if that had a massive time between the last 2 calls, and if so, scale the second from the first m_dma_speed.Update(num_samples); - m_dma_speed.CacheAverageSpeed(false); //To test different lengths + m_dma_speed.CacheAverageSpeed(false); //To1 test different lengths // This average will be slightly outdated when retrieved later as m_time_at_custom_speed // could have increased in the meanwhile, but it's ok, the error is small enough m_dma_speed.CacheAverageSpeed(true, m_time_at_custom_speed); - bool PrintPushedSamples = true; //To delete + static bool PrintPushedSamples = true; if (PrintPushedSamples) INFO_LOG(AUDIO, "dma_mixer added samples: %u, speed: %lf", num_samples, m_dma_speed.GetCachedAverageSpeed()); m_dma_mixer.PushSamples(samples, num_samples); @@ -686,14 +689,11 @@ void Mixer::PushDMASamples(const s16* samples, u32 num_samples) void Mixer::PushStreamingSamples(const s16* samples, u32 num_samples) { - bool PrintPushedSamples = false; //To delete - if (PrintPushedSamples) INFO_LOG(AUDIO, "streaming_mixer added samples: %u", num_samples); m_streaming_mixer.PushSamples(samples, num_samples); - //To review: this mixer varies a lot in num_samples? // Check whether the wii mote speaker mixers have finished pushing. We do it - // from this mixer as it's the one with the higher update frequency. - // Yes, it's a bit of a hack but it works fine + // from this mixer as it's the one with the higher update frequency + // (168 samples per push at 32 or 48kHz). Yes, it's a bit of a hack but it works fine double time_delta = double(num_samples) / m_dma_mixer.GetInputSampleRate(); m_wiimote_speaker_mixer[0].UpdatePush(-time_delta); m_wiimote_speaker_mixer[1].UpdatePush(-time_delta); @@ -832,13 +832,6 @@ void Mixer::MixerFifo::SetInputSampleRate(double rate) // but the reality of that happening on real hardware when there are non zero // samples is pretty low if not impossible m_input_sample_rate = rate; - //To review: this happens every frame it's called for the wii mote speaker - //To review: if in and out are the same but fract has been polluted by a small change in speed once, should we reset it? - // If we passed from an input sample rate different from the output one to - // one that is identical, "fract" would permanently stay offsetted, which we don't really want. - // This is likely to only happen before the game has actually played any sounds anyway - if (m_constantly_pushed) - m_fract = -1.0; } double Mixer::MixerFifo::GetInputSampleRate() const @@ -908,36 +901,23 @@ void Mixer::MixerFifo::UpdatePush(double time) currently_pushed = false; } + //To review: could we use the: Wii Mote Speaker enabled/disabled information in any way? if (m_currently_pushed != currently_pushed) { m_currently_pushed = currently_pushed; if (m_currently_pushed) { - //To do more here? e.g. save the last playable backwards sample... (maybe, not easy to do) - //To now increase indexR by 1 or 3 or 4? Otherwise it would be slightly delayed, but I guess it's fine as it would be the same case even if we didn't add samples... - //Make sure the first read sample (index) will be the first pushed one - //m_indexR.fetch_add((INTERP_SAMPLES + 1) * NC); - //To review: add some silent samples to make sure we don't run out of samples. The target latency precisely. - // When stretching, we don't exactly need to add a latency on top to make sure it won't run out of samples, - // but we do to keep consistency (actually, don't do it! It add useless latency but what was the other problem??? Loss of sync between mixers? Just being useless?) - //To review: this works originally but then it breaks after enabling and disabling stretching??? - //To review: make sure the post mix latency is correct. We can't know how many samples the backend will read and we can't know how many more samples we will receive before the next - //backend read, but we approximate... It should be fine - // Note that this wouldn't work if the backend/surround (mix) latency (requested samples) was too high, as you'd run out of samples in the first mix after this, - // as this possibly started towards the end of 2 mix calls, and it hasn't build up enough samples. 3 alternative solutions: - // -Find a way to always push silent wii mote speaker samples when the wii mote is not pushing any (e.g. from the DVD Stream push) - // -Slow down the first mix of this any time we start pushing, to the point where it would only exactly play the number of samples available, - // to not incur in padding for the next audio frames - // -Predict the next mix accuracy (by calling queryNofFra...) and setting that as min latency, maybe also check how long it has elapsed since the last mix call, - // to know whether we'd manage to fill it up already with the next wii mote speaker samples pushes (assuming the minimum length of a wii mote speaker push is like 0.2ms). - // Then using the min between the backend latency and the target latency. This might result in going over the max latency so it would speed up the sound speed - // The wii mote pushes 40 samples at 6000Hz, which means 2.5 submissions per frame at 60fps. After pressing the input responsible for triggering samples, at least 2 batches of samples will be pushed in the next frame, though the mixing could happen at any time... - // On original HW, might have been turned off on the last sample, meaning that if the sample wasn't 0, it wouldn't have made any additional sounds - //To review: could we use the: Wii Mote Speaker enabled/disabled information in any way? + // Add some silent samples to make sure we don't run out of samples, the target latency + // precisely. When stretching, we don't need to add a latency it's not related to time. + // Real wii motes deal with this in 2 ways: by disabling the speaker after a sound and by + // padding the last sample otherwise if (!SConfig::GetInstance().m_audio_stretch) { + //To1 add: make sure the post mix latency is correct (== to target latency) + //So do the max between target latency AND backend/surround (mix) latency + //To review: also multiply this by game speed... u32 num_samples = - (Config::Get(Config::MAIN_AUDIO_MIXER_MAX_LATENCY) / 1000.0) * 0.5 * m_input_sample_rate; + (Config::Get(Config::MAIN_AUDIO_MIXER_MAX_LATENCY) / 2000.0) * m_input_sample_rate; num_samples = std::min(num_samples, MAX_SAMPLES); memset(m_mixer->m_conversion_buffer, 0, num_samples * NC * sizeof(m_mixer->m_conversion_buffer[0])); @@ -950,7 +930,7 @@ void Mixer::MixerFifo::UpdatePush(double time) constexpr u32 num_samples = INTERP_SAMPLES + 1; // Add enough samples of silence to make sure when it has finished reading it won't stop on a - // non zero sample (which would break padding) + // non zero sample (which would restrict the range of the other mixers) s16 silent_samples[num_samples * NC]{}; PushSamples(silent_samples, num_samples); } diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 51f5f7f26dfd..368036555997 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -75,6 +75,10 @@ class Mixer final // Interpolation reserved/required samples static constexpr u8 INTERP_SAMPLES = 3u; + //To undo constexpr? + static constexpr double STRETCHING_CATCH_UP_SPEED = 1.25; + static constexpr double NON_STRETCHING_CATCH_UP_SPEED = 1.015; + // A single mixer in/out buffer. The original alignment they might have had on real HW isn't kept class MixerFifo final { @@ -153,7 +157,7 @@ class Mixer final {this, 3000, false}, {this, 3000, false}, {this, 3000, false}, {this, 3000, false}}; u32 m_sample_rate; - // the size is always left at 0 + // The size is always left at 0, they are never initialed std::vector m_scratch_buffer; std::array m_interpolation_buffer; s16 m_conversion_buffer[MAX_SAMPLES * NC]; @@ -163,7 +167,7 @@ class Mixer final // Target emulation speed, but it can fallback to the actual emulation speed double m_target_speed = 1.0; //To make atomic (m_fract as well???) (make sure they aren't used twice in a line if so) double m_time_behind_target_speed = 0.0; - bool m_time_behind_target_speed_growing = false; + bool m_behind_target_speed = false; std::atomic m_time_at_custom_speed{0.0}; bool m_latency_catching_up = false; diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 1bc06cba9494..f003b811813b 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -39,6 +39,7 @@ static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) } const u32 frame_block = std::round(sample_rate * frame_block_time / 1000.0); assert(frame_block > 1); + //To make sure this is even needed... return MathUtil::NearestPowerOf2(frame_block); } @@ -94,7 +95,7 @@ void SurroundDecoder::Clear() // Receive and decode samples void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) { - //To make sure + //To make sure it works assert(num_samples % m_frame_block_size == 0); // We support a max of MAX_BLOCKS_BUFFERED blocks in the buffer, because of m_decoded_fifo, // just increase if you need. This might trigger if you have very high backend latencies diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp index 64ea46af3561..ef5ab35ce3cc 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp @@ -22,7 +22,7 @@ #include "AudioCommon/WaveFile.h" #include "Common/FileUtil.h" #endif -//#pragma optimize("", off) //To delete +#pragma optimize("", off) //To delete namespace WiimoteEmu { @@ -34,22 +34,52 @@ static const s32 yamaha_difflookup[] = {1, 3, 5, 7, 9, 11, 13, 15, static const s32 yamaha_indexscale[] = {230, 230, 230, 230, 307, 409, 512, 614, 230, 230, 230, 230, 307, 409, 512, 614}; -static double av_clip16(double a) -{ - if ((s32(a) + 32768) & ~65535) - return (s32(a) >> 31) ^ 32767; - else - return a; -} - +//static s32 av_clip16(s32 a) +//{ +// //To simplify this and move to double (SHRT_MAX+1=32768) +// if ((a + 32768) & ~65535) +// return (a >> 31) ^ 32767; +// else +// return a; +//} + +// In short, what this does is passing an encoded and approximate offset from the previous sample. +// The nibble will determine how how our output sample grows from the previous one, based on +// a limited table and range, and it will also determine how big our next sample offset is going to be (???). +// Due to the nature of the encoding, which relies on a lot of conditions and clamping, the decoded +// samples won't be centered around 0 anymore (even if they originally were) as the predictor will +// accumulate errors which make it shift from 0 (or any original value) over time. +// The loss in quality is more because of the limited offset range than this, but this bring +// a padding/clipping problem where when there is no sound, it will still output a value +// different from 0, causing clipping if mixed with other sound sources static s16 adpcm_yamaha_expand_nibble(ADPCMState& s, u8 nibble) { - s.predictor += (s.step * yamaha_difflookup[nibble]) / 8.0; - s.predictor = av_clip16(s.predictor); + double predictor_delta = (s.step * yamaha_difflookup[nibble]) / 8.0; + assert(predictor_delta == std::clamp(predictor_delta, -65536.0, 65535.0)); + predictor_delta = std::clamp(predictor_delta, -65536.0, 65535.0); // Likely useless + s.predictor += predictor_delta; + s.predictor = std::clamp(s.predictor, -32768.0, 32767.0); s.step = (s.step * yamaha_indexscale[nibble]) / 256.0; + // If we didn't clip this, we could retrieve the original information of where we are within the wave + // spectrum. If we would be over the limits, then our predictor is wrong. + // We could also detect a wrong predictor by checking when the nibble changes direction two times, + // which LIKELY means the wave should have been centered in the middle of these two changes. + // We could also detect it when we get clipping (clamping) or the predictor. s.step = std::clamp(s.step, 127.0, 24576.0); return s.predictor; } +//static s16 ExpandNibble(ADPCMState* s, u8 nibble) +//{ +// s32 predictor_delta = (s->step * yamaha_difflookup[nibble]) / 8; +// predictor_delta = std::clamp(predictor_delta, -0x10000, 0xffff); +// s->predictor = std::clamp(s->predictor + predictor_delta, +// std::numeric_limits::min(), +// std::numeric_limits::max()); +// s->step = +// std::clamp(s->step * diff_lookup[nibble & 7], 0x7f, 0x6000); +// +// return s->predictor; +//} #ifdef WIIMOTE_SPEAKER_DUMP std::ofstream ofile; @@ -183,12 +213,13 @@ void SpeakerLogic::Reset() reg_data = {}; // Yamaha ADPCM state initialize - adpcm_state.predictor = 0; //To make sure it's called (what about when the speaker is muted or disabled) - adpcm_state.step = 127; + adpcm_state.predictor = 0.0; //To make sure it's called (what about when the speaker is muted or disabled) + adpcm_state.step = 127.0; } void SpeakerLogic::DoState(PointerWrap& p) { + //To increase state type num p.Do(adpcm_state); p.Do(reg_data); } diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 71333bcc58a6..8c622b4e711b 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -177,8 +177,14 @@ void AudioPane::CreateWidgets() m_emu_speed_tolerance_slider = new QSlider(Qt::Horizontal); m_emu_speed_tolerance_indicator = new QLabel(); m_emu_speed_tolerance_indicator->setAlignment(Qt::AlignRight); + QSize min_size = m_emu_speed_tolerance_indicator->minimumSize(); + min_size.setWidth(45); + // Avoid the slider in line with this resizing because of text length changes. + // it would be best to do it dynamically based on the translation + m_emu_speed_tolerance_indicator->setMinimumSize(min_size); m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); mixer_box->setLayout(mixer_layout); + //To review: maybe have a set of 4 or 5 options to keep it simpler m_stretching_enable->setToolTip(tr( "Enables stretching of the audio (pitch correction) to match the emulation speed.\nIt might " @@ -192,7 +198,6 @@ void AudioPane::CreateWidgets() "too high (>40), sound will play old samples backwards when we slow down or stutter." "\nif set too low (<10), sound might lose quality if you have frequent small stutters." "\nSet 0 to have it on all the times. Slide all the way left to disable.")); - //To make sure you got the best value (and review description at 0) mixer_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); mixer_layout->addWidget(m_emu_speed_tolerance_label, 1, 0); @@ -204,7 +209,6 @@ void AudioPane::CreateWidgets() m_main_layout->setRowStretch(0, 0); dsp_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - m_main_layout->addWidget(dsp_box, 0, 0); m_main_layout->addWidget(volume_box, 0, 1, -1, 1); m_main_layout->addWidget(backend_box, 1, 0); @@ -308,6 +312,8 @@ void AudioPane::LoadSettings() m_emu_speed_tolerance_slider->setValue(SConfig::GetInstance().m_audio_emu_speed_tolerance); if (m_emu_speed_tolerance_slider->value() < 0) m_emu_speed_tolerance_indicator->setText(tr("Disabled")); + else if (m_emu_speed_tolerance_slider->value() == 0) + m_emu_speed_tolerance_indicator->setText(tr("None")); else m_emu_speed_tolerance_indicator->setText( tr("%1 ms").arg(m_emu_speed_tolerance_slider->value())); @@ -424,6 +430,8 @@ void AudioPane::SaveSettings() SConfig::GetInstance().m_audio_emu_speed_tolerance = m_emu_speed_tolerance_slider->value(); if (m_emu_speed_tolerance_slider->value() < 0) m_emu_speed_tolerance_indicator->setText(tr("Disabled")); + else if (m_emu_speed_tolerance_slider->value() == 0) + m_emu_speed_tolerance_indicator->setText(tr("None")); else m_emu_speed_tolerance_indicator->setText( tr("%1 ms").arg(m_emu_speed_tolerance_slider->value())); From 2721c0a11dee36ec9b8085a80c85e0053c1707c0 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 29 Jul 2020 21:42:58 +0300 Subject: [PATCH 17/56] Added relative input support. Implemented it for Win Mouse. --- Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp | 6 ++++++ Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h | 1 + 2 files changed, 7 insertions(+) diff --git a/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp b/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp index 3a396ddf37ff..b0f312289033 100644 --- a/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp +++ b/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp @@ -15,6 +15,7 @@ #include "Common/MathUtil.h" #include "Common/Thread.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" namespace ciface::Core { @@ -445,4 +446,9 @@ auto DeviceContainer::DetectInput(const std::vector& device_strings return detections; } + +Device::InputChannel Device::Input::GetCurrentInputChannel() const +{ + return ControllerInterface::GetCurrentInputChannel(); +} } // namespace ciface::Core diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h index a98d580465a3..dbb46ba13442 100644 --- a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h @@ -86,6 +86,7 @@ class KeyboardMouse : public Core::Device std::string name; }; + // TODO: copy new implementation from DInputKeyboardMouse.cpp class Axis : public Input { public: From c9956161fd07263dd9bcf9cd0ebb782b87731b2b Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 29 Jul 2020 22:00:24 +0300 Subject: [PATCH 18/56] Fixes --- Source/Core/AudioCommon/PulseAudioStream.cpp | 1 + Source/Core/Core/Config/MainSettings.cpp | 2 +- Source/Core/Core/State.cpp | 2 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 3 ++- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/Core/AudioCommon/PulseAudioStream.cpp b/Source/Core/AudioCommon/PulseAudioStream.cpp index fc82ff653711..e324e097d735 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.cpp +++ b/Source/Core/AudioCommon/PulseAudioStream.cpp @@ -120,6 +120,7 @@ bool PulseAudio::PulseInit() m_pa_ba.tlength = BUFFER_SAMPLES * m_channels * m_bytespersample; // designed latency, only change this flag for low latency output + // TODO: review this, audio stretching and DPLII won't work correctly if the latency is dynamic pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); m_pa_error = pa_stream_connect_playback(m_pa_s, nullptr, &m_pa_ba, flags, nullptr, nullptr); diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index f3795d4e36a4..6a48f13fbfdd 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -39,7 +39,7 @@ const Info MAIN_DPL2_DECODER{{System::Main, "Core", "DPL2Decoder"}, false} const Info MAIN_DPL2_QUALITY{{System::Main, "Core", "DPL2Quality"}, AudioCommon::GetDefaultDPL2Quality()}; const Info MAIN_DPL2_BASS_REDIRECTION{{System::Main, "Core", "DPL2BassRedirection"}, false}; -// TODO: rename "AudioBackendLatency", it's confusing +// TODO: rename ini "AudioBackendLatency", it's confusing const Info MAIN_AUDIO_BACKEND_LATENCY{{System::Main, "Core", "AudioLatency"}, 20}; const Info MAIN_AUDIO_MIXER_MAX_LATENCY{{System::Main, "Core", "AudioMixerMaxLatency"}, 40}; const Info MAIN_AUDIO_STRETCH{{System::Main, "Core", "AudioStretch"}, false}; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 8e832d8e2896..876e94773826 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 130; // Last changed in PR 9545 +constexpr u32 STATE_VERSION = 131; // Last changed in PR ???? // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 8c622b4e711b..0474a30098aa 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -624,7 +624,8 @@ void AudioPane::OnEmulationStateChanged(bool running) if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) { - // TODO: disable this later if the audio device turned out unable to support surround + // TODO: If the audio device turned out unable to support surround, add the text "failed" in the + // UI name, so the user can try to enable it again or disable surround from the game. bool enable_dolby_pro_logic = supports_current_emulation_state; m_dolby_pro_logic->setEnabled(enable_dolby_pro_logic); EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); From b870400d88781cd64dd75f93533df8c1bf97fa73 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 29 Jul 2020 22:15:12 +0300 Subject: [PATCH 19/56] Fixed nonsense comment --- .../InputCommon/ControlReference/ControlReference.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp index ee427d030c55..caa897e9f6ea 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.cpp +++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp @@ -50,14 +50,7 @@ enum class InputGateFlags : u8 // As of now these thread_local variables are only accessed by the host (UI) and the game thread. // Gate is "open" by default in case we don't bother setting it. static thread_local InputGateFlags tls_input_gate_flags = InputGateFlags::FullOpenMask; -// This list in never cleaned but unless InputReference(s) are not constantly removed and re-added -// on the fly, it's not a problem. We can't store the blocking in the object instance as it needs -// to be per thread. But in any case, this is safe as we only ever compare values to it. -// An alternative would be to have an ever increasing handle for each newly added InputReference. -// An even better alternative would be to have an helper struct, which contains the std::vector, -// and the helper struct would add itslef to a static (non thread_local) list on construction, -// and remove itself on destruction, meaning that if an InputReference got destroyed, we -// could safely remove all its references from the list in every thread. +// InputReference(s)* never change static thread_local std::vector tls_blocked_inputs; // From 833c040f9f8742811716b6a3acb7dfdf97e50e4a Mon Sep 17 00:00:00 2001 From: Filoppi Date: Mon, 3 Aug 2020 01:53:00 +0300 Subject: [PATCH 20/56] WIP (possibly worsened as of now) --- .../FreeSurround/FreeSurroundDecoder.h | 15 +- .../source/FreeSurroundDecoder.cpp | 30 +- Externals/FreeSurround/source/KissFFT.cpp | 3 + Source/Core/AudioCommon/AudioCommon.cpp | 2 +- Source/Core/AudioCommon/AudioStretcher.cpp | 2 +- Source/Core/AudioCommon/CubebStream.cpp | 26 +- Source/Core/AudioCommon/Enums.h | 7 +- Source/Core/AudioCommon/Mixer.cpp | 415 ++++++++++++------ Source/Core/AudioCommon/Mixer.h | 123 +++--- Source/Core/AudioCommon/SurroundDecoder.cpp | 198 +++++---- Source/Core/AudioCommon/SurroundDecoder.h | 19 +- Source/Core/AudioCommon/WASAPIStream.cpp | 6 +- Source/Core/Common/FixedSizeQueue.h | 36 +- Source/Core/Core/Config/MainSettings.cpp | 1 + Source/Core/Core/Config/MainSettings.h | 3 + .../Core/ConfigLoaders/IsSettingSaveable.cpp | 3 +- .../Core/HW/WiimoteEmu/EmuSubroutines.cpp | 7 + Source/Core/Core/HW/WiimoteEmu/Speaker.cpp | 17 +- Source/Core/Core/HW/WiimoteEmu/Speaker.h | 6 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 37 +- 20 files changed, 610 insertions(+), 346 deletions(-) diff --git a/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h b/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h index a5bf6eae07ba..1186ffefa871 100644 --- a/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h +++ b/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h @@ -17,7 +17,7 @@ // Dolphin: taken from https://hydrogenaud.io/index.php?topic=52235.0 but heavily modified // A more up to date version is available here: -// https://github.com/kodi-adsp/adsp.freesurround/blob/master/src/FreeSurroundDecoder.cpp +// https://github.com/kodi-adsp/adsp.freesurround #ifndef FREESURROUND_DECODER_H #define FREESURROUND_DECODER_H @@ -72,10 +72,11 @@ class DPL2FSDecoder { // @param setup The output channel setup -- determines the number of output // channels and their place in the sound field. // @param blocksize Granularity at which data is processed by the decode() - // function. Must be a power of two and should correspond to ca. 10ms worth - // of single-channel samples (default is 4096 for 44.1Khz data). Do not make - // it shorter or longer than 5ms to 20ms since the granularity at which - // locations are decoded changes with this. + // function. Must be a multiple of two (better if power of) and should + // correspond to ca. 10ms worth of single-channel samples + // (default is 4096 at 44.1Khz data (Dolphin: ???)). + // Do not make it shorter or longer than 5ms to 20ms since the granularity + // at which locations are decoded changes with this (Dolphin: not true, 40+). DPL2FSDecoder(); ~DPL2FSDecoder(); @@ -90,7 +91,9 @@ class DPL2FSDecoder { // @return A pointer to an internal buffer of exactly blocksize (multiplexed) // multichannel samples. The actual number of values depends on the number of // output channels in the chosen channel setup. - float *decode(float *input); + // Modified by Dolphin to take a ring buffer in 2 parts + float* decode(const float* input_part_1, const float* input_part_2, + unsigned int part_1_num); // Flush the internal buffer. void flush(); diff --git a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp index b70539dd7ff6..474a930b3da5 100644 --- a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp +++ b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp @@ -57,8 +57,9 @@ void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, inverse = kiss_fftr_alloc(N, 1, 0, 0); C = static_cast(chn_alloc[setup].size()); - // Allocate per-channel buffers - outbuf.resize((N + N / 2) * C); + // Allocate per-channel buffers (pad if we already have data) + float outputval = outbuf.size() > 0 ? outbuf.back() : 0.f; + outbuf.resize((N + N / 2) * C, outputval); for (unsigned int k = 0; k < signal.size(); k++) { signal[k].resize(N); @@ -77,8 +78,8 @@ void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, set_center_image(1); set_front_separation(1); set_rear_separation(1); - set_low_cutoff(40.0f / (samplerate / 2.f)); - set_high_cutoff(90.0f / (samplerate / 2.f)); + set_low_cutoff(40.0f); + set_high_cutoff(90.0f); set_bass_redirection(false); initialized = true; @@ -86,15 +87,20 @@ void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, // decode a stereo chunk, produces a multichannel chunk of the same size // (lagged) -float *DPL2FSDecoder::decode(float *input) { +float* DPL2FSDecoder::decode(const float* input_part_1, + const float* input_part_2, unsigned int part_1_num) { if (initialized) { + part_1_num = min(part_1_num, N); // append incoming data to the end of the input buffer - memcpy(&inbuf[N], &input[0], sizeof(float) * 2 * N); + memcpy(&inbuf[N], &input_part_1[0], sizeof(float) * 2 * part_1_num); + if (part_1_num < N) + memcpy(&inbuf[N + part_1_num * 2], &input_part_2[0], sizeof(float) * 2 + * (N - part_1_num)); // process first and second half, overlapped buffered_decode(&inbuf[0]); buffered_decode(&inbuf[N]); - // shift last half of the input to the beginning (for overlapping with a - // future block) + // shift the third half of the input to the beginning (for overlapping with + // a future block) memcpy(&inbuf[0], &inbuf[2 * N], sizeof(float) * N); buffer_empty = false; return &outbuf[0]; @@ -120,8 +126,12 @@ void DPL2FSDecoder::set_focus(float v) { focus = v; } void DPL2FSDecoder::set_center_image(float v) { center_image = v; } void DPL2FSDecoder::set_front_separation(float v) { front_separation = v; } void DPL2FSDecoder::set_rear_separation(float v) { rear_separation = v; } -void DPL2FSDecoder::set_low_cutoff(float v) { lo_cut = v * (N / 2); } -void DPL2FSDecoder::set_high_cutoff(float v) { hi_cut = v * (N / 2); } +void DPL2FSDecoder::set_low_cutoff(float v) { + lo_cut = (v / (samplerate / 2.f)) * (N / 2); +} +void DPL2FSDecoder::set_high_cutoff(float v) { + hi_cut = (v / (samplerate / 2.f)) * (N / 2); +} void DPL2FSDecoder::set_bass_redirection(bool v) { use_lfe = v; } // helper functions diff --git a/Externals/FreeSurround/source/KissFFT.cpp b/Externals/FreeSurround/source/KissFFT.cpp index 9799628f1517..603595b275aa 100644 --- a/Externals/FreeSurround/source/KissFFT.cpp +++ b/Externals/FreeSurround/source/KissFFT.cpp @@ -43,6 +43,8 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. functions. */ +#include + static void kf_bfly2(kiss_fft_cpx *Fout, const size_t fstride, const kiss_fft_cfg st, int m) { kiss_fft_cpx *Fout2; @@ -379,6 +381,7 @@ kiss_fft_cfg kiss_fft_alloc(int nfft, int inverse_fft, void *mem, sizeof(kiss_fft_cpx) * (nfft - 1); /* twiddle factors*/ if (lenmem == NULL) { + assert(false); // Support removed by Dolphin (unsafe) st = (kiss_fft_cfg) new char[memneeded]; } else { if (mem != NULL && *lenmem >= memneeded) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 83f12c6a181c..303d931fe3b5 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -144,7 +144,7 @@ std::string GetDefaultSoundBackend() DPL2Quality GetDefaultDPL2Quality() { - return DPL2Quality::Low; + return DPL2Quality::Normal; } std::vector GetSoundBackends() diff --git a/Source/Core/AudioCommon/AudioStretcher.cpp b/Source/Core/AudioCommon/AudioStretcher.cpp index c572b816e19d..926c3cf4f011 100644 --- a/Source/Core/AudioCommon/AudioStretcher.cpp +++ b/Source/Core/AudioCommon/AudioStretcher.cpp @@ -10,6 +10,7 @@ AudioStretcher::AudioStretcher(u32 sample_rate) : m_sample_rate(sample_rate) { m_sound_touch.setChannels(2); m_sound_touch.setSampleRate(m_sample_rate); + // Some "random" values that seemed good for Dolphin m_sound_touch.setSetting(SETTING_SEQUENCE_MS, 62); m_sound_touch.setSetting(SETTING_SEEKWINDOW_MS, 28); // Unfortunately the AA filter is only applied on the sample rate transposer, which we don't use, @@ -105,5 +106,4 @@ double AudioStretcher::GetAcceptableLatency() const { return m_sound_touch.getSetting(SETTING_NOMINAL_OUTPUT_SEQUENCE) / double(m_sample_rate); } - } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index 85b445e50d02..711813961a25 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -51,6 +51,7 @@ bool CubebStream::CreateStream() AudioCommon::GetOSMixerSampleRate() : AudioCommon::GetDefaultSampleRate()); + // cubeb always accepts 6 channels and then downmixes to 2 if they are not natively supported m_stereo = !SConfig::GetInstance().ShouldUseDPL2Decoder(); cubeb_stream_params params; @@ -68,14 +69,15 @@ bool CubebStream::CreateStream() params.layout = CUBEB_LAYOUT_3F2_LFE; } - // In samples - // Max supported by cubeb is 96000 and min is 1 (in samples) + // In samples. Max supported by cubeb is 96000 and min is 1 (in samples) u32 minimum_latency = 0; if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) ERROR_LOG_FMT(AUDIO, "Error getting minimum latency"); #ifdef _WIN32 - // Custom latency is ignored in Windows, despite being exposed (always 10ms, WASAPI max is 5000ms) + // Latency is ignored in Windows, despite being exposed (always 10ms, WASAPI max is 5000ms). + // However, it seems to somewhat be used when using its mixer (in case of up mixing + // or down mixing the n of channels) and audio thread runs too slow to keep up. const u32 final_latency = minimum_latency; #else const u32 target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; @@ -83,6 +85,8 @@ bool CubebStream::CreateStream() #endif INFO_LOG(AUDIO, "Latency: %u frames", final_latency); + // Note that cubeb latency might not be fixed and could dynamically adjust, especially + // when using its mixer and the audio thread can't keep up with itself. return cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, nullptr, ¶ms, final_latency, DataCallback, StateCallback, this) == CUBEB_OK; @@ -90,9 +94,12 @@ bool CubebStream::CreateStream() void CubebStream::DestroyStream() { - // Can't fail - cubeb_stream_destroy(m_stream); - m_stream = nullptr; + if (m_stream) + { + // Can't fail + cubeb_stream_destroy(m_stream); + m_stream = nullptr; + } } bool CubebStream::SetRunning(bool running) @@ -102,13 +109,13 @@ bool CubebStream::SetRunning(bool running) m_should_restart = false; if (m_settings_changed && running) { - m_settings_changed = false; DestroyStream(); // It's very hard for cubeb to fail starting a stream so we don't trigger a restart request if (!CreateStream()) { return false; } + m_settings_changed = false; } if (running) @@ -118,17 +125,16 @@ bool CubebStream::SetRunning(bool running) m_running = true; return true; } - return false; } else { - if (cubeb_stream_stop(m_stream) == CUBEB_OK) + if (!m_stream || cubeb_stream_stop(m_stream) == CUBEB_OK) { m_running = false; return true; } - return false; } + return false; } CubebStream::~CubebStream() diff --git a/Source/Core/AudioCommon/Enums.h b/Source/Core/AudioCommon/Enums.h index ec979ff94187..07b56eb74b88 100644 --- a/Source/Core/AudioCommon/Enums.h +++ b/Source/Core/AudioCommon/Enums.h @@ -8,9 +8,8 @@ namespace AudioCommon { enum class DPL2Quality { - Lowest = 0, - Low = 1, - High = 2, - Highest = 3 + Normal = 0, + High = 1, + Extreme = 2 }; } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 084e7e7e1006..0644226f1a5b 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -13,9 +13,10 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Swap.h" -#include "Core/Core.h" +#include "Common/Timer.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" +#include "Core/Core.h" #include "Common/Logging/Log.h" //To delete and all the uses (or make debug log) #include "VideoCommon/OnScreenDisplay.h" //To delete and all the uses @@ -28,6 +29,10 @@ Mixer::Mixer(u32 sample_rate) m_scratch_buffer.reserve(MAX_SAMPLES * NC); m_dma_speed.Start(true); + // These settings can't change at runtime, there aren't exposed to the UI + m_min_latency = Config::Get(Config::MAIN_AUDIO_MIXER_MIN_LATENCY) / 1000.0; + m_max_latency = Config::Get(Config::MAIN_AUDIO_MIXER_MAX_LATENCY) / 1000.0; + INFO_LOG(AUDIO, "Mixer is initialized"); m_on_state_changed_handle = Core::AddOnStateChangedCallback([this](Core::State state) { @@ -49,6 +54,7 @@ void Mixer::SetPaused(bool paused) // It would be nice to call m_dma_speed.Start(true) if m_dma_speed and paused are false, // but it doesn't seem to be thread safe (needs more investigation, but it's not necessary) m_dma_speed.SetPaused(paused); + m_last_mix_time = Common::Timer::GetTimeUs(); } void Mixer::DoState(PointerWrap& p) @@ -69,19 +75,28 @@ void Mixer::DoState(PointerWrap& p) void Mixer::UpdateSettings(u32 sample_rate) { + // Theoretically, we should change the playback rate of the currently buffered samples + // to not hear a different in pitch, but it's a minor thing m_sample_rate = sample_rate; m_stretcher.SetSampleRate(m_sample_rate); if (m_surround_changed) { m_surround_changed = false; - m_surround_decoder.Clear(); + // The cases here deal with the fact whether it was on or off + if (m_surround_decoder.CanReturnSamples() && !m_was_surround) + m_was_surround = true; + else if (m_was_surround) + m_was_surround = false; + else + m_surround_decoder.Clear(); } m_surround_decoder.InitAndSetSampleRate(m_sample_rate); + m_last_mix_time = Common::Timer::GetTimeUs(); } -// render num_samples sample pairs to samples[] -// advance indexR with sample position -// returns the new number of samples mixed (not the ones played backwards) +// -Render num_samples sample pairs to samples[] +// -Advance indexR with by the amount read +// -Return the new number of samples mixed (not the ones played backwards) u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) { // Cache access in non-volatile variable @@ -89,15 +104,36 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) // cache it locally although it's written here. // The writing pointer will be modified outside, but it will only increase, // so we will just ignore new written data while interpolating. - // Without this cache, the compiler wouldn't be allowed to optimize the - // interpolation loop. + // Without this cache, the compiler wouldn't be allowed loops. u32 indexR = m_indexR.load(); u32 indexW = m_indexW.load(); + double input_sample_rate = m_input_sample_rate.load(); + // The rate can be any, unfortunately we don't apply an anti aliasing filer, which means // we might get aliasing at higher rates (unless our mixer sample rate is very high) - double rate = (m_input_sample_rate * (stretching ? 1.0 : m_mixer->GetCurrentSpeed())) / - m_mixer->m_sample_rate; + double rate = (input_sample_rate * m_mixer->GetMixingSpeed()) / m_mixer->m_sample_rate; + + if (!stretching) + { + // Latency should be based on how many samples left we will have after the mixer has run + // (predicted), not before. Otherwise if there is a sudden change of speed in between mixes, + // or if the samples pushes and reads are done with very different timings, the latency won't + // be stable at all and we will end up constantly adjusting it towards a value that makes no + // sense. Also, this way we can target a latency of "0" + s32 post_mix_samples = SamplesDifference(indexW, indexR, rate, m_fract.load()) / NC; + post_mix_samples -= num_samples * rate + INTERP_SAMPLES; + double latency = std::max(post_mix_samples, 0) / input_sample_rate; + INFO_LOG(AUDIO, "latency: %lf", latency); + // This isn't big enough to notice but it is enough to make a difference and recover latency + + AdjustSpeedByLatency(latency, 0.0, m_mixer->GetMinLatency(), m_mixer->GetMaxLatency(), + NON_STRETCHING_CATCH_UP_SPEED, rate, m_latency_catching_up_direction); + } + else + { + m_latency_catching_up_direction = 0; + } s32 lVolume = m_lVolume.load(); s32 rVolume = m_rVolume.load(); @@ -144,9 +180,9 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) // We can't play backwards mixers that are not constantly pushed as we don't know when the // last sound started (we could not, but it's not worth implementing). // If required, this could be disabled from a config - if (behind_samples > 0 && m_constantly_pushed && !stretching) + if (behind_samples > 0 && !stretching && m_constantly_pushed) { - rate = m_input_sample_rate / m_mixer->m_sample_rate; //To review (this should actually follow the rate but with no prediction...) + rate = input_sample_rate / m_mixer->m_sample_rate; //To review (this should actually follow the rate but with no prediction...) s16* back_samples = samples + actual_samples_count * NC; // I've been thinking of this a lot and this is the bast way to deal with it. // If we don't have enough samples to mix the number of samples requested, we play back all samples starting from the last mixed one @@ -184,8 +220,8 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) // really disturbing, they are very low, and it's not like dolphin didn't have them before. // Also in general, ping ponging wouldn't give you enough time to appreciate the sounds, // they would constantly be interrupted. The only solution to crackling would be cross fade? - CubicInterpolation(back_samples, enable_backwards * behind_samples, rate, m_backwards_indexR, indexW, s[0], s[1], - lVolume, rVolume, false); + CubicInterpolation(back_samples, enable_backwards * behind_samples, rate, m_backwards_indexR, + indexW, s[0], s[1], lVolume, rVolume, false); } // Padding (constantly pushing the last sample when we run out to avoid sudden changes in the // audio wave). This is only needed on mixers that don't constantly push but are currently pushing, @@ -194,7 +230,8 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) { if (indexW > 8) OSD::AddMessage("Behind samples: " + std::to_string(behind_samples), 0U); - //To review: if we re-enable padding on wii mote forever, make sure it's disabled once we disconnect it. Also, fill with padded samples when starting to push it + //To review: if we re-enable padding on wii mote forever, make sure it's disabled once we disconnect it. + //Also, fill with padded samples when starting to push it unsigned int current_sample = actual_samples_count * 2; for (; current_sample < num_samples * 2; current_sample += 2) { @@ -222,21 +259,28 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r -1.5f, 2.0f, 0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f }; - u32 available_samples = SamplesDifference(indexW, indexR); s8 direction = forwards ? 1 : -1; - double& fract = forwards ? m_fract : m_backwards_fract; - s16* interpolation_buffer = m_mixer->m_interpolation_buffer.data(); - - u32 requested_samples = u32(rate * num_samples) * NC + NC; // Increase by 1 for imprecisions - u32 readable_samples = forwards ? available_samples : (MAX_SAMPLES * NC); - u32 samples_to_read = std::min(requested_samples + (INTERP_SAMPLES * NC), readable_samples); - u32 first_indexR = GetNextIndexR(indexR); - u32 last_indexR = first_indexR + samples_to_read * direction; - // Do the swaps once instead of processing them for each iteration below - for (u32 k = first_indexR; k != last_indexR + direction * NC; k += direction * NC) + double fract = forwards ? m_fract.load() : m_backwards_fract; + u32 available_samples = SamplesDifference(indexW, indexR, rate, fract); // Forwards only + s16* interpolation_buffer; + if (m_big_endians) + { + interpolation_buffer = m_mixer->m_interpolation_buffer.data(); + u32 requested_samples = u32(rate * num_samples) * NC + NC; // Increase by 1 for imprecisions + u32 readable_samples = forwards ? available_samples : (MAX_SAMPLES * NC); + u32 samples_to_read = std::min(requested_samples + (INTERP_SAMPLES * NC), readable_samples); + u32 first_indexR = GetNextIndexR(indexR, rate, fract); //To review: this is broken with backwards fract + u32 last_indexR = first_indexR + samples_to_read * direction; + // Do the swaps once instead of processing them for each iteration below + for (u32 k = first_indexR; k != last_indexR + direction * NC; k += direction * NC) + { + interpolation_buffer[(k + 0) & INDEX_MASK] = Common::swap16(m_buffer[(k + 0) & INDEX_MASK]); + interpolation_buffer[(k + 1) & INDEX_MASK] = Common::swap16(m_buffer[(k + 1) & INDEX_MASK]); + } + } + else { - interpolation_buffer[(k + 0) & INDEX_MASK] = Common::swap16(m_buffer[(k + 0) & INDEX_MASK]); - interpolation_buffer[(k + 1) & INDEX_MASK] = Common::swap16(m_buffer[(k + 1) & INDEX_MASK]); + interpolation_buffer = m_buffer.data(); } // fract requested to be reset so sure it will be 0 in the first cycle @@ -246,7 +290,7 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r } u32 i = 0; - u32 next_available_samples = available_samples; + u32 next_available_samples = available_samples; // Forwards only // Stop 3 (INTERP_SAMPLES) samples from the end as we need to interpolate with them while (i < num_samples && (!forwards || (next_available_samples > INTERP_SAMPLES * NC && next_available_samples <= available_samples))) @@ -273,9 +317,11 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r // run out of samples, but that won't sound any worse indexR += NC * whole * direction; - available_samples = next_available_samples; - next_available_samples = - SamplesDifference(indexW, indexR); + if (forwards) + { + available_samples = next_available_samples; + next_available_samples = SamplesDifference(indexW, indexR, rate, fract); + } const float x2 = float(fract); // x const float x1 = x2 * x2; // x^2 @@ -314,14 +360,26 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r ++i; } + if (forwards) + m_fract.store(fract); + else + m_backwards_fract = fract; + return i; } u32 Mixer::Mix(s16* samples, u32 num_samples) { - // We can't mix if the emulation is paused as m_dma_speed would return wrong speeds - if (!samples || num_samples == 0 || m_dma_speed.IsPaused()) + // We can't mix if the emulation is paused as m_dma_speed would return wrong speeds. + // We should still update the stretcher with the current speed, but num_samples == 0 + // only happens at the start and end of the emulation + if (!samples || num_samples == 0) return 0; + if (m_dma_speed.IsPaused()) + { + memset(samples, 0, num_samples * NC * sizeof(samples[0])); + return 0; + } bool stretching = SConfig::GetInstance().m_audio_stretch; @@ -330,6 +388,8 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) // backend latency in seconds double time_delta = double(num_samples) / m_sample_rate; + m_backend_latency = time_delta; + m_last_mix_time = Common::Timer::GetTimeUs(); double average_actual_speed = m_dma_speed.GetCachedAverageSpeed(false, true, true); bool predicting = true; @@ -337,14 +397,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) double actual_speed = m_dma_speed.GetLastSpeed(predicting, true); //INFO_LOG(AUDIO, "dma_mixer current speed: %lf", average_actual_speed); - double stretching_target_speed = 1.0; - //To review: keep the previous target speed while we calculate the new one (to get the correct indexR)? Have it double buffered. Also, maintain in when pass in between stretching and not - double& target_speed = stretching ? stretching_target_speed : m_target_speed; - target_speed = emulation_speed; - if (stretching) - { - m_target_speed = 1.0; - } + double target_speed = emulation_speed; if (!frame_limiter) { @@ -387,7 +440,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) if (actual_speed / emulation_speed < 1.0 - FallbackDelta) { //To make sure you got the best m_audio_emu_speed_tolerance value - //To review: this keeps trigger and going back to normal on GC due to the uneven sample rates? It also does NOT work with DPLII at the moment + //To review: this keeps trigger and going back to normal on GC due to the uneven sample rates? //To review: how does this sound if we always use actual_speed with sound stretching? //To review: test at higher backend latencies: smooth it over time? Ignore first missed frame? // If we fell behind of m_time_behind_target_speed seconds of samples, start using the actual emulation speed @@ -424,27 +477,31 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) } } - //To1 add a min latency as well for when we can't keep up with the target speed and we are running out of samples, better than playing backwards for the first part - //To1 review: this should be done per mixer - //To review, the stretcher can get stuck looping while stopping the process (breakpoint) for like 20 seconds? Only with DPLII? - double latency; + //To re-implement some mechanism for witch if the latency is a lot higher that the max, then it's played backwards faster, + //this is especially needed now as our buffers are larger, though it can only happen if the audio thread has got some problems. + //If you do the above, make sure you don't go over watch you are trying to reach by clamping the speed multiplication + //To implement negative min latency and slow down sound based on it to avoid finishing samples that frame? It should work if it only happens once in a while + //To review the case where we go lower than min latency. Should we drastically slow down the playback speed or not? The usage of min latency isn't really + //to prevent us running out of samples within an audio frame, but in the long run, meaning that is builds up enough samples so that + //timing differences won't make us run out + //To review, audio get stuck looping after stopping the process (breakpoint) for like 20 seconds? It seems like the buffer gets filled and + //then the same number of samples are written to it as they are being read, so you never move. + //double latency; // The target latency used to be iTimingVariance if stretching was off and // m_audio_stretch_max_latency if it was on. In the first case, the reason was to cache enough // samples to be able to withstand small hangs and changes in speed, but our new approach is to // play samples backwards when we are out of new ones so these are never problems. - double max_latency = Config::Get(Config::MAIN_AUDIO_MIXER_MAX_LATENCY) / 1000.0; //To read as little times as possible + //double max_latency = m_max_latency; + // In case you don't want to risk playing samples backwards, or your PC is unstable, or your audio + // backend constantly changes the number of samples it asks the mixer for, set this != from 0 + //double min_latency = m_min_latency; //To1 review: this doesn't seem to be needed when the frame_limiter is disabled (it does reach max latency more often, but it doesn't cause any problems), but when //we can't reach the target sped, this might sound bad? if (!frame_limiter || m_behind_target_speed) { static double mult = 1.0; - max_latency *= mult; + //max_latency *= mult; } - //To fix max_latency does NOT work with stretching and DPLII at the same time, just do the min of max latency and out_samples. Make sure this doesn't trigger when we are asked 0 samples? Or pass in the min block size required for latency - //bool is_surround = m_scratch_buffer.data() == samples; - //max_latency = std::max(time_delta, max_latency); - double catch_up_speed; - double target_latency; if (stretching) { @@ -454,51 +511,40 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) // already post mix. Normal mixers latency does not exist as they are all processed // immediately. Not that this isn't the whole stretcher latency, there is also the unprocessed // part which we can't control - latency = m_stretcher.GetProcessedLatency(); - double acceptable_latency = m_stretcher.GetAcceptableLatency() - time_delta; - target_latency = acceptable_latency + max_latency * 0.5; - max_latency += acceptable_latency; + const double latency = m_stretcher.GetProcessedLatency(); + INFO_LOG(AUDIO, "latency: %lf", latency); + const double acceptable_latency = m_stretcher.GetAcceptableLatency() - time_delta; + const double min_latency = m_min_latency + acceptable_latency; + const double max_latency = m_max_latency + acceptable_latency; // When we are pitch correcting it's harder to hear the change so we correct faster - catch_up_speed = STRETCHING_CATCH_UP_SPEED; - } - else - { - // Latency should be based on how many samples left we will have after the mixer has run - // (predicted), not before. Otherwise if there is a sudden change of speed in between mixes, - // or if the samples pushes and reads are done with very different timings, the latency won't - // be stable at all and we will end up constantly adjusting it towards a value that makes no - // sense. Also, this way we can target a latency of "0" - double rate = m_dma_mixer.GetInputSampleRate() * target_speed / m_sample_rate; - s32 post_mix_samples = m_dma_mixer.NumSamples(); - post_mix_samples -= num_samples * rate + INTERP_SAMPLES; - latency = std::max(post_mix_samples, 0) / m_dma_mixer.GetInputSampleRate(); - // This isn't big enough to notice but it is enough to make a difference and recover latency - catch_up_speed = NON_STRETCHING_CATCH_UP_SPEED; - target_latency = max_latency * 0.5; - } - //INFO_LOG(AUDIO, "latency: %lf", latency); + //STRETCHING_CATCH_UP_SPEED - // Instead of constantly adjusting the playback speed to be as close as possible to the target - // latency as we did before (which lowers quality due to fluctuations), we now have a latency - // tolerance, and while it is self adjusting when it goes too low, we need to make sure it - // doesn't go too high. So when it goes over the limit, we speed up the playback by a very small - // amount, almost unnoticeable, until we will have reached the target latency again. - // The only downside of having a variable latency is in music games, where you need to - // press a button when you hear a sound - if (latency > (m_latency_catching_up ? target_latency : max_latency)) - { - m_latency_catching_up = true; - //To1 review: this will be very slow if we are going at 0.1 speed already. - //If we add instead, we'd hear the difference more when at low speeds, and we'd catch up slower at high speeds. - //Maybe have a minimum of catch_up_speed to apply (e.g. 0.5 speed)? - target_speed *= catch_up_speed; - OSD::AddMessage("Reached max latency", 0U); + // Note that these changes in speed won't be "saved", so they are instantaneous, + // we'll assume the same starting speed next frame + AdjustSpeedByLatency(latency, acceptable_latency, min_latency, max_latency, + STRETCHING_CATCH_UP_SPEED, target_speed, + m_stretching_latency_catching_up_direction); } - else + + if (m_was_surround) { - m_latency_catching_up = false; + bool has_finished; + // As for stretching below, this won't follow the new rate but is still better + // than losing samples when changing settings + u32 received_samples = m_surround_decoder.ReturnSamples(samples, num_samples, has_finished); + num_samples -= received_samples; + samples += received_samples * NC; + + if (has_finished) + { + m_was_surround = false; + m_surround_decoder.Clear(); + } } + m_target_speed.store(target_speed); + m_mixing_speed.store(stretching ? 1 : target_speed); + if (stretching) { if (!m_stretching) @@ -510,9 +556,15 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) // as possible m_stretcher.SetTempo(target_speed, predicting); + // Note that here we haven't locked the mixers m_indexR, but at worse it will only + // increase by the time we get to the actual mixing. + // This might also be accessing a different mixer input sample rate than we'd find + // in the mixing, but the case where it changes is extremely rare and harmless u32 available_samples = std::min(m_dma_mixer.AvailableSamples(), m_streaming_mixer.AvailableSamples()); for (u8 i = 0; i < 4; ++i) { + // As long as the delay to realize the mixer has stopped pushing is lower than the stretching + // accepted latency, this will work if (m_wiimote_speaker_mixer[i].IsCurrentlyPushed()) { available_samples = @@ -551,9 +603,10 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) if (m_stretching) { + m_stretching_latency_catching_up_direction = 0; // Play out whatever we had left. Unprocessed samples will be lost. // Of course this behaves weirdly when toggling stretching every audio frame, - // but it's better than nothing + // and it doesn't follow the new rate, but it's better than losing samples u32 received_samples = m_stretcher.GetStretchedSamples(samples, num_samples, false); num_samples -= received_samples; samples += received_samples * NC; @@ -577,41 +630,92 @@ u32 Mixer::MixSurround(float* samples, u32 num_samples) { memset(samples, 0, num_samples * SURROUND_CHANNELS * sizeof(samples[0])); - //To1 review this code just seems wrong and breaks if we change the sample rate? - //To fix QuerySamplesNeededForSurroundOutput doesn't work with num_samples 0 or small - u32 needed_samples = m_surround_decoder.QuerySamplesNeededForSurroundOutput(num_samples); - - // If we set our latency too high, we might need more samples than we have, - // as the surround decoder can only accept exactly "needed_samples" - m_scratch_buffer.reserve(needed_samples * NC); - - //To have another intermediary buffer here? So we constantly read from mix and when we have enough we put into DPLII. - //We have no alternative as the sound stretcher returns a random number of samples every time, the rest will be padded, - //we can't just ask for 0 and then all of sudden ask for a ton of samples, it won't work. - //We need to put them aside as we go. Also, there is no way of predicting how many samples the mixer will be able to - //produce, nor revert the changes in case it did not produce enough. - //Reduce MAX_BLOCKS_BUFFERED if you do - - // Time stretching can be applied before decoding 5.1, it should be fine theoretically (untested). + // TODO: some backends might ask for a constantly changing number of samples, + // possibly only the ones that do some kind of internal mixing (when your device + // doesn't actually support 5.1), like cubeb or PulseAudio, but it might + // only happen if they notice they failed to keep up sync. + // The simplest solution sounds like finding a way of noticing that and setting + // the max number of samples ever required as the minimum latency of the surround + // so that we'd never run out of samples (when we are asked for more than usual, + // we'd ignore the last samples away). Unfortunately there is no simple solution, + // being constrained to blocks and unable to stretch. + + // Our latency might have increased + m_scratch_buffer.reserve(num_samples * NC); + + // TODO: we could have special path here which mixes samples directly in float, given that the + // cubic interpolation spits out floats. + + // Time stretching can be applied before decoding 5.1, it should be fine theoretically. // Mix() may also use m_scratch_buffer internally, but is safe because we alternate reads - // and writes. It returns the actual number of computed samples, which might be less - // than the required ones, but as long as it computed something (it's not just all padding) - // then we should use it for surround, otherwise these sounds would be missed - u32 available_samples = Mix(m_scratch_buffer.data(), needed_samples); - //To fix: we can't reach this at lower latencies? Or with stretching on - if (available_samples != needed_samples) - { - ERROR_LOG_FMT(AUDIO, "Error decoding surround samples"); - // This needs to do padding - return 0; - } + // and writes. + u32 mixed_samples = Mix(m_scratch_buffer.data(), num_samples); - m_surround_decoder.PushSamples(m_scratch_buffer.data(), needed_samples); - m_surround_decoder.GetDecodedSamples(samples, num_samples); + m_surround_decoder.PushSamples(m_scratch_buffer.data(), mixed_samples); + // Don't get any surround sample if the mixer return 0 as we are likely paused + m_surround_decoder.GetDecodedSamples(samples, mixed_samples); return num_samples; } +void Mixer::AdjustSpeedByLatency(double latency, double acceptable_latency, double min_latency, + double max_latency, double catch_up_speed, double& target_speed, + s8& latency_catching_up_direction) +{ + // Avoid divisions by 0 (don't rely on this, it's awful) + if (max_latency == min_latency) + { + if (latency > max_latency) + target_speed *= catch_up_speed; + else + target_speed /= catch_up_speed; + return; + } + static bool lock = false; //To delete + assert(!lock); + const double target_latency = min_latency + ((max_latency - min_latency) * 0.5); + // Instead of constantly adjusting the playback speed to be as close as possible to the target + // latency as we did before (which lowers quality due to fluctuations), we now have a latency + // tolerance, and while it is self adjusting when it goes too low, we need to make sure it + // doesn't go too high. So when it goes over the limit, we speed up the playback by a very small + // amount, almost unnoticeable, until we will have reached the target latency again. + // The only downside of having a variable latency is in music games, where you need to + // press a button when you hear a sound, but the variation is small enough + if ((latency_catching_up_direction == 0 && latency > max_latency) || + (latency_catching_up_direction > 0 && latency > target_latency)) + { + // Don't ever multiply the catch up speed by less than 1 + double times_over = std::max((latency - max_latency) / (max_latency - target_latency), 1.0); + latency_catching_up_direction = 1; + //To1 review: this will be very slow if we are going at 0.1 speed already. + //If we add instead, we'd hear the difference more when at low speeds, and we'd catch up slower + //at high speeds. Maybe have a minimum of catch_up_speed to apply (e.g. 0.5 speed)? + //Multiplying is good also because you recover faster an error that is likely happening again faster (not 100% true) + // It would be correct to somehow catch up faster if the target speed was greater than one + // already, especially because the correction wouldn't be audible, as the pitch change hearing + // tolerance goes one to one with the speed, but when going at very slow speeds, it would be + // incredibly slow to catch up, so it's incorrect. + target_speed += catch_up_speed * times_over; + OSD::AddMessage("Reached max latency", 0U); + } + else if ((latency_catching_up_direction == 0 && latency < min_latency && + latency > acceptable_latency) || + (latency_catching_up_direction < 0 && latency < target_latency)) + { + // This will likely always be 1 + double times_under = std::max((latency - min_latency) / (min_latency - target_latency), 1.0); + latency_catching_up_direction = -1; + //To review: this can go negative!!! + //To review: have a more aggressive speed when under latency? + target_speed -= catch_up_speed * times_under; + OSD::AddMessage("Reached min latency", 0U); + } + else + { + latency_catching_up_direction = 0; + } +} + void Mixer::MixerFifo::PushSamples(const s16* samples, u32 num_samples) { if (!samples || num_samples == 0) // nullptr can happen @@ -714,11 +818,11 @@ void Mixer::PushWiimoteSpeakerSamples(u8 index, const s16* samples, u32 num_samp m_wiimote_speaker_mixer[index].UpdatePush(double(num_samples) / sample_rate); + // Pretend they are stereo (we should add support for mono mixers but it's a lot of code) for (u32 i = 0; i < num_samples; ++i) { - // Wii mote speaker samples are mono and aren't swapped like the other, so we pre-swap them - m_conversion_buffer[i * NC] = Common::swap16(samples[i]); - m_conversion_buffer[i * NC + 1] = m_conversion_buffer[i * NC]; + m_conversion_buffer[i * NC] = samples[i]; + m_conversion_buffer[i * NC + 1] = samples[i]; } m_wiimote_speaker_mixer[index].PushSamples(m_conversion_buffer, num_samples); @@ -826,12 +930,11 @@ void Mixer::MixerFifo::DoState(PointerWrap& p) p.Do(m_rVolume); } -void Mixer::MixerFifo::SetInputSampleRate(double rate) +void Mixer::MixerFifo::SetInputSampleRate(double sample_rate) { // We should theoretically play all the current samples at the old sample rate, - // but the reality of that happening on real hardware when there are non zero - // samples is pretty low if not impossible - m_input_sample_rate = rate; + // but the reality of that happening when we have non silent samples is pretty low + m_input_sample_rate = sample_rate; } double Mixer::MixerFifo::GetInputSampleRate() const @@ -841,7 +944,7 @@ double Mixer::MixerFifo::GetInputSampleRate() const // For places that don't support floating point sample rates u32 Mixer::MixerFifo::GetRoundedInputSampleRate() const { - return round(m_input_sample_rate); + return std::round(m_input_sample_rate); } void Mixer::MixerFifo::SetVolume(u32 lVolume, u32 rVolume) @@ -853,7 +956,7 @@ void Mixer::MixerFifo::SetVolume(u32 lVolume, u32 rVolume) u32 Mixer::MixerFifo::AvailableSamples() const { u32 fifo_samples = NumSamples(); - // Mixer::MixerFifo::Mix always keeps some sample in the buffer, we want to ignore them + // Interpolation always keeps some sample in the buffer, we want to ignore them if (fifo_samples <= INTERP_SAMPLES) return 0; return (fifo_samples - INTERP_SAMPLES) * m_mixer->m_sample_rate / m_input_sample_rate; @@ -865,17 +968,22 @@ u32 Mixer::MixerFifo::NumSamples() const } u32 Mixer::MixerFifo::SamplesDifference(u32 indexW, u32 indexR) const +{ + double rate = (m_input_sample_rate * m_mixer->GetMixingSpeed()) / m_mixer->m_sample_rate; + return SamplesDifference(indexW, indexR, rate, m_fract.load()); +} +u32 Mixer::MixerFifo::SamplesDifference(u32 indexW, u32 indexR, double rate, double fract) const { // We can't have more than MAX_SAMPLES, if we do, we loop over - u32 diff = indexW - GetNextIndexR(indexR); + u32 diff = indexW - GetNextIndexR(indexR, rate, fract); u32 normalized_diff = diff & INDEX_MASK; return normalized_diff == 0u ? (diff == 0u ? 0u : (MAX_SAMPLES * NC)) : normalized_diff; } -u32 Mixer::MixerFifo::GetNextIndexR(u32 indexR) const +u32 Mixer::MixerFifo::GetNextIndexR(u32 indexR, double rate, double fract) const { - double rate = (m_input_sample_rate * m_mixer->GetCurrentSpeed()) / m_mixer->m_sample_rate; - return indexR + (m_fract >= 0.0 ? NC * u32(m_fract + rate) : 0.0); + //To review: this can still return an indexR greater than indexW... Stupid. Maybe save that is_finished bool anyway? + return indexR + (fract >= 0.0 ? NC * u32(fract + rate) : 0.0); } void Mixer::MixerFifo::UpdatePush(double time) @@ -901,24 +1009,37 @@ void Mixer::MixerFifo::UpdatePush(double time) currently_pushed = false; } - //To review: could we use the: Wii Mote Speaker enabled/disabled information in any way? + //To do: we could directly set m_currently_pushed when the wii mote speaker is muted or disabled if (m_currently_pushed != currently_pushed) { m_currently_pushed = currently_pushed; if (m_currently_pushed) { - // Add some silent samples to make sure we don't run out of samples, the target latency - // precisely. When stretching, we don't need to add a latency it's not related to time. - // Real wii motes deal with this in 2 ways: by disabling the speaker after a sound and by - // padding the last sample otherwise + static bool without = false; + if (without) return; //To delete + // Add some silent samples to make sure we don't run out of samples immediately, + // as we can't know when the mix is going to happen. + // When stretching, we don't need to add a latency as it's not related to time. + //To review: what if the wiimote push literally 20 samples and that's it? In that case we'd add a lot of latency for nothing? Maybe this is not worth it? if (!SConfig::GetInstance().m_audio_stretch) { - //To1 add: make sure the post mix latency is correct (== to target latency) - //So do the max between target latency AND backend/surround (mix) latency - //To review: also multiply this by game speed... - u32 num_samples = - (Config::Get(Config::MAIN_AUDIO_MIXER_MAX_LATENCY) / 2000.0) * m_input_sample_rate; + // Use AvailableSamples() to ignore INTERP_SAMPLES + const double current_latency = + (time + (AvailableSamples() / m_mixer->GetSampleRate())) / m_mixer->GetCurrentSpeed(); + const double target_latency = m_mixer->GetMinLatency() + + ((m_mixer->GetMaxLatency() - m_mixer->GetMinLatency()) * 0.5); + // Try to avoid adding the whole backend latency by predicting when the next audio thread + // mix will run + const double backend_elapsed_time = + (Common::Timer::GetTimeUs() - m_mixer->m_last_mix_time) / 1000000.0; + const double backend_time_alpha = + 1.0 - std::min(backend_elapsed_time / m_mixer->m_backend_latency, 1.0); + const double latency_to_add = + target_latency - current_latency + (m_mixer->m_backend_latency * backend_time_alpha); + + u32 num_samples = std::round(std::max(latency_to_add, 0.0) * m_input_sample_rate); num_samples = std::min(num_samples, MAX_SAMPLES); + memset(m_mixer->m_conversion_buffer, 0, num_samples * NC * sizeof(m_mixer->m_conversion_buffer[0])); PushSamples(m_mixer->m_conversion_buffer, num_samples); @@ -930,7 +1051,9 @@ void Mixer::MixerFifo::UpdatePush(double time) constexpr u32 num_samples = INTERP_SAMPLES + 1; // Add enough samples of silence to make sure when it has finished reading it won't stop on a - // non zero sample (which would restrict the range of the other mixers) + // non zero sample (which would restrict the range of the other mixers). + // Real wii motes deal with this in 2 ways: by disabling the speaker after a sound or by + // padding the last sample. s16 silent_samples[num_samples * NC]{}; PushSamples(silent_samples, num_samples); } diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 368036555997..4f1473ed18fa 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -54,13 +54,21 @@ class Mixer final u32 GetSampleRate() const { return m_sample_rate; } double GetCurrentSpeed() const { return m_target_speed; } + double GetMixingSpeed() const { return m_mixing_speed; } + double GetMinLatency() const { return m_min_latency; } + double GetMaxLatency() const { return m_max_latency; } + + static void AdjustSpeedByLatency(double latency, double acceptable_latency, double min_latency, + double max_latency, double catch_up_speed, double& target_speed, + s8& latency_catching_up_direction); // 512ms at 32kHz and ~341ms at 48kHz (on Wii, GC has similar values). // Make sure this is a power of 2 for INDEX_MASK to work, - // if not change all the "& INDEX_MASK" to "% (MAX_SAMPLES * NC)". + // if not change all "& INDEX_MASK" to "% (MAX_SAMPLES * NC)". // It's important that this is high enough to allow for enough backwards // samples to be played in during a stutter. It doesn't make much sense that - // this is independent from the sample rate, but it's fine + // this is independent from the sample rate, but it's fine. + // If we ever wanted to allow extremely high emulation speeds, we'd need to increase this static constexpr u32 MAX_SAMPLES = 16384; private: @@ -73,18 +81,18 @@ class Mixer final static constexpr u32 INDEX_MASK = MAX_SAMPLES * NC - 1; // Interpolation reserved/required samples - static constexpr u8 INTERP_SAMPLES = 3u; + static constexpr u32 INTERP_SAMPLES = 3u; - //To undo constexpr? + static constexpr double NON_STRETCHING_CATCH_UP_SPEED = 1.0175; static constexpr double STRETCHING_CATCH_UP_SPEED = 1.25; - static constexpr double NON_STRETCHING_CATCH_UP_SPEED = 1.015; // A single mixer in/out buffer. The original alignment they might have had on real HW isn't kept class MixerFifo final { public: - MixerFifo(Mixer* mixer, unsigned sample_rate, bool constantly_pushed = true) - : m_mixer(mixer), m_input_sample_rate(sample_rate), m_constantly_pushed(constantly_pushed) + MixerFifo(Mixer* mixer, unsigned sample_rate, bool big_endians, bool constantly_pushed = true) + : m_mixer(mixer), m_input_sample_rate(sample_rate), + m_big_endians(big_endians), m_constantly_pushed(constantly_pushed) { } void DoState(PointerWrap& p); @@ -92,97 +100,110 @@ class Mixer final // Returns the actual mixed samples num, pads the rest with the last sample. // Executed from sound stream thread u32 Mix(s16* samples, u32 num_samples, bool stretching = false); - void SetInputSampleRate(double rate); + void SetInputSampleRate(double sample_rate); double GetInputSampleRate() const; u32 GetRoundedInputSampleRate() const; bool IsCurrentlyPushed() const { return m_currently_pushed; } // Expects values from 0 to 255 void SetVolume(u32 lVolume, u32 rVolume); - // Returns the max number of samples we will be able to mix. + // Returns the max number of samples we will be able to mix (in output sample rate). // This is not precise due to the interpolation fract, but it's close enough u32 AvailableSamples() const; u32 NumSamples() const; u32 SamplesDifference(u32 indexW, u32 indexR) const; - u32 GetNextIndexR(u32 indexR) const; + u32 SamplesDifference(u32 indexW, u32 indexR, double rate, double fract) const; + u32 GetNextIndexR(u32 indexR, double rate, double fract) const; void UpdatePush(double time); // Returns the actual number of samples written. Outputs the last played sample for padding. - // We pass in in/out samples to cache the last calculated value. It's in int32 range because - // cubic interpolation can produce values over int16 and we don't want to lose the extra + // It's in int32 range because the interpolation can produce values over int16 and we don't want + // to lose the extra u32 CubicInterpolation(s16* samples, u32 num_samples, double rate, u32& indexR, u32 indexW, s32& l_s, s32& r_s, s32 lVolume, s32 rVolume, bool forwards = true); private: Mixer* m_mixer; std::atomic m_input_sample_rate; - // Ring input buffer directly from emulated HW. In big endians. + // Ring input buffer directly from emulated HW. Might be big endians. // Even indexes are right channel, as opposed to the output std::array m_buffer{}; - // Write (how many have been written, index of the next one to write) - // Start from INTERP_SAMPLES + 1 so that we gradually blend into the initial samples - std::atomic m_indexW{(INTERP_SAMPLES + 1) * NC}; - // Read (how many have been read - 1, index of last one to have been read and that we are - // currently interpolating) - std::atomic m_indexR{0}; + // m_buffer indexes + // Write: how many have been written, index of the next one to write. + // Starts from INTERP_SAMPLES + 1 so that we gradually blend into the initial samples. + // Read: how many have been read - 1, index of last one to have been read and that we are + // currently interpolating. // If their difference is 0, we will have read all the samples (or there are none left anyway). // We could still re-read the current indexR to get the last sample value (0 if never written). - // This is because we don't want to increase indexR over indexW to tell that we have finished reading - // the pushed samples, it would be confusing. - // indexR is the last read (fully consumed, we moved over) value, while indexW is the last written + 1 (the next one to be written), - // so if they are the same, we have read over the last written. (???) - u32 m_backwards_indexR = 0; + // This is because we don't want to increase indexR over indexW to tell that we have finished + // reading the pushed samples, it would be confusing. They loop over the max range fine. + std::atomic m_indexW{(INTERP_SAMPLES + 1) * NC}; + std::atomic m_indexR{0}; + u32 m_backwards_indexR = 0; // Keep track of backwards playing samples + + // See comment on CubicInterpolation() + s32 m_last_output_samples[NC]{}; + // In and out rate might not be 1, this is the fract position we are reading between 2 samples. + // We only write this from the audio thread. + // It would be nice to reset once in a while, like on DoState(), or when the in/out sample rates + // change, as it can fall out of alignment, but it would be wrong and the gains would be minimal + std::atomic m_fract{-1.0}; + double m_backwards_fract = -1.0; // Doesn't need to be atomic + s8 m_latency_catching_up_direction = 0; + // Volume range is 0-256 std::atomic m_lVolume{256}; std::atomic m_rVolume{256}; - // Se comment on CubicInterpolation() - s32 m_last_output_samples[NC]{}; - // It would be nice to reset once in a while, like on do state, or when the in/out sample rates - // change, as it can fall out of alignment, but it would be wrong and the gains would be minimal - double m_fract = -1.0; - double m_backwards_fract = -1.0; - //To review: if this was off, that mixer won't gather a buffer/latency, and it would always be on the brink of playback - //Don't do any padding for the wiimote speaker, because samples are only submitted when played, - //so the last one might not be 0, which would end up offsetting every other sample forever - // Ignores simple sound stretching and padding and backwards playing - bool m_constantly_pushed; + // Some mixers don't constantly push new samples and might push at arbitrary times, + // in that case, we can't play samples backwards and we need some special code to handle + // the max/min latency + const bool m_constantly_pushed; std::atomic m_currently_pushed{false}; double m_last_push_timer = -1.0; + const bool m_big_endians; }; - MixerFifo m_dma_mixer{this, 32000}; - MixerFifo m_streaming_mixer{this, 48000}; - // There is no way of knowing when the wii mote speakers will have finished pushing samples - MixerFifo m_wiimote_speaker_mixer[4]{ - {this, 3000, false}, {this, 3000, false}, {this, 3000, false}, {this, 3000, false}}; - u32 m_sample_rate; + MixerFifo m_dma_mixer{this, 32000, true}; + MixerFifo m_streaming_mixer{this, 48000, true}; + MixerFifo m_wiimote_speaker_mixer[4]{{this, 6000, false, false}, + {this, 6000, false, false}, + {this, 6000, false, false}, + {this, 6000, false, false}}; - // The size is always left at 0, they are never initialed + // The size is always left at 0, they are never initialed, we use memset std::vector m_scratch_buffer; std::array m_interpolation_buffer; s16 m_conversion_buffer[MAX_SAMPLES * NC]; - int m_on_state_changed_handle = -1; - - // Target emulation speed, but it can fallback to the actual emulation speed - double m_target_speed = 1.0; //To make atomic (m_fract as well???) (make sure they aren't used twice in a line if so) + // Target mixing (playback) speed, it's usually equal the target emulation speed + std::atomic m_target_speed{1.0}; + // Same as m_target_speed, but set to 1 when stretching as that takes care of speed + std::atomic m_mixing_speed{1.0}; double m_time_behind_target_speed = 0.0; bool m_behind_target_speed = false; std::atomic m_time_at_custom_speed{0.0}; - bool m_latency_catching_up = false; + s8 m_stretching_latency_catching_up_direction = 0; + std::atomic m_backend_latency{0.0}; + std::atomic m_last_mix_time{0.0}; + double m_min_latency; + double m_max_latency; + // Average of the last 0.425 seconds, a good balance between reactiveness and smoothness. + // Backend latency should be <= this to work well. + // Start at the most common sample rate and pushed samples num per batch + AudioSpeedCounter m_dma_speed{0.425, 32000, 560}; + u32 m_sample_rate; // Only changed by main or emulation thread when the backend is not running bool m_stretching = false; std::atomic m_surround_changed{false}; + bool m_was_surround = false; AudioCommon::AudioStretcher m_stretcher; AudioCommon::SurroundDecoder m_surround_decoder; + int m_on_state_changed_handle = -1; + WaveFileWriter m_wave_writer_dtk; WaveFileWriter m_wave_writer_dsp; bool m_log_dtk_audio = false; bool m_log_dsp_audio = false; - - // Average of the last 0.425 seconds, Start at the most - // common sample rate and pushed samples num per batch - AudioSpeedCounter m_dma_speed{0.425, 32000, 560}; }; diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index f003b811813b..b207c23999ba 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -9,51 +9,35 @@ #include "AudioCommon/Enums.h" #include "AudioCommon/SurroundDecoder.h" -#include "Common/MathUtil.h" #include "Core/Config/MainSettings.h" - +#include "VideoCommon/OnScreenDisplay.h" #pragma optimize("", off) //To delete namespace AudioCommon { -// Quality (higher quality also means more latency). Needs to be a pow of 2 so we find the closest +//To align backend latency and this one (and explain it in UI) +// Quality (higher quality also means more latency). Needs to be a multiple of 2 static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) { u32 frame_block_time; // ms switch (quality) { - case DPL2Quality::Lowest: - frame_block_time = 10; - break; case DPL2Quality::High: - frame_block_time = 40; + frame_block_time = 20; break; - // TODO: review this case, it's too much, FreeSurround said to not go over 20ms. - // The quality/latency trade off is not worth it and it might introduce crackling. - case DPL2Quality::Highest: - frame_block_time = 80; + // FreeSurround said to not go over 20ms, so this is overkill already + case DPL2Quality::Extreme: + frame_block_time = 40; break; - case DPL2Quality::Low: + case DPL2Quality::Normal: default: - frame_block_time = 20; + frame_block_time = 10; } - const u32 frame_block = std::round(sample_rate * frame_block_time / 1000.0); + u32 frame_block = std::round(sample_rate * frame_block_time / 1000.0); + frame_block = (frame_block / 2) * 2; + // Assert because FreeSurround would crash anyway, this can only be triggered by code changes assert(frame_block > 1); - //To make sure this is even needed... - return MathUtil::NearestPowerOf2(frame_block); -} - -u32 SurroundDecoder::QuerySamplesNeededForSurroundOutput(u32 output_samples) const -{ - //To review: what would happen if they are ==? How is the output fifo supposed to decide the input amount? - if (output_samples > u32(m_decoded_fifo.size()) / SURROUND_CHANNELS) - { - // Output stereo samples needed to have at least the desired number of surround samples - u32 samples_needed = output_samples - (u32(m_decoded_fifo.size()) / SURROUND_CHANNELS); - return samples_needed + m_frame_block_size - (samples_needed % m_frame_block_size); - } - - return 0; + return frame_block; } SurroundDecoder::SurroundDecoder(u32 sample_rate) @@ -66,21 +50,23 @@ SurroundDecoder::~SurroundDecoder() = default; void SurroundDecoder::InitAndSetSampleRate(u32 sample_rate) { - if (m_sample_rate == sample_rate) - return; - m_sample_rate = sample_rate; - - m_frame_block_size = - DPL2QualityToFrameBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), m_sample_rate); - - // This DPLII quality at this sample rate is not supported, increase the size or lower your - // settings - assert(m_frame_block_size * STEREO_CHANNELS <= m_float_conversion_buffer.max_size()); - assert(m_frame_block_size * SURROUND_CHANNELS * MAX_BLOCKS_BUFFERED <= m_decoded_fifo.max_size()); + u32 frame_block_size = + DPL2QualityToFrameBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), sample_rate); // Re-init. It should keep the samples in the buffer (and filling the rest with 0) while just - // changing the settings - m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate); + // updating the settings + if (m_sample_rate != sample_rate || m_frame_block_size != frame_block_size) + { + m_frame_block_size = frame_block_size; + m_sample_rate = sample_rate; + m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate); + + // Increase m_decoded_fifo size if this becomes common occurrence + // (the warning is sent with a very large safety margin) + if (m_frame_block_size * SURROUND_CHANNELS * MAX_BLOCKS_BUFFERED > m_decoded_fifo.max_size()) + OSD::AddMessage("This DPLII Quality at this Sample Rate is not supported.\nLower your " + "settings to not risk missing samples", 6000U); + } // The LFE channel (bass redirection) is disabled in the surround decoder, as most people // have their own low pass crossover m_fsdecoder->set_bass_redirection(Config::Get(Config::MAIN_DPL2_BASS_REDIRECTION)); @@ -90,59 +76,119 @@ void SurroundDecoder::Clear() { m_fsdecoder->flush(); m_decoded_fifo.clear(); + m_float_conversion_buffer.clear(); } // Receive and decode samples void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) { - //To make sure it works - assert(num_samples % m_frame_block_size == 0); - // We support a max of MAX_BLOCKS_BUFFERED blocks in the buffer, because of m_decoded_fifo, - // just increase if you need. This might trigger if you have very high backend latencies - assert(num_samples <= m_frame_block_size * MAX_BLOCKS_BUFFERED); - u32 remaining_samples = num_samples; - size_t sample_index = 0; - - while (remaining_samples > 0) + u32 read_samples = 0; + // We do it this way so m_float_conversion_buffer never exceeds m_frame_block_size + // and we can be sure we won't miss samples on that front + while (num_samples > 0) { - // Convert to float - for (size_t i = 0, end = m_frame_block_size * STEREO_CHANNELS; i < end; ++i) + u32 samples_to_block = 0; + if (u32(m_float_conversion_buffer.size() / STEREO_CHANNELS) < m_frame_block_size) { - m_float_conversion_buffer[i] = - in[i + sample_index * STEREO_CHANNELS] / float(std::numeric_limits::max()); + samples_to_block = + m_frame_block_size - (u32(m_float_conversion_buffer.size() / STEREO_CHANNELS)); } + u32 samples_to_push = std::min(samples_to_block, num_samples); - // Decode. Note that output isn't clamped within -1.f and +1.f - const float* dpl2_fs = m_fsdecoder->decode(m_float_conversion_buffer.data()); - - // Add to ring buffer and fix channel mapping - // FreeSurround: - // FL | FC | FR | BL | BR | LFE - // Most backends: - // FL | FR | FC | LFE | BL | BR - for (size_t i = 0; i < m_frame_block_size; ++i) + // Convert to float (samples are from SHRT_MIN to SHRT_MAX) + for (size_t i = read_samples * STEREO_CHANNELS; + i < (samples_to_push * STEREO_CHANNELS) + (read_samples * STEREO_CHANNELS); ++i) { - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 0]); // LEFTFRONT - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 2]); // RIGHTFRONT - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 1]); // CENTREFRONT - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 5]); // LFE/SUB - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 3]); // LEFTREAR - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 4]); // RIGHTREAR + m_float_conversion_buffer.push(in[i] / float(-std::numeric_limits::min())); } + read_samples += samples_to_push; + num_samples -= samples_to_push; - remaining_samples = remaining_samples - s32(m_frame_block_size); - sample_index = sample_index + m_frame_block_size; + // We have enough samples to process a block + if (m_float_conversion_buffer.size() >= m_frame_block_size * STEREO_CHANNELS) + { + // Decode. Note that output isn't clamped within -1.f and +1.f. + // Will return half silent (~0) samples for the first call + const float* dpl2_fs = m_fsdecoder->decode( + &m_float_conversion_buffer.front(), &m_float_conversion_buffer.beginning(), + u32(m_float_conversion_buffer.size_to_end() / STEREO_CHANNELS)); + m_float_conversion_buffer.erase(m_frame_block_size * STEREO_CHANNELS); + + // TODO: modify FreeSurround to have the same channel mapping for performance (see channel_id) + // Add to ring buffer and fix channel mapping. + // If, at maxed out settings, your backend latency is 8 times higher than the block size, this + // will overwrite older samples. Avoid adding a check/warning for now as it's very unlikely + // FreeSurround: + // FL | FC | FR | BL | BR | LFE + // Most backends: + // FL | FR | FC | LFE | BL | BR + for (size_t i = 0; i < m_frame_block_size; ++i) + { + m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 0]); // LEFTFRONT + m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 2]); // RIGHTFRONT + m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 1]); // CENTREFRONT + m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 5]); // LFE/SUB + m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 3]); // LEFTREAR + m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 4]); // RIGHTREAR + } + } } } void SurroundDecoder::GetDecodedSamples(float* out, u32 num_samples) { - // TODO: this could be optimized by copying the ring buffer in two parts + if (num_samples == 0) + return; + // Copy to output array with desired num_samples - for (size_t i = 0, num_samples_output = num_samples * SURROUND_CHANNELS; i < num_samples_output; - ++i) + const u32 readable_samples = + std::min(num_samples * SURROUND_CHANNELS, u32(m_decoded_fifo.size())); + const u32 front_readable_samples = std::min(readable_samples, u32(m_decoded_fifo.size_to_end())); + // Samples looping over the ring buffer + const u32 beginning_readable_samples = readable_samples - front_readable_samples; + + memcpy(&out[0], &m_decoded_fifo.front(), sizeof(float) * front_readable_samples); + if (beginning_readable_samples > 0) + memcpy(&out[front_readable_samples], &m_decoded_fifo.beginning(), + sizeof(float) * beginning_readable_samples); + m_decoded_fifo.erase(readable_samples); + + if (readable_samples > 0) + { + memcpy(&m_last_decoded_samples[0], &out[readable_samples - SURROUND_CHANNELS], + sizeof(float) * SURROUND_CHANNELS); + } + + // Padding should never happen. Once we have reached enough samples in the buffer, + // it should always keep that number, unless the backend latency changes + u32 read_samples = readable_samples; + while (read_samples < num_samples * SURROUND_CHANNELS) { - out[i] = m_decoded_fifo.pop_front(); + memcpy(&out[read_samples], &m_last_decoded_samples[0], sizeof(float) * SURROUND_CHANNELS); + read_samples += SURROUND_CHANNELS; } } + +u32 SurroundDecoder::ReturnSamples(s16* out, u32 num_samples, bool& has_finished) +{ + const u32 readable_samples = + std::min(u32(m_float_conversion_buffer.size()), num_samples * STEREO_CHANNELS); + for (size_t i = 0, k = 0; i < readable_samples; ++i) + { + out[k] = + s16(std::clamp(s32(m_float_conversion_buffer[i] * s32(-std::numeric_limits::min())), + s32(std::numeric_limits::min()), s32(std::numeric_limits::max()))); + ++k; + } + m_float_conversion_buffer.erase(readable_samples); + has_finished = (m_float_conversion_buffer.size() == 0); + // We could also return a part of the samples kept for future overlap in m_decoded_fifo + // and the already converted ones in m_decoded_fifo, but it's not worth it + return readable_samples / STEREO_CHANNELS; +} + +bool SurroundDecoder::CanReturnSamples() const +{ + return m_float_conversion_buffer.size() > 0; +} } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/SurroundDecoder.h b/Source/Core/AudioCommon/SurroundDecoder.h index b4b91cd1918e..7350222551ce 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.h +++ b/Source/Core/AudioCommon/SurroundDecoder.h @@ -4,7 +4,6 @@ #pragma once -#include #include #include "Common/CommonTypes.h" @@ -20,24 +19,26 @@ class SurroundDecoder public: explicit SurroundDecoder(u32 sample_rate); ~SurroundDecoder(); - u32 QuerySamplesNeededForSurroundOutput(u32 output_samples) const; void InitAndSetSampleRate(u32 sample_rate); void PushSamples(const s16* in, u32 num_samples); void GetDecodedSamples(float* out, u32 num_samples); + u32 ReturnSamples(s16* out, u32 num_samples, bool& has_finished); + bool CanReturnSamples() const; void Clear(); -private: - u32 m_sample_rate = 0; - u32 m_frame_block_size; - static constexpr u32 STEREO_CHANNELS = 2; static constexpr u32 SURROUND_CHANNELS = 6; - // Max supported samples rate is about 192kHz at highest block quality (~80ms) - static constexpr u32 MAX_BLOCKS_SIZE = 16384; + // Max supported samples rate is about 192kHz at highest block quality (~40ms) + static constexpr u32 MAX_BLOCKS_SIZE = 7680; static constexpr u32 MAX_BLOCKS_BUFFERED = 8; +private: + u32 m_sample_rate = 0; + u32 m_frame_block_size = 0; + std::unique_ptr m_fsdecoder; - std::array m_float_conversion_buffer; + FixedSizeQueue m_float_conversion_buffer; FixedSizeQueue m_decoded_fifo; + float m_last_decoded_samples[SURROUND_CHANNELS]; }; } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 16faf152227e..a0a64f6d7d21 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -646,6 +646,10 @@ bool WASAPIStream::SetRunning(bool running) GetMixer()->GetSampleRate()); m_surround = false; + // Let the mixer know so it can clean the samples left in the surround decoder + GetMixer()->SetSurroundChanged(); + GetMixer()->UpdateSettings(sample_rate); + m_format.Format.nChannels = STEREO_CHANNELS; m_format.Format.nBlockAlign = m_format.Format.nChannels * m_format.Format.wBitsPerSample / 8; m_format.Format.nAvgBytesPerSec = @@ -941,7 +945,7 @@ void WASAPIStream::SoundLoop() // From float(32) (~-1 to ~1 but can go beyond limits) to signed short(16) for (u32 i = 0; i < mixed_samples * SURROUND_CHANNELS; ++i) { - m_surround_samples[i] *= volume * std::numeric_limits::max(); + m_surround_samples[i] *= volume * -std::numeric_limits::min(); m_surround_samples[i] = std::clamp(m_surround_samples[i], float(std::numeric_limits::min()), float(std::numeric_limits::max())); diff --git a/Source/Core/Common/FixedSizeQueue.h b/Source/Core/Common/FixedSizeQueue.h index 4d6b243457f8..c7eed89edcb5 100644 --- a/Source/Core/Common/FixedSizeQueue.h +++ b/Source/Core/Common/FixedSizeQueue.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -58,6 +59,7 @@ class FixedSizeQueue tail = (tail + 1) % N; } + // From front void pop() { assert(count > 0); @@ -75,6 +77,27 @@ class FixedSizeQueue return temp; } + // From front + void erase(size_t num) + { + assert(count >= num); + if constexpr (!std::is_trivial_v) + { + while (num > 0) + { + storage[head] = {}; + head = (head + 1) % N; + count--; + num--; + } + } + else + { + head = (head + num) % N; + count -= num; + } + } + T& operator[](size_t index) { assert(index < count); @@ -88,14 +111,21 @@ class FixedSizeQueue T& front() noexcept { return storage[head]; } const T& front() const noexcept { return storage[head]; } + T& back() noexcept { return storage[tail]; } + const T& back() const noexcept { return storage[tail]; } + // Only use if size_to_end() > size() + T& beginning() noexcept { return storage[0]; } + const T& beginning() const noexcept { return storage[0]; } size_t size() const noexcept { return count; } size_t max_size() const noexcept { return N; } + // Helper to know how many more samples we could read before needing to loop over + size_t size_to_end() const noexcept { return N - head; } bool empty() const noexcept { return size() == 0; } private: std::array storage; - unsigned int head = 0; - unsigned int tail = 0; + size_t head = 0; + size_t tail = 0; // Not necessary but avoids a lot of calculations - unsigned int count = 0; + size_t count = 0; }; diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 6a48f13fbfdd..3de0386d3531 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -41,6 +41,7 @@ const Info MAIN_DPL2_QUALITY{{System::Main, "Core", "D const Info MAIN_DPL2_BASS_REDIRECTION{{System::Main, "Core", "DPL2BassRedirection"}, false}; // TODO: rename ini "AudioBackendLatency", it's confusing const Info MAIN_AUDIO_BACKEND_LATENCY{{System::Main, "Core", "AudioLatency"}, 20}; +const Info MAIN_AUDIO_MIXER_MIN_LATENCY{{System::Main, "Core", "AudioMixerMinLatency"}, 0}; const Info MAIN_AUDIO_MIXER_MAX_LATENCY{{System::Main, "Core", "AudioMixerMaxLatency"}, 40}; const Info MAIN_AUDIO_STRETCH{{System::Main, "Core", "AudioStretch"}, false}; const Info MAIN_MEMCARD_A_PATH{{System::Main, "Core", "MemcardAPath"}, ""}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 996d72d76a10..9e41a924630f 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -41,6 +41,9 @@ extern const Info MAIN_DPL2_DECODER; extern const Info MAIN_DPL2_QUALITY; extern const Info MAIN_DPL2_BASS_REDIRECTION; extern const Info MAIN_AUDIO_BACKEND_LATENCY; +// Only set this different from zero in case your audio backend constantly changes the number of +// samples it asks the mixer for +extern const Info MAIN_AUDIO_MIXER_MIN_LATENCY; extern const Info MAIN_AUDIO_MIXER_MAX_LATENCY; extern const Info MAIN_AUDIO_STRETCH; extern const Info MAIN_MEMCARD_A_PATH; diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index 20b667f31553..3bc100b87278 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -34,7 +34,7 @@ bool IsSettingSaveable(const Config::Location& config_location) } } - static constexpr std::array s_setting_saveable = { + static constexpr std::array s_setting_saveable = { // Main.Core &Config::MAIN_DEFAULT_ISO.GetLocation(), @@ -45,6 +45,7 @@ bool IsSettingSaveable(const Config::Location& config_location) &Config::MAIN_DPL2_DECODER.GetLocation(), &Config::MAIN_DPL2_QUALITY.GetLocation(), &Config::MAIN_DPL2_BASS_REDIRECTION.location, + &Config::MAIN_AUDIO_MIXER_MIN_LATENCY.location, &Config::MAIN_AUDIO_MIXER_MAX_LATENCY.location, &Config::MAIN_RAM_OVERRIDE_ENABLE.GetLocation(), &Config::MAIN_MEM1_SIZE.GetLocation(), diff --git a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp index bf71f7e6f667..1c4681386971 100644 --- a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp @@ -20,6 +20,7 @@ #include "Core/HW/WiimoteReal/WiimoteReal.h" #include "InputCommon/ControllerEmu/ControlGroup/Attachments.h" #include "InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.h" +#pragma optimize("", off) //To delete namespace WiimoteEmu { @@ -357,6 +358,9 @@ void Wiimote::HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature& { m_speaker_mute = rpt.enable; + // TODO: this is actually an HW write + m_speaker_logic.ResetDecoder(); + if (rpt.ack) SendAck(OutputReportID::SpeakerMute, ErrorCode::Success); } @@ -365,6 +369,9 @@ void Wiimote::HandleSpeakerEnable(const WiimoteCommon::OutputReportEnableFeature { m_status.speaker = rpt.enable; + // TODO: this is actually an HW write + m_speaker_logic.ResetDecoder(); + if (rpt.ack) SendAck(OutputReportID::SpeakerEnable, ErrorCode::Success); } diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp index ef5ab35ce3cc..6544f02453e4 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp @@ -92,11 +92,12 @@ void stopdamnwav() } #endif -void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan) +void SpeakerLogic::SpeakerData(const u8* data, int length) { if (reg_data.sample_rate == 0 || length == 0) return; + //To reivew: not sure about mute... // Even if volume is zero or WiimoteEnableSpeaker is off we process samples to maintain proper // decoder state @@ -166,6 +167,7 @@ void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan) // Multiply by 256, floor to int, and clamp to 255 for a uniformly mapped conversion. const double volume = float(reg_data.volume) * 256.f / volume_divisor; + // Pan is -1.0 (L) to +1.0 (R) // This used the "Constant Power Pan Law", but it is undesiderable // if the pan is 0, and it implied that the loudness of a wiimote speaker // is equal to the loudness of your device speakers, which isn't true at all. @@ -173,7 +175,7 @@ void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan) // We should play the samples from the wiimote at the native volume they came with, // because you can lower their volume from the wii settings, and because they are // already extremely low quality, so any additional quality loss isn't welcome. - speaker_pan = std::clamp(speaker_pan, -1.f, 1.f); + float speaker_pan = std::clamp(float(m_speaker_pan_setting.GetValue()) / 100, -1.f, 1.f); const u32 l_volume = std::min(u32(std::min(1.f - speaker_pan, 1.f) * volume), 255u); const u32 r_volume = std::min(u32(std::min(1.f + speaker_pan, 1.f) * volume), 255u); @@ -212,14 +214,19 @@ void SpeakerLogic::Reset() { reg_data = {}; + ResetDecoder(); +} + +//To call on re-connect for sure +void SpeakerLogic::ResetDecoder() +{ // Yamaha ADPCM state initialize - adpcm_state.predictor = 0.0; //To make sure it's called (what about when the speaker is muted or disabled) + adpcm_state.predictor = 0.0; adpcm_state.step = 127.0; } void SpeakerLogic::DoState(PointerWrap& p) { - //To increase state type num p.Do(adpcm_state); p.Do(reg_data); } @@ -239,7 +246,7 @@ int SpeakerLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) if (0x00 == addr) { - SpeakerData(data_in, count, m_speaker_pan_setting.GetValue() / 100); + SpeakerData(data_in, count); return count; } else diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.h b/Source/Core/Core/HW/WiimoteEmu/Speaker.h index 0fb576a379e6..d1ce2c428ae7 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.h +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.h @@ -29,11 +29,11 @@ class SpeakerLogic : public I2CSlave static constexpr u8 SPEAKER_DATA_OFFSET = 0x00; void Reset(); + void ResetDecoder(); void DoState(PointerWrap& p); private: - // Pan is -1.0 (L) to +1.0 (R) - void SpeakerData(const u8* data, int length, float speaker_pan); + void SpeakerData(const u8* data, int length); // TODO: enum class static const u8 DATA_FORMAT_ADPCM = 0x00; @@ -47,7 +47,7 @@ class SpeakerLogic : public I2CSlave u8 speaker_data; u8 unk_1; u8 format; - // seems to always play at 6khz no matter what this is set to? + // Seems to always play at 6kHz no matter what this is set to? // or maybe it only applies to pcm input // Little-endian: u16 sample_rate; diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 0474a30098aa..47fd995c4026 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -113,12 +113,15 @@ void AudioPane::CreateWidgets() // Unfortunately this creates an empty space if added to the widget. We should find a better way m_use_os_sample_rate->setHidden(true); + //To specify that latency increases by at least half the block size. Align latencies for better results m_dolby_pro_logic->setToolTip( tr("Enables Dolby Pro Logic II emulation using 5.1 surround.\nCertain backends and DPS " "emulation engines only.\nAutomatically disabled if not supported by your audio device." "\nYou need to enable surround from the game settings in GC games or in the menu settings " "on Wii." - "\nThe console will still output 2.0, but the encoder will extract information for 5.1." + "\nThe emulation will still output 2.0, but the encoder will extract information for 5.1." + "\nIf you align your backend latency to the DPLII block size," + "\nthe added latency will be half the DPLII block size." "\nIf unsure, leave off.")); auto* dolby_quality_layout = new QHBoxLayout; @@ -127,18 +130,18 @@ void AudioPane::CreateWidgets() m_dolby_quality_slider = new QSlider(Qt::Horizontal); m_dolby_quality_slider->setMinimum(0); - m_dolby_quality_slider->setMaximum(3); + m_dolby_quality_slider->setMaximum(2); m_dolby_quality_slider->setPageStep(1); m_dolby_quality_slider->setTickPosition(QSlider::TicksBelow); m_dolby_quality_slider->setToolTip( tr("Quality of the DPLII decoder. Also increases audio latency")); m_dolby_quality_slider->setTracking(true); - m_dolby_quality_low_label = new QLabel(GetDPL2QualityLabel(AudioCommon::DPL2Quality::Lowest)); + m_dolby_quality_low_label = new QLabel(GetDPL2QualityLabel(AudioCommon::DPL2Quality::Normal)); m_dolby_quality_highest_label = - new QLabel(GetDPL2QualityLabel(AudioCommon::DPL2Quality::Highest)); + new QLabel(GetDPL2QualityLabel(AudioCommon::DPL2Quality::Extreme)); m_dolby_quality_latency_label = - new QLabel(GetDPL2ApproximateLatencyLabel(AudioCommon::DPL2Quality::Highest)); + new QLabel(GetDPL2ApproximateLatencyLabel(AudioCommon::DPL2Quality::Extreme)); dolby_quality_layout->addWidget(m_dolby_quality_low_label); dolby_quality_layout->addWidget(m_dolby_quality_slider); @@ -677,15 +680,13 @@ QString AudioPane::GetDPL2QualityLabel(AudioCommon::DPL2Quality value) const { switch (value) { - case AudioCommon::DPL2Quality::Lowest: - return tr("Lowest"); - case AudioCommon::DPL2Quality::Low: - return tr("Low"); - case AudioCommon::DPL2Quality::Highest: - return tr("Highest"); case AudioCommon::DPL2Quality::High: - default: return tr("High"); + case AudioCommon::DPL2Quality::Extreme: + return tr("Extreme"); + case AudioCommon::DPL2Quality::Normal: + default: + return tr("Normal"); } } @@ -693,15 +694,13 @@ QString AudioPane::GetDPL2ApproximateLatencyLabel(AudioCommon::DPL2Quality value { switch (value) { - case AudioCommon::DPL2Quality::Lowest: - return tr("Latency: ~10ms"); - case AudioCommon::DPL2Quality::Low: - return tr("Latency: ~20ms"); - case AudioCommon::DPL2Quality::Highest: - return tr("Latency: ~80ms"); case AudioCommon::DPL2Quality::High: + return tr("Block Size: 20ms"); + case AudioCommon::DPL2Quality::Extreme: + return tr("Block Size: 40ms"); + case AudioCommon::DPL2Quality::Normal: default: - return tr("Latency: ~40ms"); + return tr("Block Size: 10ms"); } } From fc9aec7412783a5131c908becaabb59ee09ba906 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sat, 5 Dec 2020 15:19:48 +0200 Subject: [PATCH 21/56] Added ability to make input settings depend on each other Add some more tooltips to input mapping widgets. Add edit condition to numeric settings (e.g. a setting that doesn't make sense unless another one is enabled). Fix group disabling leaving some widgets not grayed out. Increase mapping widget refresh rate from 30 to 60 (making them smoother) --- .../Config/Mapping/MappingNumeric.cpp | 3 + .../Config/Mapping/MappingWidget.cpp | 73 ++++++++++++++++++- .../DolphinQt/Config/Mapping/MappingWidget.h | 12 ++- .../DolphinQt/Config/Mapping/MappingWindow.h | 2 +- .../ControllerEmu/ControlGroup/ControlGroup.h | 8 +- .../ControllerEmu/Setting/NumericSetting.h | 14 +++- 6 files changed, 103 insertions(+), 9 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp index d84310ffa712..bfebe78341f6 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp @@ -75,6 +75,9 @@ void MappingDouble::Update() MappingBool::MappingBool(MappingWidget* parent, ControllerEmu::NumericSetting* setting) : QCheckBox(parent), m_setting(*setting) { + if (const auto ui_description = m_setting.GetUIDescription()) + setToolTip(tr(ui_description)); + connect(this, &QCheckBox::stateChanged, this, [this, parent](int value) { m_setting.SetValue(value != 0); ConfigChanged(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp index 230d66a29111..56fae20877a9 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp @@ -5,7 +5,6 @@ #include "DolphinQt/Config/Mapping/MappingWidget.h" #include -#include #include #include #include @@ -133,17 +132,24 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con for (auto& setting : group->numeric_settings) { QWidget* setting_widget = nullptr; + bool has_edit_condition = false; switch (setting->GetType()) { case ControllerEmu::SettingType::Double: setting_widget = new MappingDouble( this, static_cast*>(setting.get())); + has_edit_condition = + static_cast*>(setting.get())->GetEditCondition() != + nullptr; break; case ControllerEmu::SettingType::Bool: setting_widget = new MappingBool(this, static_cast*>(setting.get())); + has_edit_condition = + static_cast*>(setting.get())->GetEditCondition() != + nullptr; break; default: @@ -159,6 +165,16 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con hbox->addWidget(CreateSettingAdvancedMappingButton(*setting)); form_layout->addRow(tr(setting->GetUIName()), hbox); + + QFormLayout::TakeRowResult row; + row.fieldItem = form_layout->itemAt(form_layout->rowCount() - 1, QFormLayout::FieldRole); + row.labelItem = form_layout->itemAt(form_layout->rowCount() - 1, QFormLayout::LabelRole); + row.labelItem->widget()->setToolTip(tr(setting->GetUIDescription())); + + if (has_edit_condition) + { + m_edit_condition_numeric_settings.emplace_back(std::make_tuple(setting.get(), row, group)); + } } } @@ -175,7 +191,20 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con { QWidget* widget = form_layout->itemAt(i)->widget(); if (widget != nullptr && widget != group_enable_label && widget != group_enable_checkbox) + { widget->setEnabled(group->enabled); + } + // Layouts could be even more nested but they likely never will + else if (QLayout* nested_layout = form_layout->itemAt(i)->layout()) + { + for (int k = 0; k < nested_layout->count(); ++k) + { + if (widget = nested_layout->itemAt(k)->widget()) + { + widget->setEnabled(group->enabled); + } + } + } } }; enable_group_by_checkbox(); @@ -184,9 +213,50 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con [group_enable_checkbox, group] { group_enable_checkbox->setChecked(group->enabled); }); } + // This isn't called immediately when the edit condition changes + // but it's called at frequent regular intervals so it's fine. + // Connecting checkbox changes events would have been quite complicated + // given that groups have the ability to override the enabled value as well + connect(this, &MappingWidget::Update, this, &MappingWidget::RefreshSettingsEnabled); + return group_box; } +void MappingWidget::RefreshSettingsEnabled() +{ + for (auto& numeric_settings : m_edit_condition_numeric_settings) + { + bool enabled = true; + const ControllerEmu::NumericSettingBase* setting = std::get<0>(numeric_settings); + switch (setting->GetType()) + { + case ControllerEmu::SettingType::Double: + enabled = static_cast*>(setting)->IsEnabled(); + break; + + case ControllerEmu::SettingType::Bool: + enabled = static_cast*>(setting)->IsEnabled(); + break; + } + enabled = enabled && std::get<2>(numeric_settings)->enabled; + + if (QWidget* widget = std::get<1>(numeric_settings).labelItem->widget()) + { + widget->setEnabled(enabled); + } + if (QLayout* layout = std::get<1>(numeric_settings).fieldItem->layout()) + { + for (int i = 0; i < layout->count(); ++i) + { + if (QWidget* widget = layout->itemAt(i)->widget()) + { + widget->setEnabled(enabled); + } + } + } + } +} + ControllerEmu::EmulatedController* MappingWidget::GetController() const { return m_parent->GetController(); @@ -197,6 +267,7 @@ MappingWidget::CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingB { const auto button = new QPushButton(tr("...")); button->setFixedWidth(QFontMetrics(font()).boundingRect(button->text()).width() * 2); + button->setToolTip(tr("Advanced mapping")); button->connect(button, &QPushButton::clicked, [this, &setting]() { if (setting.IsSimpleValue()) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h index 851d2c12ce17..52caa70b6849 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h @@ -9,6 +9,7 @@ #include #include +#include constexpr int WIDGET_MAX_WIDTH = 112; @@ -28,7 +29,8 @@ class EmulatedController; class NumericSettingBase; } // namespace ControllerEmu -constexpr int INDICATOR_UPDATE_FREQ = 30; +// For smoother UI, this should be based on the current monitor refresh rate +constexpr int INDICATOR_UPDATE_FREQ = 60; class MappingWidget : public QWidget { @@ -51,10 +53,16 @@ class MappingWidget : public QWidget protected: int GetPort() const; + void RefreshSettingsEnabled(); + QGroupBox* CreateGroupBox(ControllerEmu::ControlGroup* group); QGroupBox* CreateGroupBox(const QString& name, ControllerEmu::ControlGroup* group); QPushButton* CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingBase& setting); private: - MappingWindow* m_parent; + MappingWindow* const m_parent; + + std::vector> + m_edit_condition_numeric_settings; }; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index aade3b23df22..6848002e17ac 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -57,7 +57,7 @@ class MappingWindow final : public QDialog signals: // Emitted when config has changed so widgets can update to reflect the change. void ConfigChanged(); - // Emitted at 30hz for real-time indicators to be updated. + // Emitted at INDICATOR_UPDATE_FREQ(Hz) for real-time indicators to be updated. void Update(); void Save(); diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h index 390b7f898608..75adbb4d5ee7 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h @@ -77,10 +77,11 @@ class ControlGroup template void AddSetting(SettingValue* value, const NumericSettingDetails& details, std::common_type_t default_value_, std::common_type_t min_value = {}, - std::common_type_t max_value = T(100)) + std::common_type_t max_value = T(100), + NumericSetting* edit_condition = nullptr) { - numeric_settings.emplace_back( - std::make_unique>(value, details, default_value_, min_value, max_value)); + numeric_settings.emplace_back(std::make_unique>( + value, details, default_value_, min_value, max_value, edit_condition)); } void AddVirtualNotchSetting(SettingValue* value, double max_virtual_notch_deg); @@ -100,6 +101,7 @@ class ControlGroup bool enabled = true; std::vector> controls; + // Settings can point to eatch other so never remove them individually std::vector> numeric_settings; }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h b/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h index 25c4ab52c49e..c3390bc9b016 100644 --- a/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h +++ b/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h @@ -57,6 +57,9 @@ class NumericSettingBase virtual InputReference& GetInputReference() = 0; virtual const InputReference& GetInputReference() const = 0; + // Can be edited in UI? This setting should not used if is false + virtual bool IsEnabled() const = 0; + virtual bool IsSimpleValue() const = 0; // Convert a literal expression e.g. "7.0" to a regular value. (disables expression parsing) @@ -89,9 +92,10 @@ class NumericSetting final : public NumericSettingBase "NumericSetting is only implemented for int, double, and bool."); NumericSetting(SettingValue* value, const NumericSettingDetails& details, - ValueType default_value, ValueType min_value, ValueType max_value) + ValueType default_value, ValueType min_value, ValueType max_value, + NumericSetting* edit_condition = nullptr) : NumericSettingBase(details), m_value(*value), m_default_value(default_value), - m_min_value(min_value), m_max_value(max_value) + m_min_value(min_value), m_max_value(max_value), m_edit_condition(edit_condition) { m_value.SetValue(m_default_value); } @@ -125,6 +129,9 @@ class NumericSetting final : public NumericSettingBase } } + bool IsEnabled() const override { return !m_edit_condition || m_edit_condition->GetValue(); } + const NumericSetting* GetEditCondition() const { return m_edit_condition; } + bool IsSimpleValue() const override { return m_value.IsSimpleValue(); } void SimplifyIfPossible() override @@ -153,6 +160,9 @@ class NumericSetting final : public NumericSettingBase const ValueType m_default_value; const ValueType m_min_value; const ValueType m_max_value; + + // Assuming settings are never destroyed if not all together + const NumericSetting* m_edit_condition; }; template From 0d8d71657923ee1e671d09f6a6a377b6b3f39ad4 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sat, 5 Dec 2020 15:21:20 +0200 Subject: [PATCH 22/56] Renaming audio config variables global setting iLatency to iAudioBackendLatency. ini setting AudioLatency to AudioBackendLatency. Both were confusing, especially with all the new implemented audio (and latency) settings. --- Source/Core/AudioCommon/AudioCommon.cpp | 4 ++-- Source/Core/AudioCommon/AudioCommon.h | 3 ++- Source/Core/Core/Config/MainSettings.cpp | 3 +-- Source/Core/Core/ConfigManager.cpp | 6 +++--- Source/Core/Core/ConfigManager.h | 2 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 10 +++++----- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 303d931fe3b5..fd32a2aa2117 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -222,8 +222,8 @@ unsigned long GetMaxSupportedLatency() unsigned long GetUserTargetLatency() { - // iLatency is not unsigned - unsigned long target_latency = std::max(SConfig::GetInstance().iLatency, 0); + // iAudioBackendLatency is not unsigned + unsigned long target_latency = std::max(SConfig::GetInstance().iAudioBackendLatency, 0); return std::min(target_latency, AudioCommon::GetMaxSupportedLatency()); } diff --git a/Source/Core/AudioCommon/AudioCommon.h b/Source/Core/AudioCommon/AudioCommon.h index e23aeef3d231..b19ab3d474d6 100644 --- a/Source/Core/AudioCommon/AudioCommon.h +++ b/Source/Core/AudioCommon/AudioCommon.h @@ -39,7 +39,8 @@ unsigned long GetDefaultSampleRate(); // and DVD sample rate, but let's theorize the worse case (GC 48kHz mode: ~48043Hz). // Of course we shouldn't use anything above half of what this returns unsigned long GetMaxSupportedLatency(); -// Already clamped by GetMaxSupportedLatency(). Can return 0 +// Returns the user preferred backend latency. +// Can return 0 and is clamped by GetMaxSupportedLatency() unsigned long GetUserTargetLatency(); // Returns the OS mixer sample rate (based on the currently used audio device) unsigned long GetOSMixerSampleRate(); diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 3de0386d3531..7c255a635ea2 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -39,8 +39,7 @@ const Info MAIN_DPL2_DECODER{{System::Main, "Core", "DPL2Decoder"}, false} const Info MAIN_DPL2_QUALITY{{System::Main, "Core", "DPL2Quality"}, AudioCommon::GetDefaultDPL2Quality()}; const Info MAIN_DPL2_BASS_REDIRECTION{{System::Main, "Core", "DPL2BassRedirection"}, false}; -// TODO: rename ini "AudioBackendLatency", it's confusing -const Info MAIN_AUDIO_BACKEND_LATENCY{{System::Main, "Core", "AudioLatency"}, 20}; +const Info MAIN_AUDIO_BACKEND_LATENCY{{System::Main, "Core", "AudioBackendLatency"}, 20}; const Info MAIN_AUDIO_MIXER_MIN_LATENCY{{System::Main, "Core", "AudioMixerMinLatency"}, 0}; const Info MAIN_AUDIO_MIXER_MAX_LATENCY{{System::Main, "Core", "AudioMixerMaxLatency"}, 40}; const Info MAIN_AUDIO_STRETCH{{System::Main, "Core", "AudioStretch"}, false}; diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 5378f46e53f8..0836d0387623 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -220,7 +220,7 @@ void SConfig::SaveCoreSettings(IniFile& ini) core->Set("SelectedLanguage", SelectedLanguage); core->Set("OverrideRegionSettings", bOverrideRegionSettings); core->Set("DPL2Decoder", bDPL2Decoder); - core->Set("AudioLatency", iLatency); + core->Set("AudioBackendLatency", iAudioBackendLatency); core->Set("UseOSMixerSampleRate", &bUseOSMixerSampleRate); core->Set("AudioStretch", m_audio_stretch); core->Set("AudioEmuSpeedTolerance", &m_audio_emu_speed_tolerance); @@ -481,7 +481,7 @@ void SConfig::LoadCoreSettings(IniFile& ini) DiscIO::ToGameCubeLanguage(Config::GetDefaultLanguage())); core->Get("OverrideRegionSettings", &bOverrideRegionSettings, false); core->Get("DPL2Decoder", &bDPL2Decoder, false); - core->Get("AudioLatency", &iLatency, 20); + core->Get("AudioBackendLatency", &iAudioBackendLatency, 20); core->Get("UseOSMixerSampleRate", &bUseOSMixerSampleRate, false); core->Get("AudioStretch", &m_audio_stretch, false); core->Get("AudioEmuSpeedTolerance", &m_audio_emu_speed_tolerance, 20); @@ -767,7 +767,7 @@ void SConfig::LoadDefaults() bOverrideRegionSettings = false; bWii = false; bDPL2Decoder = false; - iLatency = 20; + iAudioBackendLatency = 20; m_audio_stretch = false; m_audio_emu_speed_tolerance = 20; bUsePanicHandlers = true; diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 1ffbbe3804d6..0fcbabcb9059 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -126,7 +126,7 @@ struct SConfig bool bDPL2Decoder = false; // Don't access directly, use AudioInterface::GetUserTargetLatency() - int iLatency = 20; + int iAudioBackendLatency = 20; bool bUseOSMixerSampleRate = false; bool m_audio_stretch = false; int m_audio_emu_speed_tolerance = 20; diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 47fd995c4026..01849103bc2d 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -113,7 +113,6 @@ void AudioPane::CreateWidgets() // Unfortunately this creates an empty space if added to the widget. We should find a better way m_use_os_sample_rate->setHidden(true); - //To specify that latency increases by at least half the block size. Align latencies for better results m_dolby_pro_logic->setToolTip( tr("Enables Dolby Pro Logic II emulation using 5.1 surround.\nCertain backends and DPS " "emulation engines only.\nAutomatically disabled if not supported by your audio device." @@ -183,7 +182,7 @@ void AudioPane::CreateWidgets() QSize min_size = m_emu_speed_tolerance_indicator->minimumSize(); min_size.setWidth(45); // Avoid the slider in line with this resizing because of text length changes. - // it would be best to do it dynamically based on the translation + // It would be best to do it dynamically based on the translation m_emu_speed_tolerance_indicator->setMinimumSize(min_size); m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); mixer_box->setLayout(mixer_layout); @@ -301,7 +300,7 @@ void AudioPane::LoadSettings() if (m_latency_control_supported) { m_ignore_save_settings = true; - m_latency_spin->setValue(SConfig::GetInstance().iLatency); + m_latency_spin->setValue(SConfig::GetInstance().iAudioBackendLatency); m_ignore_save_settings = false; } @@ -415,9 +414,10 @@ void AudioPane::SaveSettings() } // Latency - if (m_latency_control_supported && SConfig::GetInstance().iLatency != m_latency_spin->value()) + if (m_latency_control_supported && + SConfig::GetInstance().iAudioBackendLatency != m_latency_spin->value()) { - SConfig::GetInstance().iLatency = m_latency_spin->value(); + SConfig::GetInstance().iAudioBackendLatency = m_latency_spin->value(); backend_setting_changed = true; } From f6430d51855d008c2e036451ac908f148dad7870 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sat, 5 Dec 2020 15:21:49 +0200 Subject: [PATCH 23/56] Fix some comments --- Source/Core/AudioCommon/Mixer.cpp | 4 ++-- Source/Core/AudioCommon/SurroundDecoder.cpp | 3 +-- Source/Core/AudioCommon/WASAPIStream.cpp | 8 ++++---- Source/Core/Core/HW/DVD/DVDInterface.cpp | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 0644226f1a5b..1a042f5170e1 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -637,13 +637,13 @@ u32 Mixer::MixSurround(float* samples, u32 num_samples) // The simplest solution sounds like finding a way of noticing that and setting // the max number of samples ever required as the minimum latency of the surround // so that we'd never run out of samples (when we are asked for more than usual, - // we'd ignore the last samples away). Unfortunately there is no simple solution, + // we'd ignore the last samples away). Unfortunately there is no easy solution, // being constrained to blocks and unable to stretch. // Our latency might have increased m_scratch_buffer.reserve(num_samples * NC); - // TODO: we could have special path here which mixes samples directly in float, given that the + // TODO: we could have a special path here which mixes samples directly in float, given that the // cubic interpolation spits out floats. // Time stretching can be applied before decoding 5.1, it should be fine theoretically. diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index b207c23999ba..29bab9bf03e9 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -15,7 +15,6 @@ namespace AudioCommon { -//To align backend latency and this one (and explain it in UI) // Quality (higher quality also means more latency). Needs to be a multiple of 2 static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) { @@ -35,7 +34,7 @@ static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) } u32 frame_block = std::round(sample_rate * frame_block_time / 1000.0); frame_block = (frame_block / 2) * 2; - // Assert because FreeSurround would crash anyway, this can only be triggered by code changes + // Assert because FreeSurround would crash anyway, this can't be triggered as of now assert(frame_block > 1); return frame_block; } diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index a0a64f6d7d21..65e15e5a241d 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -601,8 +601,8 @@ bool WASAPIStream::SetRunning(bool running) // We can't move all of this between Init() and the destructor as if we paused the game, WASAPI // would keep exclusive access of the device. I've also tried to move all of this in SoundLoop() // (m_thread), but m_need_data_event failed to trigger. - // Let's just hope it works fine without it, at worse it will cause a small leak, but this - // has always been the case. + // Let's just hope it works fine without it, at worse it will cause a small memory leak, but + // this has always been the case. // We don't need IAudioClient3, it doesn't add anything for exclusive mode. result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast(&m_audio_client)); @@ -659,8 +659,8 @@ bool WASAPIStream::SetRunning(bool running) result = m_audio_client->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, stream_flags, device_period, device_period, &m_format.Format, nullptr); } - // TODO: just like we fallback here above if we don't support surround, we should add support - // for more devices (e.g. bitrate, channels, format) by falling back as well + // TODO: just like we do above, fallback here if we don't support surround. We should also + // add support for more devices (e.g. bitrate, channels, format) by falling back as well if (result == AUDCLNT_E_UNSUPPORTED_FORMAT) { diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index b9f844c6afa6..6549dba2428d 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -249,7 +249,7 @@ static size_t ProcessDTKSamples(std::vector* temp_pcm, const std::vector Date: Sun, 6 Dec 2020 15:59:08 +0200 Subject: [PATCH 24/56] Restored DPLII highest quality Haven't proven it myself but @LAGonauta said he could clearly hear the difference between 40 and 80ms despite FreeSurround saying to not go over 20ms of block size. --- Source/Core/AudioCommon/Enums.h | 7 ++++--- Source/Core/AudioCommon/SurroundDecoder.cpp | 17 +++++++++++++---- Source/Core/AudioCommon/SurroundDecoder.h | 4 ++-- Source/Core/DolphinQt/Settings/AudioPane.cpp | 12 ++++++++---- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Source/Core/AudioCommon/Enums.h b/Source/Core/AudioCommon/Enums.h index 07b56eb74b88..72705eb88c90 100644 --- a/Source/Core/AudioCommon/Enums.h +++ b/Source/Core/AudioCommon/Enums.h @@ -8,8 +8,9 @@ namespace AudioCommon { enum class DPL2Quality { - Normal = 0, - High = 1, - Extreme = 2 + Low = 0, + Normal = 1, + High = 2, + Extreme = 3 }; } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 29bab9bf03e9..825596f7d83a 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -21,16 +21,19 @@ static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) u32 frame_block_time; // ms switch (quality) { + case DPL2Quality::Low: + frame_block_time = 10; + break; + // FreeSurround said to not go over 20ms, so this might be overkill already case DPL2Quality::High: - frame_block_time = 20; + frame_block_time = 40; break; - // FreeSurround said to not go over 20ms, so this is overkill already case DPL2Quality::Extreme: - frame_block_time = 40; + frame_block_time = 80; break; case DPL2Quality::Normal: default: - frame_block_time = 10; + frame_block_time = 20; } u32 frame_block = std::round(sample_rate * frame_block_time / 1000.0); frame_block = (frame_block / 2) * 2; @@ -58,6 +61,12 @@ void SurroundDecoder::InitAndSetSampleRate(u32 sample_rate) { m_frame_block_size = frame_block_size; m_sample_rate = sample_rate; + // If we passed in a block size that is a power of 2, decoding performance would be better, + // (quality would be the same), though we've decided to prioritize low latency over performance, + // so it's better to have a block size aligned (or being a multiple/dividend) of the backend + // latency. We could write some code and UI setting to align the backend latency to the DPLII + // one but it would be straight forward as some backends don't support custom latency. + // If this turned out to be too intensive on slower CPUs, we could reconsider it. m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate); // Increase m_decoded_fifo size if this becomes common occurrence diff --git a/Source/Core/AudioCommon/SurroundDecoder.h b/Source/Core/AudioCommon/SurroundDecoder.h index 7350222551ce..38a9d0b5d2ba 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.h +++ b/Source/Core/AudioCommon/SurroundDecoder.h @@ -28,8 +28,8 @@ class SurroundDecoder static constexpr u32 STEREO_CHANNELS = 2; static constexpr u32 SURROUND_CHANNELS = 6; - // Max supported samples rate is about 192kHz at highest block quality (~40ms) - static constexpr u32 MAX_BLOCKS_SIZE = 7680; + // Max supported samples rate is about 192kHz at highest block quality (~80ms) + static constexpr u32 MAX_BLOCKS_SIZE = 15360; static constexpr u32 MAX_BLOCKS_BUFFERED = 8; private: diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 01849103bc2d..33130356c063 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -129,7 +129,7 @@ void AudioPane::CreateWidgets() m_dolby_quality_slider = new QSlider(Qt::Horizontal); m_dolby_quality_slider->setMinimum(0); - m_dolby_quality_slider->setMaximum(2); + m_dolby_quality_slider->setMaximum(3); m_dolby_quality_slider->setPageStep(1); m_dolby_quality_slider->setTickPosition(QSlider::TicksBelow); m_dolby_quality_slider->setToolTip( @@ -680,6 +680,8 @@ QString AudioPane::GetDPL2QualityLabel(AudioCommon::DPL2Quality value) const { switch (value) { + case AudioCommon::DPL2Quality::Low: + return tr("Low"); case AudioCommon::DPL2Quality::High: return tr("High"); case AudioCommon::DPL2Quality::Extreme: @@ -694,13 +696,15 @@ QString AudioPane::GetDPL2ApproximateLatencyLabel(AudioCommon::DPL2Quality value { switch (value) { + case AudioCommon::DPL2Quality::Low: + return tr("Block Size: 10ms"); case AudioCommon::DPL2Quality::High: - return tr("Block Size: 20ms"); - case AudioCommon::DPL2Quality::Extreme: return tr("Block Size: 40ms"); + case AudioCommon::DPL2Quality::Extreme: + return tr("Block Size: 80ms"); case AudioCommon::DPL2Quality::Normal: default: - return tr("Block Size: 10ms"); + return tr("Block Size: 20ms"); } } From 8037645af8097f0d6c45c898ded7a0b348c91447 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 6 Dec 2020 16:00:57 +0200 Subject: [PATCH 25/56] Grammar error --- Source/Core/AudioCommon/SurroundDecoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 825596f7d83a..5a480adcb184 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -65,7 +65,7 @@ void SurroundDecoder::InitAndSetSampleRate(u32 sample_rate) // (quality would be the same), though we've decided to prioritize low latency over performance, // so it's better to have a block size aligned (or being a multiple/dividend) of the backend // latency. We could write some code and UI setting to align the backend latency to the DPLII - // one but it would be straight forward as some backends don't support custom latency. + // one but it wouldn't be straight forward as some backends don't support custom latency. // If this turned out to be too intensive on slower CPUs, we could reconsider it. m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate); From 564a405480d026f116194e6421f0a56ff76694f5 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 6 Dec 2020 18:19:34 +0200 Subject: [PATCH 26/56] Audio Pane: UI improvements for DPLII -Added a space between values and "Hz". -Also slightly refactored a few other things in the audio pane, mostly the alignment. --- Source/Core/AudioCommon/WASAPIStream.cpp | 6 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 150 ++++++++----------- Source/Core/DolphinQt/Settings/AudioPane.h | 7 +- 3 files changed, 70 insertions(+), 93 deletions(-) diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 65e15e5a241d..8ee67295b56c 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -69,7 +69,7 @@ class AutoPrintMsg std::string message = "WASAPI failed to start, the current audio device likely does not support 2.0 16-bit " + std::to_string(m_WASAPI_stream->GetMixer()->GetSampleRate()) + - "Hz PCM audio." + " Hz PCM audio." "\nWASAPI exclusive mode (event driven) won't work"; ERROR_LOG(AUDIO, message.c_str()); @@ -641,7 +641,7 @@ bool WASAPIStream::SetRunning(bool running) if (m_surround && result == AUDCLNT_E_UNSUPPORTED_FORMAT) { WARN_LOG(AUDIO, - "WASAPI: Your current audio device doesn't support 5.1 16-bit %uHz" + "WASAPI: Your current audio device doesn't support 5.1 16-bit %u Hz" " PCM audio. Will fallback to 2.0 16-bit", GetMixer()->GetSampleRate()); @@ -665,7 +665,7 @@ bool WASAPIStream::SetRunning(bool running) if (result == AUDCLNT_E_UNSUPPORTED_FORMAT) { WARN_LOG(AUDIO, - "WASAPI: Your current audio device doesn't support 2.0 16-bit %uHz" + "WASAPI: Your current audio device doesn't support 2.0 16-bit %u Hz" " PCM audio. Exclusive mode (event driven) won't work", GetMixer()->GetSampleRate()); diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 33130356c063..472f5d311d1a 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -53,6 +53,7 @@ void AudioPane::CreateWidgets() auto* dsp_box = new QGroupBox(tr("DSP Emulation Engine")); auto* dsp_layout = new QVBoxLayout; + dsp_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); dsp_box->setLayout(dsp_layout); m_dsp_hle = new QRadioButton(tr("DSP HLE (fast)")); m_dsp_lle = new QRadioButton(tr("DSP LLE Recompiler")); @@ -84,10 +85,22 @@ void AudioPane::CreateWidgets() auto* backend_box = new QGroupBox(tr("Backend Settings")); auto* backend_layout = new QFormLayout; + + backend_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); backend_box->setLayout(backend_layout); m_backend_label = new QLabel(tr("Audio Backend:")); m_backend_combo = new QComboBox(); - m_dolby_pro_logic = new QCheckBox(tr("Dolby Pro Logic II Decoder")); + m_dolby_pro_logic = new QCheckBox(tr("Dolby Pro Logic II (5.1)")); + + m_dolby_pro_logic->setToolTip( + tr("Enables Dolby Pro Logic II emulation using 5.1 surround.\nCertain backends and DPS " + "emulation engines only.\nAutomatically disabled if not supported by your audio device." + "\nYou need to enable surround from the game settings in GC games or in the menu settings " + "on Wii." + "\nThe emulation will still output 2.0, but the encoder will extract information for 5.1." + "\nIf you align your backend latency to the DPLII block size," + "\nthe added latency will be half the DPLII block size." + "\nIf unsure, leave off.")); if (m_latency_control_supported) { @@ -103,49 +116,16 @@ void AudioPane::CreateWidgets() m_use_os_sample_rate = new QCheckBox(tr("Use OS Mixer sample rate")); m_use_os_sample_rate->setToolTip( - tr("Directly mixes at the current OS mixer sample rate as opposed to %1" - "Hz.\nThis will make resampling from 32kHz sources more accurate, possibly improving" + tr("Directly mixes at the current OS mixer sample rate as opposed to %1 " + "Hz.\nThis will make resampling from 32 kHz sources more accurate, possibly improving" " audio\n" "quality at the cost of performance. It won't follow changes to your OS setting." "\nIf unsure, leave off.") .arg(AudioCommon::GetDefaultSampleRate())); - // Unfortunately this creates an empty space if added to the widget. We should find a better way + // Unfortunately this creates an empty space when added to the layout. We should find a better way m_use_os_sample_rate->setHidden(true); - m_dolby_pro_logic->setToolTip( - tr("Enables Dolby Pro Logic II emulation using 5.1 surround.\nCertain backends and DPS " - "emulation engines only.\nAutomatically disabled if not supported by your audio device." - "\nYou need to enable surround from the game settings in GC games or in the menu settings " - "on Wii." - "\nThe emulation will still output 2.0, but the encoder will extract information for 5.1." - "\nIf you align your backend latency to the DPLII block size," - "\nthe added latency will be half the DPLII block size." - "\nIf unsure, leave off.")); - - auto* dolby_quality_layout = new QHBoxLayout; - - m_dolby_quality_label = new QLabel(tr("Decoding Quality:")); - - m_dolby_quality_slider = new QSlider(Qt::Horizontal); - m_dolby_quality_slider->setMinimum(0); - m_dolby_quality_slider->setMaximum(3); - m_dolby_quality_slider->setPageStep(1); - m_dolby_quality_slider->setTickPosition(QSlider::TicksBelow); - m_dolby_quality_slider->setToolTip( - tr("Quality of the DPLII decoder. Also increases audio latency")); - m_dolby_quality_slider->setTracking(true); - - m_dolby_quality_low_label = new QLabel(GetDPL2QualityLabel(AudioCommon::DPL2Quality::Normal)); - m_dolby_quality_highest_label = - new QLabel(GetDPL2QualityLabel(AudioCommon::DPL2Quality::Extreme)); - m_dolby_quality_latency_label = - new QLabel(GetDPL2ApproximateLatencyLabel(AudioCommon::DPL2Quality::Extreme)); - - dolby_quality_layout->addWidget(m_dolby_quality_low_label); - dolby_quality_layout->addWidget(m_dolby_quality_slider); - dolby_quality_layout->addWidget(m_dolby_quality_highest_label); - backend_layout->setFormAlignment(Qt::AlignLeft | Qt::AlignTop); backend_layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); backend_layout->addRow(m_backend_label, m_backend_combo); @@ -160,32 +140,31 @@ void AudioPane::CreateWidgets() m_wasapi_device_combo->setToolTip(tr("Some devices might not work with WASAPI Exclusive mode")); m_wasapi_device_sample_rate_combo->setToolTip( - tr("Output (and mix) sample rate.\nAnything above 48kHz will have very minimal " + tr("Output (and mix) sample rate.\nAnything above 48 kHz will have very minimal " "improvements to quality at the cost of performance.")); backend_layout->addRow(m_wasapi_device_label, m_wasapi_device_combo); backend_layout->addRow(m_wasapi_device_sample_rate_label, m_wasapi_device_sample_rate_combo); #endif - backend_layout->addWidget(m_use_os_sample_rate); + backend_layout->addRow(m_use_os_sample_rate); backend_layout->addRow(m_dolby_pro_logic); - backend_layout->addRow(m_dolby_quality_label); - backend_layout->addRow(dolby_quality_layout); - backend_layout->addRow(m_dolby_quality_latency_label); auto* mixer_box = new QGroupBox(tr("Mixer Settings")); auto* mixer_layout = new QGridLayout; + + mixer_layout->setAlignment(Qt::AlignLeft | Qt::AlignTop); + mixer_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + mixer_box->setLayout(mixer_layout); m_stretching_enable = new QCheckBox(tr("Audio Stretching")); m_emu_speed_tolerance_slider = new QSlider(Qt::Horizontal); m_emu_speed_tolerance_indicator = new QLabel(); - m_emu_speed_tolerance_indicator->setAlignment(Qt::AlignRight); QSize min_size = m_emu_speed_tolerance_indicator->minimumSize(); min_size.setWidth(45); // Avoid the slider in line with this resizing because of text length changes. // It would be best to do it dynamically based on the translation m_emu_speed_tolerance_indicator->setMinimumSize(min_size); m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); - mixer_box->setLayout(mixer_layout); //To review: maybe have a set of 4 or 5 options to keep it simpler m_stretching_enable->setToolTip(tr( @@ -200,21 +179,42 @@ void AudioPane::CreateWidgets() "too high (>40), sound will play old samples backwards when we slow down or stutter." "\nif set too low (<10), sound might lose quality if you have frequent small stutters." "\nSet 0 to have it on all the times. Slide all the way left to disable.")); - + + m_dolby_quality_label = new QLabel(tr("DPLII Decoding Quality:")); + + m_dolby_quality_slider = new QSlider(Qt::Horizontal); + m_dolby_quality_slider->setMinimum(0); + m_dolby_quality_slider->setMaximum(3); + m_dolby_quality_slider->setPageStep(1); + m_dolby_quality_slider->setTickPosition(QSlider::TicksBelow); + m_dolby_quality_slider->setToolTip( + tr("Quality of the DPLII decoder. Also increases audio latency")); + m_dolby_quality_slider->setTracking(true); + + m_dolby_quality_latency_label = new QLabel(); + + min_size = m_dolby_quality_latency_label->minimumSize(); + min_size.setWidth(143); + // Avoid the slider in line with this resizing because of text length changes. + // It would be best to do it dynamically based on the translation + m_dolby_quality_latency_label->setMinimumSize(min_size); + mixer_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); mixer_layout->addWidget(m_emu_speed_tolerance_label, 1, 0); mixer_layout->addWidget(m_emu_speed_tolerance_slider, 1, 1); mixer_layout->addWidget(m_emu_speed_tolerance_indicator, 1, 2); + mixer_layout->addWidget(m_dolby_quality_label, 2, 0); + mixer_layout->addWidget(m_dolby_quality_slider, 2, 1); + mixer_layout->addWidget(m_dolby_quality_latency_label, 2, 2); - m_main_layout = new QGridLayout; - + QGridLayout* m_main_layout = new QGridLayout; m_main_layout->setRowStretch(0, 0); + m_main_layout->setAlignment(Qt::AlignTop); - dsp_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - m_main_layout->addWidget(dsp_box, 0, 0); + m_main_layout->addWidget(dsp_box, 0, 0, Qt::AlignTop); m_main_layout->addWidget(volume_box, 0, 1, -1, 1); - m_main_layout->addWidget(backend_box, 1, 0); - m_main_layout->addWidget(mixer_box, 2, 0); + m_main_layout->addWidget(backend_box, 1, 0, Qt::AlignTop); + m_main_layout->addWidget(mixer_box, 2, 0, Qt::AlignTop); setLayout(m_main_layout); } @@ -290,7 +290,7 @@ void AudioPane::LoadSettings() m_dolby_quality_slider->setValue(int(Config::Get(Config::MAIN_DPL2_QUALITY))); m_ignore_save_settings = false; m_dolby_quality_latency_label->setText( - GetDPL2ApproximateLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); + GetDPL2QualityAndLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); if (AudioCommon::SupportsDPL2Decoder(current) && !m_dsp_hle->isChecked()) { EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); @@ -405,7 +405,7 @@ void AudioPane::SaveSettings() Config::SetBase(Config::MAIN_DPL2_QUALITY, static_cast(m_dolby_quality_slider->value())); m_dolby_quality_latency_label->setText( - GetDPL2ApproximateLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); + GetDPL2QualityAndLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); backend_setting_changed = true; // Not a mistake } if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) @@ -467,7 +467,7 @@ void AudioPane::SaveSettings() if (m_wasapi_device_sample_rate_combo->currentIndex() > 0 && canSelectDeviceSampleRate) { QString qs = m_wasapi_device_sample_rate_combo->currentText(); - qs.chop(2); // Remove "Hz" from the end + qs.chop(3); // Remove " Hz" from the end deviceSampleRate = qs.toStdString(); } @@ -554,12 +554,12 @@ void AudioPane::OnWASAPIDeviceChanged() m_wasapi_device_sample_rate_label->setEnabled(true); m_wasapi_device_sample_rate_combo->addItem( - tr("Default Dolphin Sample Rate (%1Hz)").arg(AudioCommon::GetDefaultSampleRate())); + tr("Default Dolphin Sample Rate (%1 Hz)").arg(AudioCommon::GetDefaultSampleRate())); for (const auto deviceSampleRate : WASAPIStream::GetSelectedDeviceSampleRates()) { m_wasapi_device_sample_rate_combo->addItem( - QString::number(deviceSampleRate).append(tr("Hz"))); + QString::number(deviceSampleRate).append(tr(" Hz"))); } } else @@ -569,17 +569,17 @@ void AudioPane::OnWASAPIDeviceChanged() if (m_running) { std::string sample_rate_text; - sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate + "Hz"; - if (sample_rate_text == "0Hz") + sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate + " Hz"; + if (sample_rate_text == "0 Hz") { - sample_rate_text = std::to_string(AudioCommon::GetDefaultSampleRate()) + "Hz"; + sample_rate_text = std::to_string(AudioCommon::GetDefaultSampleRate()) + " Hz"; } m_wasapi_device_sample_rate_combo->addItem(tr(sample_rate_text.c_str())); } else { m_wasapi_device_sample_rate_combo->addItem( - tr("Select a Device (%1Hz)").arg(AudioCommon::GetDefaultSampleRate())); + tr("Select a Device (%1 Hz)").arg(AudioCommon::GetDefaultSampleRate())); } } @@ -596,7 +596,7 @@ void AudioPane::LoadWASAPIDeviceSampleRate() } else { - std::string sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate + "Hz"; + std::string sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate + " Hz"; m_wasapi_device_sample_rate_combo->setCurrentText(QString::fromStdString(sample_rate_text)); // Saved sample rate not found, reset it if (m_wasapi_device_combo->currentIndex() < 1) @@ -676,35 +676,19 @@ void AudioPane::CheckNeedForLatencyControl() std::any_of(backends.cbegin(), backends.cend(), AudioCommon::SupportsLatencyControl); } -QString AudioPane::GetDPL2QualityLabel(AudioCommon::DPL2Quality value) const -{ - switch (value) - { - case AudioCommon::DPL2Quality::Low: - return tr("Low"); - case AudioCommon::DPL2Quality::High: - return tr("High"); - case AudioCommon::DPL2Quality::Extreme: - return tr("Extreme"); - case AudioCommon::DPL2Quality::Normal: - default: - return tr("Normal"); - } -} - -QString AudioPane::GetDPL2ApproximateLatencyLabel(AudioCommon::DPL2Quality value) const +QString AudioPane::GetDPL2QualityAndLatencyLabel(AudioCommon::DPL2Quality value) const { switch (value) { case AudioCommon::DPL2Quality::Low: - return tr("Block Size: 10ms"); + return tr("Low (Block Size: %1 ms)").arg(10); case AudioCommon::DPL2Quality::High: - return tr("Block Size: 40ms"); + return tr("High (Block Size: %1 ms)").arg(40); case AudioCommon::DPL2Quality::Extreme: - return tr("Block Size: 80ms"); + return tr("Extreme (Block Size: %1 ms)").arg(80); case AudioCommon::DPL2Quality::Normal: default: - return tr("Block Size: 20ms"); + return tr("Normal (Block Size: %1 ms)").arg(20); } } @@ -712,7 +696,5 @@ void AudioPane::EnableDolbyQualityWidgets(bool enabled) const { m_dolby_quality_label->setEnabled(enabled); m_dolby_quality_slider->setEnabled(enabled); - m_dolby_quality_low_label->setEnabled(enabled); - m_dolby_quality_highest_label->setEnabled(enabled); m_dolby_quality_latency_label->setEnabled(enabled); } diff --git a/Source/Core/DolphinQt/Settings/AudioPane.h b/Source/Core/DolphinQt/Settings/AudioPane.h index fcafcbcc1157..824595af1e02 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.h +++ b/Source/Core/DolphinQt/Settings/AudioPane.h @@ -45,15 +45,12 @@ class AudioPane final : public QWidget void CheckNeedForLatencyControl(); bool m_latency_control_supported; - QString GetDPL2QualityLabel(AudioCommon::DPL2Quality value) const; - QString GetDPL2ApproximateLatencyLabel(AudioCommon::DPL2Quality value) const; + QString GetDPL2QualityAndLatencyLabel(AudioCommon::DPL2Quality value) const; void EnableDolbyQualityWidgets(bool enabled) const; bool m_running; bool m_ignore_save_settings; - QGridLayout* m_main_layout; - // DSP Engine QRadioButton* m_dsp_hle; QRadioButton* m_dsp_lle; @@ -69,8 +66,6 @@ class AudioPane final : public QWidget QCheckBox* m_dolby_pro_logic; QLabel* m_dolby_quality_label; QSlider* m_dolby_quality_slider; - QLabel* m_dolby_quality_low_label; - QLabel* m_dolby_quality_highest_label; QLabel* m_dolby_quality_latency_label; QLabel* m_latency_label; QSpinBox* m_latency_spin; From c1b2004eaf3f04501e9c49d14a568ca5d4dfb392 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 6 Dec 2020 18:24:55 +0200 Subject: [PATCH 27/56] Add audio latency UI suffix (ms) --- Source/Core/DolphinQt/Settings/AudioPane.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 472f5d311d1a..402c755ba13e 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -108,6 +108,7 @@ void AudioPane::CreateWidgets() m_latency_spin = new QSpinBox(); m_latency_spin->setMinimum(0); m_latency_spin->setMaximum(std::min((int)AudioCommon::GetMaxSupportedLatency(), 200)); + m_latency_spin->setSuffix(tr(" ms")); m_latency_spin->setToolTip( tr("Target latency (in ms). Higher values may reduce audio" " crackling.\nCertain backends only. Values above 20ms are not suggested.")); From 5096339bd1d24570415ae6ba94a30ef0e854d187 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 6 Dec 2020 21:16:53 +0200 Subject: [PATCH 28/56] Small Audio Pane code cleanup --- Source/Core/DolphinQt/Settings/AudioPane.cpp | 32 ++++++++++++-------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 402c755ba13e..0cd15532d1ec 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -31,6 +31,10 @@ #include "DolphinQt/Config/SettingsWindow.h" #include "DolphinQt/Settings.h" +#ifdef _WIN32 +#define WASAPI_DEFAULT_DEVICE_NAME "default" +#endif + AudioPane::AudioPane() { m_running = false; @@ -325,7 +329,7 @@ void AudioPane::LoadSettings() #ifdef _WIN32 m_ignore_save_settings = true; - if (SConfig::GetInstance().sWASAPIDevice == "default") + if (SConfig::GetInstance().sWASAPIDevice == WASAPI_DEFAULT_DEVICE_NAME) { m_wasapi_device_combo->setCurrentIndex(0); } @@ -336,7 +340,7 @@ void AudioPane::LoadSettings() // Saved device not found, reset it if (m_wasapi_device_combo->currentIndex() < 1) { - SConfig::GetInstance().sWASAPIDevice = "default"; + SConfig::GetInstance().sWASAPIDevice = WASAPI_DEFAULT_DEVICE_NAME; } } LoadWASAPIDeviceSampleRate(); @@ -443,7 +447,7 @@ void AudioPane::SaveSettings() #ifdef _WIN32 // If left at default, Dolphin will automatically // pick a device and sample rate - std::string device = "default"; + std::string device = WASAPI_DEFAULT_DEVICE_NAME; if (m_wasapi_device_combo->currentIndex() > 0) device = m_wasapi_device_combo->currentText().toStdString(); @@ -464,7 +468,8 @@ void AudioPane::SaveSettings() std::string deviceSampleRate = "0"; - bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default"; + bool canSelectDeviceSampleRate = + SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME; if (m_wasapi_device_sample_rate_combo->currentIndex() > 0 && canSelectDeviceSampleRate) { QString qs = m_wasapi_device_sample_rate_combo->currentText(); @@ -548,7 +553,8 @@ void AudioPane::OnWASAPIDeviceChanged() // Don't allow users to select a sample rate for the default device, // even though that would be possible, the default device can change // at any time so it wouldn't make sense - bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default"; + bool canSelectDeviceSampleRate = + SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME; if (canSelectDeviceSampleRate) { m_wasapi_device_sample_rate_combo->setEnabled(true); @@ -569,13 +575,13 @@ void AudioPane::OnWASAPIDeviceChanged() m_wasapi_device_sample_rate_label->setEnabled(false); if (m_running) { - std::string sample_rate_text; - sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate + " Hz"; - if (sample_rate_text == "0 Hz") + std::string sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate; + if (sample_rate_text == "0") { - sample_rate_text = std::to_string(AudioCommon::GetDefaultSampleRate()) + " Hz"; + sample_rate_text = std::to_string(AudioCommon::GetDefaultSampleRate()); } - m_wasapi_device_sample_rate_combo->addItem(tr(sample_rate_text.c_str())); + m_wasapi_device_sample_rate_combo->addItem( + tr("%1 Hz").arg(QString::fromStdString(sample_rate_text))); } else { @@ -589,7 +595,8 @@ void AudioPane::OnWASAPIDeviceChanged() void AudioPane::LoadWASAPIDeviceSampleRate() { - bool canSelectDeviceSampleRate = SConfig::GetInstance().sWASAPIDevice != "default"; + bool canSelectDeviceSampleRate = + SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME; if (SConfig::GetInstance().sWASAPIDeviceSampleRate == "0" || !canSelectDeviceSampleRate) { m_wasapi_device_sample_rate_combo->setCurrentIndex(0); @@ -647,7 +654,8 @@ void AudioPane::OnEmulationStateChanged(bool running) m_wasapi_device_label->setEnabled(supports_current_emulation_state); m_wasapi_device_combo->setEnabled(supports_current_emulation_state); bool canSelectDeviceSampleRate = - SConfig::GetInstance().sWASAPIDevice != "default" && supports_current_emulation_state; + SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME && + supports_current_emulation_state; m_wasapi_device_sample_rate_label->setEnabled(canSelectDeviceSampleRate); m_wasapi_device_sample_rate_combo->setEnabled(canSelectDeviceSampleRate); bool is_wasapi = backend == BACKEND_WASAPI; From 4382b2acbb970fec2b2e260b3926e472b4df2bb7 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 6 Dec 2020 22:29:23 +0200 Subject: [PATCH 29/56] Improved some bad audio panel code --- Source/Core/DolphinQt/Settings/AudioPane.cpp | 25 +++++++++++++------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 0cd15532d1ec..77c747ab6bc4 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -5,6 +5,7 @@ #include "DolphinQt/Settings/AudioPane.h" #include +#include #include #include @@ -57,6 +58,8 @@ void AudioPane::CreateWidgets() auto* dsp_box = new QGroupBox(tr("DSP Emulation Engine")); auto* dsp_layout = new QVBoxLayout; + QFontMetrics font_metrics(font()); + dsp_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); dsp_box->setLayout(dsp_layout); m_dsp_hle = new QRadioButton(tr("DSP HLE (fast)")); @@ -82,7 +85,7 @@ void AudioPane::CreateWidgets() m_volume_slider->setToolTip(tr("Using this is preferred over the OS mixer volume")); m_volume_indicator->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); - m_volume_indicator->setFixedWidth(QFontMetrics(font()).boundingRect(tr("%1 %").arg(100)).width()); + m_volume_indicator->setFixedWidth(font_metrics.boundingRect(tr("%1 %").arg(100)).width()); volume_layout->addWidget(m_volume_slider, 0, Qt::AlignHCenter); volume_layout->addWidget(m_volume_indicator, 0, Qt::AlignHCenter); @@ -165,9 +168,9 @@ void AudioPane::CreateWidgets() m_emu_speed_tolerance_slider = new QSlider(Qt::Horizontal); m_emu_speed_tolerance_indicator = new QLabel(); QSize min_size = m_emu_speed_tolerance_indicator->minimumSize(); - min_size.setWidth(45); - // Avoid the slider in line with this resizing because of text length changes. - // It would be best to do it dynamically based on the translation + min_size.setWidth( + std::max(std::max(font_metrics.width(tr("Disabled")), font_metrics.width(tr("None"))), + font_metrics.width(tr("%1 ms").arg(125)))); m_emu_speed_tolerance_indicator->setMinimumSize(min_size); m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); //To review: maybe have a set of 4 or 5 options to keep it simpler @@ -187,9 +190,11 @@ void AudioPane::CreateWidgets() m_dolby_quality_label = new QLabel(tr("DPLII Decoding Quality:")); + int max_dolby_quality = int(AudioCommon::DPL2Quality::Extreme); + m_dolby_quality_slider = new QSlider(Qt::Horizontal); m_dolby_quality_slider->setMinimum(0); - m_dolby_quality_slider->setMaximum(3); + m_dolby_quality_slider->setMaximum(max_dolby_quality); m_dolby_quality_slider->setPageStep(1); m_dolby_quality_slider->setTickPosition(QSlider::TicksBelow); m_dolby_quality_slider->setToolTip( @@ -199,9 +204,13 @@ void AudioPane::CreateWidgets() m_dolby_quality_latency_label = new QLabel(); min_size = m_dolby_quality_latency_label->minimumSize(); - min_size.setWidth(143); - // Avoid the slider in line with this resizing because of text length changes. - // It would be best to do it dynamically based on the translation + int max_width = 0; + for (int i = 0; i <= max_dolby_quality; ++i) + { + max_width = std::max(max_width, font_metrics.width(GetDPL2QualityAndLatencyLabel( + AudioCommon::DPL2Quality(max_dolby_quality)))); + } + min_size.setWidth(max_width); m_dolby_quality_latency_label->setMinimumSize(min_size); mixer_layout->addWidget(m_stretching_enable, 0, 0, 1, -1); From 4648fdd1ccd5e61faadbfecf129fa0aa6770304f Mon Sep 17 00:00:00 2001 From: Filoppi Date: Mon, 7 Dec 2020 20:25:13 +0200 Subject: [PATCH 30/56] WASAPI: added support for devices that don't support 48kHz Plus Audio Pane cleanup --- Source/Core/AudioCommon/WASAPIStream.cpp | 32 +++--- Source/Core/DolphinQt/Settings/AudioPane.cpp | 103 ++++++++++++------- Source/Core/DolphinQt/Settings/AudioPane.h | 4 +- 3 files changed, 87 insertions(+), 52 deletions(-) diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 8ee67295b56c..529f835c223a 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -385,9 +385,10 @@ std::vector WASAPIStream::GetSelectedDeviceSampleRates() return device_sample_rates; } - // Anything below 48kHz should not be used, but I've added 44.1kHz in case - // a device doesn't support 48kHz. Any new value can be added here - unsigned long worst_to_best_sample_rates[5] = {44100, 48000, 96000, 192000, 384000}; + // Anything below 48kHz should not be used, but I've added 44.1kHz and 32kHz in case + // a device doesn't support 48kHz. Any new value can be added here. + // Unfortunately there is no easy way to get the list of supported formats. + unsigned long worst_to_best_sample_rates[6] = {32000, 44100, 48000, 96000, 192000, 384000}; s32 i = ARRAYSIZE(worst_to_best_sample_rates) - 1; WAVEFORMATEXTENSIBLE format; @@ -431,10 +432,13 @@ std::vector WASAPIStream::GetSelectedDeviceSampleRates() --i; } - HandleWinAPI("Found no supported device formats, will default to " + - std::to_string(AudioCommon::GetDefaultSampleRate()) + - " whether supported or not", - result); + if (device_sample_rates.size() == 0) + { + HandleWinAPI("Found no supported device formats, will default to " + + std::to_string(AudioCommon::GetDefaultSampleRate()) + + " whether supported or not", + result); + } device->Release(); audio_client->Release(); @@ -493,14 +497,18 @@ bool WASAPIStream::SetRunning(bool running) if (running) { - unsigned long sample_rate = AudioCommon::GetDefaultSampleRate(); + bool using_default_device = SConfig::GetInstance().sWASAPIDevice == DEFAULT_DEVICE_NAME; + // Start from the current OS selected Mixer sample rate if we are the default device. This is + // because the default device might not support Dolphin's default sample rate, thus WASAPI would + // fail to start + unsigned long sample_rate = using_default_device ? AudioCommon::GetOSMixerSampleRate() : + AudioCommon::GetDefaultSampleRate(); // Make sure the selected mode is still supported (ignored if we are using the default device) char* pEnd = nullptr; unsigned long user_preferred_sample_rate = std::strtoull(SConfig::GetInstance().sWASAPIDeviceSampleRate.c_str(), &pEnd, 10); - if (SConfig::GetInstance().sWASAPIDevice != DEFAULT_DEVICE_NAME && pEnd != nullptr && - user_preferred_sample_rate != 0) + if (!using_default_device && pEnd != nullptr && user_preferred_sample_rate != 0) { std::vector selected_device_sample_rates = WASAPIStream::GetSelectedDeviceSampleRates(); @@ -659,8 +667,8 @@ bool WASAPIStream::SetRunning(bool running) result = m_audio_client->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, stream_flags, device_period, device_period, &m_format.Format, nullptr); } - // TODO: just like we do above, fallback here if we don't support surround. We should also - // add support for more devices (e.g. bitrate, channels, format) by falling back as well + // TODO: just like we do above, we should add support for more devices + // (e.g. bitrate, channels, format) by falling back and trying again if (result == AUDCLNT_E_UNSUPPORTED_FORMAT) { diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 77c747ab6bc4..59b1a147d128 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -34,12 +34,16 @@ #ifdef _WIN32 #define WASAPI_DEFAULT_DEVICE_NAME "default" +#define WASAPI_INVALID_SAMPLE_RATE "0" #endif AudioPane::AudioPane() { m_running = false; m_ignore_save_settings = false; +#ifdef _WIN32 + m_wasapi_device_supports_default_sample_rate = false; +#endif CheckNeedForLatencyControl(); CreateWidgets(); @@ -124,10 +128,11 @@ void AudioPane::CreateWidgets() m_use_os_sample_rate = new QCheckBox(tr("Use OS Mixer sample rate")); m_use_os_sample_rate->setToolTip( - tr("Directly mixes at the current OS mixer sample rate as opposed to %1 " - "Hz.\nThis will make resampling from 32 kHz sources more accurate, possibly improving" + tr("Directly mixes and outputs at the current OS mixer sample rate (as opposed to %1 " + "Hz).\nThis will make resampling from 32 kHz sources more accurate, possibly improving" " audio\n" - "quality at the cost of performance. It won't follow changes to your OS setting." + "quality at the cost of performance. It won't follow changes to your OS setting after " + "start." "\nIf unsure, leave off.") .arg(AudioCommon::GetDefaultSampleRate())); @@ -173,7 +178,8 @@ void AudioPane::CreateWidgets() font_metrics.width(tr("%1 ms").arg(125)))); m_emu_speed_tolerance_indicator->setMinimumSize(min_size); m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); - //To review: maybe have a set of 4 or 5 options to keep it simpler + //To review: maybe have a set of 4 or 5 options to keep it simpler (low, high, ...). + //To add descrpition to m_emu_speed_tolerance_label explaining that it's the time the emulation need to be offsetted by to start using the current speed m_stretching_enable->setToolTip(tr( "Enables stretching of the audio (pitch correction) to match the emulation speed.\nIt might " @@ -379,7 +385,7 @@ void AudioPane::SaveSettings() if (SConfig::GetInstance().bDSPHLE != m_dsp_hle->isChecked() || SConfig::GetInstance().m_DSPEnableJIT != m_dsp_lle->isChecked()) { - OnDspChanged(); + OnDSPChanged(); } SConfig::GetInstance().bDSPHLE = m_dsp_hle->isChecked(); Config::SetBaseOrCurrent(Config::MAIN_DSP_HLE, m_dsp_hle->isChecked()); @@ -475,20 +481,10 @@ void AudioPane::SaveSettings() } } - std::string deviceSampleRate = "0"; - - bool canSelectDeviceSampleRate = - SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME; - if (m_wasapi_device_sample_rate_combo->currentIndex() > 0 && canSelectDeviceSampleRate) - { - QString qs = m_wasapi_device_sample_rate_combo->currentText(); - qs.chop(3); // Remove " Hz" from the end - deviceSampleRate = qs.toStdString(); - } - - if (SConfig::GetInstance().sWASAPIDeviceSampleRate != deviceSampleRate) + std::string device_sample_rate = GetWASAPIDeviceSampleRate(); + if (SConfig::GetInstance().sWASAPIDeviceSampleRate != device_sample_rate) { - SConfig::GetInstance().sWASAPIDeviceSampleRate = deviceSampleRate; + SConfig::GetInstance().sWASAPIDeviceSampleRate = device_sample_rate; backend_setting_changed = true; } #endif @@ -497,7 +493,7 @@ void AudioPane::SaveSettings() surround_enabled_changed); } -void AudioPane::OnDspChanged() +void AudioPane::OnDSPChanged() { const auto backend = SConfig::GetInstance().sBackend; @@ -540,6 +536,7 @@ void AudioPane::OnBackendChanged() m_wasapi_device_combo->clear(); m_wasapi_device_combo->addItem(tr("Default Device")); + // TODO: The UI isn't updated when any device changes or the user changes any settings for (const auto device : WASAPIStream::GetAvailableDevices()) m_wasapi_device_combo->addItem(QString::fromStdString(device)); @@ -559,23 +556,31 @@ void AudioPane::OnWASAPIDeviceChanged() m_ignore_save_settings = true; m_wasapi_device_sample_rate_combo->clear(); + m_wasapi_device_supports_default_sample_rate = false; // Don't allow users to select a sample rate for the default device, // even though that would be possible, the default device can change // at any time so it wouldn't make sense - bool canSelectDeviceSampleRate = + bool can_select_device_sample_rate = SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME; - if (canSelectDeviceSampleRate) + if (can_select_device_sample_rate) { m_wasapi_device_sample_rate_combo->setEnabled(true); m_wasapi_device_sample_rate_label->setEnabled(true); - m_wasapi_device_sample_rate_combo->addItem( - tr("Default Dolphin Sample Rate (%1 Hz)").arg(AudioCommon::GetDefaultSampleRate())); - - for (const auto deviceSampleRate : WASAPIStream::GetSelectedDeviceSampleRates()) + for (const auto device_sample_rate : WASAPIStream::GetSelectedDeviceSampleRates()) { + if (device_sample_rate == AudioCommon::GetDefaultSampleRate()) + { + m_wasapi_device_supports_default_sample_rate = true; + } m_wasapi_device_sample_rate_combo->addItem( - QString::number(deviceSampleRate).append(tr(" Hz"))); + QString::number(device_sample_rate).append(tr(" Hz"))); + } + + if (m_wasapi_device_supports_default_sample_rate) + { + m_wasapi_device_sample_rate_combo->insertItem( + 0, tr("Default Dolphin Sample Rate (%1 Hz)").arg(AudioCommon::GetDefaultSampleRate())); } } else @@ -585,7 +590,7 @@ void AudioPane::OnWASAPIDeviceChanged() if (m_running) { std::string sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate; - if (sample_rate_text == "0") + if (sample_rate_text == WASAPI_INVALID_SAMPLE_RATE) { sample_rate_text = std::to_string(AudioCommon::GetDefaultSampleRate()); } @@ -595,7 +600,7 @@ void AudioPane::OnWASAPIDeviceChanged() else { m_wasapi_device_sample_rate_combo->addItem( - tr("Select a Device (%1 Hz)").arg(AudioCommon::GetDefaultSampleRate())); + tr("Select a Device (%1 Hz)").arg(AudioCommon::GetOSMixerSampleRate())); } } @@ -604,24 +609,44 @@ void AudioPane::OnWASAPIDeviceChanged() void AudioPane::LoadWASAPIDeviceSampleRate() { - bool canSelectDeviceSampleRate = + bool can_select_device_sample_rate = SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME; - if (SConfig::GetInstance().sWASAPIDeviceSampleRate == "0" || !canSelectDeviceSampleRate) + if (SConfig::GetInstance().sWASAPIDeviceSampleRate == WASAPI_INVALID_SAMPLE_RATE || + !can_select_device_sample_rate) { + // Even if we had specified a custom device thay didn't support Dolphin's default sample rate, + // we pre-select the highest possible sample rate from the list and leave users the choice m_wasapi_device_sample_rate_combo->setCurrentIndex(0); - SConfig::GetInstance().sWASAPIDeviceSampleRate = "0"; + SConfig::GetInstance().sWASAPIDeviceSampleRate = GetWASAPIDeviceSampleRate(); } else { - std::string sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate + " Hz"; - m_wasapi_device_sample_rate_combo->setCurrentText(QString::fromStdString(sample_rate_text)); + QString device_sample_rate = + tr("%1 Hz").arg(QString::fromStdString(SConfig::GetInstance().sWASAPIDeviceSampleRate)); + m_wasapi_device_sample_rate_combo->setCurrentText(device_sample_rate); // Saved sample rate not found, reset it - if (m_wasapi_device_combo->currentIndex() < 1) + if (m_wasapi_device_sample_rate_combo->currentText() != device_sample_rate) { - SConfig::GetInstance().sWASAPIDeviceSampleRate = "0"; + m_wasapi_device_sample_rate_combo->setCurrentIndex(0); + SConfig::GetInstance().sWASAPIDeviceSampleRate = GetWASAPIDeviceSampleRate(); } } } + +std::string AudioPane::GetWASAPIDeviceSampleRate() const +{ + bool can_select_device_sample_rate = + SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME; + if ((!m_wasapi_device_supports_default_sample_rate || + m_wasapi_device_sample_rate_combo->currentIndex() > 0) && + can_select_device_sample_rate) + { + QString device_sample_rate = m_wasapi_device_sample_rate_combo->currentText(); + device_sample_rate.chop(tr(" Hz").length()); // Remove " Hz" from the end + return device_sample_rate.toStdString(); + } + return WASAPI_INVALID_SAMPLE_RATE; +} #endif void AudioPane::OnEmulationStateChanged(bool running) @@ -645,7 +670,7 @@ void AudioPane::OnEmulationStateChanged(bool running) if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) { // TODO: If the audio device turned out unable to support surround, add the text "failed" in the - // UI name, so the user can try to enable it again or disable surround from the game. + // label and gray it out, so users can try to enable it again or disable surround from the game bool enable_dolby_pro_logic = supports_current_emulation_state; m_dolby_pro_logic->setEnabled(enable_dolby_pro_logic); EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); @@ -662,11 +687,11 @@ void AudioPane::OnEmulationStateChanged(bool running) #ifdef _WIN32 m_wasapi_device_label->setEnabled(supports_current_emulation_state); m_wasapi_device_combo->setEnabled(supports_current_emulation_state); - bool canSelectDeviceSampleRate = + bool can_select_device_sample_rate = SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME && supports_current_emulation_state; - m_wasapi_device_sample_rate_label->setEnabled(canSelectDeviceSampleRate); - m_wasapi_device_sample_rate_combo->setEnabled(canSelectDeviceSampleRate); + m_wasapi_device_sample_rate_label->setEnabled(can_select_device_sample_rate); + m_wasapi_device_sample_rate_combo->setEnabled(can_select_device_sample_rate); bool is_wasapi = backend == BACKEND_WASAPI; if (is_wasapi) { diff --git a/Source/Core/DolphinQt/Settings/AudioPane.h b/Source/Core/DolphinQt/Settings/AudioPane.h index 824595af1e02..9e773436984d 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.h +++ b/Source/Core/DolphinQt/Settings/AudioPane.h @@ -38,8 +38,9 @@ class AudioPane final : public QWidget #ifdef _WIN32 void OnWASAPIDeviceChanged(); void LoadWASAPIDeviceSampleRate(); + std::string GetWASAPIDeviceSampleRate() const; #endif - void OnDspChanged(); + void OnDSPChanged(); void OnVolumeChanged(int volume); void CheckNeedForLatencyControl(); @@ -75,6 +76,7 @@ class AudioPane final : public QWidget QLabel* m_wasapi_device_sample_rate_label; QComboBox* m_wasapi_device_combo; QComboBox* m_wasapi_device_sample_rate_combo; + bool m_wasapi_device_supports_default_sample_rate; #endif // Audio Stretching From 97d7ec16cf42378052535e7820850d8a0409976d Mon Sep 17 00:00:00 2001 From: Filoppi Date: Mon, 7 Dec 2020 22:40:07 +0200 Subject: [PATCH 31/56] DPLII added UI feedback to let users know if it was enabled successfully And wrote some code to refresh WASAPI devices every time users click on the list --- Source/Core/AudioCommon/AudioCommon.cpp | 11 ++ Source/Core/AudioCommon/AudioCommon.h | 1 + Source/Core/AudioCommon/CubebStream.h | 1 + Source/Core/AudioCommon/SoundStream.h | 2 + Source/Core/AudioCommon/WASAPIStream.cpp | 10 +- Source/Core/AudioCommon/WASAPIStream.h | 1 + Source/Core/DolphinQt/Settings/AudioPane.cpp | 185 ++++++++++++++----- Source/Core/DolphinQt/Settings/AudioPane.h | 23 ++- 8 files changed, 178 insertions(+), 56 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index fd32a2aa2117..8ddd2207f4e8 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -210,6 +210,16 @@ bool BackendSupportsRuntimeSettingsChanges() return false; } +bool IsSurroundEnabled() +{ + std::lock_guard guard(g_sound_stream_mutex); + if (g_sound_stream) + { + return g_sound_stream->IsSurroundEnabled(); + } + return SConfig::GetInstance().bDPL2Decoder; +} + unsigned long GetDefaultSampleRate() { return 48000ul; @@ -217,6 +227,7 @@ unsigned long GetDefaultSampleRate() unsigned long GetMaxSupportedLatency() { + // On the right we have the highest sample rate supported by the emulation return (Mixer::MAX_SAMPLES * 1000) / (54000000.0 / 1124.0); } diff --git a/Source/Core/AudioCommon/AudioCommon.h b/Source/Core/AudioCommon/AudioCommon.h index b19ab3d474d6..a93175a950b4 100644 --- a/Source/Core/AudioCommon/AudioCommon.h +++ b/Source/Core/AudioCommon/AudioCommon.h @@ -32,6 +32,7 @@ bool SupportsDPL2Decoder(std::string_view backend); bool SupportsLatencyControl(std::string_view backend); bool SupportsVolumeChanges(std::string_view backend); bool BackendSupportsRuntimeSettingsChanges(); +bool IsSurroundEnabled(); // Default "Dolphin" output and internal mixer sample rate unsigned long GetDefaultSampleRate(); // Returns the min buffer time length it can hold (in ms), our backends can't have a latency diff --git a/Source/Core/AudioCommon/CubebStream.h b/Source/Core/AudioCommon/CubebStream.h index 1e16f3b3aae1..735a84302a5d 100644 --- a/Source/Core/AudioCommon/CubebStream.h +++ b/Source/Core/AudioCommon/CubebStream.h @@ -23,6 +23,7 @@ class CubebStream final : public SoundStream void SetVolume(int) override; bool SupportsRuntimeSettingsChanges() const override { return true; } + bool IsSurroundEnabled() const override { return !m_stereo; } void OnSettingsChanged() override { m_settings_changed = true; diff --git a/Source/Core/AudioCommon/SoundStream.h b/Source/Core/AudioCommon/SoundStream.h index a1a766e4a781..3e0c9c440e68 100644 --- a/Source/Core/AudioCommon/SoundStream.h +++ b/Source/Core/AudioCommon/SoundStream.h @@ -28,6 +28,8 @@ class SoundStream virtual void SoundLoop() {} virtual void Update() {} virtual bool SupportsRuntimeSettingsChanges() const { return false; } + // No need to implement this if SupportsRuntimeSettingsChanges() returns false + virtual bool IsSurroundEnabled() const { return false; } virtual void OnSettingsChanged() {} // Can be called by the main thread or the emulator/video thread, // never concurrently. Only call this through AudioCommons diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 529f835c223a..334f4e2c8104 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -348,7 +348,9 @@ std::vector WASAPIStream::GetSelectedDeviceSampleRates() if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) return device_sample_rates; - if (SConfig::GetInstance().sWASAPIDevice == DEFAULT_DEVICE_NAME) + bool using_default_device = SConfig::GetInstance().sWASAPIDevice == DEFAULT_DEVICE_NAME; + + if (using_default_device) { result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); } @@ -434,8 +436,10 @@ std::vector WASAPIStream::GetSelectedDeviceSampleRates() if (device_sample_rates.size() == 0) { - HandleWinAPI("Found no supported device formats, will default to " + - std::to_string(AudioCommon::GetDefaultSampleRate()) + + unsigned long sample_rate = using_default_device ? AudioCommon::GetOSMixerSampleRate() : + AudioCommon::GetDefaultSampleRate(); + HandleWinAPI("Found no supported device formats, will try with " + + std::to_string(sample_rate) + " whether supported or not", result); } diff --git a/Source/Core/AudioCommon/WASAPIStream.h b/Source/Core/AudioCommon/WASAPIStream.h index 7c5c68800c61..37ecbb38f861 100644 --- a/Source/Core/AudioCommon/WASAPIStream.h +++ b/Source/Core/AudioCommon/WASAPIStream.h @@ -41,6 +41,7 @@ class WASAPIStream final : public SoundStream bool IsUsingDefaultDevice() const { return m_using_default_device; } bool SupportsRuntimeSettingsChanges() const override { return true; } + bool IsSurroundEnabled() const override { return m_surround; } void OnSettingsChanged() override { m_should_restart = true; } static bool IsValid() { return true; } diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 59b1a147d128..bc311ba7c71c 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -20,6 +19,7 @@ #include #include #include +#include #include "AudioCommon/AudioCommon.h" #include "AudioCommon/Enums.h" @@ -37,6 +37,15 @@ #define WASAPI_INVALID_SAMPLE_RATE "0" #endif +void ClickEventComboBox::showPopup() +{ + if (m_audio_pane) + { + m_audio_pane->OnCustomShowPopup(this); + } + QComboBox::showPopup(); +} + AudioPane::AudioPane() { m_running = false; @@ -55,6 +64,27 @@ AudioPane::AudioPane() [=](Core::State state) { OnEmulationStateChanged(state != Core::State::Uninitialized); }); OnEmulationStateChanged(Core::GetState() != Core::State::Uninitialized); + + m_timer = new QTimer(this); +} + +void AudioPane::showEvent(QShowEvent* event) +{ + // Start updating the DPLII settings every second (we have no simple safe way to bind or listen + // for events in the backend so we just refresh as the game is running to see if DPLII + // was enabled successfully) + connect(m_timer, &QTimer::timeout, this, &AudioPane::RefreshDolbyWidgets); + RefreshDolbyWidgets(); + m_timer->start(1000); + + QWidget::showEvent(event); +} +void AudioPane::hideEvent(QHideEvent* event) +{ + disconnect(m_timer, &QTimer::timeout, this, &AudioPane::RefreshDolbyWidgets); + m_timer->stop(); + + QWidget::hideEvent(event); } void AudioPane::CreateWidgets() @@ -125,6 +155,8 @@ void AudioPane::CreateWidgets() " crackling.\nCertain backends only. Values above 20ms are not suggested.")); } + // TODO: implement this in other Operative Systems, + // as of now this is only supported on (all) Windows backends m_use_os_sample_rate = new QCheckBox(tr("Use OS Mixer sample rate")); m_use_os_sample_rate->setToolTip( @@ -148,7 +180,8 @@ void AudioPane::CreateWidgets() #ifdef _WIN32 m_wasapi_device_label = new QLabel(tr("Device:")); m_wasapi_device_sample_rate_label = new QLabel(tr("Device Sample Rate:")); - m_wasapi_device_combo = new QComboBox; + m_wasapi_device_combo = new ClickEventComboBox; + m_wasapi_device_combo->m_audio_pane = this; m_wasapi_device_sample_rate_combo = new QComboBox; m_wasapi_device_combo->setToolTip(tr("Some devices might not work with WASAPI Exclusive mode")); @@ -283,13 +316,13 @@ void AudioPane::LoadSettings() // Backend m_ignore_save_settings = true; - const auto current = SConfig::GetInstance().sBackend; + const auto current_backend = SConfig::GetInstance().sBackend; bool selection_set = false; m_backend_combo->clear(); for (const auto& backend : AudioCommon::GetSoundBackends()) { m_backend_combo->addItem(tr(backend.c_str()), QVariant(QString::fromStdString(backend))); - if (backend == current) + if (backend == current_backend) { m_backend_combo->setCurrentIndex(m_backend_combo->count() - 1); selection_set = true; @@ -311,7 +344,7 @@ void AudioPane::LoadSettings() m_ignore_save_settings = false; m_dolby_quality_latency_label->setText( GetDPL2QualityAndLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); - if (AudioCommon::SupportsDPL2Decoder(current) && !m_dsp_hle->isChecked()) + if (AudioCommon::SupportsDPL2Decoder(current_backend) && !m_dsp_hle->isChecked()) { EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); } @@ -340,27 +373,11 @@ void AudioPane::LoadSettings() m_emu_speed_tolerance_indicator->setText( tr("%1 ms").arg(m_emu_speed_tolerance_slider->value())); - m_ignore_save_settings = false; - #ifdef _WIN32 - m_ignore_save_settings = true; - if (SConfig::GetInstance().sWASAPIDevice == WASAPI_DEFAULT_DEVICE_NAME) - { - m_wasapi_device_combo->setCurrentIndex(0); - } - else - { - m_wasapi_device_combo->setCurrentText( - QString::fromStdString(SConfig::GetInstance().sWASAPIDevice)); - // Saved device not found, reset it - if (m_wasapi_device_combo->currentIndex() < 1) - { - SConfig::GetInstance().sWASAPIDevice = WASAPI_DEFAULT_DEVICE_NAME; - } - } + LoadWASAPIDevice(); LoadWASAPIDeviceSampleRate(); - m_ignore_save_settings = false; #endif + m_ignore_save_settings = false; // Call this again to "clamp" values that might not have been accepted SaveSettings(); @@ -393,13 +410,13 @@ void AudioPane::SaveSettings() Config::SetBaseOrCurrent(Config::MAIN_DSP_JIT, m_dsp_lle->isChecked()); // Backend - const auto selection = + const auto selected_backend = m_backend_combo->itemData(m_backend_combo->currentIndex()).toString().toStdString(); auto& backend = SConfig::GetInstance().sBackend; - if (selection != backend) + if (selected_backend != backend) { - backend = selection; + backend = selected_backend; OnBackendChanged(); } @@ -416,6 +433,10 @@ void AudioPane::SaveSettings() if (SConfig::GetInstance().bDPL2Decoder != m_dolby_pro_logic->isChecked()) { SConfig::GetInstance().bDPL2Decoder = m_dolby_pro_logic->isChecked(); + if (!m_dolby_pro_logic->isChecked()) + { + m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1)")); + } backend_setting_changed = true; surround_enabled_changed = true; } @@ -428,7 +449,10 @@ void AudioPane::SaveSettings() GetDPL2QualityAndLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); backend_setting_changed = true; // Not a mistake } - if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) + // If we have disabled surround while the game is running, disable all its settings immediately, + // don't wait for the timer + if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked() && + (!m_running || (surround_enabled_changed && !m_dolby_pro_logic->isChecked()))) { EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); } @@ -467,17 +491,23 @@ void AudioPane::SaveSettings() if (m_wasapi_device_combo->currentIndex() > 0) device = m_wasapi_device_combo->currentText().toStdString(); - if (SConfig::GetInstance().sWASAPIDevice != device) + bool device_changed = SConfig::GetInstance().sWASAPIDevice != device; + + // Force OnWASAPIDeviceChanged() to be called if we have the default device to update some labels + if (device_changed || device == WASAPI_DEFAULT_DEVICE_NAME) { - assert(device != ""); + assert(device != ""); //To turn into a log and delete the include WARN_LOG(AUDIO, "WASAPI: Device names called \"%s\" are not supported", DEFAULT_DEVICE_NAME); SConfig::GetInstance().sWASAPIDevice = device; bool is_wasapi = backend == BACKEND_WASAPI; if (is_wasapi) { OnWASAPIDeviceChanged(); - LoadWASAPIDeviceSampleRate(); - backend_setting_changed = true; + if (device_changed) + { + LoadWASAPIDeviceSampleRate(); + backend_setting_changed = true; + } } } @@ -525,8 +555,6 @@ void AudioPane::OnBackendChanged() m_wasapi_device_combo->setHidden(!is_wasapi); m_wasapi_device_sample_rate_combo->setHidden(!is_wasapi); - // TODO: implement this in other Operative Systems, - // as of now this is only supported on (all) Windows backends m_use_os_sample_rate->setHidden(is_wasapi); if (is_wasapi) @@ -536,7 +564,6 @@ void AudioPane::OnBackendChanged() m_wasapi_device_combo->clear(); m_wasapi_device_combo->addItem(tr("Default Device")); - // TODO: The UI isn't updated when any device changes or the user changes any settings for (const auto device : WASAPIStream::GetAvailableDevices()) m_wasapi_device_combo->addItem(QString::fromStdString(device)); @@ -577,6 +604,7 @@ void AudioPane::OnWASAPIDeviceChanged() QString::number(device_sample_rate).append(tr(" Hz"))); } + // For clarity, add the default sample rate as a special, first, setting if (m_wasapi_device_supports_default_sample_rate) { m_wasapi_device_sample_rate_combo->insertItem( @@ -587,26 +615,32 @@ void AudioPane::OnWASAPIDeviceChanged() { m_wasapi_device_sample_rate_combo->setEnabled(false); m_wasapi_device_sample_rate_label->setEnabled(false); - if (m_running) - { - std::string sample_rate_text = SConfig::GetInstance().sWASAPIDeviceSampleRate; - if (sample_rate_text == WASAPI_INVALID_SAMPLE_RATE) - { - sample_rate_text = std::to_string(AudioCommon::GetDefaultSampleRate()); - } - m_wasapi_device_sample_rate_combo->addItem( - tr("%1 Hz").arg(QString::fromStdString(sample_rate_text))); - } - else - { - m_wasapi_device_sample_rate_combo->addItem( - tr("Select a Device (%1 Hz)").arg(AudioCommon::GetOSMixerSampleRate())); - } + m_wasapi_device_sample_rate_combo->addItem( + tr("Select a Device (%1 Hz)").arg(AudioCommon::GetOSMixerSampleRate())); } m_ignore_save_settings = false; } +void AudioPane::LoadWASAPIDevice() +{ + if (SConfig::GetInstance().sWASAPIDevice == WASAPI_DEFAULT_DEVICE_NAME) + { + m_wasapi_device_combo->setCurrentIndex(0); + } + else + { + QString device = QString::fromStdString(SConfig::GetInstance().sWASAPIDevice); + m_wasapi_device_combo->setCurrentText(device); + // Saved device not found, reset it (don't reset the saved sample rate) + if (m_wasapi_device_combo->currentText() != device) + { + m_wasapi_device_combo->setCurrentIndex(0); + SConfig::GetInstance().sWASAPIDevice = WASAPI_DEFAULT_DEVICE_NAME; + } + } +} + void AudioPane::LoadWASAPIDeviceSampleRate() { bool can_select_device_sample_rate = @@ -669,11 +703,11 @@ void AudioPane::OnEmulationStateChanged(bool running) if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) { - // TODO: If the audio device turned out unable to support surround, add the text "failed" in the - // label and gray it out, so users can try to enable it again or disable surround from the game bool enable_dolby_pro_logic = supports_current_emulation_state; m_dolby_pro_logic->setEnabled(enable_dolby_pro_logic); EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); + if (!m_running) + m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1)")); } if (m_latency_control_supported && AudioCommon::SupportsLatencyControl(SConfig::GetInstance().sBackend)) @@ -711,6 +745,36 @@ void AudioPane::OnVolumeChanged(int volume) m_volume_indicator->setText(tr("%1 %").arg(volume)); } +void AudioPane::OnCustomShowPopup(QWidget* widget) +{ +#ifdef _WIN32 + if (widget == m_wasapi_device_combo) + { + m_ignore_save_settings = true; + + m_wasapi_device_combo->clear(); + m_wasapi_device_combo->addItem(tr("Default Device")); + + for (const auto device : WASAPIStream::GetAvailableDevices()) + m_wasapi_device_combo->addItem(QString::fromStdString(device)); + + std::string device = SConfig::GetInstance().sWASAPIDevice; + + LoadWASAPIDevice(); + + m_ignore_save_settings = false; + + if (device != SConfig::GetInstance().sWASAPIDevice) + { + // Restore it so that saving will trigger OnWASAPIDeviceChanged() again + SConfig::GetInstance().sWASAPIDevice = device; + + SaveSettings(); + } + } +#endif +} + void AudioPane::CheckNeedForLatencyControl() { std::vector backends = AudioCommon::GetSoundBackends(); @@ -735,6 +799,25 @@ QString AudioPane::GetDPL2QualityAndLatencyLabel(AudioCommon::DPL2Quality value) } } +void AudioPane::RefreshDolbyWidgets() +{ + const auto backend = SConfig::GetInstance().sBackend; + if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked() && m_running && + AudioCommon::BackendSupportsRuntimeSettingsChanges()) + { + bool surround_enabled = AudioCommon::IsSurroundEnabled(); + if (!surround_enabled && SConfig::GetInstance().bDPL2Decoder) + { + m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1) (FAILED)")); + } + else + { + m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1)")); + } + EnableDolbyQualityWidgets(surround_enabled); + } +} + void AudioPane::EnableDolbyQualityWidgets(bool enabled) const { m_dolby_quality_label->setEnabled(enabled); diff --git a/Source/Core/DolphinQt/Settings/AudioPane.h b/Source/Core/DolphinQt/Settings/AudioPane.h index 9e773436984d..dcdb5abfa55d 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.h +++ b/Source/Core/DolphinQt/Settings/AudioPane.h @@ -4,6 +4,7 @@ #pragma once +#include #include namespace AudioCommon @@ -12,7 +13,6 @@ enum class DPL2Quality; } class QCheckBox; -class QComboBox; class QLabel; class QGridLayout; class QRadioButton; @@ -20,12 +20,28 @@ class QSlider; class QSpinBox; class SettingsWindow; +class ClickEventComboBox : public QComboBox +{ + Q_OBJECT +public: + class AudioPane* m_audio_pane = nullptr; + +private: + virtual void showPopup() override; +}; + class AudioPane final : public QWidget { Q_OBJECT public: explicit AudioPane(); + void OnCustomShowPopup(QWidget* widget); + +protected: + virtual void showEvent(QShowEvent* event) override; + virtual void hideEvent(QHideEvent* event) override; + private: void CreateWidgets(); void ConnectWidgets(); @@ -37,6 +53,7 @@ class AudioPane final : public QWidget void OnBackendChanged(); #ifdef _WIN32 void OnWASAPIDeviceChanged(); + void LoadWASAPIDevice(); void LoadWASAPIDeviceSampleRate(); std::string GetWASAPIDeviceSampleRate() const; #endif @@ -47,10 +64,12 @@ class AudioPane final : public QWidget bool m_latency_control_supported; QString GetDPL2QualityAndLatencyLabel(AudioCommon::DPL2Quality value) const; + void RefreshDolbyWidgets(); void EnableDolbyQualityWidgets(bool enabled) const; bool m_running; bool m_ignore_save_settings; + QTimer* m_timer; // DSP Engine QRadioButton* m_dsp_hle; @@ -74,7 +93,7 @@ class AudioPane final : public QWidget #ifdef _WIN32 QLabel* m_wasapi_device_label; QLabel* m_wasapi_device_sample_rate_label; - QComboBox* m_wasapi_device_combo; + ClickEventComboBox* m_wasapi_device_combo; QComboBox* m_wasapi_device_sample_rate_combo; bool m_wasapi_device_supports_default_sample_rate; #endif From 1ef4ca9da32a17d6356afb52f88851b339462486 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 8 Dec 2020 02:22:46 +0200 Subject: [PATCH 32/56] DPLII improvements With PulseAudio and OpenAL. Plus Audio Pane improvements. --- Source/Core/AudioCommon/AudioCommon.cpp | 2 +- Source/Core/AudioCommon/CubebStream.h | 2 + Source/Core/AudioCommon/OpenALStream.cpp | 17 +++---- Source/Core/AudioCommon/OpenALStream.h | 2 + Source/Core/AudioCommon/PulseAudioStream.cpp | 47 +++++++++++++++++--- Source/Core/AudioCommon/PulseAudioStream.h | 1 + Source/Core/DolphinQt/Settings/AudioPane.cpp | 21 +++++---- 7 files changed, 69 insertions(+), 23 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 8ddd2207f4e8..0b2baab7da8f 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -217,7 +217,7 @@ bool IsSurroundEnabled() { return g_sound_stream->IsSurroundEnabled(); } - return SConfig::GetInstance().bDPL2Decoder; + return SConfig::GetInstance().ShouldUseDPL2Decoder(); } unsigned long GetDefaultSampleRate() diff --git a/Source/Core/AudioCommon/CubebStream.h b/Source/Core/AudioCommon/CubebStream.h index 735a84302a5d..3b2c048cc5a8 100644 --- a/Source/Core/AudioCommon/CubebStream.h +++ b/Source/Core/AudioCommon/CubebStream.h @@ -23,6 +23,8 @@ class CubebStream final : public SoundStream void SetVolume(int) override; bool SupportsRuntimeSettingsChanges() const override { return true; } + // Cubeb will accept a 6.0 channels stream even if our device does not support it + // by just downmixing it (thus making it partially pointless) bool IsSurroundEnabled() const override { return !m_stereo; } void OnSettingsChanged() override { diff --git a/Source/Core/AudioCommon/OpenALStream.cpp b/Source/Core/AudioCommon/OpenALStream.cpp index ee0bf03fa76d..170d6710c78f 100644 --- a/Source/Core/AudioCommon/OpenALStream.cpp +++ b/Source/Core/AudioCommon/OpenALStream.cpp @@ -120,6 +120,8 @@ bool OpenALStream::Init() return false; } + m_use_surround = SConfig::GetInstance().ShouldUseDPL2Decoder(); + m_mixer->UpdateSettings(SConfig::GetInstance().bUseOSMixerSampleRate ? AudioCommon::GetOSMixerSampleRate() : AudioCommon::GetDefaultSampleRate()); @@ -223,15 +225,14 @@ void OpenALStream::SoundLoop() { Common::SetCurrentThreadName("Audio thread - openal"); - bool float32_capable = palIsExtensionPresent("AL_EXT_float32") != 0; - bool surround_capable = palIsExtensionPresent("AL_EXT_MCFORMATS") || IsCreativeXFi(); - bool use_surround = SConfig::GetInstance().ShouldUseDPL2Decoder() && surround_capable; - + bool float32_capable = palIsExtensionPresent("AL_EXT_float32"); // As there is no extension to check for 32-bit fixed point support // and we know that only a X-Fi with hardware OpenAL supports it, // we just check if one is being used. bool fixed32_capable = IsCreativeXFi(); + // TODO: there is no reason for OpenAL to not have SupportsRuntimeSettingsChanges() as true + // as everything can be changed the loop below, though being deprecated I didn't bother u32 frequency = m_mixer->GetSampleRate(); // Can't have zero samples per buffer @@ -298,7 +299,7 @@ void OpenALStream::SoundLoop() unsigned int min_frames = frames_per_buffer; - if (use_surround) + if (m_use_surround) { std::array dpl2; u32 rendered_frames = m_mixer->MixSurround(dpl2.data(), min_frames); @@ -352,9 +353,9 @@ void OpenALStream::SoundLoop() if (err == AL_INVALID_ENUM) { // 5.1 is not supported by the host, fallback to stereo in the next audio frame - WARN_LOG_FMT( - AUDIO, "Unable to set 5.1 surround mode. Updating OpenAL Soft might fix this issue."); - use_surround = false; + WARN_LOG_FMT(AUDIO, "Unable to set 5.1 surround mode, falling back to 2.0. Updating OpenAL " + "Soft might fix this issue."); + m_use_surround = false; } } else diff --git a/Source/Core/AudioCommon/OpenALStream.h b/Source/Core/AudioCommon/OpenALStream.h index dbc146d3fd1d..2531ddc98296 100644 --- a/Source/Core/AudioCommon/OpenALStream.h +++ b/Source/Core/AudioCommon/OpenALStream.h @@ -63,6 +63,7 @@ class OpenALStream final : public SoundStream void Update() override; static bool IsValid(); + bool IsSurroundEnabled() const override { return m_use_surround; } private: std::thread m_thread; @@ -72,6 +73,7 @@ class OpenALStream final : public SoundStream std::array m_buffers; ALuint m_source; ALfloat m_volume; + bool m_use_surround; #endif // _WIN32 }; diff --git a/Source/Core/AudioCommon/PulseAudioStream.cpp b/Source/Core/AudioCommon/PulseAudioStream.cpp index e324e097d735..8db4bdc224d9 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.cpp +++ b/Source/Core/AudioCommon/PulseAudioStream.cpp @@ -38,7 +38,7 @@ PulseAudio::~PulseAudio() m_thread.join(); } -// Called on audio thread. +// Called on audio thread void PulseAudio::SoundLoop() { Common::SetCurrentThreadName("Audio thread - pulse"); @@ -126,11 +126,44 @@ bool PulseAudio::PulseInit() m_pa_error = pa_stream_connect_playback(m_pa_s, nullptr, &m_pa_ba, flags, nullptr, nullptr); if (m_pa_error < 0) { - ERROR_LOG_FMT(AUDIO, "PulseAudio failed to initialize: {}", pa_strerror(m_pa_error)); - return false; + pa_stream_disconnect(m_pa_s); + pa_stream_unref(m_pa_s); + m_pa_s = nullptr; + + if (m_stereo) + { + ERROR_LOG_FMT(AUDIO, "PulseAudio failed to initialize (6.0, falling back to 2.0): {}", pa_strerror(m_pa_error)); + m_stereo = false; + + m_channels = m_stereo ? 2 : 6; + ss.format = PA_SAMPLE_S16LE; + m_bytespersample = sizeof(s16); + ss.channels = m_channels; + m_pa_ba.tlength = BUFFER_SAMPLES * m_channels * m_bytespersample; + channel_map_p = nullptr; + assert(pa_sample_spec_valid(&ss)); + + m_pa_s = pa_stream_new(m_pa_ctx, "Playback", &ss, channel_map_p); + pa_stream_set_write_callback(m_pa_s, WriteCallback, this); + pa_stream_set_underflow_callback(m_pa_s, UnderflowCallback, this); + + m_pa_error = pa_stream_connect_playback(m_pa_s, nullptr, &m_pa_ba, flags, nullptr, nullptr); + if (m_pa_error < 0) + { + pa_stream_disconnect(m_pa_s); + pa_stream_unref(m_pa_s); + m_pa_s = nullptr; + } + } + + if (m_pa_error < 0) + { + ERROR_LOG_FMT(AUDIO, "PulseAudio failed to initialize (2.0): {}", pa_strerror(m_pa_error)); + return false; + } } - INFO_LOG_FMT(AUDIO, "Pulse successfully initialized"); + INFO_LOG_FMT(AUDIO, "PulseAudio successfully initialized"); return true; } @@ -139,6 +172,9 @@ void PulseAudio::PulseShutdown() pa_context_disconnect(m_pa_ctx); pa_context_unref(m_pa_ctx); pa_mainloop_free(m_pa_ml); + m_pa_ml = nullptr; + m_pa_mlapi = nullptr; + m_pa_ctx = nullptr; } void PulseAudio::StateCallback(pa_context* c) @@ -157,7 +193,8 @@ void PulseAudio::StateCallback(pa_context* c) break; } } -// on underflow, increase pulseaudio latency in ~10ms steps + +// On underflow, increase pulseaudio latency in ~10ms steps void PulseAudio::UnderflowCallback(pa_stream* s) { m_pa_ba.tlength += BUFFER_SAMPLES * m_channels * m_bytespersample; diff --git a/Source/Core/AudioCommon/PulseAudioStream.h b/Source/Core/AudioCommon/PulseAudioStream.h index ee3a99f813b6..c05c198eb7f4 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.h +++ b/Source/Core/AudioCommon/PulseAudioStream.h @@ -22,6 +22,7 @@ class PulseAudio final : public SoundStream bool Init() override; bool SetRunning(bool running) override { return running; } + bool IsSurroundEnabled() const override { return !m_stereo; } static bool IsValid() { return true; } void StateCallback(pa_context* c); void WriteCallback(pa_stream* s, size_t length); diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index bc311ba7c71c..8ca3a5773771 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -135,10 +135,12 @@ void AudioPane::CreateWidgets() m_dolby_pro_logic->setToolTip( tr("Enables Dolby Pro Logic II emulation using 5.1 surround.\nCertain backends and DPS " - "emulation engines only.\nAutomatically disabled if not supported by your audio device." - "\nYou need to enable surround from the game settings in GC games or in the menu settings " + "emulation engines only.\n" + "You need to enable surround from the game settings in GC games or in the menu settings " "on Wii." "\nThe emulation will still output 2.0, but the encoder will extract information for 5.1." + "\nSome backends will notify when failed to enable it," + "\nwhile some other will just downmix it to stereo if not supported." "\nIf you align your backend latency to the DPLII block size," "\nthe added latency will be half the DPLII block size." "\nIf unsure, leave off.")); @@ -161,10 +163,8 @@ void AudioPane::CreateWidgets() m_use_os_sample_rate->setToolTip( tr("Directly mixes and outputs at the current OS mixer sample rate (as opposed to %1 " - "Hz).\nThis will make resampling from 32 kHz sources more accurate, possibly improving" - " audio\n" - "quality at the cost of performance. It won't follow changes to your OS setting after " - "start." + "Hz).\nIt avoids any additional resamplings, possibly improving quality and performance." + "\nIt won't follow changes to your OS setting after starting the emulation." "\nIf unsure, leave off.") .arg(AudioCommon::GetDefaultSampleRate())); @@ -748,6 +748,7 @@ void AudioPane::OnVolumeChanged(int volume) void AudioPane::OnCustomShowPopup(QWidget* widget) { #ifdef _WIN32 + // Refresh the WASAPI devices every time we try to select them if (widget == m_wasapi_device_combo) { m_ignore_save_settings = true; @@ -802,19 +803,21 @@ QString AudioPane::GetDPL2QualityAndLatencyLabel(AudioCommon::DPL2Quality value) void AudioPane::RefreshDolbyWidgets() { const auto backend = SConfig::GetInstance().sBackend; - if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked() && m_running && - AudioCommon::BackendSupportsRuntimeSettingsChanges()) + if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked() && m_running) { bool surround_enabled = AudioCommon::IsSurroundEnabled(); if (!surround_enabled && SConfig::GetInstance().bDPL2Decoder) { + // This message will likely only appear if the audio backend has failed to + // initialize because of 5.1, if it fails before that, it won't disable surround m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1) (FAILED)")); } else { m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1)")); } - EnableDolbyQualityWidgets(surround_enabled); + EnableDolbyQualityWidgets(surround_enabled && + AudioCommon::BackendSupportsRuntimeSettingsChanges()); } } From 9fef3ad2b562c8b50d572b3fcb852014b7231986 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 8 Dec 2020 02:41:07 +0200 Subject: [PATCH 33/56] PulseAudio fix inverted bool check --- Source/Core/AudioCommon/PulseAudioStream.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/AudioCommon/PulseAudioStream.cpp b/Source/Core/AudioCommon/PulseAudioStream.cpp index 8db4bdc224d9..a046519da42a 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.cpp +++ b/Source/Core/AudioCommon/PulseAudioStream.cpp @@ -130,10 +130,10 @@ bool PulseAudio::PulseInit() pa_stream_unref(m_pa_s); m_pa_s = nullptr; - if (m_stereo) + if (!m_stereo) { ERROR_LOG_FMT(AUDIO, "PulseAudio failed to initialize (6.0, falling back to 2.0): {}", pa_strerror(m_pa_error)); - m_stereo = false; + m_stereo = true; m_channels = m_stereo ? 2 : 6; ss.format = PA_SAMPLE_S16LE; From 1d52233a4379aaf829939b2af2da72318e2baf3c Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 8 Dec 2020 02:42:27 +0200 Subject: [PATCH 34/56] Fixed line over 100 characters --- Source/Core/AudioCommon/PulseAudioStream.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Core/AudioCommon/PulseAudioStream.cpp b/Source/Core/AudioCommon/PulseAudioStream.cpp index a046519da42a..e356fbedde87 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.cpp +++ b/Source/Core/AudioCommon/PulseAudioStream.cpp @@ -120,7 +120,7 @@ bool PulseAudio::PulseInit() m_pa_ba.tlength = BUFFER_SAMPLES * m_channels * m_bytespersample; // designed latency, only change this flag for low latency output - // TODO: review this, audio stretching and DPLII won't work correctly if the latency is dynamic + // TODO: review this, audio stretching and DPLII won't work correctly if latency is dynamic pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); m_pa_error = pa_stream_connect_playback(m_pa_s, nullptr, &m_pa_ba, flags, nullptr, nullptr); @@ -132,7 +132,8 @@ bool PulseAudio::PulseInit() if (!m_stereo) { - ERROR_LOG_FMT(AUDIO, "PulseAudio failed to initialize (6.0, falling back to 2.0): {}", pa_strerror(m_pa_error)); + ERROR_LOG_FMT(AUDIO, "PulseAudio failed to initialize (6.0, falling back to 2.0): {}", + pa_strerror(m_pa_error)); m_stereo = true; m_channels = m_stereo ? 2 : 6; From 1ec6aa7e0ea0e37ac0079b47ff741a7a8b3d4490 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 8 Dec 2020 23:09:39 +0200 Subject: [PATCH 35/56] Audio Streams fixes -DPLII support -Emulation pause and unpause fixes/support. -Memory leaks -Safety/cleaning up --- Source/Core/AudioCommon/AlsaSoundStream.cpp | 1 + Source/Core/AudioCommon/OpenALStream.cpp | 2 +- Source/Core/AudioCommon/OpenALStream.h | 3 +- Source/Core/AudioCommon/OpenSLESStream.cpp | 3 + Source/Core/AudioCommon/OpenSLESStream.h | 2 +- Source/Core/AudioCommon/PulseAudioStream.cpp | 85 +++++++++++--------- Source/Core/AudioCommon/PulseAudioStream.h | 13 +-- Source/Core/AudioCommon/WASAPIStream.cpp | 1 + 8 files changed, 63 insertions(+), 47 deletions(-) diff --git a/Source/Core/AudioCommon/AlsaSoundStream.cpp b/Source/Core/AudioCommon/AlsaSoundStream.cpp index 1e290858be74..97501b0fb9e5 100644 --- a/Source/Core/AudioCommon/AlsaSoundStream.cpp +++ b/Source/Core/AudioCommon/AlsaSoundStream.cpp @@ -34,6 +34,7 @@ bool AlsaSound::Init() m_thread_status.store(ALSAThreadStatus::PAUSED); if (!AlsaInit()) { + AlsaShutdown(); m_thread_status.store(ALSAThreadStatus::STOPPED); return false; } diff --git a/Source/Core/AudioCommon/OpenALStream.cpp b/Source/Core/AudioCommon/OpenALStream.cpp index 170d6710c78f..a5ef729d7d4c 100644 --- a/Source/Core/AudioCommon/OpenALStream.cpp +++ b/Source/Core/AudioCommon/OpenALStream.cpp @@ -223,7 +223,7 @@ static bool IsCreativeXFi() void OpenALStream::SoundLoop() { - Common::SetCurrentThreadName("Audio thread - openal"); + Common::SetCurrentThreadName("Audio thread - OpenAL"); bool float32_capable = palIsExtensionPresent("AL_EXT_float32"); // As there is no extension to check for 32-bit fixed point support diff --git a/Source/Core/AudioCommon/OpenALStream.h b/Source/Core/AudioCommon/OpenALStream.h index 2531ddc98296..271b5245e500 100644 --- a/Source/Core/AudioCommon/OpenALStream.h +++ b/Source/Core/AudioCommon/OpenALStream.h @@ -54,7 +54,7 @@ class OpenALStream final : public SoundStream { #ifdef _WIN32 public: - OpenALStream() : m_source(0) {} + OpenALStream() : m_source(0), m_use_surround(false) {} ~OpenALStream() override; bool Init() override; void SoundLoop() override; @@ -74,6 +74,5 @@ class OpenALStream final : public SoundStream ALuint m_source; ALfloat m_volume; bool m_use_surround; - #endif // _WIN32 }; diff --git a/Source/Core/AudioCommon/OpenSLESStream.cpp b/Source/Core/AudioCommon/OpenSLESStream.cpp index 4b77e6656b10..bc6075c82afd 100644 --- a/Source/Core/AudioCommon/OpenSLESStream.cpp +++ b/Source/Core/AudioCommon/OpenSLESStream.cpp @@ -136,6 +136,9 @@ OpenSLESStream::~OpenSLESStream() engineObject = nullptr; engineEngine = nullptr; } + + g_mixer = nullptr; + curBuffer = 0; } void OpenSLESStream::SetVolume(int volume) diff --git a/Source/Core/AudioCommon/OpenSLESStream.h b/Source/Core/AudioCommon/OpenSLESStream.h index c7877688aa0f..d2867fbc0a8f 100644 --- a/Source/Core/AudioCommon/OpenSLESStream.h +++ b/Source/Core/AudioCommon/OpenSLESStream.h @@ -15,7 +15,7 @@ class OpenSLESStream final : public SoundStream public: ~OpenSLESStream() override; bool Init() override; - bool SetRunning(bool running) override { return running; } + bool SetRunning(bool running) override { return true; } void SetVolume(int volume) override; static bool IsValid() { return true; } diff --git a/Source/Core/AudioCommon/PulseAudioStream.cpp b/Source/Core/AudioCommon/PulseAudioStream.cpp index e356fbedde87..e02c22e5dccc 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.cpp +++ b/Source/Core/AudioCommon/PulseAudioStream.cpp @@ -26,9 +26,24 @@ bool PulseAudio::Init() NOTICE_LOG_FMT(AUDIO, "PulseAudio backend using {} channels", m_channels); - m_run_thread.Set(); - m_thread = std::thread(&PulseAudio::SoundLoop, this); + if (PulseInit()) + { + m_run_thread.Set(); + m_thread = std::thread(&PulseAudio::SoundLoop, this); + return true; + } + else + { + PulseShutdown(); + } + return false; +} +bool PulseAudio::SetRunning(bool running) +{ + // Differently from other backends, we don't start or stop the stream here, + // we just play mute/zero samples if we are not running + m_running = running; return true; } @@ -41,18 +56,15 @@ PulseAudio::~PulseAudio() // Called on audio thread void PulseAudio::SoundLoop() { - Common::SetCurrentThreadName("Audio thread - pulse"); + Common::SetCurrentThreadName("Audio thread - Pulse"); - if (PulseInit()) - { - while (m_run_thread.IsSet() && m_pa_connected == 1 && m_pa_error >= 0) - m_pa_error = pa_mainloop_iterate(m_pa_ml, 1, nullptr); + while (m_run_thread.IsSet() && m_pa_connected == 1 && m_pa_error >= 0) + m_pa_error = pa_mainloop_iterate(m_pa_ml, 1, nullptr); - if (m_pa_error < 0) - ERROR_LOG_FMT(AUDIO, "PulseAudio error: {}", pa_strerror(m_pa_error)); + if (m_pa_error < 0) + ERROR_LOG_FMT(AUDIO, "PulseAudio error: {}", pa_strerror(m_pa_error)); - PulseShutdown(); - } + PulseShutdown(); } bool PulseAudio::PulseInit() @@ -60,7 +72,7 @@ bool PulseAudio::PulseInit() m_pa_error = 0; m_pa_connected = 0; - // create pulseaudio main loop and context + // create PulseAudio main loop and context // also register the async state callback which is called when the connection to the pa server has // changed m_pa_ml = pa_mainloop_new(); @@ -69,7 +81,7 @@ bool PulseAudio::PulseInit() m_pa_error = pa_context_connect(m_pa_ctx, nullptr, PA_CONTEXT_NOFLAGS, nullptr); pa_context_set_state_callback(m_pa_ctx, StateCallback, this); - // wait until we're connected to the pulseaudio server + // wait until we're connected to the PulseAudio server while (m_pa_connected == 0 && m_pa_error >= 0) m_pa_error = pa_mainloop_iterate(m_pa_ml, 1, nullptr); @@ -126,17 +138,18 @@ bool PulseAudio::PulseInit() m_pa_error = pa_stream_connect_playback(m_pa_s, nullptr, &m_pa_ba, flags, nullptr, nullptr); if (m_pa_error < 0) { - pa_stream_disconnect(m_pa_s); - pa_stream_unref(m_pa_s); - m_pa_s = nullptr; - + // Theoretically PulseAudio should not fail based on the number of channels (as it just remixes + // anyway), but we never know so we fallback to stereo if (!m_stereo) { ERROR_LOG_FMT(AUDIO, "PulseAudio failed to initialize (6.0, falling back to 2.0): {}", pa_strerror(m_pa_error)); m_stereo = true; - m_channels = m_stereo ? 2 : 6; + pa_stream_disconnect(m_pa_s); + pa_stream_unref(m_pa_s); + + m_channels = 2; ss.format = PA_SAMPLE_S16LE; m_bytespersample = sizeof(s16); ss.channels = m_channels; @@ -149,12 +162,6 @@ bool PulseAudio::PulseInit() pa_stream_set_underflow_callback(m_pa_s, UnderflowCallback, this); m_pa_error = pa_stream_connect_playback(m_pa_s, nullptr, &m_pa_ba, flags, nullptr, nullptr); - if (m_pa_error < 0) - { - pa_stream_disconnect(m_pa_s); - pa_stream_unref(m_pa_s); - m_pa_s = nullptr; - } } if (m_pa_error < 0) @@ -170,6 +177,12 @@ bool PulseAudio::PulseInit() void PulseAudio::PulseShutdown() { + if (m_pa_s) + { + pa_stream_disconnect(m_pa_s); + pa_stream_unref(m_pa_s); + m_pa_s = nullptr; + } pa_context_disconnect(m_pa_ctx); pa_context_unref(m_pa_ctx); pa_mainloop_free(m_pa_ml); @@ -195,14 +208,14 @@ void PulseAudio::StateCallback(pa_context* c) } } -// On underflow, increase pulseaudio latency in ~10ms steps +// On underflow, increase PulseAudio latency in ~10ms steps void PulseAudio::UnderflowCallback(pa_stream* s) { m_pa_ba.tlength += BUFFER_SAMPLES * m_channels * m_bytespersample; pa_operation* op = pa_stream_set_buffer_attr(s, &m_pa_ba, nullptr, nullptr); pa_operation_unref(op); - WARN_LOG_FMT(AUDIO, "pulseaudio underflow, new latency: {} bytes", m_pa_ba.tlength); + WARN_LOG_FMT(AUDIO, "PulseAudio underflow, new latency: {} bytes", m_pa_ba.tlength); } void PulseAudio::WriteCallback(pa_stream* s, size_t length) @@ -211,35 +224,31 @@ void PulseAudio::WriteCallback(pa_stream* s, size_t length) int frames = (length / bytes_per_frame); size_t trunc_length = frames * bytes_per_frame; - // fetch dst buffer directly from pulseaudio, so no memcpy is needed + // fetch dst buffer directly from PulseAudio, so no memcpy is needed void* buffer; m_pa_error = pa_stream_begin_write(s, &buffer, &trunc_length); if (!buffer || m_pa_error < 0) return; // error will be printed from main loop - if (m_stereo) - { - // use the raw s16 stereo mix - m_mixer->Mix((s16*)buffer, frames); - } - else + if (m_running) { - if (m_channels == 6) // Extract dpl2/5.1 Surround + if (m_stereo) { - m_mixer->MixSurround((float*)buffer, frames); + // use the raw s16 stereo mix + m_mixer->Mix((s16*)buffer, frames); } else { - ERROR_LOG_FMT(AUDIO, "Unsupported number of PA channels requested: {}", m_channels); - return; + // Extract dpl2/5.1 Surround + m_mixer->MixSurround((float*)buffer, frames); } } m_pa_error = pa_stream_write(s, buffer, trunc_length, nullptr, 0, PA_SEEK_RELATIVE); } -// Callbacks that forward to internal methods (required because PulseAudio is a C API). +// Callbacks that forward to internal methods (required because PulseAudio is a C API) void PulseAudio::StateCallback(pa_context* c, void* userdata) { diff --git a/Source/Core/AudioCommon/PulseAudioStream.h b/Source/Core/AudioCommon/PulseAudioStream.h index c05c198eb7f4..175c92e36824 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.h +++ b/Source/Core/AudioCommon/PulseAudioStream.h @@ -8,6 +8,8 @@ #include #endif +#include + #include "AudioCommon/SoundStream.h" #include "Common/CommonTypes.h" #include "Common/Flag.h" @@ -21,7 +23,7 @@ class PulseAudio final : public SoundStream ~PulseAudio() override; bool Init() override; - bool SetRunning(bool running) override { return running; } + bool SetRunning(bool running) override; bool IsSurroundEnabled() const override { return !m_stereo; } static bool IsValid() { return true; } void StateCallback(pa_context* c); @@ -41,6 +43,7 @@ class PulseAudio final : public SoundStream std::thread m_thread; Common::Flag m_run_thread; + std::atomic m_running = false; bool m_stereo; // stereo, else surround int m_bytespersample; @@ -48,10 +51,10 @@ class PulseAudio final : public SoundStream int m_pa_error; int m_pa_connected; - pa_mainloop* m_pa_ml; - pa_mainloop_api* m_pa_mlapi; - pa_context* m_pa_ctx; - pa_stream* m_pa_s; + pa_mainloop* m_pa_ml = nullptr; + pa_mainloop_api* m_pa_mlapi = nullptr; + pa_context* m_pa_ctx = nullptr; + pa_stream* m_pa_s = nullptr; pa_buffer_attr m_pa_ba; #endif }; diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 334f4e2c8104..b4785fe3301a 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -481,6 +481,7 @@ bool WASAPIStream::Init() result = m_enumerator->RegisterEndpointNotificationCallback(m_notification_client); if (FAILED(result)) { + // We can continue without this m_notification_client->Release(); m_notification_client = nullptr; } From f60017f80c904e7acb064f8503f40b377abb1730 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 9 Dec 2020 02:42:51 +0200 Subject: [PATCH 36/56] DPLII now automatically determines the best block size (latency) based on the backend latency Making the best compromise between quality and latency. Also added config/setting "DPL2PerformanceOverLatency" to prioritize performance over anything else. --- Source/Core/AudioCommon/AudioCommon.h | 2 +- Source/Core/AudioCommon/Mixer.cpp | 23 ++++- Source/Core/AudioCommon/Mixer.h | 3 +- Source/Core/AudioCommon/SurroundDecoder.cpp | 90 ++++++++++++++++--- Source/Core/AudioCommon/SurroundDecoder.h | 13 ++- Source/Core/Core/Config/MainSettings.cpp | 2 + Source/Core/Core/Config/MainSettings.h | 2 +- .../Core/ConfigLoaders/IsSettingSaveable.cpp | 3 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 15 ++-- 9 files changed, 120 insertions(+), 33 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.h b/Source/Core/AudioCommon/AudioCommon.h index a93175a950b4..d82883f1b4ff 100644 --- a/Source/Core/AudioCommon/AudioCommon.h +++ b/Source/Core/AudioCommon/AudioCommon.h @@ -40,7 +40,7 @@ unsigned long GetDefaultSampleRate(); // and DVD sample rate, but let's theorize the worse case (GC 48kHz mode: ~48043Hz). // Of course we shouldn't use anything above half of what this returns unsigned long GetMaxSupportedLatency(); -// Returns the user preferred backend latency. +// Returns the user preferred backend latency in ms. // Can return 0 and is clamped by GetMaxSupportedLatency() unsigned long GetUserTargetLatency(); // Returns the OS mixer sample rate (based on the currently used audio device) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 1a042f5170e1..983c62842079 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -10,6 +10,7 @@ #include #include +#include "AudioCommon/AudioCommon.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Swap.h" @@ -24,7 +25,7 @@ Mixer::Mixer(u32 sample_rate) : m_sample_rate(sample_rate), m_stretcher(sample_rate), - m_surround_decoder(sample_rate) + m_surround_decoder(sample_rate, AudioCommon::GetUserTargetLatency() * sample_rate / 1000) { m_scratch_buffer.reserve(MAX_SAMPLES * NC); m_dma_speed.Start(true); @@ -90,7 +91,11 @@ void Mixer::UpdateSettings(u32 sample_rate) else m_surround_decoder.Clear(); } - m_surround_decoder.InitAndSetSampleRate(m_sample_rate); + m_surround_decoder.Init(m_sample_rate); + // Latency might have change but we don't know the new value yet, wait for a Mix() call + // from the audio thread to update the surround decoder settings (in the meantime, + // it will just be guessed) + m_update_surround_latency = true; m_last_mix_time = Common::Timer::GetTimeUs(); } @@ -428,7 +433,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) // What we likely want to do is check the emulation speed difference between the target speed and the averaged emulation speed, // but then use the last frame emulation speed for more accuracy (and clamping it to the target speed) - static double FallbackDelta = 0.0; //0.005 or before 0.1 //To review and rename both + static double FallbackDelta = 0.0; //0.005 or before 0.1 //To review and rename both static double FallbackDelta2 = 0.001; // Only do if we are actually going slower, if we are going faster, it's likely to be a slight incorrection of the actual speed (because it's averaged). // Given that we ignore the frames where we went faster, overall we will consume less audio samples that what we have received, causing less padding. @@ -643,8 +648,18 @@ u32 Mixer::MixSurround(float* samples, u32 num_samples) // Our latency might have increased m_scratch_buffer.reserve(num_samples * NC); + // Update the surround decoder with the new latency every time there is a change + // or the first time. + // Latency might be dynamic so we don't want to keep adjusting the surround decoder + // latency optimizations, but we can't easily know the audio backend latency upfront + if (m_update_surround_latency && num_samples != 0) + { + m_update_surround_latency = false; + m_surround_decoder.Init(m_sample_rate, num_samples); + } + // TODO: we could have a special path here which mixes samples directly in float, given that the - // cubic interpolation spits out floats. + // cubic interpolation spits out floats // Time stretching can be applied before decoding 5.1, it should be fine theoretically. // Mix() may also use m_scratch_buffer internally, but is safe because we alternate reads diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 4f1473ed18fa..0d10a1ab475f 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -192,10 +192,11 @@ class Mixer final // Start at the most common sample rate and pushed samples num per batch AudioSpeedCounter m_dma_speed{0.425, 32000, 560}; - u32 m_sample_rate; // Only changed by main or emulation thread when the backend is not running + u32 m_sample_rate; // Only changed by main or emulation thread when the backend is not running bool m_stretching = false; std::atomic m_surround_changed{false}; bool m_was_surround = false; + bool m_update_surround_latency = false; AudioCommon::AudioStretcher m_stretcher; AudioCommon::SurroundDecoder m_surround_decoder; diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 5a480adcb184..90665ac4be5f 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -9,58 +9,120 @@ #include "AudioCommon/Enums.h" #include "AudioCommon/SurroundDecoder.h" +#include "Common/MathUtil.h" #include "Core/Config/MainSettings.h" #include "VideoCommon/OnScreenDisplay.h" #pragma optimize("", off) //To delete namespace AudioCommon { -// Quality (higher quality also means more latency). Needs to be a multiple of 2 -static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate) +static bool IsInteger(double value) { - u32 frame_block_time; // ms + return std::floor(value) == std::ceil(value); +} +// Quality (higher quality also means more latency). Needs to be a multiple of 2. +// We set a range for the quality in which we try to find a latency that is a multiple of our +// own mixer latency, to keep them in sync, which decreases the final latency, but also +// try to find a block size which is a power of 2 to improve performance. +static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate, + u32 block_size_aid_latency_in_samples) +{ + u32 frame_block_time_min, frame_block_time_max; // Ranges in ms switch (quality) { case DPL2Quality::Low: - frame_block_time = 10; + frame_block_time_min = 10; // Going lower than 10 would probably sound too bad + frame_block_time_max = 20; break; - // FreeSurround said to not go over 20ms, so this might be overkill already case DPL2Quality::High: - frame_block_time = 40; + frame_block_time_min = 40; + frame_block_time_max = 60; break; case DPL2Quality::Extreme: - frame_block_time = 80; + frame_block_time_min = 60; + frame_block_time_max = 100; break; case DPL2Quality::Normal: default: - frame_block_time = 20; + // FreeSurround said to not go over 20ms, so this might be overkill already but that's likely a + // mistake + frame_block_time_min = 20; + frame_block_time_max = 40; + } + + // Try to find a multiple or dividend of the backend latency to align them + double frame_block_time_average = (frame_block_time_min + frame_block_time_max) * 0.5; + double backend_latency = double(block_size_aid_latency_in_samples * 1000) / sample_rate; + double ratio = frame_block_time_average / backend_latency; + double ratio_inverted = backend_latency / frame_block_time_average; + bool is_multiple_or_dividend = IsInteger(ratio) || IsInteger(ratio_inverted); + + // if is_multiple_or_dividend, already accept frame_block_time_average + double latency_aligned_frame_block_time = + is_multiple_or_dividend ? frame_block_time_average : backend_latency; + while (latency_aligned_frame_block_time < frame_block_time_min) + { + latency_aligned_frame_block_time += backend_latency; + } + double dividend = 2.0; + while (latency_aligned_frame_block_time > frame_block_time_max) + { + latency_aligned_frame_block_time = backend_latency / dividend; + dividend *= 2.0; + if (latency_aligned_frame_block_time < frame_block_time_min) + { + // We really don't want to go over the min (or the max) so fallback to the pre-selected quality average + latency_aligned_frame_block_time = frame_block_time_average; + } } + + double frame_block_time = latency_aligned_frame_block_time; u32 frame_block = std::round(sample_rate * frame_block_time / 1000.0); - frame_block = (frame_block / 2) * 2; + + // If we are prioritizing performance over latency, try to make the block size a power of 2 + if (Config::Get(Config::MAIN_DPL2_PERFORMANCE_OVER_LATENCY)) + { + frame_block = MathUtil::NearestPowerOf2(frame_block); + } + + // Find the actual used frame_block_time to see if within the accepted ranges + frame_block_time = frame_block * 1000 / sample_rate; + if (frame_block_time > frame_block_time_max || frame_block_time < frame_block_time_min) + { + // Recalculate the new block size based on the middle point + frame_block = std::round(sample_rate * latency_aligned_frame_block_time / 1000.0); + frame_block = (frame_block / 2) * 2; + } // Assert because FreeSurround would crash anyway, this can't be triggered as of now assert(frame_block > 1); return frame_block; } -SurroundDecoder::SurroundDecoder(u32 sample_rate) +SurroundDecoder::SurroundDecoder(u32 sample_rate, u32 num_samples) { m_fsdecoder = std::make_unique(); - InitAndSetSampleRate(sample_rate); + Init(sample_rate, num_samples); } SurroundDecoder::~SurroundDecoder() = default; -void SurroundDecoder::InitAndSetSampleRate(u32 sample_rate) +void SurroundDecoder::Init(u32 sample_rate, u32 num_samples) { + // Guess the new block size aid latency if it hasn't been provided + double old_latency = double(m_block_size_aid_latency_in_samples) / m_sample_rate; + u32 new_latency_in_samples = sample_rate * old_latency; + u32 frame_block_size = - DPL2QualityToFrameBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), sample_rate); + DPL2QualityToFrameBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), sample_rate, + num_samples == 0 ? new_latency_in_samples : num_samples); // Re-init. It should keep the samples in the buffer (and filling the rest with 0) while just // updating the settings if (m_sample_rate != sample_rate || m_frame_block_size != frame_block_size) { - m_frame_block_size = frame_block_size; m_sample_rate = sample_rate; + m_block_size_aid_latency_in_samples = num_samples; + m_frame_block_size = frame_block_size; // If we passed in a block size that is a power of 2, decoding performance would be better, // (quality would be the same), though we've decided to prioritize low latency over performance, // so it's better to have a block size aligned (or being a multiple/dividend) of the backend diff --git a/Source/Core/AudioCommon/SurroundDecoder.h b/Source/Core/AudioCommon/SurroundDecoder.h index 38a9d0b5d2ba..ce2cf7446433 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.h +++ b/Source/Core/AudioCommon/SurroundDecoder.h @@ -17,9 +17,11 @@ namespace AudioCommon class SurroundDecoder { public: - explicit SurroundDecoder(u32 sample_rate); + // Asks for the mixer/backend sample rate and latency in samples (can be guessed for now) + explicit SurroundDecoder(u32 sample_rate, u32 num_samples); ~SurroundDecoder(); - void InitAndSetSampleRate(u32 sample_rate); + // Can be used to re-initialize as well. num_samples is the latency, can be left 0 to guess it + void Init(u32 sample_rate, u32 num_samples = 0); void PushSamples(const s16* in, u32 num_samples); void GetDecodedSamples(float* out, u32 num_samples); u32 ReturnSamples(s16* out, u32 num_samples, bool& has_finished); @@ -28,12 +30,15 @@ class SurroundDecoder static constexpr u32 STEREO_CHANNELS = 2; static constexpr u32 SURROUND_CHANNELS = 6; - // Max supported samples rate is about 192kHz at highest block quality (~80ms) - static constexpr u32 MAX_BLOCKS_SIZE = 15360; + // Max supported samples rate is about 192kHz at highest block quality (~100ms) + static constexpr u32 MAX_BLOCKS_SIZE = 19200; static constexpr u32 MAX_BLOCKS_BUFFERED = 8; private: u32 m_sample_rate = 0; + // Backend latency used to aid the block size calculation (to find the best compromize + // between quality, performance and latency) + u32 m_block_size_aid_latency_in_samples = 0; u32 m_frame_block_size = 0; std::unique_ptr m_fsdecoder; diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 7c255a635ea2..5b6911739c56 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -39,6 +39,8 @@ const Info MAIN_DPL2_DECODER{{System::Main, "Core", "DPL2Decoder"}, false} const Info MAIN_DPL2_QUALITY{{System::Main, "Core", "DPL2Quality"}, AudioCommon::GetDefaultDPL2Quality()}; const Info MAIN_DPL2_BASS_REDIRECTION{{System::Main, "Core", "DPL2BassRedirection"}, false}; +const Info MAIN_DPL2_PERFORMANCE_OVER_LATENCY{ + {System::Main, "Core", "DPL2PerformanceOverLatency"}, false}; const Info MAIN_AUDIO_BACKEND_LATENCY{{System::Main, "Core", "AudioBackendLatency"}, 20}; const Info MAIN_AUDIO_MIXER_MIN_LATENCY{{System::Main, "Core", "AudioMixerMinLatency"}, 0}; const Info MAIN_AUDIO_MIXER_MAX_LATENCY{{System::Main, "Core", "AudioMixerMaxLatency"}, 40}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 9e41a924630f..464e096cb7a6 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -40,7 +40,7 @@ extern const Info MAIN_OVERRIDE_REGION_SETTINGS; extern const Info MAIN_DPL2_DECODER; extern const Info MAIN_DPL2_QUALITY; extern const Info MAIN_DPL2_BASS_REDIRECTION; -extern const Info MAIN_AUDIO_BACKEND_LATENCY; +extern const Info MAIN_DPL2_PERFORMANCE_OVER_LATENCY; // Only set this different from zero in case your audio backend constantly changes the number of // samples it asks the mixer for extern const Info MAIN_AUDIO_MIXER_MIN_LATENCY; diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index 3bc100b87278..a033b3dfbc86 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -34,7 +34,7 @@ bool IsSettingSaveable(const Config::Location& config_location) } } - static constexpr std::array s_setting_saveable = { + static constexpr std::array s_setting_saveable = { // Main.Core &Config::MAIN_DEFAULT_ISO.GetLocation(), @@ -45,6 +45,7 @@ bool IsSettingSaveable(const Config::Location& config_location) &Config::MAIN_DPL2_DECODER.GetLocation(), &Config::MAIN_DPL2_QUALITY.GetLocation(), &Config::MAIN_DPL2_BASS_REDIRECTION.location, + &Config::MAIN_DPL2_PERFORMANCE_OVER_LATENCY.location, &Config::MAIN_AUDIO_MIXER_MIN_LATENCY.location, &Config::MAIN_AUDIO_MIXER_MAX_LATENCY.location, &Config::MAIN_RAM_OVERRIDE_ENABLE.GetLocation(), diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 8ca3a5773771..ac4b60d917d1 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -141,8 +141,7 @@ void AudioPane::CreateWidgets() "\nThe emulation will still output 2.0, but the encoder will extract information for 5.1." "\nSome backends will notify when failed to enable it," "\nwhile some other will just downmix it to stereo if not supported." - "\nIf you align your backend latency to the DPLII block size," - "\nthe added latency will be half the DPLII block size." + "\nIt will add a latency on top of the backend one." "\nIf unsure, leave off.")); if (m_latency_control_supported) @@ -237,7 +236,9 @@ void AudioPane::CreateWidgets() m_dolby_quality_slider->setPageStep(1); m_dolby_quality_slider->setTickPosition(QSlider::TicksBelow); m_dolby_quality_slider->setToolTip( - tr("Quality of the DPLII decoder. Also increases audio latency")); + tr("Quality of the DPLII decoder. Also increases audio latency.\nThe selected preset will be " + "used to find the best compromise between quality and latency.")); + m_dolby_quality_slider->setTracking(true); m_dolby_quality_latency_label = new QLabel(); @@ -789,14 +790,14 @@ QString AudioPane::GetDPL2QualityAndLatencyLabel(AudioCommon::DPL2Quality value) switch (value) { case AudioCommon::DPL2Quality::Low: - return tr("Low (Block Size: %1 ms)").arg(10); + return tr("Low (Block Size: ~%1 ms)").arg(10); case AudioCommon::DPL2Quality::High: - return tr("High (Block Size: %1 ms)").arg(40); + return tr("High (Block Size: ~%1 ms)").arg(50); case AudioCommon::DPL2Quality::Extreme: - return tr("Extreme (Block Size: %1 ms)").arg(80); + return tr("Extreme (Block Size: ~%1 ms)").arg(80); case AudioCommon::DPL2Quality::Normal: default: - return tr("Normal (Block Size: %1 ms)").arg(20); + return tr("Normal (Block Size: ~%1 ms)").arg(30); } } From 98b2cec859eeb23934e742aa7a169a20f5e9bfed Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 9 Dec 2020 23:36:34 +0200 Subject: [PATCH 37/56] WASAPI fixed audio device being saved by name It's not really "safe" as more than one device could have the same name. It's now saved by device unique ID (assigned by Windows). Also fixed a few memory leaks in WASAPI. --- Source/Core/AudioCommon/WASAPIStream.cpp | 181 ++++++++++++++----- Source/Core/AudioCommon/WASAPIStream.h | 5 +- Source/Core/Core/ConfigManager.cpp | 8 +- Source/Core/Core/ConfigManager.h | 3 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 100 ++++++---- Source/Core/DolphinQt/Settings/AudioPane.h | 1 + 6 files changed, 212 insertions(+), 86 deletions(-) diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index b4785fe3301a..e35df074d9e4 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -25,9 +25,6 @@ #define STEREO_CHANNELS 2 #define SURROUND_CHANNELS 6 -// Name that the config string we read from will have if it wants the default device/endpoint to be -// used. This is better than an empty string as some devices might theoretically not have a name -#define DEFAULT_DEVICE_NAME "default" // Helper to avoid having to call CoUninitialize() on multiple return cases class AutoCoInit @@ -197,7 +194,7 @@ WASAPIStream::~WASAPIStream() } } -std::vector WASAPIStream::GetAvailableDevices() +std::vector> WASAPIStream::GetAvailableDevices() { AutoCoInit aci; if (!aci.Succeeded()) @@ -212,7 +209,7 @@ std::vector WASAPIStream::GetAvailableDevices() if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) return {}; - std::vector device_names; + std::vector> device_ids_and_names; IMMDeviceCollection* devices; result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices); @@ -228,39 +225,51 @@ std::vector WASAPIStream::GetAvailableDevices() for (u32 i = 0; i < count; ++i) { IMMDevice* device; - devices->Item(i, &device); + result = devices->Item(i, &device); if (!HandleWinAPI("Failed to get device " + std::to_string(i), result)) continue; IPropertyStore* device_properties; - result = device->OpenPropertyStore(STGM_READ, &device_properties); if (!HandleWinAPI("Failed to get IPropertyStore", result)) + { + device->Release(); continue; + } PROPVARIANT device_name; PropVariantInit(&device_name); + result = device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); - device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); + if (!HandleWinAPI("Failed to get Device Name", result)) + { + device_properties->Release(); + device_properties = nullptr; + device->Release(); + continue; + } - device_names.push_back(TStrToUTF8(device_name.pwszVal)); + wchar_t* device_id = nullptr; + result = device->GetId(&device_id); - if (device_names.back() == DEFAULT_DEVICE_NAME) + if (HandleWinAPI("Failed to get Device ID", result)) { - WARN_LOG(AUDIO, "WASAPI: Device names called \"%s\" are not supported", DEFAULT_DEVICE_NAME); + device_ids_and_names.push_back( + std::make_pair(TStrToUTF8(device_id), TStrToUTF8(device_name.pwszVal))); + CoTaskMemFree(device_id); } PropVariantClear(&device_name); - device_properties->Release(); device_properties = nullptr; + device->Release(); } devices->Release(); enumerator->Release(); - return device_names; + return device_ids_and_names; } IMMDevice* WASAPIStream::GetDeviceByName(const std::string& name) @@ -284,49 +293,126 @@ IMMDevice* WASAPIStream::GetDeviceByName(const std::string& name) if (!HandleWinAPI("Failed to get available devices", result)) { enumerator->Release(); - return {}; + return nullptr; } + IMMDevice* found_device = nullptr; + UINT count; devices->GetCount(&count); for (u32 i = 0; i < count; ++i) { IMMDevice* device; - devices->Item(i, &device); + result = devices->Item(i, &device); if (!HandleWinAPI("Failed to get device " + std::to_string(i), result)) continue; IPropertyStore* device_properties; - result = device->OpenPropertyStore(STGM_READ, &device_properties); if (!HandleWinAPI("Failed to get IPropertyStore", result)) + { + device->Release(); continue; + } PROPVARIANT device_name; PropVariantInit(&device_name); + result = device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); - device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); - - device_properties->Release(); - device_properties = nullptr; + if (!HandleWinAPI("Failed to get Device Name", result)) + { + device_properties->Release(); + device_properties = nullptr; + device->Release(); + } if (TStrToUTF8(device_name.pwszVal) == name) { PropVariantClear(&device_name); - devices->Release(); - enumerator->Release(); - return device; + device_properties->Release(); + device_properties = nullptr; + found_device = device; + break; + } + else + { + PropVariantClear(&device_name); + device_properties->Release(); + device_properties = nullptr; + device->Release(); } + } - PropVariantClear(&device_name); + devices->Release(); + enumerator->Release(); + + return found_device; +} + +IMMDevice* WASAPIStream::GetDeviceByID(const std::string& id) +{ + AutoCoInit aci; + if (!aci.Succeeded()) + return nullptr; + + IMMDeviceEnumerator* enumerator = nullptr; + + HRESULT result = + CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)); + + if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) + return nullptr; + + IMMDeviceCollection* devices; + result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices); + + if (!HandleWinAPI("Failed to get available devices", result)) + { + enumerator->Release(); + return {}; + } + + IMMDevice* found_device = nullptr; + + UINT count; + devices->GetCount(&count); + + for (u32 i = 0; i < count; ++i) + { + IMMDevice* device; + result = devices->Item(i, &device); + if (!HandleWinAPI("Failed to get device " + std::to_string(i), result)) + continue; + + wchar_t* device_id = nullptr; + result = device->GetId(&device_id); + + if (!HandleWinAPI("Failed to get Device ID", result)) + { + device->Release(); + continue; + } + + if (TStrToUTF8(device_id) == id) + { + CoTaskMemFree(device_id); + found_device = device; + break; + } + else + { + CoTaskMemFree(device_id); + device->Release(); + } } devices->Release(); enumerator->Release(); - return nullptr; + return found_device; } std::vector WASAPIStream::GetSelectedDeviceSampleRates() @@ -348,7 +434,7 @@ std::vector WASAPIStream::GetSelectedDeviceSampleRates() if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) return device_sample_rates; - bool using_default_device = SConfig::GetInstance().sWASAPIDevice == DEFAULT_DEVICE_NAME; + bool using_default_device = SConfig::GetInstance().sWASAPIDeviceID.empty(); if (using_default_device) { @@ -356,14 +442,18 @@ std::vector WASAPIStream::GetSelectedDeviceSampleRates() } else { - device = GetDeviceByName(SConfig::GetInstance().sWASAPIDevice); + device = GetDeviceByID(SConfig::GetInstance().sWASAPIDeviceID); result = S_OK; if (!device) { - ERROR_LOG(AUDIO, "WASAPI: Can't find device '%s', falling back to default", - SConfig::GetInstance().sWASAPIDevice.c_str()); - result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + device = GetDeviceByName(SConfig::GetInstance().sWASAPIDeviceName); + if (!device) + { + ERROR_LOG(AUDIO, "WASAPI: Can't find device '%s', falling back to default", + SConfig::GetInstance().sWASAPIDeviceName.c_str()); + result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + } } } @@ -502,7 +592,7 @@ bool WASAPIStream::SetRunning(bool running) if (running) { - bool using_default_device = SConfig::GetInstance().sWASAPIDevice == DEFAULT_DEVICE_NAME; + bool using_default_device = SConfig::GetInstance().sWASAPIDeviceID.empty(); // Start from the current OS selected Mixer sample rate if we are the default device. This is // because the default device might not support Dolphin's default sample rate, thus WASAPI would // fail to start @@ -560,29 +650,34 @@ bool WASAPIStream::SetRunning(bool running) HRESULT result; - if (SConfig::GetInstance().sWASAPIDevice == DEFAULT_DEVICE_NAME) + if (SConfig::GetInstance().sWASAPIDeviceID.empty()) { result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); m_using_default_device = true; } else { - device = GetDeviceByName(SConfig::GetInstance().sWASAPIDevice); + device = GetDeviceByID(SConfig::GetInstance().sWASAPIDeviceID); result = S_OK; m_using_default_device = false; - // Whether this is the first time WASAPI starts running or not, - // we will fallback on the default device if the custom one is not found. - // This might happen if the device got removed (WASAPI would trigger m_should_restart). - // An alternative would be to NOT fallback on the default device if a custom one - // is specified, especially in case the specified one was previously available but then - // just got disconnected, but overall, this should be fine if (!device) { - ERROR_LOG(AUDIO, "WASAPI: Can't find device '%s', falling back to default", - SConfig::GetInstance().sWASAPIDevice.c_str()); - result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); - m_using_default_device = true; + device = GetDeviceByName(SConfig::GetInstance().sWASAPIDeviceName); + + // Whether this is the first time WASAPI starts running or not, + // we will fallback on the default device if the custom one is not found. + // This might happen if the device got removed (WASAPI would trigger m_should_restart). + // An alternative would be to NOT fallback on the default device if a custom one + // is specified, especially in case the specified one was previously available but then + // just got disconnected, but overall, this should be fine + if (!device) + { + ERROR_LOG(AUDIO, "WASAPI: Can't find device '%s', falling back to default", + SConfig::GetInstance().sWASAPIDeviceName.c_str()); + result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + m_using_default_device = true; + } } } diff --git a/Source/Core/AudioCommon/WASAPIStream.h b/Source/Core/AudioCommon/WASAPIStream.h index 37ecbb38f861..0b6619f5a46a 100644 --- a/Source/Core/AudioCommon/WASAPIStream.h +++ b/Source/Core/AudioCommon/WASAPIStream.h @@ -45,8 +45,11 @@ class WASAPIStream final : public SoundStream void OnSettingsChanged() override { m_should_restart = true; } static bool IsValid() { return true; } - static std::vector GetAvailableDevices(); + // Returns the IDs and Names of all the devices + static std::vector> GetAvailableDevices(); + // Returns the first device found with the (friendly) name we passed in static IMMDevice* GetDeviceByName(const std::string& name); + static IMMDevice* GetDeviceByID(const std::string& id); // Returns the user selected device supported sample rates at 16 bit and 2 channels, // so it ignores 24 bit or support for 5 channels. If we are starting up WASAPI with DPL2 on, // it will try these sample rates anyway, or fallback to the dolphin default one, diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 0836d0387623..432be40e9464 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -280,7 +280,8 @@ void SConfig::SaveDSPSettings(IniFile& ini) dsp->Set("CaptureLog", m_DSPCaptureLog); #ifdef _WIN32 - dsp->Set("WASAPIDevice", sWASAPIDevice); + dsp->Set("WASAPIDeviceName", sWASAPIDeviceName); + dsp->Set("WASAPIDeviceID", sWASAPIDeviceID); dsp->Set("WASAPIDeviceSampleRate", sWASAPIDeviceSampleRate); #endif } @@ -553,8 +554,9 @@ void SConfig::LoadDSPSettings(IniFile& ini) dsp->Get("CaptureLog", &m_DSPCaptureLog, false); #ifdef _WIN32 - dsp->Get("WASAPIDevice", &sWASAPIDevice, "default"); - dsp->Get("WASAPIDeviceSampleRate", &sWASAPIDeviceSampleRate, "0"); + dsp->Get("WASAPIDeviceName", &sWASAPIDeviceName, ""); + dsp->Get("WASAPIDeviceID", &sWASAPIDeviceID, ""); + dsp->Get("WASAPIDeviceSampleRate", &sWASAPIDeviceSampleRate, ""); #endif m_IsMuted = false; diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 0fcbabcb9059..5238706d46dd 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -308,7 +308,8 @@ struct SConfig #ifdef _WIN32 // WSAPI settings - std::string sWASAPIDevice; + std::string sWASAPIDeviceName; + std::string sWASAPIDeviceID; std::string sWASAPIDeviceSampleRate; #endif diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index ac4b60d917d1..bf1d9926cdc5 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -4,7 +4,6 @@ #include "DolphinQt/Settings/AudioPane.h" -#include #include #include @@ -25,6 +24,8 @@ #include "AudioCommon/Enums.h" #include "AudioCommon/WASAPIStream.h" +#include "Common/Logging/Log.h" + #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -32,11 +33,6 @@ #include "DolphinQt/Config/SettingsWindow.h" #include "DolphinQt/Settings.h" -#ifdef _WIN32 -#define WASAPI_DEFAULT_DEVICE_NAME "default" -#define WASAPI_INVALID_SAMPLE_RATE "0" -#endif - void ClickEventComboBox::showPopup() { if (m_audio_pane) @@ -485,20 +481,21 @@ void AudioPane::SaveSettings() tr("%1 ms").arg(m_emu_speed_tolerance_slider->value())); #ifdef _WIN32 - // If left at default, Dolphin will automatically - // pick a device and sample rate - std::string device = WASAPI_DEFAULT_DEVICE_NAME; + // If left at default, Dolphin will automatically pick a device and sample rate + std::string device_id = ""; + bool default_device = m_wasapi_device_combo->currentIndex() <= 0; - if (m_wasapi_device_combo->currentIndex() > 0) - device = m_wasapi_device_combo->currentText().toStdString(); + if (!default_device) + device_id = m_wasapi_devices_ids[m_wasapi_device_combo->currentIndex() - 1]; - bool device_changed = SConfig::GetInstance().sWASAPIDevice != device; + bool device_changed = SConfig::GetInstance().sWASAPIDeviceID != device_id; // Force OnWASAPIDeviceChanged() to be called if we have the default device to update some labels - if (device_changed || device == WASAPI_DEFAULT_DEVICE_NAME) + if (device_changed || default_device) { - assert(device != ""); //To turn into a log and delete the include WARN_LOG(AUDIO, "WASAPI: Device names called \"%s\" are not supported", DEFAULT_DEVICE_NAME); - SConfig::GetInstance().sWASAPIDevice = device; + SConfig::GetInstance().sWASAPIDeviceID = device_id; + SConfig::GetInstance().sWASAPIDeviceName = + default_device ? "" : m_wasapi_device_combo->currentText().toStdString(); bool is_wasapi = backend == BACKEND_WASAPI; if (is_wasapi) @@ -562,11 +559,15 @@ void AudioPane::OnBackendChanged() { m_ignore_save_settings = true; + m_wasapi_devices_ids.clear(); m_wasapi_device_combo->clear(); m_wasapi_device_combo->addItem(tr("Default Device")); for (const auto device : WASAPIStream::GetAvailableDevices()) - m_wasapi_device_combo->addItem(QString::fromStdString(device)); + { + m_wasapi_devices_ids.emplace_back(device.first); + m_wasapi_device_combo->addItem(QString::fromStdString(device.second)); + } OnWASAPIDeviceChanged(); @@ -588,8 +589,7 @@ void AudioPane::OnWASAPIDeviceChanged() // Don't allow users to select a sample rate for the default device, // even though that would be possible, the default device can change // at any time so it wouldn't make sense - bool can_select_device_sample_rate = - SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME; + bool can_select_device_sample_rate = !SConfig::GetInstance().sWASAPIDeviceID.empty(); if (can_select_device_sample_rate) { m_wasapi_device_sample_rate_combo->setEnabled(true); @@ -625,28 +625,50 @@ void AudioPane::OnWASAPIDeviceChanged() void AudioPane::LoadWASAPIDevice() { - if (SConfig::GetInstance().sWASAPIDevice == WASAPI_DEFAULT_DEVICE_NAME) + if (SConfig::GetInstance().sWASAPIDeviceID.empty() && + SConfig::GetInstance().sWASAPIDeviceName.empty()) { m_wasapi_device_combo->setCurrentIndex(0); } else { - QString device = QString::fromStdString(SConfig::GetInstance().sWASAPIDevice); - m_wasapi_device_combo->setCurrentText(device); - // Saved device not found, reset it (don't reset the saved sample rate) - if (m_wasapi_device_combo->currentText() != device) + bool try_with_name = true; + s32 i = 0; + std::vector::iterator it = std::find(m_wasapi_devices_ids.begin(), m_wasapi_devices_ids.end(), + SConfig::GetInstance().sWASAPIDeviceID); + if (it != m_wasapi_devices_ids.end()) + { + i = std::distance(m_wasapi_devices_ids.begin(), it) + 1; + } + if (i > 0) + { + try_with_name = false; + m_wasapi_device_combo->setCurrentIndex(i); + if (m_wasapi_device_combo->currentIndex() != i) + { + try_with_name = true; + } + } + + if (try_with_name) { - m_wasapi_device_combo->setCurrentIndex(0); - SConfig::GetInstance().sWASAPIDevice = WASAPI_DEFAULT_DEVICE_NAME; + QString device = QString::fromStdString(SConfig::GetInstance().sWASAPIDeviceName); + m_wasapi_device_combo->setCurrentText(device); + // Saved device not found, reset it (don't reset the saved sample rate) + if (m_wasapi_device_combo->currentText() != device) + { + m_wasapi_device_combo->setCurrentIndex(0); + SConfig::GetInstance().sWASAPIDeviceID = ""; + SConfig::GetInstance().sWASAPIDeviceName = ""; + } } } } void AudioPane::LoadWASAPIDeviceSampleRate() { - bool can_select_device_sample_rate = - SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME; - if (SConfig::GetInstance().sWASAPIDeviceSampleRate == WASAPI_INVALID_SAMPLE_RATE || + bool can_select_device_sample_rate = !SConfig::GetInstance().sWASAPIDeviceID.empty(); + if (SConfig::GetInstance().sWASAPIDeviceSampleRate.empty() || !can_select_device_sample_rate) { // Even if we had specified a custom device thay didn't support Dolphin's default sample rate, @@ -670,8 +692,7 @@ void AudioPane::LoadWASAPIDeviceSampleRate() std::string AudioPane::GetWASAPIDeviceSampleRate() const { - bool can_select_device_sample_rate = - SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME; + bool can_select_device_sample_rate = !SConfig::GetInstance().sWASAPIDeviceID.empty(); if ((!m_wasapi_device_supports_default_sample_rate || m_wasapi_device_sample_rate_combo->currentIndex() > 0) && can_select_device_sample_rate) @@ -680,7 +701,7 @@ std::string AudioPane::GetWASAPIDeviceSampleRate() const device_sample_rate.chop(tr(" Hz").length()); // Remove " Hz" from the end return device_sample_rate.toStdString(); } - return WASAPI_INVALID_SAMPLE_RATE; + return ""; } #endif @@ -723,8 +744,7 @@ void AudioPane::OnEmulationStateChanged(bool running) m_wasapi_device_label->setEnabled(supports_current_emulation_state); m_wasapi_device_combo->setEnabled(supports_current_emulation_state); bool can_select_device_sample_rate = - SConfig::GetInstance().sWASAPIDevice != WASAPI_DEFAULT_DEVICE_NAME && - supports_current_emulation_state; + !SConfig::GetInstance().sWASAPIDeviceID.empty() && supports_current_emulation_state; m_wasapi_device_sample_rate_label->setEnabled(can_select_device_sample_rate); m_wasapi_device_sample_rate_combo->setEnabled(can_select_device_sample_rate); bool is_wasapi = backend == BACKEND_WASAPI; @@ -754,22 +774,26 @@ void AudioPane::OnCustomShowPopup(QWidget* widget) { m_ignore_save_settings = true; + m_wasapi_devices_ids.clear(); m_wasapi_device_combo->clear(); m_wasapi_device_combo->addItem(tr("Default Device")); for (const auto device : WASAPIStream::GetAvailableDevices()) - m_wasapi_device_combo->addItem(QString::fromStdString(device)); + { + m_wasapi_devices_ids.emplace_back(device.first); + m_wasapi_device_combo->addItem(QString::fromStdString(device.second)); + } - std::string device = SConfig::GetInstance().sWASAPIDevice; + std::string device_id = SConfig::GetInstance().sWASAPIDeviceID; LoadWASAPIDevice(); m_ignore_save_settings = false; - if (device != SConfig::GetInstance().sWASAPIDevice) + if (device_id != SConfig::GetInstance().sWASAPIDeviceID) { - // Restore it so that saving will trigger OnWASAPIDeviceChanged() again - SConfig::GetInstance().sWASAPIDevice = device; + // Restore it so that SaveSettings() will trigger OnWASAPIDeviceChanged() + SConfig::GetInstance().sWASAPIDeviceID = device_id; SaveSettings(); } diff --git a/Source/Core/DolphinQt/Settings/AudioPane.h b/Source/Core/DolphinQt/Settings/AudioPane.h index dcdb5abfa55d..b4026464f412 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.h +++ b/Source/Core/DolphinQt/Settings/AudioPane.h @@ -95,6 +95,7 @@ class AudioPane final : public QWidget QLabel* m_wasapi_device_sample_rate_label; ClickEventComboBox* m_wasapi_device_combo; QComboBox* m_wasapi_device_sample_rate_combo; + std::vector m_wasapi_devices_ids; bool m_wasapi_device_supports_default_sample_rate; #endif From d0bc115dcd64e864502defbe99b193ba92f95846 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Thu, 10 Dec 2020 23:28:14 +0200 Subject: [PATCH 38/56] DPLII improvements -Improved code to auto determine best DPLII block size (it turned out to be mostly useless so I will get rid of it) -Small DPLII fixes --- Source/Core/AudioCommon/AudioStretcher.cpp | 2 +- Source/Core/AudioCommon/Mixer.cpp | 50 ++++++------ Source/Core/AudioCommon/Mixer.h | 2 +- Source/Core/AudioCommon/SurroundDecoder.cpp | 86 +++++++++++--------- Source/Core/AudioCommon/SurroundDecoder.h | 6 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 10 +-- 6 files changed, 85 insertions(+), 71 deletions(-) diff --git a/Source/Core/AudioCommon/AudioStretcher.cpp b/Source/Core/AudioCommon/AudioStretcher.cpp index 926c3cf4f011..49318dd103b2 100644 --- a/Source/Core/AudioCommon/AudioStretcher.cpp +++ b/Source/Core/AudioCommon/AudioStretcher.cpp @@ -94,7 +94,7 @@ void AudioStretcher::SetSampleRate(u32 sample_rate) m_sound_touch.setSampleRate(m_sample_rate); } -// Returns the samples of "processed" samples waiting to be read +// Returns the latency of "processed" samples waiting to be read double AudioStretcher::GetProcessedLatency() const { return m_sound_touch.numSamples() / double(m_sample_rate); diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 983c62842079..6459ffab86ac 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -83,16 +83,22 @@ void Mixer::UpdateSettings(u32 sample_rate) if (m_surround_changed) { m_surround_changed = false; - // The cases here deal with the fact whether it was on or off - if (m_surround_decoder.CanReturnSamples() && !m_was_surround) - m_was_surround = true; - else if (m_was_surround) - m_was_surround = false; + // The cases here deal with the fact whether it was on or off. + // Given that we play the remaining DPLII samples when turning it off, + // it's important to clear its buffer completely so that when turned on + // again, it will be still latency aligned with the backend + if (m_surround_decoder.CanReturnSamples() && !m_disabling_surround) + { + m_disabling_surround = true; + } else + { + m_disabling_surround = false; m_surround_decoder.Clear(); + } } m_surround_decoder.Init(m_sample_rate); - // Latency might have change but we don't know the new value yet, wait for a Mix() call + // Latency might have changed but we don't know the new value yet, wait for a Mix() call // from the audio thread to update the surround decoder settings (in the meantime, // it will just be guessed) m_update_surround_latency = true; @@ -385,6 +391,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) memset(samples, 0, num_samples * NC * sizeof(samples[0])); return 0; } + u32 original_num_samples = num_samples; bool stretching = SConfig::GetInstance().m_audio_stretch; @@ -415,7 +422,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) m_behind_target_speed = false; } } - // actual_speed varies by about 0.5% every audio frame so we need to go back and forth and then when we have lost enough, + // Actual_speed varies by about 0.5% every audio frame so we need to go back and forth and then when we have lost enough, // we will start using the average actual speed. To come back at full emulation speed, we check for how long we had been // running at full speed, we don't wait for m_time_behind_target_speed to come back to 0 because that will never happen, // what's lost is lost (though recovery could still happen if for some reason we had cycles imprecisions within a frame???) @@ -531,7 +538,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) m_stretching_latency_catching_up_direction); } - if (m_was_surround) + if (m_disabling_surround) { bool has_finished; // As for stretching below, this won't follow the new rate but is still better @@ -542,7 +549,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) if (has_finished) { - m_was_surround = false; + m_disabling_surround = false; m_surround_decoder.Clear(); } } @@ -628,30 +635,21 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) m_wiimote_speaker_mixer[3].Mix(samples, num_samples, false); } - return num_samples; + return original_num_samples; } u32 Mixer::MixSurround(float* samples, u32 num_samples) { memset(samples, 0, num_samples * SURROUND_CHANNELS * sizeof(samples[0])); - // TODO: some backends might ask for a constantly changing number of samples, - // possibly only the ones that do some kind of internal mixing (when your device - // doesn't actually support 5.1), like cubeb or PulseAudio, but it might - // only happen if they notice they failed to keep up sync. - // The simplest solution sounds like finding a way of noticing that and setting - // the max number of samples ever required as the minimum latency of the surround - // so that we'd never run out of samples (when we are asked for more than usual, - // we'd ignore the last samples away). Unfortunately there is no easy solution, - // being constrained to blocks and unable to stretch. - // Our latency might have increased m_scratch_buffer.reserve(num_samples * NC); - // Update the surround decoder with the new latency every time there is a change - // or the first time. - // Latency might be dynamic so we don't want to keep adjusting the surround decoder - // latency optimizations, but we can't easily know the audio backend latency upfront + // Update the surround decoder with the new latency every time there is a setting change + // (or the first time) as there is no easy way to know it upfront. + // Some backends might dynamically adjust latency when the thread fails to keep up, + // unfortunately that case would break DPLII latency sync but there isn't much we can do, + // if we keep adjusting it's block size, we'd hear cracking on the spot so... if (m_update_surround_latency && num_samples != 0) { m_update_surround_latency = false; @@ -666,6 +664,10 @@ u32 Mixer::MixSurround(float* samples, u32 num_samples) // and writes. u32 mixed_samples = Mix(m_scratch_buffer.data(), num_samples); + // To keep the backend latency aligned with the DPLII decoder latency, + // we push in samples even if they have padded silence at the end. The alternative would be to + // only pass in the actual samples processed by the emulation, which would avoid breaking the + // decoder state but it would break the latency alignment. m_surround_decoder.PushSamples(m_scratch_buffer.data(), mixed_samples); // Don't get any surround sample if the mixer return 0 as we are likely paused m_surround_decoder.GetDecodedSamples(samples, mixed_samples); diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 0d10a1ab475f..bb47d70bb5d1 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -195,7 +195,7 @@ class Mixer final u32 m_sample_rate; // Only changed by main or emulation thread when the backend is not running bool m_stretching = false; std::atomic m_surround_changed{false}; - bool m_was_surround = false; + bool m_disabling_surround = false; bool m_update_surround_latency = false; AudioCommon::AudioStretcher m_stretcher; AudioCommon::SurroundDecoder m_surround_decoder; diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 90665ac4be5f..87fb602ec081 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -12,7 +12,6 @@ #include "Common/MathUtil.h" #include "Core/Config/MainSettings.h" #include "VideoCommon/OnScreenDisplay.h" -#pragma optimize("", off) //To delete namespace AudioCommon { @@ -20,18 +19,22 @@ static bool IsInteger(double value) { return std::floor(value) == std::ceil(value); } -// Quality (higher quality also means more latency). Needs to be a multiple of 2. + +// Quality (higher quality also means more latency). // We set a range for the quality in which we try to find a latency that is a multiple of our -// own mixer latency, to keep them in sync, which decreases the final latency, but also -// try to find a block size which is a power of 2 to improve performance. +// own backend latency, to keep them in sync, which decreases the final latency (if they are aligned +// the added latency is half the block size), or prioritize performance and make it a pow of 2. static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate, u32 block_size_aid_latency_in_samples) { - u32 frame_block_time_min, frame_block_time_max; // Ranges in ms + u32 frame_block_time_min, frame_block_time_max; + // Ranges in ms. Make sure the average of min and max is an integer, + // it's better if they are even as well switch (quality) { case DPL2Quality::Low: - frame_block_time_min = 10; // Going lower than 10 would probably sound too bad + // This already sounds terrible, don't go any lower + frame_block_time_min = 6; frame_block_time_max = 20; break; case DPL2Quality::High: @@ -45,11 +48,12 @@ static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate, case DPL2Quality::Normal: default: // FreeSurround said to not go over 20ms, so this might be overkill already but that's likely a - // mistake + // mistake, the difference can be told frame_block_time_min = 20; frame_block_time_max = 40; } + //To review: this is all useless, added latency is always half of the DPLII block size... right? // Try to find a multiple or dividend of the backend latency to align them double frame_block_time_average = (frame_block_time_min + frame_block_time_max) * 0.5; double backend_latency = double(block_size_aid_latency_in_samples * 1000) / sample_rate; @@ -58,41 +62,47 @@ static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate, bool is_multiple_or_dividend = IsInteger(ratio) || IsInteger(ratio_inverted); // if is_multiple_or_dividend, already accept frame_block_time_average - double latency_aligned_frame_block_time = + double best_frame_block_time = is_multiple_or_dividend ? frame_block_time_average : backend_latency; - while (latency_aligned_frame_block_time < frame_block_time_min) + while (best_frame_block_time < frame_block_time_min) { - latency_aligned_frame_block_time += backend_latency; + best_frame_block_time += backend_latency; } - double dividend = 2.0; - while (latency_aligned_frame_block_time > frame_block_time_max) + while (best_frame_block_time > frame_block_time_max) { - latency_aligned_frame_block_time = backend_latency / dividend; - dividend *= 2.0; - if (latency_aligned_frame_block_time < frame_block_time_min) + // Theoretically even a DPLII latency of 3/4 the backend latency + // would improve syncing, but it's too complicated to put in + best_frame_block_time *= 0.5; + if (best_frame_block_time < frame_block_time_min) { - // We really don't want to go over the min (or the max) so fallback to the pre-selected quality average - latency_aligned_frame_block_time = frame_block_time_average; + // We really don't want to go over the min (or the max) so fallback to quality average + best_frame_block_time = frame_block_time_average; } } - double frame_block_time = latency_aligned_frame_block_time; - u32 frame_block = std::round(sample_rate * frame_block_time / 1000.0); + // If sample_rate*frame_block_time is not a multiple of 1000 or the result isn't a multiple of 2 + // (this needs to return a multiple of 2), we can't align the latencies so fallback to using pow 2 + double frame_block_double = sample_rate * best_frame_block_time / 1000.0; + u32 frame_block = std::round(frame_block_double); + bool use_power_of_2 = !IsInteger(frame_block_double) || (frame_block != ((frame_block / 2) * 2)); // If we are prioritizing performance over latency, try to make the block size a power of 2 - if (Config::Get(Config::MAIN_DPL2_PERFORMANCE_OVER_LATENCY)) + if (use_power_of_2 || Config::Get(Config::MAIN_DPL2_PERFORMANCE_OVER_LATENCY)) { + // Recalculate the new block size based on the preset middle point + frame_block = std::round(sample_rate * frame_block_time_average / 1000.0); frame_block = MathUtil::NearestPowerOf2(frame_block); - } - // Find the actual used frame_block_time to see if within the accepted ranges - frame_block_time = frame_block * 1000 / sample_rate; - if (frame_block_time > frame_block_time_max || frame_block_time < frame_block_time_min) - { - // Recalculate the new block size based on the middle point - frame_block = std::round(sample_rate * latency_aligned_frame_block_time / 1000.0); - frame_block = (frame_block / 2) * 2; + // Find the actual used frame_block_time to see if it's within the accepted ranges + double frame_block_time = frame_block * 1000 / sample_rate; + if (frame_block_time > frame_block_time_max || frame_block_time < frame_block_time_min) + { + frame_block = std::round(sample_rate * frame_block_time_average / 1000.0); + } } + + frame_block = (frame_block / 2) * 2; + // Assert because FreeSurround would crash anyway, this can't be triggered as of now assert(frame_block > 1); return frame_block; @@ -111,17 +121,18 @@ void SurroundDecoder::Init(u32 sample_rate, u32 num_samples) // Guess the new block size aid latency if it hasn't been provided double old_latency = double(m_block_size_aid_latency_in_samples) / m_sample_rate; u32 new_latency_in_samples = sample_rate * old_latency; + if (num_samples != 0) + new_latency_in_samples = num_samples; - u32 frame_block_size = - DPL2QualityToFrameBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), sample_rate, - num_samples == 0 ? new_latency_in_samples : num_samples); + u32 frame_block_size = DPL2QualityToFrameBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), + sample_rate, new_latency_in_samples); // Re-init. It should keep the samples in the buffer (and filling the rest with 0) while just // updating the settings if (m_sample_rate != sample_rate || m_frame_block_size != frame_block_size) { m_sample_rate = sample_rate; - m_block_size_aid_latency_in_samples = num_samples; + m_block_size_aid_latency_in_samples = new_latency_in_samples; m_frame_block_size = frame_block_size; // If we passed in a block size that is a power of 2, decoding performance would be better, // (quality would be the same), though we've decided to prioritize low latency over performance, @@ -149,7 +160,6 @@ void SurroundDecoder::Clear() m_float_conversion_buffer.clear(); } -// Receive and decode samples void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) { u32 read_samples = 0; @@ -187,7 +197,7 @@ void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) // TODO: modify FreeSurround to have the same channel mapping for performance (see channel_id) // Add to ring buffer and fix channel mapping. // If, at maxed out settings, your backend latency is 8 times higher than the block size, this - // will overwrite older samples. Avoid adding a check/warning for now as it's very unlikely + // will overwrite older samples. Avoid adding a check/warning for now as it's very unlikely. // FreeSurround: // FL | FC | FR | BL | BR | LFE // Most backends: @@ -243,17 +253,15 @@ u32 SurroundDecoder::ReturnSamples(s16* out, u32 num_samples, bool& has_finished { const u32 readable_samples = std::min(u32(m_float_conversion_buffer.size()), num_samples * STEREO_CHANNELS); - for (size_t i = 0, k = 0; i < readable_samples; ++i) + for (size_t i = 0; i < readable_samples; ++i) { - out[k] = + out[i] = s16(std::clamp(s32(m_float_conversion_buffer[i] * s32(-std::numeric_limits::min())), s32(std::numeric_limits::min()), s32(std::numeric_limits::max()))); - ++k; } m_float_conversion_buffer.erase(readable_samples); has_finished = (m_float_conversion_buffer.size() == 0); - // We could also return a part of the samples kept for future overlap in m_decoded_fifo - // and the already converted ones in m_decoded_fifo, but it's not worth it + // We could also return (and "unconvert") the samples within the m_fsdecoder but it's not worth it return readable_samples / STEREO_CHANNELS; } diff --git a/Source/Core/AudioCommon/SurroundDecoder.h b/Source/Core/AudioCommon/SurroundDecoder.h index ce2cf7446433..86a842e49335 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.h +++ b/Source/Core/AudioCommon/SurroundDecoder.h @@ -22,8 +22,11 @@ class SurroundDecoder ~SurroundDecoder(); // Can be used to re-initialize as well. num_samples is the latency, can be left 0 to guess it void Init(u32 sample_rate, u32 num_samples = 0); + // Receive and decode samples void PushSamples(const s16* in, u32 num_samples); void GetDecodedSamples(float* out, u32 num_samples); + // Gives us back some of the samples which are still being computed, to not miss samples when + // DPLII is disabled u32 ReturnSamples(s16* out, u32 num_samples, bool& has_finished); bool CanReturnSamples() const; void Clear(); @@ -42,7 +45,8 @@ class SurroundDecoder u32 m_frame_block_size = 0; std::unique_ptr m_fsdecoder; - FixedSizeQueue m_float_conversion_buffer; + FixedSizeQueue m_float_conversion_buffer; + // This can end up being quite big, we could make a ptr and only allocate it when DPLII is used FixedSizeQueue m_decoded_fifo; float m_last_decoded_samples[SURROUND_CHANNELS]; }; diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index bf1d9926cdc5..256af85802c4 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -24,8 +24,6 @@ #include "AudioCommon/Enums.h" #include "AudioCommon/WASAPIStream.h" -#include "Common/Logging/Log.h" - #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -207,7 +205,8 @@ void AudioPane::CreateWidgets() m_emu_speed_tolerance_indicator->setMinimumSize(min_size); m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); //To review: maybe have a set of 4 or 5 options to keep it simpler (low, high, ...). - //To add descrpition to m_emu_speed_tolerance_label explaining that it's the time the emulation need to be offsetted by to start using the current speed + //Also add descrpition to m_emu_speed_tolerance_label explaining that it's the time + //the emulation need to be offsetted by to start using the current speed m_stretching_enable->setToolTip(tr( "Enables stretching of the audio (pitch correction) to match the emulation speed.\nIt might " @@ -232,7 +231,8 @@ void AudioPane::CreateWidgets() m_dolby_quality_slider->setPageStep(1); m_dolby_quality_slider->setTickPosition(QSlider::TicksBelow); m_dolby_quality_slider->setToolTip( - tr("Quality of the DPLII decoder. Also increases audio latency.\nThe selected preset will be " + tr("Quality of the DPLII decoder. Also increases audio latency by about half the block " + "time.\nThe selected preset will be " "used to find the best compromise between quality and latency.")); m_dolby_quality_slider->setTracking(true); @@ -814,7 +814,7 @@ QString AudioPane::GetDPL2QualityAndLatencyLabel(AudioCommon::DPL2Quality value) switch (value) { case AudioCommon::DPL2Quality::Low: - return tr("Low (Block Size: ~%1 ms)").arg(10); + return tr("Low (Block Size: ~%1 ms)").arg(12); case AudioCommon::DPL2Quality::High: return tr("High (Block Size: ~%1 ms)").arg(50); case AudioCommon::DPL2Quality::Extreme: From dc9fa1551abcc0f1fd018b82a81190b12cad53fa Mon Sep 17 00:00:00 2001 From: Filoppi Date: Mon, 28 Dec 2020 23:54:14 +0200 Subject: [PATCH 39/56] Sound backends improvements -Uniquely defined all sound beckends in themselves, as a lot of their settings/capabilities were spread around the UI code. -Added UI information for when a backend has failed to start. -DPLII WIP (messy, full of debug stuff) --- .../FreeSurround/FreeSurroundDecoder.h | 24 +- .../source/FreeSurroundDecoder.cpp | 52 ++- Source/Core/AudioCommon/AlsaSoundStream.h | 6 +- Source/Core/AudioCommon/AudioCommon.cpp | 166 +++++--- Source/Core/AudioCommon/AudioCommon.h | 12 +- Source/Core/AudioCommon/AudioStretcher.cpp | 19 +- Source/Core/AudioCommon/CubebStream.cpp | 58 ++- Source/Core/AudioCommon/CubebStream.h | 10 +- Source/Core/AudioCommon/Enums.h | 8 + Source/Core/AudioCommon/Mixer.cpp | 88 ++-- Source/Core/AudioCommon/Mixer.h | 7 +- Source/Core/AudioCommon/NullSoundStream.h | 2 + Source/Core/AudioCommon/OpenALStream.cpp | 35 +- Source/Core/AudioCommon/OpenALStream.h | 12 +- Source/Core/AudioCommon/OpenSLESStream.h | 6 +- Source/Core/AudioCommon/PulseAudioStream.cpp | 22 +- Source/Core/AudioCommon/PulseAudioStream.h | 9 +- Source/Core/AudioCommon/SoundStream.h | 19 +- Source/Core/AudioCommon/SurroundDecoder.cpp | 391 +++++++++++------- Source/Core/AudioCommon/SurroundDecoder.h | 34 +- Source/Core/AudioCommon/WASAPIStream.cpp | 39 +- Source/Core/AudioCommon/WASAPIStream.h | 24 +- Source/Core/AudioCommon/WaveFile.cpp | 39 +- Source/Core/AudioCommon/WaveFile.h | 8 +- Source/Core/Common/FixedSizeQueue.h | 48 ++- Source/Core/Common/MathUtil.h | 20 + Source/Core/Core/Config/MainSettings.cpp | 2 - Source/Core/Core/Config/MainSettings.h | 1 - .../Core/ConfigLoaders/IsSettingSaveable.cpp | 3 +- Source/Core/Core/ConfigManager.cpp | 6 +- Source/Core/Core/ConfigManager.h | 11 +- .../Core/HW/WiimoteEmu/EmuSubroutines.cpp | 1 - Source/Core/Core/HW/WiimoteEmu/Speaker.cpp | 3 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 165 +++++--- 34 files changed, 848 insertions(+), 502 deletions(-) diff --git a/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h b/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h index 1186ffefa871..1c73590b87b0 100644 --- a/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h +++ b/Externals/FreeSurround/include/FreeSurround/FreeSurroundDecoder.h @@ -31,25 +31,26 @@ typedef std::complex cplx; // right). The ordering here also determines the ordering of interleaved // samples in the output signal. +// Dolphin: channels mapping modified to be the same as most audio backends typedef enum channel_id { ci_none = 0, ci_front_left = 1 << 1, - ci_front_center_left = 1 << 2, - ci_front_center = 1 << 3, - ci_front_center_right = 1 << 4, - ci_front_right = 1 << 5, + ci_front_right = 1 << 2, + ci_front_center_left = 1 << 3, + ci_front_center = 1 << 4, + ci_front_center_right = 1 << 5, ci_side_front_left = 1 << 6, ci_side_front_right = 1 << 7, ci_side_center_left = 1 << 8, ci_side_center_right = 1 << 9, ci_side_back_left = 1 << 10, ci_side_back_right = 1 << 11, - ci_back_left = 1 << 12, - ci_back_center_left = 1 << 13, - ci_back_center = 1 << 14, - ci_back_center_right = 1 << 15, - ci_back_right = 1 << 16, - ci_lfe = 1 << 31 + ci_lfe = 1 << 12, + ci_back_left = 1 << 13, + ci_back_center_left = 1 << 14, + ci_back_center = 1 << 15, + ci_back_center_right = 1 << 16, + ci_back_right = 1 << 31 } channel_id; // The supported output channel setups. A channel setup is defined by the set @@ -112,7 +113,8 @@ class DPL2FSDecoder { void set_bass_redirection(bool v); // number of samples currently held in the buffer - unsigned int buffered(); + unsigned int buffered() const; + float* input_buffer(); private: // constants diff --git a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp index 474a930b3da5..d07d763eb6c1 100644 --- a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp +++ b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp @@ -39,13 +39,33 @@ DPL2FSDecoder::~DPL2FSDecoder() { void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, unsigned int sample_rate) { + int prev_N = N; setup = chsetup; N = blsize; + bool N_changed = !initialized || C != prev_N; samplerate = sample_rate; // Initialize the parameters + int prev_C = C; + C = static_cast(chn_alloc[setup].size()); + bool C_changed = !initialized || C != prev_C; wnd = std::vector(N); - inbuf = std::vector(3 * N); + if (N_changed) + { + inbuf = std::vector(3 * N); + } + else + { + size_t prev_size = inbuf.size(); + inbuf.resize(3 * N); + // Pad the data in the first third of the input buffer + if (prev_size > 0 && prev_size < inbuf.size()) + { + //To finish + for (size_t i = prev_size; i < inbuf.size(); i += 2) + memcpy(&inbuf[i], &inbuf[prev_N - 2], sizeof(float) * 2); + } + } lt = std::vector(N); rt = std::vector(N); dst = std::vector(N); @@ -55,16 +75,26 @@ void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, delete[] inverse; forward = kiss_fftr_alloc(N, 0, 0, 0); inverse = kiss_fftr_alloc(N, 1, 0, 0); - C = static_cast(chn_alloc[setup].size()); // Allocate per-channel buffers (pad if we already have data) - float outputval = outbuf.size() > 0 ? outbuf.back() : 0.f; - outbuf.resize((N + N / 2) * C, outputval); - for (unsigned int k = 0; k < signal.size(); k++) + if (C_changed) { - signal[k].resize(N); + outbuf = std::vector((N + N / 2) * C); } - signal.resize(C, signal.size() > 0 ? signal.back() : std::vector(N)); + else + { + size_t prev_size = outbuf.size(); + outbuf.resize((N + N / 2) * C); + if (prev_size > 0) + { + //To review, probably not needed, the new output is never based on the old one, it's just to shift the memory quicker + for (size_t i = prev_size; i < outbuf.size(); i += C) + memcpy(&outbuf[i], &outbuf[prev_size - C], sizeof(float) * C); + } + } + for (unsigned int k = 0; k < std::min(C, unsigned int(signal.size())); k++) + signal[k].resize(N); + signal.resize(C, std::vector(N)); // Init the window function for (unsigned int k = 0; k < N; k++) @@ -99,7 +129,7 @@ float* DPL2FSDecoder::decode(const float* input_part_1, // process first and second half, overlapped buffered_decode(&inbuf[0]); buffered_decode(&inbuf[N]); - // shift the third half of the input to the beginning (for overlapping with + // shift the third part of the input to the beginning (for overlapping with // a future block) memcpy(&inbuf[0], &inbuf[2 * N], sizeof(float) * N); buffer_empty = false; @@ -116,7 +146,9 @@ void DPL2FSDecoder::flush() { } // number of samples currently held in the buffer -unsigned int DPL2FSDecoder::buffered() { return buffer_empty ? 0 : N / 2; } +unsigned int DPL2FSDecoder::buffered() const { return buffer_empty ? 0 : N / 2; } + +float* DPL2FSDecoder::input_buffer() { return &inbuf[0]; } // set soundfield & rendering parameters void DPL2FSDecoder::set_circular_wrap(float v) { circular_wrap = v; } @@ -242,7 +274,7 @@ void DPL2FSDecoder::buffered_decode(float *input) { } } - // shift the last 2/3 to the first 2/3 of the output buffer + // shift the last 2/3 (a block) to the first 2/3 of the output buffer memcpy(&outbuf[0], &outbuf[C * N / 2], N * C * sizeof(float)); // and clear the rest memset(&outbuf[C * N], 0, C * sizeof(float) * N / 2); diff --git a/Source/Core/AudioCommon/AlsaSoundStream.h b/Source/Core/AudioCommon/AlsaSoundStream.h index ecb7d3214458..126b8c7b0927 100644 --- a/Source/Core/AudioCommon/AlsaSoundStream.h +++ b/Source/Core/AudioCommon/AlsaSoundStream.h @@ -18,8 +18,10 @@ class AlsaSound final : public SoundStream { -#if defined(HAVE_ALSA) && HAVE_ALSA public: + static std::string GetName() { return "ALSA"; } +#if defined(HAVE_ALSA) && HAVE_ALSA + static bool IsValid() { return true; } AlsaSound(); ~AlsaSound() override; @@ -27,8 +29,6 @@ class AlsaSound final : public SoundStream void SoundLoop() override; bool SetRunning(bool running) override; - static bool IsValid() { return true; } - private: // maximum number of frames the buffer can hold static constexpr size_t BUFFER_SIZE_MAX = 8192; diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 0b2baab7da8f..b89b87f452b5 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -26,12 +26,13 @@ // This shouldn't be a global, at least not here. std::unique_ptr g_sound_stream; +bool g_selected_sound_stream_failed = false; std::mutex g_sound_stream_mutex; std::mutex g_sound_stream_running_mutex; namespace AudioCommon { -static bool s_audio_dump_start = false; +static bool s_audio_dump_started = false; static bool s_sound_stream_running = false; constexpr int AUDIO_VOLUME_MIN = 0; @@ -40,19 +41,19 @@ constexpr int AUDIO_VOLUME_MAX = 100; static std::unique_ptr CreateSoundStreamForBackend(std::string_view backend) { // We only check IsValid on backends that are only available on some platforms - if (backend == BACKEND_CUBEB) + if (backend == CubebStream::GetName() && CubebStream::IsValid()) return std::make_unique(); - else if (backend == BACKEND_OPENAL && OpenALStream::IsValid()) + else if (backend == OpenALStream::GetName() && OpenALStream::IsValid()) return std::make_unique(); - else if (backend == BACKEND_NULLSOUND) + else if (backend == NullSound::GetName()) // NullSound is always valid return std::make_unique(); - else if (backend == BACKEND_ALSA && AlsaSound::IsValid()) + else if (backend == AlsaSound::GetName() && AlsaSound::IsValid()) return std::make_unique(); - else if (backend == BACKEND_PULSEAUDIO && PulseAudio::IsValid()) + else if (backend == PulseAudio::GetName() && PulseAudio::IsValid()) return std::make_unique(); - else if (backend == BACKEND_OPENSLES && OpenSLESStream::IsValid()) + else if (backend == OpenSLESStream::GetName() && OpenSLESStream::IsValid()) return std::make_unique(); - else if (backend == BACKEND_WASAPI && WASAPIStream::IsValid()) + else if (backend == WASAPIStream::GetName() && WASAPIStream::IsValid()) return std::make_unique(); return {}; } @@ -82,33 +83,35 @@ void PostInitSoundStream() std::string backend = SConfig::GetInstance().sBackend; g_sound_stream = CreateSoundStreamForBackend(backend); + g_selected_sound_stream_failed = false; if (!g_sound_stream) { - WARN_LOG(AUDIO, "Unknown backend %s, using %s instead", backend.c_str(), + WARN_LOG(AUDIO, "Unknown backend %s, using %s instead (default)", backend.c_str(), GetDefaultSoundBackend().c_str()); backend = GetDefaultSoundBackend(); - g_sound_stream = CreateSoundStreamForBackend(GetDefaultSoundBackend()); + g_sound_stream = CreateSoundStreamForBackend(backend); } if (!g_sound_stream || !g_sound_stream->Init()) { WARN_LOG(AUDIO, "Could not initialize backend %s, using %s instead", backend.c_str(), - BACKEND_NULLSOUND); + NullSound::GetName().c_str()); g_sound_stream = std::make_unique(); - g_sound_stream->Init(); + g_sound_stream->Init(); // NullSound can't fail + g_selected_sound_stream_failed = true; } } UpdateSoundStreamSettings(true); // This can fail, but we don't really care as it just won't produce any sounds, - // also the user might be able to fix it up by changing his device settings + // also the user might be able to fix it up by changing their device settings // and pausing and unpausing the emulation. - // Note that when we start a game, this is called here, but then it's called - // with false and true again, so basically the backend is restarted - SetSoundStreamRunning(true); + // Note that when we start a game, this is called here, but then it's called with false + // and true again, so basically the backend is "restarted" with every emulation state change + SetSoundStreamRunning(true, true); - if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_start) + if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_started) StartAudioDump(); } @@ -116,13 +119,15 @@ void ShutdownSoundStream() { INFO_LOG_FMT(AUDIO, "Shutting down sound stream"); - if (SConfig::GetInstance().m_DumpAudio && s_audio_dump_start) + if (SConfig::GetInstance().m_DumpAudio && s_audio_dump_started) StopAudioDump(); - SetSoundStreamRunning(false); + SetSoundStreamRunning(false, true); { std::lock_guard guard(g_sound_stream_mutex); + g_selected_sound_stream_failed = false; g_sound_stream.reset(); + s_sound_stream_running = false; // Force this off in case the backend failed to stop } INFO_LOG_FMT(AUDIO, "Done shutting down sound stream"); @@ -130,18 +135,30 @@ void ShutdownSoundStream() std::string GetDefaultSoundBackend() { - std::string backend = BACKEND_NULLSOUND; + std::string backend = NullSound::GetName(); #if defined ANDROID - backend = BACKEND_OPENSLES; + backend = OpenSLESStream::GetName(); #elif defined __linux__ if (AlsaSound::IsValid()) - backend = BACKEND_ALSA; + backend = AlsaSound::GetName(); #elif defined(__APPLE__) || defined(_WIN32) - backend = BACKEND_CUBEB; + backend = CubebStream::GetName(); #endif return backend; } +std::string GetBackendName() +{ + std::lock_guard guard(g_sound_stream_mutex); + // The only case in which the started stream is different from the config one is when it failed + // to start and fell back to NullSound + if (g_selected_sound_stream_failed && g_sound_stream) + { + return NullSound::GetName(); + } + return SConfig::GetInstance().sBackend; +} + DPL2Quality GetDefaultDPL2Quality() { return DPL2Quality::Normal; @@ -151,53 +168,75 @@ std::vector GetSoundBackends() { std::vector backends; - backends.emplace_back(BACKEND_NULLSOUND); - backends.emplace_back(BACKEND_CUBEB); + backends.emplace_back(NullSound::GetName()); + if (CubebStream::IsValid()) + backends.emplace_back(CubebStream::GetName()); if (AlsaSound::IsValid()) - backends.emplace_back(BACKEND_ALSA); + backends.emplace_back(AlsaSound::GetName()); if (PulseAudio::IsValid()) - backends.emplace_back(BACKEND_PULSEAUDIO); + backends.emplace_back(PulseAudio::GetName()); if (OpenALStream::IsValid()) - backends.emplace_back(BACKEND_OPENAL); + backends.emplace_back(OpenALStream::GetName()); if (OpenSLESStream::IsValid()) - backends.emplace_back(BACKEND_OPENSLES); + backends.emplace_back(OpenSLESStream::GetName()); if (WASAPIStream::IsValid()) - backends.emplace_back(BACKEND_WASAPI); + backends.emplace_back(WASAPIStream::GetName()); return backends; } -bool SupportsDPL2Decoder(std::string_view backend) +bool SupportsSurround(std::string_view backend) { - if (backend == BACKEND_OPENAL) - return true; - if (backend == BACKEND_CUBEB) - return true; - if (backend == BACKEND_PULSEAUDIO) - return true; - if (backend == BACKEND_WASAPI) - return true; + if (backend == CubebStream::GetName()) + return CubebStream::SupportsSurround(); + if (backend == AlsaSound::GetName()) + return AlsaSound::SupportsSurround(); + if (backend == PulseAudio::GetName()) + return PulseAudio::SupportsSurround(); + if (backend == OpenALStream::GetName()) + return OpenALStream::SupportsSurround(); + if (backend == OpenSLESStream::GetName()) + return OpenSLESStream::SupportsSurround(); + if (backend == WASAPIStream::GetName()) + return WASAPIStream::SupportsSurround(); + return false; } bool SupportsLatencyControl(std::string_view backend) { - // TODO: we should ask the backends whether they support this -#ifdef _WIN32 - return backend == BACKEND_OPENAL || backend == BACKEND_WASAPI; -#else - // TODO: test whether cubeb supports latency on Mac OS X and Linux (different internal backends). - // It does NOT support latency on Win 10 (at least on my devices) despite exposing it and - // seemingly supporting it on WASAPI - return backend == BACKEND_CUBEB; -#endif + if (backend == CubebStream::GetName()) + return CubebStream::SupportsCustomLatency(); + if (backend == AlsaSound::GetName()) + return AlsaSound::SupportsCustomLatency(); + if (backend == PulseAudio::GetName()) + return PulseAudio::SupportsCustomLatency(); + if (backend == OpenALStream::GetName()) + return OpenALStream::SupportsCustomLatency(); + if (backend == OpenSLESStream::GetName()) + return OpenSLESStream::SupportsCustomLatency(); + if (backend == WASAPIStream::GetName()) + return WASAPIStream::SupportsCustomLatency(); + + return false; } bool SupportsVolumeChanges(std::string_view backend) { - // TODO: we should ask the backends whether they support this - return backend == BACKEND_CUBEB || backend == BACKEND_OPENAL || backend == BACKEND_WASAPI || - BACKEND_OPENSLES; + if (backend == CubebStream::GetName()) + return CubebStream::SupportsVolumeChanges(); + if (backend == AlsaSound::GetName()) + return AlsaSound::SupportsVolumeChanges(); + if (backend == PulseAudio::GetName()) + return PulseAudio::SupportsVolumeChanges(); + if (backend == OpenALStream::GetName()) + return OpenALStream::SupportsVolumeChanges(); + if (backend == OpenSLESStream::GetName()) + return OpenSLESStream::SupportsVolumeChanges(); + if (backend == WASAPIStream::GetName()) + return WASAPIStream::SupportsVolumeChanges(); + + return false; } bool BackendSupportsRuntimeSettingsChanges() @@ -210,14 +249,15 @@ bool BackendSupportsRuntimeSettingsChanges() return false; } -bool IsSurroundEnabled() +SurroundState GetSurroundState() { std::lock_guard guard(g_sound_stream_mutex); if (g_sound_stream) { - return g_sound_stream->IsSurroundEnabled(); + return g_sound_stream->GetSurroundState(); } - return SConfig::GetInstance().ShouldUseDPL2Decoder(); + return SConfig::GetInstance().ShouldUseDPL2Decoder() ? SurroundState::EnabledNotRunning : + SurroundState::Disabled; } unsigned long GetDefaultSampleRate() @@ -321,7 +361,7 @@ unsigned long GetOSMixerSampleRate() #endif } -void UpdateSoundStreamSettings(bool volume_changed, bool settings_changed, bool surround_changed) +void UpdateSoundStreamSettings(bool volume_changed, bool settings_changed) { // This can be called from any threads, needs to be protected std::lock_guard guard(g_sound_stream_mutex); @@ -332,11 +372,7 @@ void UpdateSoundStreamSettings(bool volume_changed, bool settings_changed, bool int volume = SConfig::GetInstance().m_IsMuted ? 0 : SConfig::GetInstance().m_Volume; g_sound_stream->SetVolume(volume); } - if (surround_changed) - { - g_sound_stream->GetMixer()->SetSurroundChanged(); - } - if (settings_changed || surround_changed) + if (settings_changed) { // Some backends will be able to apply changes in settings at runtime g_sound_stream->OnSettingsChanged(); @@ -348,7 +384,7 @@ bool SetSoundStreamRunning(bool running, bool send_error) { // This can be called by the main thread while a previous // SetRunning() might still be waiting to finish, so we need to protect it - // as most backends could even crash + // as most backends could even crash. No need to check g_sound_stream_mutex std::lock_guard guard(g_sound_stream_running_mutex); if (!g_sound_stream) @@ -379,9 +415,9 @@ void SendAIBuffer(const short* samples, unsigned int num_samples) if (!g_sound_stream) return; - if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_start) + if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_started) StartAudioDump(); - else if (!SConfig::GetInstance().m_DumpAudio && s_audio_dump_start) + else if (!SConfig::GetInstance().m_DumpAudio && s_audio_dump_started) StopAudioDump(); Mixer* pMixer = g_sound_stream->GetMixer(); @@ -405,7 +441,7 @@ void StartAudioDump() File::CreateFullPath(audio_file_name_dsp); g_sound_stream->GetMixer()->StartLogDTKAudio(audio_file_name_dtk); g_sound_stream->GetMixer()->StartLogDSPAudio(audio_file_name_dsp); - s_audio_dump_start = true; + s_audio_dump_started = true; } void StopAudioDump() @@ -414,7 +450,7 @@ void StopAudioDump() return; g_sound_stream->GetMixer()->StopLogDTKAudio(); g_sound_stream->GetMixer()->StopLogDSPAudio(); - s_audio_dump_start = false; + s_audio_dump_started = false; } void IncreaseVolume(unsigned short offset) diff --git a/Source/Core/AudioCommon/AudioCommon.h b/Source/Core/AudioCommon/AudioCommon.h index d82883f1b4ff..e42cc0149fca 100644 --- a/Source/Core/AudioCommon/AudioCommon.h +++ b/Source/Core/AudioCommon/AudioCommon.h @@ -17,6 +17,7 @@ class Mixer; extern std::unique_ptr g_sound_stream; +extern bool g_selected_sound_stream_failed; extern std::mutex g_sound_stream_mutex; extern std::mutex g_sound_stream_running_mutex; @@ -27,12 +28,13 @@ void PostInitSoundStream(); void ShutdownSoundStream(); std::string GetDefaultSoundBackend(); std::vector GetSoundBackends(); +std::string GetBackendName(); DPL2Quality GetDefaultDPL2Quality(); -bool SupportsDPL2Decoder(std::string_view backend); +bool SupportsSurround(std::string_view backend); bool SupportsLatencyControl(std::string_view backend); bool SupportsVolumeChanges(std::string_view backend); bool BackendSupportsRuntimeSettingsChanges(); -bool IsSurroundEnabled(); +SurroundState GetSurroundState(); // Default "Dolphin" output and internal mixer sample rate unsigned long GetDefaultSampleRate(); // Returns the min buffer time length it can hold (in ms), our backends can't have a latency @@ -46,10 +48,8 @@ unsigned long GetUserTargetLatency(); // Returns the OS mixer sample rate (based on the currently used audio device) unsigned long GetOSMixerSampleRate(); // Either volume only, or other settings that require a backend re-init (e.g. Latency, Device, -// Sample Rate, DPLII Enabled, DPLII Quality), or DPLII Enabled which also requires to clean the -// surround buffer -void UpdateSoundStreamSettings(bool volume_changed, bool settings_changed = false, - bool surround_changed = false); +// Sample Rate, DPLII Enabled, DPLII Quality) +void UpdateSoundStreamSettings(bool volume_changed, bool settings_changed = false); bool SetSoundStreamRunning(bool running, bool send_error = false); void SendAIBuffer(const short* samples, unsigned int num_samples); void StartAudioDump(); diff --git a/Source/Core/AudioCommon/AudioStretcher.cpp b/Source/Core/AudioCommon/AudioStretcher.cpp index 49318dd103b2..49de86c8d452 100644 --- a/Source/Core/AudioCommon/AudioStretcher.cpp +++ b/Source/Core/AudioCommon/AudioStretcher.cpp @@ -51,19 +51,18 @@ u32 AudioStretcher::GetStretchedSamples(s16* out, u32 num_out, bool pad) m_last_stretched_sample[1] = out[samples_received * 2 - 1]; } - if (!pad) + if (pad) { - return samples_received; + // Perform padding if we've run out of samples + for (u32 i = samples_received; i < num_out; ++i) + { + out[i * 2 + 0] = m_last_stretched_sample[0]; + out[i * 2 + 1] = m_last_stretched_sample[1]; + } } - // Perform padding if we've run out of samples - for (u32 i = samples_received; i < num_out; ++i) - { - out[i * 2 + 0] = m_last_stretched_sample[0]; - out[i * 2 + 1] = m_last_stretched_sample[1]; - } - - return num_out; + // Always return the valid mixed samples + return samples_received; } // Call this before ProcessSamples() diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index 711813961a25..a19654576ef5 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -74,22 +74,23 @@ bool CubebStream::CreateStream() if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) ERROR_LOG_FMT(AUDIO, "Error getting minimum latency"); -#ifdef _WIN32 - // Latency is ignored in Windows, despite being exposed (always 10ms, WASAPI max is 5000ms). - // However, it seems to somewhat be used when using its mixer (in case of up mixing - // or down mixing the n of channels) and audio thread runs too slow to keep up. - const u32 final_latency = minimum_latency; -#else - const u32 target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; - const u32 final_latency = std::clamp(target_latency, minimum_latency, 96000u); -#endif + u32 final_latency = minimum_latency; + if (SupportsCustomLatency()) + { + const u32 target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; + final_latency = std::clamp(target_latency, minimum_latency, 96000u); + } INFO_LOG(AUDIO, "Latency: %u frames", final_latency); - // Note that cubeb latency might not be fixed and could dynamically adjust, especially - // when using its mixer and the audio thread can't keep up with itself. - return cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, - nullptr, ¶ms, final_latency, DataCallback, StateCallback, - this) == CUBEB_OK; + // Note that cubeb latency might not be fixed and could dynamically adjust when the audio thread + // can't keep up with itself, especially when using its internal mixer. + if (cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, nullptr, + ¶ms, final_latency, DataCallback, StateCallback, this) == CUBEB_OK) + { + cubeb_stream_set_volume(m_stream, m_volume); + return true; + } + return false; } void CubebStream::DestroyStream() @@ -104,7 +105,7 @@ void CubebStream::DestroyStream() bool CubebStream::SetRunning(bool running) { - assert(running != m_running); + assert(running != m_running); // Can't happen, but if it did, it's not fully supported m_should_restart = false; if (m_settings_changed && running) @@ -169,7 +170,32 @@ void CubebStream::Update() } } +AudioCommon::SurroundState CubebStream::GetSurroundState() const +{ + if (m_stream) + { + return m_stereo ? AudioCommon::SurroundState::Disabled : AudioCommon::SurroundState::Enabled; + } + return SConfig::GetInstance().ShouldUseDPL2Decoder() ? + AudioCommon::SurroundState::EnabledNotRunning : + AudioCommon::SurroundState::Disabled; +} + +bool CubebStream::SupportsCustomLatency() +{ +#ifdef _WIN32 + // Latency is ignored in Windows, despite being exposed (always 10ms, WASAPI max is 5000ms). + // However, it seems to somewhat be used when using its internal mixer (in case of up mixing + // or down mixing the n of channels) and the audio thread runs too slow to keep up. + return false; +#else + // TODO: test whether cubeb supports latency on Mac OS X and Linux (different internal backends) + return true; +#endif +} + void CubebStream::SetVolume(int volume) { - cubeb_stream_set_volume(m_stream, volume / 100.0f); + m_volume = volume / 100.f; + cubeb_stream_set_volume(m_stream, m_volume); } diff --git a/Source/Core/AudioCommon/CubebStream.h b/Source/Core/AudioCommon/CubebStream.h index 3b2c048cc5a8..39d3e2cf1ec3 100644 --- a/Source/Core/AudioCommon/CubebStream.h +++ b/Source/Core/AudioCommon/CubebStream.h @@ -16,16 +16,21 @@ class CubebStream final : public SoundStream { public: + static std::string GetName() { return "Cubeb"; } + static bool IsValid() { return true; } ~CubebStream() override; bool Init() override; bool SetRunning(bool running) override; void Update() override; void SetVolume(int) override; + static bool SupportsSurround() { return true; } + static bool SupportsCustomLatency(); + static bool SupportsVolumeChanges() { return true; } bool SupportsRuntimeSettingsChanges() const override { return true; } // Cubeb will accept a 6.0 channels stream even if our device does not support it // by just downmixing it (thus making it partially pointless) - bool IsSurroundEnabled() const override { return !m_stereo; } + AudioCommon::SurroundState GetSurroundState() const override; void OnSettingsChanged() override { m_settings_changed = true; @@ -36,7 +41,8 @@ class CubebStream final : public SoundStream bool CreateStream(); void DestroyStream(); - bool m_stereo = false; + float m_volume = 1.f; + bool m_stereo = true; std::shared_ptr m_ctx; cubeb_stream* m_stream = nullptr; diff --git a/Source/Core/AudioCommon/Enums.h b/Source/Core/AudioCommon/Enums.h index 72705eb88c90..fcaf0862e89f 100644 --- a/Source/Core/AudioCommon/Enums.h +++ b/Source/Core/AudioCommon/Enums.h @@ -13,4 +13,12 @@ enum class DPL2Quality High = 2, Extreme = 3 }; + +enum class SurroundState +{ + Disabled, // Surround is off or unsupported by backend + EnabledNotRunning, // Backend not running, starting or paused + Failed, // Backend tried to activate it but failed + Enabled // Backend is running and has enabled surround successfully +}; } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 6459ffab86ac..ef9a655876af 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -10,7 +10,6 @@ #include #include -#include "AudioCommon/AudioCommon.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Swap.h" @@ -25,7 +24,7 @@ Mixer::Mixer(u32 sample_rate) : m_sample_rate(sample_rate), m_stretcher(sample_rate), - m_surround_decoder(sample_rate, AudioCommon::GetUserTargetLatency() * sample_rate / 1000) + m_surround_decoder(sample_rate) { m_scratch_buffer.reserve(MAX_SAMPLES * NC); m_dma_speed.Start(true); @@ -80,34 +79,13 @@ void Mixer::UpdateSettings(u32 sample_rate) // to not hear a different in pitch, but it's a minor thing m_sample_rate = sample_rate; m_stretcher.SetSampleRate(m_sample_rate); - if (m_surround_changed) - { - m_surround_changed = false; - // The cases here deal with the fact whether it was on or off. - // Given that we play the remaining DPLII samples when turning it off, - // it's important to clear its buffer completely so that when turned on - // again, it will be still latency aligned with the backend - if (m_surround_decoder.CanReturnSamples() && !m_disabling_surround) - { - m_disabling_surround = true; - } - else - { - m_disabling_surround = false; - m_surround_decoder.Clear(); - } - } m_surround_decoder.Init(m_sample_rate); - // Latency might have changed but we don't know the new value yet, wait for a Mix() call - // from the audio thread to update the surround decoder settings (in the meantime, - // it will just be guessed) - m_update_surround_latency = true; m_last_mix_time = Common::Timer::GetTimeUs(); } // -Render num_samples sample pairs to samples[] // -Advance indexR with by the amount read -// -Return the new number of samples mixed (not the ones played backwards) +// -Return the new number of samples mixed (not the ones played backwards nor padded) u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) { // Cache access in non-volatile variable @@ -379,7 +357,9 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r return i; } -u32 Mixer::Mix(s16* samples, u32 num_samples) +// Returns the number of samples that the backend should play. +// The samples array might not be zeroed +u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) { // We can't mix if the emulation is paused as m_dma_speed would return wrong speeds. // We should still update the stretcher with the current speed, but num_samples == 0 @@ -398,7 +378,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) double emulation_speed = SConfig::GetInstance().m_EmulationSpeed; bool frame_limiter = emulation_speed > 0.0 && !Core::GetIsThrottlerTempDisabled(); - // backend latency in seconds + // Backend latency in seconds double time_delta = double(num_samples) / m_sample_rate; m_backend_latency = time_delta; m_last_mix_time = Common::Timer::GetTimeUs(); @@ -489,6 +469,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) } } + //To do: add ability to skip/throw away samples if the latency is too big, instead of stretching it or keeping the delay in //To re-implement some mechanism for witch if the latency is a lot higher that the max, then it's played backwards faster, //this is especially needed now as our buffers are larger, though it can only happen if the audio thread has got some problems. //If you do the above, make sure you don't go over watch you are trying to reach by clamping the speed multiplication @@ -538,18 +519,18 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) m_stretching_latency_catching_up_direction); } - if (m_disabling_surround) + //To review: when this happens, we are gonna be forced to accelerate sounds as we have "surround decoder latency" of additional sounds + //in the mixer out of the blue and need to go back to standard latency + if (!surround && m_surround_decoder.CanReturnSamples()) { - bool has_finished; // As for stretching below, this won't follow the new rate but is still better // than losing samples when changing settings - u32 received_samples = m_surround_decoder.ReturnSamples(samples, num_samples, has_finished); + u32 received_samples = m_surround_decoder.ReturnSamples(samples, num_samples); num_samples -= received_samples; samples += received_samples * NC; - if (has_finished) + if (!m_surround_decoder.CanReturnSamples()) { - m_disabling_surround = false; m_surround_decoder.Clear(); } } @@ -627,12 +608,16 @@ u32 Mixer::Mix(s16* samples, u32 num_samples) m_stretching = false; } - m_dma_mixer.Mix(samples, num_samples, false); - m_streaming_mixer.Mix(samples, num_samples, false); + //To review this (it's for surround). It can actually cause desyncs and forever increase DPLII latency, but we could fix it from ther + u32 actual_mixed_samples = m_dma_mixer.Mix(samples, num_samples, false); + actual_mixed_samples = + std::max(actual_mixed_samples, m_streaming_mixer.Mix(samples, num_samples, false)); m_wiimote_speaker_mixer[0].Mix(samples, num_samples, false); m_wiimote_speaker_mixer[1].Mix(samples, num_samples, false); m_wiimote_speaker_mixer[2].Mix(samples, num_samples, false); m_wiimote_speaker_mixer[3].Mix(samples, num_samples, false); + // Note that if you don't return the padded samples in the count, some backends like OpenAL might automatically stretch the audio + //return actual_mixed_samples; } return original_num_samples; @@ -645,32 +630,39 @@ u32 Mixer::MixSurround(float* samples, u32 num_samples) // Our latency might have increased m_scratch_buffer.reserve(num_samples * NC); - // Update the surround decoder with the new latency every time there is a setting change - // (or the first time) as there is no easy way to know it upfront. - // Some backends might dynamically adjust latency when the thread fails to keep up, - // unfortunately that case would break DPLII latency sync but there isn't much we can do, - // if we keep adjusting it's block size, we'd hear cracking on the spot so... - if (m_update_surround_latency && num_samples != 0) - { - m_update_surround_latency = false; - m_surround_decoder.Init(m_sample_rate, num_samples); - } - // TODO: we could have a special path here which mixes samples directly in float, given that the // cubic interpolation spits out floats // Time stretching can be applied before decoding 5.1, it should be fine theoretically. // Mix() may also use m_scratch_buffer internally, but is safe because we alternate reads // and writes. - u32 mixed_samples = Mix(m_scratch_buffer.data(), num_samples); - + // Previously we used to immediately mix the block size of the surround decoder and then wait + // for it to be empty again before mixing, which worked but is troublesome for many reasons: + // - We want to try to be consistent in the number of samples we pass to the mix, as the stretching is delicate + // - We don't want to take more than we possibly have (or before we have them at the beginning), there is a nice balance between the in and out, if we add + // a surround block to the game, it desyncs everything, causing latency to go up and down + // - We don't want to write code to revert a mix that hasn't produced enough samples (unconsume the buffers), + // or to even ever reach that situation + // - We want to spread the computation among frames as much as possible + // - It simply wouldn't work well with dynamic backend latency, which is a thing. And it doesn't work well with dynamic settings, which can now be changed at runtime (like backend latency and DPLII block size). It would run out of samples in the first frame + // - It could possibly never start playing because + u32 mixed_samples = Mix(m_scratch_buffer.data(), num_samples, true); + + //To review: if we appened silent mix samples to the left of the buffer instead than on the right, especially the first time we start the emulation or we changed audio settings. + //With that though, The mixer would readjust itself by playing samples quicker or slower, which could be a pro or con depending on the case (https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/AudioCommon/SurroundDecoder.cpp). + //Also the DPLII output buffer might still get desyncronized and bigger (???). + //To review: when enabling surround you will get half a block size of silence, should we pass in the last padded value from the stereo mixer and replace it with that? + //To review: do we actually need to pad this? Isn't it better to let DPLII do the padding? Though we'd miss the play backwards system and the default padding. Review the comment. + //If we suddenly push less samples for a few updates, it would be ok, it will build up again later anyway, but we might lose the perfect latency alignment... + //Also if we don't pass in mixed_samples, when stopping you wouldn't be able to hear the last samples??? + //To review: could the 2 emulation mixers run out of sync (and get a lot desynced) with this (or with any other backend runtime setting change)? // To keep the backend latency aligned with the DPLII decoder latency, // we push in samples even if they have padded silence at the end. The alternative would be to // only pass in the actual samples processed by the emulation, which would avoid breaking the // decoder state but it would break the latency alignment. m_surround_decoder.PushSamples(m_scratch_buffer.data(), mixed_samples); - // Don't get any surround sample if the mixer return 0 as we are likely paused - m_surround_decoder.GetDecodedSamples(samples, mixed_samples); + // Don't get any surround sample if the Mix returned 0 as we are likely paused + m_surround_decoder.GetDecodedSamples(samples, mixed_samples == 0 ? 0 : num_samples); return num_samples; } diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index bb47d70bb5d1..7a82615f9a5f 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -27,7 +27,7 @@ class Mixer final void SetPaused(bool paused); // Called from audio threads: - u32 Mix(s16* samples, u32 num_samples); + u32 Mix(s16* samples, u32 num_samples, bool surround = false); u32 MixSurround(float* samples, u32 num_samples); // Called from main thread: @@ -49,8 +49,6 @@ class Mixer final // Only call when the audio thread (Mix()) is not running void UpdateSettings(u32 sample_rate); - // Useful to clean the surround buffer when we enable/disable it - void SetSurroundChanged() { m_surround_changed = true; } u32 GetSampleRate() const { return m_sample_rate; } double GetCurrentSpeed() const { return m_target_speed; } @@ -97,7 +95,6 @@ class Mixer final } void DoState(PointerWrap& p); void PushSamples(const s16* samples, u32 num_samples); - // Returns the actual mixed samples num, pads the rest with the last sample. // Executed from sound stream thread u32 Mix(s16* samples, u32 num_samples, bool stretching = false); void SetInputSampleRate(double sample_rate); @@ -194,8 +191,6 @@ class Mixer final u32 m_sample_rate; // Only changed by main or emulation thread when the backend is not running bool m_stretching = false; - std::atomic m_surround_changed{false}; - bool m_disabling_surround = false; bool m_update_surround_latency = false; AudioCommon::AudioStretcher m_stretcher; AudioCommon::SurroundDecoder m_surround_decoder; diff --git a/Source/Core/AudioCommon/NullSoundStream.h b/Source/Core/AudioCommon/NullSoundStream.h index 4161705c053d..420a715b8cc8 100644 --- a/Source/Core/AudioCommon/NullSoundStream.h +++ b/Source/Core/AudioCommon/NullSoundStream.h @@ -5,12 +5,14 @@ #pragma once #include "AudioCommon/SoundStream.h" +#include "Common/Common.h" // Sound mixer is still created and samples are still pushed to it, // but they aren't outputtted class NullSound final : public SoundStream { public: + static std::string GetName() { return _trans("No Audio Output"); } bool Init() override; bool SetRunning(bool running) override; }; diff --git a/Source/Core/AudioCommon/OpenALStream.cpp b/Source/Core/AudioCommon/OpenALStream.cpp index a5ef729d7d4c..2670b0b2fbd9 100644 --- a/Source/Core/AudioCommon/OpenALStream.cpp +++ b/Source/Core/AudioCommon/OpenALStream.cpp @@ -163,10 +163,6 @@ void OpenALStream::SetVolume(int volume) palSourcef(m_source, AL_GAIN, m_volume); } -void OpenALStream::Update() -{ -} - bool OpenALStream::SetRunning(bool running) { if (running) @@ -180,6 +176,24 @@ bool OpenALStream::SetRunning(bool running) return true; } +AudioCommon::SurroundState OpenALStream::GetSurroundState() const +{ + if (m_run_thread.IsSet()) + { + if (m_use_surround) + { + return AudioCommon::SurroundState::Enabled; + } + if (SConfig::GetInstance().ShouldUseDPL2Decoder()) + { + return AudioCommon::SurroundState::Failed; + } + } + return SConfig::GetInstance().ShouldUseDPL2Decoder() ? + AudioCommon::SurroundState::EnabledNotRunning : + AudioCommon::SurroundState::Disabled; +} + static ALenum CheckALError(const char* desc) { ALenum err = palGetError(); @@ -231,20 +245,14 @@ void OpenALStream::SoundLoop() // we just check if one is being used. bool fixed32_capable = IsCreativeXFi(); - // TODO: there is no reason for OpenAL to not have SupportsRuntimeSettingsChanges() as true - // as everything can be changed the loop below, though being deprecated I didn't bother u32 frequency = m_mixer->GetSampleRate(); // Can't have zero samples per buffer unsigned int target_latency = std::max(AudioCommon::GetUserTargetLatency(), 1ul); - u32 frames_per_buffer; - frames_per_buffer = frequency / 1000 * target_latency / OAL_BUFFERS; - + u32 frames_per_buffer = frequency / 1000 * target_latency / OAL_BUFFERS; if (frames_per_buffer > OAL_MAX_FRAMES) - { frames_per_buffer = OAL_MAX_FRAMES; - } INFO_LOG_FMT(AUDIO, "Using {} buffers, each with {} audio frames for a total of {}.", OAL_BUFFERS, frames_per_buffer, frames_per_buffer * OAL_BUFFERS); @@ -297,14 +305,14 @@ void OpenALStream::SoundLoop() num_buffers_queued -= num_buffers_processed; } - unsigned int min_frames = frames_per_buffer; + const unsigned int min_frames = frames_per_buffer; if (m_use_surround) { std::array dpl2; u32 rendered_frames = m_mixer->MixSurround(dpl2.data(), min_frames); - if (rendered_frames < min_frames) + if (!rendered_frames) continue; if (float32_capable) @@ -362,6 +370,7 @@ void OpenALStream::SoundLoop() { u32 rendered_frames = m_mixer->Mix(m_realtime_buffer.data(), min_frames); + // If we haven't mixed enough samples, it will stretch the sound if (!rendered_frames) continue; diff --git a/Source/Core/AudioCommon/OpenALStream.h b/Source/Core/AudioCommon/OpenALStream.h index 271b5245e500..ba45493b9bfd 100644 --- a/Source/Core/AudioCommon/OpenALStream.h +++ b/Source/Core/AudioCommon/OpenALStream.h @@ -7,6 +7,7 @@ #include #include "AudioCommon/SoundStream.h" +#include "Common/Common.h" #include "Common/Event.h" #include "Core/Core.h" #include "Core/HW/AudioInterface.h" @@ -52,18 +53,21 @@ class OpenALStream final : public SoundStream { -#ifdef _WIN32 public: + static std::string GetName() { return _trans("OpenAL (Deprecated)"); } + static bool IsValid(); +#ifdef _WIN32 OpenALStream() : m_source(0), m_use_surround(false) {} ~OpenALStream() override; bool Init() override; void SoundLoop() override; void SetVolume(int volume) override; bool SetRunning(bool running) override; - void Update() override; + static bool SupportsSurround() { return true; } + static bool SupportsCustomLatency() { return true; } + static bool SupportsVolumeChanges() { return true; } - static bool IsValid(); - bool IsSurroundEnabled() const override { return m_use_surround; } + AudioCommon::SurroundState GetSurroundState() const override; private: std::thread m_thread; diff --git a/Source/Core/AudioCommon/OpenSLESStream.h b/Source/Core/AudioCommon/OpenSLESStream.h index d2867fbc0a8f..d443d4f91b4c 100644 --- a/Source/Core/AudioCommon/OpenSLESStream.h +++ b/Source/Core/AudioCommon/OpenSLESStream.h @@ -11,13 +11,15 @@ class OpenSLESStream final : public SoundStream { -#ifdef ANDROID public: + static std::string GetName() { return "OpenSLES"; } +#ifdef ANDROID + static bool IsValid() { return true; } ~OpenSLESStream() override; bool Init() override; bool SetRunning(bool running) override { return true; } void SetVolume(int volume) override; - static bool IsValid() { return true; } + static bool SupportsVolumeChanges() { return true; } private: std::thread thread; diff --git a/Source/Core/AudioCommon/PulseAudioStream.cpp b/Source/Core/AudioCommon/PulseAudioStream.cpp index e02c22e5dccc..d1bd966174d4 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.cpp +++ b/Source/Core/AudioCommon/PulseAudioStream.cpp @@ -17,8 +17,6 @@ namespace const size_t BUFFER_SAMPLES = 512; // ~10 ms - needs to be at least 240 for surround } -PulseAudio::PulseAudio() = default; - bool PulseAudio::Init() { m_stereo = !SConfig::GetInstance().ShouldUseDPL2Decoder(); @@ -132,7 +130,7 @@ bool PulseAudio::PulseInit() m_pa_ba.tlength = BUFFER_SAMPLES * m_channels * m_bytespersample; // designed latency, only change this flag for low latency output - // TODO: review this, audio stretching and DPLII won't work correctly if latency is dynamic + // TODO: review this, audio stretching won't work correctly if latency is dynamic pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); m_pa_error = pa_stream_connect_playback(m_pa_s, nullptr, &m_pa_ba, flags, nullptr, nullptr); @@ -191,6 +189,24 @@ void PulseAudio::PulseShutdown() m_pa_ctx = nullptr; } +AudioCommon::SurroundState PulseAudio::GetSurroundState() const +{ + if (m_run_thread.IsSet() && m_pa_connected == 1 && m_pa_error >= 0) + { + if (!m_stereo) + { + return AudioCommon::SurroundState::Enabled; + } + if (SConfig::GetInstance().ShouldUseDPL2Decoder()) + { + return AudioCommon::SurroundState::Failed; + } + } + return SConfig::GetInstance().ShouldUseDPL2Decoder() ? + AudioCommon::SurroundState::EnabledNotRunning : + AudioCommon::SurroundState::Disabled; +} + void PulseAudio::StateCallback(pa_context* c) { pa_context_state_t state = pa_context_get_state(c); diff --git a/Source/Core/AudioCommon/PulseAudioStream.h b/Source/Core/AudioCommon/PulseAudioStream.h index 175c92e36824..3c2bf6c844c9 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.h +++ b/Source/Core/AudioCommon/PulseAudioStream.h @@ -17,15 +17,16 @@ class PulseAudio final : public SoundStream { -#if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO public: - PulseAudio(); + static std::string GetName() { return "Pulse"; } +#if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO + static bool IsValid() { return true; } ~PulseAudio() override; bool Init() override; bool SetRunning(bool running) override; - bool IsSurroundEnabled() const override { return !m_stereo; } - static bool IsValid() { return true; } + static bool SupportsSurround() { return true; } + AudioCommon::SurroundState GetSurroundState() const override; void StateCallback(pa_context* c); void WriteCallback(pa_stream* s, size_t length); void UnderflowCallback(pa_stream* s); diff --git a/Source/Core/AudioCommon/SoundStream.h b/Source/Core/AudioCommon/SoundStream.h index 3e0c9c440e68..5cbb2e48e853 100644 --- a/Source/Core/AudioCommon/SoundStream.h +++ b/Source/Core/AudioCommon/SoundStream.h @@ -7,12 +7,15 @@ #include #include "AudioCommon/Mixer.h" +#include "AudioCommon/Enums.h" #include "Common/CommonTypes.h" class Mixer; // Abstract class for different sound backends. -// Needs to be created and destroyed within the same thread +// Needs to be created and destroyed within the same thread. +// You generally want to redeclare IsValid() and GetName(). +// And optionally SupportsSurround(), SupportsCustomLatency() and SupportsVolumeChanges() class SoundStream { protected: @@ -21,17 +24,25 @@ class SoundStream public: SoundStream(); virtual ~SoundStream() {} + // Returns a friendly name for UI and configs + static std::string GetName() { return ""; } static bool IsValid() { return false; } + static bool SupportsSurround() { return false; } + static bool SupportsCustomLatency() { return false; } + static bool SupportsVolumeChanges() { return false; } Mixer* GetMixer() const { return m_mixer.get(); } virtual bool Init() { return false; } virtual void SetVolume(int) {} virtual void SoundLoop() {} virtual void Update() {} virtual bool SupportsRuntimeSettingsChanges() const { return false; } - // No need to implement this if SupportsRuntimeSettingsChanges() returns false - virtual bool IsSurroundEnabled() const { return false; } + // This is for UI so it doesn't need to be 100% up to date + virtual AudioCommon::SurroundState GetSurroundState() const + { + return AudioCommon::SurroundState::Disabled; + } virtual void OnSettingsChanged() {} // Can be called by the main thread or the emulator/video thread, - // never concurrently. Only call this through AudioCommons + // never concurrently. Only call this through AudioCommon virtual bool SetRunning(bool running) { return false; } }; diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 87fb602ec081..cf8ab91bebcc 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -12,142 +12,107 @@ #include "Common/MathUtil.h" #include "Core/Config/MainSettings.h" #include "VideoCommon/OnScreenDisplay.h" +#include "Common/Logging/Log.h" //To delete and all the uses (or make debug log) +#pragma optimize("", off) //To delete namespace AudioCommon { -static bool IsInteger(double value) +//To move... +// Quality (higher quality means more latency). +// Returns the number of samples the DPLII decoder will take in one push (counts one channels). +// Needs to be a multiple of DECODER_GRANULARITY, or a power of it for better performance. +static u32 DPL2QualityToBlockSize(DPL2Quality quality, u32 sample_rate) { - return std::floor(value) == std::ceil(value); -} - -// Quality (higher quality also means more latency). -// We set a range for the quality in which we try to find a latency that is a multiple of our -// own backend latency, to keep them in sync, which decreases the final latency (if they are aligned -// the added latency is half the block size), or prioritize performance and make it a pow of 2. -static u32 DPL2QualityToFrameBlockSize(DPL2Quality quality, u32 sample_rate, - u32 block_size_aid_latency_in_samples) -{ - u32 frame_block_time_min, frame_block_time_max; + u32 block_time_min, block_time_max; + //To expose quality as int? Also try to find out what latency does physical DPLII HW actually adds // Ranges in ms. Make sure the average of min and max is an integer, - // it's better if they are even as well + // it's better if they are even as well. + // FreeSurround said to keep it between 5 and 20ms, so this might be overkill + // already but that's likely a mistake, the difference can be told, though on a physical decoder, + // the added latency isn't as high? switch (quality) { case DPL2Quality::Low: // This already sounds terrible, don't go any lower - frame_block_time_min = 6; - frame_block_time_max = 20; + block_time_min = 6; + block_time_max = 20; + block_time_min = 10; //To delete + block_time_max = 10; //To delete break; case DPL2Quality::High: - frame_block_time_min = 40; - frame_block_time_max = 60; + block_time_min = 40; + block_time_max = 60; + //To delete + block_time_min = 30; + block_time_max = 30; break; case DPL2Quality::Extreme: - frame_block_time_min = 60; - frame_block_time_max = 100; + //To revert + block_time_min = 954; + block_time_max = 954; break; case DPL2Quality::Normal: default: - // FreeSurround said to not go over 20ms, so this might be overkill already but that's likely a - // mistake, the difference can be told - frame_block_time_min = 20; - frame_block_time_max = 40; + block_time_min = 20; + block_time_max = 40; + block_time_max = 20; //To delete } - //To review: this is all useless, added latency is always half of the DPLII block size... right? - // Try to find a multiple or dividend of the backend latency to align them - double frame_block_time_average = (frame_block_time_min + frame_block_time_max) * 0.5; - double backend_latency = double(block_size_aid_latency_in_samples * 1000) / sample_rate; - double ratio = frame_block_time_average / backend_latency; - double ratio_inverted = backend_latency / frame_block_time_average; - bool is_multiple_or_dividend = IsInteger(ratio) || IsInteger(ratio_inverted); - - // if is_multiple_or_dividend, already accept frame_block_time_average - double best_frame_block_time = - is_multiple_or_dividend ? frame_block_time_average : backend_latency; - while (best_frame_block_time < frame_block_time_min) - { - best_frame_block_time += backend_latency; - } - while (best_frame_block_time > frame_block_time_max) - { - // Theoretically even a DPLII latency of 3/4 the backend latency - // would improve syncing, but it's too complicated to put in - best_frame_block_time *= 0.5; - if (best_frame_block_time < frame_block_time_min) - { - // We really don't want to go over the min (or the max) so fallback to quality average - best_frame_block_time = frame_block_time_average; - } - } + double block_time_average = (block_time_min + block_time_max) * 0.5; - // If sample_rate*frame_block_time is not a multiple of 1000 or the result isn't a multiple of 2 - // (this needs to return a multiple of 2), we can't align the latencies so fallback to using pow 2 - double frame_block_double = sample_rate * best_frame_block_time / 1000.0; - u32 frame_block = std::round(frame_block_double); - bool use_power_of_2 = !IsInteger(frame_block_double) || (frame_block != ((frame_block / 2) * 2)); + u32 block_size = std::round(sample_rate * block_time_average / 1000.0); + //block_size = MathUtil::NearestPowerOf2(block_size); + //To delete and restore above + block_size = + (block_size / SurroundDecoder::DECODER_GRANULARITY) * SurroundDecoder::DECODER_GRANULARITY; - // If we are prioritizing performance over latency, try to make the block size a power of 2 - if (use_power_of_2 || Config::Get(Config::MAIN_DPL2_PERFORMANCE_OVER_LATENCY)) + // Find the actual used block_time to see if it's within the accepted ranges + double block_time = block_size * 1000 / sample_rate; + if (block_time > block_time_max || block_time < block_time_min) { - // Recalculate the new block size based on the preset middle point - frame_block = std::round(sample_rate * frame_block_time_average / 1000.0); - frame_block = MathUtil::NearestPowerOf2(frame_block); - - // Find the actual used frame_block_time to see if it's within the accepted ranges - double frame_block_time = frame_block * 1000 / sample_rate; - if (frame_block_time > frame_block_time_max || frame_block_time < frame_block_time_min) - { - frame_block = std::round(sample_rate * frame_block_time_average / 1000.0); - } + block_size = std::round(sample_rate * block_time_average / 1000.0); + block_size = + (block_size / SurroundDecoder::DECODER_GRANULARITY) * SurroundDecoder::DECODER_GRANULARITY; } - frame_block = (frame_block / 2) * 2; + // Assert because FreeSurround wouldn't work or crash anyway, this can't be triggered as of now + assert(block_size > 1 && block_size <= SurroundDecoder::MAX_BLOCKS_SIZE); + return block_size; +} - // Assert because FreeSurround would crash anyway, this can't be triggered as of now - assert(frame_block > 1); - return frame_block; +//To move to AudioCommon or AudioInterface? +static inline s16 convert_float_to_s16(float sample) +{ + return s16(std::clamp(s32(sample * -std::numeric_limits::min()), + s32(std::numeric_limits::min()), + s32(std::numeric_limits::max()))); } -SurroundDecoder::SurroundDecoder(u32 sample_rate, u32 num_samples) +SurroundDecoder::SurroundDecoder(u32 sample_rate) { m_fsdecoder = std::make_unique(); - Init(sample_rate, num_samples); + Init(sample_rate); } SurroundDecoder::~SurroundDecoder() = default; -void SurroundDecoder::Init(u32 sample_rate, u32 num_samples) +void SurroundDecoder::Init(u32 sample_rate) { - // Guess the new block size aid latency if it hasn't been provided - double old_latency = double(m_block_size_aid_latency_in_samples) / m_sample_rate; - u32 new_latency_in_samples = sample_rate * old_latency; - if (num_samples != 0) - new_latency_in_samples = num_samples; - - u32 frame_block_size = DPL2QualityToFrameBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), - sample_rate, new_latency_in_samples); + u32 block_size = DPL2QualityToBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), sample_rate); // Re-init. It should keep the samples in the buffer (and filling the rest with 0) while just - // updating the settings - if (m_sample_rate != sample_rate || m_frame_block_size != frame_block_size) + // updating the settings. Variables are duplicate from the decoder because they are not exposed + if (m_sample_rate != sample_rate || m_block_size != block_size) { m_sample_rate = sample_rate; - m_block_size_aid_latency_in_samples = new_latency_in_samples; - m_frame_block_size = frame_block_size; - // If we passed in a block size that is a power of 2, decoding performance would be better, - // (quality would be the same), though we've decided to prioritize low latency over performance, - // so it's better to have a block size aligned (or being a multiple/dividend) of the backend - // latency. We could write some code and UI setting to align the backend latency to the DPLII - // one but it wouldn't be straight forward as some backends don't support custom latency. - // If this turned out to be too intensive on slower CPUs, we could reconsider it. - m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate); - - // Increase m_decoded_fifo size if this becomes common occurrence - // (the warning is sent with a very large safety margin) - if (m_frame_block_size * SURROUND_CHANNELS * MAX_BLOCKS_BUFFERED > m_decoded_fifo.max_size()) - OSD::AddMessage("This DPLII Quality at this Sample Rate is not supported.\nLower your " - "settings to not risk missing samples", 6000U); + m_block_size = block_size; + m_returnable_samples_in_decoder = std::min( + m_returnable_samples_in_decoder, m_block_size / DECODER_GRANULARITY * STEREO_CHANNELS); + latency_warning_sent = false; + m_fsdecoder->Init(cs_5point1, m_block_size, m_sample_rate); } + //To delete + Clear(); // The LFE channel (bass redirection) is disabled in the surround decoder, as most people // have their own low pass crossover m_fsdecoder->set_bass_redirection(Config::Get(Config::MAIN_DPL2_BASS_REDIRECTION)); @@ -158,26 +123,37 @@ void SurroundDecoder::Clear() m_fsdecoder->flush(); m_decoded_fifo.clear(); m_float_conversion_buffer.clear(); + m_returnable_samples_in_decoder = 0; + latency_warning_sent = false; + memset(m_last_decoded_samples, 0.f, sizeof(m_last_decoded_samples)); } void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) { + if (!latency_warning_sent && num_samples > m_decoded_fifo.max_size() / SURROUND_CHANNELS) + { + // Increase m_decoded_fifo size if this becomes common occurrence + latency_warning_sent = true; + OSD::AddMessage("This DPLII Quality at this Sample Rate/Latency is not supported.\nLower your " + "settings to not risk missing samples", + 6000U); + } + u32 read_samples = 0; - // We do it this way so m_float_conversion_buffer never exceeds m_frame_block_size + // We do it this way so m_float_conversion_buffer never exceeds m_block_size // and we can be sure we won't miss samples on that front while (num_samples > 0) { u32 samples_to_block = 0; - if (u32(m_float_conversion_buffer.size() / STEREO_CHANNELS) < m_frame_block_size) + if (u32(m_float_conversion_buffer.size() / STEREO_CHANNELS) < m_block_size) //To delete condition if we resize m_float_conversion_buffer { - samples_to_block = - m_frame_block_size - (u32(m_float_conversion_buffer.size() / STEREO_CHANNELS)); + samples_to_block = m_block_size - (u32(m_float_conversion_buffer.size() / STEREO_CHANNELS)); } u32 samples_to_push = std::min(samples_to_block, num_samples); // Convert to float (samples are from SHRT_MIN to SHRT_MAX) for (size_t i = read_samples * STEREO_CHANNELS; - i < (samples_to_push * STEREO_CHANNELS) + (read_samples * STEREO_CHANNELS); ++i) + i < (samples_to_push + read_samples) * STEREO_CHANNELS; ++i) { m_float_conversion_buffer.push(in[i] / float(-std::numeric_limits::min())); } @@ -185,63 +161,145 @@ void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) num_samples -= samples_to_push; // We have enough samples to process a block - if (m_float_conversion_buffer.size() >= m_frame_block_size * STEREO_CHANNELS) + if (m_float_conversion_buffer.size() >= m_block_size * STEREO_CHANNELS) { // Decode. Note that output isn't clamped within -1.f and +1.f. // Will return half silent (~0) samples for the first call - const float* dpl2_fs = m_fsdecoder->decode( + const float* out = m_fsdecoder->decode( &m_float_conversion_buffer.front(), &m_float_conversion_buffer.beginning(), - u32(m_float_conversion_buffer.size_to_end() / STEREO_CHANNELS)); - m_float_conversion_buffer.erase(m_frame_block_size * STEREO_CHANNELS); - - // TODO: modify FreeSurround to have the same channel mapping for performance (see channel_id) - // Add to ring buffer and fix channel mapping. - // If, at maxed out settings, your backend latency is 8 times higher than the block size, this - // will overwrite older samples. Avoid adding a check/warning for now as it's very unlikely. - // FreeSurround: - // FL | FC | FR | BL | BR | LFE - // Most backends: - // FL | FR | FC | LFE | BL | BR - for (size_t i = 0; i < m_frame_block_size; ++i) - { - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 0]); // LEFTFRONT - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 2]); // RIGHTFRONT - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 1]); // CENTREFRONT - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 5]); // LFE/SUB - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 3]); // LEFTREAR - m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 4]); // RIGHTREAR - } + u32(m_float_conversion_buffer.head_to_end() / STEREO_CHANNELS)); + m_float_conversion_buffer_copy = std::vector(m_float_conversion_buffer.size()); + m_float_conversion_buffer.copy_to_array(&m_float_conversion_buffer_copy[0], + m_float_conversion_buffer.size()); + m_float_conversion_buffer.erase(m_block_size * STEREO_CHANNELS); + + //To review: quality is trash? Is there a mapping problem? Compare with vanilla dolphin (Backends: FL | FR | FC | LFE | BL | BR) + // FreeSurround mapping has been modified to match + m_decoded_fifo.push_array(out, m_block_size * SURROUND_CHANNELS); + + m_returnable_samples_in_decoder = m_block_size / DECODER_GRANULARITY * STEREO_CHANNELS; } } } +//To review: sometimes when enabling DPLII in the middle of a game you hear cracking (mixed with DPLII decent output). This is because until we have stabilized the additional DPLII latency, we +//should read from m_decoded_fifo or we'd cause missed alignment. A better alternative would be to pre-fill them with 0 by calculating their number, but would it work when the backend latency suddenly changes? void SurroundDecoder::GetDecodedSamples(float* out, u32 num_samples) { if (num_samples == 0) return; - // Copy to output array with desired num_samples - const u32 readable_samples = - std::min(num_samples * SURROUND_CHANNELS, u32(m_decoded_fifo.size())); - const u32 front_readable_samples = std::min(readable_samples, u32(m_decoded_fifo.size_to_end())); - // Samples looping over the ring buffer - const u32 beginning_readable_samples = readable_samples - front_readable_samples; + static u32 max_count = 0; + // Find the expected latency at the current setup + u32 latency_samples = 0; + u32 samples_in = num_samples; + u32 block_size = m_block_size; + u32 in_buffer = 0; + u32 out_buffer = 0; + u32 count = 0; + static std::vector v1; + static std::vector v2; + static std::vector v3; + //To do: try this and make sure it doesn't stall for any combination of numbers, though it shouldn't as it's basically the LCM + for (u32 i = 1; i < 4800; ++i) + { + samples_in = i; + for (u32 k = 2; k < 2400; k += 2) + { + block_size = k; + latency_samples = 0; + in_buffer = 0; + out_buffer = 0; + count = 0; + while (true) + { + in_buffer += samples_in; + out_buffer += (in_buffer / block_size) * block_size; + in_buffer = in_buffer % block_size; + out_buffer -= std::min(out_buffer, samples_in); + // We are done, we have found the "LCM" + if (latency_samples == in_buffer + out_buffer) + { + //To find the min needed count... + if (count >= 200) + { + break; + } + ++count; + } + else + { + if (count > 0) + { + v1.push_back(samples_in); + v2.push_back(block_size); + v3.push_back(count); + if (count > max_count) + { + // INFO_LOG(AUDIO_INTERFACE, "count is broken %u", count); + max_count = count; + } + } + latency_samples = in_buffer + out_buffer; + } + } + } + } + INFO_LOG(AUDIO_INTERFACE, "max_count %u", max_count); - memcpy(&out[0], &m_decoded_fifo.front(), sizeof(float) * front_readable_samples); - if (beginning_readable_samples > 0) - memcpy(&out[front_readable_samples], &m_decoded_fifo.beginning(), - sizeof(float) * beginning_readable_samples); - m_decoded_fifo.erase(readable_samples); + //To do: scale m_decoded_fifo to the bigger multiple of the new block size that it can contain? + //and m_float_conversion_buffer to the min between prev num and new block size. + //Or actually what you want is to have the new latency be half of the block size so base it on that? + u32 buffered_samples = (u32(m_float_conversion_buffer.size()) / STEREO_CHANNELS) + + (u32(m_decoded_fifo.size()) / SURROUND_CHANNELS); + static bool enable_safety = false; //To review: we don't wanna do this at the end though? We'd miss the last samples + // We need to have built up at least a block size of samples, otherwise in the next request we might not have any left + //To review: this actually isn't right as it doesn't consider that in the next frame we MIGHT have enough samples to continue? If so, we should append them to the right side of the out array? + if (enable_safety && buffered_samples < m_block_size) + { + // No need for padding or zeroing, this can only happen at the beginning, before we have any samples + //To review: if we increased DPLII quality, or the backend unexpectedly changed latency, or the mixer returned less samples than usual, this might happen so we do want to pad + return; + } - if (readable_samples > 0) + //To review: the order of this code, shouldn't it be inverted? And all the ones in ReturnSamples() + u32 read_samples = s32(m_decoded_fifo.copy_to_array(out, num_samples * SURROUND_CHANNELS)); + if (read_samples > 0) { - memcpy(&m_last_decoded_samples[0], &out[readable_samples - SURROUND_CHANNELS], + m_decoded_fifo.erase(read_samples); + memcpy(&m_last_decoded_samples[0], &out[read_samples - SURROUND_CHANNELS], sizeof(float) * SURROUND_CHANNELS); } + //To fixup and comment out. Also, it seems like the added latency ISN'T half the block size at all... Bring back the latency alignment code??? Though explain it. + //If backend latency is a multiple of the block size, then the extra latency is 0. If it's a dividend or a weird multiplier (like 1.5 times), latency is smaller than when it's not but it's not 0. + //double times_to_make_a_block = double(m_block_size) / num_samples; + //u32 min_dpli_latency = m_block_size / DECODER_GRANULARITY; + buffered_samples = (u32(m_float_conversion_buffer.size()) / STEREO_CHANNELS) + (u32(m_decoded_fifo.size()) / SURROUND_CHANNELS); + u32 added_dpl2_latency = buffered_samples; + u32 guessed_added_dpl2_latency1 = (num_samples * ((MathUtil::LCM(num_samples, m_block_size) / num_samples) - 1)); + guessed_added_dpl2_latency1 = MathUtil::LCM(num_samples, m_block_size) - num_samples; + guessed_added_dpl2_latency1 = latency_samples; + u32 guessed_added_dpl2_latency2 = m_block_size * ((MathUtil::LCM(num_samples, m_block_size) / m_block_size) - 1); + u32 guessed_added_dpl2_latency3 = m_block_size * ((MathUtil::LCM(num_samples, m_block_size) / num_samples) - 1); // This seems to be the best one (often correct, or a multiple/divident of the actual added latency) + u32 guessed_added_dpl2_latency4 = num_samples * ((MathUtil::LCM(num_samples, m_block_size) / m_block_size) - 1); + s32 lcm1 = MathUtil::LCM(num_samples, m_block_size); + s32 lcm2 = MathUtil::LCM(num_samples - m_block_size, m_block_size); + s32 lcm3 = MathUtil::LCM(num_samples, m_block_size - num_samples); + s32 lcm4 = MathUtil::LCM(m_block_size - num_samples, m_block_size); + s32 lcm5 = MathUtil::LCM(num_samples - m_block_size, num_samples); + INFO_LOG(AUDIO_INTERFACE, + "backend_latency %u block_size %u " + "added_dpl2_latency %u ga_dpl2_latency1 %u ga_dpl2_latency2 " + "%u ga_dpl2_latency3 %u, gad_dpl2_latency4 %u LCM1 %u LCM2 %u LCM3 %u LCM4 " + "%u LCM5 %u", + num_samples, m_block_size, added_dpl2_latency, guessed_added_dpl2_latency1, + guessed_added_dpl2_latency2, guessed_added_dpl2_latency3, guessed_added_dpl2_latency4, + lcm1, lcm2, lcm3, lcm4, lcm5); + + //To review: this breaks latency alignment when changing sample rate or quality (e.g. having a fixed larger latency in the DPLII decoder that will never go) // Padding should never happen. Once we have reached enough samples in the buffer, // it should always keep that number, unless the backend latency changes - u32 read_samples = readable_samples; while (read_samples < num_samples * SURROUND_CHANNELS) { memcpy(&out[read_samples], &m_last_decoded_samples[0], sizeof(float) * SURROUND_CHANNELS); @@ -249,24 +307,59 @@ void SurroundDecoder::GetDecodedSamples(float* out, u32 num_samples) } } -u32 SurroundDecoder::ReturnSamples(s16* out, u32 num_samples, bool& has_finished) +//To review: return order (and test...) +u32 SurroundDecoder::ReturnSamples(s16* out, u32 num_samples) { - const u32 readable_samples = - std::min(u32(m_float_conversion_buffer.size()), num_samples * STEREO_CHANNELS); + u32 writable_samples = num_samples * STEREO_CHANNELS; + + // We first return (and unconvert) the samples already converted by the m_fsdecoder + u32 readable_samples = + std::min(u32(m_decoded_fifo.size()) / SURROUND_CHANNELS * STEREO_CHANNELS, writable_samples); + const float center_volume = float(std::sin(MathUtil::PI / 4)); // Constant Power Pan Law for (size_t i = 0; i < readable_samples; ++i) { - out[i] = - s16(std::clamp(s32(m_float_conversion_buffer[i] * s32(-std::numeric_limits::min())), - s32(std::numeric_limits::min()), s32(std::numeric_limits::max()))); + // Theoretically we should unmix the 5.1 into 2.0 better but it's really minor + const size_t c = i % STEREO_CHANNELS; // stereo channel + const size_t s = (i / STEREO_CHANNELS) * SURROUND_CHANNELS; // surround shift + out[i] = convert_float_to_s16(m_decoded_fifo[c + s]); + + // Add back the center (index 2) to L and R and reduce its volume + const s16 center = convert_float_to_s16(m_decoded_fifo[2 + s] * center_volume); + out[i] = s16(std::clamp(s32(out[i] + center), s32(std::numeric_limits::min()), + s32(std::numeric_limits::max()))); + } + m_decoded_fifo.erase(readable_samples / STEREO_CHANNELS * SURROUND_CHANNELS); + writable_samples -= readable_samples; + u32 written_samples = readable_samples; + + // Then we return the yet unconverted samples within the m_fsdecoder + readable_samples = std::min(m_returnable_samples_in_decoder, writable_samples); + for (size_t i = written_samples, k = (m_block_size * DECODER_GRANULARITY / STEREO_CHANNELS) - + m_returnable_samples_in_decoder; + i < readable_samples + written_samples; ++i, ++k) + { + out[i] = convert_float_to_s16(m_fsdecoder->input_buffer()[k]); + m_fsdecoder->input_buffer()[k] = 0.f; + } + m_returnable_samples_in_decoder -= readable_samples; + writable_samples -= readable_samples; + written_samples += readable_samples; + + // Then the ones from the pending block push + readable_samples = std::min(u32(m_float_conversion_buffer.size()), writable_samples); + for (size_t i = 0; i < readable_samples; ++i) + { + out[i + written_samples] = convert_float_to_s16(m_float_conversion_buffer[i]); } m_float_conversion_buffer.erase(readable_samples); - has_finished = (m_float_conversion_buffer.size() == 0); - // We could also return (and "unconvert") the samples within the m_fsdecoder but it's not worth it - return readable_samples / STEREO_CHANNELS; + written_samples += readable_samples; + + return written_samples / STEREO_CHANNELS; } bool SurroundDecoder::CanReturnSamples() const { - return m_float_conversion_buffer.size() > 0; + return m_decoded_fifo.size() > 0 || m_returnable_samples_in_decoder > 0 || + m_float_conversion_buffer.size() > 0; } } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/SurroundDecoder.h b/Source/Core/AudioCommon/SurroundDecoder.h index 86a842e49335..263ef704b3aa 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.h +++ b/Source/Core/AudioCommon/SurroundDecoder.h @@ -13,41 +13,45 @@ class DPL2FSDecoder; namespace AudioCommon { -// GC/Wii encodes sounds for DPLII, so we only support 5.1, anything more wouldn't add anything +// GC/Wii encodes sounds for DPLII, so we only support 5.1, anything more wouldn't be able to decode +// any additional information. Wii mote sounds will pass through here as well if enabled class SurroundDecoder { public: - // Asks for the mixer/backend sample rate and latency in samples (can be guessed for now) - explicit SurroundDecoder(u32 sample_rate, u32 num_samples); + explicit SurroundDecoder(u32 sample_rate); ~SurroundDecoder(); - // Can be used to re-initialize as well. num_samples is the latency, can be left 0 to guess it - void Init(u32 sample_rate, u32 num_samples = 0); + // Can be used to re-initialize as well + void Init(u32 sample_rate); // Receive and decode samples void PushSamples(const s16* in, u32 num_samples); void GetDecodedSamples(float* out, u32 num_samples); - // Gives us back some of the samples which are still being computed, to not miss samples when - // DPLII is disabled - u32 ReturnSamples(s16* out, u32 num_samples, bool& has_finished); + // Gives us back the samples which are still being computed, to not miss samples when + // disabling DPLII + u32 ReturnSamples(s16* out, u32 num_samples); bool CanReturnSamples() const; void Clear(); + // A DPLII decoder block is internally divided in 2 + static constexpr u32 DECODER_GRANULARITY = 2; static constexpr u32 STEREO_CHANNELS = 2; static constexpr u32 SURROUND_CHANNELS = 6; - // Max supported samples rate is about 192kHz at highest block quality (~100ms) - static constexpr u32 MAX_BLOCKS_SIZE = 19200; + //To restore and review: this doesn't seem to always be checked correctly, it can break without warning over 100ms + // Max supported block size (e.g. samples rate 192kHz at highest block quality, 100ms) + static constexpr u32 MAX_BLOCKS_SIZE = 19200 * 48; + // Max times our backend/mixer latency can be greater than our block size (e.g. 800ms at 192kHz) static constexpr u32 MAX_BLOCKS_BUFFERED = 8; private: u32 m_sample_rate = 0; - // Backend latency used to aid the block size calculation (to find the best compromize - // between quality, performance and latency) - u32 m_block_size_aid_latency_in_samples = 0; - u32 m_frame_block_size = 0; + u32 m_block_size = 0; // Doesn't count channels + u32 m_returnable_samples_in_decoder = 0; // Counts channels + bool latency_warning_sent = false; std::unique_ptr m_fsdecoder; FixedSizeQueue m_float_conversion_buffer; + std::vector m_float_conversion_buffer_copy; //To delete // This can end up being quite big, we could make a ptr and only allocate it when DPLII is used FixedSizeQueue m_decoded_fifo; - float m_last_decoded_samples[SURROUND_CHANNELS]; + float m_last_decoded_samples[SURROUND_CHANNELS] = {0.f}; }; } // namespace AudioCommon diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index e35df074d9e4..86172da51814 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -172,7 +172,6 @@ static bool HandleWinAPI(std::string message, HRESULT result) WASAPIStream::WASAPIStream() { - // Just to avoid moving AutoCoInit to the header file m_aci = std::make_unique(); } @@ -272,7 +271,7 @@ std::vector> WASAPIStream::GetAvailableDevic return device_ids_and_names; } -IMMDevice* WASAPIStream::GetDeviceByName(const std::string& name) +IMMDevice* WASAPIStream::GetDeviceByName(std::string_view name) { AutoCoInit aci; if (!aci.Succeeded()) @@ -351,7 +350,7 @@ IMMDevice* WASAPIStream::GetDeviceByName(const std::string& name) return found_device; } -IMMDevice* WASAPIStream::GetDeviceByID(const std::string& id) +IMMDevice* WASAPIStream::GetDeviceByID(std::string_view id) { AutoCoInit aci; if (!aci.Succeeded()) @@ -581,7 +580,7 @@ bool WASAPIStream::Init() bool WASAPIStream::SetRunning(bool running) { - assert(running != m_running); + assert(running != m_running); // Can't happen, but if it did, it's not supported // Can be called by multiple threads (main and emulation), we need to initialize COM AutoCoInit aci; @@ -617,10 +616,10 @@ bool WASAPIStream::SetRunning(bool running) } } + m_surround = SConfig::GetInstance().ShouldUseDPL2Decoder(); // Recreate the mixer with our preferred sample rate GetMixer()->UpdateSettings(sample_rate); - m_surround = SConfig::GetInstance().ShouldUseDPL2Decoder(); m_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; m_format.Format.nChannels = m_surround ? SURROUND_CHANNELS : STEREO_CHANNELS; m_format.Format.nSamplesPerSec = GetMixer()->GetSampleRate(); @@ -682,7 +681,9 @@ bool WASAPIStream::SetRunning(bool running) } if (!HandleWinAPI("Failed to get default endpoint", result)) + { return false; + } // Show a friendly name in the log (no other use) IPropertyStore* device_properties; @@ -754,9 +755,6 @@ bool WASAPIStream::SetRunning(bool running) GetMixer()->GetSampleRate()); m_surround = false; - // Let the mixer know so it can clean the samples left in the surround decoder - GetMixer()->SetSurroundChanged(); - GetMixer()->UpdateSettings(sample_rate); m_format.Format.nChannels = STEREO_CHANNELS; m_format.Format.nBlockAlign = m_format.Format.nChannels * m_format.Format.wBitsPerSample / 8; @@ -921,7 +919,6 @@ bool WASAPIStream::SetRunning(bool running) m_thread.join(); - m_surround = false; m_using_default_device = false; // As long as we don't call SetRunning(false) while m_running was already false, @@ -963,21 +960,39 @@ void WASAPIStream::Update() { // We need to pass through AudioCommon as it has a mutex and // to make sure s_sound_stream_running is updated - if (AudioCommon::SetSoundStreamRunning(false, false)) + if (AudioCommon::SetSoundStreamRunning(false)) { // m_should_restart is triggered when the device is currently // invalidated, and it will stay for a while, so this new call // to SetRunning(true) might fail, but if it fails some // specific reasons, it will set m_should_restart true again. // A Sleep(10) call also seemed to fix the problem but it's hacky. - AudioCommon::SetSoundStreamRunning(true, false); + AudioCommon::SetSoundStreamRunning(true); } } else { - AudioCommon::SetSoundStreamRunning(true, false); + AudioCommon::SetSoundStreamRunning(true); + } + } +} + +AudioCommon::SurroundState WASAPIStream::GetSurroundState() const +{ + if (m_running) + { + if (m_surround) + { + return AudioCommon::SurroundState::Enabled; + } + if (SConfig::GetInstance().ShouldUseDPL2Decoder()) + { + return AudioCommon::SurroundState::Failed; } } + return SConfig::GetInstance().ShouldUseDPL2Decoder() ? + AudioCommon::SurroundState::EnabledNotRunning : + AudioCommon::SurroundState::Disabled; } void WASAPIStream::SoundLoop() diff --git a/Source/Core/AudioCommon/WASAPIStream.h b/Source/Core/AudioCommon/WASAPIStream.h index 0b6619f5a46a..3c7f3aa6759b 100644 --- a/Source/Core/AudioCommon/WASAPIStream.h +++ b/Source/Core/AudioCommon/WASAPIStream.h @@ -7,14 +7,15 @@ #ifdef _WIN32 #include #include -#endif #include #include #include #include +#endif #include "AudioCommon/SoundStream.h" +#include "Common/Common.h" struct IAudioClient; struct IAudioRenderClient; @@ -27,9 +28,11 @@ class AutoCoInit; class WASAPIStream final : public SoundStream { -#ifdef _WIN32 public: - explicit WASAPIStream(); + static std::string GetName() { return _trans("WASAPI (Exclusive Mode)"); } +#ifdef _WIN32 + static bool IsValid() { return true; } + WASAPIStream(); ~WASAPIStream(); bool Init() override; bool SetRunning(bool running) override; @@ -40,18 +43,17 @@ class WASAPIStream final : public SoundStream bool ShouldRestart() const { return m_should_restart; } bool IsUsingDefaultDevice() const { return m_using_default_device; } + static bool SupportsSurround() { return true; } + static bool SupportsCustomLatency() { return true; } + static bool SupportsVolumeChanges() { return true; } bool SupportsRuntimeSettingsChanges() const override { return true; } - bool IsSurroundEnabled() const override { return m_surround; } + AudioCommon::SurroundState GetSurroundState() const override; void OnSettingsChanged() override { m_should_restart = true; } - static bool IsValid() { return true; } // Returns the IDs and Names of all the devices static std::vector> GetAvailableDevices(); - // Returns the first device found with the (friendly) name we passed in - static IMMDevice* GetDeviceByName(const std::string& name); - static IMMDevice* GetDeviceByID(const std::string& id); // Returns the user selected device supported sample rates at 16 bit and 2 channels, - // so it ignores 24 bit or support for 5 channels. If we are starting up WASAPI with DPL2 on, + // so it ignores 24 bit or support for 5 channels. If we are starting up WASAPI with DPLII on, // it will try these sample rates anyway, or fallback to the dolphin default one, // but generally if a device supports a sample rate, it supports it with any bitrate/channels. // It doesn't just return any device supported sample rate, just the ones we care for in Dolphin @@ -60,6 +62,10 @@ class WASAPIStream final : public SoundStream private: bool SetRestartFromResult(HRESULT result); + // Returns the first device found with the (friendly) name we passed in + static IMMDevice* GetDeviceByName(std::string_view name); + static IMMDevice* GetDeviceByID(std::string_view id); + u32 m_frames_in_buffer = 0; std::atomic m_running = false; std::atomic m_should_restart = false; diff --git a/Source/Core/AudioCommon/WaveFile.cpp b/Source/Core/AudioCommon/WaveFile.cpp index efc21594e3aa..557392be268f 100644 --- a/Source/Core/AudioCommon/WaveFile.cpp +++ b/Source/Core/AudioCommon/WaveFile.cpp @@ -26,15 +26,15 @@ WaveFileWriter::~WaveFileWriter() Stop(); } -bool WaveFileWriter::Start(const std::string& filename, unsigned int HLESampleRate) +bool WaveFileWriter::Start(const std::string& file_name, u32 sample_rate) { // Ask to delete file - if (File::Exists(filename)) + if (File::Exists(file_name)) { if (SConfig::GetInstance().m_DumpAudioSilent || AskYesNoFmtT("Delete the existing file '{0}'?", filename)) { - File::Delete(filename); + File::Delete(file_name); } else { @@ -50,7 +50,7 @@ bool WaveFileWriter::Start(const std::string& filename, unsigned int HLESampleRa return false; } - file.Open(filename, "wb"); + file.Open(file_name, "wb"); if (!file) { PanicAlertFmtT( @@ -63,9 +63,9 @@ bool WaveFileWriter::Start(const std::string& filename, unsigned int HLESampleRa audio_size = 0; if (basename.empty()) - SplitPath(filename, nullptr, &basename, nullptr); + SplitPath(file_name, nullptr, &basename, nullptr); - current_sample_rate = HLESampleRate; + current_sample_rate = sample_rate; // ----------------- // Write file header @@ -78,7 +78,6 @@ bool WaveFileWriter::Start(const std::string& filename, unsigned int HLESampleRa Write(16); // size of fmt block Write(0x00020001); // two channels, uncompressed - const u32 sample_rate = HLESampleRate; Write(sample_rate); Write(sample_rate * 2 * 2); // two channels, 16bit @@ -115,12 +114,12 @@ void WaveFileWriter::Write4(const char* ptr) file.WriteBytes(ptr, 4); } -void WaveFileWriter::AddStereoSamplesBE(const short* sample_data, u32 count, int sample_rate) +void WaveFileWriter::AddStereoSamplesBE(const short* sample_data, u32 count, u32 sample_rate) { if (!file) ERROR_LOG_FMT(AUDIO, "WaveFileWriter - file not open."); - if (count > BUFFER_SIZE * 2) + if (count > BUFFER_SIZE / 2) ERROR_LOG_FMT(AUDIO, "WaveFileWriter - buffer too small (count = {}).", count); if (skip_silence) @@ -130,28 +129,32 @@ void WaveFileWriter::AddStereoSamplesBE(const short* sample_data, u32 count, int for (u32 i = 0; i < count * 2; i++) { if (sample_data[i]) + { all_zero = false; + break; + } } if (all_zero) return; } - for (u32 i = 0; i < count; i++) - { - // Flip the audio channels from RL to LR - conv_buffer[2 * i] = Common::swap16((u16)sample_data[2 * i + 1]); - conv_buffer[2 * i + 1] = Common::swap16((u16)sample_data[2 * i]); - } - if (sample_rate != current_sample_rate) { Stop(); file_index++; std::ostringstream filename; filename << File::GetUserPath(D_DUMPAUDIO_IDX) << basename << file_index << ".wav"; - Start(filename.str(), sample_rate); - current_sample_rate = sample_rate; + current_sample_rate = sample_rate; // Avoid trying again if Start() fails + if (!Start(filename.str(), sample_rate)) + return; + } + + for (u32 i = 0; i < count; i++) + { + // Flip the audio channels from RL to LR + conv_buffer[2 * i] = Common::swap16((u16)sample_data[2 * i + 1]); + conv_buffer[2 * i + 1] = Common::swap16((u16)sample_data[2 * i]); } file.WriteBytes(conv_buffer.data(), count * 4); diff --git a/Source/Core/AudioCommon/WaveFile.h b/Source/Core/AudioCommon/WaveFile.h index be8709cce1b0..d26469c0225b 100644 --- a/Source/Core/AudioCommon/WaveFile.h +++ b/Source/Core/AudioCommon/WaveFile.h @@ -31,11 +31,11 @@ class WaveFileWriter WaveFileWriter(WaveFileWriter&&) = delete; WaveFileWriter& operator=(WaveFileWriter&&) = delete; - bool Start(const std::string& filename, unsigned int HLESampleRate); + bool Start(const std::string& file_name, u32 sample_rate); void Stop(); void SetSkipSilence(bool skip) { skip_silence = skip; } - void AddStereoSamplesBE(const short* sample_data, u32 count, int sample_rate); // big endian + void AddStereoSamplesBE(const short* sample_data, u32 count, u32 sample_rate); // big endian u32 GetAudioSize() const { return audio_size; } private: @@ -48,6 +48,6 @@ class WaveFileWriter void Write(u32 value); void Write4(const char* ptr); std::string basename; - int current_sample_rate; - int file_index = 0; + u32 current_sample_rate; + unsigned int file_index = 0; }; diff --git a/Source/Core/Common/FixedSizeQueue.h b/Source/Core/Common/FixedSizeQueue.h index c7eed89edcb5..6a0d5ac74235 100644 --- a/Source/Core/Common/FixedSizeQueue.h +++ b/Source/Core/Common/FixedSizeQueue.h @@ -14,7 +14,7 @@ // STL-look-a-like interface, but name is mixed case to distinguish it clearly from the // real STL classes. // -// Not fully featured, no safety checking yet. Add features as needed. +// Not fully featured. Add features as needed. template class FixedSizeQueue @@ -47,6 +47,46 @@ class FixedSizeQueue tail = (tail + 1) % N; } + // Copies over an array, loops over of num is greater than max_size + void push_array(const T* t, size_t num) + { + size_t back_writable_num = std::min(tail_to_end(), num); + memcpy(&back(), t, sizeof(T) * back_writable_num); + t += back_writable_num; + size_t readable_num = num - back_writable_num; + while (readable_num > 0) + { + size_t beginning_writable_num = std::min(readable_num, N); + memcpy(&beginning(), t, sizeof(T) * beginning_writable_num); + t += beginning_writable_num; + readable_num -= beginning_writable_num; + } + + while (num > 0) + { + if (count == N) + head = (head + 1) % N; + else + count++; + tail = (tail + 1) % N; + --num; + } + } + + // Takes a max num to copy (from head to tail), returns the actual copied number. + // Doesn't loop over if num is greater than our size + size_t copy_to_array(T* t, size_t num) + { + num = std::min(num, count); + const size_t front_readable_num = std::min(head_to_end(), num); + const size_t beginning_readable_num = num - front_readable_num; + + memcpy(&t[0], &front(), sizeof(T) * front_readable_num); + if (beginning_readable_num > 0) + memcpy(&t[front_readable_num], &beginning(), sizeof(T) * beginning_readable_num); + return num; + } + template void emplace(Args&&... args) { @@ -113,13 +153,15 @@ class FixedSizeQueue const T& front() const noexcept { return storage[head]; } T& back() noexcept { return storage[tail]; } const T& back() const noexcept { return storage[tail]; } - // Only use if size_to_end() > size() + // Only use if head_to_end() > size() T& beginning() noexcept { return storage[0]; } const T& beginning() const noexcept { return storage[0]; } size_t size() const noexcept { return count; } size_t max_size() const noexcept { return N; } // Helper to know how many more samples we could read before needing to loop over - size_t size_to_end() const noexcept { return N - head; } + size_t head_to_end() const noexcept { return N - head; } + // Helper to know how many more samples we could write before needing to loop over + size_t tail_to_end() const noexcept { return N - tail; } bool empty() const noexcept { return size() == 0; } private: diff --git a/Source/Core/Common/MathUtil.h b/Source/Core/Common/MathUtil.h index 21bdf8805660..3fd147037842 100644 --- a/Source/Core/Common/MathUtil.h +++ b/Source/Core/Common/MathUtil.h @@ -100,6 +100,26 @@ constexpr s32 NearestPowerOf2(s32 value) return (next - value) > (value - prev) ? prev : next; } +constexpr bool IsPowerOfTwo(s32 value) +{ + return value != 0 && (value & (value - 1)) == 0; +} + +//To move back to u32 +// Greatest common divisor +constexpr s32 GCD(s32 a, s32 b) +{ + if (b == 0) + return a; + return GCD(b, a % b); +} + +// Least/lowest common multiple +constexpr s32 LCM(s32 a, s32 b) +{ + return (a / GCD(a, b)) * b; +} + template struct Rectangle { diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 5b6911739c56..7c255a635ea2 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -39,8 +39,6 @@ const Info MAIN_DPL2_DECODER{{System::Main, "Core", "DPL2Decoder"}, false} const Info MAIN_DPL2_QUALITY{{System::Main, "Core", "DPL2Quality"}, AudioCommon::GetDefaultDPL2Quality()}; const Info MAIN_DPL2_BASS_REDIRECTION{{System::Main, "Core", "DPL2BassRedirection"}, false}; -const Info MAIN_DPL2_PERFORMANCE_OVER_LATENCY{ - {System::Main, "Core", "DPL2PerformanceOverLatency"}, false}; const Info MAIN_AUDIO_BACKEND_LATENCY{{System::Main, "Core", "AudioBackendLatency"}, 20}; const Info MAIN_AUDIO_MIXER_MIN_LATENCY{{System::Main, "Core", "AudioMixerMinLatency"}, 0}; const Info MAIN_AUDIO_MIXER_MAX_LATENCY{{System::Main, "Core", "AudioMixerMaxLatency"}, 40}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 464e096cb7a6..17618bf9ad57 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -40,7 +40,6 @@ extern const Info MAIN_OVERRIDE_REGION_SETTINGS; extern const Info MAIN_DPL2_DECODER; extern const Info MAIN_DPL2_QUALITY; extern const Info MAIN_DPL2_BASS_REDIRECTION; -extern const Info MAIN_DPL2_PERFORMANCE_OVER_LATENCY; // Only set this different from zero in case your audio backend constantly changes the number of // samples it asks the mixer for extern const Info MAIN_AUDIO_MIXER_MIN_LATENCY; diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index a033b3dfbc86..3bc100b87278 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -34,7 +34,7 @@ bool IsSettingSaveable(const Config::Location& config_location) } } - static constexpr std::array s_setting_saveable = { + static constexpr std::array s_setting_saveable = { // Main.Core &Config::MAIN_DEFAULT_ISO.GetLocation(), @@ -45,7 +45,6 @@ bool IsSettingSaveable(const Config::Location& config_location) &Config::MAIN_DPL2_DECODER.GetLocation(), &Config::MAIN_DPL2_QUALITY.GetLocation(), &Config::MAIN_DPL2_BASS_REDIRECTION.location, - &Config::MAIN_DPL2_PERFORMANCE_OVER_LATENCY.location, &Config::MAIN_AUDIO_MIXER_MIN_LATENCY.location, &Config::MAIN_AUDIO_MIXER_MAX_LATENCY.location, &Config::MAIN_RAM_OVERRIDE_ENABLE.GetLocation(), diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 432be40e9464..3759f35f04e6 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -221,9 +221,9 @@ void SConfig::SaveCoreSettings(IniFile& ini) core->Set("OverrideRegionSettings", bOverrideRegionSettings); core->Set("DPL2Decoder", bDPL2Decoder); core->Set("AudioBackendLatency", iAudioBackendLatency); - core->Set("UseOSMixerSampleRate", &bUseOSMixerSampleRate); + core->Set("UseOSMixerSampleRate", bUseOSMixerSampleRate); core->Set("AudioStretch", m_audio_stretch); - core->Set("AudioEmuSpeedTolerance", &m_audio_emu_speed_tolerance); + core->Set("AudioEmuSpeedTolerance", m_audio_emu_speed_tolerance); core->Set("AgpCartAPath", m_strGbaCartA); core->Set("AgpCartBPath", m_strGbaCartB); core->Set("SlotA", m_EXIDevice[0]); @@ -483,7 +483,7 @@ void SConfig::LoadCoreSettings(IniFile& ini) core->Get("OverrideRegionSettings", &bOverrideRegionSettings, false); core->Get("DPL2Decoder", &bDPL2Decoder, false); core->Get("AudioBackendLatency", &iAudioBackendLatency, 20); - core->Get("UseOSMixerSampleRate", &bUseOSMixerSampleRate, false); + core->Get("UseOSMixerSampleRate", &bUseOSMixerSampleRate, true); core->Get("AudioStretch", &m_audio_stretch, false); core->Get("AudioEmuSpeedTolerance", &m_audio_emu_speed_tolerance, 20); core->Get("AgpCartAPath", &m_strGbaCartA); diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 5238706d46dd..0e931effb51e 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -48,15 +48,6 @@ enum SIDevices : int; struct BootParameters; -// DSP Backend Types -#define BACKEND_NULLSOUND _trans("No Audio Output") -#define BACKEND_ALSA "ALSA" -#define BACKEND_CUBEB "Cubeb" -#define BACKEND_OPENAL _trans("OpenAL (Deprecated)") -#define BACKEND_PULSEAUDIO "Pulse" -#define BACKEND_OPENSLES "OpenSLES" -#define BACKEND_WASAPI _trans("WASAPI (Exclusive Mode)") - enum class GPUDeterminismMode { Auto, @@ -127,7 +118,7 @@ struct SConfig bool bDPL2Decoder = false; // Don't access directly, use AudioInterface::GetUserTargetLatency() int iAudioBackendLatency = 20; - bool bUseOSMixerSampleRate = false; + bool bUseOSMixerSampleRate = true; bool m_audio_stretch = false; int m_audio_emu_speed_tolerance = 20; diff --git a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp index 1c4681386971..927d70ea73f2 100644 --- a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp @@ -20,7 +20,6 @@ #include "Core/HW/WiimoteReal/WiimoteReal.h" #include "InputCommon/ControllerEmu/ControlGroup/Attachments.h" #include "InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.h" -#pragma optimize("", off) //To delete namespace WiimoteEmu { diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp index 6544f02453e4..8e7b411f64a0 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp @@ -22,7 +22,6 @@ #include "AudioCommon/WaveFile.h" #include "Common/FileUtil.h" #endif -#pragma optimize("", off) //To delete namespace WiimoteEmu { @@ -56,7 +55,7 @@ static s16 adpcm_yamaha_expand_nibble(ADPCMState& s, u8 nibble) { double predictor_delta = (s.step * yamaha_difflookup[nibble]) / 8.0; assert(predictor_delta == std::clamp(predictor_delta, -65536.0, 65535.0)); - predictor_delta = std::clamp(predictor_delta, -65536.0, 65535.0); // Likely useless + predictor_delta = std::clamp(predictor_delta, -65536.0, 65535.0); // Likely useless s.predictor += predictor_delta; s.predictor = std::clamp(s.predictor, -32768.0, 32767.0); s.step = (s.step * yamaha_indexscale[nibble]) / 256.0; diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 256af85802c4..91d282e21c72 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -22,7 +22,10 @@ #include "AudioCommon/AudioCommon.h" #include "AudioCommon/Enums.h" +#include "AudioCommon/NullSoundStream.h" +#ifdef _WIN32 #include "AudioCommon/WASAPIStream.h" +#endif #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" @@ -109,7 +112,6 @@ void AudioPane::CreateWidgets() m_volume_slider->setMinimum(0); m_volume_slider->setMaximum(100); - m_volume_slider->setToolTip(tr("Using this is preferred over the OS mixer volume")); m_volume_indicator->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); @@ -206,7 +208,7 @@ void AudioPane::CreateWidgets() m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); //To review: maybe have a set of 4 or 5 options to keep it simpler (low, high, ...). //Also add descrpition to m_emu_speed_tolerance_label explaining that it's the time - //the emulation need to be offsetted by to start using the current speed + //the emulation need to be offsetted by to start using the current speed. Do the same on android m_stretching_enable->setToolTip(tr( "Enables stretching of the audio (pitch correction) to match the emulation speed.\nIt might " @@ -233,7 +235,7 @@ void AudioPane::CreateWidgets() m_dolby_quality_slider->setToolTip( tr("Quality of the DPLII decoder. Also increases audio latency by about half the block " "time.\nThe selected preset will be " - "used to find the best compromise between quality and latency.")); + "used to find the best compromise between quality and performance.")); m_dolby_quality_slider->setTracking(true); @@ -300,6 +302,8 @@ void AudioPane::LoadSettings() { auto& settings = Settings::Instance(); + m_ignore_save_settings = true; + // DSP if (SConfig::GetInstance().bDSPHLE) { @@ -312,7 +316,6 @@ void AudioPane::LoadSettings() } // Backend - m_ignore_save_settings = true; const auto current_backend = SConfig::GetInstance().sBackend; bool selection_set = false; m_backend_combo->clear(); @@ -324,9 +327,13 @@ void AudioPane::LoadSettings() m_backend_combo->setCurrentIndex(m_backend_combo->count() - 1); selection_set = true; } + // Fallback to default if the serialized one isn't valid + else if (!selection_set && backend == AudioCommon::GetDefaultSoundBackend()) + { + m_backend_combo->setCurrentIndex(m_backend_combo->count() - 1); + } } - if (!selection_set) - m_backend_combo->setCurrentIndex(-1); + m_ignore_save_settings = false; OnBackendChanged(); @@ -334,14 +341,14 @@ void AudioPane::LoadSettings() // Volume OnVolumeChanged(settings.GetVolume()); + m_ignore_save_settings = true; + // DPL2 m_dolby_pro_logic->setChecked(SConfig::GetInstance().bDPL2Decoder); - m_ignore_save_settings = true; m_dolby_quality_slider->setValue(int(Config::Get(Config::MAIN_DPL2_QUALITY))); - m_ignore_save_settings = false; m_dolby_quality_latency_label->setText( GetDPL2QualityAndLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); - if (AudioCommon::SupportsDPL2Decoder(current_backend) && !m_dsp_hle->isChecked()) + if (AudioCommon::SupportsSurround(current_backend) && !m_dsp_hle->isChecked()) { EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); } @@ -349,13 +356,9 @@ void AudioPane::LoadSettings() // Latency if (m_latency_control_supported) { - m_ignore_save_settings = true; m_latency_spin->setValue(SConfig::GetInstance().iAudioBackendLatency); - m_ignore_save_settings = false; } - m_ignore_save_settings = true; - // Sample rate m_use_os_sample_rate->setChecked(SConfig::GetInstance().bUseOSMixerSampleRate); @@ -392,8 +395,8 @@ void AudioPane::SaveSettings() auto& settings = Settings::Instance(); bool volume_changed = false; - bool backend_setting_changed = false; - bool surround_enabled_changed = false; + u32 changed_backend_setting = 0; + bool surround_quality_changed = false; // DSP if (SConfig::GetInstance().bDSPHLE != m_dsp_hle->isChecked() || @@ -434,8 +437,7 @@ void AudioPane::SaveSettings() { m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1)")); } - backend_setting_changed = true; - surround_enabled_changed = true; + ++changed_backend_setting; } if (Config::Get(Config::MAIN_DPL2_QUALITY) != static_cast(m_dolby_quality_slider->value())) @@ -444,14 +446,8 @@ void AudioPane::SaveSettings() static_cast(m_dolby_quality_slider->value())); m_dolby_quality_latency_label->setText( GetDPL2QualityAndLatencyLabel(Config::Get(Config::MAIN_DPL2_QUALITY))); - backend_setting_changed = true; // Not a mistake - } - // If we have disabled surround while the game is running, disable all its settings immediately, - // don't wait for the timer - if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked() && - (!m_running || (surround_enabled_changed && !m_dolby_pro_logic->isChecked()))) - { - EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); + ++changed_backend_setting; // Not a mistake + surround_quality_changed = true; } // Latency @@ -459,14 +455,14 @@ void AudioPane::SaveSettings() SConfig::GetInstance().iAudioBackendLatency != m_latency_spin->value()) { SConfig::GetInstance().iAudioBackendLatency = m_latency_spin->value(); - backend_setting_changed = true; + ++changed_backend_setting; } // Sample rate if (m_use_os_sample_rate->isChecked() != SConfig::GetInstance().bUseOSMixerSampleRate) { SConfig::GetInstance().bUseOSMixerSampleRate = m_use_os_sample_rate->isChecked(); - backend_setting_changed = true; + ++changed_backend_setting; } // Stretch @@ -497,14 +493,14 @@ void AudioPane::SaveSettings() SConfig::GetInstance().sWASAPIDeviceName = default_device ? "" : m_wasapi_device_combo->currentText().toStdString(); - bool is_wasapi = backend == BACKEND_WASAPI; + bool is_wasapi = backend == WASAPIStream::GetName(); if (is_wasapi) { OnWASAPIDeviceChanged(); if (device_changed) { LoadWASAPIDeviceSampleRate(); - backend_setting_changed = true; + ++changed_backend_setting; } } } @@ -513,41 +509,57 @@ void AudioPane::SaveSettings() if (SConfig::GetInstance().sWASAPIDeviceSampleRate != device_sample_rate) { SConfig::GetInstance().sWASAPIDeviceSampleRate = device_sample_rate; - backend_setting_changed = true; + bool is_wasapi = backend == WASAPIStream::GetName(); + if (is_wasapi) + { + ++changed_backend_setting; + } } #endif - AudioCommon::UpdateSoundStreamSettings(volume_changed, backend_setting_changed, - surround_enabled_changed); + if (AudioCommon::SupportsSurround(backend) && !m_dsp_hle->isChecked()) + { + // If we changed any backend setting while the game is running, disable the ability to change + // the DPLII quality until we have confirmed it has reactivated correctly (with the timer) + bool any_non_surround_quality_settings_changed = + (s32(changed_backend_setting) - s32(surround_quality_changed)) > 0; + bool should_enable = m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked(); + if (m_running) + should_enable = should_enable && !any_non_surround_quality_settings_changed; + EnableDolbyQualityWidgets(should_enable); + } + + AudioCommon::UpdateSoundStreamSettings(volume_changed, changed_backend_setting > 0); } void AudioPane::OnDSPChanged() { const auto backend = SConfig::GetInstance().sBackend; - m_dolby_pro_logic->setEnabled(AudioCommon::SupportsDPL2Decoder(backend) && + m_dolby_pro_logic->setEnabled(AudioCommon::SupportsSurround(backend) && !m_dsp_hle->isChecked()); EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); } void AudioPane::OnBackendChanged() { - const auto backend = SConfig::GetInstance().sBackend; + const auto backend = AudioCommon::GetBackendName(); - m_use_os_sample_rate->setEnabled(backend != BACKEND_NULLSOUND); + m_use_os_sample_rate->setEnabled(backend != NullSound::GetName()); - m_dolby_pro_logic->setEnabled(AudioCommon::SupportsDPL2Decoder(backend) && + m_dolby_pro_logic->setEnabled(AudioCommon::SupportsSurround(backend) && !m_dsp_hle->isChecked()); EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); if (m_latency_control_supported) { - m_latency_label->setEnabled(AudioCommon::SupportsLatencyControl(backend)); - m_latency_spin->setEnabled(AudioCommon::SupportsLatencyControl(backend)); + bool enable_latency = AudioCommon::SupportsLatencyControl(backend); + m_latency_label->setEnabled(enable_latency); + m_latency_spin->setEnabled(enable_latency); } #ifdef _WIN32 - bool is_wasapi = backend == BACKEND_WASAPI; + bool is_wasapi = backend == WASAPIStream::GetName(); m_wasapi_device_label->setHidden(!is_wasapi); m_wasapi_device_sample_rate_label->setHidden(!is_wasapi); m_wasapi_device_combo->setHidden(!is_wasapi); @@ -562,21 +574,21 @@ void AudioPane::OnBackendChanged() m_wasapi_devices_ids.clear(); m_wasapi_device_combo->clear(); m_wasapi_device_combo->addItem(tr("Default Device")); - for (const auto device : WASAPIStream::GetAvailableDevices()) { m_wasapi_devices_ids.emplace_back(device.first); m_wasapi_device_combo->addItem(QString::fromStdString(device.second)); } - OnWASAPIDeviceChanged(); - m_ignore_save_settings = false; + + OnWASAPIDeviceChanged(); } #endif - m_volume_slider->setEnabled(AudioCommon::SupportsVolumeChanges(backend)); - m_volume_indicator->setEnabled(AudioCommon::SupportsVolumeChanges(backend)); + bool enable_volume = AudioCommon::SupportsVolumeChanges(backend); + m_volume_slider->setEnabled(enable_volume); + m_volume_indicator->setEnabled(enable_volume); } #ifdef _WIN32 @@ -709,7 +721,32 @@ void AudioPane::OnEmulationStateChanged(bool running) { m_running = running; - const auto backend = SConfig::GetInstance().sBackend; + // If the selected backend failed to initialize, NullSound would have been started instead + const auto backend = AudioCommon::GetBackendName(); + const auto config_backend = SConfig::GetInstance().sBackend; + + if (config_backend != backend) + { + const auto backend_title = tr(m_backend_combo->itemData(m_backend_combo->currentIndex()) + .toString() + .toStdString() + .c_str()) + .append(tr(" (FAILED)")); + m_ignore_save_settings = true; + m_backend_combo->setItemText(m_backend_combo->currentIndex(), backend_title); + m_ignore_save_settings = false; + OnBackendChanged(); + } + else + { + const auto backend_title = tr(m_backend_combo->itemData(m_backend_combo->currentIndex()) + .toString() + .toStdString() + .c_str()); + m_ignore_save_settings = true; + m_backend_combo->setItemText(m_backend_combo->currentIndex(), backend_title); + m_ignore_save_settings = false; + } bool supports_current_emulation_state = !running || AudioCommon::BackendSupportsRuntimeSettingsChanges(); @@ -721,25 +758,22 @@ void AudioPane::OnEmulationStateChanged(bool running) m_backend_combo->setEnabled(!running); m_use_os_sample_rate->setEnabled(supports_current_emulation_state && - backend != BACKEND_NULLSOUND); + backend != NullSound::GetName()); - if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked()) + if (AudioCommon::SupportsSurround(backend) && !m_dsp_hle->isChecked()) { - bool enable_dolby_pro_logic = supports_current_emulation_state; - m_dolby_pro_logic->setEnabled(enable_dolby_pro_logic); - EnableDolbyQualityWidgets(m_dolby_pro_logic->isEnabled() && m_dolby_pro_logic->isChecked()); - if (!m_running) - m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1)")); + m_dolby_pro_logic->setEnabled(supports_current_emulation_state); } - if (m_latency_control_supported && - AudioCommon::SupportsLatencyControl(SConfig::GetInstance().sBackend)) + RefreshDolbyWidgets(); + + if (m_latency_control_supported) { bool enable_latency = supports_current_emulation_state && AudioCommon::SupportsLatencyControl(backend); m_latency_label->setEnabled(enable_latency); m_latency_spin->setEnabled(enable_latency); } - + #ifdef _WIN32 m_wasapi_device_label->setEnabled(supports_current_emulation_state); m_wasapi_device_combo->setEnabled(supports_current_emulation_state); @@ -747,7 +781,7 @@ void AudioPane::OnEmulationStateChanged(bool running) !SConfig::GetInstance().sWASAPIDeviceID.empty() && supports_current_emulation_state; m_wasapi_device_sample_rate_label->setEnabled(can_select_device_sample_rate); m_wasapi_device_sample_rate_combo->setEnabled(can_select_device_sample_rate); - bool is_wasapi = backend == BACKEND_WASAPI; + bool is_wasapi = backend == WASAPIStream::GetName(); if (is_wasapi) { OnWASAPIDeviceChanged(); @@ -777,7 +811,6 @@ void AudioPane::OnCustomShowPopup(QWidget* widget) m_wasapi_devices_ids.clear(); m_wasapi_device_combo->clear(); m_wasapi_device_combo->addItem(tr("Default Device")); - for (const auto device : WASAPIStream::GetAvailableDevices()) { m_wasapi_devices_ids.emplace_back(device.first); @@ -827,22 +860,26 @@ QString AudioPane::GetDPL2QualityAndLatencyLabel(AudioCommon::DPL2Quality value) void AudioPane::RefreshDolbyWidgets() { - const auto backend = SConfig::GetInstance().sBackend; - if (AudioCommon::SupportsDPL2Decoder(backend) && !m_dsp_hle->isChecked() && m_running) + const auto backend = AudioCommon::GetBackendName(); + if (AudioCommon::SupportsSurround(backend) && !m_dsp_hle->isChecked()) { - bool surround_enabled = AudioCommon::IsSurroundEnabled(); - if (!surround_enabled && SConfig::GetInstance().bDPL2Decoder) + AudioCommon::SurroundState surround_state = AudioCommon::GetSurroundState(); + bool surround_failed = m_running && surround_state == AudioCommon::SurroundState::Failed && + SConfig::GetInstance().bDPL2Decoder; + if (surround_failed) { // This message will likely only appear if the audio backend has failed to // initialize because of 5.1, if it fails before that, it won't disable surround - m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1) (FAILED)")); + m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1)").append(tr(" (FAILED)"))); } else { m_dolby_pro_logic->setText(tr("Dolby Pro Logic II (5.1)")); } - EnableDolbyQualityWidgets(surround_enabled && - AudioCommon::BackendSupportsRuntimeSettingsChanges()); + bool supports_current_emulation_state = + !m_running || AudioCommon::BackendSupportsRuntimeSettingsChanges(); + EnableDolbyQualityWidgets(!surround_failed && supports_current_emulation_state && + m_dolby_pro_logic->isChecked()); } } From caa3656d33e0959afd5b96a4adaacc55896b279b Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 29 Dec 2020 00:05:34 +0200 Subject: [PATCH 40/56] Small change --- Source/Core/AudioCommon/AudioCommon.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index b89b87f452b5..20cd4c18ba4e 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -137,12 +137,14 @@ std::string GetDefaultSoundBackend() { std::string backend = NullSound::GetName(); #if defined ANDROID - backend = OpenSLESStream::GetName(); + if (OpenSLESStream::IsValid()) + backend = OpenSLESStream::GetName(); #elif defined __linux__ if (AlsaSound::IsValid()) backend = AlsaSound::GetName(); #elif defined(__APPLE__) || defined(_WIN32) - backend = CubebStream::GetName(); + if (CubebStream::IsValid()) + backend = CubebStream::GetName(); #endif return backend; } @@ -152,7 +154,7 @@ std::string GetBackendName() std::lock_guard guard(g_sound_stream_mutex); // The only case in which the started stream is different from the config one is when it failed // to start and fell back to NullSound - if (g_selected_sound_stream_failed && g_sound_stream) + if (g_sound_stream && g_selected_sound_stream_failed) { return NullSound::GetName(); } From ebd0ee439e4ea2ed5a37b28590e10cceb3c68ddd Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 29 Dec 2020 18:29:06 +0200 Subject: [PATCH 41/56] Fixing merge part 1 --- Source/Core/AudioCommon/AudioCommon.cpp | 7 +++++++ Source/Core/Core/ConfigManager.cpp | 4 ---- Source/Core/Core/HW/DVD/DVDInterface.cpp | 2 +- Source/Core/Core/HW/WiimoteEmu/Speaker.cpp | 2 +- Source/Core/Core/HW/WiimoteEmu/Speaker.h | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 20cd4c18ba4e..11afc45662b1 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -111,6 +111,13 @@ void PostInitSoundStream() // and true again, so basically the backend is "restarted" with every emulation state change SetSoundStreamRunning(true, true); + //To review: are these still needed after merge with master? + // Ideally these two calls would be done in AudioInterface::Init so that we don't + // need to have a dependency on AudioInterface here, but this has to be done + // after creating g_sound_stream (above) and before starting audio dumping (below) + g_sound_stream->GetMixer()->SetDMAInputSampleRate(AudioInterface::GetAIDSampleRate()); + g_sound_stream->GetMixer()->SetStreamingInputSampleRate(AudioInterface::GetAISSampleRate()); + if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_started) StartAudioDump(); } diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 3759f35f04e6..ce21780b224c 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -775,10 +775,6 @@ void SConfig::LoadDefaults() bUsePanicHandlers = true; bOnScreenDisplayMessages = true; - m_analytics_id = ""; - m_analytics_enabled = false; - m_analytics_permission_asked = false; - bLoopFifoReplay = true; bJITOff = false; // debugger only settings diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 6549dba2428d..2df86c5e1eb4 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -313,7 +313,7 @@ static void DTKStreamingCallback(DIInterruptType interrupt_type, const std::vect // Determine which audio data to read next. // 168 samples (3.5ms at 48kHz/5.25ms at 32kHz on Wii, similar numbers on GC) const u32 maximum_samples = sample_rate / 2000 * 7; - + //To review: it also later one was: static const int MAXIMUM_SAMPLES = 168; u64 read_offset = 0; u32 read_length = 0; diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp index 8e7b411f64a0..1a9e9fb34208 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp @@ -245,7 +245,7 @@ int SpeakerLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) if (0x00 == addr) { - SpeakerData(data_in, count); + SpeakerData(data_in, count, m_speaker_pan_setting.GetValue() / 100); return count; } else diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.h b/Source/Core/Core/HW/WiimoteEmu/Speaker.h index d1ce2c428ae7..b7fb44e83961 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.h +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.h @@ -33,7 +33,7 @@ class SpeakerLogic : public I2CSlave void DoState(PointerWrap& p); private: - void SpeakerData(const u8* data, int length); + void SpeakerData(const u8* data, int length, float speaker_pan); // TODO: enum class static const u8 DATA_FORMAT_ADPCM = 0x00; From fbc4091820c5859372527575bafe9444af4e55e8 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Mon, 26 Feb 2018 23:37:05 +0000 Subject: [PATCH 42/56] Added a Hotkey to temporarly enable Audio Stretching. If bound to the same key as "Disable Emulation Speed Limit" and the machine is capable of running the game at 100% speed, it can be used as a workaround for bug: https://bugs.dolphin-emu.org/issues/10254 --- Source/Core/AudioCommon/Mixer.cpp | 1 + Source/Core/Core/HotkeyManager.cpp | 3 ++- Source/Core/Core/HotkeyManager.h | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index ef9a655876af..4ee52c51e0b7 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -409,6 +409,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) // Stop using emulation speed, start using actual speed for audio playback, if we fell behind enough // Filter out small inaccuracies (samples aren't submitted with perfect timing) else + if (SConfig::GetInstance().m_audio_stretch || Core::GetIsAudioStretchTempEnabled()) { const double audio_emu_speed_tolerance = SConfig::GetInstance().m_audio_emu_speed_tolerance / 1000.0; diff --git a/Source/Core/Core/HotkeyManager.cpp b/Source/Core/Core/HotkeyManager.cpp index f9c02e02e9be..1fc925603ee5 100644 --- a/Source/Core/Core/HotkeyManager.cpp +++ b/Source/Core/Core/HotkeyManager.cpp @@ -45,6 +45,7 @@ constexpr std::array s_hotkey_labels{{ _trans("Decrease Emulation Speed"), _trans("Increase Emulation Speed"), _trans("Disable Emulation Speed Limit"), + _trans("Enable Audio Stretching"), _trans("Frame Advance"), _trans("Frame Advance Decrease Speed"), @@ -312,7 +313,7 @@ struct HotkeyGroupInfo constexpr std::array s_groups_info = { {{_trans("General"), HK_OPEN, HK_REQUEST_GOLF_CONTROL}, {_trans("Volume"), HK_VOLUME_DOWN, HK_VOLUME_TOGGLE_MUTE}, - {_trans("Emulation Speed"), HK_DECREASE_EMULATION_SPEED, HK_TOGGLE_THROTTLE}, + {_trans("Emulation Speed"), HK_DECREASE_EMULATION_SPEED, HK_TOGGLE_AUDIO_STRETCHING}, {_trans("Frame Advance"), HK_FRAME_ADVANCE, HK_FRAME_ADVANCE_RESET_SPEED}, {_trans("Movie"), HK_START_RECORDING, HK_READ_ONLY_MODE}, {_trans("Stepping"), HK_STEP, HK_SKIP}, diff --git a/Source/Core/Core/HotkeyManager.h b/Source/Core/Core/HotkeyManager.h index 66732f7d24ce..0a194e9679c2 100644 --- a/Source/Core/Core/HotkeyManager.h +++ b/Source/Core/Core/HotkeyManager.h @@ -40,6 +40,7 @@ enum Hotkey HK_DECREASE_EMULATION_SPEED, HK_INCREASE_EMULATION_SPEED, HK_TOGGLE_THROTTLE, + HK_TOGGLE_AUDIO_STRETCHING, HK_FRAME_ADVANCE, HK_FRAME_ADVANCE_DECREASE_SPEED, From ee791b475b4a550c7ad5e23aacae2fe4ffdd9b3d Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 29 Dec 2020 19:08:39 +0200 Subject: [PATCH 43/56] Fixing merge part 2 --- Source/Core/Core/HotkeyManager.cpp | 3 +-- Source/Core/Core/HotkeyManager.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/Core/Core/HotkeyManager.cpp b/Source/Core/Core/HotkeyManager.cpp index 1fc925603ee5..f9c02e02e9be 100644 --- a/Source/Core/Core/HotkeyManager.cpp +++ b/Source/Core/Core/HotkeyManager.cpp @@ -45,7 +45,6 @@ constexpr std::array s_hotkey_labels{{ _trans("Decrease Emulation Speed"), _trans("Increase Emulation Speed"), _trans("Disable Emulation Speed Limit"), - _trans("Enable Audio Stretching"), _trans("Frame Advance"), _trans("Frame Advance Decrease Speed"), @@ -313,7 +312,7 @@ struct HotkeyGroupInfo constexpr std::array s_groups_info = { {{_trans("General"), HK_OPEN, HK_REQUEST_GOLF_CONTROL}, {_trans("Volume"), HK_VOLUME_DOWN, HK_VOLUME_TOGGLE_MUTE}, - {_trans("Emulation Speed"), HK_DECREASE_EMULATION_SPEED, HK_TOGGLE_AUDIO_STRETCHING}, + {_trans("Emulation Speed"), HK_DECREASE_EMULATION_SPEED, HK_TOGGLE_THROTTLE}, {_trans("Frame Advance"), HK_FRAME_ADVANCE, HK_FRAME_ADVANCE_RESET_SPEED}, {_trans("Movie"), HK_START_RECORDING, HK_READ_ONLY_MODE}, {_trans("Stepping"), HK_STEP, HK_SKIP}, diff --git a/Source/Core/Core/HotkeyManager.h b/Source/Core/Core/HotkeyManager.h index 0a194e9679c2..66732f7d24ce 100644 --- a/Source/Core/Core/HotkeyManager.h +++ b/Source/Core/Core/HotkeyManager.h @@ -40,7 +40,6 @@ enum Hotkey HK_DECREASE_EMULATION_SPEED, HK_INCREASE_EMULATION_SPEED, HK_TOGGLE_THROTTLE, - HK_TOGGLE_AUDIO_STRETCHING, HK_FRAME_ADVANCE, HK_FRAME_ADVANCE_DECREASE_SPEED, From 69b6a1c4df7ed45e05e6133a568d90bfa901f9b5 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 29 Dec 2020 19:46:26 +0200 Subject: [PATCH 44/56] Fixing merge part 3 (it now builds) --- Source/Core/DolphinQt/Settings/AudioPane.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 91d282e21c72..e2d38c2ff874 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -201,9 +201,9 @@ void AudioPane::CreateWidgets() m_emu_speed_tolerance_slider = new QSlider(Qt::Horizontal); m_emu_speed_tolerance_indicator = new QLabel(); QSize min_size = m_emu_speed_tolerance_indicator->minimumSize(); - min_size.setWidth( - std::max(std::max(font_metrics.width(tr("Disabled")), font_metrics.width(tr("None"))), - font_metrics.width(tr("%1 ms").arg(125)))); + min_size.setWidth(std::max(std::max(font_metrics.horizontalAdvance(tr("Disabled")), + font_metrics.horizontalAdvance(tr("None"))), + font_metrics.horizontalAdvance(tr("%1 ms").arg(125)))); m_emu_speed_tolerance_indicator->setMinimumSize(min_size); m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); //To review: maybe have a set of 4 or 5 options to keep it simpler (low, high, ...). @@ -245,7 +245,7 @@ void AudioPane::CreateWidgets() int max_width = 0; for (int i = 0; i <= max_dolby_quality; ++i) { - max_width = std::max(max_width, font_metrics.width(GetDPL2QualityAndLatencyLabel( + max_width = std::max(max_width, font_metrics.horizontalAdvance(GetDPL2QualityAndLatencyLabel( AudioCommon::DPL2Quality(max_dolby_quality)))); } min_size.setWidth(max_width); From 1ed6b0b18171650599b2ec5a35438121862d0793 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 29 Dec 2020 20:57:26 +0200 Subject: [PATCH 45/56] Improved DVDInterface comments --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 2df86c5e1eb4..6f87d7d5f74b 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -305,15 +305,17 @@ static void DTKStreamingCallback(DIInterruptType interrupt_type, const std::vect //To review: here we used to just hardcode 48Khz, ignoring wether we are wii or gc (and also avoiding it being double). old comment: // This determines which audio data to read next. // Similarly to DMA audio, instead of sending a different number of samples each submission, - // we just change the frequency of the submission to match the sample rate. This causes more - // imprecision on GC, where sample rates aren't integer, but it should mostly be fine. + // we just change the frequency of the submission to match the sample rate. + // This is the only way to resolve floating point sample rates from the GC without precision loss. // The Mixer (Mixer.cpp) might assume the above is true, so if you change the samples submitted // per submission, make sure you review the Mixer as well. // Determine which audio data to read next. - // 168 samples (3.5ms at 48kHz/5.25ms at 32kHz on Wii, similar numbers on GC) const u32 maximum_samples = sample_rate / 2000 * 7; //To review: it also later one was: static const int MAXIMUM_SAMPLES = 168; + // 168 samples (3.5ms at 48kHz/5.25ms at 32kHz on Wii, similar numbers on GC). + // This number isn't HW tested, it was found randomly. + static const int MAXIMUM_SAMPLES = 168; u64 read_offset = 0; u32 read_length = 0; From f23d6ffee69c4651408f4ca955e4fa85d50bedbd Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 30 Dec 2020 01:50:07 +0200 Subject: [PATCH 46/56] Updated all newer logs to use FMT --- Source/Core/AudioCommon/AudioCommon.cpp | 10 ++--- Source/Core/AudioCommon/Mixer.cpp | 10 ++--- Source/Core/AudioCommon/OpenALStream.cpp | 4 +- Source/Core/AudioCommon/SurroundDecoder.cpp | 14 +++--- Source/Core/AudioCommon/WASAPIStream.cpp | 48 +++++++++++---------- Source/Core/Core/HW/DVD/DVDInterface.cpp | 4 +- 6 files changed, 47 insertions(+), 43 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 11afc45662b1..f6cff25e7db4 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -293,7 +293,7 @@ unsigned long GetOSMixerSampleRate() HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (result != RPC_E_CHANGED_MODE && FAILED(result)) { - ERROR_LOG(AUDIO, "Failed to initialize the COM library"); + ERROR_LOG_FMT(AUDIO, "Failed to initialize the COM library"); return 0; } Common::ScopeGuard uninit([result] { @@ -312,7 +312,7 @@ unsigned long GetOSMixerSampleRate() { _com_error err(result); std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); - ERROR_LOG(AUDIO, "Failed to create MMDeviceEnumerator: (%s)", error.c_str()); + ERROR_LOG_FMT(AUDIO, "Failed to create MMDeviceEnumerator: ({})", error); return 0; } @@ -322,7 +322,7 @@ unsigned long GetOSMixerSampleRate() { _com_error err(result); std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); - ERROR_LOG(AUDIO, "Failed to obtain default endpoint: (%s)", error.c_str()); + ERROR_LOG_FMT((AUDIO, "Failed to obtain default endpoint: ({})", error); enumerator->Release(); return 0; } @@ -334,7 +334,7 @@ unsigned long GetOSMixerSampleRate() { _com_error err(result); std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); - ERROR_LOG(AUDIO, "Failed to reactivate IAudioClient: (%s)", error.c_str()); + ERROR_LOG_FMT((AUDIO, "Failed to reactivate IAudioClient: ({})", error); device->Release(); enumerator->Release(); return 0; @@ -349,7 +349,7 @@ unsigned long GetOSMixerSampleRate() { _com_error err(result); std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); - ERROR_LOG(AUDIO, "Failed to retrieve the mixer format: (%s)", error.c_str()); + ERROR_LOG_FMT(AUDIO, "Failed to retrieve the mixer format: ({})", error); // Return the default Dolphin sample rate, hoping it would work sample_rate = GetDefaultSampleRate(); } diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 4ee52c51e0b7..b99009f3d2ed 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -113,7 +113,7 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) s32 post_mix_samples = SamplesDifference(indexW, indexR, rate, m_fract.load()) / NC; post_mix_samples -= num_samples * rate + INTERP_SAMPLES; double latency = std::max(post_mix_samples, 0) / input_sample_rate; - INFO_LOG(AUDIO, "latency: %lf", latency); + INFO_LOG_FMT(AUDIO, "latency: {}", latency); // This isn't big enough to notice but it is enough to make a difference and recover latency AdjustSpeedByLatency(latency, 0.0, m_mixer->GetMinLatency(), m_mixer->GetMaxLatency(), @@ -387,7 +387,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) bool predicting = true; // Set predicting to false if we are not predicting (meaning the last samples push isn't late) double actual_speed = m_dma_speed.GetLastSpeed(predicting, true); - //INFO_LOG(AUDIO, "dma_mixer current speed: %lf", average_actual_speed); + //INFO_LOG_FMT(AUDIO, "dma_mixer current speed: {}", average_actual_speed); double target_speed = emulation_speed; @@ -461,7 +461,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) //as we'd use an average too long static bool use_new_average = true; target_speed = use_new_average ? m_dma_speed.GetCachedAverageSpeed(true, true, true) : average_actual_speed; - INFO_LOG(AUDIO, " actual_speed: %f average_actual_speed: %f", actual_speed, average_actual_speed); + INFO_LOG_FMT(AUDIO, " actual_speed: {} average_actual_speed: {}", actual_speed, average_actual_speed); m_time_at_custom_speed = m_time_at_custom_speed + time_delta; } else @@ -506,7 +506,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) // immediately. Not that this isn't the whole stretcher latency, there is also the unprocessed // part which we can't control const double latency = m_stretcher.GetProcessedLatency(); - INFO_LOG(AUDIO, "latency: %lf", latency); + INFO_LOG_FMT(AUDIO, "latency: {}", latency); const double acceptable_latency = m_stretcher.GetAcceptableLatency() - time_delta; const double min_latency = m_min_latency + acceptable_latency; const double max_latency = m_max_latency + acceptable_latency; @@ -793,7 +793,7 @@ void Mixer::PushDMASamples(const s16* samples, u32 num_samples) m_dma_speed.CacheAverageSpeed(true, m_time_at_custom_speed); static bool PrintPushedSamples = true; - if (PrintPushedSamples) INFO_LOG(AUDIO, "dma_mixer added samples: %u, speed: %lf", num_samples, m_dma_speed.GetCachedAverageSpeed()); + if (PrintPushedSamples) INFO_LOG_FMT(AUDIO, "dma_mixer added samples: {}, speed: {}", num_samples, m_dma_speed.GetCachedAverageSpeed()); m_dma_mixer.PushSamples(samples, num_samples); int sample_rate = m_dma_mixer.GetRoundedInputSampleRate(); diff --git a/Source/Core/AudioCommon/OpenALStream.cpp b/Source/Core/AudioCommon/OpenALStream.cpp index 2670b0b2fbd9..a4e279632e1c 100644 --- a/Source/Core/AudioCommon/OpenALStream.cpp +++ b/Source/Core/AudioCommon/OpenALStream.cpp @@ -254,7 +254,7 @@ void OpenALStream::SoundLoop() if (frames_per_buffer > OAL_MAX_FRAMES) frames_per_buffer = OAL_MAX_FRAMES; - INFO_LOG_FMT(AUDIO, "Using {} buffers, each with {} audio frames for a total of {}.", OAL_BUFFERS, + INFO_LOG_FMT(AUDIO, "Using {} buffers, each with {} audio frames for a total of {}", OAL_BUFFERS, frames_per_buffer, frames_per_buffer * OAL_BUFFERS); // Should we make these larger just in case the mixer ever sends more samples @@ -362,7 +362,7 @@ void OpenALStream::SoundLoop() { // 5.1 is not supported by the host, fallback to stereo in the next audio frame WARN_LOG_FMT(AUDIO, "Unable to set 5.1 surround mode, falling back to 2.0. Updating OpenAL " - "Soft might fix this issue."); + "Soft might fix this issue"); m_use_surround = false; } } diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index cf8ab91bebcc..581f7dd385c7 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -236,7 +236,7 @@ void SurroundDecoder::GetDecodedSamples(float* out, u32 num_samples) v3.push_back(count); if (count > max_count) { - // INFO_LOG(AUDIO_INTERFACE, "count is broken %u", count); + //INFO_LOG_FMT(AUDIO_INTERFACE, "count is broken {}", count); max_count = count; } } @@ -245,7 +245,7 @@ void SurroundDecoder::GetDecodedSamples(float* out, u32 num_samples) } } } - INFO_LOG(AUDIO_INTERFACE, "max_count %u", max_count); + INFO_LOG_FMT(AUDIO_INTERFACE, "max_count {}", max_count); //To do: scale m_decoded_fifo to the bigger multiple of the new block size that it can contain? //and m_float_conversion_buffer to the min between prev num and new block size. @@ -288,11 +288,11 @@ void SurroundDecoder::GetDecodedSamples(float* out, u32 num_samples) s32 lcm3 = MathUtil::LCM(num_samples, m_block_size - num_samples); s32 lcm4 = MathUtil::LCM(m_block_size - num_samples, m_block_size); s32 lcm5 = MathUtil::LCM(num_samples - m_block_size, num_samples); - INFO_LOG(AUDIO_INTERFACE, - "backend_latency %u block_size %u " - "added_dpl2_latency %u ga_dpl2_latency1 %u ga_dpl2_latency2 " - "%u ga_dpl2_latency3 %u, gad_dpl2_latency4 %u LCM1 %u LCM2 %u LCM3 %u LCM4 " - "%u LCM5 %u", + INFO_LOG_FMT(AUDIO_INTERFACE, + "backend_latency {} block_size {} " + "added_dpl2_latency {} ga_dpl2_latency1 {} ga_dpl2_latency2 " + "{} ga_dpl2_latency3 {}, gad_dpl2_latency4 {} LCM1 {} LCM2 {} LCM3 {} LCM4 " + "{} LCM5 {}", num_samples, m_block_size, added_dpl2_latency, guessed_added_dpl2_latency1, guessed_added_dpl2_latency2, guessed_added_dpl2_latency3, guessed_added_dpl2_latency4, lcm1, lcm2, lcm3, lcm4, lcm5); diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 86172da51814..1aa6fb70a3b2 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -40,7 +40,7 @@ class AutoCoInit result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); // It's very rare for this to happen so we don't append the consequences of the error if (result != RPC_E_CHANGED_MODE && FAILED(result)) - ERROR_LOG(AUDIO, "WASAPI: Failed to initialize the COM library"); + ERROR_LOG_FMT(AUDIO, "WASAPI: Failed to initialize the COM library"); } ~AutoCoInit() { @@ -69,7 +69,7 @@ class AutoPrintMsg " Hz PCM audio." "\nWASAPI exclusive mode (event driven) won't work"; - ERROR_LOG(AUDIO, message.c_str()); + ERROR_LOG_FMT(AUDIO, message.c_str()); OSD::AddMessage(message, 6000U, OSD::Color::RED); } } @@ -449,8 +449,8 @@ std::vector WASAPIStream::GetSelectedDeviceSampleRates() device = GetDeviceByName(SConfig::GetInstance().sWASAPIDeviceName); if (!device) { - ERROR_LOG(AUDIO, "WASAPI: Can't find device '%s', falling back to default", - SConfig::GetInstance().sWASAPIDeviceName.c_str()); + ERROR_LOG_FMT(AUDIO, "WASAPI: Can't find device '{}', falling back to default", + SConfig::GetInstance().sWASAPIDeviceName); result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); } } @@ -545,8 +545,9 @@ bool WASAPIStream::SetRestartFromResult(HRESULT result) // This can be triggered by changing the "Spacial sound" settings or the sample rate if (result == AUDCLNT_E_DEVICE_INVALIDATED || result == AUDCLNT_E_RESOURCES_INVALIDATED) { - WARN_LOG(AUDIO, "WASAPI: The audio device has either been removed or a setting has recently " - "changed and Windows is recomputing it. Restarting WASAPI"); + WARN_LOG_FMT(AUDIO, + "WASAPI: The audio device has either been removed or a setting has recently " + "changed and Windows is recomputing it. Restarting WASAPI"); // Doing this here is definitely a hack, the correct way would be to use IMMNotificationClient // but it seems more complicated to use, relying on errors is just simpler m_should_restart = true; @@ -749,10 +750,10 @@ bool WASAPIStream::SetRunning(bool running) // Fallback to stereo if surround 5.1 is not supported if (m_surround && result == AUDCLNT_E_UNSUPPORTED_FORMAT) { - WARN_LOG(AUDIO, - "WASAPI: Your current audio device doesn't support 5.1 16-bit %u Hz" - " PCM audio. Will fallback to 2.0 16-bit", - GetMixer()->GetSampleRate()); + WARN_LOG_FMT(AUDIO, + "WASAPI: Your current audio device doesn't support 5.1 16-bit {} Hz" + " PCM audio. Will fallback to 2.0 16-bit", + GetMixer()->GetSampleRate()); m_surround = false; @@ -770,8 +771,8 @@ bool WASAPIStream::SetRunning(bool running) if (result == AUDCLNT_E_UNSUPPORTED_FORMAT) { - WARN_LOG(AUDIO, - "WASAPI: Your current audio device doesn't support 2.0 16-bit %u Hz" + WARN_LOG_FMT(AUDIO, + "WASAPI: Your current audio device doesn't support 2.0 16-bit {} Hz" " PCM audio. Exclusive mode (event driven) won't work", GetMixer()->GetSampleRate()); @@ -805,8 +806,8 @@ bool WASAPIStream::SetRunning(bool running) return false; } - WARN_LOG(AUDIO, "WASAPI: Your current audio device doesn't support the latency (period)" - " you specified. Falling back to default device latency"); + WARN_LOG_FMT(AUDIO, "WASAPI: Your current audio device doesn't support the latency (period)" + " you specified. Falling back to default device latency"); // m_frames_in_buffer should already "include" our previously attempted latency, // so we don't need to multiply by our target latency again @@ -819,7 +820,7 @@ bool WASAPIStream::SetRunning(bool running) } // Show this even if we failed, just for information to the user - INFO_LOG(AUDIO, "WASAPI: Sample Rate: %u, Channels: %u, Audio Period: %ims", + INFO_LOG_FMT(AUDIO, "WASAPI: Sample Rate: {}, Channels: {}, Audio Period: {}ms", m_format.Format.nSamplesPerSec, m_format.Format.nChannels, device_period / 10000); if (SetRestartFromResult(result) || !HandleWinAPI("Failed to initialize IAudioClient", result)) @@ -883,8 +884,9 @@ bool WASAPIStream::SetRunning(bool running) if (device_frequency != m_format.Format.nSamplesPerSec) { // m_audio_clock->GetPosition would return unreliable values - WARN_LOG(AUDIO, "WASAPI: The device frequence is different from our sample rate, we can't" - " keep them in sync. WASAPI will still work"); + WARN_LOG_FMT(AUDIO, + "WASAPI: The device frequence is different from our sample rate, we can't" + " keep them in sync. WASAPI will still work"); m_audio_clock->Release(); m_audio_clock = nullptr; } @@ -1030,8 +1032,8 @@ void WASAPIStream::SoundLoop() { // We don't set m_should_restart to true here because // we can't guarantee it wouldn't constantly fail again - ERROR_LOG(AUDIO, "WASAPI: WaitForMultipleObjects failed. Stopping sound playback." - " Pausing and unpausing the emulation might fix the problem"); + ERROR_LOG_FMT(AUDIO, "WASAPI: WaitForMultipleObjects failed. Stopping sound playback." + " Pausing and unpausing the emulation might fix the problem"); break; } @@ -1111,10 +1113,10 @@ void WASAPIStream::SoundLoop() if (SUCCEEDED(result) && device_samples_position >= submitted_samples) { - WARN_LOG(AUDIO, "WASAPI: Fell out of sync with the device period as we could not compute" - " samples in time. " - "Restarting WASAPI to avoid crackling. If this keeps happening," - " increase your backend latency"); + WARN_LOG_FMT(AUDIO, + "WASAPI: Fell out of sync with the device period as we could not compute" + " samples in time. Restarting WASAPI to avoid crackling." + " If this keeps happening, increase your backend latency"); // Don't check more than roughly once per second, as this could trigger a lot otherwise m_wait_next_desync_check = m_format.Format.nSamplesPerSec; diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 6f87d7d5f74b..e0a20b3b4c27 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -314,7 +314,9 @@ static void DTKStreamingCallback(DIInterruptType interrupt_type, const std::vect const u32 maximum_samples = sample_rate / 2000 * 7; //To review: it also later one was: static const int MAXIMUM_SAMPLES = 168; // 168 samples (3.5ms at 48kHz/5.25ms at 32kHz on Wii, similar numbers on GC). - // This number isn't HW tested, it was found randomly. + // This number isn't HW tested, it was decided randomly. + // Real HW might read 32KB at a time, increasing the latency, which would mean our DVD audio + // could be slightly desynchronized from the video, at least compared to real HW. static const int MAXIMUM_SAMPLES = 168; u64 read_offset = 0; u32 read_length = 0; From ca4951d1cc2c867d92aaa2aad25256fedebc46d0 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 30 Dec 2020 02:04:08 +0200 Subject: [PATCH 47/56] More FMT LOG fixes --- Source/Core/AudioCommon/AudioCommon.cpp | 12 ++++++------ Source/Core/AudioCommon/WASAPIStream.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index f6cff25e7db4..2e69505fa385 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -311,7 +311,7 @@ unsigned long GetOSMixerSampleRate() if (result != S_OK) { _com_error err(result); - std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); + std::string error = TStrToUTF8(err.ErrorMessage()); ERROR_LOG_FMT(AUDIO, "Failed to create MMDeviceEnumerator: ({})", error); return 0; } @@ -321,8 +321,8 @@ unsigned long GetOSMixerSampleRate() if (result != S_OK) { _com_error err(result); - std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); - ERROR_LOG_FMT((AUDIO, "Failed to obtain default endpoint: ({})", error); + std::string error = TStrToUTF8(err.ErrorMessage()); + ERROR_LOG_FMT(AUDIO, "Failed to obtain default endpoint: ({})", error); enumerator->Release(); return 0; } @@ -333,8 +333,8 @@ unsigned long GetOSMixerSampleRate() if (result != S_OK) { _com_error err(result); - std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); - ERROR_LOG_FMT((AUDIO, "Failed to reactivate IAudioClient: ({})", error); + std::string error = TStrToUTF8(err.ErrorMessage()); + ERROR_LOG_FMT(AUDIO, "Failed to reactivate IAudioClient: ({})", error); device->Release(); enumerator->Release(); return 0; @@ -348,7 +348,7 @@ unsigned long GetOSMixerSampleRate() if (result != S_OK) { _com_error err(result); - std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); + std::string error = TStrToUTF8(err.ErrorMessage()); ERROR_LOG_FMT(AUDIO, "Failed to retrieve the mixer format: ({})", error); // Return the default Dolphin sample rate, hoping it would work sample_rate = GetDefaultSampleRate(); diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 1aa6fb70a3b2..24b6ba4883b0 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -69,7 +69,7 @@ class AutoPrintMsg " Hz PCM audio." "\nWASAPI exclusive mode (event driven) won't work"; - ERROR_LOG_FMT(AUDIO, message.c_str()); + ERROR_LOG_FMT(AUDIO, "{}", message); OSD::AddMessage(message, 6000U, OSD::Color::RED); } } From 745df5ae563b188368ccc7bd2d869955e882652a Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 30 Dec 2020 14:42:17 +0200 Subject: [PATCH 48/56] Merging and fixes --- Source/Core/DolphinQt/Settings/AudioPane.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index e2d38c2ff874..98ef7e34115a 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -245,8 +245,9 @@ void AudioPane::CreateWidgets() int max_width = 0; for (int i = 0; i <= max_dolby_quality; ++i) { + // This will work under any DPI. Adding 1 to the end result might fix some edge cases max_width = std::max(max_width, font_metrics.horizontalAdvance(GetDPL2QualityAndLatencyLabel( - AudioCommon::DPL2Quality(max_dolby_quality)))); + AudioCommon::DPL2Quality(i)))); } min_size.setWidth(max_width); m_dolby_quality_latency_label->setMinimumSize(min_size); From f7d88e1ea2e0af85dd901525517d0b143aebdd6b Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 30 Dec 2020 19:01:15 +0200 Subject: [PATCH 49/56] Updates from other PRs --- Source/Core/Common/FixedSizeQueue.h | 6 +++--- Source/Core/Core/Core.cpp | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Source/Core/Common/FixedSizeQueue.h b/Source/Core/Common/FixedSizeQueue.h index 6a0d5ac74235..217e68323004 100644 --- a/Source/Core/Common/FixedSizeQueue.h +++ b/Source/Core/Common/FixedSizeQueue.h @@ -47,7 +47,7 @@ class FixedSizeQueue tail = (tail + 1) % N; } - // Copies over an array, loops over of num is greater than max_size + // Copies over an array, loops over if num is greater than max_size void push_array(const T* t, size_t num) { size_t back_writable_num = std::min(tail_to_end(), num); @@ -158,9 +158,9 @@ class FixedSizeQueue const T& beginning() const noexcept { return storage[0]; } size_t size() const noexcept { return count; } size_t max_size() const noexcept { return N; } - // Helper to know how many more samples we could read before needing to loop over + // Helper to know how many more items we could read before needing to loop over size_t head_to_end() const noexcept { return N - head; } - // Helper to know how many more samples we could write before needing to loop over + // Helper to know how many more items we could write before needing to loop over size_t tail_to_end() const noexcept { return N - tail; } bool empty() const noexcept { return size() == 0; } diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index a3f70365846f..03e914965db0 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -532,9 +532,6 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi HW::Shutdown(); INFO_LOG_FMT(CONSOLE, "{}", StopMessage(false, "HW shutdown")); - // Clear on screen messages that haven't expired - OSD::ClearMessages(); - // The config must be restored only after the whole HW has shut down, // not when it is still running. BootManager::RestoreConfig(); From 4abec4811233ae695ed4f75883aa34119d4146e8 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sat, 16 Jan 2021 23:33:51 +0200 Subject: [PATCH 50/56] WASAPI: sent silent buffer flag if volume is 0 Hopefully it does some internal optimization. --- Source/Core/AudioCommon/WASAPIStream.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 24b6ba4883b0..4801d1c7a396 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -1039,11 +1039,11 @@ void WASAPIStream::SoundLoop() // We could as well override the SoundStream::SetVolume(int) function float volume = SConfig::GetInstance().m_IsMuted ? 0.f : SConfig::GetInstance().m_Volume / 100.f; + // Ignore them in case they fail if (m_simple_audio_volume) { float master_volume = 1.f; BOOL mute = false; - // Ignore them in case they fail m_simple_audio_volume->GetMasterVolume(&master_volume); m_simple_audio_volume->GetMute(&mute); volume *= mute ? 0.f : master_volume; @@ -1095,7 +1095,8 @@ void WASAPIStream::SoundLoop() // If the device period was set too low, the above computations might take longer // that the latency, so we'd lose this audio frame - m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, 0); + m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, + volume <= 0.f ? AUDCLNT_BUFFERFLAGS_SILENT : 0); submitted_samples += m_frames_in_buffer; m_wait_next_desync_check -= m_frames_in_buffer; From e9082d5413abae1b5ccd7cbcac955f8bb9b3bc48 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sun, 14 Feb 2021 00:31:03 +0200 Subject: [PATCH 51/56] use std memory functions. --- Source/Core/AudioCommon/Mixer.cpp | 20 ++++++++++---------- Source/Core/AudioCommon/SurroundDecoder.cpp | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index b99009f3d2ed..dd03c48a13e2 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -368,7 +368,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) return 0; if (m_dma_speed.IsPaused()) { - memset(samples, 0, num_samples * NC * sizeof(samples[0])); + std::memset(samples, 0, num_samples * NC * sizeof(samples[0])); return 0; } u32 original_num_samples = num_samples; @@ -573,12 +573,12 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) if (scratch_buffer_equal_samples) { samples = m_scratch_buffer.data(); // m_scratch_buffer might have been re-allocated - memset(samples, 0, std::max(available_samples, num_samples) * NC * sizeof(samples[0])); + std::memset(samples, 0, std::max(available_samples, num_samples) * NC * sizeof(samples[0])); } else { - memset(samples, 0, num_samples * NC * sizeof(samples[0])); - memset(m_scratch_buffer.data(), 0, available_samples * NC * sizeof(m_scratch_buffer[0])); + std::memset(samples, 0, num_samples * NC * sizeof(samples[0])); + std::memset(m_scratch_buffer.data(), 0, available_samples * NC * sizeof(m_scratch_buffer[0])); } m_dma_mixer.Mix(m_scratch_buffer.data(), available_samples, true); @@ -593,7 +593,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) } else { - memset(samples, 0, num_samples * NC * sizeof(samples[0])); + std::memset(samples, 0, num_samples * NC * sizeof(samples[0])); if (m_stretching) { @@ -626,7 +626,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) u32 Mixer::MixSurround(float* samples, u32 num_samples) { - memset(samples, 0, num_samples * SURROUND_CHANNELS * sizeof(samples[0])); + std::memset(samples, 0, num_samples * SURROUND_CHANNELS * sizeof(samples[0])); // Our latency might have increased m_scratch_buffer.reserve(num_samples * NC); @@ -757,12 +757,12 @@ void Mixer::MixerFifo::PushSamples(const s16* samples, u32 num_samples) if (over_bytes > 0) { auto bytes = num_samples * NC * size - over_bytes; - memcpy(&m_buffer[indexW & INDEX_MASK], samples, bytes); - memcpy(&m_buffer[0], samples + bytes / size, over_bytes); + std::memcpy(&m_buffer[indexW & INDEX_MASK], samples, bytes); + std::memcpy(&m_buffer[0], samples + bytes / size, over_bytes); } else { - memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * NC * size); + std::memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * NC * size); } m_indexW.fetch_add(num_samples * NC); @@ -1050,7 +1050,7 @@ void Mixer::MixerFifo::UpdatePush(double time) u32 num_samples = std::round(std::max(latency_to_add, 0.0) * m_input_sample_rate); num_samples = std::min(num_samples, MAX_SAMPLES); - memset(m_mixer->m_conversion_buffer, 0, + std::memset(m_mixer->m_conversion_buffer, 0, num_samples * NC * sizeof(m_mixer->m_conversion_buffer[0])); PushSamples(m_mixer->m_conversion_buffer, num_samples); } diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 581f7dd385c7..5fc7a35ad39a 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -125,7 +125,7 @@ void SurroundDecoder::Clear() m_float_conversion_buffer.clear(); m_returnable_samples_in_decoder = 0; latency_warning_sent = false; - memset(m_last_decoded_samples, 0.f, sizeof(m_last_decoded_samples)); + std::memset(m_last_decoded_samples, 0.f, sizeof(m_last_decoded_samples)); } void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) @@ -267,7 +267,7 @@ void SurroundDecoder::GetDecodedSamples(float* out, u32 num_samples) if (read_samples > 0) { m_decoded_fifo.erase(read_samples); - memcpy(&m_last_decoded_samples[0], &out[read_samples - SURROUND_CHANNELS], + std::memcpy(&m_last_decoded_samples[0], &out[read_samples - SURROUND_CHANNELS], sizeof(float) * SURROUND_CHANNELS); } @@ -302,7 +302,7 @@ void SurroundDecoder::GetDecodedSamples(float* out, u32 num_samples) // it should always keep that number, unless the backend latency changes while (read_samples < num_samples * SURROUND_CHANNELS) { - memcpy(&out[read_samples], &m_last_decoded_samples[0], sizeof(float) * SURROUND_CHANNELS); + std::memcpy(&out[read_samples], &m_last_decoded_samples[0], sizeof(float) * SURROUND_CHANNELS); read_samples += SURROUND_CHANNELS; } } From 56efd3e56ab0439c3ddbed08a4a07cb08aa8527b Mon Sep 17 00:00:00 2001 From: Filoppi Date: Fri, 14 May 2021 13:10:37 +0300 Subject: [PATCH 52/56] Post merge fixup --- Source/Core/AudioCommon/AudioCommon.cpp | 8 ++++---- Source/Core/AudioCommon/CubebStream.cpp | 2 +- Source/Core/AudioCommon/Mixer.cpp | 7 +------ Source/Core/AudioCommon/WASAPIStream.cpp | 14 +++++++------- Source/Core/AudioCommon/WaveFile.cpp | 7 ++++--- Source/Core/Core/ConfigManager.cpp | 2 -- Source/Core/Core/ConfigManager.h | 11 +++++++++-- Source/Core/Core/Core.cpp | 10 +++------- Source/Core/Core/HW/AudioInterface.cpp | 1 + Source/Core/Core/HW/DVD/DVDInterface.cpp | 14 +++----------- Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp | 11 +++++++---- 11 files changed, 40 insertions(+), 47 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 2e69505fa385..5852868d9c04 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -87,16 +87,16 @@ void PostInitSoundStream() if (!g_sound_stream) { - WARN_LOG(AUDIO, "Unknown backend %s, using %s instead (default)", backend.c_str(), - GetDefaultSoundBackend().c_str()); + WARN_LOG_FMT(AUDIO, "Unknown backend {}, using {} instead (default)", backend, + GetDefaultSoundBackend()); backend = GetDefaultSoundBackend(); g_sound_stream = CreateSoundStreamForBackend(backend); } if (!g_sound_stream || !g_sound_stream->Init()) { - WARN_LOG(AUDIO, "Could not initialize backend %s, using %s instead", backend.c_str(), - NullSound::GetName().c_str()); + WARN_LOG_FMT(AUDIO, "Could not initialize backend {}, using {} instead", backend, + NullSound::GetName()); g_sound_stream = std::make_unique(); g_sound_stream->Init(); // NullSound can't fail g_selected_sound_stream_failed = true; diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index a19654576ef5..05cdb1044a3b 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -80,7 +80,7 @@ bool CubebStream::CreateStream() const u32 target_latency = AudioCommon::GetUserTargetLatency() / 1000.0 * params.rate; final_latency = std::clamp(target_latency, minimum_latency, 96000u); } - INFO_LOG(AUDIO, "Latency: %u frames", final_latency); + INFO_LOG_FMT(AUDIO, "Latency: {} frames", final_latency); // Note that cubeb latency might not be fixed and could dynamically adjust when the audio thread // can't keep up with itself, especially when using its internal mixer. diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index dd03c48a13e2..79b405079b93 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -33,7 +33,7 @@ Mixer::Mixer(u32 sample_rate) m_min_latency = Config::Get(Config::MAIN_AUDIO_MIXER_MIN_LATENCY) / 1000.0; m_max_latency = Config::Get(Config::MAIN_AUDIO_MIXER_MAX_LATENCY) / 1000.0; - INFO_LOG(AUDIO, "Mixer is initialized"); + INFO_LOG_FMT(AUDIO, "Mixer is initialized"); m_on_state_changed_handle = Core::AddOnStateChangedCallback([this](Core::State state) { if (state == Core::State::Paused) @@ -409,7 +409,6 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) // Stop using emulation speed, start using actual speed for audio playback, if we fell behind enough // Filter out small inaccuracies (samples aren't submitted with perfect timing) else - if (SConfig::GetInstance().m_audio_stretch || Core::GetIsAudioStretchTempEnabled()) { const double audio_emu_speed_tolerance = SConfig::GetInstance().m_audio_emu_speed_tolerance / 1000.0; @@ -867,12 +866,10 @@ void Mixer::StartLogDTKAudio(const std::string& filename) if (success) { m_log_dtk_audio = true; - m_wave_writer_dtk.SetSkipSilence(false); NOTICE_LOG_FMT(AUDIO, "Starting DTK Audio logging"); } else { - m_wave_writer_dtk.Stop(); NOTICE_LOG_FMT(AUDIO, "Unable to start DTK Audio logging"); } } @@ -904,12 +901,10 @@ void Mixer::StartLogDSPAudio(const std::string& filename) if (success) { m_log_dsp_audio = true; - m_wave_writer_dsp.SetSkipSilence(false); NOTICE_LOG_FMT(AUDIO, "Starting DSP Audio logging"); } else { - m_wave_writer_dsp.Stop(); NOTICE_LOG_FMT(AUDIO, "Unable to start DSP Audio logging"); } } diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp index 4801d1c7a396..60eb80572478 100644 --- a/Source/Core/AudioCommon/WASAPIStream.cpp +++ b/Source/Core/AudioCommon/WASAPIStream.cpp @@ -144,12 +144,12 @@ class CustomNotificationClient : public IMMNotificationClient WASAPIStream* m_stream; }; -static bool HandleWinAPI(std::string message, HRESULT result) +static bool HandleWinAPI(std::string_view message, HRESULT result) { if (result != S_OK) { _com_error err(result); - std::string error = TStrToUTF8(err.ErrorMessage()).c_str(); + std::string error = TStrToUTF8(err.ErrorMessage()); switch (result) { @@ -164,7 +164,7 @@ static bool HandleWinAPI(std::string message, HRESULT result) break; } - ERROR_LOG(AUDIO, "WASAPI: %s (%s)", message.c_str(), error.c_str()); + ERROR_LOG_FMT(AUDIO, "WASAPI: {}: {}", message, error); } return SUCCEEDED(result); @@ -673,8 +673,8 @@ bool WASAPIStream::SetRunning(bool running) // just got disconnected, but overall, this should be fine if (!device) { - ERROR_LOG(AUDIO, "WASAPI: Can't find device '%s', falling back to default", - SConfig::GetInstance().sWASAPIDeviceName.c_str()); + ERROR_LOG_FMT(AUDIO, "WASAPI: Can't find device '{}', falling back to default", + SConfig::GetInstance().sWASAPIDeviceName); result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); m_using_default_device = true; } @@ -697,7 +697,7 @@ bool WASAPIStream::SetRunning(bool running) device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); - INFO_LOG(AUDIO, "WASAPI: Using Audio Device '%s'", TStrToUTF8(device_name.pwszVal).c_str()); + INFO_LOG_FMT(AUDIO, "WASAPI: Using Audio Device '{}'", TStrToUTF8(device_name.pwszVal)); PropVariantClear(&device_name); @@ -907,7 +907,7 @@ bool WASAPIStream::SetRunning(bool running) device->Release(); - NOTICE_LOG(AUDIO, "WASAPI: Successfully initialized"); + NOTICE_LOG_FMT(AUDIO, "WASAPI: Successfully initialized!"); m_stop_thread_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); diff --git a/Source/Core/AudioCommon/WaveFile.cpp b/Source/Core/AudioCommon/WaveFile.cpp index 557392be268f..7960da68c71c 100644 --- a/Source/Core/AudioCommon/WaveFile.cpp +++ b/Source/Core/AudioCommon/WaveFile.cpp @@ -32,7 +32,7 @@ bool WaveFileWriter::Start(const std::string& file_name, u32 sample_rate) if (File::Exists(file_name)) { if (SConfig::GetInstance().m_DumpAudioSilent || - AskYesNoFmtT("Delete the existing file '{0}'?", filename)) + AskYesNoFmtT("Delete the existing file '{0}'?", file_name)) { File::Delete(file_name); } @@ -46,7 +46,8 @@ bool WaveFileWriter::Start(const std::string& file_name, u32 sample_rate) // Check if the file is already open if (file) { - PanicAlertFmtT("The file {0} was already open, the file header will not be written.", filename); + PanicAlertFmtT("The file {0} was already open, the file header will not be written.", + file_name); return false; } @@ -56,7 +57,7 @@ bool WaveFileWriter::Start(const std::string& file_name, u32 sample_rate) PanicAlertFmtT( "The file {0} could not be opened for writing. Please check if it's already opened " "by another program.", - filename); + file_name); return false; } diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index ce21780b224c..bd682dc6ecf5 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -772,8 +772,6 @@ void SConfig::LoadDefaults() iAudioBackendLatency = 20; m_audio_stretch = false; m_audio_emu_speed_tolerance = 20; - bUsePanicHandlers = true; - bOnScreenDisplayMessages = true; bLoopFifoReplay = true; diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 0e931effb51e..ddd8e3138fb5 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -48,6 +48,15 @@ enum SIDevices : int; struct BootParameters; +// DSP Backend Types +#define BACKEND_NULLSOUND _trans("No Audio Output") +#define BACKEND_ALSA "ALSA" +#define BACKEND_CUBEB "Cubeb" +#define BACKEND_OPENAL "OpenAL" +#define BACKEND_PULSEAUDIO "Pulse" +#define BACKEND_OPENSLES "OpenSLES" +#define BACKEND_WASAPI _trans("WASAPI (Exclusive Mode)") + enum class GPUDeterminismMode { Auto, @@ -144,8 +153,6 @@ struct SConfig // Interface settings bool bConfirmStop = false; bool bHideCursor = false; - bool bUsePanicHandlers = true; - bool bOnScreenDisplayMessages = true; std::string theme_name; // Bluetooth passthrough mode settings diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 03e914965db0..d980b7fc0c39 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -532,6 +532,9 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi HW::Shutdown(); INFO_LOG_FMT(CONSOLE, "{}", StopMessage(false, "HW shutdown")); + // Clear on screen messages that haven't expired + OSD::ClearMessages(); + // The config must be restored only after the whole HW has shut down, // not when it is still running. BootManager::RestoreConfig(); @@ -969,13 +972,6 @@ void UpdateTitle(u32 ElapseTime) message += " | " + title; } - // Update the audio timestretcher with the current speed - if (g_sound_stream) - { - Mixer* pMixer = g_sound_stream->GetMixer(); - pMixer->UpdateSpeed((float)Speed / 100); - } - Host_UpdateTitle(message); } diff --git a/Source/Core/Core/HW/AudioInterface.cpp b/Source/Core/Core/HW/AudioInterface.cpp index bfbb44393ec2..1f9b3de53adf 100644 --- a/Source/Core/Core/HW/AudioInterface.cpp +++ b/Source/Core/Core/HW/AudioInterface.cpp @@ -154,6 +154,7 @@ void Init() event_type_ai = CoreTiming::RegisterEvent("AICallback", Update); + //To review: this is now duplicate? g_sound_stream->GetMixer()->SetDMAInputSampleRate(GetAIDSampleRate()); g_sound_stream->GetMixer()->SetStreamInputSampleRate(GetAISSampleRate()); } diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index e0a20b3b4c27..c8dc6b9732f7 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -298,11 +298,6 @@ static u32 AdvanceDTK(u32 maximum_samples, u32* samples_to_process) static void DTKStreamingCallback(DIInterruptType interrupt_type, const std::vector& audio_data, s64 cycles_late) { - // TODO: Should we use GetAISSampleRate instead of a fixed 48 KHz? The audio mixer is using - // GetAISSampleRate. (This doesn't affect any actual games, since they all set it to 48 KHz.) - const double sample_rate = AudioInterface::Get48KHzSampleRate(); - - //To review: here we used to just hardcode 48Khz, ignoring wether we are wii or gc (and also avoiding it being double). old comment: // This determines which audio data to read next. // Similarly to DMA audio, instead of sending a different number of samples each submission, // we just change the frequency of the submission to match the sample rate. @@ -310,9 +305,6 @@ static void DTKStreamingCallback(DIInterruptType interrupt_type, const std::vect // The Mixer (Mixer.cpp) might assume the above is true, so if you change the samples submitted // per submission, make sure you review the Mixer as well. - // Determine which audio data to read next. - const u32 maximum_samples = sample_rate / 2000 * 7; - //To review: it also later one was: static const int MAXIMUM_SAMPLES = 168; // 168 samples (3.5ms at 48kHz/5.25ms at 32kHz on Wii, similar numbers on GC). // This number isn't HW tested, it was decided randomly. // Real HW might read 32KB at a time, increasing the latency, which would mean our DVD audio @@ -331,18 +323,18 @@ static void DTKStreamingCallback(DIInterruptType interrupt_type, const std::vect if (s_stream && AudioInterface::IsPlaying()) { read_offset = s_audio_position; - read_length = AdvanceDTK(maximum_samples, &s_pending_samples); + read_length = AdvanceDTK(MAXIMUM_SAMPLES, &s_pending_samples); } else { read_length = 0; - s_pending_samples = maximum_samples; + s_pending_samples = MAXIMUM_SAMPLES; } } else { read_length = 0; - s_pending_samples = maximum_samples; + s_pending_samples = MAXIMUM_SAMPLES; } // Read the next chunk of audio data asynchronously. diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index bb88790ecaf5..06c84e1ada41 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -166,6 +166,9 @@ void Wiimote::Reset() m_i2c_bus.AddSlave(&m_speaker_logic); m_i2c_bus.AddSlave(&m_camera_logic); + //To review: is this needed now? + m_speaker_logic.m_index = m_index; + // Reset extension connections to NONE: m_is_motion_plus_attached = false; m_active_extension = ExtensionNumber::NONE; @@ -700,15 +703,15 @@ EncryptionKey Wiimote::GetExtensionEncryptionKey() const bool Wiimote::IsSideways() const { - const bool sideways_modifier_toggle = m_hotkeys->getSettingsModifier()[0]; - const bool sideways_modifier_switch = m_hotkeys->getSettingsModifier()[2]; + const bool sideways_modifier_toggle = m_hotkeys->GetSettingsModifier()[0]; + const bool sideways_modifier_switch = m_hotkeys->GetSettingsModifier()[2]; return m_sideways_setting.GetValue() ^ sideways_modifier_toggle ^ sideways_modifier_switch; } bool Wiimote::IsUpright() const { - const bool upright_modifier_toggle = m_hotkeys->getSettingsModifier()[1]; - const bool upright_modifier_switch = m_hotkeys->getSettingsModifier()[3]; + const bool upright_modifier_toggle = m_hotkeys->GetSettingsModifier()[1]; + const bool upright_modifier_switch = m_hotkeys->GetSettingsModifier()[3]; return m_upright_setting.GetValue() ^ upright_modifier_toggle ^ upright_modifier_switch; } From 78d7281642a30f51fe520df2986257e38e396a33 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Fri, 14 May 2021 13:25:32 +0300 Subject: [PATCH 53/56] Revert all input changes --- Source/Android/jni/MainAndroid.cpp | 5 - Source/Core/AudioCommon/Mixer.cpp | 1 - Source/Core/Common/Logging/Log.h | 1 + Source/Core/Common/Logging/LogManager.cpp | 1 + Source/Core/Core/ConfigManager.cpp | 2 - Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp | 2 +- Source/Core/Core/HW/GCPadEmu.cpp | 71 +++--- Source/Core/Core/HW/VideoInterface.cpp | 9 +- Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp | 2 +- .../Core/Core/HW/WiimoteEmu/ExtensionPort.cpp | 1 - .../Core/Core/HW/WiimoteEmu/ExtensionPort.h | 2 + Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp | 2 +- Source/Core/Core/Host.h | 1 - Source/Core/Core/HotkeyManager.h | 1 - Source/Core/Core/IOS/USB/USB_KBD.cpp | 2 +- Source/Core/DolphinNoGUI/MainNoGUI.cpp | 6 - .../Config/Mapping/MappingIndicator.cpp | 5 +- .../Config/Mapping/MappingIndicator.h | 5 +- .../Config/Mapping/MappingWidget.cpp | 75 +------ .../DolphinQt/Config/Mapping/MappingWidget.h | 12 +- .../Config/Mapping/MappingWindow.cpp | 3 - .../DolphinQt/Config/Mapping/MappingWindow.h | 2 +- .../Config/ToolTipControls/ToolTipWidget.h | 4 +- Source/Core/DolphinQt/Host.cpp | 16 -- Source/Core/DolphinQt/Host.h | 3 - Source/Core/DolphinQt/HotkeyScheduler.cpp | 8 +- Source/Core/DolphinQt/HotkeyScheduler.h | 1 - Source/Core/DolphinQt/MainWindow.h | 1 - Source/Core/DolphinQt/Settings.cpp | 11 - Source/Core/DolphinQt/Settings.h | 3 - .../Core/DolphinQt/Settings/InterfacePane.h | 1 - .../ControlReference/ControlReference.cpp | 210 ++---------------- .../ControlReference/ControlReference.h | 32 +-- .../ControlReference/ExpressionParser.cpp | 41 ++-- .../ControlReference/ExpressionParser.h | 6 +- .../ControlReference/FunctionExpression.h | 2 - .../ControllerEmu/Control/Control.h | 2 +- .../ControlGroup/AnalogStick.cpp | 6 +- .../ControllerEmu/ControlGroup/AnalogStick.h | 4 +- .../ControllerEmu/ControlGroup/Attachments.h | 7 +- .../ControllerEmu/ControlGroup/Buttons.h | 2 +- .../ControlGroup/ControlGroup.cpp | 18 +- .../ControllerEmu/ControlGroup/ControlGroup.h | 15 +- .../ControllerEmu/ControlGroup/Cursor.cpp | 2 +- .../ControllerEmu/ControlGroup/Cursor.h | 25 +-- .../ControllerEmu/ControlGroup/Force.cpp | 4 +- .../ControllerEmu/ControlGroup/Force.h | 4 +- .../ControlGroup/IMUGyroscope.cpp | 4 +- .../ControlGroup/ModifySettingsButton.cpp | 30 +-- .../ControlGroup/ModifySettingsButton.h | 12 +- .../ControllerEmu/ControlGroup/Slider.cpp | 2 +- .../ControllerEmu/ControlGroup/Slider.h | 2 +- .../ControllerEmu/ControlGroup/Tilt.cpp | 6 +- .../ControllerEmu/ControlGroup/Tilt.h | 4 +- .../ControllerEmu/ControlGroup/Triggers.cpp | 2 +- .../ControllerEmu/ControlGroup/Triggers.h | 2 +- .../ControllerEmu/Setting/NumericSetting.h | 22 +- .../InputCommon/ControllerEmu/StickGate.cpp | 15 +- .../InputCommon/ControllerEmu/StickGate.h | 4 +- .../ControllerInterface.cpp | 8 +- .../ControllerInterface/CoreDevice.cpp | 6 - .../ControllerInterface/CoreDevice.h | 46 +--- .../DualShockUDPClient/DualShockUDPClient.cpp | 15 +- .../DualShockUDPClient/DualShockUDPProto.h | 2 +- .../ControllerInterface/OSX/OSX.mm | 8 +- .../ControllerInterface/OSX/OSXJoystick.mm | 5 +- .../Quartz/QuartzKeyboardAndMouse.h | 9 - .../ControllerInterface/SDL/SDL.cpp | 6 +- .../Touch/ButtonManager.cpp | 40 +++- .../ControllerInterface/Touch/ButtonManager.h | 10 +- .../Wiimote/WiimoteController.cpp | 19 +- .../ControllerInterface/Win32/Win32.cpp | 18 +- .../ControllerInterface/XInput/XInput.cpp | 5 - .../ControllerInterface/Xlib/XInput2.h | 14 -- .../ControllerInterface/evdev/evdev.cpp | 6 +- Source/Core/InputCommon/GCAdapter.cpp | 38 ++-- Source/Core/InputCommon/GCAdapter_Android.cpp | 24 +- Source/Core/InputCommon/InputConfig.cpp | 3 +- Source/Core/InputCommon/InputConfig.h | 2 +- Source/Core/VideoCommon/RenderBase.cpp | 1 + Source/DSPTool/StubHost.cpp | 4 - Source/UnitTests/StubHost.cpp | 4 - 82 files changed, 309 insertions(+), 728 deletions(-) diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 231142bec229..1b698d2a17f4 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -151,11 +151,6 @@ bool Host_RendererHasFocus() return true; } -bool Host_RendererHasFullFocus() -{ - return true; -} - bool Host_RendererIsFullscreen() { return false; diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 79b405079b93..a1fbd1ec6a90 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -46,7 +46,6 @@ Mixer::Mixer(u32 sample_rate) Mixer::~Mixer() { INFO_LOG_FMT(AUDIO_INTERFACE, "Mixer is initialized"); - Core::RemoveOnStateChangedCallback(m_on_state_changed_handle); } void Mixer::SetPaused(bool paused) diff --git a/Source/Core/Common/Logging/Log.h b/Source/Core/Common/Logging/Log.h index ce136b2c0b6e..9e45881b1cc0 100644 --- a/Source/Core/Common/Logging/Log.h +++ b/Source/Core/Common/Logging/Log.h @@ -20,6 +20,7 @@ enum LOG_TYPE COMMANDPROCESSOR, COMMON, CONSOLE, + CONTROLLERINTERFACE, CORE, DISCIO, DSPHLE, diff --git a/Source/Core/Common/Logging/LogManager.cpp b/Source/Core/Common/Logging/LogManager.cpp index b03255ca9eaf..2c58148fb20d 100644 --- a/Source/Core/Common/Logging/LogManager.cpp +++ b/Source/Core/Common/Logging/LogManager.cpp @@ -123,6 +123,7 @@ LogManager::LogManager() m_log[COMMANDPROCESSOR] = {"CP", "Command Processor"}; m_log[COMMON] = {"COMMON", "Common"}; m_log[CONSOLE] = {"CONSOLE", "Dolphin Console"}; + m_log[CONTROLLERINTERFACE] = {"CI", "Controller Interface"}; m_log[CORE] = {"CORE", "Core"}; m_log[DISCIO] = {"DIO", "Disc IO"}; m_log[DSPHLE] = {"DSPHLE", "DSP HLE"}; diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index bd682dc6ecf5..cd689ca00dfe 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -147,7 +147,6 @@ void SConfig::SaveInterfaceSettings(IniFile& ini) interface->Set("ConfirmStop", bConfirmStop); interface->Set("HideCursor", bHideCursor); - interface->Set("LockCursor", bLockCursor); interface->Set("LanguageCode", m_InterfaceLanguage); interface->Set("ExtendedFPSInfo", m_InterfaceExtendedFPSInfo); interface->Set("ShowActiveTitle", m_show_active_title); @@ -405,7 +404,6 @@ void SConfig::LoadInterfaceSettings(IniFile& ini) interface->Get("ConfirmStop", &bConfirmStop, true); interface->Get("HideCursor", &bHideCursor, false); - interface->Get("LockCursor", &bLockCursor, false); interface->Get("LanguageCode", &m_InterfaceLanguage, ""); interface->Get("ExtendedFPSInfo", &m_InterfaceExtendedFPSInfo, false); interface->Get("ShowActiveTitle", &m_show_active_title, true); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp index 22cb2821d40e..99177d21a2a4 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp @@ -320,7 +320,7 @@ void CEXIIPL::TransferByte(u8& data) if (!m_command.is_write()) { u32 dev_addr = DEV_ADDR_CURSOR(ROM); - // Technically we should descramble here if descrambling logic is enabled. + // Technically we should descramble here iff descrambling logic is enabled. // At the moment, we pre-decrypt the whole thing and // ignore the "enabled" bit - see CEXIIPL::CEXIIPL data = m_rom[dev_addr]; diff --git a/Source/Core/Core/HW/GCPadEmu.cpp b/Source/Core/Core/HW/GCPadEmu.cpp index fdeff8ac1141..ffcf3f556b15 100644 --- a/Source/Core/Core/HW/GCPadEmu.cpp +++ b/Source/Core/Core/HW/GCPadEmu.cpp @@ -93,10 +93,15 @@ GCPad::GCPad(const unsigned int index) : m_index(index) // options groups.emplace_back(m_options = new ControllerEmu::ControlGroup(_trans("Options"))); - m_options->AddSetting(&m_always_connected_setting, - // i18n: Treat a controller as always being connected regardless of what - // devices the user actually has plugged in - _trans("Always Connected"), false); + m_options->AddSetting( + &m_always_connected_setting, + // i18n: Treat a controller as always being connected regardless of what + // devices the user actually has plugged in + {_trans("Always Connected"), _trans(""), + _trans("Always connected if checked.\n" + "If unchecked, it will link the emulated controller connection state\n" + "to the real default device connection state (if there is one).")}, + false); } std::string GCPad::GetName() const @@ -181,60 +186,64 @@ void GCPad::LoadDefaults(const ControllerInterface& ciface) EmulatedController::LoadDefaults(ciface); // Buttons - m_buttons->SetControlExpression(0, "X"); // A - m_buttons->SetControlExpression(1, "Z"); // B - m_buttons->SetControlExpression(2, "C"); // X - m_buttons->SetControlExpression(3, "S"); // Y - m_buttons->SetControlExpression(4, "D"); // Z + m_buttons->SetControlExpression(0, "`X`"); // A + m_buttons->SetControlExpression(1, "`Z`"); // B + m_buttons->SetControlExpression(2, "`C`"); // X + m_buttons->SetControlExpression(3, "`S`"); // Y + m_buttons->SetControlExpression(4, "`D`"); // Z #ifdef _WIN32 - m_buttons->SetControlExpression(5, "RETURN"); // Start + m_buttons->SetControlExpression(5, "`RETURN`"); // Start #else // OS X/Linux // Start - m_buttons->SetControlExpression(5, "Return"); + m_buttons->SetControlExpression(5, "`Return`"); #endif + // stick modifiers to 50 % + m_main_stick->controls[4]->control_ref->range = 0.5f; + m_c_stick->controls[4]->control_ref->range = 0.5f; + // D-Pad - m_dpad->SetControlExpression(0, "T"); // Up - m_dpad->SetControlExpression(1, "G"); // Down - m_dpad->SetControlExpression(2, "F"); // Left - m_dpad->SetControlExpression(3, "H"); // Right + m_dpad->SetControlExpression(0, "`T`"); // Up + m_dpad->SetControlExpression(1, "`G`"); // Down + m_dpad->SetControlExpression(2, "`F`"); // Left + m_dpad->SetControlExpression(3, "`H`"); // Right // C Stick - m_c_stick->SetControlExpression(0, "I"); // Up - m_c_stick->SetControlExpression(1, "K"); // Down - m_c_stick->SetControlExpression(2, "J"); // Left - m_c_stick->SetControlExpression(3, "L"); // Right + m_c_stick->SetControlExpression(0, "`I`"); // Up + m_c_stick->SetControlExpression(1, "`K`"); // Down + m_c_stick->SetControlExpression(2, "`J`"); // Left + m_c_stick->SetControlExpression(3, "`L`"); // Right // Modifier - m_c_stick->SetControlExpression(4, "Ctrl"); + m_c_stick->SetControlExpression(4, "`Ctrl`"); // Control Stick #ifdef _WIN32 - m_main_stick->SetControlExpression(0, "UP"); // Up - m_main_stick->SetControlExpression(1, "DOWN"); // Down - m_main_stick->SetControlExpression(2, "LEFT"); // Left - m_main_stick->SetControlExpression(3, "RIGHT"); // Right + m_main_stick->SetControlExpression(0, "`UP`"); // Up + m_main_stick->SetControlExpression(1, "`DOWN`"); // Down + m_main_stick->SetControlExpression(2, "`LEFT`"); // Left + m_main_stick->SetControlExpression(3, "`RIGHT`"); // Right #elif __APPLE__ m_main_stick->SetControlExpression(0, "`Up Arrow`"); // Up m_main_stick->SetControlExpression(1, "`Down Arrow`"); // Down m_main_stick->SetControlExpression(2, "`Left Arrow`"); // Left m_main_stick->SetControlExpression(3, "`Right Arrow`"); // Right #else - m_main_stick->SetControlExpression(0, "Up"); // Up - m_main_stick->SetControlExpression(1, "Down"); // Down - m_main_stick->SetControlExpression(2, "Left"); // Left - m_main_stick->SetControlExpression(3, "Right"); // Right + m_main_stick->SetControlExpression(0, "`Up`"); // Up + m_main_stick->SetControlExpression(1, "`Down`"); // Down + m_main_stick->SetControlExpression(2, "`Left`"); // Left + m_main_stick->SetControlExpression(3, "`Right`"); // Right #endif // Modifier - m_main_stick->SetControlExpression(4, "Shift"); + m_main_stick->SetControlExpression(4, "`Shift`"); // Because our defaults use keyboard input, set calibration shapes to squares. m_c_stick->SetCalibrationFromGate(ControllerEmu::SquareStickGate(1.0)); m_main_stick->SetCalibrationFromGate(ControllerEmu::SquareStickGate(1.0)); // Triggers - m_triggers->SetControlExpression(0, "Q"); // L - m_triggers->SetControlExpression(1, "W"); // R + m_triggers->SetControlExpression(0, "`Q`"); // L + m_triggers->SetControlExpression(1, "`W`"); // R } bool GCPad::GetMicButton() const diff --git a/Source/Core/Core/HW/VideoInterface.cpp b/Source/Core/Core/HW/VideoInterface.cpp index 2b039da87859..0bcdd8df88b8 100644 --- a/Source/Core/Core/HW/VideoInterface.cpp +++ b/Source/Core/Core/HW/VideoInterface.cpp @@ -28,8 +28,6 @@ #include "DiscIO/Enums.h" -#include "InputCommon/ControlReference/ControlReference.h" - #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoConfig.h" @@ -869,12 +867,7 @@ void Update(u64 ticks) if (s_half_line_of_next_si_poll == s_half_line_count) { - // If we wanted to add an option to not block the mouse input when it's - // responsable for capturing focus on the game window, put it as the last param, - // though it would break the code to prevent mouse clicks from being accepted - // after we lost focus (more flags could be added for that) - InputReference::UpdateInputGate(!SConfig::GetInstance().m_BackgroundInput, - SConfig::GetInstance().bLockCursor, true); + Core::UpdateInputGate(!SConfig::GetInstance().m_BackgroundInput); SerialInterface::UpdateDevices(); s_half_line_of_next_si_poll += 2 * SerialInterface::GetPollXLines(); } diff --git a/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp b/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp index dbd627b83818..43bf5e0ce9bf 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp @@ -228,7 +228,7 @@ WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g, void EmulatePoint(MotionState* state, ControllerEmu::Cursor* ir_group, float time_elapsed) { - const auto cursor = ir_group->GetState(time_elapsed, false); + const auto cursor = ir_group->GetState(true); if (!cursor.IsVisible()) { diff --git a/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.cpp b/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.cpp index 8a9c6de7664b..c94242da4a36 100644 --- a/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.cpp @@ -23,7 +23,6 @@ void ExtensionPort::AttachExtension(Extension* ext) m_extension = ext; m_i2c_bus.AddSlave(m_extension); - ; } } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h b/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h index 3a266bcc0741..5cfa0cbbcdad 100644 --- a/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h +++ b/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h @@ -22,6 +22,8 @@ enum ExtensionNumber : u8 UDRAW_TABLET, DRAWSOME_TABLET, TATACON, + + MAX }; // FYI: An extension must be attached. diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index 06c84e1ada41..a62116779d82 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -431,7 +431,7 @@ void Wiimote::Update() // Hotkey / settings modifier // Data is later accessed in IsSideways and IsUpright - m_hotkeys->GetState(); + m_hotkeys->UpdateState(); // Update our motion simulations. StepDynamics(); diff --git a/Source/Core/Core/Host.h b/Source/Core/Core/Host.h index 580d7eae64d9..ec539a01d2de 100644 --- a/Source/Core/Core/Host.h +++ b/Source/Core/Core/Host.h @@ -36,7 +36,6 @@ enum class HostMessageID std::vector Host_GetPreferredLocales(); bool Host_UIBlocksControllerState(); bool Host_RendererHasFocus(); -bool Host_RendererHasFullFocus(); bool Host_RendererIsFullscreen(); void Host_Message(HostMessageID id); diff --git a/Source/Core/Core/HotkeyManager.h b/Source/Core/Core/HotkeyManager.h index 66732f7d24ce..8bce8f86fe3c 100644 --- a/Source/Core/Core/HotkeyManager.h +++ b/Source/Core/Core/HotkeyManager.h @@ -29,7 +29,6 @@ enum Hotkey HK_FULLSCREEN, HK_SCREENSHOT, HK_EXIT, - HK_UNLOCK_CURSOR, HK_ACTIVATE_CHAT, HK_REQUEST_GOLF_CONTROL, diff --git a/Source/Core/Core/IOS/USB/USB_KBD.cpp b/Source/Core/Core/IOS/USB/USB_KBD.cpp index 6f2ab1f2f2c0..9cb8ed0b4e4b 100644 --- a/Source/Core/Core/IOS/USB/USB_KBD.cpp +++ b/Source/Core/Core/IOS/USB/USB_KBD.cpp @@ -212,7 +212,7 @@ std::optional USB_KBD::Write(const ReadWriteRequest& request) std::optional USB_KBD::IOCtl(const IOCtlRequest& request) { if (SConfig::GetInstance().m_WiiKeyboard && !Core::WantsDeterminism() && - InputReference::GetInputGate() && !m_message_queue.empty()) + ControlReference::GetInputGate() && !m_message_queue.empty()) { Memory::CopyToEmu(request.buffer_out, &m_message_queue.front(), sizeof(MessageData)); m_message_queue.pop(); diff --git a/Source/Core/DolphinNoGUI/MainNoGUI.cpp b/Source/Core/DolphinNoGUI/MainNoGUI.cpp index 95f11388830f..7c64a5cbeb24 100644 --- a/Source/Core/DolphinNoGUI/MainNoGUI.cpp +++ b/Source/Core/DolphinNoGUI/MainNoGUI.cpp @@ -98,12 +98,6 @@ bool Host_RendererHasFocus() return s_platform->IsWindowFocused(); } -bool Host_RendererHasFullFocus() -{ - // Mouse capturing isn't implemented - return s_platform->IsWindowFocused(); -} - bool Host_RendererIsFullscreen() { return s_platform->IsWindowFullscreen(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp index 0f1563dfe136..b07d61c8c011 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp @@ -16,9 +16,6 @@ #include #include -#include "Core/Core.h" -#include "Core/HW/Wiimote.h" - #include "Common/MathUtil.h" #include "InputCommon/ControlReference/ControlReference.h" @@ -296,7 +293,7 @@ void MappingIndicator::paintEvent(QPaintEvent*) void CursorIndicator::Draw() { - const auto adj_coord = m_cursor_group.GetState(0.f, true); + const auto adj_coord = m_cursor_group.GetState(true); DrawReshapableInput(m_cursor_group, CURSOR_TV_COLOR, adj_coord.IsVisible() ? diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h index 45ce4cbfb6d7..7e3fedfe7505 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h @@ -106,10 +106,7 @@ class TiltIndicator : public ReshapableInputIndicator class CursorIndicator : public ReshapableInputIndicator { public: - explicit CursorIndicator(ControllerEmu::Cursor& cursor) : m_cursor_group(cursor) - { - m_cursor_group.ResetState(true); - } + explicit CursorIndicator(ControllerEmu::Cursor& cursor) : m_cursor_group(cursor) {} private: void Draw() override; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp index 56fae20877a9..01512d0cc317 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp @@ -5,6 +5,7 @@ #include "DolphinQt/Config/Mapping/MappingWidget.h" #include +#include #include #include #include @@ -132,24 +133,17 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con for (auto& setting : group->numeric_settings) { QWidget* setting_widget = nullptr; - bool has_edit_condition = false; switch (setting->GetType()) { case ControllerEmu::SettingType::Double: setting_widget = new MappingDouble( this, static_cast*>(setting.get())); - has_edit_condition = - static_cast*>(setting.get())->GetEditCondition() != - nullptr; break; case ControllerEmu::SettingType::Bool: setting_widget = new MappingBool(this, static_cast*>(setting.get())); - has_edit_condition = - static_cast*>(setting.get())->GetEditCondition() != - nullptr; break; default: @@ -165,16 +159,6 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con hbox->addWidget(CreateSettingAdvancedMappingButton(*setting)); form_layout->addRow(tr(setting->GetUIName()), hbox); - - QFormLayout::TakeRowResult row; - row.fieldItem = form_layout->itemAt(form_layout->rowCount() - 1, QFormLayout::FieldRole); - row.labelItem = form_layout->itemAt(form_layout->rowCount() - 1, QFormLayout::LabelRole); - row.labelItem->widget()->setToolTip(tr(setting->GetUIDescription())); - - if (has_edit_condition) - { - m_edit_condition_numeric_settings.emplace_back(std::make_tuple(setting.get(), row, group)); - } } } @@ -191,20 +175,7 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con { QWidget* widget = form_layout->itemAt(i)->widget(); if (widget != nullptr && widget != group_enable_label && widget != group_enable_checkbox) - { widget->setEnabled(group->enabled); - } - // Layouts could be even more nested but they likely never will - else if (QLayout* nested_layout = form_layout->itemAt(i)->layout()) - { - for (int k = 0; k < nested_layout->count(); ++k) - { - if (widget = nested_layout->itemAt(k)->widget()) - { - widget->setEnabled(group->enabled); - } - } - } } }; enable_group_by_checkbox(); @@ -213,50 +184,9 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con [group_enable_checkbox, group] { group_enable_checkbox->setChecked(group->enabled); }); } - // This isn't called immediately when the edit condition changes - // but it's called at frequent regular intervals so it's fine. - // Connecting checkbox changes events would have been quite complicated - // given that groups have the ability to override the enabled value as well - connect(this, &MappingWidget::Update, this, &MappingWidget::RefreshSettingsEnabled); - return group_box; } -void MappingWidget::RefreshSettingsEnabled() -{ - for (auto& numeric_settings : m_edit_condition_numeric_settings) - { - bool enabled = true; - const ControllerEmu::NumericSettingBase* setting = std::get<0>(numeric_settings); - switch (setting->GetType()) - { - case ControllerEmu::SettingType::Double: - enabled = static_cast*>(setting)->IsEnabled(); - break; - - case ControllerEmu::SettingType::Bool: - enabled = static_cast*>(setting)->IsEnabled(); - break; - } - enabled = enabled && std::get<2>(numeric_settings)->enabled; - - if (QWidget* widget = std::get<1>(numeric_settings).labelItem->widget()) - { - widget->setEnabled(enabled); - } - if (QLayout* layout = std::get<1>(numeric_settings).fieldItem->layout()) - { - for (int i = 0; i < layout->count(); ++i) - { - if (QWidget* widget = layout->itemAt(i)->widget()) - { - widget->setEnabled(enabled); - } - } - } - } -} - ControllerEmu::EmulatedController* MappingWidget::GetController() const { return m_parent->GetController(); @@ -267,13 +197,12 @@ MappingWidget::CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingB { const auto button = new QPushButton(tr("...")); button->setFixedWidth(QFontMetrics(font()).boundingRect(button->text()).width() * 2); - button->setToolTip(tr("Advanced mapping")); button->connect(button, &QPushButton::clicked, [this, &setting]() { if (setting.IsSimpleValue()) setting.SetExpressionFromValue(); - IOWindow io(this, GetController(), &setting.GetInputReference(), IOWindow::Type::InputSetting); + IOWindow io(this, GetController(), &setting.GetInputReference(), IOWindow::Type::Input); io.exec(); setting.SimplifyIfPossible(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h index 52caa70b6849..851d2c12ce17 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h @@ -9,7 +9,6 @@ #include #include -#include constexpr int WIDGET_MAX_WIDTH = 112; @@ -29,8 +28,7 @@ class EmulatedController; class NumericSettingBase; } // namespace ControllerEmu -// For smoother UI, this should be based on the current monitor refresh rate -constexpr int INDICATOR_UPDATE_FREQ = 60; +constexpr int INDICATOR_UPDATE_FREQ = 30; class MappingWidget : public QWidget { @@ -53,16 +51,10 @@ class MappingWidget : public QWidget protected: int GetPort() const; - void RefreshSettingsEnabled(); - QGroupBox* CreateGroupBox(ControllerEmu::ControlGroup* group); QGroupBox* CreateGroupBox(const QString& name, ControllerEmu::ControlGroup* group); QPushButton* CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingBase& setting); private: - MappingWindow* const m_parent; - - std::vector> - m_edit_condition_numeric_settings; + MappingWindow* m_parent; }; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 4b9ca314ce6e..ac6230cec476 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -445,9 +445,6 @@ void MappingWindow::SetMappingType(MappingWindow::Type type) return; } - // Automatically hide the tab bar if its redundant (we only have one tab) - m_tab_widget->setTabBarAutoHide(true); - widget->LoadSettings(); m_config = widget->GetConfig(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index 6848002e17ac..aade3b23df22 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -57,7 +57,7 @@ class MappingWindow final : public QDialog signals: // Emitted when config has changed so widgets can update to reflect the change. void ConfigChanged(); - // Emitted at INDICATOR_UPDATE_FREQ(Hz) for real-time indicators to be updated. + // Emitted at 30hz for real-time indicators to be updated. void Update(); void Save(); diff --git a/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipWidget.h b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipWidget.h index 8b5ffe5518ef..2aea3e0b29d6 100644 --- a/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipWidget.h +++ b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipWidget.h @@ -10,6 +10,8 @@ #include "DolphinQt/Config/Graphics/BalloonTip.h" +constexpr int TOOLTIP_DELAY = 300; + template class ToolTipWidget : public Derived { @@ -25,7 +27,7 @@ class ToolTipWidget : public Derived { if (m_timer_id) return; - m_timer_id = this->startTimer(300); + m_timer_id = this->startTimer(TOOLTIP_DELAY); } void leaveEvent(QEvent* event) override diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index b6a419571b4d..fdf0309b2615 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -66,11 +66,6 @@ bool Host::GetRenderFocus() return m_render_focus; } -bool Host::GetRenderFullFocus() -{ - return m_render_full_focus; -} - void Host::SetRenderFocus(bool focus) { m_render_focus = focus; @@ -81,11 +76,6 @@ void Host::SetRenderFocus(bool focus) }); } -void Host::SetRenderFullFocus(bool focus) -{ - m_render_full_focus = focus; -} - bool Host::GetRenderFullscreen() { return m_render_fullscreen; @@ -141,11 +131,6 @@ bool Host_RendererHasFocus() return Host::GetInstance()->GetRenderFocus(); } -bool Host_RendererHasFullFocus() -{ - return Host::GetInstance()->GetRenderFullFocus(); -} - bool Host_RendererIsFullscreen() { return Host::GetInstance()->GetRenderFullscreen(); @@ -185,7 +170,6 @@ void Host_RequestRenderWindowSize(int w, int h) bool Host_UIBlocksControllerState() { - // TODO: shouldn't this call WantCaptureKeyboard and "controller" as well? return ImGui::GetCurrentContext() && ImGui::GetIO().WantCaptureKeyboard; } diff --git a/Source/Core/DolphinQt/Host.h b/Source/Core/DolphinQt/Host.h index 33051627a7c4..090e620336a9 100644 --- a/Source/Core/DolphinQt/Host.h +++ b/Source/Core/DolphinQt/Host.h @@ -23,12 +23,10 @@ class Host final : public QObject static Host* GetInstance(); bool GetRenderFocus(); - bool GetRenderFullFocus(); bool GetRenderFullscreen(); void SetRenderHandle(void* handle); void SetRenderFocus(bool focus); - void SetRenderFullFocus(bool focus); void SetRenderFullscreen(bool fullscreen); void ResizeSurface(int new_width, int new_height); void RequestNotifyMapLoaded(); @@ -45,6 +43,5 @@ class Host final : public QObject std::atomic m_render_handle{nullptr}; std::atomic m_render_focus{false}; - std::atomic m_render_full_focus{false}; std::atomic m_render_fullscreen{false}; }; diff --git a/Source/Core/DolphinQt/HotkeyScheduler.cpp b/Source/Core/DolphinQt/HotkeyScheduler.cpp index b8df1c0b7d34..30bc61aec0f9 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt/HotkeyScheduler.cpp @@ -156,12 +156,12 @@ void HotkeyScheduler::Run() if (Core::GetState() != Core::State::Stopping) { // Obey window focus (config permitting) before checking hotkeys. - InputReference::UpdateInputGate(Config::Get(Config::MAIN_FOCUSED_HOTKEYS), false, true); + Core::UpdateInputGate(Config::Get(Config::MAIN_FOCUSED_HOTKEYS)); HotkeyManagerEmu::GetStatus(); // Everything else on the host thread (controller config dialog) should always get input. - InputReference::SetInputGateOpen(); + ControlReference::SetInputGate(true); if (!Core::IsRunningAndStarted()) continue; @@ -213,10 +213,6 @@ void HotkeyScheduler::Run() if (IsHotkey(HK_EXIT)) emit ExitHotkey(); - // Unlock Cursor - if (IsHotkey(HK_UNLOCK_CURSOR)) - emit UnlockCursor(); - auto& settings = Settings::Instance(); // Toggle Chat diff --git a/Source/Core/DolphinQt/HotkeyScheduler.h b/Source/Core/DolphinQt/HotkeyScheduler.h index 8ae02301ef06..4717eb44920b 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.h +++ b/Source/Core/DolphinQt/HotkeyScheduler.h @@ -27,7 +27,6 @@ class HotkeyScheduler : public QObject void ChangeDisc(); void ExitHotkey(); - void UnlockCursor(); void ActivateChat(); void RequestGolfControl(); void FullScreenHotkey(); diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index 79383b354179..60653412bbc1 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -108,7 +108,6 @@ class MainWindow final : public QMainWindow void SetFullScreenResolution(bool fullscreen); void FullScreen(); - void UnlockCursor(); void ScreenShot(); void CreateComponents(); diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index 0456b268f99b..cc92a67c38bd 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -232,17 +232,6 @@ bool Settings::GetHideCursor() const return SConfig::GetInstance().bHideCursor; } -void Settings::SetLockCursor(bool lock_cursor) -{ - SConfig::GetInstance().bLockCursor = lock_cursor; - emit LockCursorChanged(); -} - -bool Settings::GetLockCursor() const -{ - return SConfig::GetInstance().bLockCursor; -} - void Settings::SetKeepWindowOnTop(bool top) { if (IsKeepWindowOnTopEnabled() == top) diff --git a/Source/Core/DolphinQt/Settings.h b/Source/Core/DolphinQt/Settings.h index 22cb72666637..63396c3e7695 100644 --- a/Source/Core/DolphinQt/Settings.h +++ b/Source/Core/DolphinQt/Settings.h @@ -96,8 +96,6 @@ class Settings final : public QObject // Graphics void SetHideCursor(bool hide_cursor); bool GetHideCursor() const; - void SetLockCursor(bool lock_cursor); - bool GetLockCursor() const; void SetKeepWindowOnTop(bool top); bool IsKeepWindowOnTopEnabled() const; @@ -166,7 +164,6 @@ class Settings final : public QObject void MetadataRefreshCompleted(); void AutoRefreshToggled(bool enabled); void HideCursorChanged(); - void LockCursorChanged(); void KeepWindowOnTopChanged(bool top); void VolumeChanged(int volume); void NANDRefresh(); diff --git a/Source/Core/DolphinQt/Settings/InterfacePane.h b/Source/Core/DolphinQt/Settings/InterfacePane.h index 6fde8d25fe89..d135de599272 100644 --- a/Source/Core/DolphinQt/Settings/InterfacePane.h +++ b/Source/Core/DolphinQt/Settings/InterfacePane.h @@ -45,5 +45,4 @@ class InterfacePane final : public QWidget QCheckBox* m_checkbox_show_active_title; QCheckBox* m_checkbox_pause_on_focus_lost; QCheckBox* m_checkbox_hide_mouse; - QCheckBox* m_checkbox_lock_mouse; }; diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp index caa897e9f6ea..671f2f3379a4 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.cpp +++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp @@ -4,54 +4,19 @@ #include "InputCommon/ControlReference/ControlReference.h" -#include - -#include "Core/Host.h" - using namespace ciface::ExpressionParser; -// See Control::FocusFlags for more details -enum class InputGateFlags : u8 -{ - // This is always true if the user accepts background input, otherwise - // it's only true when the window has focus, similarly to the state below - HasFocus = 0x01, - // Full focus means the cursor is captured/locked inside the game window. - // Ignored if mouse capturing is off. Implies HasFocus - HasFullFocus = 0x02, - // To ignore some specific inputs temporarily, e.g. mouse down on mouse capture, - // as the user presses mouse down to get full focus. - HadFocus = 0x04, - HadFullFocus = 0x08, - // Given that the input gate is updated at the SI rate, in 60fps - // Wii games it can happen that 2 input gate updates happen between - // emulated Wiimote input updates, so HadFocus would have been - // turned to true already, leaving us unable to block inputs because - // of focus changes. Even with a timer it wouldn't work - // as it would need to depend on emulation speed. - HadHadFocus = 0x10, - HadHadFullFocus = 0x20, - // Fully (force) open, always accept any input. Has priority over any other flag - Ignore = 0x40, - // Accept calls to ignore (block) input when focus, or full focus, is acquired or lost - IgnoreInputOnFocusChanged = 0x80, - - // Most or all input will pass in this case. - // If none of these are true, the gate is fully closed - OpenMask = HasFocus | Ignore, - // All input will pass in this case. Remove these flags to block - FullOpenMask = HasFocus | HasFullFocus | Ignore, - FocusMask = HasFocus | HasFullFocus, +static thread_local bool tls_input_gate = true; - FocusHistoryMask = HasFocus | HadFocus | HadHadFocus, - FullFocusHistoryMask = HasFullFocus | HadFullFocus | HadHadFullFocus -}; +void ControlReference::SetInputGate(bool enable) +{ + tls_input_gate = enable; +} -// As of now these thread_local variables are only accessed by the host (UI) and the game thread. -// Gate is "open" by default in case we don't bother setting it. -static thread_local InputGateFlags tls_input_gate_flags = InputGateFlags::FullOpenMask; -// InputReference(s)* never change -static thread_local std::vector tls_blocked_inputs; +bool ControlReference::GetInputGate() +{ + return tls_input_gate; +} // // UpdateReference @@ -94,9 +59,7 @@ std::optional ControlReference::SetExpression(std::string expr) return parse_result.description; } -ControlReference::ControlReference() - : range(1.0), default_range(1.0), m_parsed_expression(nullptr), - m_parse_status(ParseStatus::EmptyExpression) +ControlReference::ControlReference() : range(1), m_parsed_expression(nullptr) { } @@ -106,10 +69,6 @@ InputReference::InputReference() : ControlReference() { } -IgnoreGateInputReference::IgnoreGateInputReference() : InputReference() -{ -} - OutputReference::OutputReference() : ControlReference() { } @@ -123,140 +82,6 @@ bool OutputReference::IsInput() const return false; } -void InputReference::SetInputGateOpen() -{ - tls_input_gate_flags = InputGateFlags(u8(tls_input_gate_flags) | u8(InputGateFlags::Ignore)); -} - -bool InputReference::GetInputGate() -{ - return (u8(tls_input_gate_flags) & u8(InputGateFlags::FocusMask)) == - u8(InputGateFlags::FocusMask) || - (u8(tls_input_gate_flags) & u8(InputGateFlags::Ignore)); -} - -void InputReference::UpdateInputGate(bool require_focus, bool require_full_focus, - bool ignore_input_on_focus_changed) -{ - u8 input_gate = 0; - - // If the user accepts background input, the input should also be accepted - // even if an on screen interface is active - if (!require_focus || (Host_RendererHasFocus() && !Host_UIBlocksControllerState())) - { - input_gate |= u8(InputGateFlags::HasFocus); - - if (!require_focus || !require_full_focus || Host_RendererHasFullFocus()) - input_gate |= u8(InputGateFlags::HasFullFocus); - - static_assert(u8(InputGateFlags::HadFocus) == u8(InputGateFlags::HasFocus) << 2); - static_assert(u8(InputGateFlags::HadHadFocus) == u8(InputGateFlags::HadFocus) << 2); - static_assert(u8(InputGateFlags::HadFullFocus) == u8(InputGateFlags::HasFullFocus) << 2); - static_assert(u8(InputGateFlags::HadHadFullFocus) == u8(InputGateFlags::HadFullFocus) << 2); - input_gate |= (u8(tls_input_gate_flags) & u8(InputGateFlags::HasFocus)) << 2; - input_gate |= (u8(tls_input_gate_flags) & u8(InputGateFlags::HadFocus)) << 2; - input_gate |= (u8(tls_input_gate_flags) & u8(InputGateFlags::HasFullFocus)) << 2; - input_gate |= (u8(tls_input_gate_flags) & u8(InputGateFlags::HadFullFocus)) << 2; - - if (require_focus && ignore_input_on_focus_changed) - input_gate |= u8(InputGateFlags::IgnoreInputOnFocusChanged); - } - else - { - // No need to set InputGateFlags::HadFocus or InputGateFlags::HadFullFocus - // as they are only used when passing from not focus to focus - } - tls_input_gate_flags = InputGateFlags(input_gate); -} - -bool InputReference::FilterInput() -{ - if (!m_parsed_expression) - { - return false; - } - - // Theoretically we'd want to check the input flags individually on every function in the - // expression, but given how annoying, slow and useless it would be to do that, we just "sum" - // input flags of all of our functions, the highest priority flags will win (IgnoreFocus). - // If there are no actual inputs in the expression, the default will be returned (RequireFocus) - u8 focus_flags = u8(m_parsed_expression->GetFocusFlags()); - - // Return true even if the gate is blocked in some cases, things like battery level - // always need to pass - if ((u8(tls_input_gate_flags) & u8(InputGateFlags::Ignore)) || - (focus_flags & u8(Device::FocusFlags::IgnoreFocus))) - { - return true; - } - - // This a rare case, it's usually skipped - if ((focus_flags & u8(Device::FocusFlags::IgnoreOnFocusChanged)) && - (u8(tls_input_gate_flags) & u8(InputGateFlags::IgnoreInputOnFocusChanged))) - { - // If we had focus, make sure we still have it, if not, ignore the input. - // We should also check if the focus was actually required from the input gate or if it is - // fake, but it doesn't matter as the flag IgnoreInputOnFocusChanged would not have been on - if ((u8(tls_input_gate_flags) & u8(InputGateFlags::HasFocus)) && !Host_RendererHasFocus()) - return false; - - // Unfortunately on focus loss "ignoring" input wouldn't always work without checking the - // current host focus value. Before this change the game would often react to a mouse click - // that made the window lose focus. That is because of multiple reasons, - // mainly the input gate not being updated enough (only at SI rate) and as a consequence - // some inputs might be read before being blocked by the next SI input gate update. - // Increasing the update rate of the input gate would break the opposite case though, - // (ignoring the mouse click which made the window gain focus) which is more important. - // Loads of things play a role: - // -The update rate of the input gate (SI rate, twice the video frame rate) - // -The update rate of the emulated Wiimote (Wiimote::UPDATE_FREQ: 200Hz) - // -Small delays in window activation and de-activation events from QT/Windows - // -Even if devices are updated at SI rate, the mouse might return a slightly outdated inputs - // This problem doesn't happen on GC, it only happens if an input is bound to an emulated - // Wiimote. - - // If the focus up bits are either 1 or 2, then focus has been acquired - u8 focus_bits = u8(InputGateFlags::FocusHistoryMask) & u8(tls_input_gate_flags); - bool focus_changed = focus_bits != 0 && focus_bits != u8(InputGateFlags::FocusHistoryMask); - u8 full_focus_bits = u8(InputGateFlags::FullFocusHistoryMask) & u8(tls_input_gate_flags); - bool full_focus_changed = - full_focus_bits != 0 && full_focus_bits != u8(InputGateFlags::FullFocusHistoryMask); - - bool is_blocked = false; - std::vector::iterator it = - std::find(tls_blocked_inputs.begin(), tls_blocked_inputs.end(), this); - if (it != tls_blocked_inputs.end()) - { - is_blocked = true; - } - - if ((u8(focus_flags) & u8(Device::FocusFlags::RequireFullFocus)) ? full_focus_changed : - focus_changed) - { - // Block until release if pressed - if (!is_blocked && m_parsed_expression->GetValue() > 0.0) - tls_blocked_inputs.push_back(this); - return false; - } - - if (is_blocked) - { - if (m_parsed_expression->GetValue() > 0.0) - return false; - // Unblock on release. - // We should probably do this if the IgnoreInputOnFocusChanged flag is turned off as well, - // but it's not worth it (never happens for now) - tls_blocked_inputs.erase(it); - } - } - // Exclude this flag from the comparison as it's not actually in InputGateFlags - focus_flags &= ~u8(Device::FocusFlags::IgnoreOnFocusChanged); - - // Remove all the input gate flags from the input flags, if we have no - // "requirements" left, then we can go on - return (focus_flags & ~u8(tls_input_gate_flags)) == 0; -} - // // InputReference :: State // @@ -265,15 +90,7 @@ bool InputReference::FilterInput() // ControlState InputReference::State(const ControlState ignore) { - // Every expression can have a different filter (which depends on the gate) - if (FilterInput()) - return m_parsed_expression->GetValue() * range; - return 0.0; -} - -ControlState IgnoreGateInputReference::State(const ControlState ignore) -{ - if (m_parsed_expression) + if (m_parsed_expression && GetInputGate()) return m_parsed_expression->GetValue() * range; return 0.0; } @@ -282,8 +99,9 @@ ControlState IgnoreGateInputReference::State(const ControlState ignore) // OutputReference :: State // // Set the state of all binded outputs -// overrides ControlReference::State ... combined them so I could make the GUI simple / inputs == -// same as outputs one list. Ignores the input gate for now +// overrides ControlReference::State .. combined them so I could make the GUI simple / inputs == +// same as outputs one list +// I was lazy and it works so watever // ControlState OutputReference::State(const ControlState state) { diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.h b/Source/Core/InputCommon/ControlReference/ControlReference.h index 264377fd5e81..59a21e8b7db3 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.h +++ b/Source/Core/InputCommon/ControlReference/ControlReference.h @@ -10,8 +10,6 @@ #include "InputCommon/ControlReference/ExpressionParser.h" #include "InputCommon/ControllerInterface/CoreDevice.h" -using namespace ciface::Core; - // ControlReference // // These are what you create to actually use the inputs, InputReference or OutputReference. @@ -21,9 +19,14 @@ using namespace ciface::Core; // when you change a ControlReference's expression, // you must use UpdateReference on it to rebind controls // + class ControlReference { public: + // Note: this is per thread. + static void SetInputGate(bool enable); + static bool GetInputGate(); + virtual ~ControlReference(); virtual ControlState State(const ControlState state = 0) = 0; virtual bool IsInput() const = 0; @@ -39,10 +42,7 @@ class ControlReference // Returns a human-readable error description when the given expression is invalid. std::optional SetExpression(std::string expr); - // State value multiplier ControlState range; - // Useful for resetting settings. Always 1 except for input modifies - ControlState default_range; protected: ControlReference(); @@ -78,33 +78,11 @@ inline ControlState ControlReference::GetState() class InputReference : public ControlReference { public: - // Note: the input gate is per thread. - // Set the gate to be fully open, ignoring focus - static void SetInputGateOpen(); - // Check if the gate is fully open - static bool GetInputGate(); - // This is mostly to cache values for cheaper retrieval and consistency within a frame - static void UpdateInputGate(bool require_focus, bool require_full_focus = false, - bool ignore_input_on_focus_changed = false); - InputReference(); - bool FilterInput(); bool IsInput() const override; ControlState State(const ControlState state) override; }; -// -// IgnoreGateInputReference -// -// Control reference for inputs that should ignore focus, like battery level -// -class IgnoreGateInputReference : public InputReference -{ -public: - IgnoreGateInputReference(); - ControlState State(const ControlState state) override; -}; - // // OutputReference // diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index c96ea0599c32..1a7ca5a8ef99 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -247,22 +247,18 @@ ParseStatus Lexer::Tokenize(std::vector& tokens) class ControlExpression : public Expression { public: - // Keep a shared_ptr to the device so the control pointer doesn't become invalid. - std::shared_ptr m_device; - - explicit ControlExpression(ControlQualifier qualifier_) : qualifier(qualifier_) {} + explicit ControlExpression(ControlQualifier qualifier) : m_qualifier(qualifier) {} ControlState GetValue() const override { - if (s_hotkey_suppressions.IsSuppressed(input)) + if (s_hotkey_suppressions.IsSuppressed(m_input)) return 0; - else - return GetValueIgnoringSuppression(); + return GetValueIgnoringSuppression(); } ControlState GetValueIgnoringSuppression() const { - if (!input) + if (!m_input) return 0.0; // Note: Inputs may return negative values in situations where opposing directions are @@ -271,27 +267,29 @@ class ControlExpression : public Expression // FYI: Clamping values greater than 1.0 is purposely not done to support unbounded values in // the future. (e.g. raw accelerometer/gyro data) - return std::max(0.0, input->GetState()); + return std::max(0.0, m_input->GetState()); } void SetValue(ControlState value) override { - if (output) - output->SetState(value); + if (m_output) + m_output->SetState(value); } - int CountNumControls() const override { return (input || output) ? 1 : 0; } + int CountNumControls() const override { return (m_input || m_output) ? 1 : 0; } void UpdateReferences(ControlEnvironment& env) override { - m_device = env.FindDevice(qualifier); - input = env.FindInput(qualifier); - output = env.FindOutput(qualifier); + m_device = env.FindDevice(m_qualifier); + m_input = env.FindInput(m_qualifier); + m_output = env.FindOutput(m_qualifier); } - Device::Input* GetInput() const { return input; }; + Device::Input* GetInput() const { return m_input; }; private: - ControlQualifier qualifier; - Device::Input* input = nullptr; - Device::Output* output = nullptr; + // Keep a shared_ptr to the device so the control pointer doesn't become invalid. + std::shared_ptr m_device; + ControlQualifier m_qualifier; + Device::Input* m_input = nullptr; + Device::Output* m_output = nullptr; }; bool HotkeySuppressions::IsSuppressedIgnoringModifiers(Device::Input* input, @@ -371,6 +369,7 @@ class BinaryExpression : public Expression } case TOK_ASSIGN: { + // Use this carefully as it's extremely powerful and can end up in unforeseen situations lhs->SetValue(rhs->GetValue()); return lhs->GetValue(); } @@ -565,6 +564,9 @@ class HotkeyExpression : public Expression // This class proxies all methods to its either left-hand child if it has bound controls, or its // right-hand child. Its intended use is for supporting old-style barewords expressions. +// Note that if you have a keyboard device as default device and the expression is a single digit +// number, this will usually resolve in a numerical key instead of a numerical value. +// Though if this expression belongs to NumericSetting, it will likely be simplifed back to a value. class CoalesceExpression : public Expression { public: @@ -945,6 +947,7 @@ static std::unique_ptr ParseBarewordExpression(const std::string& st qualifier.control_name = str; qualifier.has_device = false; + // This control expression will only work (find the specified control) with the default device. return std::make_unique(qualifier); } diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.h b/Source/Core/InputCommon/ControlReference/ExpressionParser.h index 9688bc112811..14670848ae39 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.h +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.h @@ -13,8 +13,6 @@ namespace ciface::ExpressionParser { -using namespace ciface::Core; - enum TokenType { TOK_WHITESPACE, @@ -65,7 +63,9 @@ class Token enum class ParseStatus { Successful, + // Note that the expression could still work in this case (be valid and return a value) SyntaxError, + // Will return the default value EmptyExpression, }; @@ -109,6 +109,7 @@ class ControlQualifier public: bool has_device; Core::DeviceQualifier device_qualifier; + // Makes no distinction between input and output std::string control_name; ControlQualifier() : has_device(false) {} @@ -168,7 +169,6 @@ class Expression virtual ControlState GetValue() const = 0; virtual void SetValue(ControlState state) = 0; virtual int CountNumControls() const = 0; - virtual Device::FocusFlags GetFocusFlags() const { return Device::FocusFlags::Default; } virtual void UpdateReferences(ControlEnvironment& finder) = 0; }; diff --git a/Source/Core/InputCommon/ControlReference/FunctionExpression.h b/Source/Core/InputCommon/ControlReference/FunctionExpression.h index e710cd9495ac..3b29baa9ae20 100644 --- a/Source/Core/InputCommon/ControlReference/FunctionExpression.h +++ b/Source/Core/InputCommon/ControlReference/FunctionExpression.h @@ -15,7 +15,6 @@ namespace ciface::ExpressionParser { -// if input > this, then it passes constexpr ControlState CONDITION_THRESHOLD = 0.5; class FunctionExpression : public Expression @@ -33,7 +32,6 @@ class FunctionExpression : public Expression using ArgumentValidation = std::variant; int CountNumControls() const override; - Device::FocusFlags GetFocusFlags() const override; void UpdateReferences(ControlEnvironment& env) override; ArgumentValidation SetArguments(std::vector>&& args); diff --git a/Source/Core/InputCommon/ControllerEmu/Control/Control.h b/Source/Core/InputCommon/ControllerEmu/Control/Control.h index bfdd54dd6078..4a1bb585deec 100644 --- a/Source/Core/InputCommon/ControllerEmu/Control/Control.h +++ b/Source/Core/InputCommon/ControllerEmu/Control/Control.h @@ -23,7 +23,7 @@ class Control virtual ~Control(); template - T GetState() + T GetState() const { return control_ref->GetState(); } diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp index ed4f65bc6774..36733c9f7ede 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp @@ -29,10 +29,10 @@ AnalogStick::AnalogStick(const char* const name_, const char* const ui_name_, for (auto& named_direction : named_directions) AddInput(Translate, named_direction); - AddInput(Translate, _trans("Modifier"), 0.5); + AddInput(Translate, _trans("Modifier")); } -AnalogStick::ReshapeData AnalogStick::GetReshapableState(bool adjusted) +AnalogStick::ReshapeData AnalogStick::GetReshapableState(bool adjusted) const { const ControlState y = controls[0]->GetState() - controls[1]->GetState(); const ControlState x = controls[3]->GetState() - controls[2]->GetState(); @@ -46,7 +46,7 @@ AnalogStick::ReshapeData AnalogStick::GetReshapableState(bool adjusted) return Reshape(x, y, modifier); } -AnalogStick::StateData AnalogStick::GetState() +AnalogStick::StateData AnalogStick::GetState() const { return GetReshapableState(true); } diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h index ac0266f7c1a2..a5ea05a211a2 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h @@ -18,10 +18,10 @@ class AnalogStick : public ReshapableInput AnalogStick(const char* name, std::unique_ptr&& stick_gate); AnalogStick(const char* name, const char* ui_name, std::unique_ptr&& stick_gate); - ReshapeData GetReshapableState(bool adjusted) final override; + ReshapeData GetReshapableState(bool adjusted) const final override; ControlState GetGateRadiusAtAngle(double ang) const override; - StateData GetState(); + StateData GetState() const; private: std::unique_ptr m_stick_gate; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Attachments.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Attachments.h index f92bd8b1fd74..77d44df09314 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Attachments.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Attachments.h @@ -10,6 +10,7 @@ #include #include "Common/CommonTypes.h" +#include "Core/HW/WiimoteEmu/ExtensionPort.h" #include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" #include "InputCommon/ControllerEmu/ControllerEmu.h" #include "InputCommon/ControllerEmu/Setting/NumericSetting.h" @@ -34,7 +35,11 @@ class Attachments : public ControlGroup private: SettingValue m_selection_value; - NumericSetting m_selection_setting = {&m_selection_value, {""}, 0, 0, 0}; + // This is here and not added to the list of numeric_settings because it's serialized differently, + // by string (to be independent from the enum), and visualized differently in the UI. + // For the rest, it's treated similarly to other numeric_settings in the group. + NumericSetting m_selection_setting = { + &m_selection_value, {""}, 0, 0, WiimoteEmu::ExtensionNumber::MAX - 1}; std::vector> m_attachments; }; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Buttons.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Buttons.h index b10677a615de..707c93fa8167 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Buttons.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Buttons.h @@ -20,7 +20,7 @@ class Buttons : public ControlGroup Buttons(const std::string& ini_name, const std::string& group_name); template - void GetState(C* const buttons, const C* bitmasks) + void GetState(C* const buttons, const C* bitmasks) const { for (auto& control : controls) *buttons |= *(bitmasks++) * control->GetState(); diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp index 6933a16db5dc..22f2973d32fd 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp @@ -44,7 +44,7 @@ void ControlGroup::AddDeadzoneSetting(SettingValue* value, double maximu // i18n: The percent symbol. _trans("%"), // i18n: Refers to the dead-zone setting of gamepad inputs. - _trans("Circular input strength to ignore and remap.")}, + _trans("Input strength to ignore and remap.")}, 0, 0, maximum_deadzone); } @@ -72,8 +72,7 @@ void ControlGroup::LoadConfig(IniFile::Section* sec, const std::string& defdev, } // range - sec->Get(group + c->name + "/Range", &c->control_ref->range, - c->control_ref->default_range * 100.0); + sec->Get(group + c->name + "/Range", &c->control_ref->range, 100.0); c->control_ref->range /= 100; } @@ -154,26 +153,19 @@ void ControlGroup::SetControlExpression(int index, const std::string& expression controls.at(index)->control_ref->SetExpression(expression); } -void ControlGroup::AddInput(Translatability translate, std::string name_, ControlState range) +void ControlGroup::AddInput(Translatability translate, std::string name_) { controls.emplace_back(std::make_unique(translate, std::move(name_))); - controls.back().get()->control_ref->default_range = range; - controls.back().get()->control_ref->range = range; } -void ControlGroup::AddInput(Translatability translate, std::string name_, std::string ui_name_, - ControlState range) +void ControlGroup::AddInput(Translatability translate, std::string name_, std::string ui_name_) { controls.emplace_back(std::make_unique(translate, std::move(name_), std::move(ui_name_))); - controls.back().get()->control_ref->default_range = range; - controls.back().get()->control_ref->range = range; } -void ControlGroup::AddOutput(Translatability translate, std::string name_, ControlState range) +void ControlGroup::AddOutput(Translatability translate, std::string name_) { controls.emplace_back(std::make_unique(translate, std::move(name_))); - controls.back().get()->control_ref->default_range = range; - controls.back().get()->control_ref->range = range; } } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h index 75adbb4d5ee7..45c2367c101b 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h @@ -69,19 +69,17 @@ class ControlGroup void SetControlExpression(int index, const std::string& expression); - void AddInput(Translatability translate, std::string name, ControlState range = 1.0); - void AddInput(Translatability translate, std::string name, std::string ui_name, - ControlState range = 1.0); - void AddOutput(Translatability translate, std::string name, ControlState range = 1.0); + void AddInput(Translatability translate, std::string name); + void AddInput(Translatability translate, std::string name, std::string ui_name); + void AddOutput(Translatability translate, std::string name); template void AddSetting(SettingValue* value, const NumericSettingDetails& details, std::common_type_t default_value_, std::common_type_t min_value = {}, - std::common_type_t max_value = T(100), - NumericSetting* edit_condition = nullptr) + std::common_type_t max_value = T(100)) { - numeric_settings.emplace_back(std::make_unique>( - value, details, default_value_, min_value, max_value, edit_condition)); + numeric_settings.emplace_back( + std::make_unique>(value, details, default_value_, min_value, max_value)); } void AddVirtualNotchSetting(SettingValue* value, double max_virtual_notch_deg); @@ -101,7 +99,6 @@ class ControlGroup bool enabled = true; std::vector> controls; - // Settings can point to eatch other so never remove them individually std::vector> numeric_settings; }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp index fa379624dba2..4dbb5a11ed30 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp @@ -64,7 +64,7 @@ Cursor::Cursor(std::string name_, std::string ui_name_) AddSetting(&m_autohide_setting, {_trans("Auto-Hide")}, false); } -Cursor::ReshapeData Cursor::GetReshapableState(bool adjusted) +Cursor::ReshapeData Cursor::GetReshapableState(bool adjusted) const { const ControlState y = controls[0]->GetState() - controls[1]->GetState(); const ControlState x = controls[3]->GetState() - controls[2]->GetState(); diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h index 0c41cd6a7797..a6b38fb4bba5 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h @@ -12,11 +12,6 @@ namespace ControllerEmu { -// Instead of having two instances of this, we have one with two states, -// one for the UI and one for the game. Otherwise when bringing up the -// input settings, it would also affect the cursor state in the game. -// This is not a problem with other ReshapableInput as they don't -// cache a state class Cursor : public ReshapableInput { public: @@ -30,12 +25,11 @@ class Cursor : public ReshapableInput Cursor(std::string name, std::string ui_name); - ReshapeData GetReshapableState(bool adjusted) final override; + ReshapeData GetReshapableState(bool adjusted) const final override; ControlState GetGateRadiusAtAngle(double ang) const override; - StateData GetState(float absolute_time_elapsed, bool is_ui); - - void ResetState(bool is_ui); + // Modifies the state + StateData GetState(bool adjusted); // Yaw movement in radians. ControlState GetTotalYaw() const; @@ -54,21 +48,22 @@ class Cursor : public ReshapableInput static constexpr int AUTO_HIDE_MS = 2500; static constexpr double AUTO_HIDE_DEADZONE = 0.001; - // Not adjusted by width/height/center. - StateData m_state[2]; - StateData m_prev_result[2]; + // Not adjusted by width/height/center: + StateData m_state; + + // Adjusted: + StateData m_prev_result; - int m_auto_hide_timer[2] = {AUTO_HIDE_MS, AUTO_HIDE_MS}; + int m_auto_hide_timer = AUTO_HIDE_MS; using Clock = std::chrono::steady_clock; - Clock::time_point m_last_update[2]; + Clock::time_point m_last_update; SettingValue m_yaw_setting; SettingValue m_pitch_setting; SettingValue m_vertical_offset_setting; SettingValue m_relative_setting; - SettingValue m_relative_absolute_time_setting; SettingValue m_autohide_setting; }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Force.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Force.cpp index af9246c54d8f..457b36205b3a 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Force.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Force.cpp @@ -65,7 +65,7 @@ Force::Force(const std::string& name_) : ReshapableInput(name_, name_, GroupType 90, 1, 180); } -Force::ReshapeData Force::GetReshapableState(bool adjusted) +Force::ReshapeData Force::GetReshapableState(bool adjusted) const { const ControlState y = controls[0]->GetState() - controls[1]->GetState(); const ControlState x = controls[3]->GetState() - controls[2]->GetState(); @@ -77,7 +77,7 @@ Force::ReshapeData Force::GetReshapableState(bool adjusted) return Reshape(x, y); } -Force::StateData Force::GetState(bool adjusted) +Force::StateData Force::GetState(bool adjusted) const { const auto state = GetReshapableState(adjusted); ControlState z = controls[4]->GetState() - controls[5]->GetState(); diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Force.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Force.h index ae9cabe34083..983f668d7840 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Force.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Force.h @@ -19,12 +19,12 @@ class Force : public ReshapableInput explicit Force(const std::string& name); - ReshapeData GetReshapableState(bool adjusted) final override; + ReshapeData GetReshapableState(bool adjusted) const final override; ControlState GetGateRadiusAtAngle(double ang) const final override; ControlState GetDefaultInputRadiusAtAngle(double angle) const final override; - StateData GetState(bool adjusted = true); + StateData GetState(bool adjusted = true) const; // Velocities returned in m/s. ControlState GetSpeed() const; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp index c30011335ddd..95c74b18964f 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp @@ -40,7 +40,7 @@ IMUGyroscope::IMUGyroscope(std::string name_, std::string ui_name_) // i18n: "°/s" is the symbol for degrees (angular measurement) divided by seconds. _trans("°/s"), // i18n: Refers to the dead-zone setting of gyroscope input. - _trans("Angular velocity to ignore.")}, + _trans("Angular velocity to ignore and remap.")}, 2, 0, 180); AddSetting(&m_calibration_period_setting, @@ -137,7 +137,7 @@ std::optional IMUGyroscope::GetState() const auto state = GetRawState(); // If the input gate is disabled, miscalibration to zero values would occur. - if (InputReference::GetInputGate()) + if (ControlReference::GetInputGate()) UpdateCalibration(state); state -= m_calibration; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.cpp index 6dc200d1e17e..1cf2b5a579df 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.cpp @@ -26,52 +26,52 @@ ModifySettingsButton::ModifySettingsButton(std::string button_name) void ModifySettingsButton::AddInput(std::string button_name, bool toggle) { ControlGroup::AddInput(Translate, std::move(button_name)); - threshold_exceeded.emplace_back(false); - associated_settings.emplace_back(false); - associated_settings_toggle.emplace_back(toggle); + m_threshold_exceeded.emplace_back(false); + m_associated_settings.emplace_back(false); + m_associated_settings_toggle.emplace_back(toggle); } -void ModifySettingsButton::GetState() +void ModifySettingsButton::UpdateState() { for (size_t i = 0; i < controls.size(); ++i) { const bool state = controls[i]->GetState(); - if (!associated_settings_toggle[i]) + if (!m_associated_settings_toggle[i]) { // not toggled - associated_settings[i] = state; + m_associated_settings[i] = state; } else { // toggle (loading savestates does not en-/disable toggle) // after we passed the threshold, we en-/disable. but after that, we don't change it // anymore - if (!threshold_exceeded[i] && state) + if (!m_threshold_exceeded[i] && state) { - associated_settings[i] = !associated_settings[i]; + m_associated_settings[i] = !m_associated_settings[i]; - if (associated_settings[i]) + if (m_associated_settings[i]) OSD::AddMessage(controls[i]->ui_name + ": on"); else OSD::AddMessage(controls[i]->ui_name + ": off"); - threshold_exceeded[i] = true; + m_threshold_exceeded[i] = true; } if (!state) - threshold_exceeded[i] = false; + m_threshold_exceeded[i] = false; } } } -const std::vector& ModifySettingsButton::isSettingToggled() const +const std::vector& ModifySettingsButton::IsSettingToggled() const { - return associated_settings_toggle; + return m_associated_settings_toggle; } -const std::vector& ModifySettingsButton::getSettingsModifier() const +const std::vector& ModifySettingsButton::GetSettingsModifier() const { - return associated_settings; + return m_associated_settings; } } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.h index 667f3e5c7aa3..b060aa0caafe 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.h @@ -18,14 +18,14 @@ class ModifySettingsButton : public Buttons void AddInput(std::string button_name, bool toggle = false); - void GetState(); + void UpdateState(); - const std::vector& isSettingToggled() const; - const std::vector& getSettingsModifier() const; + const std::vector& IsSettingToggled() const; + const std::vector& GetSettingsModifier() const; private: - std::vector threshold_exceeded; // internal calculation (if "state" was above threshold) - std::vector associated_settings_toggle; // is setting toggled or hold? - std::vector associated_settings; // result + std::vector m_threshold_exceeded; // internal calculation (if "state" was above threshold) + std::vector m_associated_settings_toggle; // is setting toggled or hold? + std::vector m_associated_settings; // result }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Slider.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Slider.cpp index bd70c64b534f..d8aeea4eeece 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Slider.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Slider.cpp @@ -29,7 +29,7 @@ Slider::Slider(const std::string& name_) : Slider(name_, name_) { } -Slider::StateData Slider::GetState() +Slider::StateData Slider::GetState() const { const ControlState deadzone = m_deadzone_setting.GetValue() / 100; const ControlState state = controls[1]->GetState() - controls[0]->GetState(); diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Slider.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Slider.h index de969f59b5ae..930241d7fe9c 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Slider.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Slider.h @@ -23,7 +23,7 @@ class Slider : public ControlGroup Slider(const std::string& name_, const std::string& ui_name_); explicit Slider(const std::string& name_); - StateData GetState(); + StateData GetState() const; private: SettingValue m_deadzone_setting; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp index d39fcf3a3f14..053d0bf429d2 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp @@ -23,7 +23,7 @@ Tilt::Tilt(const std::string& name_) : ReshapableInput(name_, name_, GroupType:: AddInput(Translate, _trans("Left")); AddInput(Translate, _trans("Right")); - AddInput(Translate, _trans("Modifier"), 0.5); + AddInput(Translate, _trans("Modifier")); AddSetting(&m_max_angle_setting, {_trans("Angle"), @@ -42,7 +42,7 @@ Tilt::Tilt(const std::string& name_) : ReshapableInput(name_, name_, GroupType:: 7, 1, 50); } -Tilt::ReshapeData Tilt::GetReshapableState(bool adjusted) +Tilt::ReshapeData Tilt::GetReshapableState(bool adjusted) const { const ControlState y = controls[0]->GetState() - controls[1]->GetState(); const ControlState x = controls[3]->GetState() - controls[2]->GetState(); @@ -56,7 +56,7 @@ Tilt::ReshapeData Tilt::GetReshapableState(bool adjusted) return Reshape(x, y, modifier); } -Tilt::StateData Tilt::GetState() +Tilt::StateData Tilt::GetState() const { return GetReshapableState(true); } diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.h index dc4c2364de03..cb50c5eb7387 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.h @@ -19,14 +19,14 @@ class Tilt : public ReshapableInput explicit Tilt(const std::string& name); - ReshapeData GetReshapableState(bool adjusted) final override; + ReshapeData GetReshapableState(bool adjusted) const final override; ControlState GetGateRadiusAtAngle(double angle) const final override; // Tilt is using the gate radius to adjust the tilt angle so we must provide an unadjusted value // for the default input radius. ControlState GetDefaultInputRadiusAtAngle(double angle) const final override; - StateData GetState(); + StateData GetState() const; // Return peak rotational velocity (for a complete turn) in radians/sec ControlState GetMaxRotationalVelocity() const; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Triggers.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Triggers.cpp index bdfec69808c5..7aaac42b723f 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Triggers.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Triggers.cpp @@ -21,7 +21,7 @@ Triggers::Triggers(const std::string& name_) : ControlGroup(name_, GroupType::Tr AddDeadzoneSetting(&m_deadzone_setting, 50); } -Triggers::StateData Triggers::GetState() +Triggers::StateData Triggers::GetState() const { const size_t trigger_count = controls.size(); const ControlState deadzone = m_deadzone_setting.GetValue() / 100; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Triggers.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Triggers.h index 8b72d6738be1..5d75ff09bce1 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Triggers.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Triggers.h @@ -26,7 +26,7 @@ class Triggers : public ControlGroup explicit Triggers(const std::string& name); - StateData GetState(); + StateData GetState() const; private: SettingValue m_deadzone_setting; diff --git a/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h b/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h index c3390bc9b016..86bd2e39c34f 100644 --- a/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h +++ b/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h @@ -57,9 +57,6 @@ class NumericSettingBase virtual InputReference& GetInputReference() = 0; virtual const InputReference& GetInputReference() const = 0; - // Can be edited in UI? This setting should not used if is false - virtual bool IsEnabled() const = 0; - virtual bool IsSimpleValue() const = 0; // Convert a literal expression e.g. "7.0" to a regular value. (disables expression parsing) @@ -92,10 +89,9 @@ class NumericSetting final : public NumericSettingBase "NumericSetting is only implemented for int, double, and bool."); NumericSetting(SettingValue* value, const NumericSettingDetails& details, - ValueType default_value, ValueType min_value, ValueType max_value, - NumericSetting* edit_condition = nullptr) + ValueType default_value, ValueType min_value, ValueType max_value) : NumericSettingBase(details), m_value(*value), m_default_value(default_value), - m_min_value(min_value), m_max_value(max_value), m_edit_condition(edit_condition) + m_min_value(min_value), m_max_value(max_value) { m_value.SetValue(m_default_value); } @@ -129,9 +125,6 @@ class NumericSetting final : public NumericSettingBase } } - bool IsEnabled() const override { return !m_edit_condition || m_edit_condition->GetValue(); } - const NumericSetting* GetEditCondition() const { return m_edit_condition; } - bool IsSimpleValue() const override { return m_value.IsSimpleValue(); } void SimplifyIfPossible() override @@ -160,9 +153,6 @@ class NumericSetting final : public NumericSettingBase const ValueType m_default_value; const ValueType m_min_value; const ValueType m_max_value; - - // Assuming settings are never destroyed if not all together - const NumericSetting* m_edit_condition; }; template @@ -175,7 +165,10 @@ class SettingValue public: ValueType GetValue() const { - if (!IsSimpleValue()) + // Only update dynamic values when the input gate is enabled. + // Otherwise settings will all change to 0 when window focus is lost. + // This is very undesirable for things like battery level or attached extension. + if (!IsSimpleValue() && ControlReference::GetInputGate()) m_value = m_input.GetState(); return m_value; @@ -196,8 +189,7 @@ class SettingValue mutable std::atomic m_value = {}; // Unfortunately InputReference's state grabbing is non-const requiring mutable here. - // Use IgnoreGateInputReference so we don't lose the setting when we lose focus. - mutable IgnoreGateInputReference m_input; + mutable InputReference m_input; }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.cpp b/Source/Core/InputCommon/ControllerEmu/StickGate.cpp index 85cf48152f6a..d57a2402cfc3 100644 --- a/Source/Core/InputCommon/ControllerEmu/StickGate.cpp +++ b/Source/Core/InputCommon/ControllerEmu/StickGate.cpp @@ -128,7 +128,8 @@ std::optional SquareStickGate::GetIdealCalibrationSampleCount() const ReshapableInput::ReshapableInput(std::string name_, std::string ui_name_, GroupType type_) : ControlGroup(std::move(name_), std::move(ui_name_), type_) { - AddDeadzoneSetting(&m_deadzone_setting, 75); + // 50 is not always enough but users can set it to more with an expression + AddDeadzoneSetting(&m_deadzone_setting, 50); } ControlState ReshapableInput::GetDeadzoneRadiusAtAngle(double angle) const @@ -280,13 +281,15 @@ void ReshapableInput::SaveConfig(IniFile::Section* section, const std::string& d } ReshapableInput::ReshapeData ReshapableInput::Reshape(ControlState x, ControlState y, - ControlState modifier, ControlState clamp) + ControlState modifier, + ControlState clamp) const { x -= m_center.x; y -= m_center.y; // We run this even if both x and y will be zero. - // The angle value will be random but dist will stay 0 + // In that case, std::atan2(0, 0) returns a valid non-NaN value, but the exact value + // (which depends on the signs of x and y) does not matter here as dist is zero // TODO: make the AtAngle functions work with negative angles: ControlState angle = std::atan2(y, x) + MathUtil::TAU; @@ -311,7 +314,11 @@ ReshapableInput::ReshapeData ReshapableInput::Reshape(ControlState x, ControlSta // This is affected by the modifier's "range" setting which defaults to 50%. if (modifier) { - dist *= modifier; + // TODO: Modifier's range setting gets reset to 100% when the clear button is clicked. + // This causes the modifier to not behave how a user might suspect. + // Retaining the old scale-by-50% behavior until range is fixed to clear to 50%. + dist *= 0.5; + // dist *= modifier; } // Apply deadzone as a percentage of the user-defined calibration shape/size: diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.h b/Source/Core/InputCommon/ControllerEmu/StickGate.h index c04a39af9531..c2b2f7b1834e 100644 --- a/Source/Core/InputCommon/ControllerEmu/StickGate.h +++ b/Source/Core/InputCommon/ControllerEmu/StickGate.h @@ -91,7 +91,7 @@ class ReshapableInput : public ControlGroup virtual ControlState GetVirtualNotchSize() const { return 0.0; }; virtual ControlState GetGateRadiusAtAngle(double angle) const = 0; - virtual ReshapeData GetReshapableState(bool adjusted) = 0; + virtual ReshapeData GetReshapableState(bool adjusted) const = 0; virtual ControlState GetDefaultInputRadiusAtAngle(double ang) const; void SetCalibrationToDefault(); @@ -109,7 +109,7 @@ class ReshapableInput : public ControlGroup protected: ReshapeData Reshape(ControlState x, ControlState y, ControlState modifier = 0.0, - ControlState clamp = 1.0); + ControlState clamp = 1.0) const; private: void LoadConfig(IniFile::Section*, const std::string&, const std::string&) override; diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 738741f256f6..0cae47c741f5 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -237,7 +237,7 @@ void ControllerInterface::AddDevice(std::shared_ptr device device->SetId(id); } - NOTICE_LOG_FMT(SERIALINTERFACE, "Added device: {}", device->GetQualifiedName()); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "Added device: {}", device->GetQualifiedName()); m_devices.emplace_back(std::move(device)); } @@ -252,7 +252,7 @@ void ControllerInterface::RemoveDevice(std::functionGetQualifiedName()); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "Removed device: {}", dev->GetQualifiedName()); return true; } return false; @@ -265,16 +265,14 @@ void ControllerInterface::RemoveDevice(std::functionUpdateInput(); - should_update_fake_relative_axes = false; } } diff --git a/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp b/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp index b0f312289033..3a396ddf37ff 100644 --- a/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp +++ b/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp @@ -15,7 +15,6 @@ #include "Common/MathUtil.h" #include "Common/Thread.h" -#include "InputCommon/ControllerInterface/ControllerInterface.h" namespace ciface::Core { @@ -446,9 +445,4 @@ auto DeviceContainer::DetectInput(const std::vector& device_strings return detections; } - -Device::InputChannel Device::Input::GetCurrentInputChannel() const -{ - return ControllerInterface::GetCurrentInputChannel(); -} } // namespace ciface::Core diff --git a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h index abe0dde17321..c368578949de 100644 --- a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h +++ b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h @@ -61,40 +61,6 @@ class Device virtual bool IsMatchingName(std::string_view name) const; }; - // A set of flags we can define to determine whether this control should be - // read (or written), or ignored/blocked, based on our current game app/window focus. - // As of now they are only used by inputs but nothing prevents us from implementing them - // on outputs, and they are not limited to focus, if we came up with new blocking conditions. - // - // These flags are per Control, but they will get summed up with other expressions - // from the same ControlReference. This is because checking them per input/output - // would have been too complicated, expensive, and ultimately, useless. - // So they are in order of priority, and some of them are mutually exclusive. - // Users can use function expressions to customize the focus requirements of a - // ControlReference, but we manually hardcode them in some Inputs (e.g. mouse) so - // users don't have to bother in most cases, as it's easy to understand as a concept. - enum class FocusFlags : u8 - { - // The input is only passed if we have focus (or the user accepts - // background input) - RequireFocus = 0x01, - // The input is only passed if we have "full" focus, which means the mouse - // has been locked into the game window. Ignored if mouse locking is off - RequireFullFocus = 0x02, - // Some inputs are able to make you lose or gain focus or full focus, - // for example a mouse click, or the Windows key. When these are pressed - // and there is a window focus change, ignore them for the time being, - // as the user didn't want the application to react - IgnoreOnFocusChanged = 0x04, - // Forces the input to be passed even if we have no focus, - // useful for things like battery level. This is not 0 because it needs - // higher priority over other flags - IgnoreFocus = 0x80, - - // Even expressions that are fixed should return this - Default = RequireFocus - }; - // // Input // @@ -107,17 +73,15 @@ class Device // undesirable behavior in our mapping logic. virtual bool IsDetectable() const { return true; } - virtual FocusFlags GetFocusFlags() const { return FocusFlags::Default; } - // Implementations should return a value from 0.0 to 1.0 across their normal range. // One input should be provided for each "direction". (e.g. 2 for each axis) - // If possible, negative values may be returned in situations where an opposing input - // is activated. (e.g. When an underlying axis, X, is currently negative, "Axis X-", - // will return a positive value and "Axis X+" may return a negative value.) + // If possible, negative values may be returned in situations where an opposing input is + // activated. (e.g. When an underlying axis, X, is currently negative, "Axis X-", will return a + // positive value and "Axis X+" may return a negative value.) // Doing so is solely to allow our input detection logic to better detect false positives. // This is necessary when making use of "FullAnalogSurface" as multiple inputs will be seen - // increasing from 0.0 to 1.0 as a user tries to map just one. The negative values provide - // a view of the underlying axis. (Negative values are clamped off before they reach + // increasing from 0.0 to 1.0 as a user tries to map just one. The negative values provide a + // view of the underlying axis. (Negative values are clamped off before they reach // expression-parser or controller-emu) virtual ControlState GetState() const = 0; diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp index 78f9830b8531..ea855632c987 100644 --- a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp @@ -125,9 +125,6 @@ class Device final : public Core::Device bool IsDetectable() const override { return false; } - // We don't need focus to pass the battery level - FocusFlags GetFocusFlags() const override { return FocusFlags::IgnoreFocus; } - private: const BatteryState& m_battery; }; @@ -208,7 +205,7 @@ static bool IsSameController(const Proto::MessageType::PortInfo& a, static void HotplugThreadFunc() { Common::SetCurrentThreadName("DualShockUDPClient Hotplug Thread"); - INFO_LOG_FMT(SERIALINTERFACE, "DualShockUDPClient hotplug thread started"); + INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient hotplug thread started"); while (s_hotplug_thread_running.IsSet()) { @@ -228,7 +225,7 @@ static void HotplugThreadFunc() if (server.m_socket.send(&list_ports, sizeof list_ports, server.m_address, server.m_port) != sf::Socket::Status::Done) { - ERROR_LOG_FMT(SERIALINTERFACE, "DualShockUDPClient HotplugThreadFunc send failed"); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient HotplugThreadFunc send failed"); } } } @@ -280,7 +277,7 @@ static void HotplugThreadFunc() } } } - INFO_LOG_FMT(SERIALINTERFACE, "DualShockUDPClient hotplug thread stopped"); + INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient hotplug thread stopped"); } static void StartHotplugThread() @@ -313,7 +310,7 @@ static void StopHotplugThread() static void Restart() { - INFO_LOG_FMT(SERIALINTERFACE, "DualShockUDPClient Restart"); + INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient Restart"); StopHotplugThread(); @@ -397,7 +394,7 @@ void Init() void PopulateDevices() { - INFO_LOG_FMT(SERIALINTERFACE, "DualShockUDPClient PopulateDevices"); + INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient PopulateDevices"); // s_servers has already been updated so we can't use it to know which devices we removed, // also it's good to remove all of them before adding new ones so that their id will be set @@ -513,7 +510,7 @@ void Device::UpdateInput() if (m_socket.send(&data_req, sizeof(data_req), m_server_address, m_server_port) != sf::Socket::Status::Done) { - ERROR_LOG_FMT(SERIALINTERFACE, "DualShockUDPClient UpdateInput send failed"); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient UpdateInput send failed"); } } diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h index 582b48355566..5d445e11dabb 100644 --- a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h @@ -249,7 +249,7 @@ struct Message if (crc32_in_header != crc32_calculated) { NOTICE_LOG_FMT( - SERIALINTERFACE, + CONTROLLERINTERFACE, "DualShockUDPClient Received message with bad CRC in header: got {:08x}, expected {:08x}", crc32_in_header, crc32_calculated); return std::nullopt; diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm b/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm index de878b6a3e43..63bddf499e9c 100644 --- a/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm @@ -178,11 +178,11 @@ void Init(void* window) HIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); if (!HIDManager) - ERROR_LOG_FMT(SERIALINTERFACE, "Failed to create HID Manager reference"); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "Failed to create HID Manager reference"); IOHIDManagerSetDeviceMatching(HIDManager, nullptr); if (IOHIDManagerOpen(HIDManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) - ERROR_LOG_FMT(SERIALINTERFACE, "Failed to open HID Manager"); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "Failed to open HID Manager"); // Callbacks for acquisition or loss of a matching device IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, DeviceMatchingCallback, nullptr); @@ -198,7 +198,7 @@ void Init(void* window) // Enable hotplugging s_hotplug_thread = std::thread([] { Common::SetCurrentThreadName("IOHIDManager Hotplug Thread"); - NOTICE_LOG_FMT(SERIALINTERFACE, "IOHIDManager hotplug thread started"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "IOHIDManager hotplug thread started"); IOHIDManagerScheduleWithRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop); s_stopper.AddToRunLoop(CFRunLoopGetCurrent(), OurRunLoop); @@ -206,7 +206,7 @@ void Init(void* window) s_stopper.RemoveFromRunLoop(CFRunLoopGetCurrent(), OurRunLoop); IOHIDManagerUnscheduleFromRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop); - NOTICE_LOG_FMT(SERIALINTERFACE, "IOHIDManager hotplug thread stopped"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "IOHIDManager hotplug thread stopped"); }); } diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm index 39794426a815..f08f704275f3 100644 --- a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm @@ -109,8 +109,9 @@ break; } - NOTICE_LOG_FMT(SERIALINTERFACE, "Unknown IOHIDElement, ignoring (Usage: {:x}, Type: {:x})", - usage, IOHIDElementGetType(e)); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, + "Unknown IOHIDElement, ignoring (Usage: {:x}, Type: {:x})", usage, + IOHIDElementGetType(e)); break; } diff --git a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h index 1024280ebdd5..740954082be4 100644 --- a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h +++ b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h @@ -38,10 +38,6 @@ class KeyboardAndMouse : public Core::Device std::string GetName() const override; bool IsDetectable() const override { return false; } ControlState GetState() const override; - FocusFlags GetFocusFlags() const override - { - return FocusFlags((u8(FocusFlags::RequireFocus) | u8(FocusFlags::RequireFullFocus))); - } private: const float& m_axis; @@ -55,11 +51,6 @@ class KeyboardAndMouse : public Core::Device explicit Button(CGMouseButton button) : m_button(button) {} std::string GetName() const override; ControlState GetState() const override; - FocusFlags GetFocusFlags() const override - { - return FocusFlags(u8(FocusFlags::RequireFocus) | u8(FocusFlags::RequireFullFocus) | - u8(FocusFlags::IgnoreOnFocusChanged)); - } private: CGMouseButton m_button; diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp index e232fd6ee70b..f908181c8d87 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp @@ -81,7 +81,7 @@ void Init() { #if !SDL_VERSION_ATLEAST(2, 0, 0) if (SDL_Init(SDL_INIT_JOYSTICK) != 0) - ERROR_LOG_FMT(SERIALINTERFACE, "SDL failed to initialize"); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL failed to initialize"); return; #else s_hotplug_thread = std::thread([] { @@ -95,14 +95,14 @@ void Init() if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC) != 0) { - ERROR_LOG_FMT(SERIALINTERFACE, "SDL failed to initialize"); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL failed to initialize"); return; } const Uint32 custom_events_start = SDL_RegisterEvents(2); if (custom_events_start == static_cast(-1)) { - ERROR_LOG_FMT(SERIALINTERFACE, "SDL failed to register custom events"); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL failed to register custom events"); return; } s_stop_event_type = custom_events_start; diff --git a/Source/Core/InputCommon/ControllerInterface/Touch/ButtonManager.cpp b/Source/Core/InputCommon/ControllerInterface/Touch/ButtonManager.cpp index 75f69b8823ae..a749f83a0113 100644 --- a/Source/Core/InputCommon/ControllerInterface/Touch/ButtonManager.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Touch/ButtonManager.cpp @@ -766,7 +766,7 @@ bool InputDevice::PressEvent(int button, int action) if (binding.second->m_bind_type == BIND_BUTTON) m_buttons[binding.second->m_button_type] = action == BUTTON_PRESSED ? true : false; else - m_axises[binding.second->m_button_type] = action == BUTTON_PRESSED ? 1.0f : 0.0f; + m_axes[binding.second->m_button_type] = action == BUTTON_PRESSED ? 1.0f : 0.0f; handled = true; } } @@ -780,34 +780,54 @@ void InputDevice::AxisEvent(int axis, float value) if (binding.second->m_bind == axis) { if (binding.second->m_bind_type == BIND_AXIS) - m_axises[binding.second->m_button_type] = value; + m_axes[binding.second->m_button_type] = value; else m_buttons[binding.second->m_button_type] = value > 0.5f ? true : false; } } } -bool InputDevice::ButtonValue(int pad_id, ButtonType button) +bool InputDevice::ButtonValue(int pad_id, ButtonType button) const { - const auto& binding = m_input_binds.find(std::make_pair(pad_id, button)); + const auto binding = m_input_binds.find(std::make_pair(pad_id, button)); if (binding == m_input_binds.end()) return false; if (binding->second->m_bind_type == BIND_BUTTON) - return m_buttons[binding->second->m_button_type]; + { + const auto button = m_buttons.find(binding->second->m_button_type); + if (button == m_buttons.end()) + return false; + return button->second; + } else - return (m_axises[binding->second->m_button_type] * binding->second->m_neg) > 0.5f; + { + const auto axis = m_axes.find(binding->second->m_button_type); + if (axis == m_axes.end()) + return false; + return (axis->second * binding->second->m_neg) > 0.5f; + } } -float InputDevice::AxisValue(int pad_id, ButtonType axis) +float InputDevice::AxisValue(int pad_id, ButtonType axis) const { - const auto& binding = m_input_binds.find(std::make_pair(pad_id, axis)); + const auto binding = m_input_binds.find(std::make_pair(pad_id, axis)); if (binding == m_input_binds.end()) return 0.0f; if (binding->second->m_bind_type == BIND_AXIS) - return m_axises[binding->second->m_button_type] * binding->second->m_neg; + { + const auto axis = m_axes.find(binding->second->m_button_type); + if (axis == m_axes.end()) + return 0.0f; + return axis->second * binding->second->m_neg; + } else - return m_buttons[binding->second->m_button_type] == BUTTON_PRESSED ? 1.0f : 0.0f; + { + const auto button = m_buttons.find(binding->second->m_button_type); + if (button == m_buttons.end()) + return 0.0f; + return button->second == BUTTON_PRESSED ? 1.0f : 0.0f; + } } } // namespace ButtonManager diff --git a/Source/Core/InputCommon/ControllerInterface/Touch/ButtonManager.h b/Source/Core/InputCommon/ControllerInterface/Touch/ButtonManager.h index 2139c2e2caf9..13b52bb989f1 100644 --- a/Source/Core/InputCommon/ControllerInterface/Touch/ButtonManager.h +++ b/Source/Core/InputCommon/ControllerInterface/Touch/ButtonManager.h @@ -210,7 +210,7 @@ class Button public: Button() : m_state(BUTTON_RELEASED) {} void SetState(ButtonState state) { m_state = state; } - bool Pressed() { return m_state == BUTTON_PRESSED; } + bool Pressed() const { return m_state == BUTTON_PRESSED; } ~Button() {} }; class Axis @@ -221,7 +221,7 @@ class Axis public: Axis() : m_value(0.0f) {} void SetValue(float value) { m_value = value; } - float AxisValue() { return m_value; } + float AxisValue() const { return m_value; } ~Axis() {} }; @@ -244,7 +244,7 @@ class InputDevice private: const std::string m_dev; std::map m_buttons; - std::map m_axises; + std::map m_axes; // Key is pad_id and ButtonType std::map, sBind*> m_input_binds; @@ -263,8 +263,8 @@ class InputDevice } bool PressEvent(int button, int action); void AxisEvent(int axis, float value); - bool ButtonValue(int pad_id, ButtonType button); - float AxisValue(int pad_id, ButtonType axis); + bool ButtonValue(int pad_id, ButtonType button) const; + float AxisValue(int pad_id, ButtonType axis) const; }; void Init(const std::string&); diff --git a/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp b/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp index 9647d68c006b..eaa56ef44918 100644 --- a/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp @@ -15,8 +15,6 @@ namespace ciface::WiimoteController { -using namespace ciface; - static constexpr char SOURCE_NAME[] = "Bluetooth"; static constexpr size_t IR_SENSITIVITY_LEVEL_COUNT = 5; @@ -41,7 +39,7 @@ class Button final : public Core::Device::Input }; // GetState returns value divided by supplied "extent". -template +template class GenericInput : public Core::Device::Input { public: @@ -52,8 +50,6 @@ class GenericInput : public Core::Device::Input bool IsDetectable() const override { return Detectable; } - Core::Device::FocusFlags GetFocusFlags() const override { return TFocusFlags; } - std::string GetName() const override { return m_name; } ControlState GetState() const final override { return ControlState(m_value) / m_extent; } @@ -67,8 +63,8 @@ class GenericInput : public Core::Device::Input template using AnalogInput = GenericInput; -template -using UndetectableAnalogInput = GenericInput; +template +using UndetectableAnalogInput = GenericInput; // GetName() is appended with '-' or '+' based on sign of "extent" value. template @@ -292,13 +288,10 @@ Device::Device(std::unique_ptr wiimote) : m_wiimote(std::m AddInput(new AnalogInput(&m_classic_state.triggers[1], classic_prefix + "R-Analog", 1.f)); // Specialty inputs: - AddInput(new UndetectableAnalogInput( - &m_battery, "Battery", 1.f)); - AddInput(new UndetectableAnalogInput( + AddInput(new UndetectableAnalogInput(&m_battery, "Battery", 1.f)); + AddInput(new UndetectableAnalogInput( &m_extension_number_input, "Attached Extension", WiimoteEmu::ExtensionNumber(1))); - AddInput(new UndetectableAnalogInput( - &m_mplus_attached_input, "Attached MotionPlus", 1)); + AddInput(new UndetectableAnalogInput(&m_mplus_attached_input, "Attached MotionPlus", 1)); AddOutput(new Motor(&m_rumble_level)); } diff --git a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp index 096ad3182709..b7cf19a13d6c 100644 --- a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp @@ -53,7 +53,7 @@ void ciface::Win32::Init(void* hwnd) if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) { - ERROR_LOG_FMT(SERIALINTERFACE, "CoInitializeEx failed: {}", GetLastError()); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "CoInitializeEx failed: {}", GetLastError()); return; } Common::ScopeGuard uninit([] { CoUninitialize(); }); @@ -67,12 +67,12 @@ void ciface::Win32::Init(void* hwnd) ATOM window_class = RegisterClassEx(&window_class_info); if (!window_class) { - NOTICE_LOG_FMT(SERIALINTERFACE, "RegisterClassEx failed: {}", GetLastError()); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "RegisterClassEx failed: {}", GetLastError()); return; } Common::ScopeGuard unregister([&window_class] { if (!UnregisterClass(MAKEINTATOM(window_class), GetModuleHandle(nullptr))) - ERROR_LOG_FMT(SERIALINTERFACE, "UnregisterClass failed: {}", GetLastError()); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "UnregisterClass failed: {}", GetLastError()); }); message_window = CreateWindowEx(0, L"Message", nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, @@ -80,12 +80,12 @@ void ciface::Win32::Init(void* hwnd) promise_guard.Exit(); if (!message_window) { - ERROR_LOG_FMT(SERIALINTERFACE, "CreateWindowEx failed: {}", GetLastError()); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "CreateWindowEx failed: {}", GetLastError()); return; } Common::ScopeGuard destroy([&] { if (!DestroyWindow(message_window)) - ERROR_LOG_FMT(SERIALINTERFACE, "DestroyWindow failed: {}", GetLastError()); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "DestroyWindow failed: {}", GetLastError()); }); std::array devices; @@ -103,7 +103,7 @@ void ciface::Win32::Init(void* hwnd) if (!RegisterRawInputDevices(devices.data(), static_cast(devices.size()), static_cast(sizeof(decltype(devices)::value_type)))) { - ERROR_LOG_FMT(SERIALINTERFACE, "RegisterRawInputDevices failed: {}", GetLastError()); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "RegisterRawInputDevices failed: {}", GetLastError()); return; } @@ -128,18 +128,18 @@ void ciface::Win32::PopulateDevices(void* hwnd) s_done_populating.Reset(); PostMessage(s_message_window, WM_INPUT_DEVICE_CHANGE, 0, 0); if (!s_done_populating.WaitFor(std::chrono::seconds(10))) - ERROR_LOG_FMT(SERIALINTERFACE, "win32 timed out when trying to populate devices"); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "win32 timed out when trying to populate devices"); } else { - ERROR_LOG_FMT(SERIALINTERFACE, + ERROR_LOG_FMT(CONTROLLERINTERFACE, "win32 asked to populate devices, but device thread isn't running"); } } void ciface::Win32::DeInit() { - NOTICE_LOG_FMT(SERIALINTERFACE, "win32 DeInit"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "win32 DeInit"); if (s_thread.joinable()) { PostMessage(s_message_window, WM_DOLPHIN_STOP, 0, 0); diff --git a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp index f45e539ba674..875cce47302c 100644 --- a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp +++ b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp @@ -119,11 +119,6 @@ class Battery final : public Core::Device::Input std::string GetName() const override { return "Battery"; } ControlState GetState() const override { return m_level; } bool IsDetectable() const override { return false; } - // We don't need focus to pass the battery level - virtual Core::Device::FocusFlags GetFocusFlags() const - { - return Core::Device::FocusFlags::IgnoreFocus; - } private: const ControlState& m_level; diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h index dbb46ba13442..71681a3e1054 100644 --- a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h @@ -55,11 +55,6 @@ class KeyboardMouse : public Core::Device std::string GetName() const override { return name; } Button(unsigned int index, unsigned int* buttons); ControlState GetState() const override; - FocusFlags GetFocusFlags() const override - { - return FocusFlags(u8(FocusFlags::RequireFocus) | u8(FocusFlags::RequireFullFocus) | - u8(FocusFlags::IgnoreOnFocusChanged)); - } private: const unsigned int* m_buttons; @@ -74,10 +69,6 @@ class KeyboardMouse : public Core::Device bool IsDetectable() const override { return false; } Cursor(u8 index, bool positive, const float* cursor); ControlState GetState() const override; - FocusFlags GetFocusFlags() const override - { - return FocusFlags((u8(FocusFlags::RequireFocus) | u8(FocusFlags::RequireFullFocus))); - } private: const float* m_cursor; @@ -86,7 +77,6 @@ class KeyboardMouse : public Core::Device std::string name; }; - // TODO: copy new implementation from DInputKeyboardMouse.cpp class Axis : public Input { public: @@ -94,10 +84,6 @@ class KeyboardMouse : public Core::Device bool IsDetectable() const override { return false; } Axis(u8 index, bool positive, const float* axis); ControlState GetState() const override; - FocusFlags GetFocusFlags() const override - { - return FocusFlags(u8(FocusFlags::RequireFocus) | u8(FocusFlags::RequireFullFocus)); - } private: const float* m_axis; diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp index 214dd323acac..265d81ad405e 100644 --- a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp @@ -253,7 +253,7 @@ static void AddDeviceNode(const char* devnode) auto evdev_device = FindDeviceWithUniqueIDAndPhysicalLocation(uniq, phys); if (evdev_device) { - NOTICE_LOG_FMT(SERIALINTERFACE, + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev combining devices with unique id: {}, physical location: {}", uniq, phys); evdev_device->AddNode(devnode, fd, dev); @@ -282,7 +282,7 @@ static void AddDeviceNode(const char* devnode) static void HotplugThreadFunc() { Common::SetCurrentThreadName("evdev Hotplug Thread"); - NOTICE_LOG_FMT(SERIALINTERFACE, "evdev hotplug thread started"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev hotplug thread started"); udev* const udev = udev_new(); Common::ScopeGuard udev_guard([udev] { udev_unref(udev); }); @@ -337,7 +337,7 @@ static void HotplugThreadFunc() AddDeviceNode(devnode); } } - NOTICE_LOG_FMT(SERIALINTERFACE, "evdev hotplug thread stopped"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev hotplug thread stopped"); } static void StartHotplugThread() diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index cf16d06b0c10..6041defaa3f9 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -88,7 +88,8 @@ static void Read() int err = libusb_interrupt_transfer(s_handle, s_endpoint_in, s_controller_payload_swap, sizeof(s_controller_payload_swap), &payload_size, 16); if (err) - ERROR_LOG_FMT(SERIALINTERFACE, "adapter libusb read failed: err={}", libusb_error_name(err)); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "adapter libusb read failed: err={}", + libusb_error_name(err)); { std::lock_guard lk(s_mutex); @@ -121,7 +122,8 @@ static void Write() const int err = libusb_interrupt_transfer(s_handle, s_endpoint_out, payload, sizeof(payload), &size, 16); if (err != 0) - ERROR_LOG_FMT(SERIALINTERFACE, "adapter libusb write failed: err={}", libusb_error_name(err)); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "adapter libusb write failed: err={}", + libusb_error_name(err)); } } @@ -154,7 +156,7 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl static void ScanThreadFunc() { Common::SetCurrentThreadName("GC Adapter Scanning Thread"); - NOTICE_LOG_FMT(SERIALINTERFACE, "GC Adapter scanning thread started"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter scanning thread started"); #if defined(LIBUSB_API_VERSION) && LIBUSB_API_VERSION >= 0x01000102 #ifndef __FreeBSD__ @@ -170,7 +172,7 @@ static void ScanThreadFunc() nullptr, &s_hotplug_handle) != LIBUSB_SUCCESS) s_libusb_hotplug_enabled = false; if (s_libusb_hotplug_enabled) - NOTICE_LOG_FMT(SERIALINTERFACE, "Using libUSB hotplug detection"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "Using libUSB hotplug detection"); } #endif @@ -187,7 +189,7 @@ static void ScanThreadFunc() else Common::SleepCurrentThread(500); } - NOTICE_LOG_FMT(SERIALINTERFACE, "GC Adapter scanning thread stopped"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter scanning thread stopped"); } void SetAdapterCallback(std::function func) @@ -265,7 +267,7 @@ static bool CheckDeviceAccess(libusb_device* device) if (ret != 0) { // could not acquire the descriptor, no point in trying to use it. - ERROR_LOG_FMT(SERIALINTERFACE, "libusb_get_device_descriptor failed with error: {}", ret); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "libusb_get_device_descriptor failed with error: {}", ret); return false; } @@ -275,7 +277,7 @@ static bool CheckDeviceAccess(libusb_device* device) return false; } - NOTICE_LOG_FMT(SERIALINTERFACE, "Found GC Adapter with Vendor: {:X} Product: {:X} Devnum: {}", + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "Found GC Adapter with Vendor: {:X} Product: {:X} Devnum: {}", desc.idVendor, desc.idProduct, 1); // In case of failure, capture the libusb error code into the adapter status @@ -287,14 +289,14 @@ static bool CheckDeviceAccess(libusb_device* device) if (ret == LIBUSB_ERROR_ACCESS) { ERROR_LOG_FMT( - SERIALINTERFACE, + CONTROLLERINTERFACE, "Dolphin does not have access to this device: Bus {:03d} Device {:03d}: ID {:04X}:{:04X}.", bus, port, desc.idVendor, desc.idProduct); return false; } if (ret != 0) { - ERROR_LOG_FMT(SERIALINTERFACE, "libusb_open failed to open device with error = {}", ret); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "libusb_open failed to open device with error = {}", ret); return false; } @@ -303,14 +305,14 @@ static bool CheckDeviceAccess(libusb_device* device) { ret = libusb_detach_kernel_driver(s_handle, 0); if (ret != 0 && ret != LIBUSB_ERROR_NOT_SUPPORTED) - ERROR_LOG_FMT(SERIALINTERFACE, "libusb_detach_kernel_driver failed with error: {}", ret); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "libusb_detach_kernel_driver failed with error: {}", ret); } // This call makes Nyko-brand (and perhaps other) adapters work. // However it returns LIBUSB_ERROR_PIPE with Mayflash adapters. const int transfer = libusb_control_transfer(s_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000); if (transfer < 0) - WARN_LOG_FMT(SERIALINTERFACE, "libusb_control_transfer failed with error: {}", transfer); + WARN_LOG_FMT(CONTROLLERINTERFACE, "libusb_control_transfer failed with error: {}", transfer); // this split is needed so that we don't avoid claiming the interface when // detaching the kernel driver is successful @@ -324,7 +326,7 @@ static bool CheckDeviceAccess(libusb_device* device) ret = libusb_claim_interface(s_handle, 0); if (ret != 0) { - ERROR_LOG_FMT(SERIALINTERFACE, "libusb_claim_interface failed with error: {}", ret); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "libusb_claim_interface failed with error: {}", ret); libusb_close(s_handle); s_handle = nullptr; return false; @@ -410,7 +412,7 @@ static void Reset() } if (s_detect_callback != nullptr) s_detect_callback(); - NOTICE_LOG_FMT(SERIALINTERFACE, "GC Adapter detached"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter detached"); } GCPadStatus Input(int chan) @@ -436,8 +438,8 @@ GCPadStatus Input(int chan) controller_payload_copy[0] != LIBUSB_DT_HID) { // This can occur for a few frames on initialization. - ERROR_LOG_FMT(SERIALINTERFACE, "error reading payload (size: {}, type: {:02x})", payload_size, - controller_payload_copy[0]); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "error reading payload (size: {}, type: {:02x})", + payload_size, controller_payload_copy[0]); } else { @@ -446,8 +448,8 @@ GCPadStatus Input(int chan) if (type != ControllerTypes::CONTROLLER_NONE && s_controller_type[chan] == ControllerTypes::CONTROLLER_NONE) { - NOTICE_LOG_FMT(SERIALINTERFACE, "New device connected to Port {} of Type: {:02x}", chan + 1, - controller_payload_copy[1 + (9 * chan)]); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "New device connected to Port {} of Type: {:02x}", + chan + 1, controller_payload_copy[1 + (9 * chan)]); get_origin = true; } @@ -551,7 +553,7 @@ static void ResetRumbleLockNeeded() int size = 0; libusb_interrupt_transfer(s_handle, s_endpoint_out, rumble, sizeof(rumble), &size, 16); - INFO_LOG_FMT(SERIALINTERFACE, "Rumble state reset"); + INFO_LOG_FMT(CONTROLLERINTERFACE, "Rumble state reset"); } void Output(int chan, u8 rumble_command) diff --git a/Source/Core/InputCommon/GCAdapter_Android.cpp b/Source/Core/InputCommon/GCAdapter_Android.cpp index a2b169abda72..88888b345a1d 100644 --- a/Source/Core/InputCommon/GCAdapter_Android.cpp +++ b/Source/Core/InputCommon/GCAdapter_Android.cpp @@ -64,7 +64,7 @@ static u64 s_last_init = 0; static void ScanThreadFunc() { Common::SetCurrentThreadName("GC Adapter Scanning Thread"); - NOTICE_LOG_FMT(SERIALINTERFACE, "GC Adapter scanning thread started"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter scanning thread started"); JNIEnv* env = IDCache::GetEnvForThread(); @@ -78,13 +78,13 @@ static void ScanThreadFunc() Common::SleepCurrentThread(1000); } - NOTICE_LOG_FMT(SERIALINTERFACE, "GC Adapter scanning thread stopped"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter scanning thread stopped"); } static void Write() { Common::SetCurrentThreadName("GC Adapter Write Thread"); - NOTICE_LOG_FMT(SERIALINTERFACE, "GC Adapter write thread started"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter write thread started"); JNIEnv* env = IDCache::GetEnvForThread(); jmethodID output_func = env->GetStaticMethodID(s_adapter_class, "Output", "([B)I"); @@ -108,7 +108,7 @@ static void Write() // Netplay sends invalid data which results in size = 0x00. Ignore it. if (size != write_size && size != 0x00) { - ERROR_LOG_FMT(SERIALINTERFACE, "error writing rumble (size: {})", size); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "error writing rumble (size: {})", size); Reset(); } } @@ -116,13 +116,13 @@ static void Write() Common::YieldCPU(); } - NOTICE_LOG_FMT(SERIALINTERFACE, "GC Adapter write thread stopped"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter write thread stopped"); } static void Read() { Common::SetCurrentThreadName("GC Adapter Read Thread"); - NOTICE_LOG_FMT(SERIALINTERFACE, "GC Adapter read thread started"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter read thread started"); bool first_read = true; JNIEnv* env = IDCache::GetEnvForThread(); @@ -179,7 +179,7 @@ static void Read() s_fd = 0; s_detected = false; - NOTICE_LOG_FMT(SERIALINTERFACE, "GC Adapter read thread stopped"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter read thread stopped"); } void Init() @@ -229,7 +229,7 @@ static void Reset() s_detected = false; s_fd = 0; - NOTICE_LOG_FMT(SERIALINTERFACE, "GC Adapter detached"); + NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter detached"); } void Shutdown() @@ -270,8 +270,8 @@ GCPadStatus Input(int chan) GCPadStatus pad = {}; if (payload_size != controller_payload_copy.size()) { - ERROR_LOG_FMT(SERIALINTERFACE, "error reading payload (size: {}, type: {:02x})", payload_size, - controller_payload_copy[0]); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "error reading payload (size: {}, type: {:02x})", + payload_size, controller_payload_copy[0]); Reset(); } else @@ -281,8 +281,8 @@ GCPadStatus Input(int chan) if (type != ControllerTypes::CONTROLLER_NONE && s_controller_type[chan] == ControllerTypes::CONTROLLER_NONE) { - ERROR_LOG_FMT(SERIALINTERFACE, "New device connected to Port {} of Type: {:02x}", chan + 1, - controller_payload_copy[1 + (9 * chan)]); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "New device connected to Port {} of Type: {:02x}", + chan + 1, controller_payload_copy[1 + (9 * chan)]); get_origin = true; } diff --git a/Source/Core/InputCommon/InputConfig.cpp b/Source/Core/InputCommon/InputConfig.cpp index 07a9b62daf2a..3b9fb7f809bb 100644 --- a/Source/Core/InputCommon/InputConfig.cpp +++ b/Source/Core/InputCommon/InputConfig.cpp @@ -133,7 +133,6 @@ bool InputConfig::LoadConfig(bool isGC) } #endif controller->LoadConfig(&config); - // Update refs controller->UpdateReferences(g_controller_interface); controller_names.push_back(controller->GetName()); @@ -171,7 +170,7 @@ void InputConfig::SaveConfig() inifile.Save(ini_filename); } -ControllerEmu::EmulatedController* InputConfig::GetController(int index) +ControllerEmu::EmulatedController* InputConfig::GetController(int index) const { return m_controllers.at(index).get(); } diff --git a/Source/Core/InputCommon/InputConfig.h b/Source/Core/InputCommon/InputConfig.h index 0eeeedaed8ae..a873e8250328 100644 --- a/Source/Core/InputCommon/InputConfig.h +++ b/Source/Core/InputCommon/InputConfig.h @@ -34,7 +34,7 @@ class InputConfig m_controllers.emplace_back(std::make_unique(std::forward(args)...)); } - ControllerEmu::EmulatedController* GetController(int index); + ControllerEmu::EmulatedController* GetController(int index) const; void ClearControllers(); bool ControllersNeedToBeCreated() const; bool IsControllerControlledByGamepadDevice(int index) const; diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 9eac785c5aa9..710e8c5e7566 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -776,6 +776,7 @@ void Renderer::UpdateDrawRectangle() const float win_width = static_cast(m_backbuffer_width); const float win_height = static_cast(m_backbuffer_height); + // FIXME: this breaks at very low widget sizes // Make ControllerInterface aware of the render window region actually being used // to adjust mouse cursor inputs. g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / (win_width / win_height)); diff --git a/Source/DSPTool/StubHost.cpp b/Source/DSPTool/StubHost.cpp index 6dce41694bad..19c85a642b54 100644 --- a/Source/DSPTool/StubHost.cpp +++ b/Source/DSPTool/StubHost.cpp @@ -39,10 +39,6 @@ bool Host_RendererHasFocus() { return false; } -bool Host_RendererHasFullFocus() -{ - return false; -} bool Host_RendererIsFullscreen() { return false; diff --git a/Source/UnitTests/StubHost.cpp b/Source/UnitTests/StubHost.cpp index 7a84a9ece971..4b9db8f03338 100644 --- a/Source/UnitTests/StubHost.cpp +++ b/Source/UnitTests/StubHost.cpp @@ -43,10 +43,6 @@ bool Host_RendererHasFocus() { return false; } -bool Host_RendererHasFullFocus() -{ - return false; -} bool Host_RendererIsFullscreen() { return false; From a62c4e0dde37573318d63732f29f1e3e4defa973 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Fri, 14 May 2021 13:31:45 +0300 Subject: [PATCH 54/56] Post merge fixup --- .../source/FreeSurroundDecoder.cpp | 2 +- Source/Core/AudioCommon/AudioCommon.cpp | 53 ++++++------------- Source/Core/AudioCommon/Mixer.cpp | 3 +- Source/Core/AudioCommon/Mixer.h | 6 +-- .../Core/ConfigLoaders/IsSettingSaveable.cpp | 6 +-- Source/Core/Core/HW/AudioInterface.cpp | 3 +- Source/Core/Core/HW/WiimoteEmu/Speaker.cpp | 4 +- Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp | 1 - Source/Core/Core/State.cpp | 2 +- Source/Core/DolphinLib.props | 3 ++ 10 files changed, 31 insertions(+), 52 deletions(-) diff --git a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp index d07d763eb6c1..07a456a2198b 100644 --- a/Externals/FreeSurround/source/FreeSurroundDecoder.cpp +++ b/Externals/FreeSurround/source/FreeSurroundDecoder.cpp @@ -92,7 +92,7 @@ void DPL2FSDecoder::Init(channel_setup chsetup, unsigned int blsize, memcpy(&outbuf[i], &outbuf[prev_size - C], sizeof(float) * C); } } - for (unsigned int k = 0; k < std::min(C, unsigned int(signal.size())); k++) + for (unsigned int k = 0; k < std::min(C, static_cast(signal.size())); k++) signal[k].resize(N); signal.resize(C, std::vector(N)); diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 5852868d9c04..383bec5076fa 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -60,49 +60,33 @@ static std::unique_ptr CreateSoundStreamForBackend(std::string_view void InitSoundStream() { + std::lock_guard guard(g_sound_stream_mutex); + + std::string backend = SConfig::GetInstance().sBackend; + g_sound_stream = CreateSoundStreamForBackend(backend); + g_selected_sound_stream_failed = false; + + if (!g_sound_stream) { - WARN_LOG_FMT(AUDIO, "Unknown backend {}, using {} instead.", backend, GetDefaultSoundBackend()); + WARN_LOG_FMT(AUDIO, "Unknown backend {}, using {} instead (default)", backend, + GetDefaultSoundBackend()); backend = GetDefaultSoundBackend(); - g_sound_stream = CreateSoundStreamForBackend(GetDefaultSoundBackend()); + g_sound_stream = CreateSoundStreamForBackend(backend); } if (!g_sound_stream || !g_sound_stream->Init()) { - WARN_LOG_FMT(AUDIO, "Could not initialize backend {}, using {} instead.", backend, - BACKEND_NULLSOUND); + WARN_LOG_FMT(AUDIO, "Could not initialize backend {}, using {} instead", backend, + NullSound::GetName()); g_sound_stream = std::make_unique(); - g_sound_stream->Init(); + g_sound_stream->Init(); // NullSound can't fail + g_selected_sound_stream_failed = true; } } +// This needs to be called after AudioInterface::Init where input sample rates are set void PostInitSoundStream() { - // This needs to be called after AudioInterface::Init where input sample rates are set - UpdateSoundStream(); - std::lock_guard guard(g_sound_stream_mutex); - - std::string backend = SConfig::GetInstance().sBackend; - g_sound_stream = CreateSoundStreamForBackend(backend); - g_selected_sound_stream_failed = false; - - if (!g_sound_stream) - { - WARN_LOG_FMT(AUDIO, "Unknown backend {}, using {} instead (default)", backend, - GetDefaultSoundBackend()); - backend = GetDefaultSoundBackend(); - g_sound_stream = CreateSoundStreamForBackend(backend); - } - - if (!g_sound_stream || !g_sound_stream->Init()) - { - WARN_LOG_FMT(AUDIO, "Could not initialize backend {}, using {} instead", backend, - NullSound::GetName()); - g_sound_stream = std::make_unique(); - g_sound_stream->Init(); // NullSound can't fail - g_selected_sound_stream_failed = true; - } - } - UpdateSoundStreamSettings(true); // This can fail, but we don't really care as it just won't produce any sounds, // also the user might be able to fix it up by changing their device settings @@ -111,13 +95,6 @@ void PostInitSoundStream() // and true again, so basically the backend is "restarted" with every emulation state change SetSoundStreamRunning(true, true); - //To review: are these still needed after merge with master? - // Ideally these two calls would be done in AudioInterface::Init so that we don't - // need to have a dependency on AudioInterface here, but this has to be done - // after creating g_sound_stream (above) and before starting audio dumping (below) - g_sound_stream->GetMixer()->SetDMAInputSampleRate(AudioInterface::GetAIDSampleRate()); - g_sound_stream->GetMixer()->SetStreamingInputSampleRate(AudioInterface::GetAISSampleRate()); - if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_started) StartAudioDump(); } diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index a1fbd1ec6a90..fe178f360fa2 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -45,7 +45,8 @@ Mixer::Mixer(u32 sample_rate) Mixer::~Mixer() { - INFO_LOG_FMT(AUDIO_INTERFACE, "Mixer is initialized"); + INFO_LOG_FMT(AUDIO_INTERFACE, "Mixer is uninitialized"); + Core::RemoveOnStateChangedCallback(&m_on_state_changed_handle); } void Mixer::SetPaused(bool paused) diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 7a82615f9a5f..05f51c8cbf76 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -89,8 +89,8 @@ class Mixer final { public: MixerFifo(Mixer* mixer, unsigned sample_rate, bool big_endians, bool constantly_pushed = true) - : m_mixer(mixer), m_input_sample_rate(sample_rate), - m_big_endians(big_endians), m_constantly_pushed(constantly_pushed) + : m_mixer(mixer), m_input_sample_rate(sample_rate), m_constantly_pushed(constantly_pushed), + m_big_endians(big_endians) { } void DoState(PointerWrap& p); @@ -191,7 +191,7 @@ class Mixer final u32 m_sample_rate; // Only changed by main or emulation thread when the backend is not running bool m_stretching = false; - bool m_update_surround_latency = false; + bool m_update_surround_latency = false; //To delete? AudioCommon::AudioStretcher m_stretcher; AudioCommon::SurroundDecoder m_surround_decoder; diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index 3bc100b87278..82fa831e6027 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -44,9 +44,9 @@ bool IsSettingSaveable(const Config::Location& config_location) &Config::MAIN_ALLOW_SD_WRITES.GetLocation(), &Config::MAIN_DPL2_DECODER.GetLocation(), &Config::MAIN_DPL2_QUALITY.GetLocation(), - &Config::MAIN_DPL2_BASS_REDIRECTION.location, - &Config::MAIN_AUDIO_MIXER_MIN_LATENCY.location, - &Config::MAIN_AUDIO_MIXER_MAX_LATENCY.location, + &Config::MAIN_DPL2_BASS_REDIRECTION.GetLocation(), + &Config::MAIN_AUDIO_MIXER_MIN_LATENCY.GetLocation(), + &Config::MAIN_AUDIO_MIXER_MAX_LATENCY.GetLocation(), &Config::MAIN_RAM_OVERRIDE_ENABLE.GetLocation(), &Config::MAIN_MEM1_SIZE.GetLocation(), &Config::MAIN_MEM2_SIZE.GetLocation(), diff --git a/Source/Core/Core/HW/AudioInterface.cpp b/Source/Core/Core/HW/AudioInterface.cpp index 1f9b3de53adf..d7ee6e07d501 100644 --- a/Source/Core/Core/HW/AudioInterface.cpp +++ b/Source/Core/Core/HW/AudioInterface.cpp @@ -154,9 +154,8 @@ void Init() event_type_ai = CoreTiming::RegisterEvent("AICallback", Update); - //To review: this is now duplicate? g_sound_stream->GetMixer()->SetDMAInputSampleRate(GetAIDSampleRate()); - g_sound_stream->GetMixer()->SetStreamInputSampleRate(GetAISSampleRate()); + g_sound_stream->GetMixer()->SetStreamingInputSampleRate(GetAISSampleRate()); } void Shutdown() diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp index 1a9e9fb34208..c7c59361aee4 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp @@ -91,7 +91,7 @@ void stopdamnwav() } #endif -void SpeakerLogic::SpeakerData(const u8* data, int length) +void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan) { if (reg_data.sample_rate == 0 || length == 0) return; @@ -174,7 +174,7 @@ void SpeakerLogic::SpeakerData(const u8* data, int length) // We should play the samples from the wiimote at the native volume they came with, // because you can lower their volume from the wii settings, and because they are // already extremely low quality, so any additional quality loss isn't welcome. - float speaker_pan = std::clamp(float(m_speaker_pan_setting.GetValue()) / 100, -1.f, 1.f); + speaker_pan = std::clamp(speaker_pan, -1.f, 1.f); const u32 l_volume = std::min(u32(std::min(1.f - speaker_pan, 1.f) * volume), 255u); const u32 r_volume = std::min(u32(std::min(1.f + speaker_pan, 1.f) * volume), 255u); diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index a62116779d82..c5ddfb225506 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -166,7 +166,6 @@ void Wiimote::Reset() m_i2c_bus.AddSlave(&m_speaker_logic); m_i2c_bus.AddSlave(&m_camera_logic); - //To review: is this needed now? m_speaker_logic.m_index = m_index; // Reset extension connections to NONE: diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 876e94773826..8e832d8e2896 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 131; // Last changed in PR ???? +constexpr u32 STATE_VERSION = 130; // Last changed in PR 9545 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 13c155801fd7..66411947d2fb 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -2,6 +2,7 @@ + @@ -667,12 +668,14 @@ + + From 9bfae40c40f1ad5b4ebb853266cb6fc63c8726a4 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sat, 15 May 2021 22:11:06 +0300 Subject: [PATCH 55/56] Update FixedSizeQueue --- Source/Core/Common/FixedSizeQueue.h | 59 +++++++++++++++-------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/Source/Core/Common/FixedSizeQueue.h b/Source/Core/Common/FixedSizeQueue.h index 217e68323004..386efcbb643a 100644 --- a/Source/Core/Common/FixedSizeQueue.h +++ b/Source/Core/Common/FixedSizeQueue.h @@ -6,34 +6,31 @@ #include #include -#include #include #include #include +#include "Common/Assert.h" + // STL-look-a-like interface, but name is mixed case to distinguish it clearly from the // real STL classes. // // Not fully featured. Add features as needed. +// A Circular/Ring buffer template class FixedSizeQueue { public: void clear() { - if constexpr (!std::is_trivial_v) - { - // The clear of non-trivial objects previously used "storage = {}". However, this causes GCC - // to take a very long time to compile the file/function, as well as generating huge amounts - // of debug information (~2GB object file, ~600MB of debug info). - while (count > 0) - pop(); - } + // The clear of non-trivial objects previously used "storage = {}". However, this causes GCC + // to take a very long time to compile the file/function, as well as generating huge amounts + // of debug information (~2GB object file, ~600MB of debug info). + erase(count); head = 0; tail = 0; - count = 0; } void push(T t) @@ -47,17 +44,19 @@ class FixedSizeQueue tail = (tail + 1) % N; } - // Copies over an array, loops over if num is greater than max_size + // Copies over an array, loops over if num is greater than max_size. + // Only available if T is trivial, just use push() otherwise. + template >> void push_array(const T* t, size_t num) { - size_t back_writable_num = std::min(tail_to_end(), num); - memcpy(&back(), t, sizeof(T) * back_writable_num); + const size_t back_writable_num = std::min(tail_to_end(), num); + std::memcpy(&back(), t, sizeof(T) * back_writable_num); t += back_writable_num; size_t readable_num = num - back_writable_num; while (readable_num > 0) { - size_t beginning_writable_num = std::min(readable_num, N); - memcpy(&beginning(), t, sizeof(T) * beginning_writable_num); + const size_t beginning_writable_num = std::min(readable_num, N); + std::memcpy(&beginning(), t, sizeof(T) * beginning_writable_num); t += beginning_writable_num; readable_num -= beginning_writable_num; } @@ -74,16 +73,18 @@ class FixedSizeQueue } // Takes a max num to copy (from head to tail), returns the actual copied number. - // Doesn't loop over if num is greater than our size + // Doesn't loop over if num is greater than our size. + // Only available if T is trivial, just copy by index otherwise. + template >> size_t copy_to_array(T* t, size_t num) { num = std::min(num, count); const size_t front_readable_num = std::min(head_to_end(), num); const size_t beginning_readable_num = num - front_readable_num; - memcpy(&t[0], &front(), sizeof(T) * front_readable_num); + std::memcpy(&t[0], &front(), sizeof(T) * front_readable_num); if (beginning_readable_num > 0) - memcpy(&t[front_readable_num], &beginning(), sizeof(T) * beginning_readable_num); + std::memcpy(&t[front_readable_num], &beginning(), sizeof(T) * beginning_readable_num); return num; } @@ -102,7 +103,7 @@ class FixedSizeQueue // From front void pop() { - assert(count > 0); + ASSERT(count > 0); if constexpr (!std::is_trivial_v) storage[head] = {}; @@ -120,7 +121,7 @@ class FixedSizeQueue // From front void erase(size_t num) { - assert(count >= num); + ASSERT(count >= num); if constexpr (!std::is_trivial_v) { while (num > 0) @@ -138,31 +139,33 @@ class FixedSizeQueue } } - T& operator[](size_t index) + // From head/front, not the absolute index + T& operator[](size_t index) noexcept { - assert(index < count); + ASSERT(index < count); return storage[(head + index) % N]; } - const T& operator[](size_t index) const + const T& operator[](size_t index) const noexcept { - assert(index < count); + ASSERT(index < count); return storage[(head + index) % N]; } + size_t size() const noexcept { return count; } + size_t max_size() const noexcept { return N; } + bool empty() const noexcept { return size() == 0; } + T& front() noexcept { return storage[head]; } const T& front() const noexcept { return storage[head]; } T& back() noexcept { return storage[tail]; } const T& back() const noexcept { return storage[tail]; } - // Only use if head_to_end() > size() + // Returns the first index of the internal array. Only read if head_to_end() > size() T& beginning() noexcept { return storage[0]; } const T& beginning() const noexcept { return storage[0]; } - size_t size() const noexcept { return count; } - size_t max_size() const noexcept { return N; } // Helper to know how many more items we could read before needing to loop over size_t head_to_end() const noexcept { return N - head; } // Helper to know how many more items we could write before needing to loop over size_t tail_to_end() const noexcept { return N - tail; } - bool empty() const noexcept { return size() == 0; } private: std::array storage; From 0ed77588674ef8c69e0c981874d4a77910d89c00 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Sat, 15 May 2021 22:11:52 +0300 Subject: [PATCH 56/56] Add const to vars and comments --- Source/Core/AudioCommon/Mixer.cpp | 76 ++++++++++---------- Source/Core/AudioCommon/SurroundDecoder.cpp | 9 +-- Source/Core/Core/HW/WiimoteEmu/Speaker.cpp | 2 +- Source/Core/DolphinQt/Settings/AudioPane.cpp | 5 +- 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index fe178f360fa2..9c3ad5d6cd47 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -97,7 +97,7 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) u32 indexR = m_indexR.load(); u32 indexW = m_indexW.load(); - double input_sample_rate = m_input_sample_rate.load(); + const double input_sample_rate = m_input_sample_rate.load(); // The rate can be any, unfortunately we don't apply an anti aliasing filer, which means // we might get aliasing at higher rates (unless our mixer sample rate is very high) @@ -112,7 +112,7 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) // sense. Also, this way we can target a latency of "0" s32 post_mix_samples = SamplesDifference(indexW, indexR, rate, m_fract.load()) / NC; post_mix_samples -= num_samples * rate + INTERP_SAMPLES; - double latency = std::max(post_mix_samples, 0) / input_sample_rate; + const double latency = std::max(post_mix_samples, 0) / input_sample_rate; INFO_LOG_FMT(AUDIO, "latency: {}", latency); // This isn't big enough to notice but it is enough to make a difference and recover latency @@ -124,15 +124,15 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) m_latency_catching_up_direction = 0; } - s32 lVolume = m_lVolume.load(); - s32 rVolume = m_rVolume.load(); + const s32 lVolume = m_lVolume.load(); + const s32 rVolume = m_rVolume.load(); s32 s[NC]; // Padding samples s[0] = m_last_output_samples[1]; s[1] = m_last_output_samples[1]; // Actual number of samples written (not padded nor backwards played) - u32 actual_samples_count = + const u32 actual_samples_count = CubicInterpolation(samples, num_samples, rate, indexR, indexW, s[0], s[1], lVolume, rVolume); m_last_output_samples[0] = s[0]; m_last_output_samples[1] = s[1]; @@ -163,7 +163,7 @@ u32 Mixer::MixerFifo::Mix(s16* samples, u32 num_samples, bool stretching) // I can't tell if this is better than padding (silence) when going at about 40% of the target speed static bool enable_backwards = true; //To review - s32 behind_samples = num_samples - actual_samples_count; + const s32 behind_samples = num_samples - actual_samples_count; // This might sound bad if we are constantly missing a few samples, but that should never happen, // and we couldn't predict it anyway (we should start playing backwards as soon as we can). // We can't play backwards mixers that are not constantly pushed as we don't know when the @@ -248,7 +248,7 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r -1.5f, 2.0f, 0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f }; - s8 direction = forwards ? 1 : -1; + const s8 direction = forwards ? 1 : -1; double fract = forwards ? m_fract.load() : m_backwards_fract; u32 available_samples = SamplesDifference(indexW, indexR, rate, fract); // Forwards only s16* interpolation_buffer; @@ -316,10 +316,10 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r const float x1 = x2 * x2; // x^2 const float x0 = x1 * x2; // x^3 - float y0 = coeffs[0] * x0 + coeffs[1] * x1 + coeffs[2] * x2 + coeffs[3]; - float y1 = coeffs[4] * x0 + coeffs[5] * x1 + coeffs[6] * x2 + coeffs[7]; - float y2 = coeffs[8] * x0 + coeffs[9] * x1 + coeffs[10] * x2 + coeffs[11]; - float y3 = coeffs[12] * x0 + coeffs[13] * x1 + coeffs[14] * x2 + coeffs[15]; + const float y0 = coeffs[0] * x0 + coeffs[1] * x1 + coeffs[2] * x2 + coeffs[3]; + const float y1 = coeffs[4] * x0 + coeffs[5] * x1 + coeffs[6] * x2 + coeffs[7]; + const float y2 = coeffs[8] * x0 + coeffs[9] * x1 + coeffs[10] * x2 + coeffs[11]; + const float y3 = coeffs[12] * x0 + coeffs[13] * x1 + coeffs[14] * x2 + coeffs[15]; // The first and last sample act as control points, the middle ones have more importance. // The very first and last samples might never directly be used but it shouldn't be a problem. @@ -327,14 +327,14 @@ u32 Mixer::MixerFifo::CubicInterpolation(s16* samples, u32 num_samples, double r // and trade latency with a small hit in quality, but it's not worth it. // We could have ignored the direction while playing backwards, and just read // indexR over our actually play direction, but I thought that was wrong and weird - float l_s_f = y0 * interpolation_buffer[(indexR + 1) & INDEX_MASK] + - y1 * interpolation_buffer[(indexR + 2 * direction + 1) & INDEX_MASK] + - y2 * interpolation_buffer[(indexR + 4 * direction + 1) & INDEX_MASK] + - y3 * interpolation_buffer[(indexR + 6 * direction + 1) & INDEX_MASK]; - float r_s_f = y0 * interpolation_buffer[indexR & INDEX_MASK] + - y1 * interpolation_buffer[(indexR + 2 * direction) & INDEX_MASK] + - y2 * interpolation_buffer[(indexR + 4 * direction) & INDEX_MASK] + - y3 * interpolation_buffer[(indexR + 6 * direction) & INDEX_MASK]; + const float l_s_f = y0 * interpolation_buffer[(indexR + 1) & INDEX_MASK] + + y1 * interpolation_buffer[(indexR + 2 * direction + 1) & INDEX_MASK] + + y2 * interpolation_buffer[(indexR + 4 * direction + 1) & INDEX_MASK] + + y3 * interpolation_buffer[(indexR + 6 * direction + 1) & INDEX_MASK]; + const float r_s_f = y0 * interpolation_buffer[indexR & INDEX_MASK] + + y1 * interpolation_buffer[(indexR + 2 * direction) & INDEX_MASK] + + y2 * interpolation_buffer[(indexR + 4 * direction) & INDEX_MASK] + + y3 * interpolation_buffer[(indexR + 6 * direction) & INDEX_MASK]; // Could this benefit from multiplying the volume as a float before the round? l_s = (s32(std::round(l_s_f)) * lVolume) >> 8; @@ -371,22 +371,22 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) std::memset(samples, 0, num_samples * NC * sizeof(samples[0])); return 0; } - u32 original_num_samples = num_samples; + const u32 original_num_samples = num_samples; - bool stretching = SConfig::GetInstance().m_audio_stretch; + const bool stretching = SConfig::GetInstance().m_audio_stretch; - double emulation_speed = SConfig::GetInstance().m_EmulationSpeed; - bool frame_limiter = emulation_speed > 0.0 && !Core::GetIsThrottlerTempDisabled(); + const double emulation_speed = SConfig::GetInstance().m_EmulationSpeed; + const bool frame_limiter = emulation_speed > 0.0 && !Core::GetIsThrottlerTempDisabled(); // Backend latency in seconds - double time_delta = double(num_samples) / m_sample_rate; + const double time_delta = double(num_samples) / m_sample_rate; m_backend_latency = time_delta; m_last_mix_time = Common::Timer::GetTimeUs(); - double average_actual_speed = m_dma_speed.GetCachedAverageSpeed(false, true, true); + const double average_actual_speed = m_dma_speed.GetCachedAverageSpeed(false, true, true); bool predicting = true; // Set predicting to false if we are not predicting (meaning the last samples push isn't late) - double actual_speed = m_dma_speed.GetLastSpeed(predicting, true); + const double actual_speed = m_dma_speed.GetLastSpeed(predicting, true); //INFO_LOG_FMT(AUDIO, "dma_mixer current speed: {}", average_actual_speed); double target_speed = emulation_speed; @@ -525,7 +525,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) { // As for stretching below, this won't follow the new rate but is still better // than losing samples when changing settings - u32 received_samples = m_surround_decoder.ReturnSamples(samples, num_samples); + const u32 received_samples = m_surround_decoder.ReturnSamples(samples, num_samples); num_samples -= received_samples; samples += received_samples * NC; @@ -565,7 +565,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) } } - bool scratch_buffer_equal_samples = m_scratch_buffer.data() == samples; + const bool scratch_buffer_equal_samples = m_scratch_buffer.data() == samples; // If the in and out sample rates difference is too high, available_samples might over the max m_scratch_buffer.reserve(available_samples * NC); @@ -600,7 +600,7 @@ u32 Mixer::Mix(s16* samples, u32 num_samples, bool surround) // Play out whatever we had left. Unprocessed samples will be lost. // Of course this behaves weirdly when toggling stretching every audio frame, // and it doesn't follow the new rate, but it's better than losing samples - u32 received_samples = m_stretcher.GetStretchedSamples(samples, num_samples, false); + const u32 received_samples = m_stretcher.GetStretchedSamples(samples, num_samples, false); num_samples -= received_samples; samples += received_samples * NC; @@ -646,7 +646,7 @@ u32 Mixer::MixSurround(float* samples, u32 num_samples) // - We want to spread the computation among frames as much as possible // - It simply wouldn't work well with dynamic backend latency, which is a thing. And it doesn't work well with dynamic settings, which can now be changed at runtime (like backend latency and DPLII block size). It would run out of samples in the first frame // - It could possibly never start playing because - u32 mixed_samples = Mix(m_scratch_buffer.data(), num_samples, true); + const u32 mixed_samples = Mix(m_scratch_buffer.data(), num_samples, true); //To review: if we appened silent mix samples to the left of the buffer instead than on the right, especially the first time we start the emulation or we changed audio settings. //With that though, The mixer would readjust itself by playing samples quicker or slower, which could be a pro or con depending on the case (https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/AudioCommon/SurroundDecoder.cpp). @@ -751,7 +751,7 @@ void Mixer::MixerFifo::PushSamples(const s16* samples, u32 num_samples) // to alleviate the workload on main thread // and we simply store raw data here to make fast mem copy constexpr u32 size = sizeof(s16); - int over_bytes = (num_samples * NC - (MAX_SAMPLES * NC - (indexW & INDEX_MASK))) * size; + const int over_bytes = (num_samples * NC - (MAX_SAMPLES * NC - (indexW & INDEX_MASK))) * size; if (over_bytes > 0) { @@ -795,7 +795,7 @@ void Mixer::PushDMASamples(const s16* samples, u32 num_samples) if (PrintPushedSamples) INFO_LOG_FMT(AUDIO, "dma_mixer added samples: {}, speed: {}", num_samples, m_dma_speed.GetCachedAverageSpeed()); m_dma_mixer.PushSamples(samples, num_samples); - int sample_rate = m_dma_mixer.GetRoundedInputSampleRate(); + const int sample_rate = m_dma_mixer.GetRoundedInputSampleRate(); if (m_log_dsp_audio) m_wave_writer_dsp.AddStereoSamplesBE(samples, num_samples, sample_rate); } @@ -807,13 +807,13 @@ void Mixer::PushStreamingSamples(const s16* samples, u32 num_samples) // Check whether the wii mote speaker mixers have finished pushing. We do it // from this mixer as it's the one with the higher update frequency // (168 samples per push at 32 or 48kHz). Yes, it's a bit of a hack but it works fine - double time_delta = double(num_samples) / m_dma_mixer.GetInputSampleRate(); + const double time_delta = double(num_samples) / m_dma_mixer.GetInputSampleRate(); m_wiimote_speaker_mixer[0].UpdatePush(-time_delta); m_wiimote_speaker_mixer[1].UpdatePush(-time_delta); m_wiimote_speaker_mixer[2].UpdatePush(-time_delta); m_wiimote_speaker_mixer[3].UpdatePush(-time_delta); - int sample_rate = m_streaming_mixer.GetRoundedInputSampleRate(); + const int sample_rate = m_streaming_mixer.GetRoundedInputSampleRate(); if (m_log_dtk_audio) m_wave_writer_dtk.AddStereoSamplesBE(samples, num_samples, sample_rate); } @@ -960,7 +960,7 @@ void Mixer::MixerFifo::SetVolume(u32 lVolume, u32 rVolume) u32 Mixer::MixerFifo::AvailableSamples() const { - u32 fifo_samples = NumSamples(); + const u32 fifo_samples = NumSamples(); // Interpolation always keeps some sample in the buffer, we want to ignore them if (fifo_samples <= INTERP_SAMPLES) return 0; @@ -974,14 +974,14 @@ u32 Mixer::MixerFifo::NumSamples() const u32 Mixer::MixerFifo::SamplesDifference(u32 indexW, u32 indexR) const { - double rate = (m_input_sample_rate * m_mixer->GetMixingSpeed()) / m_mixer->m_sample_rate; + const double rate = (m_input_sample_rate * m_mixer->GetMixingSpeed()) / m_mixer->m_sample_rate; return SamplesDifference(indexW, indexR, rate, m_fract.load()); } u32 Mixer::MixerFifo::SamplesDifference(u32 indexW, u32 indexR, double rate, double fract) const { // We can't have more than MAX_SAMPLES, if we do, we loop over - u32 diff = indexW - GetNextIndexR(indexR, rate, fract); - u32 normalized_diff = diff & INDEX_MASK; + const u32 diff = indexW - GetNextIndexR(indexR, rate, fract); + const u32 normalized_diff = diff & INDEX_MASK; return normalized_diff == 0u ? (diff == 0u ? 0u : (MAX_SAMPLES * NC)) : normalized_diff; } diff --git a/Source/Core/AudioCommon/SurroundDecoder.cpp b/Source/Core/AudioCommon/SurroundDecoder.cpp index 5fc7a35ad39a..e7d2afae0e56 100644 --- a/Source/Core/AudioCommon/SurroundDecoder.cpp +++ b/Source/Core/AudioCommon/SurroundDecoder.cpp @@ -58,7 +58,7 @@ static u32 DPL2QualityToBlockSize(DPL2Quality quality, u32 sample_rate) block_time_max = 20; //To delete } - double block_time_average = (block_time_min + block_time_max) * 0.5; + const double block_time_average = (block_time_min + block_time_max) * 0.5; u32 block_size = std::round(sample_rate * block_time_average / 1000.0); //block_size = MathUtil::NearestPowerOf2(block_size); @@ -67,7 +67,7 @@ static u32 DPL2QualityToBlockSize(DPL2Quality quality, u32 sample_rate) (block_size / SurroundDecoder::DECODER_GRANULARITY) * SurroundDecoder::DECODER_GRANULARITY; // Find the actual used block_time to see if it's within the accepted ranges - double block_time = block_size * 1000 / sample_rate; + const double block_time = block_size * 1000 / sample_rate; if (block_time > block_time_max || block_time < block_time_min) { block_size = std::round(sample_rate * block_time_average / 1000.0); @@ -98,7 +98,8 @@ SurroundDecoder::~SurroundDecoder() = default; void SurroundDecoder::Init(u32 sample_rate) { - u32 block_size = DPL2QualityToBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), sample_rate); + const u32 block_size = + DPL2QualityToBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY), sample_rate); // Re-init. It should keep the samples in the buffer (and filling the rest with 0) while just // updating the settings. Variables are duplicate from the decoder because they are not exposed @@ -149,7 +150,7 @@ void SurroundDecoder::PushSamples(const s16* in, u32 num_samples) { samples_to_block = m_block_size - (u32(m_float_conversion_buffer.size() / STEREO_CHANNELS)); } - u32 samples_to_push = std::min(samples_to_block, num_samples); + const u32 samples_to_push = std::min(samples_to_block, num_samples); // Convert to float (samples are from SHRT_MIN to SHRT_MAX) for (size_t i = read_samples * STEREO_CHANNELS; diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp index c7c59361aee4..fe6a93a6e765 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp @@ -96,7 +96,7 @@ void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan) if (reg_data.sample_rate == 0 || length == 0) return; - //To reivew: not sure about mute... + //To review: not sure about mute... // Even if volume is zero or WiimoteEnableSpeaker is off we process samples to maintain proper // decoder state diff --git a/Source/Core/DolphinQt/Settings/AudioPane.cpp b/Source/Core/DolphinQt/Settings/AudioPane.cpp index 98ef7e34115a..a04e3c30aec7 100644 --- a/Source/Core/DolphinQt/Settings/AudioPane.cpp +++ b/Source/Core/DolphinQt/Settings/AudioPane.cpp @@ -207,7 +207,7 @@ void AudioPane::CreateWidgets() m_emu_speed_tolerance_indicator->setMinimumSize(min_size); m_emu_speed_tolerance_label = new QLabel(tr("Emulation Speed Tolerance:")); //To review: maybe have a set of 4 or 5 options to keep it simpler (low, high, ...). - //Also add descrpition to m_emu_speed_tolerance_label explaining that it's the time + //Also add description to m_emu_speed_tolerance_label explaining that it's the time //the emulation need to be offsetted by to start using the current speed. Do the same on android m_stretching_enable->setToolTip(tr( @@ -386,8 +386,9 @@ void AudioPane::LoadSettings() void AudioPane::SaveSettings() { + //To use QSignalBlocker // Avoids multiple calls to this when we are modifying the widgets - // in a way that would trigger multiple SaveSettings() callbacks + // in a way that would trigger multiple SaveSettings() callbacks if (m_ignore_save_settings) { return;