SoundTouch resampler integration

This commit is contained in:
Vestral 2022-01-05 17:26:12 +09:00 committed by Megamouse
parent 3a804674c9
commit 107107107c
28 changed files with 576 additions and 402 deletions

4
.gitmodules vendored
View file

@ -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

View file

@ -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)

34
3rdparty/SoundTouch/CMakeLists.txt vendored Normal file
View file

@ -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
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/soundtouch/include>
$<INSTALL_INTERFACE:/soundtouch/include>)
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 ()

1
3rdparty/SoundTouch/soundtouch vendored Submodule

@ -0,0 +1 @@
Subproject commit 83cfba67b6af80bb9bfafc0b324718c4841f2991

77
3rdparty/SoundTouch/soundtouch.vcxproj vendored Normal file
View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClInclude Include="soundtouch\include\FIFOSampleBuffer.h" />
<ClInclude Include="soundtouch\include\FIFOSamplePipe.h" />
<ClInclude Include="soundtouch\include\SoundTouch.h" />
<ClInclude Include="soundtouch\include\STTypes.h" />
<ClInclude Include="soundtouch\source\SoundTouch\AAFilter.h" />
<ClInclude Include="soundtouch\source\SoundTouch\FIRFilter.h" />
<ClInclude Include="soundtouch\source\SoundTouch\InterpolateCubic.h" />
<ClInclude Include="soundtouch\source\SoundTouch\InterpolateLinear.h" />
<ClInclude Include="soundtouch\source\SoundTouch\InterpolateShannon.h" />
<ClInclude Include="soundtouch\source\SoundTouch\RateTransposer.h" />
<ClInclude Include="soundtouch\source\SoundTouch\TDStretch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="soundtouch\source\SoundTouch\AAFilter.cpp" />
<ClCompile Include="soundtouch\source\SoundTouch\FIFOSampleBuffer.cpp" />
<ClCompile Include="soundtouch\source\SoundTouch\FIRFilter.cpp" />
<ClCompile Include="soundtouch\source\SoundTouch\InterpolateCubic.cpp" />
<ClCompile Include="soundtouch\source\SoundTouch\InterpolateLinear.cpp" />
<ClCompile Include="soundtouch\source\SoundTouch\InterpolateShannon.cpp" />
<ClCompile Include="soundtouch\source\SoundTouch\RateTransposer.cpp" />
<ClCompile Include="soundtouch\source\SoundTouch\SoundTouch.cpp" />
<ClCompile Include="soundtouch\source\SoundTouch\sse_optimized.cpp" />
<ClCompile Include="soundtouch\source\SoundTouch\TDStretch.cpp" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{508c291a-3d18-49f5-b25d-f7c8db92cb21}</ProjectGuid>
<RootNamespace>soundtouch</RootNamespace>
</PropertyGroup>
<Import Project="$(SolutionDir)\buildfiles\msvc\common_default.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(SolutionDir)\buildfiles\msvc\common_default_macros.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_default.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_debug.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_release.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<SDLCheck>false</SDLCheck>
<PreprocessorDefinitions>SOUNDTOUCH_ALLOW_SSE;ST_NO_EXCEPTION_HANDLING;USE_MULTICH_ALWAYS;SOUNDTOUCH_FLOAT_SAMPLES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>./soundtouch/source/SoundTouch;./soundtouch/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="soundtouch\include\FIFOSampleBuffer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soundtouch\include\FIFOSamplePipe.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soundtouch\include\SoundTouch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soundtouch\include\STTypes.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soundtouch\source\SoundTouch\FIRFilter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soundtouch\source\SoundTouch\InterpolateCubic.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soundtouch\source\SoundTouch\InterpolateLinear.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soundtouch\source\SoundTouch\InterpolateShannon.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soundtouch\source\SoundTouch\RateTransposer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soundtouch\source\SoundTouch\TDStretch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soundtouch\source\SoundTouch\AAFilter.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="soundtouch\source\SoundTouch\InterpolateLinear.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soundtouch\source\SoundTouch\InterpolateShannon.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soundtouch\source\SoundTouch\RateTransposer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soundtouch\source\SoundTouch\SoundTouch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soundtouch\source\SoundTouch\sse_optimized.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soundtouch\source\SoundTouch\TDStretch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soundtouch\source\SoundTouch\AAFilter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soundtouch\source\SoundTouch\FIFOSampleBuffer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soundtouch\source\SoundTouch\FIRFilter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soundtouch\source\SoundTouch\InterpolateCubic.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -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<u32>(_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;
}

View file

@ -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);

View file

@ -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}

View file

@ -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<s16 *>(dst)[i] = static_cast<s16>(std::clamp(src[i] * 32768.5f, -32768.0f, 32767.0f));
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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; }
};

View file

@ -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;

View file

@ -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<u32(u32, void *)> cb)
{
std::lock_guard lock(m_cb_mutex);

View file

@ -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;

View file

@ -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; }

View file

@ -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<u32>(hr));
return 1.0f;
}
return new_ratio;
}
void XAudio2Backend::SetWriteCallback(std::function<u32(u32, void *)> cb)
{
std::lock_guard lock(m_cb_mutex);

View file

@ -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;

View file

@ -0,0 +1,53 @@
#include "stdafx.h"
#include "Emu/Audio/audio_resampler.h"
#include <algorithm>
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<u32>(ch_cnt));
resampler.setSampleRate(static_cast<u32>(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<f32* /* buffer */, u32 /* samples */> 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();
}

View file

@ -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<f32* /* buffer */, u32 /* samples */> get_samples(u32 sample_cnt);
u32 samples_available() const;
f64 get_resample_ratio();
void flush();
private:
soundtouch::SoundTouch resampler{};
};

