diff --git a/.gitmodules b/.gitmodules index fac2313a0f..aea2082923 100644 --- a/.gitmodules +++ b/.gitmodules @@ -72,3 +72,7 @@ path = 3rdparty/cubeb/cubeb url = ../../mozilla/cubeb.git ignore = dirty +[submodule "3rdparty/SoundTouch/soundtouch"] + path = 3rdparty/SoundTouch/soundtouch + url = ../../RPCS3/soundtouch.git + ignore = dirty diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 09783d3378..232616e487 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -131,6 +131,9 @@ add_subdirectory(discord-rpc) # Cubeb add_subdirectory(cubeb EXCLUDE_FROM_ALL) +# SoundTouch +add_subdirectory(SoundTouch EXCLUDE_FROM_ALL) + # libevdev set(LIBEVDEV_TARGET 3rdparty_dummy_lib) if(USE_LIBEVDEV) @@ -312,3 +315,4 @@ add_library(3rdparty::ffmpeg ALIAS 3rdparty_ffmpeg) add_library(3rdparty::glew ALIAS 3rdparty_glew) add_library(3rdparty::wolfssl ALIAS wolfssl) add_library(3rdparty::libcurl ALIAS libcurl) +add_library(3rdparty::soundtouch ALIAS soundtouch) diff --git a/3rdparty/SoundTouch/CMakeLists.txt b/3rdparty/SoundTouch/CMakeLists.txt new file mode 100644 index 0000000000..33d1e7d5e6 --- /dev/null +++ b/3rdparty/SoundTouch/CMakeLists.txt @@ -0,0 +1,34 @@ +add_library(soundtouch STATIC EXCLUDE_FROM_ALL + soundtouch/source/SoundTouch/AAFilter.cpp + soundtouch/source/SoundTouch/FIFOSampleBuffer.cpp + soundtouch/source/SoundTouch/FIRFilter.cpp + soundtouch/source/SoundTouch/InterpolateCubic.cpp + soundtouch/source/SoundTouch/InterpolateLinear.cpp + soundtouch/source/SoundTouch/InterpolateShannon.cpp + soundtouch/source/SoundTouch/RateTransposer.cpp + soundtouch/source/SoundTouch/SoundTouch.cpp + soundtouch/source/SoundTouch/sse_optimized.cpp + soundtouch/source/SoundTouch/TDStretch.cpp +) + +target_include_directories(soundtouch PRIVATE + soundtouch/source/SoundTouch + soundtouch/include) + +target_include_directories(soundtouch INTERFACE + $ + $) + +set_property(TARGET soundtouch PROPERTY FOLDER "3rdparty/") + +target_compile_definitions(soundtouch PUBLIC + ST_NO_EXCEPTION_HANDLING + USE_MULTICH_ALWAYS + SOUNDTOUCH_FLOAT_SAMPLES; +) + +if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86|X86|amd64|AMD64|em64t|EM64T)") + target_compile_definitions(soundtouch PUBLIC + SOUNDTOUCH_ALLOW_SSE + ) +endif () diff --git a/3rdparty/SoundTouch/soundtouch b/3rdparty/SoundTouch/soundtouch new file mode 160000 index 0000000000..83cfba67b6 --- /dev/null +++ b/3rdparty/SoundTouch/soundtouch @@ -0,0 +1 @@ +Subproject commit 83cfba67b6af80bb9bfafc0b324718c4841f2991 diff --git a/3rdparty/SoundTouch/soundtouch.vcxproj b/3rdparty/SoundTouch/soundtouch.vcxproj new file mode 100644 index 0000000000..77ed4bb9b2 --- /dev/null +++ b/3rdparty/SoundTouch/soundtouch.vcxproj @@ -0,0 +1,77 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {508c291a-3d18-49f5-b25d-f7c8db92cb21} + soundtouch + + + + + + StaticLibrary + false + true + Unicode + x64 + + + + + + + + + + + + + + + + + + TurnOffAllWarnings + false + SOUNDTOUCH_ALLOW_SSE;ST_NO_EXCEPTION_HANDLING;USE_MULTICH_ALWAYS;SOUNDTOUCH_FLOAT_SAMPLES;%(PreprocessorDefinitions) + ./soundtouch/source/SoundTouch;./soundtouch/include;%(AdditionalIncludeDirectories) + + + + + + diff --git a/3rdparty/SoundTouch/soundtouch.vcxproj.filters b/3rdparty/SoundTouch/soundtouch.vcxproj.filters new file mode 100644 index 0000000000..dac3e1803b --- /dev/null +++ b/3rdparty/SoundTouch/soundtouch.vcxproj.filters @@ -0,0 +1,78 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + diff --git a/Utilities/simple_ringbuf.cpp b/Utilities/simple_ringbuf.cpp index ea25b502f2..bc9950cc48 100644 --- a/Utilities/simple_ringbuf.cpp +++ b/Utilities/simple_ringbuf.cpp @@ -33,7 +33,7 @@ simple_ringbuf& simple_ringbuf::operator=(simple_ringbuf&& other) return *this; } -u32 simple_ringbuf::get_free_size() +u32 simple_ringbuf::get_free_size() const { const u64 _rw_ptr = rw_ptr; const u32 rd = static_cast(_rw_ptr); @@ -42,12 +42,12 @@ u32 simple_ringbuf::get_free_size() return wr >= rd ? buf_size - 1 - (wr - rd) : rd - wr - 1U; } -u32 simple_ringbuf::get_used_size() +u32 simple_ringbuf::get_used_size() const { return buf_size - 1 - get_free_size(); } -u32 simple_ringbuf::get_total_size() +u32 simple_ringbuf::get_total_size() const { return buf_size; } diff --git a/Utilities/simple_ringbuf.h b/Utilities/simple_ringbuf.h index 11912eed86..9fb0c6f8c0 100644 --- a/Utilities/simple_ringbuf.h +++ b/Utilities/simple_ringbuf.h @@ -25,9 +25,9 @@ public: simple_ringbuf(simple_ringbuf&& other); simple_ringbuf& operator=(simple_ringbuf&& other); - u32 get_free_size(); - u32 get_used_size(); - u32 get_total_size(); + u32 get_free_size() const; + u32 get_used_size() const; + u32 get_total_size() const; // Thread unsafe functions. void set_buf_size(u32 size); diff --git a/rpcs3.sln b/rpcs3.sln index 311df85953..67d979a987 100644 --- a/rpcs3.sln +++ b/rpcs3.sln @@ -50,6 +50,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rpcs3", "rpcs3\rpcs3.vcxpro {D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {D6973076-9317-4EF2-A0B8-B7A18AC0713E} {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} = {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} {FDA7B080-03B0-48C8-B24F-88118981422A} = {FDA7B080-03B0-48C8-B24F-88118981422A} + {508c291a-3d18-49f5-b25d-f7c8db92cb21} = {508c291a-3d18-49f5-b25d-f7c8db92cb21} {DA6F56B4-06A4-441D-AD70-AC5A7D51FADB} = {DA6F56B4-06A4-441D-AD70-AC5A7D51FADB} {FDC361C5-7734-493B-8CFB-037308B35122} = {FDC361C5-7734-493B-8CFB-037308B35122} {8F85B6CC-250F-4ACA-A617-E820A74E3E3C} = {8F85B6CC-250F-4ACA-A617-E820A74E3E3C} @@ -82,6 +83,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Cubeb", "rpcs3\Cubeb.vcxpro EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcubeb", "3rdparty\cubeb\libcubeb.vcxproj", "{FDA7B080-03B0-48C8-B24F-88118981422A}" EndProject +Project("{508c291a-3d18-49f5-b25d-f7c8db92cb21}") = "soundtouch", "3rdparty\SoundTouch\soundtouch.vcxproj", "{508c291a-3d18-49f5-b25d-f7c8db92cb21}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -168,6 +171,10 @@ Global {FDA7B080-03B0-48C8-B24F-88118981422A}.Debug|x64.Build.0 = Debug|x64 {FDA7B080-03B0-48C8-B24F-88118981422A}.Release|x64.ActiveCfg = Release|x64 {FDA7B080-03B0-48C8-B24F-88118981422A}.Release|x64.Build.0 = Release|x64 + {508c291a-3d18-49f5-b25d-f7c8db92cb21}.Debug|x64.ActiveCfg = Debug|x64 + {508c291a-3d18-49f5-b25d-f7c8db92cb21}.Debug|x64.Build.0 = Debug|x64 + {508c291a-3d18-49f5-b25d-f7c8db92cb21}.Release|x64.ActiveCfg = Release|x64 + {508c291a-3d18-49f5-b25d-f7c8db92cb21}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -194,6 +201,7 @@ Global {A37E4273-85DB-4217-B775-CE971B87D9DF} = {B0AC29FD-7B01-4B5E-9C8D-0A081E4C5668} {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} = {10FBF193-D532-4CCF-B875-4C7091A7F6C2} {FDA7B080-03B0-48C8-B24F-88118981422A} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} + {508c291a-3d18-49f5-b25d-f7c8db92cb21} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06CC7920-E085-4B81-9582-8DE8AAD42510} diff --git a/rpcs3/Emu/Audio/AudioBackend.cpp b/rpcs3/Emu/Audio/AudioBackend.cpp index d2edb2a822..88872440c4 100644 --- a/rpcs3/Emu/Audio/AudioBackend.cpp +++ b/rpcs3/Emu/Audio/AudioBackend.cpp @@ -27,24 +27,10 @@ bool AudioBackend::get_convert_to_s16() const return m_sample_size == AudioSampleSize::S16; } -bool AudioBackend::has_capability(u32 cap) const +void AudioBackend::convert_to_s16(u32 cnt, const f32* src, void* dst) { - return (cap & GetCapabilities()) == cap; -} - -void AudioBackend::dump_capabilities(std::string& out) const -{ - u32 count = 0; - const u32 capabilities = GetCapabilities(); - - if (capabilities & SET_FREQUENCY_RATIO) + for (usz i = 0; i < cnt; i++) { - fmt::append(out, "%sSET_FREQUENCY_RATIO", count > 0 ? " | " : ""); - count++; - } - - if (count == 0) - { - fmt::append(out, "NONE"); + static_cast(dst)[i] = static_cast(std::clamp(src[i] * 32768.5f, -32768.0f, 32767.0f)); } } diff --git a/rpcs3/Emu/Audio/AudioBackend.h b/rpcs3/Emu/Audio/AudioBackend.h index f71dc871a2..ad8fed2eaf 100644 --- a/rpcs3/Emu/Audio/AudioBackend.h +++ b/rpcs3/Emu/Audio/AudioBackend.h @@ -39,11 +39,6 @@ enum class AudioChannelCnt : u32 class AudioBackend { public: - enum Capabilities : u32 - { - SET_FREQUENCY_RATIO = 0x1, // Implements SetFrequencyRatio - }; - AudioBackend(); virtual ~AudioBackend() = default; @@ -52,7 +47,6 @@ public: * Pure virtual methods */ virtual std::string_view GetName() const = 0; - virtual u32 GetCapabilities() const = 0; virtual void Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) = 0; virtual void Close() = 0; @@ -84,19 +78,6 @@ public: */ virtual bool Operational() { return true; } - /* - * Virtual methods - should be implemented depending on backend capabilities - */ - - // Sets a new frequency ratio. Backend is allowed to modify the ratio value, e.g. clamping it to the allowed range - // Returns the new frequency ratio set - // Should be implemented if capabilities & SET_FREQUENCY_RATIO - virtual f32 SetFrequencyRatio(f32 /* new_ratio */) // returns the new ratio - { - fmt::throw_exception("SetFrequencyRatio() not implemented"); - return 1.0f; - } - /* * Helper methods */ @@ -108,9 +89,10 @@ public: bool get_convert_to_s16() const; - bool has_capability(u32 cap) const; - - void dump_capabilities(std::string& out) const; + /* + * Convert float buffer to s16 one. src and dst could be the same. cnt is number of buffer elements. + */ + static void convert_to_s16(u32 cnt, const f32* src, void* dst); protected: AudioSampleSize m_sample_size = AudioSampleSize::FLOAT; diff --git a/rpcs3/Emu/Audio/AudioDumper.cpp b/rpcs3/Emu/Audio/AudioDumper.cpp index 1b3a4e3cf9..63e4d23a3e 100644 --- a/rpcs3/Emu/Audio/AudioDumper.cpp +++ b/rpcs3/Emu/Audio/AudioDumper.cpp @@ -4,11 +4,22 @@ #include "Utilities/date_time.h" #include "Emu/System.h" -AudioDumper::AudioDumper(u16 ch, u32 sample_rate, u32 sample_size) - : m_header(ch, sample_rate, sample_size) +AudioDumper::AudioDumper() { - if (GetCh()) +} + +AudioDumper::~AudioDumper() +{ + Close(); +} + +void AudioDumper::Open(u16 ch, u32 sample_rate, u32 sample_size) +{ + Close(); + + if (ch) { + m_header = WAVHeader(ch, sample_rate, sample_size); std::string path = fs::get_cache_dir() + "audio_"; if (const std::string id = Emu.GetTitleID(); !id.empty()) { @@ -20,12 +31,14 @@ AudioDumper::AudioDumper(u16 ch, u32 sample_rate, u32 sample_size) } } -AudioDumper::~AudioDumper() +void AudioDumper::Close() { if (GetCh()) { m_output.seek(0); m_output.write(m_header); // rewrite file header + m_output.close(); + m_header.FMT.NumChannels = 0; } } diff --git a/rpcs3/Emu/Audio/AudioDumper.h b/rpcs3/Emu/Audio/AudioDumper.h index 44ada0f4bd..79eee14ec7 100644 --- a/rpcs3/Emu/Audio/AudioDumper.h +++ b/rpcs3/Emu/Audio/AudioDumper.h @@ -67,9 +67,12 @@ class AudioDumper fs::file m_output{}; public: - AudioDumper(u16 ch, u32 sample_rate, u32 sample_size); + AudioDumper(); ~AudioDumper(); + void Open(u16 ch, u32 sample_rate, u32 sample_size); + void Close(); + void WriteData(const void* buffer, u32 size); u16 GetCh() const { return m_header.FMT.NumChannels; } }; diff --git a/rpcs3/Emu/Audio/Cubeb/CubebBackend.h b/rpcs3/Emu/Audio/Cubeb/CubebBackend.h index 30a25a0492..c1be026ef2 100644 --- a/rpcs3/Emu/Audio/Cubeb/CubebBackend.h +++ b/rpcs3/Emu/Audio/Cubeb/CubebBackend.h @@ -18,9 +18,6 @@ public: std::string_view GetName() const override { return "Cubeb"sv; } - static const u32 capabilities = 0; - u32 GetCapabilities() const override { return capabilities; } - bool Initialized() override; bool Operational() override; diff --git a/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp b/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp index 9c47a7a162..5e07400bbe 100644 --- a/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp +++ b/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp @@ -189,26 +189,6 @@ bool FAudioBackend::IsPlaying() return m_playing; } -f32 FAudioBackend::SetFrequencyRatio(f32 new_ratio) -{ - new_ratio = std::clamp(new_ratio, FAUDIO_MIN_FREQ_RATIO, FAUDIO_DEFAULT_FREQ_RATIO); - - if (m_source_voice == nullptr) - { - FAudio_.error("SetFrequencyRatio() called uninitialized"); - return 1.0f; - } - - const u32 res = FAudioSourceVoice_SetFrequencyRatio(m_source_voice, new_ratio, FAUDIO_COMMIT_NOW); - if (res) - { - FAudio_.error("FAudioSourceVoice_SetFrequencyRatio() failed(0x%08x)", res); - return 1.0f; - } - - return new_ratio; -} - void FAudioBackend::SetWriteCallback(std::function cb) { std::lock_guard lock(m_cb_mutex); diff --git a/rpcs3/Emu/Audio/FAudio/FAudioBackend.h b/rpcs3/Emu/Audio/FAudio/FAudioBackend.h index f867e17961..07c5e8b205 100644 --- a/rpcs3/Emu/Audio/FAudio/FAudioBackend.h +++ b/rpcs3/Emu/Audio/FAudio/FAudioBackend.h @@ -21,9 +21,6 @@ public: std::string_view GetName() const override { return "FAudio"sv; } - static const u32 capabilities = SET_FREQUENCY_RATIO; - u32 GetCapabilities() const override { return capabilities; } - bool Initialized() override; bool Operational() override; @@ -37,8 +34,6 @@ public: void Pause() override; bool IsPlaying() override; - f32 SetFrequencyRatio(f32 new_ratio) override; - private: static constexpr u32 INTERNAL_BUF_SIZE_MS = 25; diff --git a/rpcs3/Emu/Audio/Null/NullAudioBackend.h b/rpcs3/Emu/Audio/Null/NullAudioBackend.h index 97d3f4e963..148ee1d8b5 100644 --- a/rpcs3/Emu/Audio/Null/NullAudioBackend.h +++ b/rpcs3/Emu/Audio/Null/NullAudioBackend.h @@ -10,9 +10,6 @@ public: std::string_view GetName() const override { return "Null"sv; } - static const u32 capabilities = 0; - u32 GetCapabilities() const override { return capabilities; } - void Open(AudioFreq /* freq */, AudioSampleSize /* sample_size */, AudioChannelCnt /* ch_cnt */) override { m_playing = false; } void Close() override { m_playing = false; } diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp index e4fc5872ce..4e0f3e54a4 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp @@ -210,26 +210,6 @@ bool XAudio2Backend::IsPlaying() return m_playing; } -f32 XAudio2Backend::SetFrequencyRatio(f32 new_ratio) -{ - if (m_source_voice == nullptr) - { - XAudio.error("SetFrequencyRatio() called uninitialized"); - return 1.0f; - } - - new_ratio = std::clamp(new_ratio, XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_DEFAULT_FREQ_RATIO); - - const HRESULT hr = m_source_voice->SetFrequencyRatio(new_ratio); - if (FAILED(hr)) - { - XAudio.error("SetFrequencyRatio() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); - return 1.0f; - } - - return new_ratio; -} - void XAudio2Backend::SetWriteCallback(std::function cb) { std::lock_guard lock(m_cb_mutex); diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h index f847c7692e..acd916dac2 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h @@ -23,9 +23,6 @@ public: std::string_view GetName() const override { return "XAudio2"sv; } - static const u32 capabilities = SET_FREQUENCY_RATIO; - u32 GetCapabilities() const override { return capabilities; } - bool Initialized() override; bool Operational() override; @@ -39,8 +36,6 @@ public: void Pause() override; bool IsPlaying() override; - f32 SetFrequencyRatio(f32 new_ratio) override; - private: static constexpr u32 INTERNAL_BUF_SIZE_MS = 25; diff --git a/rpcs3/Emu/Audio/audio_resampler.cpp b/rpcs3/Emu/Audio/audio_resampler.cpp new file mode 100644 index 0000000000..c2d318db40 --- /dev/null +++ b/rpcs3/Emu/Audio/audio_resampler.cpp @@ -0,0 +1,53 @@ +#include "stdafx.h" +#include "Emu/Audio/audio_resampler.h" +#include + +audio_resampler::audio_resampler() +{ + resampler.setSetting(SETTING_SEQUENCE_MS, 20); // Resampler frame size (reduce latency at cost of slight sound quality degradation) + resampler.setSetting(SETTING_USE_QUICKSEEK, 1); // Use fast quick seeking algorithm (substantally reduces computation time) +} + +audio_resampler::~audio_resampler() +{ +} + +void audio_resampler::set_params(AudioChannelCnt ch_cnt, AudioFreq freq) +{ + flush(); + resampler.setChannels(static_cast(ch_cnt)); + resampler.setSampleRate(static_cast(freq)); +} + +f64 audio_resampler::set_tempo(f64 new_tempo) +{ + new_tempo = std::clamp(new_tempo, RESAMPLER_MIN_FREQ_VAL, RESAMPLER_MAX_FREQ_VAL); + resampler.setTempo(new_tempo); + return new_tempo; +} + +void audio_resampler::put_samples(const f32* buf, u32 sample_cnt) +{ + resampler.putSamples(buf, sample_cnt); +} + +std::pair audio_resampler::get_samples(u32 sample_cnt) +{ + f32 *const buf = resampler.bufBegin(); + return std::make_pair(buf, resampler.receiveSamples(sample_cnt)); +} + +u32 audio_resampler::samples_available() const +{ + return resampler.numSamples(); +} + +f64 audio_resampler::get_resample_ratio() +{ + return resampler.getInputOutputSampleRatio(); +} + +void audio_resampler::flush() +{ + resampler.clear(); +} diff --git a/rpcs3/Emu/Audio/audio_resampler.h b/rpcs3/Emu/Audio/audio_resampler.h new file mode 100644 index 0000000000..22889cb11c --- /dev/null +++ b/rpcs3/Emu/Audio/audio_resampler.h @@ -0,0 +1,37 @@ +#pragma once + +#include "util/types.hpp" +#include "Emu/Audio/AudioBackend.h" + +#ifndef _MSC_VER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-override" +#endif +#include "SoundTouch.h" +#ifndef _MSC_VER +#pragma GCC diagnostic pop +#endif + +constexpr f64 RESAMPLER_MAX_FREQ_VAL = 1.0; +constexpr f64 RESAMPLER_MIN_FREQ_VAL = 0.1; + +class audio_resampler +{ +public: + audio_resampler(); + ~audio_resampler(); + + void set_params(AudioChannelCnt ch_cnt, AudioFreq freq); + f64 set_tempo(f64 new_tempo); + + void put_samples(const f32* buf, u32 sample_cnt); + std::pair get_samples(u32 sample_cnt); + + u32 samples_available() const; + f64 get_resample_ratio(); + + void flush(); + +private: + soundtouch::SoundTouch resampler{}; +}; diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 48de8e321d..8251d6efb7 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -114,6 +114,7 @@ target_sources(rpcs3_emu PRIVATE # Audio target_sources(rpcs3_emu PRIVATE Audio/audio_device_listener.cpp + Audio/audio_resampler.cpp Audio/AudioDumper.cpp Audio/AudioBackend.cpp Audio/Cubeb/CubebBackend.cpp @@ -144,6 +145,10 @@ target_link_libraries(rpcs3_emu PUBLIC 3rdparty::cubeb) +target_link_libraries(rpcs3_emu + PUBLIC + 3rdparty::soundtouch) + # Cell target_sources(rpcs3_emu PRIVATE Cell/MFC.cpp diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.cpp b/rpcs3/Emu/Cell/Modules/cellAudio.cpp index 08692d2a33..6d9b680564 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAudio.cpp @@ -8,10 +8,6 @@ #include -#if defined(ARCH_X64) -#include "emmintrin.h" -#endif - LOG_CHANNEL(cellAudio); vm::gvar g_audio_buffer; @@ -60,11 +56,7 @@ void cell_audio_config::reset(bool backend_changed) backend = Emu.GetCallbacks().get_audio(); } - { - std::string str; - backend->dump_capabilities(str); - cellAudio.notice("cellAudio initializing. Backend: %s, Capabilities: %s", backend->GetName(), str.c_str()); - } + cellAudio.notice("cellAudio initializing. Backend: %s", backend->GetName()); const AudioFreq freq = AudioFreq::FREQ_48K; const AudioSampleSize sample_size = raw.convert_to_s16 ? AudioSampleSize::S16 : AudioSampleSize::FLOAT; @@ -85,14 +77,14 @@ void cell_audio_config::reset(bool backend_changed) audio_channels = backend->get_channels(); audio_sampling_rate = backend->get_sampling_rate(); - audio_block_period = AUDIO_BUFFER_SAMPLES * 1000000 / audio_sampling_rate; + audio_block_period = AUDIO_BUFFER_SAMPLES * 1'000'000 / audio_sampling_rate; audio_sample_size = backend->get_sample_size(); - audio_min_buffer_duration = backend->GetCallbackFrameLen(); + audio_min_buffer_duration = backend->GetCallbackFrameLen() + u32{AUDIO_BUFFER_SAMPLES} * 2.0 / audio_sampling_rate; // Add 2 blocks to allow jitter compensation audio_buffer_length = AUDIO_BUFFER_SAMPLES * audio_channels; audio_buffer_size = audio_buffer_length * audio_sample_size; - desired_buffer_duration = raw.desired_buffer_duration * 1000llu; + desired_buffer_duration = std::max(static_cast(audio_min_buffer_duration * 1000), raw.desired_buffer_duration) * 1000llu; buffering_enabled = raw.buffering_enabled && raw.renderer != audio_renderer::null; minimum_block_period = audio_block_period / 2; @@ -106,18 +98,18 @@ void cell_audio_config::reset(bool backend_changed) const bool raw_time_stretching_enabled = buffering_enabled && raw.enable_time_stretching && (raw.time_stretching_threshold > 0); - time_stretching_enabled = raw_time_stretching_enabled && backend->has_capability(AudioBackend::SET_FREQUENCY_RATIO); - + time_stretching_enabled = raw_time_stretching_enabled; time_stretching_threshold = raw.time_stretching_threshold / 100.0f; // Warn if audio backend does not support all requested features if (raw.buffering_enabled && !buffering_enabled) { cellAudio.error("Audio backend %s does not support buffering, this option will be ignored.", backend->GetName()); - } - if (raw_time_stretching_enabled && !time_stretching_enabled) - { - cellAudio.error("Audio backend %s does not support time stretching, this option will be ignored.", backend->GetName()); + + if (raw.enable_time_stretching) + { + cellAudio.error("Audio backend %s does not support time stretching, this option will be ignored.", backend->GetName()); + } } } @@ -125,7 +117,6 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg) : backend(_cfg.backend) , cfg(_cfg) , buf_sz(AUDIO_BUFFER_SAMPLES * _cfg.audio_channels) - , emu_paused(Emu.IsPaused()) { // Initialize buffers if (cfg.num_allocated_buffers > MAX_AUDIO_BUFFERS) @@ -139,23 +130,26 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg) } // Init audio dumper if enabled - if (g_cfg.audio.dump_to_file) + if (cfg.raw.dump_to_file) { - m_dump.reset(new AudioDumper(cfg.audio_channels, cfg.audio_sampling_rate, cfg.audio_sample_size)); + m_dump.Open(cfg.audio_channels, cfg.audio_sampling_rate, cfg.audio_sample_size); } + // Configure resampler + resampler.set_params(static_cast(cfg.audio_channels), AudioFreq::FREQ_48K); + resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); + const f64 buffer_dur_mult = [&]() { - const f64 min_buf_dur = _cfg.audio_min_buffer_duration + 0.01; // Add 10ms to allow jitter compensation - if (cfg.raw.buffering_enabled) + if (cfg.buffering_enabled) { - return std::max(min_buf_dur, cfg.raw.desired_buffer_duration / 1000.0 * 2); // Allocate 2x buffer to keep buffering algorithm happy + return cfg.desired_buffer_duration / 1'000'000.0 + 0.02; // Add 20ms to buffer to keep buffering algorithm happy } - return min_buf_dur; + return cfg.audio_min_buffer_duration; }(); - cb_ringbuf.set_buf_size(static_cast(_cfg.audio_channels * _cfg.audio_sampling_rate * _cfg.audio_sample_size * buffer_dur_mult)); + cb_ringbuf.set_buf_size(static_cast(cfg.audio_channels * cfg.audio_sampling_rate * cfg.audio_sample_size * buffer_dur_mult)); backend->SetWriteCallback(std::bind(&audio_ringbuffer::backend_write_callback, this, std::placeholders::_1, std::placeholders::_2)); } @@ -171,19 +165,18 @@ audio_ringbuffer::~audio_ringbuffer() f32 audio_ringbuffer::set_frequency_ratio(f32 new_ratio) { - if (!has_capability(AudioBackend::SET_FREQUENCY_RATIO)) - { - ensure(new_ratio == 1.0f); - frequency_ratio = 1.0f; - } - else - { - frequency_ratio = backend->SetFrequencyRatio(new_ratio); - //cellAudio.trace("set_frequency_ratio(%1.2f) -> %1.2f", new_ratio, frequency_ratio); - } + frequency_ratio = resampler.set_tempo(new_ratio); + return frequency_ratio; } +float* audio_ringbuffer::get_buffer(u32 num) const +{ + AUDIT(num < cfg.num_allocated_buffers); + AUDIT(buffer[num]); + return buffer[num].get(); +} + u32 audio_ringbuffer::backend_write_callback(u32 size, void *buf) { if (!backend_active.observe()) backend_active = true; @@ -196,50 +189,96 @@ u64 audio_ringbuffer::get_timestamp() return get_system_time(); } -void audio_ringbuffer::enqueue(const float* in_buffer) +float* audio_ringbuffer::get_current_buffer() const +{ + return get_buffer(cur_pos); +} + +u64 audio_ringbuffer::get_enqueued_samples() const +{ + AUDIT(cfg.buffering_enabled); + const u64 ringbuf_samples = cb_ringbuf.get_used_size() / (cfg.audio_sample_size * cfg.audio_channels); + + if (cfg.time_stretching_enabled) + { + return ringbuf_samples + resampler.samples_available(); + } + + return ringbuf_samples; +} + +u64 audio_ringbuffer::get_enqueued_playtime() const +{ + AUDIT(cfg.buffering_enabled); + + return get_enqueued_samples() * 1'000'000 / cfg.audio_sampling_rate; +} + +void audio_ringbuffer::enqueue(bool enqueue_silence, bool force) { AUDIT(cur_pos < cfg.num_allocated_buffers); // Prepare buffer - const void* buf = in_buffer; + static float silence_buffer[u32{AUDIO_MAX_CHANNELS_COUNT} * u32{AUDIO_BUFFER_SAMPLES}]{}; + float* buf = silence_buffer; - if (buf == nullptr) + if (!enqueue_silence) { buf = buffer[cur_pos].get(); cur_pos = (cur_pos + 1) % cfg.num_allocated_buffers; } // Dump audio if enabled - if (m_dump) - { - m_dump->WriteData(buf, cfg.audio_buffer_size); - } + m_dump.WriteData(buf, cfg.audio_buffer_size); - enqueued_samples += AUDIO_BUFFER_SAMPLES; - - // Start playing audio - play(); - - if (!backend_active.observe()) + if (!backend_active.observe() && !force) { // backend is not ready yet return; } // Enqueue audio - const u32 data_size = AUDIO_BUFFER_SAMPLES * cfg.audio_sample_size * cfg.audio_channels; - - if (cb_ringbuf.get_free_size() >= data_size) + if (cfg.time_stretching_enabled) { - cb_ringbuf.push(buf, data_size); + resampler.put_samples(buf, AUDIO_BUFFER_SAMPLES); + } + else + { + // Since time stretching step is skipped, we can commit to buffer directly + commit_data(buf, AUDIO_BUFFER_SAMPLES); } } -void audio_ringbuffer::enqueue_silence(u32 buf_count) +void audio_ringbuffer::enqueue_silence(u32 buf_count, bool force) { for (u32 i = 0; i < buf_count; i++) { - enqueue(silence_buffer); + enqueue(true, force); + } +} + +void audio_ringbuffer::process_resampled_data() +{ + if (!cfg.time_stretching_enabled) return; + + const auto samples = resampler.get_samples(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * cfg.audio_channels)); + commit_data(samples.first, samples.second); +} + +void audio_ringbuffer::commit_data(f32* buf, u32 sample_cnt) +{ + sample_cnt *= cfg.audio_channels; + + if (cfg.backend->get_convert_to_s16()) + { + AudioBackend::convert_to_s16(sample_cnt, buf, buf); + } + + sample_cnt *= cfg.audio_sample_size; + + if (cb_ringbuf.get_free_size() >= sample_cnt) + { + cb_ringbuf.push(buf, sample_cnt); } } @@ -250,82 +289,44 @@ void audio_ringbuffer::play() return; } - if (frequency_ratio != 1.0f) - { - set_frequency_ratio(1.0f); - } - playing = true; - - ensure(enqueued_samples > 0); - play_timestamp = get_timestamp(); backend->Play(); } void audio_ringbuffer::flush() { - //cellAudio.trace("Flushing an estimated %llu enqueued samples", enqueued_samples); - backend->Pause(); cb_ringbuf.flush(); + resampler.flush(); + backend_active = false; playing = false; - if (frequency_ratio != 1.0f) + if (frequency_ratio != RESAMPLER_MAX_FREQ_VAL) { - set_frequency_ratio(1.0f); + frequency_ratio = set_frequency_ratio(RESAMPLER_MAX_FREQ_VAL); } - - enqueued_samples = 0; } -u64 audio_ringbuffer::update() +u64 audio_ringbuffer::update(bool emu_is_paused) { // Check emulator pause state - if (Emu.IsPaused()) + if (emu_is_paused) { // Emulator paused if (playing) { flush(); } - emu_paused = true; } - else if (emu_paused) + else { // Emulator unpaused - if (enqueued_samples > 0) - { - play(); - } - emu_paused = false; + play(); } - // Prepare timestamp and playing status + // Prepare timestamp const u64 timestamp = get_timestamp(); - const bool new_playing = !emu_paused && get_backend_playing(); - - // Calculate how many audio samples have played since last time - if (cfg.buffering_enabled && (playing || new_playing)) - { - enqueued_samples = cb_ringbuf.get_used_size() / (cfg.audio_sample_size * cfg.audio_channels); - } - - // Update playing state - if (playing != new_playing) - { - if (!new_playing) - { - cellAudio.warning("Audio backend stopped unexpectedly, likely due to a buffer underrun"); - - flush(); - playing = false; - } - else - { - playing = true; - } - } // Store and return timestamp update_timestamp = timestamp; @@ -445,15 +446,14 @@ void cell_audio_thread::reset_ports(s32 offset) } } -void cell_audio_thread::advance(u64 timestamp, bool reset) +void cell_audio_thread::advance(u64 timestamp) { + ringbuffer->process_resampled_data(); + std::unique_lock lock(mutex); // update ports - if (reset) - { - reset_ports(0); - } + reset_ports(0); for (auto& port : ports) { @@ -470,9 +470,7 @@ void cell_audio_thread::advance(u64 timestamp, bool reset) if (cfg.buffering_enabled) { // Calculate rolling average of enqueued playtime - const u64 enqueued_playtime = ringbuffer->get_enqueued_playtime(/* raw */ true); - m_average_playtime = cfg.period_average_alpha * enqueued_playtime + (1.0f - cfg.period_average_alpha) * m_average_playtime; - //cellAudio.error("m_average_playtime=%4.2f, enqueued_playtime=%u", m_average_playtime, enqueued_playtime); + m_average_playtime = cfg.period_average_alpha * ringbuffer->get_enqueued_playtime() + (1.0f - cfg.period_average_alpha) * m_average_playtime; } m_counter++; @@ -539,6 +537,7 @@ namespace audio .enable_time_stretching = static_cast(g_cfg.audio.enable_time_stretching), .time_stretching_threshold = g_cfg.audio.time_stretching_threshold, .convert_to_s16 = static_cast(g_cfg.audio.convert_to_s16), + .dump_to_file = static_cast(g_cfg.audio.dump_to_file), .downmix = g_cfg.audio.audio_channel_downmix, .renderer = g_cfg.audio.renderer, .provider = g_cfg.audio.provider @@ -564,7 +563,8 @@ namespace audio raw.enable_time_stretching != new_raw.enable_time_stretching || raw.convert_to_s16 != new_raw.convert_to_s16 || raw.downmix != new_raw.downmix || - raw.renderer != new_raw.renderer) + raw.renderer != new_raw.renderer || + raw.dump_to_file != new_raw.dump_to_file) { g_audio.cfg.raw = new_raw; g_audio.m_update_configuration = raw.renderer != new_raw.renderer ? audio_backend_update::ALL : audio_backend_update::PARAM; @@ -597,6 +597,7 @@ void cell_audio_thread::reset_counters() m_last_period_end = m_start_time; m_dynamic_period = 0; m_backend_failed = false; + m_audio_should_restart = true; } cell_audio_thread::cell_audio_thread() @@ -658,10 +659,13 @@ void cell_audio_thread::operator()() m_backend_failed = false; } - const u64 timestamp = ringbuffer->update(); + const bool emu_paused = Emu.IsPaused(); + const u64 timestamp = ringbuffer->update(emu_paused); - if (Emu.IsPaused()) + if (emu_paused) { + m_audio_should_restart = true; + ringbuffer->flush(); thread_ctrl::wait_for(10000); continue; } @@ -670,6 +674,30 @@ void cell_audio_thread::operator()() const u64 time_since_last_period = timestamp - m_last_period_end; + // Handle audio restart + if (m_audio_should_restart) + { + // align to 5.(3)ms on global clock - some games seem to prefer this + const s64 audio_period_alignment_delta = (timestamp - m_start_time) % cfg.audio_block_period; + if (audio_period_alignment_delta > cfg.period_comparison_margin) + { + thread_ctrl::wait_for(audio_period_alignment_delta - cfg.period_comparison_margin); + } + + if (cfg.buffering_enabled) + { + // Restart algorithm + cellAudio.trace("restarting audio"); + ringbuffer->enqueue_silence(cfg.desired_full_buffers, true); + finish_port_volume_stepping(); + m_average_playtime = static_cast(ringbuffer->get_enqueued_playtime()); + untouched_expected = 0; + } + + m_audio_should_restart = false; + continue; + } + if (!cfg.buffering_enabled) { const u64 period_end = (m_counter * cfg.audio_block_period) + m_start_time; @@ -684,95 +712,72 @@ void cell_audio_thread::operator()() else { const u64 enqueued_samples = ringbuffer->get_enqueued_samples(); - f32 frequency_ratio = ringbuffer->get_frequency_ratio(); - u64 enqueued_playtime = ringbuffer->get_enqueued_playtime(); + const f32 frequency_ratio = ringbuffer->get_frequency_ratio(); + const u64 enqueued_playtime = ringbuffer->get_enqueued_playtime(); const u64 enqueued_buffers = enqueued_samples / AUDIO_BUFFER_SAMPLES; - const bool playing = ringbuffer->is_playing(); - const auto tag_info = count_port_buffer_tags(); const u32 active_ports = std::get<0>(tag_info); const u32 in_progress = std::get<1>(tag_info); const u32 untouched = std::get<2>(tag_info); const u32 incomplete = std::get<3>(tag_info); - // Wait for a dynamic period - try to maintain an average as close as possible to 5.(3)ms - if (!playing) + // Ratio between the rolling average of the audio period, and the desired audio period + const f32 average_playtime_ratio = m_average_playtime / cfg.audio_buffer_length; + + // Use the above average ratio to decide how much buffer we should be aiming for + f32 desired_duration_adjusted = cfg.desired_buffer_duration + (cfg.audio_block_period / 2.0f); + if (average_playtime_ratio < 1.0f) { - // When the buffer is empty, always use the correct block period - m_dynamic_period = cfg.audio_block_period; + desired_duration_adjusted /= std::max(average_playtime_ratio, 0.25f); } - else + + if (cfg.time_stretching_enabled) { - // Ratio between the rolling average of the audio period, and the desired audio period - const f32 average_playtime_ratio = m_average_playtime / cfg.audio_buffer_length; - - // Use the above average ratio to decide how much buffer we should be aiming for - f32 desired_duration_adjusted = cfg.desired_buffer_duration + (cfg.audio_block_period / 2.0f); - if (average_playtime_ratio < 1.0f) - { - desired_duration_adjusted /= std::max(average_playtime_ratio, 0.25f); - } - - if (cfg.time_stretching_enabled) - { - // Calculate what the playtime is without a frequency ratio - const u64 raw_enqueued_playtime = ringbuffer->get_enqueued_playtime(/* raw= */ true); - - // 1.0 means exactly as desired - // <1.0 means not as full as desired - // >1.0 means more full than desired - const f32 desired_duration_rate = raw_enqueued_playtime / desired_duration_adjusted; - - // update frequency ratio if necessary - f32 new_ratio = frequency_ratio; - if (desired_duration_rate < cfg.time_stretching_threshold) - { - const f32 normalized_desired_duration_rate = desired_duration_rate / cfg.time_stretching_threshold; - const f32 request_ratio = normalized_desired_duration_rate * cfg.time_stretching_scale; - AUDIT(request_ratio <= 1.0f); - - // change frequency ratio in steps - if (std::abs(frequency_ratio - request_ratio) > cfg.time_stretching_step) - { - new_ratio = ringbuffer->set_frequency_ratio(request_ratio); - } - } - else if (frequency_ratio != 1.0f) - { - new_ratio = ringbuffer->set_frequency_ratio(1.0f); - } - - if (new_ratio != frequency_ratio) - { - // ratio changed, calculate new dynamic period - frequency_ratio = new_ratio; - enqueued_playtime = ringbuffer->get_enqueued_playtime(); - m_dynamic_period = 0; - } - } - - // 1.0 means exactly as desired // <1.0 means not as full as desired // >1.0 means more full than desired const f32 desired_duration_rate = enqueued_playtime / desired_duration_adjusted; - if (desired_duration_rate >= 1.0f) + // update frequency ratio if necessary + if (desired_duration_rate < cfg.time_stretching_threshold) { - // more full than desired - const f32 multiplier = 1.0f / desired_duration_rate; - m_dynamic_period = cfg.maximum_block_period - static_cast((cfg.maximum_block_period - cfg.audio_block_period) * multiplier); + const f32 normalized_desired_duration_rate = desired_duration_rate / cfg.time_stretching_threshold; + const f32 request_ratio = normalized_desired_duration_rate * cfg.time_stretching_scale; + AUDIT(request_ratio <= RESAMPLER_MAX_FREQ_VAL); + + // change frequency ratio in steps + const f32 req_time_stretching_step = (request_ratio + frequency_ratio) / 2.0f; + if (req_time_stretching_step > cfg.time_stretching_step) + { + ringbuffer->set_frequency_ratio(req_time_stretching_step); + } } - else + else if (frequency_ratio != RESAMPLER_MAX_FREQ_VAL) { - // not as full as desired - const f32 multiplier = desired_duration_rate * desired_duration_rate; // quite aggressive, but helps more times than it hurts - m_dynamic_period = cfg.minimum_block_period + static_cast((cfg.audio_block_period - cfg.minimum_block_period) * multiplier); + ringbuffer->set_frequency_ratio(RESAMPLER_MAX_FREQ_VAL); } } - s64 time_left = m_dynamic_period - time_since_last_period; + // 1.0 means exactly as desired + // <1.0 means not as full as desired + // >1.0 means more full than desired + const f32 desired_duration_rate = enqueued_playtime / desired_duration_adjusted; + + if (desired_duration_rate >= 1.0f) + { + // more full than desired + const f32 multiplier = 1.0f / desired_duration_rate; + m_dynamic_period = cfg.maximum_block_period - static_cast((cfg.maximum_block_period - cfg.audio_block_period) * multiplier); + } + else + { + // not as full as desired + const f32 multiplier = desired_duration_rate * desired_duration_rate; // quite aggressive, but helps more times than it hurts + m_dynamic_period = cfg.minimum_block_period + static_cast((cfg.audio_block_period - cfg.minimum_block_period) * multiplier); + } + + const s64 time_left = m_dynamic_period - time_since_last_period; if (time_left > cfg.period_comparison_margin) { thread_ctrl::wait_for(get_thread_wait_delay(time_left)); @@ -784,10 +789,7 @@ void cell_audio_thread::operator()() { // no need to mix, just enqueue silence and advance time cellAudio.trace("enqueuing silence: no active ports, enqueued_buffers=%llu", enqueued_buffers); - if (playing) - { - ringbuffer->enqueue_silence(1); - } + ringbuffer->enqueue_silence(); untouched_expected = 0; advance(timestamp); continue; @@ -797,16 +799,6 @@ void cell_audio_thread::operator()() //cellAudio.error("active=%u, in_progress=%u, untouched=%u, incomplete=%u", active_ports, in_progress, untouched, incomplete); if (untouched > untouched_expected) { - if (!playing) - { - // We ran out of buffer, probably because we waited too long - // Don't enqueue anything, just advance time - cellAudio.trace("advancing time: untouched=%u/%u (expected=%u), enqueued_buffers=0", untouched, active_ports, untouched_expected); - untouched_expected = untouched; - advance(timestamp); - continue; - } - // Games may sometimes "skip" audio periods entirely if they're falling behind (a sort of "frameskip" for audio) // As such, if the game doesn't touch buffers for too long we advance time hoping the game recovers if ( @@ -831,10 +823,7 @@ void cell_audio_thread::operator()() { // There's no audio in the buffers, simply advance time cellAudio.trace("enqueuing silence: untouched=%u/%u (expected=%u), enqueued_buffers=%llu", untouched, active_ports, untouched_expected, enqueued_buffers); - if (playing) - { - ringbuffer->enqueue_silence(1); - } + ringbuffer->enqueue_silence(); untouched_expected = untouched; advance(timestamp); continue; @@ -858,25 +847,6 @@ void cell_audio_thread::operator()() { cellAudio.trace("enqueueing: untouched=%u/%u (expected=%u), incomplete=%u/%u enqueued_buffers=%llu", untouched, active_ports, untouched_expected, incomplete, active_ports, enqueued_buffers); } - - // Handle audio restart - if (!playing) - { - // We are not playing (likely buffer underrun) - // align to 5.(3)ms on global clock - some games seem to prefer this - const s64 audio_period_alignment_delta = (timestamp - m_start_time) % cfg.audio_block_period; - if (audio_period_alignment_delta > cfg.period_comparison_margin) - { - thread_ctrl::wait_for(audio_period_alignment_delta - cfg.period_comparison_margin); - } - - // Flush, add silence, restart algorithm - cellAudio.trace("play/resume audio: received first audio buffer"); - ringbuffer->flush(); - ringbuffer->enqueue_silence(cfg.desired_full_buffers); - finish_port_volume_stepping(); - m_average_playtime = static_cast(ringbuffer->get_enqueued_playtime()); - } } // Mix @@ -1112,27 +1082,6 @@ void cell_audio_thread::mix(float *out_buffer, s32 offset) { std::memset(out_buffer, 0, out_buffer_sz * sizeof(float)); } - else if (cfg.backend->get_convert_to_s16()) - { - // convert the data from float to s16 with clipping: - // 2x MULPS - // 2x MAXPS (optional) - // 2x MINPS (optional) - // 2x CVTPS2DQ (converts float to s32) - // PACKSSDW (converts s32 to s16 with signed saturation) - -#if defined(ARCH_X64) - for (usz i = 0; i < out_buffer_sz; i += 8) - { - const auto scale = _mm_set1_ps(0x8000); - _mm_store_ps(out_buffer + i / 2, _mm_castsi128_ps(_mm_packs_epi32( - _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer + i), scale)), - _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer + i + 4), scale))))); - } -#else - fmt::throw_exception("Not supported"); -#endif - } } void cell_audio_thread::finish_port_volume_stepping() diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.h b/rpcs3/Emu/Cell/Modules/cellAudio.h index 82606d20d2..98b8e6e796 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.h +++ b/rpcs3/Emu/Cell/Modules/cellAudio.h @@ -6,6 +6,7 @@ #include "Emu/Memory/vm.h" #include "Emu/Audio/AudioBackend.h" #include "Emu/Audio/AudioDumper.h" +#include "Emu/Audio/audio_resampler.h" #include "Emu/system_config_types.h" struct lv2_event_queue; @@ -204,6 +205,7 @@ struct cell_audio_config bool enable_time_stretching = false; s64 time_stretching_threshold = 0; bool convert_to_s16 = false; + bool dump_to_file = false; audio_downmix downmix = audio_downmix::downmix_to_stereo; audio_renderer renderer = audio_renderer::null; audio_provider provider = audio_provider::none; @@ -236,9 +238,9 @@ struct cell_audio_config u32 desired_full_buffers = 0; u32 num_allocated_buffers = 0; // number of ringbuffer buffers - const f32 period_average_alpha = 0.02f; // alpha factor for the m_average_period rolling average + static constexpr f32 period_average_alpha = 0.02f; // alpha factor for the m_average_period rolling average - const s64 period_comparison_margin = 250; // when comparing the current period time with the desired period, if it is below this number of usecs we do not wait any longer + static constexpr s64 period_comparison_margin = 250; // when comparing the current period time with the desired period, if it is below this number of usecs we do not wait any longer u64 fully_untouched_timeout = 0; // timeout if the game has not touched any audio buffer yet u64 partially_untouched_timeout = 0; // timeout if the game has not touched all audio buffers yet @@ -251,8 +253,8 @@ struct cell_audio_config bool time_stretching_enabled = false; f32 time_stretching_threshold = 0.0f; // we only apply time stretching below this buffer fill rate (adjusted for average period) - const f32 time_stretching_step = 0.1f; // will only reduce/increase the frequency ratio in steps of at least this value - const f32 time_stretching_scale = 0.9f; + static constexpr f32 time_stretching_step = 0.1f; // will only reduce/increase the frequency ratio in steps of at least this value + static constexpr f32 time_stretching_scale = 0.9f; /* * Constructor @@ -274,24 +276,22 @@ private: const u32 buf_sz; - std::unique_ptr m_dump; + AudioDumper m_dump{}; std::unique_ptr buffer[MAX_AUDIO_BUFFERS]; - const float silence_buffer[u32{AUDIO_MAX_CHANNELS_COUNT} * u32{AUDIO_BUFFER_SAMPLES}] = { 0 }; simple_ringbuf cb_ringbuf{}; + audio_resampler resampler{}; atomic_t backend_active = false; bool playing = false; - bool emu_paused = false; u64 update_timestamp = 0; u64 play_timestamp = 0; u64 last_remainder = 0; - u64 enqueued_samples = 0; - f32 frequency_ratio = 1.0f; + f32 frequency_ratio = RESAMPLER_MAX_FREQ_VAL; u32 cur_pos = 0; @@ -300,6 +300,7 @@ private: return backend->IsPlaying(); } + void commit_data(f32* buf, u32 sample_cnt); u32 backend_write_callback(u32 size, void *buf); public: @@ -307,38 +308,19 @@ public: ~audio_ringbuffer(); void play(); - void enqueue(const float* in_buffer = nullptr); void flush(); - u64 update(); - void enqueue_silence(u32 buf_count = 1); + u64 update(bool emu_is_paused); + void enqueue(bool enqueue_silence = false, bool force = false); + void enqueue_silence(u32 buf_count = 1, bool force = false); + void process_resampled_data(); f32 set_frequency_ratio(f32 new_ratio); - float* get_buffer(u32 num) const - { - AUDIT(num < cfg.num_allocated_buffers); - AUDIT(buffer[num].get() != nullptr); - return buffer[num].get(); - } - + float* get_buffer(u32 num) const; static u64 get_timestamp(); + float* get_current_buffer() const; - float* get_current_buffer() const - { - return get_buffer(cur_pos); - } - - u64 get_enqueued_samples() const - { - AUDIT(cfg.buffering_enabled); - return enqueued_samples; - } - - u64 get_enqueued_playtime(bool raw = false) const - { - AUDIT(cfg.buffering_enabled); - u64 sampling_rate = raw ? cfg.audio_sampling_rate : static_cast(cfg.audio_sampling_rate * frequency_ratio); - return enqueued_samples * 1'000'000 / sampling_rate; - } + u64 get_enqueued_samples() const; + u64 get_enqueued_playtime() const; bool is_playing() const { @@ -350,11 +332,6 @@ public: return frequency_ratio; } - u32 has_capability(u32 cap) const - { - return backend->has_capability(cap); - } - bool get_operational_status() const { return backend->Operational(); @@ -373,7 +350,7 @@ private: std::unique_ptr ringbuffer; void reset_ports(s32 offset = 0); - void advance(u64 timestamp, bool reset = true); + void advance(u64 timestamp); std::tuple count_port_buffer_tags(); template void mix(float *out_buffer, s32 offset = 0); @@ -414,6 +391,7 @@ public: u64 m_dynamic_period = 0; f32 m_average_playtime = 0.0f; bool m_backend_failed = false; + bool m_audio_should_restart = false; cell_audio_thread(); @@ -421,11 +399,6 @@ public: audio_port* open_port(); - bool has_capability(u32 cap) const - { - return ringbuffer->has_capability(cap); - } - static constexpr auto thread_name = "cellAudio Thread"sv; }; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 5a8674adf9..d329bcfe1f 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -227,7 +227,7 @@ struct cfg_root : cfg::node cfg::_enum renderer{ this, "Renderer", audio_renderer::cubeb, true }; cfg::_enum provider{ this, "Audio provider", audio_provider::cell_audio, false }; - cfg::_bool dump_to_file{ this, "Dump to file" }; + cfg::_bool dump_to_file{ this, "Dump to file", false, true }; cfg::_bool convert_to_s16{ this, "Convert to 16 bit", false, true }; cfg::_enum audio_channel_downmix{ this, "Audio Channels", audio_downmix::downmix_to_stereo, true }; cfg::_int<0, 200> volume{ this, "Master Volume", 100, true }; diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index b8d7e47275..a5cc8bbc05 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -40,7 +40,7 @@ Use - ..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\zlib\zlib;..\llvm\include;..\llvm_build\include;$(VULKAN_SDK)\Include + ..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\zlib\zlib;..\llvm\include;..\llvm_build\include;$(VULKAN_SDK)\Include MaxSpeed HAVE_VULKAN;%(PreprocessorDefinitions) HAVE_VULKAN;%(PreprocessorDefinitions) @@ -53,6 +53,7 @@ + true @@ -435,6 +436,7 @@ + true @@ -790,4 +792,4 @@ - + \ No newline at end of file diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 0820031b31..1141ac48bc 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -858,7 +858,7 @@ Emu\Io - + Emu\Io @@ -1024,6 +1024,9 @@ Emu\GPU\RSX + + Emu\Audio + @@ -2020,6 +2023,24 @@ Emu\Io + + Emu\Audio + + + Emu\NP + + + Emu\NP + + + Emu\NP + + + Emu\NP + + + Emu\NP + @@ -2029,4 +2050,4 @@ Emu\GPU\RSX\Common\Interpreter - + \ No newline at end of file diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 56a9313457..707b6a3de5 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -1,4 +1,4 @@ - + @@ -71,7 +71,7 @@ - ..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\release;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) + ..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\release;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) release\ false @@ -88,7 +88,7 @@ Level3 - Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;Qt5MultimediaWidgets.lib;Qt5Svg.lib;libcubeb.lib;cubeb.lib;Avrt.lib;%(AdditionalDependencies) + Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;Qt5MultimediaWidgets.lib;Qt5Svg.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;%(AdditionalDependencies) ..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Release;..\3rdparty\glslang\build\SPIRV\Release;..\3rdparty\glslang\build\OGLCompilersDLL\Release;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Release;..\3rdparty\glslang\build\glslang\Release;..\3rdparty\SPIRV\build\source\Release;..\3rdparty\SPIRV\build\source\opt\Release;..\lib\$(CONFIGURATION)-$(PLATFORM);..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) true @@ -122,7 +122,7 @@ - ..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\debug;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) + ..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\debug;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) debug\ false @@ -139,7 +139,7 @@ $(IntDir)vc$(PlatformToolsetVersion).pdb - Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmaind.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgetsd.lib;$(QTDIR)\lib\Qt5Guid.lib;$(QTDIR)\lib\Qt5Cored.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5WinExtrasd.lib;Qt5Concurrentd.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimediad.lib;Qt5MultimediaWidgetsd.lib;Qt5Svgd.lib;libcubeb.lib;cubeb.lib;Avrt.lib;%(AdditionalDependencies) + Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmaind.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgetsd.lib;$(QTDIR)\lib\Qt5Guid.lib;$(QTDIR)\lib\Qt5Cored.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5WinExtrasd.lib;Qt5Concurrentd.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimediad.lib;Qt5MultimediaWidgetsd.lib;Qt5Svgd.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;%(AdditionalDependencies) ..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Debug;..\3rdparty\glslang\build\SPIRV\Debug;..\3rdparty\glslang\build\OGLCompilersDLL\Debug;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Debug;..\3rdparty\glslang\build\glslang\Debug;..\3rdparty\SPIRV\build\source\opt\Debug;..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;..\lib\$(CONFIGURATION)-$(PLATFORM);$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /VERBOSE %(AdditionalOptions) true @@ -1517,4 +1517,4 @@ - \ No newline at end of file +