AudioStream: Add surround expansion via FreeSurround
This commit is contained in:
parent
5ec94a4644
commit
e8ecb09890
|
@ -3628,14 +3628,12 @@ void FullscreenUI::DrawControllerSettingsPage()
|
||||||
|
|
||||||
MenuHeading(FSUI_CSTR("Input Sources"));
|
MenuHeading(FSUI_CSTR("Input Sources"));
|
||||||
|
|
||||||
#ifdef ENABLE_SDL2
|
|
||||||
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_COG, "Enable SDL Input Source"),
|
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_COG, "Enable SDL Input Source"),
|
||||||
FSUI_CSTR("The SDL input source supports most controllers."), "InputSources", "SDL", true, true,
|
FSUI_CSTR("The SDL input source supports most controllers."), "InputSources", "SDL", true, true,
|
||||||
false);
|
false);
|
||||||
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_WIFI, "SDL DualShock 4 / DualSense Enhanced Mode"),
|
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_WIFI, "SDL DualShock 4 / DualSense Enhanced Mode"),
|
||||||
FSUI_CSTR("Provides vibration and LED control support over Bluetooth."), "InputSources",
|
FSUI_CSTR("Provides vibration and LED control support over Bluetooth."), "InputSources",
|
||||||
"SDLControllerEnhancedMode", false, bsi->GetBoolValue("InputSources", "SDL", true), false);
|
"SDLControllerEnhancedMode", false, bsi->GetBoolValue("InputSources", "SDL", true), false);
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_COG, "Enable XInput Input Source"),
|
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_COG, "Enable XInput Input Source"),
|
||||||
FSUI_CSTR("The XInput source provides support for XBox 360/XBox One/XBox Series controllers."),
|
FSUI_CSTR("The XInput source provides support for XBox 360/XBox One/XBox Series controllers."),
|
||||||
|
@ -4800,25 +4798,30 @@ void FullscreenUI::DrawAudioSettingsPage()
|
||||||
DrawEnumSetting(
|
DrawEnumSetting(
|
||||||
bsi, FSUI_CSTR("Audio Backend"),
|
bsi, FSUI_CSTR("Audio Backend"),
|
||||||
FSUI_CSTR("The audio backend determines how frames produced by the emulator are submitted to the host."), "Audio",
|
FSUI_CSTR("The audio backend determines how frames produced by the emulator are submitted to the host."), "Audio",
|
||||||
"Backend", Settings::DEFAULT_AUDIO_BACKEND, &Settings::ParseAudioBackend, &Settings::GetAudioBackendName,
|
"Backend", AudioStream::DEFAULT_BACKEND, &AudioStream::ParseBackendName, &AudioStream::GetBackendName,
|
||||||
&Settings::GetAudioBackendDisplayName, AudioBackend::Count);
|
&AudioStream::GetBackendDisplayName, AudioBackend::Count);
|
||||||
|
DrawEnumSetting(bsi, FSUI_CSTR("Expansion Mode"),
|
||||||
|
FSUI_CSTR("Determines how audio is expanded from stereo to surround for supported games."), "Audio",
|
||||||
|
"ExpansionMode", AudioStreamParameters::DEFAULT_EXPANSION_MODE, &AudioStream::ParseExpansionMode,
|
||||||
|
&AudioStream::GetExpansionModeName, &AudioStream::GetExpansionModeDisplayName,
|
||||||
|
AudioExpansionMode::Count);
|
||||||
DrawEnumSetting(bsi, FSUI_CSTR("Stretch Mode"),
|
DrawEnumSetting(bsi, FSUI_CSTR("Stretch Mode"),
|
||||||
FSUI_CSTR("Determines quality of audio when not running at 100% speed."), "Audio", "StretchMode",
|
FSUI_CSTR("Determines quality of audio when not running at 100% speed."), "Audio", "StretchMode",
|
||||||
Settings::DEFAULT_AUDIO_STRETCH_MODE, &AudioStream::ParseStretchMode,
|
AudioStreamParameters::DEFAULT_STRETCH_MODE, &AudioStream::ParseStretchMode,
|
||||||
&AudioStream::GetStretchModeName, &AudioStream::GetStretchModeDisplayName, AudioStretchMode::Count);
|
&AudioStream::GetStretchModeName, &AudioStream::GetStretchModeDisplayName, AudioStretchMode::Count);
|
||||||
DrawIntRangeSetting(bsi, FSUI_CSTR("Buffer Size"),
|
DrawIntRangeSetting(bsi, FSUI_CSTR("Buffer Size"),
|
||||||
FSUI_CSTR("Determines the amount of audio buffered before being pulled by the host API."),
|
FSUI_CSTR("Determines the amount of audio buffered before being pulled by the host API."),
|
||||||
"Audio", "BufferMS", Settings::DEFAULT_AUDIO_BUFFER_MS, 10, 500, "%d ms");
|
"Audio", "BufferMS", AudioStreamParameters::DEFAULT_BUFFER_MS, 10, 500, "%d ms");
|
||||||
|
|
||||||
const u32 output_latency =
|
const u32 output_latency =
|
||||||
GetEffectiveUIntSetting(bsi, "Audio", "OutputLatencyMS", Settings::DEFAULT_AUDIO_OUTPUT_LATENCY_MS);
|
GetEffectiveUIntSetting(bsi, "Audio", "OutputLatencyMS", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS);
|
||||||
bool output_latency_minimal = (output_latency == 0);
|
bool output_latency_minimal = (output_latency == 0);
|
||||||
if (ToggleButton(FSUI_CSTR("Minimal Output Latency"),
|
if (ToggleButton(FSUI_CSTR("Minimal Output Latency"),
|
||||||
FSUI_CSTR("When enabled, the minimum supported output latency will be used for the host API."),
|
FSUI_CSTR("When enabled, the minimum supported output latency will be used for the host API."),
|
||||||
&output_latency_minimal))
|
&output_latency_minimal))
|
||||||
{
|
{
|
||||||
bsi->SetUIntValue("Audio", "OutputLatencyMS",
|
bsi->SetUIntValue("Audio", "OutputLatencyMS",
|
||||||
output_latency_minimal ? 0 : Settings::DEFAULT_AUDIO_OUTPUT_LATENCY_MS);
|
output_latency_minimal ? 0 : AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS);
|
||||||
SetSettingsChanged(bsi);
|
SetSettingsChanged(bsi);
|
||||||
}
|
}
|
||||||
if (!output_latency_minimal)
|
if (!output_latency_minimal)
|
||||||
|
@ -4827,7 +4830,7 @@ void FullscreenUI::DrawAudioSettingsPage()
|
||||||
bsi, FSUI_CSTR("Output Latency"),
|
bsi, FSUI_CSTR("Output Latency"),
|
||||||
FSUI_CSTR("Determines how much latency there is between the audio being picked up by the host API, and "
|
FSUI_CSTR("Determines how much latency there is between the audio being picked up by the host API, and "
|
||||||
"played through speakers."),
|
"played through speakers."),
|
||||||
"Audio", "OutputLatencyMS", Settings::DEFAULT_AUDIO_OUTPUT_LATENCY_MS, 1, 500, "%d ms");
|
"Audio", "OutputLatencyMS", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS, 1, 500, "%d ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
EndMenuButtons();
|
EndMenuButtons();
|
||||||
|
@ -7109,6 +7112,7 @@ TRANSLATE_NOOP("FullscreenUI", "Depth Buffer");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Desktop Mode");
|
TRANSLATE_NOOP("FullscreenUI", "Desktop Mode");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Details");
|
TRANSLATE_NOOP("FullscreenUI", "Details");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Details unavailable for game not scanned in game list.");
|
TRANSLATE_NOOP("FullscreenUI", "Details unavailable for game not scanned in game list.");
|
||||||
|
TRANSLATE_NOOP("FullscreenUI", "Determines how audio is expanded from stereo to surround for supported games.");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Determines how large the on-screen messages and monitor are.");
|
TRANSLATE_NOOP("FullscreenUI", "Determines how large the on-screen messages and monitor are.");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Determines how much latency there is between the audio being picked up by the host API, and played through speakers.");
|
TRANSLATE_NOOP("FullscreenUI", "Determines how much latency there is between the audio being picked up by the host API, and played through speakers.");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Determines how much of the area typically not visible on a consumer TV set to crop/hide.");
|
TRANSLATE_NOOP("FullscreenUI", "Determines how much of the area typically not visible on a consumer TV set to crop/hide.");
|
||||||
|
@ -7182,6 +7186,7 @@ TRANSLATE_NOOP("FullscreenUI", "Exit And Save State");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Exit DuckStation");
|
TRANSLATE_NOOP("FullscreenUI", "Exit DuckStation");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Exit Without Saving");
|
TRANSLATE_NOOP("FullscreenUI", "Exit Without Saving");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Exits Big Picture mode, returning to the desktop interface.");
|
TRANSLATE_NOOP("FullscreenUI", "Exits Big Picture mode, returning to the desktop interface.");
|
||||||
|
TRANSLATE_NOOP("FullscreenUI", "Expansion Mode");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Failed to copy text to clipboard.");
|
TRANSLATE_NOOP("FullscreenUI", "Failed to copy text to clipboard.");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Failed to delete save state.");
|
TRANSLATE_NOOP("FullscreenUI", "Failed to delete save state.");
|
||||||
TRANSLATE_NOOP("FullscreenUI", "Failed to delete {}.");
|
TRANSLATE_NOOP("FullscreenUI", "Failed to delete {}.");
|
||||||
|
|
|
@ -380,34 +380,3 @@ void Host::ReleaseGPUDevice()
|
||||||
g_gpu_device.reset();
|
g_gpu_device.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef __ANDROID__
|
|
||||||
|
|
||||||
std::unique_ptr<AudioStream> Host::CreateAudioStream(AudioBackend backend, u32 sample_rate, u32 channels, u32 buffer_ms,
|
|
||||||
u32 latency_ms, AudioStretchMode stretch)
|
|
||||||
{
|
|
||||||
switch (backend)
|
|
||||||
{
|
|
||||||
#ifdef ENABLE_CUBEB
|
|
||||||
case AudioBackend::Cubeb:
|
|
||||||
return AudioStream::CreateCubebAudioStream(sample_rate, channels, buffer_ms, latency_ms, stretch);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ENABLE_SDL2
|
|
||||||
case AudioBackend::SDL:
|
|
||||||
return AudioStream::CreateSDLAudioStream(sample_rate, channels, buffer_ms, latency_ms, stretch);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
case AudioBackend::XAudio2:
|
|
||||||
return AudioStream::CreateXAudio2Stream(sample_rate, channels, buffer_ms, latency_ms, stretch);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case AudioBackend::Null:
|
|
||||||
return AudioStream::CreateNullStream(sample_rate, channels, buffer_ms);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
class SettingsInterface;
|
class SettingsInterface;
|
||||||
struct WindowInfo;
|
struct WindowInfo;
|
||||||
enum class AudioBackend : u8;
|
enum class AudioBackend : u8;
|
||||||
|
enum class AudioExpansionMode : u8;
|
||||||
enum class AudioStretchMode : u8;
|
enum class AudioStretchMode : u8;
|
||||||
enum class RenderAPI : u32;
|
enum class RenderAPI : u32;
|
||||||
class AudioStream;
|
class AudioStream;
|
||||||
|
@ -72,11 +73,6 @@ SettingsInterface* GetSettingsInterface();
|
||||||
/// If an input profile is being used, this will be the input layer, otherwise the layered interface.
|
/// If an input profile is being used, this will be the input layer, otherwise the layered interface.
|
||||||
SettingsInterface* GetSettingsInterfaceForBindings();
|
SettingsInterface* GetSettingsInterfaceForBindings();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend, u32 sample_rate, u32 channels, u32 buffer_ms,
|
|
||||||
u32 latency_ms, AudioStretchMode stretch);
|
|
||||||
|
|
||||||
/// Debugger feedback.
|
/// Debugger feedback.
|
||||||
void ReportDebuggerMessage(const std::string_view& message);
|
void ReportDebuggerMessage(const std::string_view& message);
|
||||||
void ReportFormattedDebuggerMessage(const char* format, ...);
|
void ReportFormattedDebuggerMessage(const char* format, ...);
|
||||||
|
|
|
@ -306,16 +306,12 @@ void Settings::Load(SettingsInterface& si)
|
||||||
cdrom_seek_speedup = si.GetIntValue("CDROM", "SeekSpeedup", 1);
|
cdrom_seek_speedup = si.GetIntValue("CDROM", "SeekSpeedup", 1);
|
||||||
|
|
||||||
audio_backend =
|
audio_backend =
|
||||||
ParseAudioBackend(si.GetStringValue("Audio", "Backend", GetAudioBackendName(DEFAULT_AUDIO_BACKEND)).c_str())
|
AudioStream::ParseBackendName(
|
||||||
.value_or(DEFAULT_AUDIO_BACKEND);
|
si.GetStringValue("Audio", "Backend", AudioStream::GetBackendName(AudioStream::DEFAULT_BACKEND)).c_str())
|
||||||
|
.value_or(AudioStream::DEFAULT_BACKEND);
|
||||||
audio_driver = si.GetStringValue("Audio", "Driver");
|
audio_driver = si.GetStringValue("Audio", "Driver");
|
||||||
audio_output_device = si.GetStringValue("Audio", "OutputDevice");
|
audio_output_device = si.GetStringValue("Audio", "OutputDevice");
|
||||||
audio_stretch_mode =
|
audio_stream_parameters.Load(si, "Audio");
|
||||||
AudioStream::ParseStretchMode(
|
|
||||||
si.GetStringValue("Audio", "StretchMode", AudioStream::GetStretchModeName(DEFAULT_AUDIO_STRETCH_MODE)).c_str())
|
|
||||||
.value_or(DEFAULT_AUDIO_STRETCH_MODE);
|
|
||||||
audio_output_latency_ms = si.GetUIntValue("Audio", "OutputLatencyMS", DEFAULT_AUDIO_OUTPUT_LATENCY_MS);
|
|
||||||
audio_buffer_ms = si.GetUIntValue("Audio", "BufferMS", DEFAULT_AUDIO_BUFFER_MS);
|
|
||||||
audio_output_volume = si.GetUIntValue("Audio", "OutputVolume", 100);
|
audio_output_volume = si.GetUIntValue("Audio", "OutputVolume", 100);
|
||||||
audio_fast_forward_volume = si.GetUIntValue("Audio", "FastForwardVolume", 100);
|
audio_fast_forward_volume = si.GetUIntValue("Audio", "FastForwardVolume", 100);
|
||||||
|
|
||||||
|
@ -422,6 +418,9 @@ void Settings::Load(SettingsInterface& si)
|
||||||
si.GetIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold", 128);
|
si.GetIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold", 128);
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
|
// No expansion due to license incompatibility.
|
||||||
|
audio_expansion_mode = AudioExpansionMode::Disabled;
|
||||||
|
|
||||||
// Android users are incredibly silly and don't understand that stretch is in the aspect ratio list...
|
// Android users are incredibly silly and don't understand that stretch is in the aspect ratio list...
|
||||||
if (si.GetBoolValue("Display", "Stretch", false))
|
if (si.GetBoolValue("Display", "Stretch", false))
|
||||||
display_aspect_ratio = DisplayAspectRatio::MatchWindow;
|
display_aspect_ratio = DisplayAspectRatio::MatchWindow;
|
||||||
|
@ -563,12 +562,10 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
||||||
si.SetIntValue("CDROM", "ReadSpeedup", cdrom_read_speedup);
|
si.SetIntValue("CDROM", "ReadSpeedup", cdrom_read_speedup);
|
||||||
si.SetIntValue("CDROM", "SeekSpeedup", cdrom_seek_speedup);
|
si.SetIntValue("CDROM", "SeekSpeedup", cdrom_seek_speedup);
|
||||||
|
|
||||||
si.SetStringValue("Audio", "Backend", GetAudioBackendName(audio_backend));
|
si.SetStringValue("Audio", "Backend", AudioStream::GetBackendName(audio_backend));
|
||||||
si.SetStringValue("Audio", "Driver", audio_driver.c_str());
|
si.SetStringValue("Audio", "Driver", audio_driver.c_str());
|
||||||
si.SetStringValue("Audio", "OutputDevice", audio_output_device.c_str());
|
si.SetStringValue("Audio", "OutputDevice", audio_output_device.c_str());
|
||||||
si.SetStringValue("Audio", "StretchMode", AudioStream::GetStretchModeName(audio_stretch_mode));
|
audio_stream_parameters.Save(si, "Audio");
|
||||||
si.SetUIntValue("Audio", "BufferMS", audio_buffer_ms);
|
|
||||||
si.SetUIntValue("Audio", "OutputLatencyMS", audio_output_latency_ms);
|
|
||||||
si.SetUIntValue("Audio", "OutputVolume", audio_output_volume);
|
si.SetUIntValue("Audio", "OutputVolume", audio_output_volume);
|
||||||
si.SetUIntValue("Audio", "FastForwardVolume", audio_fast_forward_volume);
|
si.SetUIntValue("Audio", "FastForwardVolume", audio_fast_forward_volume);
|
||||||
si.SetBoolValue("Audio", "OutputMuted", audio_output_muted);
|
si.SetBoolValue("Audio", "OutputMuted", audio_output_muted);
|
||||||
|
@ -1570,64 +1567,9 @@ const char* Settings::GetDisplayScreenshotFormatExtension(DisplayScreenshotForma
|
||||||
return s_display_screenshot_format_extensions[static_cast<size_t>(format)];
|
return s_display_screenshot_format_extensions[static_cast<size_t>(format)];
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr const std::array s_audio_backend_names = {
|
|
||||||
"Null",
|
|
||||||
#ifdef ENABLE_CUBEB
|
|
||||||
"Cubeb",
|
|
||||||
#endif
|
|
||||||
#ifdef ENABLE_SDL2
|
|
||||||
"SDL",
|
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
|
||||||
"XAudio2",
|
|
||||||
#endif
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
"AAudio", "OpenSLES",
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
static constexpr const std::array s_audio_backend_display_names = {
|
|
||||||
TRANSLATE_NOOP("AudioBackend", "Null (No Output)"),
|
|
||||||
#ifdef ENABLE_CUBEB
|
|
||||||
TRANSLATE_NOOP("AudioBackend", "Cubeb"),
|
|
||||||
#endif
|
|
||||||
#ifdef ENABLE_SDL2
|
|
||||||
TRANSLATE_NOOP("AudioBackend", "SDL"),
|
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
|
||||||
TRANSLATE_NOOP("AudioBackend", "XAudio2"),
|
|
||||||
#endif
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
"AAudio",
|
|
||||||
"OpenSL ES",
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
std::optional<AudioBackend> Settings::ParseAudioBackend(const char* str)
|
|
||||||
{
|
|
||||||
int index = 0;
|
|
||||||
for (const char* name : s_audio_backend_names)
|
|
||||||
{
|
|
||||||
if (StringUtil::Strcasecmp(name, str) == 0)
|
|
||||||
return static_cast<AudioBackend>(index);
|
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* Settings::GetAudioBackendName(AudioBackend backend)
|
|
||||||
{
|
|
||||||
return s_audio_backend_names[static_cast<int>(backend)];
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* Settings::GetAudioBackendDisplayName(AudioBackend backend)
|
|
||||||
{
|
|
||||||
return Host::TranslateToCString("AudioBackend", s_audio_backend_display_names[static_cast<int>(backend)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr const std::array s_controller_type_names = {
|
static constexpr const std::array s_controller_type_names = {
|
||||||
"None", "DigitalController", "AnalogController", "AnalogJoystick", "GunCon", "PlayStationMouse", "NeGcon", "NeGconRumble"};
|
"None", "DigitalController", "AnalogController", "AnalogJoystick",
|
||||||
|
"GunCon", "PlayStationMouse", "NeGcon", "NeGconRumble"};
|
||||||
static constexpr const std::array s_controller_display_names = {
|
static constexpr const std::array s_controller_display_names = {
|
||||||
TRANSLATE_NOOP("ControllerType", "None"),
|
TRANSLATE_NOOP("ControllerType", "None"),
|
||||||
TRANSLATE_NOOP("ControllerType", "Digital Controller"),
|
TRANSLATE_NOOP("ControllerType", "Digital Controller"),
|
||||||
|
@ -2031,8 +1973,9 @@ static const char* s_log_filters[] = {
|
||||||
"WAVWriter",
|
"WAVWriter",
|
||||||
"WindowInfo",
|
"WindowInfo",
|
||||||
|
|
||||||
#ifdef ENABLE_CUBEB
|
#ifndef __ANDROID__
|
||||||
"CubebAudioStream",
|
"CubebAudioStream",
|
||||||
|
"SDLAudioStream",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_OPENGL
|
#ifdef ENABLE_OPENGL
|
||||||
|
|
|
@ -178,14 +178,12 @@ struct Settings
|
||||||
u32 cdrom_read_speedup = 1;
|
u32 cdrom_read_speedup = 1;
|
||||||
u32 cdrom_seek_speedup = 1;
|
u32 cdrom_seek_speedup = 1;
|
||||||
|
|
||||||
AudioBackend audio_backend = DEFAULT_AUDIO_BACKEND;
|
|
||||||
AudioStretchMode audio_stretch_mode = DEFAULT_AUDIO_STRETCH_MODE;
|
|
||||||
std::string audio_driver;
|
std::string audio_driver;
|
||||||
std::string audio_output_device;
|
std::string audio_output_device;
|
||||||
u32 audio_output_latency_ms = DEFAULT_AUDIO_OUTPUT_LATENCY_MS;
|
|
||||||
u32 audio_buffer_ms = DEFAULT_AUDIO_BUFFER_MS;
|
|
||||||
u32 audio_output_volume = 100;
|
u32 audio_output_volume = 100;
|
||||||
u32 audio_fast_forward_volume = 100;
|
u32 audio_fast_forward_volume = 100;
|
||||||
|
AudioStreamParameters audio_stream_parameters;
|
||||||
|
AudioBackend audio_backend = AudioStream::DEFAULT_BACKEND;
|
||||||
bool audio_output_muted : 1 = false;
|
bool audio_output_muted : 1 = false;
|
||||||
|
|
||||||
bool use_old_mdec_routines : 1 = false;
|
bool use_old_mdec_routines : 1 = false;
|
||||||
|
@ -432,10 +430,6 @@ struct Settings
|
||||||
static const char* GetDisplayScreenshotFormatDisplayName(DisplayScreenshotFormat mode);
|
static const char* GetDisplayScreenshotFormatDisplayName(DisplayScreenshotFormat mode);
|
||||||
static const char* GetDisplayScreenshotFormatExtension(DisplayScreenshotFormat mode);
|
static const char* GetDisplayScreenshotFormatExtension(DisplayScreenshotFormat mode);
|
||||||
|
|
||||||
static std::optional<AudioBackend> ParseAudioBackend(const char* str);
|
|
||||||
static const char* GetAudioBackendName(AudioBackend backend);
|
|
||||||
static const char* GetAudioBackendDisplayName(AudioBackend backend);
|
|
||||||
|
|
||||||
static std::optional<ControllerType> ParseControllerTypeName(std::string_view str);
|
static std::optional<ControllerType> ParseControllerTypeName(std::string_view str);
|
||||||
static const char* GetControllerTypeName(ControllerType type);
|
static const char* GetControllerTypeName(ControllerType type);
|
||||||
static const char* GetControllerTypeDisplayName(ControllerType type);
|
static const char* GetControllerTypeDisplayName(ControllerType type);
|
||||||
|
@ -478,18 +472,6 @@ struct Settings
|
||||||
static constexpr CPUFastmemMode DEFAULT_CPU_FASTMEM_MODE = CPUFastmemMode::Disabled;
|
static constexpr CPUFastmemMode DEFAULT_CPU_FASTMEM_MODE = CPUFastmemMode::Disabled;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(ENABLE_CUBEB)
|
|
||||||
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::Cubeb;
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::XAudio2;
|
|
||||||
#elif defined(__ANDROID__)
|
|
||||||
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::AAudio;
|
|
||||||
#elif defined(ENABLE_SDL2)
|
|
||||||
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::SDL;
|
|
||||||
#else
|
|
||||||
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::Null;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static constexpr DisplayDeinterlacingMode DEFAULT_DISPLAY_DEINTERLACING_MODE = DisplayDeinterlacingMode::Adaptive;
|
static constexpr DisplayDeinterlacingMode DEFAULT_DISPLAY_DEINTERLACING_MODE = DisplayDeinterlacingMode::Adaptive;
|
||||||
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
|
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
|
||||||
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto;
|
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto;
|
||||||
|
@ -517,15 +499,6 @@ struct Settings
|
||||||
|
|
||||||
static constexpr LOGLEVEL DEFAULT_LOG_LEVEL = LOGLEVEL_INFO;
|
static constexpr LOGLEVEL DEFAULT_LOG_LEVEL = LOGLEVEL_INFO;
|
||||||
|
|
||||||
#ifndef __ANDROID__
|
|
||||||
static constexpr u32 DEFAULT_AUDIO_BUFFER_MS = 50;
|
|
||||||
static constexpr u32 DEFAULT_AUDIO_OUTPUT_LATENCY_MS = 20;
|
|
||||||
#else
|
|
||||||
static constexpr u32 DEFAULT_AUDIO_BUFFER_MS = 100;
|
|
||||||
static constexpr u32 DEFAULT_AUDIO_OUTPUT_LATENCY_MS = 20;
|
|
||||||
#endif
|
|
||||||
static constexpr AudioStretchMode DEFAULT_AUDIO_STRETCH_MODE = AudioStretchMode::TimeStretch;
|
|
||||||
|
|
||||||
static constexpr bool DEFAULT_SAVE_STATE_COMPRESSION = true;
|
static constexpr bool DEFAULT_SAVE_STATE_COMPRESSION = true;
|
||||||
|
|
||||||
// Enable console logging by default on Linux platforms.
|
// Enable console logging by default on Linux platforms.
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include "common/bitfield.h"
|
#include "common/bitfield.h"
|
||||||
#include "common/bitutils.h"
|
#include "common/bitutils.h"
|
||||||
|
#include "common/error.h"
|
||||||
#include "common/fifo_queue.h"
|
#include "common/fifo_queue.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/path.h"
|
#include "common/path.h"
|
||||||
|
@ -43,7 +44,6 @@ namespace {
|
||||||
enum : u32
|
enum : u32
|
||||||
{
|
{
|
||||||
SPU_BASE = 0x1F801C00,
|
SPU_BASE = 0x1F801C00,
|
||||||
NUM_CHANNELS = 2,
|
|
||||||
NUM_VOICES = 24,
|
NUM_VOICES = 24,
|
||||||
NUM_VOICE_REGISTERS = 8,
|
NUM_VOICE_REGISTERS = 8,
|
||||||
VOICE_ADDRESS_SHIFT = 3,
|
VOICE_ADDRESS_SHIFT = 3,
|
||||||
|
@ -422,7 +422,7 @@ void SPU::Initialize()
|
||||||
&SPU::Execute, nullptr, false);
|
&SPU::Execute, nullptr, false);
|
||||||
s_transfer_event = TimingEvents::CreateTimingEvent(
|
s_transfer_event = TimingEvents::CreateTimingEvent(
|
||||||
"SPU Transfer", TRANSFER_TICKS_PER_HALFWORD, TRANSFER_TICKS_PER_HALFWORD, &SPU::ExecuteTransfer, nullptr, false);
|
"SPU Transfer", TRANSFER_TICKS_PER_HALFWORD, TRANSFER_TICKS_PER_HALFWORD, &SPU::ExecuteTransfer, nullptr, false);
|
||||||
s_null_audio_stream = AudioStream::CreateNullStream(SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms);
|
s_null_audio_stream = AudioStream::CreateNullStream(SAMPLE_RATE, g_settings.audio_stream_parameters.buffer_ms);
|
||||||
|
|
||||||
CreateOutputStream();
|
CreateOutputStream();
|
||||||
Reset();
|
Reset();
|
||||||
|
@ -430,19 +430,24 @@ void SPU::Initialize()
|
||||||
|
|
||||||
void SPU::CreateOutputStream()
|
void SPU::CreateOutputStream()
|
||||||
{
|
{
|
||||||
Log_InfoFmt("Creating '{}' audio stream, sample rate = {}, channels = {}, buffer = {}, latency = {}, stretching = {}",
|
Log_InfoFmt(
|
||||||
Settings::GetAudioBackendName(g_settings.audio_backend), static_cast<u32>(SAMPLE_RATE),
|
"Creating '{}' audio stream, sample rate = {}, expansion = {}, buffer = {}, latency = {}, stretching = {}",
|
||||||
static_cast<u32>(NUM_CHANNELS), g_settings.audio_buffer_ms, g_settings.audio_output_latency_ms,
|
AudioStream::GetBackendName(g_settings.audio_backend), static_cast<u32>(SAMPLE_RATE),
|
||||||
AudioStream::GetStretchModeName(g_settings.audio_stretch_mode));
|
AudioStream::GetExpansionModeName(g_settings.audio_stream_parameters.expansion_mode),
|
||||||
|
g_settings.audio_stream_parameters.buffer_ms, g_settings.audio_stream_parameters.output_latency_ms,
|
||||||
|
AudioStream::GetStretchModeName(g_settings.audio_stream_parameters.stretch_mode));
|
||||||
|
|
||||||
|
Error error;
|
||||||
s_audio_stream =
|
s_audio_stream =
|
||||||
Host::CreateAudioStream(g_settings.audio_backend, SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms,
|
AudioStream::CreateStream(g_settings.audio_backend, SAMPLE_RATE, g_settings.audio_stream_parameters, &error);
|
||||||
g_settings.audio_output_latency_ms, g_settings.audio_stretch_mode);
|
|
||||||
if (!s_audio_stream)
|
if (!s_audio_stream)
|
||||||
{
|
{
|
||||||
Host::ReportErrorAsync("Error", "Failed to create or configure audio stream, falling back to null output.");
|
Host::ReportErrorAsync(
|
||||||
|
"Error",
|
||||||
|
fmt::format("Failed to create or configure audio stream, falling back to null output. The error was:\n{}",
|
||||||
|
error.GetDescription()));
|
||||||
s_audio_stream.reset();
|
s_audio_stream.reset();
|
||||||
s_audio_stream = AudioStream::CreateNullStream(SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms);
|
s_audio_stream = AudioStream::CreateNullStream(SAMPLE_RATE, g_settings.audio_stream_parameters.buffer_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
s_audio_stream->SetOutputVolume(System::GetAudioOutputVolume());
|
s_audio_stream->SetOutputVolume(System::GetAudioOutputVolume());
|
||||||
|
|
|
@ -2748,8 +2748,8 @@ void System::UpdateSpeedLimiterState()
|
||||||
s_pre_frame_sleep = s_throttler_enabled && g_settings.display_pre_frame_sleep;
|
s_pre_frame_sleep = s_throttler_enabled && g_settings.display_pre_frame_sleep;
|
||||||
|
|
||||||
s_syncing_to_host = false;
|
s_syncing_to_host = false;
|
||||||
if (g_settings.sync_to_host_refresh_rate && (g_settings.audio_stretch_mode != AudioStretchMode::Off) &&
|
if (g_settings.sync_to_host_refresh_rate &&
|
||||||
s_target_speed == 1.0f && IsValid())
|
(g_settings.audio_stream_parameters.stretch_mode != AudioStretchMode::Off) && s_target_speed == 1.0f && IsValid())
|
||||||
{
|
{
|
||||||
float host_refresh_rate;
|
float host_refresh_rate;
|
||||||
if (g_gpu_device->GetHostRefreshRate(&host_refresh_rate))
|
if (g_gpu_device->GetHostRefreshRate(&host_refresh_rate))
|
||||||
|
@ -2780,7 +2780,8 @@ void System::UpdateSpeedLimiterState()
|
||||||
|
|
||||||
// Adjust nominal rate when resampling, or syncing to host.
|
// Adjust nominal rate when resampling, or syncing to host.
|
||||||
const bool rate_adjust =
|
const bool rate_adjust =
|
||||||
(s_syncing_to_host || g_settings.audio_stretch_mode == AudioStretchMode::Resample) && s_target_speed > 0.0f;
|
(s_syncing_to_host || g_settings.audio_stream_parameters.stretch_mode == AudioStretchMode::Resample) &&
|
||||||
|
s_target_speed > 0.0f;
|
||||||
stream->SetNominalRate(rate_adjust ? s_target_speed : 1.0f);
|
stream->SetNominalRate(rate_adjust ? s_target_speed : 1.0f);
|
||||||
|
|
||||||
if (old_target_speed < s_target_speed)
|
if (old_target_speed < s_target_speed)
|
||||||
|
@ -3714,17 +3715,15 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
{
|
{
|
||||||
Host::AddIconOSDMessage("AudioBackendSwitch", ICON_FA_HEADPHONES,
|
Host::AddIconOSDMessage("AudioBackendSwitch", ICON_FA_HEADPHONES,
|
||||||
fmt::format(TRANSLATE_FS("OSDMessage", "Switching to {} audio backend."),
|
fmt::format(TRANSLATE_FS("OSDMessage", "Switching to {} audio backend."),
|
||||||
Settings::GetAudioBackendName(g_settings.audio_backend)),
|
AudioStream::GetBackendDisplayName(g_settings.audio_backend)),
|
||||||
Host::OSD_INFO_DURATION);
|
Host::OSD_INFO_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
SPU::RecreateOutputStream();
|
SPU::RecreateOutputStream();
|
||||||
}
|
}
|
||||||
if (g_settings.audio_stretch_mode != old_settings.audio_stretch_mode)
|
if (g_settings.audio_stream_parameters.stretch_mode != old_settings.audio_stream_parameters.stretch_mode)
|
||||||
SPU::GetOutputStream()->SetStretchMode(g_settings.audio_stretch_mode);
|
SPU::GetOutputStream()->SetStretchMode(g_settings.audio_stream_parameters.stretch_mode);
|
||||||
if (g_settings.audio_buffer_ms != old_settings.audio_buffer_ms ||
|
if (g_settings.audio_stream_parameters != old_settings.audio_stream_parameters)
|
||||||
g_settings.audio_output_latency_ms != old_settings.audio_output_latency_ms ||
|
|
||||||
g_settings.audio_stretch_mode != old_settings.audio_stretch_mode)
|
|
||||||
{
|
{
|
||||||
SPU::RecreateOutputStream();
|
SPU::RecreateOutputStream();
|
||||||
UpdateSpeedLimiterState();
|
UpdateSpeedLimiterState();
|
||||||
|
|
|
@ -185,25 +185,6 @@ enum class DisplayScreenshotFormat : u8
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class AudioBackend : u8
|
|
||||||
{
|
|
||||||
Null,
|
|
||||||
#ifdef ENABLE_CUBEB
|
|
||||||
Cubeb,
|
|
||||||
#endif
|
|
||||||
#ifdef ENABLE_SDL2
|
|
||||||
SDL,
|
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
|
||||||
XAudio2,
|
|
||||||
#endif
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
AAudio,
|
|
||||||
OpenSLES,
|
|
||||||
#endif
|
|
||||||
Count
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ControllerType
|
enum class ControllerType
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -20,9 +20,11 @@ set(SRCS
|
||||||
advancedsettingswidget.cpp
|
advancedsettingswidget.cpp
|
||||||
advancedsettingswidget.h
|
advancedsettingswidget.h
|
||||||
advancedsettingswidget.ui
|
advancedsettingswidget.ui
|
||||||
|
audioexpansionsettingsdialog.ui
|
||||||
audiosettingswidget.cpp
|
audiosettingswidget.cpp
|
||||||
audiosettingswidget.h
|
audiosettingswidget.h
|
||||||
audiosettingswidget.ui
|
audiosettingswidget.ui
|
||||||
|
audiostretchsettingsdialog.ui
|
||||||
autoupdaterdialog.cpp
|
autoupdaterdialog.cpp
|
||||||
autoupdaterdialog.h
|
autoupdaterdialog.h
|
||||||
autoupdaterdialog.ui
|
autoupdaterdialog.ui
|
||||||
|
|
|
@ -0,0 +1,476 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AudioExpansionSettingsDialog</class>
|
||||||
|
<widget class="QDialog" name="AudioExpansionSettingsDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>614</width>
|
||||||
|
<height>371</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Audio Expansion Settings</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Circular Wrap:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="circularWrap">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>360</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>90</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="circularWrapLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>30</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>Shift:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="shift">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-100</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="shiftLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>Depth:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="depth">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>50</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="depthLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>10</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Focus:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="focus">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-100</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="focusLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Center Image:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="centerImage">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="centerImageLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Front Separation:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="frontSeparation">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="frontSeparationLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Rear Separation:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="rearSeparation">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="rearSeparationLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="0">
|
||||||
|
<widget class="QLabel" name="label_9">
|
||||||
|
<property name="text">
|
||||||
|
<string>Low Cutoff:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="lowCutoff">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lowCutoffLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="0">
|
||||||
|
<widget class="QLabel" name="label_10">
|
||||||
|
<property name="text">
|
||||||
|
<string>High Cutoff:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="highCutoff">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="highCutoffLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="11" column="0" colspan="2">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::RestoreDefaults</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="icon">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string><html><head/><body><p><span style=" font-weight:700;">Audio Expansion Settings</span><br/>These settings fine-tune the behavior of the FreeSurround-based channel expander.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::TextFormat::RichText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_11">
|
||||||
|
<property name="text">
|
||||||
|
<string>Block Size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="blockSize">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>8192</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>16</number>
|
||||||
|
</property>
|
||||||
|
<property name="pageStep">
|
||||||
|
<number>128</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>1024</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>128</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="blockSizeLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>30</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -1,9 +1,12 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#include "audiosettingswidget.h"
|
#include "audiosettingswidget.h"
|
||||||
|
#include "qtutils.h"
|
||||||
#include "settingswindow.h"
|
#include "settingswindow.h"
|
||||||
#include "settingwidgetbinder.h"
|
#include "settingwidgetbinder.h"
|
||||||
|
#include "ui_audioexpansionsettingsdialog.h"
|
||||||
|
#include "ui_audiostretchsettingsdialog.h"
|
||||||
|
|
||||||
#include "core/spu.h"
|
#include "core/spu.h"
|
||||||
|
|
||||||
|
@ -18,19 +21,41 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
|
||||||
m_ui.setupUi(this);
|
m_ui.setupUi(this);
|
||||||
|
|
||||||
for (u32 i = 0; i < static_cast<u32>(AudioBackend::Count); i++)
|
for (u32 i = 0; i < static_cast<u32>(AudioBackend::Count); i++)
|
||||||
m_ui.audioBackend->addItem(QString::fromUtf8(Settings::GetAudioBackendDisplayName(static_cast<AudioBackend>(i))));
|
m_ui.audioBackend->addItem(QString::fromUtf8(AudioStream::GetBackendDisplayName(static_cast<AudioBackend>(i))));
|
||||||
|
|
||||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.audioBackend, "Audio", "Backend", &Settings::ParseAudioBackend,
|
for (u32 i = 0; i < static_cast<u32>(AudioExpansionMode::Count); i++)
|
||||||
&Settings::GetAudioBackendName, Settings::DEFAULT_AUDIO_BACKEND);
|
{
|
||||||
|
m_ui.expansionMode->addItem(
|
||||||
|
QString::fromUtf8(AudioStream::GetExpansionModeDisplayName(static_cast<AudioExpansionMode>(i))));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < static_cast<u32>(AudioStretchMode::Count); i++)
|
||||||
|
{
|
||||||
|
m_ui.stretchMode->addItem(
|
||||||
|
QString::fromUtf8(AudioStream::GetStretchModeDisplayName(static_cast<AudioStretchMode>(i))));
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.audioBackend, "Audio", "Backend",
|
||||||
|
&AudioStream::ParseBackendName, &AudioStream::GetBackendName,
|
||||||
|
AudioStream::DEFAULT_BACKEND);
|
||||||
|
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.expansionMode, "Audio", "ExpansionMode",
|
||||||
|
&AudioStream::ParseExpansionMode, &AudioStream::GetExpansionModeName,
|
||||||
|
AudioStreamParameters::DEFAULT_EXPANSION_MODE);
|
||||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.stretchMode, "Audio", "StretchMode",
|
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.stretchMode, "Audio", "StretchMode",
|
||||||
&AudioStream::ParseStretchMode, &AudioStream::GetStretchModeName,
|
&AudioStream::ParseStretchMode, &AudioStream::GetStretchModeName,
|
||||||
Settings::DEFAULT_AUDIO_STRETCH_MODE);
|
AudioStreamParameters::DEFAULT_STRETCH_MODE);
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.bufferMS, "Audio", "BufferMS",
|
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.bufferMS, "Audio", "BufferMS",
|
||||||
Settings::DEFAULT_AUDIO_BUFFER_MS);
|
AudioStreamParameters::DEFAULT_BUFFER_MS);
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.outputLatencyMS, "Audio", "OutputLatencyMS",
|
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.outputLatencyMS, "Audio", "OutputLatencyMS",
|
||||||
Settings::DEFAULT_AUDIO_OUTPUT_LATENCY_MS);
|
AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS);
|
||||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.muteCDAudio, "CDROM", "MuteCDAudio", false);
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.muteCDAudio, "CDROM", "MuteCDAudio", false);
|
||||||
connect(m_ui.audioBackend, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::updateDriverNames);
|
connect(m_ui.audioBackend, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::updateDriverNames);
|
||||||
|
connect(m_ui.expansionMode, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::onExpansionModeChanged);
|
||||||
|
connect(m_ui.expansionSettings, &QToolButton::clicked, this, &AudioSettingsWidget::onExpansionSettingsClicked);
|
||||||
|
connect(m_ui.stretchMode, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::onStretchModeChanged);
|
||||||
|
connect(m_ui.stretchSettings, &QToolButton::clicked, this, &AudioSettingsWidget::onStretchSettingsClicked);
|
||||||
|
onExpansionModeChanged();
|
||||||
|
onStretchModeChanged();
|
||||||
updateDriverNames();
|
updateDriverNames();
|
||||||
|
|
||||||
m_ui.outputLatencyMinimal->setChecked(m_ui.outputLatencyMS->value() == 0);
|
m_ui.outputLatencyMinimal->setChecked(m_ui.outputLatencyMS->value() == 0);
|
||||||
|
@ -79,6 +104,9 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
|
||||||
dialog->registerWidgetHelp(m_ui.muteCDAudio, tr("Mute CD Audio"), tr("Unchecked"),
|
dialog->registerWidgetHelp(m_ui.muteCDAudio, tr("Mute CD Audio"), tr("Unchecked"),
|
||||||
tr("Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable "
|
tr("Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable "
|
||||||
"background music in some games."));
|
"background music in some games."));
|
||||||
|
dialog->registerWidgetHelp(m_ui.expansionMode, tr("Expansion Mode"), tr("Disabled (Stereo)"),
|
||||||
|
tr("Determines how audio is expanded from stereo to surround for supported games. This "
|
||||||
|
"includes games that support Dolby Pro Logic/Pro Logic II."));
|
||||||
dialog->registerWidgetHelp(
|
dialog->registerWidgetHelp(
|
||||||
m_ui.stretchMode, tr("Stretch Mode"), tr("Time Stretching"),
|
m_ui.stretchMode, tr("Stretch Mode"), tr("Time Stretching"),
|
||||||
tr("When running outside of 100% speed, adjusts the tempo on audio instead of dropping frames. Produces "
|
tr("When running outside of 100% speed, adjusts the tempo on audio instead of dropping frames. Produces "
|
||||||
|
@ -87,25 +115,46 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
|
||||||
|
|
||||||
AudioSettingsWidget::~AudioSettingsWidget() = default;
|
AudioSettingsWidget::~AudioSettingsWidget() = default;
|
||||||
|
|
||||||
|
void AudioSettingsWidget::onExpansionModeChanged()
|
||||||
|
{
|
||||||
|
const AudioExpansionMode expansion_mode =
|
||||||
|
AudioStream::ParseExpansionMode(
|
||||||
|
m_dialog
|
||||||
|
->getEffectiveStringValue("Audio", "ExpansionMode",
|
||||||
|
AudioStream::GetExpansionModeName(AudioStreamParameters::DEFAULT_EXPANSION_MODE))
|
||||||
|
.c_str())
|
||||||
|
.value_or(AudioStreamParameters::DEFAULT_EXPANSION_MODE);
|
||||||
|
m_ui.expansionSettings->setEnabled(expansion_mode != AudioExpansionMode::Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSettingsWidget::onStretchModeChanged()
|
||||||
|
{
|
||||||
|
const AudioStretchMode stretch_mode =
|
||||||
|
AudioStream::ParseStretchMode(
|
||||||
|
m_dialog
|
||||||
|
->getEffectiveStringValue("Audio", "StretchMode",
|
||||||
|
AudioStream::GetStretchModeName(AudioStreamParameters::DEFAULT_STRETCH_MODE))
|
||||||
|
.c_str())
|
||||||
|
.value_or(AudioStreamParameters::DEFAULT_STRETCH_MODE);
|
||||||
|
m_ui.stretchSettings->setEnabled(stretch_mode != AudioStretchMode::Off);
|
||||||
|
}
|
||||||
|
|
||||||
void AudioSettingsWidget::updateDriverNames()
|
void AudioSettingsWidget::updateDriverNames()
|
||||||
{
|
{
|
||||||
const AudioBackend backend =
|
const AudioBackend backend =
|
||||||
Settings::ParseAudioBackend(
|
AudioStream::ParseBackendName(
|
||||||
m_dialog
|
m_dialog->getEffectiveStringValue("Audio", "Backend", AudioStream::GetBackendName(AudioStream::DEFAULT_BACKEND))
|
||||||
->getEffectiveStringValue("Audio", "Backend", Settings::GetAudioBackendName(Settings::DEFAULT_AUDIO_BACKEND))
|
|
||||||
.c_str())
|
.c_str())
|
||||||
.value_or(Settings::DEFAULT_AUDIO_BACKEND);
|
.value_or(AudioStream::DEFAULT_BACKEND);
|
||||||
|
|
||||||
std::vector<std::string> names;
|
std::vector<std::string> names;
|
||||||
std::vector<std::pair<std::string, std::string>> devices;
|
std::vector<std::pair<std::string, std::string>> devices;
|
||||||
|
|
||||||
#ifdef ENABLE_CUBEB
|
|
||||||
if (backend == AudioBackend::Cubeb)
|
if (backend == AudioBackend::Cubeb)
|
||||||
{
|
{
|
||||||
names = AudioStream::GetCubebDriverNames();
|
names = AudioStream::GetCubebDriverNames();
|
||||||
devices = AudioStream::GetCubebOutputDevices(m_dialog->getEffectiveStringValue("Audio", "Driver", "").c_str());
|
devices = AudioStream::GetCubebOutputDevices(m_dialog->getEffectiveStringValue("Audio", "Driver", "").c_str());
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
m_ui.driver->disconnect();
|
m_ui.driver->disconnect();
|
||||||
m_ui.driver->clear();
|
m_ui.driver->clear();
|
||||||
|
@ -171,7 +220,7 @@ void AudioSettingsWidget::updateVolumeLabel()
|
||||||
|
|
||||||
void AudioSettingsWidget::onMinimalOutputLatencyChecked(bool new_value)
|
void AudioSettingsWidget::onMinimalOutputLatencyChecked(bool new_value)
|
||||||
{
|
{
|
||||||
const u32 value = new_value ? 0u : Settings::DEFAULT_AUDIO_OUTPUT_LATENCY_MS;
|
const u32 value = new_value ? 0u : AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS;
|
||||||
m_dialog->setIntSettingValue("Audio", "OutputLatencyMS", value);
|
m_dialog->setIntSettingValue("Audio", "OutputLatencyMS", value);
|
||||||
QSignalBlocker sb(m_ui.outputLatencyMS);
|
QSignalBlocker sb(m_ui.outputLatencyMS);
|
||||||
m_ui.outputLatencyMS->setValue(value);
|
m_ui.outputLatencyMS->setValue(value);
|
||||||
|
@ -211,3 +260,144 @@ void AudioSettingsWidget::onOutputMutedChanged(int new_state)
|
||||||
Host::CommitBaseSettingChanges();
|
Host::CommitBaseSettingChanges();
|
||||||
g_emu_thread->setAudioOutputMuted(muted);
|
g_emu_thread->setAudioOutputMuted(muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioSettingsWidget::onExpansionSettingsClicked()
|
||||||
|
{
|
||||||
|
QDialog dlg(QtUtils::GetRootWidget(this));
|
||||||
|
Ui::AudioExpansionSettingsDialog dlgui;
|
||||||
|
dlgui.setupUi(&dlg);
|
||||||
|
dlgui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("volume-up-line")).pixmap(32, 32));
|
||||||
|
|
||||||
|
SettingsInterface* sif = m_dialog->getSettingsInterface();
|
||||||
|
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.blockSize, "Audio", "ExpandBlockSize",
|
||||||
|
AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE, 0);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.blockSize, dlgui.blockSizeLabel);
|
||||||
|
SettingWidgetBinder::BindWidgetToFloatSetting(sif, dlgui.circularWrap, "Audio", "ExpandCircularWrap",
|
||||||
|
AudioStreamParameters::DEFAULT_EXPAND_CIRCULAR_WRAP);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.circularWrap, dlgui.circularWrapLabel);
|
||||||
|
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.shift, "Audio", "ExpandShift", 100.0f,
|
||||||
|
AudioStreamParameters::DEFAULT_EXPAND_SHIFT);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.shift, dlgui.shiftLabel, 100.0f);
|
||||||
|
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.depth, "Audio", "ExpandDepth", 10.0f,
|
||||||
|
AudioStreamParameters::DEFAULT_EXPAND_DEPTH);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.depth, dlgui.depthLabel, 10.0f);
|
||||||
|
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.focus, "Audio", "ExpandFocus", 100.0f,
|
||||||
|
AudioStreamParameters::DEFAULT_EXPAND_FOCUS);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.focus, dlgui.focusLabel, 100.0f);
|
||||||
|
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.centerImage, "Audio", "ExpandCenterImage", 100.0f,
|
||||||
|
AudioStreamParameters::DEFAULT_EXPAND_CENTER_IMAGE);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.centerImage, dlgui.centerImageLabel, 100.0f);
|
||||||
|
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.frontSeparation, "Audio", "ExpandFrontSeparation",
|
||||||
|
10.0f, AudioStreamParameters::DEFAULT_EXPAND_FRONT_SEPARATION);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.frontSeparation, dlgui.frontSeparationLabel, 10.0f);
|
||||||
|
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.rearSeparation, "Audio", "ExpandRearSeparation", 10.0f,
|
||||||
|
AudioStreamParameters::DEFAULT_EXPAND_REAR_SEPARATION);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.rearSeparation, dlgui.rearSeparationLabel, 10.0f);
|
||||||
|
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.lowCutoff, "Audio", "ExpandLowCutoff",
|
||||||
|
AudioStreamParameters::DEFAULT_EXPAND_LOW_CUTOFF);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.lowCutoff, dlgui.lowCutoffLabel);
|
||||||
|
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.highCutoff, "Audio", "ExpandHighCutoff",
|
||||||
|
AudioStreamParameters::DEFAULT_EXPAND_HIGH_CUTOFF);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.highCutoff, dlgui.highCutoffLabel);
|
||||||
|
|
||||||
|
connect(dlgui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, &dlg, &QDialog::accept);
|
||||||
|
connect(dlgui.buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [this, &dlg]() {
|
||||||
|
m_dialog->setIntSettingValue("Audio", "ExpandBlockSize",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE));
|
||||||
|
|
||||||
|
m_dialog->setFloatSettingValue("Audio", "ExpandCircularWrap",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_CIRCULAR_WRAP));
|
||||||
|
m_dialog->setFloatSettingValue(
|
||||||
|
"Audio", "ExpandShift",
|
||||||
|
m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_SHIFT));
|
||||||
|
m_dialog->setFloatSettingValue(
|
||||||
|
"Audio", "ExpandDepth",
|
||||||
|
m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_DEPTH));
|
||||||
|
m_dialog->setFloatSettingValue(
|
||||||
|
"Audio", "ExpandFocus",
|
||||||
|
m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_FOCUS));
|
||||||
|
m_dialog->setFloatSettingValue("Audio", "ExpandCenterImage",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_CENTER_IMAGE));
|
||||||
|
m_dialog->setFloatSettingValue("Audio", "ExpandFrontSeparation",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_FRONT_SEPARATION));
|
||||||
|
m_dialog->setFloatSettingValue("Audio", "ExpandRearSeparation",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_REAR_SEPARATION));
|
||||||
|
m_dialog->setIntSettingValue("Audio", "ExpandLowCutoff",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_LOW_CUTOFF));
|
||||||
|
m_dialog->setIntSettingValue("Audio", "ExpandHighCutoff",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_HIGH_CUTOFF));
|
||||||
|
|
||||||
|
dlg.done(0);
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, &AudioSettingsWidget::onExpansionSettingsClicked, Qt::QueuedConnection);
|
||||||
|
});
|
||||||
|
|
||||||
|
dlg.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSettingsWidget::onStretchSettingsClicked()
|
||||||
|
{
|
||||||
|
QDialog dlg(QtUtils::GetRootWidget(this));
|
||||||
|
Ui::AudioStretchSettingsDialog dlgui;
|
||||||
|
dlgui.setupUi(&dlg);
|
||||||
|
dlgui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("volume-up-line")).pixmap(32, 32));
|
||||||
|
|
||||||
|
SettingsInterface* sif = m_dialog->getSettingsInterface();
|
||||||
|
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.sequenceLength, "Audio", "StretchSequenceLengthMS",
|
||||||
|
AudioStreamParameters::DEFAULT_STRETCH_SEQUENCE_LENGTH, 0);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.sequenceLength, dlgui.sequenceLengthLabel);
|
||||||
|
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.seekWindowSize, "Audio", "StretchSeekWindowMS",
|
||||||
|
AudioStreamParameters::DEFAULT_STRETCH_SEEKWINDOW, 0);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.seekWindowSize, dlgui.seekWindowSizeLabel);
|
||||||
|
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.overlap, "Audio", "StretchOverlapMS",
|
||||||
|
AudioStreamParameters::DEFAULT_STRETCH_OVERLAP, 0);
|
||||||
|
QtUtils::BindLabelToSlider(dlgui.overlap, dlgui.overlapLabel);
|
||||||
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.useQuickSeek, "Audio", "StretchUseQuickSeek",
|
||||||
|
AudioStreamParameters::DEFAULT_STRETCH_USE_QUICKSEEK);
|
||||||
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.useAAFilter, "Audio", "StretchUseAAFilter",
|
||||||
|
AudioStreamParameters::DEFAULT_STRETCH_USE_AA_FILTER);
|
||||||
|
|
||||||
|
connect(dlgui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, &dlg, &QDialog::accept);
|
||||||
|
connect(dlgui.buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [this, &dlg]() {
|
||||||
|
m_dialog->setIntSettingValue("Audio", "StretchSequenceLengthMS",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<int>(AudioStreamParameters::DEFAULT_STRETCH_SEQUENCE_LENGTH));
|
||||||
|
m_dialog->setIntSettingValue("Audio", "StretchSeekWindowMS",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<int>(AudioStreamParameters::DEFAULT_STRETCH_SEEKWINDOW));
|
||||||
|
m_dialog->setIntSettingValue("Audio", "StretchOverlapMS",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<int>(AudioStreamParameters::DEFAULT_STRETCH_OVERLAP));
|
||||||
|
m_dialog->setBoolSettingValue("Audio", "StretchUseQuickSeek",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<bool>(AudioStreamParameters::DEFAULT_STRETCH_USE_QUICKSEEK));
|
||||||
|
m_dialog->setBoolSettingValue("Audio", "StretchUseAAFilter",
|
||||||
|
m_dialog->isPerGameSettings() ?
|
||||||
|
std::nullopt :
|
||||||
|
std::optional<bool>(AudioStreamParameters::DEFAULT_STRETCH_USE_AA_FILTER));
|
||||||
|
|
||||||
|
dlg.done(0);
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, &AudioSettingsWidget::onStretchSettingsClicked, Qt::QueuedConnection);
|
||||||
|
});
|
||||||
|
|
||||||
|
dlg.exec();
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ public:
|
||||||
~AudioSettingsWidget();
|
~AudioSettingsWidget();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
|
void onExpansionModeChanged();
|
||||||
|
void onStretchModeChanged();
|
||||||
|
|
||||||
void updateDriverNames();
|
void updateDriverNames();
|
||||||
void updateLatencyLabel();
|
void updateLatencyLabel();
|
||||||
void updateVolumeLabel();
|
void updateVolumeLabel();
|
||||||
|
@ -26,6 +29,9 @@ private Q_SLOTS:
|
||||||
void onFastForwardVolumeChanged(int new_value);
|
void onFastForwardVolumeChanged(int new_value);
|
||||||
void onOutputMutedChanged(int new_state);
|
void onOutputMutedChanged(int new_state);
|
||||||
|
|
||||||
|
void onExpansionSettingsClicked();
|
||||||
|
void onStretchSettingsClicked();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::AudioSettingsWidget m_ui;
|
Ui::AudioSettingsWidget m_ui;
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>516</width>
|
<width>523</width>
|
||||||
<height>435</height>
|
<height>478</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
@ -29,13 +29,40 @@
|
||||||
<string>Configuration</string>
|
<string>Configuration</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QComboBox" name="outputDevice"/>
|
||||||
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QComboBox" name="audioBackend"/>
|
<widget class="QComboBox" name="audioBackend"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_6">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Output Latency:</string>
|
<string>Driver:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0" colspan="2">
|
||||||
|
<widget class="QLabel" name="bufferingLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Maximum latency: 0 frames (0.00ms)</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Backend:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>Stretch Mode:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -46,7 +73,51 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="6" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="outputLatencyMS">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>500</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBothSides</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="outputLatencyMinimal">
|
||||||
|
<property name="text">
|
||||||
|
<string>Minimal</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="driver"/>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Buffer Size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_9">
|
||||||
|
<property name="text">
|
||||||
|
<string>Expansion Mode:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
<widget class="QSlider" name="bufferMS">
|
<widget class="QSlider" name="bufferMS">
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
<number>15</number>
|
<number>15</number>
|
||||||
|
@ -74,95 +145,40 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0">
|
<item row="6" column="0">
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Backend:</string>
|
<string>Output Latency:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="3" column="1">
|
||||||
<widget class="QLabel" name="label_6">
|
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0">
|
||||||
<property name="text">
|
|
||||||
<string>Driver:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QComboBox" name="driver"/>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="label_7">
|
|
||||||
<property name="text">
|
|
||||||
<string>Stretch Mode:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="1">
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSlider" name="outputLatencyMS">
|
<widget class="QComboBox" name="expansionMode"/>
|
||||||
<property name="maximum">
|
|
||||||
<number>500</number>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tickPosition">
|
|
||||||
<enum>QSlider::TickPosition::TicksBothSides</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tickInterval">
|
|
||||||
<number>20</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="outputLatencyMinimal">
|
<widget class="QToolButton" name="expansionSettings">
|
||||||
<property name="text">
|
<property name="icon">
|
||||||
<string>Minimal</string>
|
<iconset theme="settings-3-line"/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="QComboBox" name="outputDevice"/>
|
<layout class="QHBoxLayout" name="horizontalLayout_6" stretch="1,0">
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>Buffer Size:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="0" colspan="2">
|
|
||||||
<widget class="QLabel" name="bufferingLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Maximum latency: 0 frames (0.00ms)</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QComboBox" name="stretchMode">
|
|
||||||
<item>
|
<item>
|
||||||
<property name="text">
|
<widget class="QComboBox" name="stretchMode"/>
|
||||||
<string>Off (Noisy)</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<property name="text">
|
<widget class="QToolButton" name="stretchSettings">
|
||||||
<string>Resampling (Pitch Shift)</string>
|
<property name="icon">
|
||||||
</property>
|
<iconset theme="settings-3-line"/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
</layout>
|
||||||
<property name="text">
|
|
||||||
<string>Time Stretch (Tempo Change, Best Sound)</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AudioStretchSettingsDialog</class>
|
||||||
|
<widget class="QDialog" name="AudioStretchSettingsDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>501</width>
|
||||||
|
<height>248</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Audio Stretch Settings</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Sequence Length:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="sequenceLength">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="sequenceLengthLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>30</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>Seekwindow Size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="seekWindowSize">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="seekWindowSizeLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>Overlap:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="overlap">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>15</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="overlapLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>10</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0" colspan="2">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::RestoreDefaults</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="icon">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string><html><head/><body><p><span style=" font-weight:700;">Audio Stretch Settings</span><br/>These settings fine-tune the behavior of the SoundTouch audio time stretcher when running outside of 100% speed.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::TextFormat::RichText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="useQuickSeek">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use Quickseek</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="useAAFilter">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use Anti-Aliasing Filter</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -338,6 +338,12 @@
|
||||||
<QtUi Include="controllerbindingwidget_negconrumble.ui">
|
<QtUi Include="controllerbindingwidget_negconrumble.ui">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtUi>
|
</QtUi>
|
||||||
|
<QtUi Include="audioexpansionsettingsdialog.ui">
|
||||||
|
<FileType>Document</FileType>
|
||||||
|
</QtUi>
|
||||||
|
<QtUi Include="audiostretchsettingsdialog.ui">
|
||||||
|
<FileType>Document</FileType>
|
||||||
|
</QtUi>
|
||||||
<None Include="translations\duckstation-qt_es-es.ts" />
|
<None Include="translations\duckstation-qt_es-es.ts" />
|
||||||
<None Include="translations\duckstation-qt_tr.ts" />
|
<None Include="translations\duckstation-qt_tr.ts" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -294,6 +294,8 @@
|
||||||
<QtUi Include="graphicssettingswidget.ui" />
|
<QtUi Include="graphicssettingswidget.ui" />
|
||||||
<QtUi Include="memoryscannerwindow.ui" />
|
<QtUi Include="memoryscannerwindow.ui" />
|
||||||
<QtUi Include="controllerbindingwidget_negconrumble.ui" />
|
<QtUi Include="controllerbindingwidget_negconrumble.ui" />
|
||||||
|
<QtUi Include="audioexpansionsettingsdialog.ui" />
|
||||||
|
<QtUi Include="audiostretchsettingsdialog.ui" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="qt5.natvis" />
|
<Natvis Include="qt5.natvis" />
|
||||||
|
|
|
@ -166,6 +166,11 @@ QString QtHost::GetResourcesBasePath()
|
||||||
return QString::fromStdString(EmuFolders::Resources);
|
return QString::fromStdString(EmuFolders::Resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INISettingsInterface* QtHost::GetBaseSettingsInterface()
|
||||||
|
{
|
||||||
|
return s_base_settings_interface.get();
|
||||||
|
}
|
||||||
|
|
||||||
QIcon QtHost::GetAppIcon()
|
QIcon QtHost::GetAppIcon()
|
||||||
{
|
{
|
||||||
return QIcon(QStringLiteral(":/icons/duck.png"));
|
return QIcon(QStringLiteral(":/icons/duck.png"));
|
||||||
|
|
|
@ -275,6 +275,9 @@ QIcon GetAppIcon();
|
||||||
/// Returns the base path for resources. This may be : prefixed, if we're using embedded resources.
|
/// Returns the base path for resources. This may be : prefixed, if we're using embedded resources.
|
||||||
QString GetResourcesBasePath();
|
QString GetResourcesBasePath();
|
||||||
|
|
||||||
|
/// Returns the base settings interface. Should lock before manipulating.
|
||||||
|
INISettingsInterface* GetBaseSettingsInterface();
|
||||||
|
|
||||||
/// Downloads the specified URL to the provided path.
|
/// Downloads the specified URL to the provided path.
|
||||||
bool DownloadFile(QWidget* parent, const QString& title, std::string url, const char* path);
|
bool DownloadFile(QWidget* parent, const QString& title, std::string url, const char* path);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#include "qtutils.h"
|
#include "qtutils.h"
|
||||||
|
@ -17,9 +17,11 @@
|
||||||
#include <QtWidgets/QDialog>
|
#include <QtWidgets/QDialog>
|
||||||
#include <QtWidgets/QHeaderView>
|
#include <QtWidgets/QHeaderView>
|
||||||
#include <QtWidgets/QInputDialog>
|
#include <QtWidgets/QInputDialog>
|
||||||
|
#include <QtWidgets/QLabel>
|
||||||
#include <QtWidgets/QMainWindow>
|
#include <QtWidgets/QMainWindow>
|
||||||
#include <QtWidgets/QMessageBox>
|
#include <QtWidgets/QMessageBox>
|
||||||
#include <QtWidgets/QScrollBar>
|
#include <QtWidgets/QScrollBar>
|
||||||
|
#include <QtWidgets/QSlider>
|
||||||
#include <QtWidgets/QStatusBar>
|
#include <QtWidgets/QStatusBar>
|
||||||
#include <QtWidgets/QStyle>
|
#include <QtWidgets/QStyle>
|
||||||
#include <QtWidgets/QTableView>
|
#include <QtWidgets/QTableView>
|
||||||
|
@ -218,6 +220,15 @@ void SetWidgetFontForInheritedSetting(QWidget* widget, bool inherited)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BindLabelToSlider(QSlider* slider, QLabel* label, float range /*= 1.0f*/)
|
||||||
|
{
|
||||||
|
auto update_label = [label, range](int new_value) {
|
||||||
|
label->setText(QString::number(static_cast<int>(new_value) / range));
|
||||||
|
};
|
||||||
|
update_label(slider->value());
|
||||||
|
QObject::connect(slider, &QSlider::valueChanged, label, std::move(update_label));
|
||||||
|
}
|
||||||
|
|
||||||
void SetWindowResizeable(QWidget* widget, bool resizeable)
|
void SetWindowResizeable(QWidget* widget, bool resizeable)
|
||||||
{
|
{
|
||||||
if (QMainWindow* window = qobject_cast<QMainWindow*>(widget); window)
|
if (QMainWindow* window = qobject_cast<QMainWindow*>(widget); window)
|
||||||
|
|
|
@ -21,6 +21,8 @@ class ByteStream;
|
||||||
class QComboBox;
|
class QComboBox;
|
||||||
class QFrame;
|
class QFrame;
|
||||||
class QKeyEvent;
|
class QKeyEvent;
|
||||||
|
class QLabel;
|
||||||
|
class QSlider;
|
||||||
class QTableView;
|
class QTableView;
|
||||||
class QTreeView;
|
class QTreeView;
|
||||||
class QVariant;
|
class QVariant;
|
||||||
|
@ -96,6 +98,9 @@ QString StringViewToQString(const std::string_view& str);
|
||||||
/// Sets a widget to italics if the setting value is inherited.
|
/// Sets a widget to italics if the setting value is inherited.
|
||||||
void SetWidgetFontForInheritedSetting(QWidget* widget, bool inherited);
|
void SetWidgetFontForInheritedSetting(QWidget* widget, bool inherited);
|
||||||
|
|
||||||
|
/// Binds a label to a slider's value.
|
||||||
|
void BindLabelToSlider(QSlider* slider, QLabel* label, float range = 1.0f);
|
||||||
|
|
||||||
/// Changes whether a window is resizable.
|
/// Changes whether a window is resizable.
|
||||||
void SetWindowResizeable(QWidget* widget, bool resizeable);
|
void SetWindowResizeable(QWidget* widget, bool resizeable);
|
||||||
|
|
||||||
|
|
|
@ -179,12 +179,9 @@ if(NOT ANDROID)
|
||||||
sdl_input_source.cpp
|
sdl_input_source.cpp
|
||||||
sdl_input_source.h
|
sdl_input_source.h
|
||||||
)
|
)
|
||||||
target_compile_definitions(util PUBLIC
|
|
||||||
"ENABLE_CUBEB=1"
|
|
||||||
"ENABLE_SDL2=1"
|
|
||||||
)
|
|
||||||
target_link_libraries(util PUBLIC
|
target_link_libraries(util PUBLIC
|
||||||
cubeb
|
cubeb
|
||||||
|
freesurround
|
||||||
SDL2::SDL2
|
SDL2::SDL2
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
@ -225,7 +222,7 @@ if(WIN32)
|
||||||
xinput_source.h
|
xinput_source.h
|
||||||
)
|
)
|
||||||
target_link_libraries(util PRIVATE d3d12ma)
|
target_link_libraries(util PRIVATE d3d12ma)
|
||||||
target_link_libraries(util PRIVATE d3d11.lib d3d12.lib d3dcompiler.lib dxgi.lib winmm.lib Dwmapi.lib winhttp.lib)
|
target_link_libraries(util PRIVATE d3d11.lib d3d12.lib d3dcompiler.lib dxgi.lib winmm.lib Dwmapi.lib winhttp.lib xaudio2.lib)
|
||||||
|
|
||||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||||
target_link_libraries(util PRIVATE WinPixEventRuntime::WinPixEventRuntime)
|
target_link_libraries(util PRIVATE WinPixEventRuntime::WinPixEventRuntime)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#include "audio_stream.h"
|
#include "audio_stream.h"
|
||||||
|
@ -6,22 +6,42 @@
|
||||||
|
|
||||||
#include "common/align.h"
|
#include "common/align.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/error.h"
|
||||||
#include "common/intrin.h"
|
#include "common/intrin.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
|
#include "common/settings_interface.h"
|
||||||
|
#include "common/small_string.h"
|
||||||
#include "common/timer.h"
|
#include "common/timer.h"
|
||||||
|
|
||||||
#include "SoundTouch.h"
|
#include "SoundTouch.h"
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
#include "freesurround_decoder.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
Log_SetChannel(AudioStream);
|
Log_SetChannel(AudioStream);
|
||||||
|
|
||||||
static constexpr bool LOG_TIMESTRETCH_STATS = false;
|
static constexpr bool LOG_TIMESTRETCH_STATS = false;
|
||||||
|
|
||||||
AudioStream::AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch)
|
static constexpr const std::array<std::pair<u8, u8>, static_cast<size_t>(AudioExpansionMode::Count)>
|
||||||
: m_sample_rate(sample_rate), m_channels(channels), m_buffer_ms(buffer_ms), m_stretch_mode(stretch)
|
s_expansion_channel_count = {{
|
||||||
|
{u8(2), u8(2)}, // Disabled
|
||||||
|
{u8(3), u8(3)}, // StereoLFE
|
||||||
|
{u8(5), u8(4)}, // Quadraphonic
|
||||||
|
{u8(5), u8(5)}, // QuadraphonicLFE
|
||||||
|
{u8(6), u8(6)}, // Surround51
|
||||||
|
{u8(8), u8(8)}, // Surround71
|
||||||
|
}};
|
||||||
|
|
||||||
|
AudioStream::AudioStream(u32 sample_rate, const AudioStreamParameters& parameters)
|
||||||
|
: m_sample_rate(sample_rate), m_parameters(parameters),
|
||||||
|
m_internal_channels(s_expansion_channel_count[static_cast<size_t>(parameters.expansion_mode)].first),
|
||||||
|
m_output_channels(s_expansion_channel_count[static_cast<size_t>(parameters.expansion_mode)].second)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,13 +50,48 @@ AudioStream::~AudioStream()
|
||||||
DestroyBuffer();
|
DestroyBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AudioStream> AudioStream::CreateNullStream(u32 sample_rate, u32 channels, u32 buffer_ms)
|
std::unique_ptr<AudioStream> AudioStream::CreateNullStream(u32 sample_rate, u32 buffer_ms)
|
||||||
{
|
{
|
||||||
std::unique_ptr<AudioStream> stream(new AudioStream(sample_rate, channels, buffer_ms, AudioStretchMode::Off));
|
// no point stretching with no output
|
||||||
stream->BaseInitialize();
|
AudioStreamParameters params;
|
||||||
|
params.expansion_mode = AudioExpansionMode::Disabled;
|
||||||
|
params.stretch_mode = AudioStretchMode::Off;
|
||||||
|
params.buffer_ms = static_cast<u16>(buffer_ms);
|
||||||
|
|
||||||
|
std::unique_ptr<AudioStream> stream(new AudioStream(sample_rate, params));
|
||||||
|
stream->BaseInitialize(&StereoSampleReaderImpl);
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
|
||||||
|
std::unique_ptr<AudioStream> AudioStream::CreateStream(AudioBackend backend, u32 sample_rate,
|
||||||
|
const AudioStreamParameters& parameters, Error* error)
|
||||||
|
{
|
||||||
|
switch (backend)
|
||||||
|
{
|
||||||
|
case AudioBackend::Cubeb:
|
||||||
|
return CreateCubebAudioStream(sample_rate, parameters, error);
|
||||||
|
|
||||||
|
case AudioBackend::SDL:
|
||||||
|
return CreateSDLAudioStream(sample_rate, parameters, error);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
case AudioBackend::XAudio2:
|
||||||
|
return CreateXAudio2Stream(sample_rate, parameters, error);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case AudioBackend::Null:
|
||||||
|
return CreateNullStream(sample_rate, parameters.buffer_ms);
|
||||||
|
|
||||||
|
default:
|
||||||
|
Error::SetStringView(error, "Unknown audio backend.");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
u32 AudioStream::GetAlignedBufferSize(u32 size)
|
u32 AudioStream::GetAlignedBufferSize(u32 size)
|
||||||
{
|
{
|
||||||
static_assert(Common::IsPow2(CHUNK_SIZE));
|
static_assert(Common::IsPow2(CHUNK_SIZE));
|
||||||
|
@ -54,10 +109,99 @@ u32 AudioStream::GetMSForBufferSize(u32 sample_rate, u32 buffer_size)
|
||||||
return (buffer_size * 1000u) / sample_rate;
|
return (buffer_size * 1000u) / sample_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr const std::array s_stretch_mode_names = {"None", "Resample", "TimeStretch"};
|
static constexpr const std::array s_backend_names = {
|
||||||
static constexpr const std::array s_stretch_mode_display_names = {TRANSLATE_NOOP("AudioStream", "None"),
|
"Null",
|
||||||
TRANSLATE_NOOP("AudioStream", "Resampling"),
|
#ifndef __ANDROID__
|
||||||
TRANSLATE_NOOP("AudioStream", "Time Stretching")};
|
"Cubeb",
|
||||||
|
"SDL",
|
||||||
|
#else
|
||||||
|
"AAudio",
|
||||||
|
"OpenSLES",
|
||||||
|
#endif
|
||||||
|
#ifdef _WIN32
|
||||||
|
"XAudio2",
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
static constexpr const std::array s_backend_display_names = {
|
||||||
|
TRANSLATE_NOOP("AudioStream", "Null (No Output)"),
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
TRANSLATE_NOOP("AudioStream", "Cubeb"),
|
||||||
|
TRANSLATE_NOOP("AudioStream", "SDL"),
|
||||||
|
#else
|
||||||
|
"AAudio",
|
||||||
|
"OpenSL ES",
|
||||||
|
#endif
|
||||||
|
#ifdef _WIN32
|
||||||
|
TRANSLATE_NOOP("AudioStream", "XAudio2"),
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<AudioBackend> AudioStream::ParseBackendName(const char* str)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
for (const char* name : s_backend_names)
|
||||||
|
{
|
||||||
|
if (std::strcmp(name, str) == 0)
|
||||||
|
return static_cast<AudioBackend>(index);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* AudioStream::GetBackendName(AudioBackend backend)
|
||||||
|
{
|
||||||
|
return s_backend_names[static_cast<int>(backend)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* AudioStream::GetBackendDisplayName(AudioBackend backend)
|
||||||
|
{
|
||||||
|
return Host::TranslateToCString("AudioStream", s_backend_display_names[static_cast<int>(backend)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr const std::array s_expansion_mode_names = {
|
||||||
|
"Disabled", "StereoLFE", "Quadraphonic", "QuadraphonicLFE", "Surround51", "Surround71",
|
||||||
|
};
|
||||||
|
static constexpr const std::array s_expansion_mode_display_names = {
|
||||||
|
TRANSLATE_NOOP("AudioStream", "Disabled (Stereo)"), TRANSLATE_NOOP("AudioStream", "Stereo with LFE"),
|
||||||
|
TRANSLATE_NOOP("AudioStream", "Quadraphonic"), TRANSLATE_NOOP("AudioStream", "Quadraphonic with LFE"),
|
||||||
|
TRANSLATE_NOOP("AudioStream", "5.1 Surround"), TRANSLATE_NOOP("AudioStream", "7.1 Surround"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* AudioStream::GetExpansionModeName(AudioExpansionMode mode)
|
||||||
|
{
|
||||||
|
return (static_cast<u32>(mode) < s_expansion_mode_names.size()) ? s_expansion_mode_names[static_cast<u32>(mode)] : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* AudioStream::GetExpansionModeDisplayName(AudioExpansionMode mode)
|
||||||
|
{
|
||||||
|
return (static_cast<u32>(mode) < s_expansion_mode_display_names.size()) ?
|
||||||
|
Host::TranslateToCString("AudioStream", s_expansion_mode_display_names[static_cast<u32>(mode)]) :
|
||||||
|
"";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AudioExpansionMode> AudioStream::ParseExpansionMode(const char* name)
|
||||||
|
{
|
||||||
|
for (u8 i = 0; i < static_cast<u8>(AudioExpansionMode::Count); i++)
|
||||||
|
{
|
||||||
|
if (std::strcmp(name, s_expansion_mode_names[i]) == 0)
|
||||||
|
return static_cast<AudioExpansionMode>(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr const std::array s_stretch_mode_names = {
|
||||||
|
"None",
|
||||||
|
"Resample",
|
||||||
|
"TimeStretch",
|
||||||
|
};
|
||||||
|
static constexpr const std::array s_stretch_mode_display_names = {
|
||||||
|
TRANSLATE_NOOP("AudioStream", "Off (Noisy)"),
|
||||||
|
TRANSLATE_NOOP("AudioStream", "Resampling (Pitch Shift)"),
|
||||||
|
TRANSLATE_NOOP("AudioStream", "Time Stretch (Tempo Change, Best Sound)"),
|
||||||
|
};
|
||||||
|
|
||||||
const char* AudioStream::GetStretchModeName(AudioStretchMode mode)
|
const char* AudioStream::GetStretchModeName(AudioStretchMode mode)
|
||||||
{
|
{
|
||||||
|
@ -89,7 +233,7 @@ u32 AudioStream::GetBufferedFramesRelaxed() const
|
||||||
return (wpos + m_buffer_size - rpos) % m_buffer_size;
|
return (wpos + m_buffer_size - rpos) % m_buffer_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::ReadFrames(s16* samples, u32 num_frames)
|
void AudioStream::ReadFrames(SampleType* samples, u32 num_frames)
|
||||||
{
|
{
|
||||||
const u32 available_frames = GetBufferedFramesRelaxed();
|
const u32 available_frames = GetBufferedFramesRelaxed();
|
||||||
u32 frames_to_read = num_frames;
|
u32 frames_to_read = num_frames;
|
||||||
|
@ -97,7 +241,7 @@ void AudioStream::ReadFrames(s16* samples, u32 num_frames)
|
||||||
|
|
||||||
if (m_filling)
|
if (m_filling)
|
||||||
{
|
{
|
||||||
u32 toFill = m_buffer_size / ((m_stretch_mode != AudioStretchMode::TimeStretch) ? 32 : 400);
|
u32 toFill = m_buffer_size / ((m_parameters.stretch_mode != AudioStretchMode::TimeStretch) ? 32 : 400);
|
||||||
toFill = GetAlignedBufferSize(toFill);
|
toFill = GetAlignedBufferSize(toFill);
|
||||||
|
|
||||||
if (available_frames < toFill)
|
if (available_frames < toFill)
|
||||||
|
@ -118,7 +262,7 @@ void AudioStream::ReadFrames(s16* samples, u32 num_frames)
|
||||||
frames_to_read = available_frames;
|
frames_to_read = available_frames;
|
||||||
m_filling = true;
|
m_filling = true;
|
||||||
|
|
||||||
if (m_stretch_mode == AudioStretchMode::TimeStretch)
|
if (m_parameters.stretch_mode == AudioStretchMode::TimeStretch)
|
||||||
StretchUnderrun();
|
StretchUnderrun();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +277,7 @@ void AudioStream::ReadFrames(s16* samples, u32 num_frames)
|
||||||
// towards the end of the buffer
|
// towards the end of the buffer
|
||||||
if (end > 0)
|
if (end > 0)
|
||||||
{
|
{
|
||||||
std::memcpy(samples, &m_buffer[rpos], sizeof(s32) * end);
|
m_sample_reader(samples, &m_buffer[rpos * m_internal_channels], end);
|
||||||
rpos += end;
|
rpos += end;
|
||||||
rpos = (rpos == m_buffer_size) ? 0 : rpos;
|
rpos = (rpos == m_buffer_size) ? 0 : rpos;
|
||||||
}
|
}
|
||||||
|
@ -142,7 +286,7 @@ void AudioStream::ReadFrames(s16* samples, u32 num_frames)
|
||||||
const u32 start = frames_to_read - end;
|
const u32 start = frames_to_read - end;
|
||||||
if (start > 0)
|
if (start > 0)
|
||||||
{
|
{
|
||||||
std::memcpy(&samples[end * 2], &m_buffer[0], sizeof(s32) * start);
|
m_sample_reader(&samples[end * m_output_channels], &m_buffer[0], start);
|
||||||
rpos = start;
|
rpos = start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,19 +302,20 @@ void AudioStream::ReadFrames(s16* samples, u32 num_frames)
|
||||||
const u32 increment =
|
const u32 increment =
|
||||||
static_cast<u32>(65536.0f * (static_cast<float>(frames_to_read) / static_cast<float>(num_frames)));
|
static_cast<u32>(65536.0f * (static_cast<float>(frames_to_read) / static_cast<float>(num_frames)));
|
||||||
|
|
||||||
SampleType* resample_ptr = static_cast<SampleType*>(alloca(frames_to_read * m_channels * sizeof(SampleType)));
|
SampleType* resample_ptr =
|
||||||
std::memcpy(resample_ptr, samples, frames_to_read * m_channels * sizeof(SampleType));
|
static_cast<SampleType*>(alloca(frames_to_read * m_output_channels * sizeof(SampleType)));
|
||||||
|
std::memcpy(resample_ptr, samples, frames_to_read * m_output_channels * sizeof(SampleType));
|
||||||
|
|
||||||
SampleType* out_ptr = samples;
|
SampleType* out_ptr = samples;
|
||||||
const u32 copy_stride = sizeof(SampleType) * m_channels;
|
const u32 copy_stride = sizeof(SampleType) * m_output_channels;
|
||||||
u32 resample_subpos = 0;
|
u32 resample_subpos = 0;
|
||||||
for (u32 i = 0; i < num_frames; i++)
|
for (u32 i = 0; i < num_frames; i++)
|
||||||
{
|
{
|
||||||
std::memcpy(out_ptr, resample_ptr, copy_stride);
|
std::memcpy(out_ptr, resample_ptr, copy_stride);
|
||||||
out_ptr += m_channels;
|
out_ptr += m_output_channels;
|
||||||
|
|
||||||
resample_subpos += increment;
|
resample_subpos += increment;
|
||||||
resample_ptr += (resample_subpos >> 16) * m_channels;
|
resample_ptr += (resample_subpos >> 16) * m_output_channels;
|
||||||
resample_subpos %= 65536u;
|
resample_subpos %= 65536u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,19 +324,23 @@ void AudioStream::ReadFrames(s16* samples, u32 num_frames)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// no data, fall back to silence
|
// no data, fall back to silence
|
||||||
std::memset(samples + (frames_to_read * m_channels), 0, sizeof(s16) * m_channels * silence_frames);
|
std::memset(samples + (frames_to_read * m_output_channels), 0, silence_frames * m_output_channels * sizeof(s16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::ApplyVolume(s16* samples, u32 num_frames)
|
void AudioStream::StereoSampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames)
|
||||||
|
{
|
||||||
|
std::memcpy(dest, src, num_frames * 2 * sizeof(SampleType));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioStream::ApplyVolume(s16* samples, u32 num_samples)
|
||||||
{
|
{
|
||||||
if (m_volume == 100)
|
if (m_volume == 100)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const s32 volume_mult = static_cast<s32>((static_cast<float>(m_volume) / 100.0f) * 32768.0f);
|
const s32 volume_mult = static_cast<s32>((static_cast<float>(m_volume) / 100.0f) * 32768.0f);
|
||||||
|
|
||||||
u32 num_samples = num_frames * m_channels;
|
|
||||||
while (num_samples > 0)
|
while (num_samples > 0)
|
||||||
{
|
{
|
||||||
*samples = static_cast<s16>((static_cast<s32>(*samples) * volume_mult) >> 15);
|
*samples = static_cast<s16>((static_cast<s32>(*samples) * volume_mult) >> 15);
|
||||||
|
@ -200,12 +349,12 @@ void AudioStream::ApplyVolume(s16* samples, u32 num_frames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::InternalWriteFrames(s32* bData, u32 nSamples)
|
void AudioStream::InternalWriteFrames(s16* data, u32 num_frames)
|
||||||
{
|
{
|
||||||
const u32 free = m_buffer_size - GetBufferedFramesRelaxed();
|
const u32 free = m_buffer_size - GetBufferedFramesRelaxed();
|
||||||
if (free <= nSamples)
|
if (free <= num_frames)
|
||||||
{
|
{
|
||||||
if (m_stretch_mode == AudioStretchMode::TimeStretch)
|
if (m_parameters.stretch_mode == AudioStretchMode::TimeStretch)
|
||||||
{
|
{
|
||||||
StretchOverrun();
|
StretchOverrun();
|
||||||
}
|
}
|
||||||
|
@ -219,49 +368,66 @@ void AudioStream::InternalWriteFrames(s32* bData, u32 nSamples)
|
||||||
u32 wpos = m_wpos.load(std::memory_order_acquire);
|
u32 wpos = m_wpos.load(std::memory_order_acquire);
|
||||||
|
|
||||||
// wrapping around the end of the buffer?
|
// wrapping around the end of the buffer?
|
||||||
if ((m_buffer_size - wpos) <= nSamples)
|
if ((m_buffer_size - wpos) <= num_frames)
|
||||||
{
|
{
|
||||||
// needs to be written in two parts
|
// needs to be written in two parts
|
||||||
const u32 end = m_buffer_size - wpos;
|
const u32 end = m_buffer_size - wpos;
|
||||||
const u32 start = nSamples - end;
|
const u32 start = num_frames - end;
|
||||||
|
|
||||||
// start is zero when this chunk reaches exactly the end
|
// start is zero when this chunk reaches exactly the end
|
||||||
std::memcpy(&m_buffer[wpos], bData, end * sizeof(s32));
|
std::memcpy(&m_buffer[wpos * m_internal_channels], data, end * m_internal_channels * sizeof(SampleType));
|
||||||
if (start > 0)
|
if (start > 0)
|
||||||
std::memcpy(&m_buffer[0], bData + end, start * sizeof(s32));
|
std::memcpy(&m_buffer[0], data + end * m_internal_channels, start * m_internal_channels * sizeof(SampleType));
|
||||||
|
|
||||||
wpos = start;
|
wpos = start;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// no split
|
// no split
|
||||||
std::memcpy(&m_buffer[wpos], bData, nSamples * sizeof(s32));
|
std::memcpy(&m_buffer[wpos * m_internal_channels], data, num_frames * m_internal_channels * sizeof(SampleType));
|
||||||
wpos += nSamples;
|
wpos += num_frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_wpos.store(wpos, std::memory_order_release);
|
m_wpos.store(wpos, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::BaseInitialize()
|
void AudioStream::BaseInitialize(SampleReader sample_reader)
|
||||||
{
|
{
|
||||||
|
m_sample_reader = sample_reader;
|
||||||
|
|
||||||
AllocateBuffer();
|
AllocateBuffer();
|
||||||
|
ExpandAllocate();
|
||||||
StretchAllocate();
|
StretchAllocate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::AllocateBuffer()
|
void AudioStream::AllocateBuffer()
|
||||||
{
|
{
|
||||||
// use a larger buffer when time stretching, since we need more input
|
// use a larger buffer when time stretching, since we need more input
|
||||||
const u32 multplier =
|
// TODO: do we really? it's more the output...
|
||||||
(m_stretch_mode == AudioStretchMode::TimeStretch) ? 16 : ((m_stretch_mode == AudioStretchMode::Off) ? 1 : 2);
|
const u32 multiplier = (m_parameters.stretch_mode == AudioStretchMode::TimeStretch) ?
|
||||||
m_buffer_size = GetAlignedBufferSize(((m_buffer_ms * multplier) * m_sample_rate) / 1000);
|
16 :
|
||||||
m_target_buffer_size = GetAlignedBufferSize((m_sample_rate * m_buffer_ms) / 1000u);
|
((m_parameters.stretch_mode == AudioStretchMode::Off) ? 1 : 2);
|
||||||
m_buffer = std::unique_ptr<s32[]>(new s32[m_buffer_size]);
|
m_buffer_size = GetAlignedBufferSize(((m_parameters.buffer_ms * multiplier) * m_sample_rate) / 1000);
|
||||||
Log_DevPrintf("Allocated buffer of %u frames for buffer of %u ms [stretch %s, target size %u].", m_buffer_size,
|
m_target_buffer_size = GetAlignedBufferSize((m_sample_rate * m_parameters.buffer_ms) / 1000u);
|
||||||
m_buffer_ms, GetStretchModeName(m_stretch_mode), m_target_buffer_size);
|
|
||||||
|
m_buffer = std::make_unique<s16[]>(m_buffer_size * m_internal_channels);
|
||||||
|
m_staging_buffer = std::make_unique<s16[]>(CHUNK_SIZE * m_internal_channels);
|
||||||
|
m_float_buffer = std::make_unique<float[]>(CHUNK_SIZE * m_internal_channels);
|
||||||
|
|
||||||
|
if (IsExpansionEnabled())
|
||||||
|
m_expand_buffer = std::make_unique<float[]>(m_parameters.expand_block_size * NUM_INPUT_CHANNELS);
|
||||||
|
|
||||||
|
Log_DevFmt(
|
||||||
|
"Allocated buffer of {} frames for buffer of {} ms [expansion {} (block size {}), stretch {}, target size {}].",
|
||||||
|
m_buffer_size, m_parameters.buffer_ms, GetExpansionModeName(m_parameters.expansion_mode),
|
||||||
|
m_parameters.expand_block_size, GetStretchModeName(m_parameters.stretch_mode), m_target_buffer_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::DestroyBuffer()
|
void AudioStream::DestroyBuffer()
|
||||||
{
|
{
|
||||||
|
m_expand_buffer.reset();
|
||||||
|
m_staging_buffer.reset();
|
||||||
|
m_float_buffer.reset();
|
||||||
m_buffer.reset();
|
m_buffer.reset();
|
||||||
m_buffer_size = 0;
|
m_buffer_size = 0;
|
||||||
m_wpos.store(0, std::memory_order_release);
|
m_wpos.store(0, std::memory_order_release);
|
||||||
|
@ -270,10 +436,19 @@ void AudioStream::DestroyBuffer()
|
||||||
|
|
||||||
void AudioStream::EmptyBuffer()
|
void AudioStream::EmptyBuffer()
|
||||||
{
|
{
|
||||||
if (m_stretch_mode != AudioStretchMode::Off)
|
#ifndef __ANDROID__
|
||||||
|
if (IsExpansionEnabled())
|
||||||
|
{
|
||||||
|
m_expander->Flush();
|
||||||
|
m_expand_output_buffer = nullptr;
|
||||||
|
m_expand_buffer_pos = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (IsStretchEnabled())
|
||||||
{
|
{
|
||||||
m_soundtouch->clear();
|
m_soundtouch->clear();
|
||||||
if (m_stretch_mode == AudioStretchMode::TimeStretch)
|
if (m_parameters.stretch_mode == AudioStretchMode::TimeStretch)
|
||||||
m_soundtouch->setTempo(m_nominal_rate);
|
m_soundtouch->setTempo(m_nominal_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,13 +458,13 @@ void AudioStream::EmptyBuffer()
|
||||||
void AudioStream::SetNominalRate(float tempo)
|
void AudioStream::SetNominalRate(float tempo)
|
||||||
{
|
{
|
||||||
m_nominal_rate = tempo;
|
m_nominal_rate = tempo;
|
||||||
if (m_stretch_mode == AudioStretchMode::Resample)
|
if (m_parameters.stretch_mode == AudioStretchMode::Resample)
|
||||||
m_soundtouch->setRate(tempo);
|
m_soundtouch->setRate(tempo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::UpdateTargetTempo(float tempo)
|
void AudioStream::UpdateTargetTempo(float tempo)
|
||||||
{
|
{
|
||||||
if (m_stretch_mode != AudioStretchMode::TimeStretch)
|
if (m_parameters.stretch_mode != AudioStretchMode::TimeStretch)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// undo sqrt()
|
// undo sqrt()
|
||||||
|
@ -308,7 +483,7 @@ void AudioStream::UpdateTargetTempo(float tempo)
|
||||||
|
|
||||||
void AudioStream::SetStretchMode(AudioStretchMode mode)
|
void AudioStream::SetStretchMode(AudioStretchMode mode)
|
||||||
{
|
{
|
||||||
if (m_stretch_mode == mode)
|
if (m_parameters.stretch_mode == mode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// can't resize the buffers while paused
|
// can't resize the buffers while paused
|
||||||
|
@ -318,10 +493,10 @@ void AudioStream::SetStretchMode(AudioStretchMode mode)
|
||||||
|
|
||||||
DestroyBuffer();
|
DestroyBuffer();
|
||||||
StretchDestroy();
|
StretchDestroy();
|
||||||
m_stretch_mode = mode;
|
m_parameters.stretch_mode = mode;
|
||||||
|
|
||||||
AllocateBuffer();
|
AllocateBuffer();
|
||||||
if (m_stretch_mode != AudioStretchMode::Off)
|
if (m_parameters.stretch_mode != AudioStretchMode::Off)
|
||||||
StretchAllocate();
|
StretchAllocate();
|
||||||
|
|
||||||
if (!paused)
|
if (!paused)
|
||||||
|
@ -341,8 +516,8 @@ void AudioStream::SetOutputVolume(u32 volume)
|
||||||
void AudioStream::BeginWrite(SampleType** buffer_ptr, u32* num_frames)
|
void AudioStream::BeginWrite(SampleType** buffer_ptr, u32* num_frames)
|
||||||
{
|
{
|
||||||
// TODO: Write directly to buffer when not using stretching.
|
// TODO: Write directly to buffer when not using stretching.
|
||||||
*buffer_ptr = reinterpret_cast<s16*>(&m_staging_buffer[m_staging_buffer_pos]);
|
*buffer_ptr = &m_staging_buffer[m_staging_buffer_pos];
|
||||||
*num_frames = CHUNK_SIZE - m_staging_buffer_pos;
|
*num_frames = CHUNK_SIZE - (m_staging_buffer_pos / NUM_INPUT_CHANNELS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::WriteFrames(const SampleType* frames, u32 num_frames)
|
void AudioStream::WriteFrames(const SampleType* frames, u32 num_frames)
|
||||||
|
@ -350,41 +525,20 @@ void AudioStream::WriteFrames(const SampleType* frames, u32 num_frames)
|
||||||
Panic("not implemented");
|
Panic("not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::EndWrite(u32 num_frames)
|
|
||||||
{
|
|
||||||
// don't bother committing anything when muted
|
|
||||||
if (m_volume == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_staging_buffer_pos += num_frames;
|
|
||||||
DebugAssert(m_staging_buffer_pos <= CHUNK_SIZE);
|
|
||||||
if (m_staging_buffer_pos < CHUNK_SIZE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_staging_buffer_pos = 0;
|
|
||||||
|
|
||||||
if (m_stretch_mode != AudioStretchMode::Off)
|
|
||||||
StretchWrite();
|
|
||||||
else
|
|
||||||
InternalWriteFrames(m_staging_buffer.data(), CHUNK_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr float S16_TO_FLOAT = 1.0f / 32767.0f;
|
static constexpr float S16_TO_FLOAT = 1.0f / 32767.0f;
|
||||||
static constexpr float FLOAT_TO_S16 = 32767.0f;
|
static constexpr float FLOAT_TO_S16 = 32767.0f;
|
||||||
|
|
||||||
#if defined(CPU_ARCH_NEON)
|
#if defined(CPU_ARCH_NEON)
|
||||||
|
|
||||||
static void S16ChunkToFloat(const s32* src, float* dst)
|
static void S16ChunkToFloat(const s16* src, float* dst, u32 num_samples)
|
||||||
{
|
{
|
||||||
static_assert((AudioStream::CHUNK_SIZE % 4) == 0);
|
|
||||||
constexpr u32 iterations = AudioStream::CHUNK_SIZE / 4;
|
|
||||||
|
|
||||||
const float32x4_t S16_TO_FLOAT_V = vdupq_n_f32(S16_TO_FLOAT);
|
const float32x4_t S16_TO_FLOAT_V = vdupq_n_f32(S16_TO_FLOAT);
|
||||||
|
|
||||||
|
const u32 iterations = (num_samples + 7) / 8;
|
||||||
for (u32 i = 0; i < iterations; i++)
|
for (u32 i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
const int16x8_t sv = vreinterpretq_s16_s32(vld1q_s32(src));
|
const int16x8_t sv = vreinterpretq_s16_s32(vld1q_s32(src));
|
||||||
src += 4;
|
src += 8;
|
||||||
|
|
||||||
int32x4_t iv1 = vreinterpretq_s32_s16(vzip1q_s16(sv, sv)); // [0, 0, 1, 1, 2, 2, 3, 3]
|
int32x4_t iv1 = vreinterpretq_s32_s16(vzip1q_s16(sv, sv)); // [0, 0, 1, 1, 2, 2, 3, 3]
|
||||||
int32x4_t iv2 = vreinterpretq_s32_s16(vzip2q_s16(sv, sv)); // [4, 4, 5, 5, 6, 6, 7, 7]
|
int32x4_t iv2 = vreinterpretq_s32_s16(vzip2q_s16(sv, sv)); // [4, 4, 5, 5, 6, 6, 7, 7]
|
||||||
|
@ -401,13 +555,11 @@ static void S16ChunkToFloat(const s32* src, float* dst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void FloatChunkToS16(s32* dst, const float* src, uint size)
|
static void FloatChunkToS16(s16* dst, const float* src, u32 num_samples)
|
||||||
{
|
{
|
||||||
static_assert((AudioStream::CHUNK_SIZE % 4) == 0);
|
static_assert((AudioStream::CHUNK_SIZE % 4) == 0);
|
||||||
constexpr u32 iterations = AudioStream::CHUNK_SIZE / 4;
|
|
||||||
|
|
||||||
const float32x4_t FLOAT_TO_S16_V = vdupq_n_f32(FLOAT_TO_S16);
|
|
||||||
|
|
||||||
|
const u32 iterations = (num_samples + 7) / 8;
|
||||||
for (u32 i = 0; i < iterations; i++)
|
for (u32 i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
float32x4_t fv1 = vld1q_f32(src + 0);
|
float32x4_t fv1 = vld1q_f32(src + 0);
|
||||||
|
@ -421,23 +573,21 @@ static void FloatChunkToS16(s32* dst, const float* src, uint size)
|
||||||
|
|
||||||
int16x8_t iv = vcombine_s16(vqmovn_s32(iv1), vqmovn_s32(iv2));
|
int16x8_t iv = vcombine_s16(vqmovn_s32(iv1), vqmovn_s32(iv2));
|
||||||
vst1q_s32(dst, vreinterpretq_s32_s16(iv));
|
vst1q_s32(dst, vreinterpretq_s32_s16(iv));
|
||||||
dst += 4;
|
dst += 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(CPU_ARCH_SSE)
|
#elif defined(CPU_ARCH_SSE)
|
||||||
|
|
||||||
static void S16ChunkToFloat(const s32* src, float* dst)
|
static void S16ChunkToFloat(const s16* src, float* dst, u32 num_samples)
|
||||||
{
|
{
|
||||||
static_assert((AudioStream::CHUNK_SIZE % 4) == 0);
|
|
||||||
constexpr u32 iterations = AudioStream::CHUNK_SIZE / 4;
|
|
||||||
|
|
||||||
const __m128 S16_TO_FLOAT_V = _mm_set1_ps(S16_TO_FLOAT);
|
const __m128 S16_TO_FLOAT_V = _mm_set1_ps(S16_TO_FLOAT);
|
||||||
|
|
||||||
|
const u32 iterations = (num_samples + 7) / 8;
|
||||||
for (u32 i = 0; i < iterations; i++)
|
for (u32 i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
const __m128i sv = _mm_load_si128(reinterpret_cast<const __m128i*>(src));
|
const __m128i sv = _mm_load_si128(reinterpret_cast<const __m128i*>(src));
|
||||||
src += 4;
|
src += 8;
|
||||||
|
|
||||||
__m128i iv1 = _mm_unpacklo_epi16(sv, sv); // [0, 0, 1, 1, 2, 2, 3, 3]
|
__m128i iv1 = _mm_unpacklo_epi16(sv, sv); // [0, 0, 1, 1, 2, 2, 3, 3]
|
||||||
__m128i iv2 = _mm_unpackhi_epi16(sv, sv); // [4, 4, 5, 5, 6, 6, 7, 7]
|
__m128i iv2 = _mm_unpackhi_epi16(sv, sv); // [4, 4, 5, 5, 6, 6, 7, 7]
|
||||||
|
@ -454,13 +604,11 @@ static void S16ChunkToFloat(const s32* src, float* dst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void FloatChunkToS16(s32* dst, const float* src, uint size)
|
static void FloatChunkToS16(s16* dst, const float* src, u32 num_samples)
|
||||||
{
|
{
|
||||||
static_assert((AudioStream::CHUNK_SIZE % 4) == 0);
|
|
||||||
constexpr u32 iterations = AudioStream::CHUNK_SIZE / 4;
|
|
||||||
|
|
||||||
const __m128 FLOAT_TO_S16_V = _mm_set1_ps(FLOAT_TO_S16);
|
const __m128 FLOAT_TO_S16_V = _mm_set1_ps(FLOAT_TO_S16);
|
||||||
|
|
||||||
|
const u32 iterations = (num_samples + 7) / 8;
|
||||||
for (u32 i = 0; i < iterations; i++)
|
for (u32 i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
__m128 fv1 = _mm_load_ps(src + 0);
|
__m128 fv1 = _mm_load_ps(src + 0);
|
||||||
|
@ -474,33 +622,107 @@ static void FloatChunkToS16(s32* dst, const float* src, uint size)
|
||||||
|
|
||||||
__m128i iv = _mm_packs_epi32(iv1, iv2);
|
__m128i iv = _mm_packs_epi32(iv1, iv2);
|
||||||
_mm_store_si128(reinterpret_cast<__m128i*>(dst), iv);
|
_mm_store_si128(reinterpret_cast<__m128i*>(dst), iv);
|
||||||
dst += 4;
|
dst += 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static void S16ChunkToFloat(const s32* src, float* dst)
|
static void S16ChunkToFloat(const s16* src, float* dst, u32 num_samples)
|
||||||
{
|
{
|
||||||
for (uint i = 0; i < AudioStream::CHUNK_SIZE; ++i)
|
for (u32 i = 0; i < num_samples; ++i)
|
||||||
{
|
*(dst++) = static_cast<float>(*(src++)) / 32767.0f;
|
||||||
*(dst++) = static_cast<float>(static_cast<s16>((u32)*src)) / 32767.0f;
|
|
||||||
*(dst++) = static_cast<float>(static_cast<s16>(((u32)*src) >> 16)) / 32767.0f;
|
|
||||||
src++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void FloatChunkToS16(s32* dst, const float* src, uint size)
|
static void FloatChunkToS16(s16* dst, const float* src, u32 num_samples)
|
||||||
{
|
{
|
||||||
for (uint i = 0; i < size; ++i)
|
for (u32 i = 0; i < num_samples; ++i)
|
||||||
{
|
*(dst++) = static_cast<s16>((*(src++) * 32767.0f));
|
||||||
const s16 left = static_cast<s16>((*(src++) * 32767.0f));
|
|
||||||
const s16 right = static_cast<s16>((*(src++) * 32767.0f));
|
|
||||||
*(dst++) = (static_cast<u32>(left) & 0xFFFFu) | (static_cast<u32>(right) << 16);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void AudioStream::ExpandAllocate()
|
||||||
|
{
|
||||||
|
DebugAssert(!m_expander);
|
||||||
|
if (m_parameters.expansion_mode == AudioExpansionMode::Disabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
static constexpr std::array<std::pair<FreeSurroundDecoder::ChannelSetup, bool>,
|
||||||
|
static_cast<size_t>(AudioExpansionMode::Count)>
|
||||||
|
channel_setup_mapping = {{
|
||||||
|
{FreeSurroundDecoder::ChannelSetup::Stereo, false}, // Disabled
|
||||||
|
{FreeSurroundDecoder::ChannelSetup::Stereo, true}, // StereoLFE
|
||||||
|
{FreeSurroundDecoder::ChannelSetup::Surround41, false}, // Quadraphonic
|
||||||
|
{FreeSurroundDecoder::ChannelSetup::Surround41, true}, // QuadraphonicLFE
|
||||||
|
{FreeSurroundDecoder::ChannelSetup::Surround51, true}, // Surround51
|
||||||
|
{FreeSurroundDecoder::ChannelSetup::Surround71, true}, // Surround71
|
||||||
|
}};
|
||||||
|
|
||||||
|
const auto [fs_setup, fs_lfe] = channel_setup_mapping[static_cast<size_t>(m_parameters.expansion_mode)];
|
||||||
|
|
||||||
|
m_expander = std::make_unique<FreeSurroundDecoder>(fs_setup, m_parameters.expand_block_size);
|
||||||
|
m_expander->SetBassRedirection(fs_lfe);
|
||||||
|
m_expander->SetCircularWrap(m_parameters.expand_circular_wrap);
|
||||||
|
m_expander->SetShift(m_parameters.expand_shift);
|
||||||
|
m_expander->SetDepth(m_parameters.expand_depth);
|
||||||
|
m_expander->SetFocus(m_parameters.expand_focus);
|
||||||
|
m_expander->SetCenterImage(m_parameters.expand_center_image);
|
||||||
|
m_expander->SetFrontSeparation(m_parameters.expand_front_separation);
|
||||||
|
m_expander->SetRearSeparation(m_parameters.expand_rear_separation);
|
||||||
|
m_expander->SetLowCutoff(static_cast<float>(m_parameters.expand_low_cutoff) / m_sample_rate * 2);
|
||||||
|
m_expander->SetHighCutoff(static_cast<float>(m_parameters.expand_high_cutoff) / m_sample_rate * 2);
|
||||||
|
#else
|
||||||
|
Panic("Attempting to use expansion on Android.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioStream::EndWrite(u32 num_frames)
|
||||||
|
{
|
||||||
|
// don't bother committing anything when muted
|
||||||
|
if (m_volume == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_staging_buffer_pos += num_frames * NUM_INPUT_CHANNELS;
|
||||||
|
DebugAssert(m_staging_buffer_pos <= (CHUNK_SIZE * NUM_INPUT_CHANNELS));
|
||||||
|
if ((m_staging_buffer_pos / NUM_INPUT_CHANNELS) < CHUNK_SIZE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_staging_buffer_pos = 0;
|
||||||
|
|
||||||
|
if (!IsExpansionEnabled() && !IsStretchEnabled())
|
||||||
|
{
|
||||||
|
InternalWriteFrames(m_staging_buffer.get(), CHUNK_SIZE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
if (IsExpansionEnabled())
|
||||||
|
{
|
||||||
|
// StretchWriteBlock() overwrites the staging buffer on output, so we need to copy into the expand buffer first.
|
||||||
|
S16ChunkToFloat(m_staging_buffer.get(), m_expand_buffer.get() + m_expand_buffer_pos * NUM_INPUT_CHANNELS,
|
||||||
|
CHUNK_SIZE * NUM_INPUT_CHANNELS);
|
||||||
|
|
||||||
|
// Output the corresponding block.
|
||||||
|
if (m_expand_output_buffer)
|
||||||
|
StretchWriteBlock(m_expand_output_buffer + m_expand_buffer_pos * m_internal_channels);
|
||||||
|
|
||||||
|
// Decode the next block if we buffered enough.
|
||||||
|
m_expand_buffer_pos += CHUNK_SIZE;
|
||||||
|
if (m_expand_buffer_pos == m_parameters.expand_block_size)
|
||||||
|
{
|
||||||
|
m_expand_buffer_pos = 0;
|
||||||
|
m_expand_output_buffer = m_expander->Decode(m_expand_buffer.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
S16ChunkToFloat(m_staging_buffer.get(), m_float_buffer.get(), CHUNK_SIZE * NUM_INPUT_CHANNELS);
|
||||||
|
StretchWriteBlock(m_float_buffer.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Time stretching algorithm based on PCSX2 implementation.
|
// Time stretching algorithm based on PCSX2 implementation.
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
|
@ -511,21 +733,21 @@ ALWAYS_INLINE static bool IsInRange(const T& val, const T& min, const T& max)
|
||||||
|
|
||||||
void AudioStream::StretchAllocate()
|
void AudioStream::StretchAllocate()
|
||||||
{
|
{
|
||||||
if (m_stretch_mode == AudioStretchMode::Off)
|
if (m_parameters.stretch_mode == AudioStretchMode::Off)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_soundtouch = std::make_unique<soundtouch::SoundTouch>();
|
m_soundtouch = std::make_unique<soundtouch::SoundTouch>();
|
||||||
m_soundtouch->setSampleRate(m_sample_rate);
|
m_soundtouch->setSampleRate(m_sample_rate);
|
||||||
m_soundtouch->setChannels(m_channels);
|
m_soundtouch->setChannels(m_internal_channels);
|
||||||
|
|
||||||
m_soundtouch->setSetting(SETTING_USE_QUICKSEEK, 0);
|
m_soundtouch->setSetting(SETTING_USE_QUICKSEEK, m_parameters.stretch_use_quickseek);
|
||||||
m_soundtouch->setSetting(SETTING_USE_AA_FILTER, 0);
|
m_soundtouch->setSetting(SETTING_USE_AA_FILTER, m_parameters.stretch_use_aa_filter);
|
||||||
|
|
||||||
m_soundtouch->setSetting(SETTING_SEQUENCE_MS, 30);
|
m_soundtouch->setSetting(SETTING_SEQUENCE_MS, m_parameters.stretch_sequence_length_ms);
|
||||||
m_soundtouch->setSetting(SETTING_SEEKWINDOW_MS, 20);
|
m_soundtouch->setSetting(SETTING_SEEKWINDOW_MS, m_parameters.stretch_seekwindow_ms);
|
||||||
m_soundtouch->setSetting(SETTING_OVERLAP_MS, 10);
|
m_soundtouch->setSetting(SETTING_OVERLAP_MS, m_parameters.stretch_overlap_ms);
|
||||||
|
|
||||||
if (m_stretch_mode == AudioStretchMode::Resample)
|
if (m_parameters.stretch_mode == AudioStretchMode::Resample)
|
||||||
m_soundtouch->setRate(m_nominal_rate);
|
m_soundtouch->setRate(m_nominal_rate);
|
||||||
else
|
else
|
||||||
m_soundtouch->setTempo(m_nominal_rate);
|
m_soundtouch->setTempo(m_nominal_rate);
|
||||||
|
@ -545,21 +767,27 @@ void AudioStream::StretchDestroy()
|
||||||
m_soundtouch.reset();
|
m_soundtouch.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::StretchWrite()
|
void AudioStream::StretchWriteBlock(const float* block)
|
||||||
{
|
{
|
||||||
S16ChunkToFloat(m_staging_buffer.data(), m_float_buffer.data());
|
if (IsStretchEnabled())
|
||||||
|
|
||||||
m_soundtouch->putSamples(m_float_buffer.data(), CHUNK_SIZE);
|
|
||||||
|
|
||||||
int tempProgress;
|
|
||||||
while (tempProgress = m_soundtouch->receiveSamples((float*)m_float_buffer.data(), CHUNK_SIZE), tempProgress != 0)
|
|
||||||
{
|
{
|
||||||
FloatChunkToS16(m_staging_buffer.data(), m_float_buffer.data(), tempProgress);
|
m_soundtouch->putSamples(block, CHUNK_SIZE);
|
||||||
InternalWriteFrames(m_staging_buffer.data(), tempProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_stretch_mode == AudioStretchMode::TimeStretch)
|
u32 tempProgress;
|
||||||
UpdateStretchTempo();
|
while (tempProgress = m_soundtouch->receiveSamples(m_float_buffer.get(), CHUNK_SIZE), tempProgress != 0)
|
||||||
|
{
|
||||||
|
FloatChunkToS16(m_staging_buffer.get(), m_float_buffer.get(), tempProgress * m_internal_channels);
|
||||||
|
InternalWriteFrames(m_staging_buffer.get(), tempProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_parameters.stretch_mode == AudioStretchMode::TimeStretch)
|
||||||
|
UpdateStretchTempo();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FloatChunkToS16(m_staging_buffer.get(), block, CHUNK_SIZE * m_internal_channels);
|
||||||
|
InternalWriteFrames(m_staging_buffer.get(), CHUNK_SIZE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float AudioStream::AddAndGetAverageTempo(float val)
|
float AudioStream::AddAndGetAverageTempo(float val)
|
||||||
|
@ -691,3 +919,113 @@ void AudioStream::StretchOverrun()
|
||||||
const u32 discard = CHUNK_SIZE * 2;
|
const u32 discard = CHUNK_SIZE * 2;
|
||||||
m_rpos.store((m_rpos.load(std::memory_order_acquire) + discard) % m_buffer_size, std::memory_order_release);
|
m_rpos.store((m_rpos.load(std::memory_order_acquire) + discard) % m_buffer_size, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioStreamParameters::Load(SettingsInterface& si, const char* section)
|
||||||
|
{
|
||||||
|
stretch_mode =
|
||||||
|
AudioStream::ParseStretchMode(
|
||||||
|
si.GetStringValue(section, "StretchMode", AudioStream::GetStretchModeName(DEFAULT_STRETCH_MODE)).c_str())
|
||||||
|
.value_or(DEFAULT_STRETCH_MODE);
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
expansion_mode =
|
||||||
|
AudioStream::ParseExpansionMode(
|
||||||
|
si.GetStringValue(section, "ExpansionMode", AudioStream::GetExpansionModeName(DEFAULT_EXPANSION_MODE)).c_str())
|
||||||
|
.value_or(DEFAULT_EXPANSION_MODE);
|
||||||
|
#else
|
||||||
|
expansion_mode = AudioExpansionMode::Disabled;
|
||||||
|
#endif
|
||||||
|
output_latency_ms = static_cast<u16>(std::min<u32>(
|
||||||
|
si.GetUIntValue(section, "OutputLatencyMS", DEFAULT_OUTPUT_LATENCY_MS), std::numeric_limits<u16>::max()));
|
||||||
|
buffer_ms = static_cast<u16>(
|
||||||
|
std::min<u32>(si.GetUIntValue(section, "BufferMS", DEFAULT_BUFFER_MS), std::numeric_limits<u16>::max()));
|
||||||
|
|
||||||
|
stretch_sequence_length_ms =
|
||||||
|
static_cast<u16>(std::min<u32>(si.GetUIntValue(section, "StretchSequenceLengthMS", DEFAULT_STRETCH_SEQUENCE_LENGTH),
|
||||||
|
std::numeric_limits<u16>::max()));
|
||||||
|
stretch_seekwindow_ms = static_cast<u16>(std::min<u32>(
|
||||||
|
si.GetUIntValue(section, "StretchSeekWindowMS", DEFAULT_STRETCH_SEEKWINDOW), std::numeric_limits<u16>::max()));
|
||||||
|
stretch_overlap_ms = static_cast<u16>(std::min<u32>(
|
||||||
|
si.GetUIntValue(section, "StretchOverlapMS", DEFAULT_STRETCH_OVERLAP), std::numeric_limits<u16>::max()));
|
||||||
|
stretch_use_quickseek = si.GetBoolValue(section, "StretchUseQuickSeek", DEFAULT_STRETCH_USE_QUICKSEEK);
|
||||||
|
stretch_use_aa_filter = si.GetBoolValue(section, "StretchUseAAFilter", DEFAULT_STRETCH_USE_AA_FILTER);
|
||||||
|
|
||||||
|
expand_block_size = static_cast<u16>(std::min<u32>(
|
||||||
|
si.GetUIntValue(section, "ExpandBlockSize", DEFAULT_EXPAND_BLOCK_SIZE), std::numeric_limits<u16>::max()));
|
||||||
|
expand_block_size = std::clamp<u16>(
|
||||||
|
Common::IsPow2(expand_block_size) ? expand_block_size : Common::NextPow2(expand_block_size), 128, 8192);
|
||||||
|
expand_circular_wrap =
|
||||||
|
std::clamp(si.GetFloatValue(section, "ExpandCircularWrap", DEFAULT_EXPAND_CIRCULAR_WRAP), 0.0f, 360.0f);
|
||||||
|
expand_shift = std::clamp(si.GetFloatValue(section, "ExpandShift", DEFAULT_EXPAND_SHIFT), -1.0f, 1.0f);
|
||||||
|
expand_depth = std::clamp(si.GetFloatValue(section, "ExpandDepth", DEFAULT_EXPAND_DEPTH), 0.0f, 5.0f);
|
||||||
|
expand_focus = std::clamp(si.GetFloatValue(section, "ExpandFocus", DEFAULT_EXPAND_FOCUS), -1.0f, 1.0f);
|
||||||
|
expand_center_image =
|
||||||
|
std::clamp(si.GetFloatValue(section, "ExpandCenterImage", DEFAULT_EXPAND_CENTER_IMAGE), 0.0f, 1.0f);
|
||||||
|
expand_front_separation =
|
||||||
|
std::clamp(si.GetFloatValue(section, "ExpandFrontSeparation", DEFAULT_EXPAND_FRONT_SEPARATION), 0.0f, 10.0f);
|
||||||
|
expand_rear_separation =
|
||||||
|
std::clamp(si.GetFloatValue(section, "ExpandRearSeparation", DEFAULT_EXPAND_REAR_SEPARATION), 0.0f, 10.0f);
|
||||||
|
expand_low_cutoff =
|
||||||
|
static_cast<u8>(std::min<u32>(si.GetUIntValue(section, "ExpandLowCutoff", DEFAULT_EXPAND_LOW_CUTOFF), 100));
|
||||||
|
expand_high_cutoff =
|
||||||
|
static_cast<u8>(std::min<u32>(si.GetUIntValue(section, "ExpandHighCutoff", DEFAULT_EXPAND_HIGH_CUTOFF), 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioStreamParameters::Save(SettingsInterface& si, const char* section) const
|
||||||
|
{
|
||||||
|
si.SetStringValue(section, "StretchMode", AudioStream::GetStretchModeName(stretch_mode));
|
||||||
|
si.SetStringValue(section, "ExpansionMode", AudioStream::GetExpansionModeName(expansion_mode));
|
||||||
|
si.SetUIntValue(section, "BufferMS", buffer_ms);
|
||||||
|
si.SetUIntValue(section, "OutputLatencyMS", output_latency_ms);
|
||||||
|
|
||||||
|
si.SetUIntValue(section, "StretchSequenceLengthMS", stretch_sequence_length_ms);
|
||||||
|
si.SetUIntValue(section, "StretchSeekWindowMS", stretch_seekwindow_ms);
|
||||||
|
si.SetUIntValue(section, "StretchOverlapMS", stretch_overlap_ms);
|
||||||
|
si.SetBoolValue(section, "StretchUseQuickSeek", stretch_use_quickseek);
|
||||||
|
si.SetBoolValue(section, "StretchUseAAFilter", stretch_use_aa_filter);
|
||||||
|
|
||||||
|
si.SetUIntValue(section, "ExpandBlockSize", expand_block_size);
|
||||||
|
si.SetFloatValue(section, "ExpandCircularWrap", expand_circular_wrap);
|
||||||
|
si.SetFloatValue(section, "ExpandShift", expand_shift);
|
||||||
|
si.SetFloatValue(section, "ExpandDepth", expand_depth);
|
||||||
|
si.SetFloatValue(section, "ExpandFocus", expand_focus);
|
||||||
|
si.SetFloatValue(section, "ExpandCenterImage", expand_center_image);
|
||||||
|
si.SetFloatValue(section, "ExpandFrontSeparation", expand_front_separation);
|
||||||
|
si.SetFloatValue(section, "ExpandRearSeparation", expand_rear_separation);
|
||||||
|
si.SetUIntValue(section, "ExpandLowCutoff", expand_low_cutoff);
|
||||||
|
si.SetUIntValue(section, "ExpandHighCutoff", expand_high_cutoff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioStreamParameters::Clear(SettingsInterface& si, const char* section)
|
||||||
|
{
|
||||||
|
si.DeleteValue(section, "StretchMode");
|
||||||
|
si.DeleteValue(section, "ExpansionMode");
|
||||||
|
si.DeleteValue(section, "BufferMS");
|
||||||
|
si.DeleteValue(section, "OutputLatencyMS");
|
||||||
|
|
||||||
|
si.DeleteValue(section, "StretchSequenceLengthMS");
|
||||||
|
si.DeleteValue(section, "StretchSeekWindowMS");
|
||||||
|
si.DeleteValue(section, "StretchOverlapMS");
|
||||||
|
si.DeleteValue(section, "StretchUseQuickSeek");
|
||||||
|
si.DeleteValue(section, "StretchUseAAFilter");
|
||||||
|
|
||||||
|
si.DeleteValue(section, "ExpandBlockSize");
|
||||||
|
si.DeleteValue(section, "ExpandCircularWrap");
|
||||||
|
si.DeleteValue(section, "ExpandShift");
|
||||||
|
si.DeleteValue(section, "ExpandDepth");
|
||||||
|
si.DeleteValue(section, "ExpandFocus");
|
||||||
|
si.DeleteValue(section, "ExpandCenterImage");
|
||||||
|
si.DeleteValue(section, "ExpandFrontSeparation");
|
||||||
|
si.DeleteValue(section, "ExpandRearSeparation");
|
||||||
|
si.DeleteValue(section, "ExpandLowCutoff");
|
||||||
|
si.DeleteValue(section, "ExpandHighCutoff");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioStreamParameters::operator!=(const AudioStreamParameters& rhs) const
|
||||||
|
{
|
||||||
|
return (std::memcmp(this, &rhs, sizeof(*this)) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioStreamParameters::operator==(const AudioStreamParameters& rhs) const
|
||||||
|
{
|
||||||
|
return (std::memcmp(this, &rhs, sizeof(*this)) == 0);
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -15,10 +17,30 @@
|
||||||
#pragma warning(disable : 4324) // warning C4324: structure was padded due to alignment specifier
|
#pragma warning(disable : 4324) // warning C4324: structure was padded due to alignment specifier
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
class Error;
|
||||||
|
class SettingsInterface;
|
||||||
|
|
||||||
|
class FreeSurroundDecoder;
|
||||||
namespace soundtouch {
|
namespace soundtouch {
|
||||||
class SoundTouch;
|
class SoundTouch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class AudioBackend : u8
|
||||||
|
{
|
||||||
|
Null,
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
Cubeb,
|
||||||
|
SDL,
|
||||||
|
#else
|
||||||
|
AAudio,
|
||||||
|
OpenSLES,
|
||||||
|
#endif
|
||||||
|
#ifdef _WIN32
|
||||||
|
XAudio2,
|
||||||
|
#endif
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
enum class AudioStretchMode : u8
|
enum class AudioStretchMode : u8
|
||||||
{
|
{
|
||||||
Off,
|
Off,
|
||||||
|
@ -27,16 +49,92 @@ enum class AudioStretchMode : u8
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class AudioExpansionMode : u8
|
||||||
|
{
|
||||||
|
Disabled,
|
||||||
|
StereoLFE,
|
||||||
|
Quadraphonic,
|
||||||
|
QuadraphonicLFE,
|
||||||
|
Surround51,
|
||||||
|
Surround71,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioStreamParameters
|
||||||
|
{
|
||||||
|
AudioStretchMode stretch_mode = DEFAULT_STRETCH_MODE;
|
||||||
|
AudioExpansionMode expansion_mode = DEFAULT_EXPANSION_MODE;
|
||||||
|
u16 buffer_ms = DEFAULT_BUFFER_MS;
|
||||||
|
u16 output_latency_ms = DEFAULT_OUTPUT_LATENCY_MS;
|
||||||
|
|
||||||
|
u16 stretch_sequence_length_ms = DEFAULT_STRETCH_SEQUENCE_LENGTH;
|
||||||
|
u16 stretch_seekwindow_ms = DEFAULT_STRETCH_SEEKWINDOW;
|
||||||
|
u16 stretch_overlap_ms = DEFAULT_STRETCH_OVERLAP;
|
||||||
|
bool stretch_use_quickseek = DEFAULT_STRETCH_USE_QUICKSEEK;
|
||||||
|
bool stretch_use_aa_filter = DEFAULT_STRETCH_USE_AA_FILTER;
|
||||||
|
|
||||||
|
float expand_circular_wrap = DEFAULT_EXPAND_CIRCULAR_WRAP;
|
||||||
|
float expand_shift = DEFAULT_EXPAND_SHIFT;
|
||||||
|
float expand_depth = DEFAULT_EXPAND_DEPTH;
|
||||||
|
float expand_focus = DEFAULT_EXPAND_FOCUS;
|
||||||
|
float expand_center_image = DEFAULT_EXPAND_CENTER_IMAGE;
|
||||||
|
float expand_front_separation = DEFAULT_EXPAND_FRONT_SEPARATION;
|
||||||
|
float expand_rear_separation = DEFAULT_EXPAND_REAR_SEPARATION;
|
||||||
|
u16 expand_block_size = DEFAULT_EXPAND_BLOCK_SIZE;
|
||||||
|
u8 expand_low_cutoff = DEFAULT_EXPAND_LOW_CUTOFF;
|
||||||
|
u8 expand_high_cutoff = DEFAULT_EXPAND_HIGH_CUTOFF;
|
||||||
|
|
||||||
|
static constexpr AudioStretchMode DEFAULT_STRETCH_MODE = AudioStretchMode::TimeStretch;
|
||||||
|
static constexpr AudioExpansionMode DEFAULT_EXPANSION_MODE = AudioExpansionMode::Disabled;
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
static constexpr u16 DEFAULT_BUFFER_MS = 50;
|
||||||
|
static constexpr u16 DEFAULT_OUTPUT_LATENCY_MS = 20;
|
||||||
|
#else
|
||||||
|
static constexpr u16 DEFAULT_BUFFER_MS = 100;
|
||||||
|
static constexpr u16 DEFAULT_OUTPUT_LATENCY_MS = 20;
|
||||||
|
#endif
|
||||||
|
static constexpr u16 DEFAULT_EXPAND_BLOCK_SIZE = 1024;
|
||||||
|
static constexpr float DEFAULT_EXPAND_CIRCULAR_WRAP = 90.0f;
|
||||||
|
static constexpr float DEFAULT_EXPAND_SHIFT = 0.0f;
|
||||||
|
static constexpr float DEFAULT_EXPAND_DEPTH = 1.0f;
|
||||||
|
static constexpr float DEFAULT_EXPAND_FOCUS = 0.0f;
|
||||||
|
static constexpr float DEFAULT_EXPAND_CENTER_IMAGE = 1.0f;
|
||||||
|
static constexpr float DEFAULT_EXPAND_FRONT_SEPARATION = 1.0f;
|
||||||
|
static constexpr float DEFAULT_EXPAND_REAR_SEPARATION = 1.0f;
|
||||||
|
static constexpr u8 DEFAULT_EXPAND_LOW_CUTOFF = 40;
|
||||||
|
static constexpr u8 DEFAULT_EXPAND_HIGH_CUTOFF = 90;
|
||||||
|
|
||||||
|
static constexpr u16 DEFAULT_STRETCH_SEQUENCE_LENGTH = 30;
|
||||||
|
static constexpr u16 DEFAULT_STRETCH_SEEKWINDOW = 20;
|
||||||
|
static constexpr u16 DEFAULT_STRETCH_OVERLAP = 10;
|
||||||
|
|
||||||
|
static constexpr bool DEFAULT_STRETCH_USE_QUICKSEEK = false;
|
||||||
|
static constexpr bool DEFAULT_STRETCH_USE_AA_FILTER = false;
|
||||||
|
|
||||||
|
void Load(SettingsInterface& si, const char* section);
|
||||||
|
void Save(SettingsInterface& si, const char* section) const;
|
||||||
|
void Clear(SettingsInterface& si, const char* section);
|
||||||
|
|
||||||
|
bool operator==(const AudioStreamParameters& rhs) const;
|
||||||
|
bool operator!=(const AudioStreamParameters& rhs) const;
|
||||||
|
};
|
||||||
|
|
||||||
class AudioStream
|
class AudioStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using SampleType = s16;
|
using SampleType = s16;
|
||||||
|
|
||||||
enum : u32
|
static constexpr u32 NUM_INPUT_CHANNELS = 2;
|
||||||
{
|
static constexpr u32 MAX_OUTPUT_CHANNELS = 8;
|
||||||
CHUNK_SIZE = 64,
|
static constexpr u32 CHUNK_SIZE = 64;
|
||||||
MAX_CHANNELS = 2
|
static constexpr u32 MIN_EXPANSION_BLOCK_SIZE = 256;
|
||||||
};
|
static constexpr u32 MAX_EXPANSION_BLOCK_SIZE = 4096;
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
static constexpr AudioBackend DEFAULT_BACKEND = AudioBackend::Cubeb;
|
||||||
|
#else
|
||||||
|
static constexpr AudioBackend DEFAULT_BACKEND = AudioBackend::AAudio;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~AudioStream();
|
virtual ~AudioStream();
|
||||||
|
@ -45,12 +143,21 @@ public:
|
||||||
static u32 GetBufferSizeForMS(u32 sample_rate, u32 ms);
|
static u32 GetBufferSizeForMS(u32 sample_rate, u32 ms);
|
||||||
static u32 GetMSForBufferSize(u32 sample_rate, u32 buffer_size);
|
static u32 GetMSForBufferSize(u32 sample_rate, u32 buffer_size);
|
||||||
|
|
||||||
|
static std::optional<AudioBackend> ParseBackendName(const char* str);
|
||||||
|
static const char* GetBackendName(AudioBackend backend);
|
||||||
|
static const char* GetBackendDisplayName(AudioBackend backend);
|
||||||
|
|
||||||
|
static const char* GetExpansionModeName(AudioExpansionMode mode);
|
||||||
|
static const char* GetExpansionModeDisplayName(AudioExpansionMode mode);
|
||||||
|
static std::optional<AudioExpansionMode> ParseExpansionMode(const char* name);
|
||||||
|
|
||||||
static const char* GetStretchModeName(AudioStretchMode mode);
|
static const char* GetStretchModeName(AudioStretchMode mode);
|
||||||
static const char* GetStretchModeDisplayName(AudioStretchMode mode);
|
static const char* GetStretchModeDisplayName(AudioStretchMode mode);
|
||||||
static std::optional<AudioStretchMode> ParseStretchMode(const char* name);
|
static std::optional<AudioStretchMode> ParseStretchMode(const char* name);
|
||||||
|
|
||||||
ALWAYS_INLINE u32 GetSampleRate() const { return m_sample_rate; }
|
ALWAYS_INLINE u32 GetSampleRate() const { return m_sample_rate; }
|
||||||
ALWAYS_INLINE u32 GetChannels() const { return m_channels; }
|
ALWAYS_INLINE u32 GetInternalChannels() const { return m_internal_channels; }
|
||||||
|
ALWAYS_INLINE u32 GetOutputChannels() const { return m_internal_channels; }
|
||||||
ALWAYS_INLINE u32 GetBufferSize() const { return m_buffer_size; }
|
ALWAYS_INLINE u32 GetBufferSize() const { return m_buffer_size; }
|
||||||
ALWAYS_INLINE u32 GetTargetBufferSize() const { return m_target_buffer_size; }
|
ALWAYS_INLINE u32 GetTargetBufferSize() const { return m_target_buffer_size; }
|
||||||
ALWAYS_INLINE u32 GetOutputVolume() const { return m_volume; }
|
ALWAYS_INLINE u32 GetOutputVolume() const { return m_volume; }
|
||||||
|
@ -77,57 +184,74 @@ public:
|
||||||
|
|
||||||
void SetStretchMode(AudioStretchMode mode);
|
void SetStretchMode(AudioStretchMode mode);
|
||||||
|
|
||||||
static std::unique_ptr<AudioStream> CreateNullStream(u32 sample_rate, u32 channels, u32 buffer_ms);
|
static std::unique_ptr<AudioStream> CreateStream(AudioBackend backend, u32 sample_rate,
|
||||||
|
const AudioStreamParameters& parameters, Error* error = nullptr);
|
||||||
|
static std::unique_ptr<AudioStream> CreateNullStream(u32 sample_rate, u32 buffer_ms);
|
||||||
|
|
||||||
#ifdef ENABLE_CUBEB
|
#ifndef __ANDROID__
|
||||||
static std::unique_ptr<AudioStream> CreateCubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms,
|
|
||||||
u32 latency_ms, AudioStretchMode stretch);
|
|
||||||
static std::vector<std::string> GetCubebDriverNames();
|
static std::vector<std::string> GetCubebDriverNames();
|
||||||
static std::vector<std::pair<std::string, std::string>> GetCubebOutputDevices(const char* driver);
|
static std::vector<std::pair<std::string, std::string>> GetCubebOutputDevices(const char* driver);
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_SDL2
|
|
||||||
static std::unique_ptr<AudioStream> CreateSDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms,
|
|
||||||
AudioStretchMode stretch);
|
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
|
||||||
static std::unique_ptr<AudioStream> CreateXAudio2Stream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms,
|
|
||||||
AudioStretchMode stretch);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
|
enum ReadChannel : u8
|
||||||
void BaseInitialize();
|
{
|
||||||
|
READ_CHANNEL_FRONT_LEFT,
|
||||||
|
READ_CHANNEL_FRONT_CENTER,
|
||||||
|
READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_SIDE_LEFT,
|
||||||
|
READ_CHANNEL_SIDE_RIGHT,
|
||||||
|
READ_CHANNEL_REAR_LEFT,
|
||||||
|
READ_CHANNEL_REAR_RIGHT,
|
||||||
|
READ_CHANNEL_LFE,
|
||||||
|
READ_CHANNEL_NONE
|
||||||
|
};
|
||||||
|
|
||||||
void ReadFrames(s16* samples, u32 num_frames);
|
using SampleReader = void (*)(SampleType* dest, const SampleType* src, u32 num_frames);
|
||||||
void ApplyVolume(s16* samples, u32 num_frames);
|
|
||||||
|
AudioStream(u32 sample_rate, const AudioStreamParameters& parameters);
|
||||||
|
void BaseInitialize(SampleReader sample_reader);
|
||||||
|
|
||||||
|
void ReadFrames(SampleType* samples, u32 num_frames);
|
||||||
|
|
||||||
|
template<AudioExpansionMode mode, ReadChannel c0 = READ_CHANNEL_NONE, ReadChannel c1 = READ_CHANNEL_NONE,
|
||||||
|
ReadChannel c2 = READ_CHANNEL_NONE, ReadChannel c3 = READ_CHANNEL_NONE, ReadChannel c4 = READ_CHANNEL_NONE,
|
||||||
|
ReadChannel c5 = READ_CHANNEL_NONE, ReadChannel c6 = READ_CHANNEL_NONE, ReadChannel c7 = READ_CHANNEL_NONE>
|
||||||
|
static void SampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames);
|
||||||
|
static void StereoSampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames);
|
||||||
|
|
||||||
|
void ApplyVolume(SampleType* samples, u32 num_samples);
|
||||||
|
|
||||||
u32 m_sample_rate = 0;
|
u32 m_sample_rate = 0;
|
||||||
u32 m_channels = 0;
|
|
||||||
u32 m_buffer_ms = 0;
|
|
||||||
u32 m_volume = 0;
|
u32 m_volume = 0;
|
||||||
|
AudioStreamParameters m_parameters;
|
||||||
AudioStretchMode m_stretch_mode = AudioStretchMode::Off;
|
u8 m_internal_channels = 0;
|
||||||
|
u8 m_output_channels = 0;
|
||||||
bool m_stretch_inactive = false;
|
bool m_stretch_inactive = false;
|
||||||
bool m_filling = false;
|
bool m_filling = false;
|
||||||
bool m_paused = false;
|
bool m_paused = false;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum : u32
|
static constexpr u32 AVERAGING_BUFFER_SIZE = 256;
|
||||||
{
|
static constexpr u32 AVERAGING_WINDOW = 50;
|
||||||
AVERAGING_BUFFER_SIZE = 256,
|
static constexpr u32 STRETCH_RESET_THRESHOLD = 5;
|
||||||
AVERAGING_WINDOW = 50,
|
static constexpr u32 TARGET_IPS = 691;
|
||||||
STRETCH_RESET_THRESHOLD = 5,
|
|
||||||
TARGET_IPS = 691,
|
ALWAYS_INLINE bool IsExpansionEnabled() const { return m_parameters.expansion_mode != AudioExpansionMode::Disabled; }
|
||||||
};
|
ALWAYS_INLINE bool IsStretchEnabled() const { return m_parameters.stretch_mode != AudioStretchMode::Off; }
|
||||||
|
|
||||||
void AllocateBuffer();
|
void AllocateBuffer();
|
||||||
void DestroyBuffer();
|
void DestroyBuffer();
|
||||||
|
|
||||||
void InternalWriteFrames(s32* bData, u32 nFrames);
|
void InternalWriteFrames(SampleType* samples, u32 num_frames);
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
void ExpandAllocate();
|
||||||
|
#endif
|
||||||
|
|
||||||
void StretchAllocate();
|
void StretchAllocate();
|
||||||
void StretchDestroy();
|
void StretchDestroy();
|
||||||
void StretchWrite();
|
void StretchWriteBlock(const float* block);
|
||||||
void StretchUnderrun();
|
void StretchUnderrun();
|
||||||
void StretchOverrun();
|
void StretchOverrun();
|
||||||
|
|
||||||
|
@ -135,7 +259,8 @@ private:
|
||||||
void UpdateStretchTempo();
|
void UpdateStretchTempo();
|
||||||
|
|
||||||
u32 m_buffer_size = 0;
|
u32 m_buffer_size = 0;
|
||||||
std::unique_ptr<s32[]> m_buffer;
|
std::unique_ptr<s16[]> m_buffer;
|
||||||
|
SampleReader m_sample_reader = nullptr;
|
||||||
|
|
||||||
std::atomic<u32> m_rpos{0};
|
std::atomic<u32> m_rpos{0};
|
||||||
std::atomic<u32> m_wpos{0};
|
std::atomic<u32> m_wpos{0};
|
||||||
|
@ -156,12 +281,97 @@ private:
|
||||||
std::array<float, AVERAGING_BUFFER_SIZE> m_average_fullness = {};
|
std::array<float, AVERAGING_BUFFER_SIZE> m_average_fullness = {};
|
||||||
|
|
||||||
// temporary staging buffer, used for timestretching
|
// temporary staging buffer, used for timestretching
|
||||||
alignas(16) std::array<s32, CHUNK_SIZE> m_staging_buffer;
|
std::unique_ptr<s16[]> m_staging_buffer;
|
||||||
|
|
||||||
// float buffer, soundtouch only accepts float samples as input
|
// float buffer, soundtouch only accepts float samples as input
|
||||||
alignas(16) std::array<float, CHUNK_SIZE * MAX_CHANNELS> m_float_buffer;
|
std::unique_ptr<float[]> m_float_buffer;
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
std::unique_ptr<FreeSurroundDecoder> m_expander;
|
||||||
|
|
||||||
|
// block buffer for expansion
|
||||||
|
std::unique_ptr<float[]> m_expand_buffer;
|
||||||
|
float* m_expand_output_buffer = nullptr;
|
||||||
|
u32 m_expand_buffer_pos = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
static std::unique_ptr<AudioStream> CreateCubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters,
|
||||||
|
Error* error);
|
||||||
|
static std::unique_ptr<AudioStream> CreateSDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters,
|
||||||
|
Error* error);
|
||||||
|
#endif
|
||||||
|
#ifdef _WIN32
|
||||||
|
static std::unique_ptr<AudioStream> CreateXAudio2Stream(u32 sample_rate, const AudioStreamParameters& parameters,
|
||||||
|
Error* error);
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<AudioExpansionMode mode, AudioStream::ReadChannel c0, AudioStream::ReadChannel c1, AudioStream::ReadChannel c2,
|
||||||
|
AudioStream::ReadChannel c3, AudioStream::ReadChannel c4, AudioStream::ReadChannel c5,
|
||||||
|
AudioStream::ReadChannel c6, AudioStream::ReadChannel c7>
|
||||||
|
void AudioStream::SampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames)
|
||||||
|
{
|
||||||
|
static_assert(READ_CHANNEL_NONE == MAX_OUTPUT_CHANNELS);
|
||||||
|
static constexpr const std::array<std::pair<std::array<s8, MAX_OUTPUT_CHANNELS>, u8>,
|
||||||
|
static_cast<size_t>(AudioExpansionMode::Count)>
|
||||||
|
luts = {{
|
||||||
|
// FL FC FR SL SR RL RR LFE
|
||||||
|
{{0, -1, 1, -1, -1, -1, -1, -1}, 2}, // Disabled
|
||||||
|
{{0, -1, 1, -1, -1, -1, -1, 2}, 3}, // StereoLFE
|
||||||
|
{{0, -1, 1, -1, -1, 2, 3, -1}, 5}, // Quadraphonic
|
||||||
|
{{0, -1, 2, -1, -1, 2, 3, 4}, 5}, // QuadraphonicLFE
|
||||||
|
{{0, 1, 2, -1, -1, 3, 4, 5}, 6}, // Surround51
|
||||||
|
{{0, 1, 2, 3, 4, 5, 6, 7}, 8}, // Surround71
|
||||||
|
}};
|
||||||
|
constexpr const auto& lut = luts[static_cast<size_t>(mode)].first;
|
||||||
|
for (u32 i = 0; i < num_frames; i++)
|
||||||
|
{
|
||||||
|
if constexpr (c0 != READ_CHANNEL_NONE)
|
||||||
|
{
|
||||||
|
static_assert(lut[c0] >= 0 && lut[c0] < MAX_OUTPUT_CHANNELS);
|
||||||
|
*(dest++) = src[lut[c0]];
|
||||||
|
}
|
||||||
|
if constexpr (c1 != READ_CHANNEL_NONE)
|
||||||
|
{
|
||||||
|
static_assert(lut[c1] >= 0 && lut[c1] < MAX_OUTPUT_CHANNELS);
|
||||||
|
*(dest++) = src[lut[c1]];
|
||||||
|
}
|
||||||
|
if constexpr (c2 != READ_CHANNEL_NONE)
|
||||||
|
{
|
||||||
|
static_assert(lut[c2] >= 0 && lut[c2] < MAX_OUTPUT_CHANNELS);
|
||||||
|
*(dest++) = src[lut[c2]];
|
||||||
|
}
|
||||||
|
if constexpr (c3 != READ_CHANNEL_NONE)
|
||||||
|
{
|
||||||
|
static_assert(lut[c3] >= 0 && lut[c3] < MAX_OUTPUT_CHANNELS);
|
||||||
|
*(dest++) = src[lut[c3]];
|
||||||
|
}
|
||||||
|
if constexpr (c4 != READ_CHANNEL_NONE)
|
||||||
|
{
|
||||||
|
static_assert(lut[c4] >= 0 && lut[c4] < MAX_OUTPUT_CHANNELS);
|
||||||
|
*(dest++) = src[lut[c4]];
|
||||||
|
}
|
||||||
|
if constexpr (c5 != READ_CHANNEL_NONE)
|
||||||
|
{
|
||||||
|
static_assert(lut[c5] >= 0 && lut[c5] < MAX_OUTPUT_CHANNELS);
|
||||||
|
*(dest++) = src[lut[c5]];
|
||||||
|
}
|
||||||
|
if constexpr (c6 != READ_CHANNEL_NONE)
|
||||||
|
{
|
||||||
|
static_assert(lut[c6] >= 0 && lut[c6] < MAX_OUTPUT_CHANNELS);
|
||||||
|
*(dest++) = src[lut[c6]];
|
||||||
|
}
|
||||||
|
if constexpr (c7 != READ_CHANNEL_NONE)
|
||||||
|
{
|
||||||
|
static_assert(lut[c7] >= 0 && lut[c7] < MAX_OUTPUT_CHANNELS);
|
||||||
|
*(dest++) = src[lut[c7]];
|
||||||
|
}
|
||||||
|
|
||||||
|
src += luts[static_cast<size_t>(mode)].second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#pragma warning(pop)
|
#pragma warning(pop)
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
|
@ -7,6 +7,7 @@
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/error.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/scoped_guard.h"
|
#include "common/scoped_guard.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
@ -26,13 +27,13 @@ namespace {
|
||||||
class CubebAudioStream : public AudioStream
|
class CubebAudioStream : public AudioStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
|
CubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters);
|
||||||
~CubebAudioStream();
|
~CubebAudioStream();
|
||||||
|
|
||||||
void SetPaused(bool paused) override;
|
void SetPaused(bool paused) override;
|
||||||
void SetOutputVolume(u32 volume) override;
|
void SetOutputVolume(u32 volume) override;
|
||||||
|
|
||||||
bool Initialize(u32 latency_ms);
|
bool Initialize(Error* error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void LogCallback(const char* fmt, ...);
|
static void LogCallback(const char* fmt, ...);
|
||||||
|
@ -51,8 +52,34 @@ private:
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
CubebAudioStream::CubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch)
|
static TinyString GetCubebErrorString(int rv)
|
||||||
: AudioStream(sample_rate, channels, buffer_ms, stretch)
|
{
|
||||||
|
TinyString ret;
|
||||||
|
switch (rv)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
#define C(e) case e: ret.assign(#e); break
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
C(CUBEB_OK);
|
||||||
|
C(CUBEB_ERROR);
|
||||||
|
C(CUBEB_ERROR_INVALID_FORMAT);
|
||||||
|
C(CUBEB_ERROR_INVALID_PARAMETER);
|
||||||
|
C(CUBEB_ERROR_NOT_SUPPORTED);
|
||||||
|
C(CUBEB_ERROR_DEVICE_UNAVAILABLE);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "CUBEB_ERROR_UNKNOWN";
|
||||||
|
|
||||||
|
#undef C
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.append_format(" ({})", rv);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
CubebAudioStream::CubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters)
|
||||||
|
: AudioStream(sample_rate, parameters)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,14 +122,14 @@ void CubebAudioStream::DestroyContextAndStream()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CubebAudioStream::Initialize(u32 latency_ms)
|
bool CubebAudioStream::Initialize(Error* error)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
m_com_initialized_by_us = SUCCEEDED(hr);
|
m_com_initialized_by_us = SUCCEEDED(hr);
|
||||||
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE)
|
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE)
|
||||||
{
|
{
|
||||||
Host::ReportErrorAsync("Error", "Failed to initialize COM for Cubeb");
|
Error::SetHResult(error, "CoInitializeEx() failed: ", hr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -113,45 +140,72 @@ bool CubebAudioStream::Initialize(u32 latency_ms)
|
||||||
cubeb_init(&m_context, "DuckStation", g_settings.audio_driver.empty() ? nullptr : g_settings.audio_driver.c_str());
|
cubeb_init(&m_context, "DuckStation", g_settings.audio_driver.empty() ? nullptr : g_settings.audio_driver.c_str());
|
||||||
if (rv != CUBEB_OK)
|
if (rv != CUBEB_OK)
|
||||||
{
|
{
|
||||||
Host::ReportFormattedErrorAsync("Error", "Could not initialize cubeb context: %d", rv);
|
Error::SetStringFmt(error, "Could not initialize cubeb context: {}", GetCubebErrorString(rv));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr const std::array<std::pair<cubeb_channel_layout, SampleReader>,
|
||||||
|
static_cast<size_t>(AudioExpansionMode::Count)>
|
||||||
|
channel_setups = {{
|
||||||
|
// Disabled
|
||||||
|
{CUBEB_LAYOUT_STEREO, StereoSampleReaderImpl},
|
||||||
|
// StereoLFE
|
||||||
|
{CUBEB_LAYOUT_STEREO_LFE, &SampleReaderImpl<AudioExpansionMode::StereoLFE, READ_CHANNEL_FRONT_LEFT,
|
||||||
|
READ_CHANNEL_FRONT_RIGHT, READ_CHANNEL_LFE>},
|
||||||
|
// Quadraphonic
|
||||||
|
{CUBEB_LAYOUT_QUAD, &SampleReaderImpl<AudioExpansionMode::Quadraphonic, READ_CHANNEL_FRONT_LEFT,
|
||||||
|
READ_CHANNEL_FRONT_RIGHT, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
|
||||||
|
// QuadraphonicLFE
|
||||||
|
{CUBEB_LAYOUT_QUAD_LFE,
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::QuadraphonicLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
|
||||||
|
// Surround51
|
||||||
|
{CUBEB_LAYOUT_3F2_LFE_BACK,
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::Surround51, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
|
||||||
|
// Surround71
|
||||||
|
{CUBEB_LAYOUT_3F4_LFE,
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::Surround71, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT,
|
||||||
|
READ_CHANNEL_SIDE_LEFT, READ_CHANNEL_SIDE_RIGHT>},
|
||||||
|
}};
|
||||||
|
|
||||||
cubeb_stream_params params = {};
|
cubeb_stream_params params = {};
|
||||||
params.format = CUBEB_SAMPLE_S16LE;
|
params.format = CUBEB_SAMPLE_S16LE;
|
||||||
params.rate = m_sample_rate;
|
params.rate = m_sample_rate;
|
||||||
params.channels = m_channels;
|
params.channels = m_output_channels;
|
||||||
params.layout = CUBEB_LAYOUT_UNDEFINED;
|
params.layout = channel_setups[static_cast<size_t>(m_parameters.expansion_mode)].first;
|
||||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||||
|
|
||||||
u32 latency_frames = GetBufferSizeForMS(m_sample_rate, (latency_ms == 0) ? m_buffer_ms : latency_ms);
|
u32 latency_frames = GetBufferSizeForMS(
|
||||||
|
m_sample_rate, (m_parameters.output_latency_ms == 0) ? m_parameters.buffer_ms : m_parameters.output_latency_ms);
|
||||||
u32 min_latency_frames = 0;
|
u32 min_latency_frames = 0;
|
||||||
rv = cubeb_get_min_latency(m_context, ¶ms, &min_latency_frames);
|
rv = cubeb_get_min_latency(m_context, ¶ms, &min_latency_frames);
|
||||||
if (rv == CUBEB_ERROR_NOT_SUPPORTED)
|
if (rv == CUBEB_ERROR_NOT_SUPPORTED)
|
||||||
{
|
{
|
||||||
Log_DevPrintf("(Cubeb) Cubeb backend does not support latency queries, using latency of %d ms (%u frames).",
|
Log_DevFmt("Cubeb backend does not support latency queries, using latency of {} ms ({} frames).",
|
||||||
m_buffer_ms, latency_frames);
|
m_parameters.buffer_ms, latency_frames);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (rv != CUBEB_OK)
|
if (rv != CUBEB_OK)
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("(Cubeb) Could not get minimum latency: %d", rv);
|
Error::SetStringFmt(error, "cubeb_get_min_latency() failed: {}", GetCubebErrorString(rv));
|
||||||
DestroyContextAndStream();
|
DestroyContextAndStream();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 minimum_latency_ms = GetMSForBufferSize(m_sample_rate, min_latency_frames);
|
const u32 minimum_latency_ms = GetMSForBufferSize(m_sample_rate, min_latency_frames);
|
||||||
Log_DevPrintf("(Cubeb) Minimum latency: %u ms (%u audio frames)", minimum_latency_ms, min_latency_frames);
|
Log_DevFmt("Minimum latency: {} ms ({} audio frames)", minimum_latency_ms, min_latency_frames);
|
||||||
if (latency_ms == 0)
|
if (m_parameters.output_latency_ms == 0)
|
||||||
{
|
{
|
||||||
// use minimum
|
// use minimum
|
||||||
latency_frames = min_latency_frames;
|
latency_frames = min_latency_frames;
|
||||||
}
|
}
|
||||||
else if (minimum_latency_ms > latency_ms)
|
else if (minimum_latency_ms > m_parameters.output_latency_ms)
|
||||||
{
|
{
|
||||||
Log_WarningPrintf("(Cubeb) Minimum latency is above requested latency: %u vs %u, adjusting to compensate.",
|
Log_WarningFmt("Minimum latency is above requested latency: {} vs {}, adjusting to compensate.",
|
||||||
min_latency_frames, latency_frames);
|
min_latency_frames, latency_frames);
|
||||||
latency_frames = min_latency_frames;
|
latency_frames = min_latency_frames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,8 +225,8 @@ bool CubebAudioStream::Initialize(u32 latency_ms)
|
||||||
const cubeb_device_info& di = devices.device[i];
|
const cubeb_device_info& di = devices.device[i];
|
||||||
if (di.device_id && selected_device_name == di.device_id)
|
if (di.device_id && selected_device_name == di.device_id)
|
||||||
{
|
{
|
||||||
Log_InfoPrintf("Using output device '%s' (%s).", di.device_id,
|
Log_InfoFmt("Using output device '{}' ({}).", di.device_id,
|
||||||
di.friendly_name ? di.friendly_name : di.device_id);
|
di.friendly_name ? di.friendly_name : di.device_id);
|
||||||
selected_device = di.devid;
|
selected_device = di.devid;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -186,11 +240,11 @@ bool CubebAudioStream::Initialize(u32 latency_ms)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log_WarningPrintf("cubeb_enumerate_devices() returned %d, using default device.", rv);
|
Log_WarningFmt("cubeb_enumerate_devices() returned {}, using default device.", GetCubebErrorString(rv));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseInitialize();
|
BaseInitialize(channel_setups[static_cast<size_t>(m_parameters.expansion_mode)].second);
|
||||||
m_volume = 100;
|
m_volume = 100;
|
||||||
m_paused = false;
|
m_paused = false;
|
||||||
|
|
||||||
|
@ -205,7 +259,7 @@ bool CubebAudioStream::Initialize(u32 latency_ms)
|
||||||
|
|
||||||
if (rv != CUBEB_OK)
|
if (rv != CUBEB_OK)
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("(Cubeb) Could not create stream: %d", rv);
|
Error::SetStringFmt(error, "cubeb_stream_init() failed: {}", GetCubebErrorString(rv));
|
||||||
DestroyContextAndStream();
|
DestroyContextAndStream();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -213,7 +267,7 @@ bool CubebAudioStream::Initialize(u32 latency_ms)
|
||||||
rv = cubeb_stream_start(stream);
|
rv = cubeb_stream_start(stream);
|
||||||
if (rv != CUBEB_OK)
|
if (rv != CUBEB_OK)
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("(Cubeb) Could not start stream: %d", rv);
|
Error::SetStringFmt(error, "cubeb_stream_start() failed: {}", GetCubebErrorString(rv));
|
||||||
DestroyContextAndStream();
|
DestroyContextAndStream();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -263,12 +317,11 @@ void CubebAudioStream::SetOutputVolume(u32 volume)
|
||||||
m_volume = volume;
|
m_volume = volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AudioStream> AudioStream::CreateCubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms,
|
std::unique_ptr<AudioStream> AudioStream::CreateCubebAudioStream(u32 sample_rate,
|
||||||
u32 latency_ms, AudioStretchMode stretch)
|
const AudioStreamParameters& parameters, Error* error)
|
||||||
{
|
{
|
||||||
std::unique_ptr<CubebAudioStream> stream(
|
std::unique_ptr<CubebAudioStream> stream = std::make_unique<CubebAudioStream>(sample_rate, parameters);
|
||||||
std::make_unique<CubebAudioStream>(sample_rate, channels, buffer_ms, stretch));
|
if (!stream->Initialize(error))
|
||||||
if (!stream->Initialize(latency_ms))
|
|
||||||
stream.reset();
|
stream.reset();
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
|
@ -602,10 +602,9 @@ static std::array<const char*, static_cast<u32>(InputSourceType::Count)> s_input
|
||||||
"XInput",
|
"XInput",
|
||||||
"RawInput",
|
"RawInput",
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_SDL2
|
#ifndef __ANDROID__
|
||||||
"SDL",
|
"SDL",
|
||||||
#endif
|
#else
|
||||||
#ifdef __ANDROID__
|
|
||||||
"Android",
|
"Android",
|
||||||
#endif
|
#endif
|
||||||
}};
|
}};
|
||||||
|
@ -638,12 +637,10 @@ bool InputManager::GetInputSourceDefaultEnabled(InputSourceType type)
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_SDL2
|
#ifndef __ANDROID__
|
||||||
case InputSourceType::SDL:
|
case InputSourceType::SDL:
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#else
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
case InputSourceType::Android:
|
case InputSourceType::Android:
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
|
@ -1953,10 +1950,9 @@ void InputManager::ReloadSources(SettingsInterface& si, std::unique_lock<std::mu
|
||||||
UpdateInputSourceState(si, settings_lock, InputSourceType::XInput, &InputSource::CreateXInputSource);
|
UpdateInputSourceState(si, settings_lock, InputSourceType::XInput, &InputSource::CreateXInputSource);
|
||||||
UpdateInputSourceState(si, settings_lock, InputSourceType::RawInput, &InputSource::CreateWin32RawInputSource);
|
UpdateInputSourceState(si, settings_lock, InputSourceType::RawInput, &InputSource::CreateWin32RawInputSource);
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_SDL2
|
#ifndef __ANDROID__
|
||||||
UpdateInputSourceState(si, settings_lock, InputSourceType::SDL, &InputSource::CreateSDLSource);
|
UpdateInputSourceState(si, settings_lock, InputSourceType::SDL, &InputSource::CreateSDLSource);
|
||||||
#endif
|
#else
|
||||||
#ifdef __ANDROID__
|
|
||||||
UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource);
|
UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,10 +29,9 @@ enum class InputSourceType : u32
|
||||||
XInput,
|
XInput,
|
||||||
RawInput,
|
RawInput,
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_SDL2
|
#ifndef __ANDROID__
|
||||||
SDL,
|
SDL,
|
||||||
#endif
|
#else
|
||||||
#ifdef __ANDROID__
|
|
||||||
Android,
|
Android,
|
||||||
#endif
|
#endif
|
||||||
Count,
|
Count,
|
||||||
|
|
|
@ -76,10 +76,9 @@ public:
|
||||||
static std::unique_ptr<InputSource> CreateXInputSource();
|
static std::unique_ptr<InputSource> CreateXInputSource();
|
||||||
static std::unique_ptr<InputSource> CreateWin32RawInputSource();
|
static std::unique_ptr<InputSource> CreateWin32RawInputSource();
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_SDL2
|
#ifndef __ANDROID__
|
||||||
static std::unique_ptr<InputSource> CreateSDLSource();
|
static std::unique_ptr<InputSource> CreateSDLSource();
|
||||||
#endif
|
#else
|
||||||
#ifdef __ANDROID__
|
|
||||||
static std::unique_ptr<InputSource> CreateAndroidSource();
|
static std::unique_ptr<InputSource> CreateAndroidSource();
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
|
#include "common/error.h"
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
|
||||||
|
@ -14,13 +15,13 @@ namespace {
|
||||||
class SDLAudioStream final : public AudioStream
|
class SDLAudioStream final : public AudioStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
|
SDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters);
|
||||||
~SDLAudioStream();
|
~SDLAudioStream();
|
||||||
|
|
||||||
void SetPaused(bool paused) override;
|
void SetPaused(bool paused) override;
|
||||||
void SetOutputVolume(u32 volume) override;
|
void SetOutputVolume(u32 volume) override;
|
||||||
|
|
||||||
bool OpenDevice(u32 latency_ms);
|
bool OpenDevice(Error* error);
|
||||||
void CloseDevice();
|
void CloseDevice();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -32,17 +33,16 @@ protected:
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
static bool InitializeSDLAudio()
|
static bool InitializeSDLAudio(Error* error)
|
||||||
{
|
{
|
||||||
static bool initialized = false;
|
static bool initialized = false;
|
||||||
if (initialized)
|
if (initialized)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// May as well keep it alive until the process exits.
|
// May as well keep it alive until the process exits.
|
||||||
const int error = SDL_InitSubSystem(SDL_INIT_AUDIO);
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0)
|
||||||
if (error != 0)
|
|
||||||
{
|
{
|
||||||
Log_ErrorFmt("SDL_InitSubSystem(SDL_INIT_AUDIO) returned {}", error);
|
Error::SetStringFmt(error, "SDL_InitSubSystem(SDL_INIT_AUDIO) failed: {}", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@ static bool InitializeSDLAudio()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDLAudioStream::SDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch)
|
SDLAudioStream::SDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters)
|
||||||
: AudioStream(sample_rate, channels, buffer_ms, stretch)
|
: AudioStream(sample_rate, parameters)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,28 +63,49 @@ SDLAudioStream::~SDLAudioStream()
|
||||||
SDLAudioStream::CloseDevice();
|
SDLAudioStream::CloseDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AudioStream> AudioStream::CreateSDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms,
|
std::unique_ptr<AudioStream> AudioStream::CreateSDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters, Error* error)
|
||||||
u32 latency_ms, AudioStretchMode stretch)
|
|
||||||
{
|
{
|
||||||
if (!InitializeSDLAudio())
|
if (!InitializeSDLAudio(error))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
std::unique_ptr<SDLAudioStream> stream = std::make_unique<SDLAudioStream>(sample_rate, channels, buffer_ms, stretch);
|
std::unique_ptr<SDLAudioStream> stream = std::make_unique<SDLAudioStream>(sample_rate, parameters);
|
||||||
if (!stream->OpenDevice(latency_ms))
|
if (!stream->OpenDevice(error))
|
||||||
stream.reset();
|
stream.reset();
|
||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDLAudioStream::OpenDevice(u32 latency_ms)
|
bool SDLAudioStream::OpenDevice(Error* error)
|
||||||
{
|
{
|
||||||
DebugAssert(!IsOpen());
|
DebugAssert(!IsOpen());
|
||||||
|
|
||||||
|
static constexpr const std::array<SampleReader, static_cast<size_t>(AudioExpansionMode::Count)> sample_readers = {{
|
||||||
|
// Disabled
|
||||||
|
&StereoSampleReaderImpl,
|
||||||
|
// StereoLFE
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::StereoLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_LFE>,
|
||||||
|
// Quadraphonic
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::Quadraphonic, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||||
|
// QuadraphonicLFE
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::QuadraphonicLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||||
|
// Surround51
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::Surround51, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||||
|
// Surround71
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::Surround71, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_SIDE_LEFT, READ_CHANNEL_SIDE_RIGHT,
|
||||||
|
READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||||
|
}};
|
||||||
|
|
||||||
SDL_AudioSpec spec = {};
|
SDL_AudioSpec spec = {};
|
||||||
spec.freq = m_sample_rate;
|
spec.freq = m_sample_rate;
|
||||||
spec.channels = static_cast<Uint8>(m_channels);
|
spec.channels = m_output_channels;
|
||||||
spec.format = AUDIO_S16;
|
spec.format = AUDIO_S16;
|
||||||
spec.samples = static_cast<Uint16>(GetBufferSizeForMS(m_sample_rate, (latency_ms == 0) ? m_buffer_ms : latency_ms));
|
spec.samples = static_cast<Uint16>(GetBufferSizeForMS(
|
||||||
|
m_sample_rate, (m_parameters.output_latency_ms == 0) ? m_parameters.buffer_ms : m_parameters.output_latency_ms));
|
||||||
spec.callback = AudioCallback;
|
spec.callback = AudioCallback;
|
||||||
spec.userdata = static_cast<void*>(this);
|
spec.userdata = static_cast<void*>(this);
|
||||||
|
|
||||||
|
@ -92,13 +113,13 @@ bool SDLAudioStream::OpenDevice(u32 latency_ms)
|
||||||
m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained_spec, SDL_AUDIO_ALLOW_SAMPLES_CHANGE);
|
m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained_spec, SDL_AUDIO_ALLOW_SAMPLES_CHANGE);
|
||||||
if (m_device_id == 0)
|
if (m_device_id == 0)
|
||||||
{
|
{
|
||||||
Log_ErrorFmt("SDL_OpenAudioDevice() failed: {}", SDL_GetError());
|
Error::SetStringFmt(error, "SDL_OpenAudioDevice() failed: {}", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log_DevFmt("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples);
|
Log_DevFmt("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples);
|
||||||
|
|
||||||
BaseInitialize();
|
BaseInitialize(sample_readers[static_cast<size_t>(m_parameters.expansion_mode)]);
|
||||||
m_volume = 100;
|
m_volume = 100;
|
||||||
m_paused = false;
|
m_paused = false;
|
||||||
SDL_PauseAudioDevice(m_device_id, 0);
|
SDL_PauseAudioDevice(m_device_id, 0);
|
||||||
|
@ -124,7 +145,7 @@ void SDLAudioStream::CloseDevice()
|
||||||
void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len)
|
void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len)
|
||||||
{
|
{
|
||||||
SDLAudioStream* const this_ptr = static_cast<SDLAudioStream*>(userdata);
|
SDLAudioStream* const this_ptr = static_cast<SDLAudioStream*>(userdata);
|
||||||
const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_channels;
|
const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_output_channels;
|
||||||
|
|
||||||
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(stream), num_frames);
|
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(stream), num_frames);
|
||||||
this_ptr->ApplyVolume(reinterpret_cast<SampleType*>(stream), num_frames);
|
this_ptr->ApplyVolume(reinterpret_cast<SampleType*>(stream), num_frames);
|
||||||
|
|
|
@ -5,17 +5,16 @@
|
||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
<PreprocessorDefinitions>%(PreprocessorDefinitions);SOUNDTOUCH_FLOAT_SAMPLES;SOUNDTOUCH_ALLOW_SSE;ST_NO_EXCEPTION_HANDLING=1;SHADERC_SHAREDLIB</PreprocessorDefinitions>
|
<PreprocessorDefinitions>%(PreprocessorDefinitions);SOUNDTOUCH_FLOAT_SAMPLES;SOUNDTOUCH_ALLOW_SSE;ST_NO_EXCEPTION_HANDLING=1;SHADERC_SHAREDLIB</PreprocessorDefinitions>
|
||||||
<PreprocessorDefinitions>ENABLE_CUBEB=1;ENABLE_SDL2=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<PreprocessorDefinitions Condition="'$(Platform)'!='ARM64'">%(PreprocessorDefinitions);ENABLE_OPENGL=1;ENABLE_VULKAN=1</PreprocessorDefinitions>
|
<PreprocessorDefinitions Condition="'$(Platform)'!='ARM64'">%(PreprocessorDefinitions);ENABLE_OPENGL=1;ENABLE_VULKAN=1</PreprocessorDefinitions>
|
||||||
<PreprocessorDefinitions Condition="'$(Platform)'=='ARM64'">%(PreprocessorDefinitions);SOUNDTOUCH_USE_NEON</PreprocessorDefinitions>
|
<PreprocessorDefinitions Condition="'$(Platform)'=='ARM64'">%(PreprocessorDefinitions);SOUNDTOUCH_USE_NEON</PreprocessorDefinitions>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\soundtouch\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\d3d12ma\include</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\freesurround\include;$(SolutionDir)dep\kissfft\include;$(SolutionDir)dep\soundtouch\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\d3d12ma\include</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories Condition="'$(Platform)'!='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan\include;$(SolutionDir)dep\glslang</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories Condition="'$(Platform)'!='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan\include;$(SolutionDir)dep\glslang</AdditionalIncludeDirectories>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
|
|
||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
<Link>
|
<Link>
|
||||||
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib</AdditionalDependencies>
|
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib;xaudio2.lib</AdditionalDependencies>
|
||||||
<AdditionalDependencies Condition="'$(Platform)'!='ARM64'">%(AdditionalDependencies);opengl32.lib</AdditionalDependencies>
|
<AdditionalDependencies Condition="'$(Platform)'!='ARM64'">%(AdditionalDependencies);opengl32.lib</AdditionalDependencies>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
|
|
|
@ -241,6 +241,9 @@
|
||||||
<ProjectReference Include="..\..\dep\d3d12ma\d3d12ma.vcxproj">
|
<ProjectReference Include="..\..\dep\d3d12ma\d3d12ma.vcxproj">
|
||||||
<Project>{f351c4d8-594a-4850-b77b-3c1249812cce}</Project>
|
<Project>{f351c4d8-594a-4850-b77b-3c1249812cce}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\..\dep\freesurround\freesurround.vcxproj">
|
||||||
|
<Project>{1b0366e5-6f82-47b4-9fdd-d699c86aa077}</Project>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
|
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
|
||||||
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
|
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#include "util/audio_stream.h"
|
#include "util/audio_stream.h"
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/error.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/windows_headers.h"
|
#include "common/windows_headers.h"
|
||||||
|
|
||||||
|
@ -20,13 +21,13 @@ namespace {
|
||||||
class XAudio2AudioStream final : public AudioStream, private IXAudio2VoiceCallback
|
class XAudio2AudioStream final : public AudioStream, private IXAudio2VoiceCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
XAudio2AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
|
XAudio2AudioStream(u32 sample_rate, const AudioStreamParameters& parameters);
|
||||||
~XAudio2AudioStream();
|
~XAudio2AudioStream();
|
||||||
|
|
||||||
void SetPaused(bool paused) override;
|
void SetPaused(bool paused) override;
|
||||||
void SetOutputVolume(u32 volume) override;
|
void SetOutputVolume(u32 volume) override;
|
||||||
|
|
||||||
bool OpenDevice(u32 latency_ms);
|
bool OpenDevice(Error* error);
|
||||||
void CloseDevice();
|
void CloseDevice();
|
||||||
void EnqueueBuffer();
|
void EnqueueBuffer();
|
||||||
|
|
||||||
|
@ -56,15 +57,13 @@ private:
|
||||||
u32 m_enqueue_buffer_size = 0;
|
u32 m_enqueue_buffer_size = 0;
|
||||||
u32 m_current_buffer = 0;
|
u32 m_current_buffer = 0;
|
||||||
bool m_buffer_enqueued = false;
|
bool m_buffer_enqueued = false;
|
||||||
|
|
||||||
HMODULE m_xaudio2_library = {};
|
|
||||||
bool m_com_initialized_by_us = false;
|
bool m_com_initialized_by_us = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
XAudio2AudioStream::XAudio2AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch)
|
XAudio2AudioStream::XAudio2AudioStream(u32 sample_rate, const AudioStreamParameters& parameters)
|
||||||
: AudioStream(sample_rate, channels, buffer_ms, stretch)
|
: AudioStream(sample_rate, parameters)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,98 +72,110 @@ XAudio2AudioStream::~XAudio2AudioStream()
|
||||||
if (IsOpen())
|
if (IsOpen())
|
||||||
CloseDevice();
|
CloseDevice();
|
||||||
|
|
||||||
if (m_xaudio2_library)
|
|
||||||
FreeLibrary(m_xaudio2_library);
|
|
||||||
|
|
||||||
if (m_com_initialized_by_us)
|
if (m_com_initialized_by_us)
|
||||||
CoUninitialize();
|
CoUninitialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AudioStream> AudioStream::CreateXAudio2Stream(u32 sample_rate, u32 channels, u32 buffer_ms,
|
std::unique_ptr<AudioStream> AudioStream::CreateXAudio2Stream(u32 sample_rate, const AudioStreamParameters& parameters,
|
||||||
u32 latency_ms, AudioStretchMode stretch)
|
Error* error)
|
||||||
{
|
{
|
||||||
std::unique_ptr<XAudio2AudioStream> stream(
|
std::unique_ptr<XAudio2AudioStream> stream(std::make_unique<XAudio2AudioStream>(sample_rate, parameters));
|
||||||
std::make_unique<XAudio2AudioStream>(sample_rate, channels, buffer_ms, stretch));
|
if (!stream->OpenDevice(error))
|
||||||
if (!stream->OpenDevice(latency_ms))
|
|
||||||
stream.reset();
|
stream.reset();
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool XAudio2AudioStream::OpenDevice(u32 latency_ms)
|
bool XAudio2AudioStream::OpenDevice(Error* error)
|
||||||
{
|
{
|
||||||
DebugAssert(!IsOpen());
|
DebugAssert(!IsOpen());
|
||||||
|
|
||||||
m_xaudio2_library = LoadLibraryW(XAUDIO2_DLL_W);
|
if (m_parameters.expansion_mode == AudioExpansionMode::QuadraphonicLFE)
|
||||||
if (!m_xaudio2_library)
|
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("Failed to load '%s', make sure you're using Windows 10", XAUDIO2_DLL_A);
|
Log_ErrorPrint("QuadraphonicLFE is not supported by XAudio2.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using PFNXAUDIO2CREATE =
|
static constexpr const std::array<SampleReader, static_cast<size_t>(AudioExpansionMode::Count)> sample_readers = {{
|
||||||
HRESULT(STDAPICALLTYPE*)(IXAudio2 * *ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor);
|
// Disabled
|
||||||
PFNXAUDIO2CREATE xaudio2_create =
|
&StereoSampleReaderImpl,
|
||||||
reinterpret_cast<PFNXAUDIO2CREATE>(GetProcAddress(m_xaudio2_library, "XAudio2Create"));
|
// StereoLFE
|
||||||
if (!xaudio2_create)
|
&SampleReaderImpl<AudioExpansionMode::StereoLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
return false;
|
READ_CHANNEL_LFE>,
|
||||||
|
// Quadraphonic
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::Quadraphonic, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||||
|
// QuadraphonicLFE
|
||||||
|
nullptr,
|
||||||
|
// Surround51
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::Surround51, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||||
|
// Surround71
|
||||||
|
&SampleReaderImpl<AudioExpansionMode::Surround71, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||||
|
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT,
|
||||||
|
READ_CHANNEL_SIDE_LEFT, READ_CHANNEL_SIDE_RIGHT>,
|
||||||
|
}};
|
||||||
|
|
||||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
m_com_initialized_by_us = SUCCEEDED(hr);
|
m_com_initialized_by_us = SUCCEEDED(hr);
|
||||||
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE && hr != S_FALSE)
|
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE && hr != S_FALSE)
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("Failed to initialize COM");
|
Error::SetHResult(error, "CoInitializeEx() failed: ", hr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = xaudio2_create(m_xaudio.ReleaseAndGetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR);
|
hr = XAudio2Create(m_xaudio.ReleaseAndGetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("XAudio2Create() failed: %08X", hr);
|
Error::SetHResult(error, "XAudio2Create() failed: ", hr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = m_xaudio->CreateMasteringVoice(&m_mastering_voice, m_channels, m_sample_rate, 0, nullptr);
|
hr = m_xaudio->CreateMasteringVoice(&m_mastering_voice, m_output_channels, m_sample_rate, 0, nullptr);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("CreateMasteringVoice() failed: %08X", hr);
|
Error::SetHResult(error, "CreateMasteringVoice() failed: ", hr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: CHANNEL LAYOUT
|
||||||
WAVEFORMATEX wf = {};
|
WAVEFORMATEX wf = {};
|
||||||
wf.cbSize = sizeof(wf);
|
wf.cbSize = sizeof(wf);
|
||||||
wf.nAvgBytesPerSec = m_sample_rate * m_channels * sizeof(s16);
|
wf.nAvgBytesPerSec = m_sample_rate * m_output_channels * sizeof(s16);
|
||||||
wf.nBlockAlign = static_cast<WORD>(sizeof(s16) * m_channels);
|
wf.nBlockAlign = static_cast<WORD>(sizeof(s16) * m_output_channels);
|
||||||
wf.nChannels = static_cast<WORD>(m_channels);
|
wf.nChannels = static_cast<WORD>(m_output_channels);
|
||||||
wf.nSamplesPerSec = m_sample_rate;
|
wf.nSamplesPerSec = m_sample_rate;
|
||||||
wf.wBitsPerSample = sizeof(s16) * 8;
|
wf.wBitsPerSample = sizeof(s16) * 8;
|
||||||
wf.wFormatTag = WAVE_FORMAT_PCM;
|
wf.wFormatTag = WAVE_FORMAT_PCM;
|
||||||
hr = m_xaudio->CreateSourceVoice(&m_source_voice, &wf, 0, 1.0f, this);
|
hr = m_xaudio->CreateSourceVoice(&m_source_voice, &wf, 0, 1.0f, this);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("CreateMasteringVoice() failed: %08X", hr);
|
Error::SetHResult(error, "CreateMasteringVoice() failed: ", hr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = m_source_voice->SetFrequencyRatio(1.0f);
|
hr = m_source_voice->SetFrequencyRatio(1.0f);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("SetFrequencyRatio() failed: %08X", hr);
|
Error::SetHResult(error, "SetFrequencyRatio() failed: ", hr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_enqueue_buffer_size = std::max<u32>(INTERNAL_BUFFER_SIZE, GetBufferSizeForMS(m_sample_rate, latency_ms));
|
m_enqueue_buffer_size =
|
||||||
|
std::max<u32>(INTERNAL_BUFFER_SIZE, GetBufferSizeForMS(m_sample_rate, (m_parameters.output_latency_ms == 0) ?
|
||||||
|
m_parameters.buffer_ms :
|
||||||
|
m_parameters.output_latency_ms));
|
||||||
Log_DevPrintf("Allocating %u buffers of %u frames", NUM_BUFFERS, m_enqueue_buffer_size);
|
Log_DevPrintf("Allocating %u buffers of %u frames", NUM_BUFFERS, m_enqueue_buffer_size);
|
||||||
for (u32 i = 0; i < NUM_BUFFERS; i++)
|
for (u32 i = 0; i < NUM_BUFFERS; i++)
|
||||||
m_enqueue_buffers[i] = std::make_unique<SampleType[]>(m_enqueue_buffer_size * m_channels);
|
m_enqueue_buffers[i] = std::make_unique<SampleType[]>(m_enqueue_buffer_size * m_output_channels);
|
||||||
|
|
||||||
BaseInitialize();
|
BaseInitialize(sample_readers[static_cast<size_t>(m_parameters.expansion_mode)]);
|
||||||
m_volume = 100;
|
m_volume = 100;
|
||||||
m_paused = false;
|
m_paused = false;
|
||||||
|
|
||||||
hr = m_source_voice->Start(0, 0);
|
hr = m_source_voice->Start(0, 0);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("Start() failed: %08X", hr);
|
Error::SetHResult(error, "Start() failed: ", hr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,9 +231,9 @@ void XAudio2AudioStream::EnqueueBuffer()
|
||||||
ReadFrames(samples, m_enqueue_buffer_size);
|
ReadFrames(samples, m_enqueue_buffer_size);
|
||||||
|
|
||||||
const XAUDIO2_BUFFER buf = {
|
const XAUDIO2_BUFFER buf = {
|
||||||
static_cast<UINT32>(0), // flags
|
static_cast<UINT32>(0), // flags
|
||||||
static_cast<UINT32>(sizeof(s16) * m_channels * m_enqueue_buffer_size), // bytes
|
static_cast<UINT32>(sizeof(s16) * m_output_channels * m_enqueue_buffer_size), // bytes
|
||||||
reinterpret_cast<const BYTE*>(samples), // data
|
reinterpret_cast<const BYTE*>(samples), // data
|
||||||
0u,
|
0u,
|
||||||
0u,
|
0u,
|
||||||
0u,
|
0u,
|
||||||
|
|
Loading…
Reference in New Issue