diff --git a/.gitmodules b/.gitmodules index 4f4e8690bf9d065add1b3c301b71abcc1396d3de..e73ca99e39eaf99d0f77d2cb05e7706d361a5a1e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "opus"] path = externals/opus url = https://github.com/ogniK5377/opus.git +[submodule "soundtouch"] + path = externals/soundtouch + url = https://github.com/citra-emu/ext-soundtouch.git diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index b6eb36f20ece6f773473289e56dd55a320387974..600c45f0f2c84f8acb723778aa9b1d7d904f4aa8 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -47,6 +47,9 @@ target_include_directories(microprofile INTERFACE ./microprofile) add_library(unicorn-headers INTERFACE) target_include_directories(unicorn-headers INTERFACE ./unicorn/include) +# SoundTouch +add_subdirectory(soundtouch) + # Xbyak if (ARCHITECTURE_x86_64) # Defined before "dynarmic" above diff --git a/externals/soundtouch b/externals/soundtouch new file mode 160000 index 0000000000000000000000000000000000000000..060181eaf273180d3a7e87349895bd0cb6ccbf4a --- /dev/null +++ b/externals/soundtouch @@ -0,0 +1 @@ +Subproject commit 060181eaf273180d3a7e87349895bd0cb6ccbf4a diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 82e4850f7425a529207c6372eeaa19914cbd07a0..de5c291ceb83598af1106aefedd04e937bd8f916 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(audio_core STATIC create_target_directory_groups(audio_core) target_link_libraries(audio_core PUBLIC common core) +target_link_libraries(audio_core PRIVATE SoundTouch) if(ENABLE_CUBEB) target_link_libraries(audio_core PRIVATE cubeb) diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 5a1177d0c9f21efad9b4bf71bf1743ce9c3fe0e7..0f77fd1628d031108557864edd4fa60e9c9b93b7 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -85,6 +85,13 @@ public: } } + size_t SamplesInQueue(u32 num_channels) const { + if (!ctx) + return 0; + + return queue.size() / num_channels; + } + u32 GetNumChannels() const { return num_channels; } diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index f235d93e568af8e53bb05bf80e2c1a529821f7d8..fbb1bc225f2278a2307b33362f952d04d145d17c 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -21,6 +21,10 @@ public: private: struct NullSinkStreamImpl final : SinkStream { void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {} + + size_t SamplesInQueue(u32 /*num_channels*/) const override { + return 0; + } } null_sink_stream; }; diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h index 41b6736d882f31e05bb335354a1f9b4bc86be3d2..743a743a3681598fe4535d269abcc765c2ff51dd 100644 --- a/src/audio_core/sink_stream.h +++ b/src/audio_core/sink_stream.h @@ -25,6 +25,8 @@ public: * @param samples Samples in interleaved stereo PCM16 format. */ virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0; + + virtual std::size_t SamplesInQueue(u32 num_channels) const = 0; }; using SinkStreamPtr = std::unique_ptr<SinkStream>; diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp index dbae75d8c427dc00c31bcd3c7857b9b08019d1f3..49c6efc8595908f139e29f3951c5d531367003ce 100644 --- a/src/audio_core/stream.cpp +++ b/src/audio_core/stream.cpp @@ -90,6 +90,7 @@ void Stream::PlayNextBuffer() { queued_buffers.pop(); VolumeAdjustSamples(active_buffer->Samples()); + sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {}); diff --git a/src/core/settings.h b/src/core/settings.h index 5bf1863e6769b37b362615c732ff54eb3b3afc1a..c25f8ba709b94f4eabad58fc0da65c99d969049b 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -146,6 +146,7 @@ struct Values { // Audio std::string sink_id; + bool enable_audio_stretching; std::string audio_device_id; float volume; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 3730e85b821518ac3b3acd2d9189ab2cd3929e33..b0df154ca15cf4b19b86e59e83459541367aeb55 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -120,6 +120,9 @@ TelemetrySession::TelemetrySession() { Telemetry::AppendOSInfo(field_collection); // Log user configuration information + AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", Settings::values.sink_id); + AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching", + Settings::values.enable_audio_stretching); AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit); AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore", Settings::values.use_multi_core); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index c43e79e78bf0699ec8fa78be953964d24f4f9fba..d229225b4bb96aaf2136b2886d1ccbbf06d2f277 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -95,6 +95,8 @@ void Config::ReadValues() { qt_config->beginGroup("Audio"); Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); + Settings::values.enable_audio_stretching = + qt_config->value("enable_audio_stretching", true).toBool(); Settings::values.audio_device_id = qt_config->value("output_device", "auto").toString().toStdString(); Settings::values.volume = qt_config->value("volume", 1).toFloat(); @@ -230,6 +232,7 @@ void Config::SaveValues() { qt_config->beginGroup("Audio"); qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id)); + qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching); qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id)); qt_config->setValue("volume", Settings::values.volume); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index fbb813f6c69acc7b7aec6fc5852ded4c37d639a0..6ea59f2a35f0ddf9113b73ad168e102d423c3a3e 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -46,6 +46,8 @@ void ConfigureAudio::setConfiguration() { } ui->output_sink_combo_box->setCurrentIndex(new_sink_index); + ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); + // The device list cannot be pre-populated (nor listed) until the output sink is known. updateAudioDevices(new_sink_index); @@ -67,6 +69,7 @@ void ConfigureAudio::applyConfiguration() { Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) .toStdString(); + Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked(); Settings::values.audio_device_id = ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) .toStdString(); diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index ef67890dc8837e4be9c0c88aa35a81b35552861f..a29a0e265e921f9b6e0e6d012ae7322ae4a495fa 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -31,6 +31,16 @@ </item> </layout> </item> + <item> + <widget class="QCheckBox" name="toggle_audio_stretching"> + <property name="toolTip"> + <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string> + </property> + <property name="text"> + <string>Enable audio stretching</string> + </property> + </widget> + </item> <item> <layout class="QHBoxLayout"> <item> diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index f00b5a66b8adb0514a007a4c2768c455c386d586..991abda2e5cb168f38db2026a866f13b280b5ec0 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -108,6 +108,8 @@ void Config::ReadValues() { // Audio Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); + Settings::values.enable_audio_stretching = + sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 6ed9e7962eaa570d16330728af61e5454556aead..002a4ec152c21679c508f0a47d88c75a5935c059 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -150,6 +150,12 @@ swap_screen = # auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available) output_engine = +# Whether or not to enable the audio-stretching post-processing effect. +# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter, +# at the cost of increasing audio latency. +# 0: No, 1 (default): Yes +enable_audio_stretching = + # Which audio device to use. # auto (default): Auto-select output_device =