View file

@ -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

View file

@ -8,10 +8,6 @@
#include <cmath>
#if defined(ARCH_X64)
#include "emmintrin.h"
#endif
LOG_CHANNEL(cellAudio);
vm::gvar<char, AUDIO_PORT_OFFSET * AUDIO_PORT_COUNT> 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<s64>(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<AudioChannelCnt>(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<f64>(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<u32>(_cfg.audio_channels * _cfg.audio_sampling_rate * _cfg.audio_sample_size * buffer_dur_mult));
cb_ringbuf.set_buf_size(static_cast<u32>(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<bool>(g_cfg.audio.enable_time_stretching),
.time_stretching_threshold = g_cfg.audio.time_stretching_threshold,
.convert_to_s16 = static_cast<bool>(g_cfg.audio.convert_to_s16),
.dump_to_file = static_cast<bool>(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<f32>(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<u64>((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<u64>((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<u64>((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<u64>((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<f32>(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()

View file

@ -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<AudioDumper> m_dump;
AudioDumper m_dump{};
std::unique_ptr<float[]> 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<bool> 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<u64>(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<audio_ringbuffer> ringbuffer;
void reset_ports(s32 offset = 0);
void advance(u64 timestamp, bool reset = true);
void advance(u64 timestamp);
std::tuple<u32, u32, u32, u32> count_port_buffer_tags();
template <audio_downmix downmix>
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;
};

View file

@ -227,7 +227,7 @@ struct cfg_root : cfg::node
cfg::_enum<audio_renderer> renderer{ this, "Renderer", audio_renderer::cubeb, true };
cfg::_enum<audio_provider> 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_downmix> audio_channel_downmix{ this, "Audio Channels", audio_downmix::downmix_to_stereo, true };
cfg::_int<0, 200> volume{ this, "Master Volume", 100, true };

View file

@ -40,7 +40,7 @@
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>..\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</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\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</AdditionalIncludeDirectories>
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization>
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">HAVE_VULKAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">HAVE_VULKAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -53,6 +53,7 @@
<ItemGroup>
<ClCompile Include="..\Utilities\cheat_info.cpp" />
<ClCompile Include="Emu\Audio\audio_device_listener.cpp" />
<ClCompile Include="Emu\Audio\audio_resampler.cpp" />
<ClCompile Include="Emu\Audio\FAudio\FAudioBackend.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
@ -435,6 +436,7 @@
<ClInclude Include="..\Utilities\address_range.h" />
<ClInclude Include="..\Utilities\cheat_info.h" />
<ClInclude Include="Emu\Audio\audio_device_listener.h" />
<ClInclude Include="Emu\Audio\audio_resampler.h" />
<ClInclude Include="Emu\Audio\FAudio\FAudioBackend.h">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude>
@ -790,4 +792,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View file

@ -858,7 +858,7 @@
<ClCompile Include="Emu\Io\Buzz.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\usio.cpp">
<ClCompile Include="Emu\Io\usio.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Cell\lv2\sys_crypto_engine.cpp">
@ -1024,6 +1024,9 @@
<ClCompile Include="Emu\RSX\rsx_vertex_data.cpp">
<Filter>Emu\GPU\RSX</Filter>
</ClCompile>
<ClCompile Include="Emu\Audio\audio_resampler.cpp">
<Filter>Emu\Audio</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Crypto\aes.h">
@ -2020,6 +2023,24 @@
<ClInclude Include="Emu\Io\camera_config.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Audio\audio_resampler.h">
<Filter>Emu\Audio</Filter>
</ClInclude>
<ClInclude Include="Emu\NP\np_allocator.h">
<Filter>Emu\NP</Filter>
</ClInclude>
<ClInclude Include="Emu\NP\np_cache.h">
<Filter>Emu\NP</Filter>
</ClInclude>
<ClInclude Include="Emu\NP\np_dnshook.h">
<Filter>Emu\NP</Filter>
</ClInclude>
<ClInclude Include="Emu\NP\np_event_data.h">
<Filter>Emu\NP</Filter>
</ClInclude>
<ClInclude Include="Emu\NP\np_helpers.h">
<Filter>Emu\NP</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Emu\RSX\Common\Interpreter\FragmentInterpreter.glsl">
@ -2029,4 +2050,4 @@
<Filter>Emu\GPU\RSX\Common\Interpreter</Filter>
</None>
</ItemGroup>
</Project>
</Project>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Release|x64">
@ -71,7 +71,7 @@
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<AdditionalOptions>-Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
<AssemblerListingLocation>release\</AssemblerListingLocation>
<BrowseInformation>false</BrowseInformation>
@ -88,7 +88,7 @@
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<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;Avrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
<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)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\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</AdditionalLibraryDirectories>
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
<DataExecutionPrevention>true</DataExecutionPrevention>
@ -122,7 +122,7 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<AdditionalOptions>-Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
<AssemblerListingLocation>debug\</AssemblerListingLocation>
<BrowseInformation>false</BrowseInformation>
@ -139,7 +139,7 @@
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<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;Avrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
<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)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\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</AdditionalLibraryDirectories>
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /VERBOSE %(AdditionalOptions)</AdditionalOptions>
<DataExecutionPrevention>true</DataExecutionPrevention>
@ -1517,4 +1517,4 @@
<UserProperties MocDir=".\QTGeneratedFiles\$(ConfigurationName)" Qt5Version_x0020_x64="$(DefaultQtVersion)" RccDir=".\QTGeneratedFiles" UicDir=".\QTGeneratedFiles" />
</VisualStudio>
</ProjectExtensions>
</Project>
</Project>