SPU2: Use AudioStream for output

This commit is contained in:
Stenzek 2024-04-24 01:17:52 +10:00 committed by Connor McLaughlin
parent ca091eeea9
commit 0f5e7355ff
48 changed files with 1919 additions and 3388 deletions

View File

@ -57,9 +57,11 @@ target_sources(pcsx2-qt PRIVATE
Settings/AdvancedSettingsWidget.cpp
Settings/AdvancedSettingsWidget.h
Settings/AdvancedSettingsWidget.ui
Settings/AudioExpansionSettingsDialog.ui
Settings/AudioSettingsWidget.cpp
Settings/AudioSettingsWidget.h
Settings/AudioSettingsWidget.ui
Settings/AudioStretchSettingsDialog.ui
Settings/BIOSSettingsWidget.cpp
Settings/BIOSSettingsWidget.h
Settings/BIOSSettingsWidget.ui

View File

@ -29,6 +29,7 @@
#include "pcsx2/Input/InputManager.h"
#include "pcsx2/MTGS.h"
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/SPU2/spu2.h"
#include "pcsx2/VMManager.h"
#include "common/Assertions.h"
@ -889,6 +890,38 @@ void EmuThread::endCapture()
MTGS::RunOnGSThread(&GSEndCapture);
}
void EmuThread::setAudioOutputVolume(int volume, int fast_forward_volume)
{
if (!isOnEmuThread())
{
QMetaObject::invokeMethod(this, "setAudioOutputVolume", Qt::QueuedConnection, Q_ARG(int, volume),
Q_ARG(int, fast_forward_volume));
return;
}
if (!VMManager::HasValidVM())
return;
EmuConfig.SPU2.OutputVolume = static_cast<u32>(volume);
EmuConfig.SPU2.FastForwardVolume = static_cast<u32>(fast_forward_volume);
SPU2::SetOutputVolume(SPU2::GetResetVolume());
}
void EmuThread::setAudioOutputMuted(bool muted)
{
if (!isOnEmuThread())
{
QMetaObject::invokeMethod(this, "setAudioOutputMuted", Qt::QueuedConnection, Q_ARG(bool, muted));
return;
}
if (!VMManager::HasValidVM())
return;
EmuConfig.SPU2.OutputMuted = muted;
SPU2::SetOutputVolume(SPU2::GetResetVolume());
}
std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool recreate_window)
{
// Check if we're wanting to get exclusive fullscreen. This should be safe to read, since we're going to be calling from the GS thread.

View File

@ -112,6 +112,8 @@ public Q_SLOTS:
void queueSnapshot(quint32 gsdump_frames);
void beginCapture(const QString& path);
void endCapture();
void setAudioOutputVolume(int volume, int fast_forward_volume);
void setAudioOutputMuted(bool muted);
Q_SIGNALS:
bool messageConfirmed(const QString& title, const QString& message);

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "QtUtils.h"
@ -14,11 +14,13 @@
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialog>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QSlider>
#include <QtWidgets/QStyle>
#include <QtWidgets/QTableView>
#include <QtWidgets/QTreeView>
@ -202,6 +204,15 @@ namespace QtUtils
}
}
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)
{
if (QMainWindow* window = qobject_cast<QMainWindow*>(widget); window)

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
@ -20,7 +20,9 @@ class QAction;
class QComboBox;
class QFileInfo;
class QFrame;
class QLabel;
class QKeyEvent;
class QSlider;
class QTableView;
class QTreeView;
class QVariant;
@ -71,6 +73,9 @@ namespace QtUtils
/// Sets a widget to italics if the setting value is 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.
void SetWindowResizeable(QWidget* widget, bool resizeable);

View File

@ -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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Audio Expansion Settings&lt;/span&gt;&lt;br/&gt;These settings fine-tune the behavior of the FreeSurround-based channel expander.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>

View File

@ -1,29 +1,22 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include <QtWidgets/QMessageBox>
#include <algorithm>
#include "pcsx2/SPU2/Global.h"
#include "pcsx2/SPU2/spu2.h"
#include "pcsx2/VMManager.h"
#include "AudioSettingsWidget.h"
#include "QtHost.h"
#include "QtUtils.h"
#include "SettingWidgetBinder.h"
#include "SettingsWindow.h"
static constexpr s32 DEFAULT_SYNCHRONIZATION_MODE = 0;
static constexpr s32 DEFAULT_EXPANSION_MODE = 0;
static constexpr s32 DEFAULT_DPL_DECODING_LEVEL = 0;
static const char* DEFAULT_OUTPUT_MODULE = "cubeb";
static constexpr s32 DEFAULT_TARGET_LATENCY = 60;
static constexpr s32 DEFAULT_OUTPUT_LATENCY = 20;
static constexpr s32 DEFAULT_VOLUME = 100;
static constexpr s32 DEFAULT_SOUNDTOUCH_SEQUENCE_LENGTH = 30;
static constexpr s32 DEFAULT_SOUNDTOUCH_SEEK_WINDOW = 20;
static constexpr s32 DEFAULT_SOUNDTOUCH_OVERLAP = 10;
#include "ui_AudioExpansionSettingsDialog.h"
#include "ui_AudioStretchSettingsDialog.h"
#include "pcsx2/Host/AudioStream.h"
#include "pcsx2/SPU2/spu2.h"
#include "pcsx2/VMManager.h"
#include <QtWidgets/QMessageBox>
#include <algorithm>
#include <bit>
AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent)
: QWidget(parent)
@ -32,317 +25,481 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
SettingsInterface* sif = dialog->getSettingsInterface();
m_ui.setupUi(this);
populateOutputModules();
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.syncMode, "SPU2/Output", "SynchMode", DEFAULT_SYNCHRONIZATION_MODE);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.expansionMode, "SPU2/Output", "SpeakerConfiguration", DEFAULT_EXPANSION_MODE);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.dplLevel, "SPU2/Output", "DplDecodingLevel", DEFAULT_DPL_DECODING_LEVEL);
connect(m_ui.syncMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &AudioSettingsWidget::updateTargetLatencyRange);
connect(m_ui.expansionMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &AudioSettingsWidget::expansionModeChanged);
updateTargetLatencyRange();
expansionModeChanged();
for (u32 i = 0; i < static_cast<u32>(AudioBackend::Count); i++)
m_ui.audioBackend->addItem(QString::fromUtf8(AudioStream::GetBackendDisplayName(static_cast<AudioBackend>(i))));
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.outputModule, "SPU2/Output", "OutputModule", DEFAULT_OUTPUT_MODULE);
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(
//: Measuring unit that will appear after the number selected in its option. Adapt the space depending on your language's rules.
sif, m_ui.targetLatency, m_ui.targetLatencyLabel, tr(" ms"), "SPU2/Output", "Latency", DEFAULT_TARGET_LATENCY);
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(
sif, m_ui.outputLatency, m_ui.outputLatencyLabel, tr(" ms"), "SPU2/Output", "OutputLatency", DEFAULT_OUTPUT_LATENCY);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.outputLatencyMinimal, "SPU2/Output", "OutputLatencyMinimal", false);
connect(m_ui.outputModule, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::outputModuleChanged);
connect(m_ui.backend, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::outputBackendChanged);
connect(m_ui.targetLatency, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabels);
connect(m_ui.outputLatency, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabels);
connect(m_ui.outputLatencyMinimal, &QCheckBox::checkStateChanged, this, &AudioSettingsWidget::updateLatencyLabels);
connect(m_ui.outputLatencyMinimal, &QCheckBox::checkStateChanged, this, &AudioSettingsWidget::onMinimalOutputLatencyStateChanged);
outputModuleChanged();
m_ui.volume->setValue(m_dialog->getEffectiveIntValue("SPU2/Mixing", "FinalVolume", DEFAULT_VOLUME));
connect(m_ui.volume, &QSlider::valueChanged, this, &AudioSettingsWidget::volumeChanged);
updateVolumeLabel();
if (dialog->isPerGameSettings())
for (u32 i = 0; i < static_cast<u32>(AudioExpansionMode::Count); i++)
{
connect(m_ui.volume, &QSlider::customContextMenuRequested, this, &AudioSettingsWidget::volumeContextMenuRequested);
m_ui.volume->setContextMenuPolicy(Qt::CustomContextMenu);
if (sif->ContainsValue("SPU2/Mixing", "FinalVolume"))
{
QFont bold_font(m_ui.volume->font());
bold_font.setBold(true);
m_ui.volumeLabel->setFont(bold_font);
}
m_ui.expansionMode->addItem(
QString::fromUtf8(AudioStream::GetExpansionModeDisplayName(static_cast<AudioExpansionMode>(i))));
}
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(sif, m_ui.sequenceLength, m_ui.sequenceLengthLabel, tr(" ms"), "Soundtouch",
"SequenceLengthMS", DEFAULT_SOUNDTOUCH_SEQUENCE_LENGTH);
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(
sif, m_ui.seekWindowSize, m_ui.seekWindowSizeLabel, tr(" ms"), "Soundtouch", "SeekWindowMS", DEFAULT_SOUNDTOUCH_SEEK_WINDOW);
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(
sif, m_ui.overlap, m_ui.overlapLabel, tr(" ms"), "Soundtouch", "OverlapMS", DEFAULT_SOUNDTOUCH_OVERLAP);
connect(m_ui.resetTimestretchDefaults, &QPushButton::clicked, this, &AudioSettingsWidget::resetTimestretchDefaults);
for (u32 i = 0; i < static_cast<u32>(Pcsx2Config::SPU2Options::SPU2SyncMode::Count); i++)
{
m_ui.syncMode->addItem(
QString::fromUtf8(Pcsx2Config::SPU2Options::GetSyncModeDisplayName(
static_cast<Pcsx2Config::SPU2Options::SPU2SyncMode>(i))));
}
m_ui.label_3b->setVisible(false);
m_ui.dplLevel->setVisible(false);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.audioBackend, "SPU2/Output", "Backend",
&AudioStream::ParseBackendName, &AudioStream::GetBackendName,
Pcsx2Config::SPU2Options::DEFAULT_BACKEND);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.expansionMode, "SPU2/Output", "ExpansionMode",
&AudioStream::ParseExpansionMode, &AudioStream::GetExpansionModeName,
AudioStreamParameters::DEFAULT_EXPANSION_MODE);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.syncMode, "SPU2/Output", "SyncMode",
&Pcsx2Config::SPU2Options::ParseSyncMode, &Pcsx2Config::SPU2Options::GetSyncModeName,
Pcsx2Config::SPU2Options::DEFAULT_SYNC_MODE);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.bufferMS, "SPU2/Output", "BufferMS",
AudioStreamParameters::DEFAULT_BUFFER_MS);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.outputLatencyMS, "SPU2/Output", "OutputLatencyMS",
AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.outputLatencyMinimal, "SPU2/Output", "OutputLatencyMinimal", false);
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.syncMode, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::onSyncModeChanged);
connect(m_ui.stretchSettings, &QToolButton::clicked, this, &AudioSettingsWidget::onStretchSettingsClicked);
onExpansionModeChanged();
onSyncModeChanged();
updateDriverNames();
onMinimalOutputLatencyStateChanged();
updateLatencyLabels();
connect(m_ui.bufferMS, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabel);
connect(m_ui.outputLatencyMS, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabel);
connect(m_ui.outputLatencyMinimal, &QCheckBox::checkStateChanged, this, &AudioSettingsWidget::onMinimalOutputLatencyChanged);
onMinimalOutputLatencyChanged();
updateLatencyLabel();
// for per-game, just use the normal path, since it needs to re-read/apply
if (!dialog->isPerGameSettings())
{
m_ui.volume->setValue(m_dialog->getEffectiveIntValue("SPU2/Output", "OutputVolume", 100));
m_ui.fastForwardVolume->setValue(m_dialog->getEffectiveIntValue("SPU2/Output", "FastForwardVolume", 100));
m_ui.muted->setChecked(m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputMuted", false));
connect(m_ui.volume, &QSlider::valueChanged, this, &AudioSettingsWidget::onOutputVolumeChanged);
connect(m_ui.fastForwardVolume, &QSlider::valueChanged, this, &AudioSettingsWidget::onFastForwardVolumeChanged);
connect(m_ui.muted, &QCheckBox::checkStateChanged, this, &AudioSettingsWidget::onOutputMutedChanged);
updateVolumeLabel();
}
else
{
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(sif, m_ui.volume, m_ui.volumeLabel, tr("%"), "SPU2/Output", "OutputVolume", 100);
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(sif, m_ui.fastForwardVolume, m_ui.fastForwardVolumeLabel, tr("%"), "SPU2/Output", "FastForwardVolume", 100);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.muted, "SPU2/Output", "OutputMuted", false);
}
connect(m_ui.resetVolume, &QToolButton::clicked, this, [this]() { resetVolume(false); });
connect(m_ui.resetFastForwardVolume, &QToolButton::clicked, this, [this]() { resetVolume(true); });
dialog->registerWidgetHelp(
m_ui.audioBackend, tr("Audio Backend"), QStringLiteral("Cubeb"),
tr("The audio backend determines how frames produced by the emulator are submitted to the host. Cubeb provides the "
"lowest latency, if you encounter issues, try the SDL backend. The null backend disables all host audio "
"output."));
dialog->registerWidgetHelp(
m_ui.bufferMS, tr("Buffer Size"), tr("%1 ms").arg(AudioStreamParameters::DEFAULT_BUFFER_MS),
tr("Determines the buffer size which the time stretcher will try to keep filled. It effectively selects the "
"average latency, as audio will be stretched/shrunk to keep the buffer size within check."));
dialog->registerWidgetHelp(
m_ui.outputLatencyMS, tr("Output Latency"), tr("%1 ms").arg(AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS),
tr("Determines the latency from the buffer to the host audio output. This can be set lower than the target latency "
"to reduce audio delay."));
dialog->registerWidgetHelp(m_ui.volume, tr("Output Volume"), "100%",
tr("Controls the volume of the audio played on the host."));
dialog->registerWidgetHelp(m_ui.fastForwardVolume, tr("Fast Forward Volume"), "100%",
tr("Controls the volume of the audio played on the host when fast forwarding."));
dialog->registerWidgetHelp(m_ui.muted, tr("Mute All Sound"), tr("Unchecked"),
tr("Prevents the emulator from producing any audible sound."));
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(m_ui.expansionSettings, tr("Expansion Settings"), tr("N/A"),
tr("These settings fine-tune the behavior of the FreeSurround-based channel expander."));
dialog->registerWidgetHelp(m_ui.syncMode, tr("Synchronization"), tr("TimeStretch (Recommended)"),
tr("When running outside of 100% speed, adjusts the tempo on audio instead of dropping frames. Produces much nicer fast-forward/slowdown audio."));
dialog->registerWidgetHelp(m_ui.expansionMode, tr("Expansion"), tr("Stereo (None, Default)"),
tr("Determines how the stereo output from the emulated system is upmixed into a greater number of the output speakers."));
//: Cubeb is an audio engine name. Leave as-is.
dialog->registerWidgetHelp(m_ui.outputModule, tr("Output Module"), tr("Cubeb (Cross-platform)"),
tr("Selects the library to be used for audio output."));
dialog->registerWidgetHelp(m_ui.backend, tr("Output Backend"), tr("Default"),
tr("When the sound output module supports multiple audio backends, determines the API to be used for audio output to the system."));
dialog->registerWidgetHelp(m_ui.outputDevice, tr("Output Device"), tr("Default"),
tr("Determines which audio device to output the sound to."));
dialog->registerWidgetHelp(m_ui.targetLatency, tr("Target Latency"), tr("60 ms"),
tr("Determines the buffer size which the time stretcher will try to keep filled. It effectively selects the average latency, as "
"audio will be stretched/shrunk to keep the buffer size within check."));
dialog->registerWidgetHelp(m_ui.outputLatency, tr("Output Latency"), tr("20 ms"),
tr("Determines the latency from the buffer to the host audio output. This can be set lower than the target latency to reduce audio "
"delay."));
dialog->registerWidgetHelp(m_ui.sequenceLength, tr("Sequence Length"), tr("30 ms"), tr("This is the default length of a single processing sequence which determines how the original sound is chopped in the time-stretch algorithm. "
"Larger values mean fewer sequences are used in processing. In principle a larger value sounds better when slowing down the tempo, but worse when increasing the tempo."));
//: Seek Window: the region of samples (window) the audio stretching algorithm is allowed to search.
dialog->registerWidgetHelp(m_ui.seekWindowSize, tr("Seek Window Size"), tr("20 ms"), tr("The seeking window is for the algorithm that seeks the best possible overlapping location. "
"This determines from how wide a sample window the algorithm can use to find an optimal mixing location when the sound sequences are to be linked back together."));
dialog->registerWidgetHelp(m_ui.overlap, tr("Overlap"), tr("10 ms"), tr("When the sound sequences are mixed back together to form again a continuous sound stream, this parameter defines how much the ends of the consecutive sequences will overlap with each other."));
dialog->registerWidgetHelp(m_ui.volume, tr("Volume"), tr("100%"),
tr("Pre-applies a volume modifier to the game's audio output before forwarding it to your computer."));
dialog->registerWidgetHelp(m_ui.stretchSettings, tr("Stretch Settings"), tr("N/A"),
tr("These settings fine-tune the behavior of the SoundTouch audio time stretcher when running outside of 100% speed."));
dialog->registerWidgetHelp(m_ui.resetVolume, tr("Reset Volume"), tr("N/A"),
m_dialog->isPerGameSettings() ? tr("Resets volume back to the global/inherited setting.") :
tr("Resets volume back to the default, i.e. full."));
dialog->registerWidgetHelp(m_ui.resetFastForwardVolume, tr("Reset Fast Forward Volume"), tr("N/A"),
m_dialog->isPerGameSettings() ? tr("Resets volume back to the global/inherited setting.") :
tr("Resets volume back to the default, i.e. full."));
}
AudioSettingsWidget::~AudioSettingsWidget() = default;
void AudioSettingsWidget::expansionModeChanged()
AudioExpansionMode AudioSettingsWidget::getEffectiveExpansionMode() const
{
const bool expansion51 = m_dialog->getEffectiveIntValue("SPU2/Output", "SpeakerConfiguration", 0) == 2;
m_ui.dplLevel->setDisabled(!expansion51);
return AudioStream::ParseExpansionMode(
m_dialog->getEffectiveStringValue("SPU2/Output", "ExpansionMode",
AudioStream::GetExpansionModeName(AudioStreamParameters::DEFAULT_EXPANSION_MODE))
.c_str())
.value_or(AudioStreamParameters::DEFAULT_EXPANSION_MODE);
}
void AudioSettingsWidget::populateOutputModules()
u32 AudioSettingsWidget::getEffectiveExpansionBlockSize() const
{
for (const SndOutModule* mod : GetSndOutModules())
m_ui.outputModule->addItem(qApp->translate("SPU2", mod->GetDisplayName()), QString::fromUtf8(mod->GetIdent()));
const AudioExpansionMode expansion_mode = getEffectiveExpansionMode();
if (expansion_mode == AudioExpansionMode::Disabled)
return 0;
const u32 config_block_size = m_dialog->getEffectiveIntValue("SPU2/Output", "ExpandBlockSize",
AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE);
return std::has_single_bit(config_block_size) ? config_block_size : std::bit_ceil(config_block_size);
}
void AudioSettingsWidget::outputModuleChanged()
void AudioSettingsWidget::onExpansionModeChanged()
{
const std::string module_name(m_dialog->getEffectiveStringValue("SPU2/Output", "OutputModule", DEFAULT_OUTPUT_MODULE));
const char* const* backend_names = GetOutputModuleBackends(module_name.c_str());
const std::string backend_name(m_dialog->getEffectiveStringValue("SPU2/Output", "BackendName", ""));
QSignalBlocker sb(m_ui.backend);
m_ui.backend->clear();
if (m_dialog->isPerGameSettings())
{
const QString global_backend(QString::fromStdString(Host::GetStringSettingValue("SPU2/Output", "BackendName", "")));
m_ui.backend->addItem(tr("Use Global Setting [%1]").arg(global_backend.isEmpty() ? tr("Default") : global_backend));
}
m_ui.backend->setEnabled(backend_names != nullptr);
m_ui.backend->addItem(tr("Default"));
if (!backend_names || backend_name.empty())
m_ui.backend->setCurrentIndex(0);
if (backend_names)
{
for (u32 i = 0; backend_names[i] != nullptr; i++)
{
const int index = m_ui.backend->count();
m_ui.backend->addItem(QString::fromUtf8(backend_names[i]));
if (backend_name == backend_names[i])
m_ui.backend->setCurrentIndex(index);
}
}
updateDevices();
const AudioExpansionMode expansion_mode = getEffectiveExpansionMode();
m_ui.expansionSettings->setEnabled(expansion_mode != AudioExpansionMode::Disabled);
updateLatencyLabel();
}
void AudioSettingsWidget::outputBackendChanged()
void AudioSettingsWidget::onSyncModeChanged()
{
int index = m_ui.backend->currentIndex();
if (m_dialog->isPerGameSettings())
const Pcsx2Config::SPU2Options::SPU2SyncMode sync_mode =
Pcsx2Config::SPU2Options::ParseSyncMode(
m_dialog
->getEffectiveStringValue("SPU2/Output", "SyncMode",
Pcsx2Config::SPU2Options::GetSyncModeName(Pcsx2Config::SPU2Options::DEFAULT_SYNC_MODE))
.c_str())
.value_or(Pcsx2Config::SPU2Options::DEFAULT_SYNC_MODE);
m_ui.stretchSettings->setEnabled(sync_mode == Pcsx2Config::SPU2Options::SPU2SyncMode::TimeStretch);
}
AudioBackend AudioSettingsWidget::getEffectiveBackend() const
{
return AudioStream::ParseBackendName(m_dialog->getEffectiveStringValue("SPU2/Output", "Backend",
AudioStream::GetBackendName(Pcsx2Config::SPU2Options::DEFAULT_BACKEND))
.c_str())
.value_or(Pcsx2Config::SPU2Options::DEFAULT_BACKEND);
}
void AudioSettingsWidget::updateDriverNames()
{
const AudioBackend backend = getEffectiveBackend();
const std::vector<std::pair<std::string, std::string>> names = AudioStream::GetDriverNames(backend);
m_ui.driver->disconnect();
m_ui.driver->clear();
if (names.empty())
{
if (index == 0)
{
m_dialog->setStringSettingValue("SPU2/Output", "BackendName", std::nullopt);
return;
}
index--;
m_ui.driver->addItem(tr("Default"), QString());
m_ui.driver->setEnabled(false);
}
if (index == 0)
m_dialog->setStringSettingValue("SPU2/Output", "BackendName", "");
else
m_dialog->setStringSettingValue("SPU2/Output", "BackendName", m_ui.backend->currentText().toUtf8().constData());
{
m_ui.driver->setEnabled(true);
for (const std::pair<std::string, std::string>& it : names)
m_ui.driver->addItem(QString::fromStdString(it.second), QString::fromStdString(it.first));
updateDevices();
SettingWidgetBinder::BindWidgetToStringSetting(m_dialog->getSettingsInterface(), m_ui.driver, "SPU2/Output", "DriverName",
std::move(names.front().first));
connect(m_ui.driver, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::updateDeviceNames);
}
updateDeviceNames();
}
void AudioSettingsWidget::updateDevices()
void AudioSettingsWidget::updateDeviceNames()
{
const std::string module_name(m_dialog->getEffectiveStringValue("SPU2/Output", "OutputModule", DEFAULT_OUTPUT_MODULE));
const std::string backend_name(m_dialog->getEffectiveStringValue("SPU2/Output", "BackendName", ""));
const AudioBackend backend = getEffectiveBackend();
const std::string driver_name = m_dialog->getEffectiveStringValue("SPU2/Output", "DriverName", "");
const std::string current_device = m_dialog->getEffectiveStringValue("SPU2/Output", "DeviceName", "");
const std::vector<AudioStream::DeviceInfo> devices = AudioStream::GetOutputDevices(backend, driver_name.c_str());
m_ui.outputDevice->disconnect();
m_ui.outputDevice->clear();
m_output_device_latency = 0;
std::vector<SndOutDeviceInfo> devices(GetOutputDeviceList(module_name.c_str(), backend_name.c_str()));
if (devices.empty())
{
m_ui.outputDevice->addItem(tr("Default"));
m_ui.outputDevice->addItem(tr("Default"), QString());
m_ui.outputDevice->setEnabled(false);
}
else
{
const std::string current_device(m_dialog->getEffectiveStringValue("SPU2/Output", "DeviceName", ""));
m_ui.outputDevice->setEnabled(true);
for (const SndOutDeviceInfo& devi : devices)
bool is_known_device = false;
for (const AudioStream::DeviceInfo& di : devices)
{
m_ui.outputDevice->addItem(QString::fromStdString(devi.display_name), QString::fromStdString(devi.name));
if (devi.name == current_device)
m_output_device_latency = devi.minimum_latency_frames;
m_ui.outputDevice->addItem(QString::fromStdString(di.display_name), QString::fromStdString(di.name));
if (di.name == current_device)
{
m_output_device_latency = di.minimum_latency_frames;
is_known_device = true;
}
}
SettingWidgetBinder::BindWidgetToStringSetting(
m_dialog->getSettingsInterface(), m_ui.outputDevice, "SPU2/Output", "DeviceName", std::move(devices.front().name));
if (!is_known_device)
{
m_ui.outputDevice->addItem(tr("Unknown Device \"%1\"").arg(QString::fromStdString(current_device)),
QString::fromStdString(current_device));
}
SettingWidgetBinder::BindWidgetToStringSetting(m_dialog->getSettingsInterface(), m_ui.outputDevice, "SPU2/Output",
"DeviceName", std::move(devices.front().name));
}
updateLatencyLabel();
}
void AudioSettingsWidget::volumeChanged(int value)
void AudioSettingsWidget::updateLatencyLabel()
{
// Nasty, but needed so we don't do a full settings apply and lag while dragging.
if (SettingsInterface* sif = m_dialog->getSettingsInterface())
const u32 expand_buffer_ms = AudioStream::GetMSForBufferSize(SPU2::SAMPLE_RATE, getEffectiveExpansionBlockSize());
const u32 config_buffer_ms = m_dialog->getEffectiveIntValue("SPU2/Output", "BufferMS", AudioStreamParameters::DEFAULT_BUFFER_MS);
const u32 config_output_latency_ms = m_dialog->getEffectiveIntValue("SPU2/Output", "OutputLatencyMS", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS);
const bool minimal_output = m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputLatencyMinimal", false);
//: Preserve the %1 variable, adapt the latter ms (and/or any possible spaces in between) to your language's ruleset.
m_ui.outputLatencyLabel->setText(minimal_output ? tr("N/A") : tr("%1 ms").arg(config_output_latency_ms));
const u32 output_latency_ms = minimal_output ? AudioStream::GetMSForBufferSize(SPU2::SAMPLE_RATE, m_output_device_latency) : config_output_latency_ms;
if (output_latency_ms > 0)
{
if (!m_ui.volumeLabel->font().bold())
if (expand_buffer_ms > 0)
{
QFont bold_font(m_ui.volumeLabel->font());
bold_font.setBold(true);
m_ui.volumeLabel->setFont(bold_font);
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms buffer + %3 ms expand + %4 ms output)")
.arg(config_buffer_ms + expand_buffer_ms + output_latency_ms)
.arg(config_buffer_ms)
.arg(expand_buffer_ms)
.arg(output_latency_ms));
}
else
{
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms buffer + %3 ms output)")
.arg(config_buffer_ms + output_latency_ms)
.arg(config_buffer_ms)
.arg(output_latency_ms));
}
sif->SetIntValue("SPU2/Mixing", "FinalVolume", value);
sif->Save();
// There's two separate interfaces - one we're editing, and the active one.
// We need to reload the latter.
g_emu_thread->reloadGameSettings();
}
else
{
Host::SetBaseIntSettingValue("SPU2/Mixing", "FinalVolume", value);
Host::CommitBaseSettingChanges();
// Push through to emu thread since we're not applying.
if (QtHost::IsVMValid())
if (expand_buffer_ms > 0)
{
Host::RunOnCPUThread([]() {
if (!VMManager::HasValidVM())
return;
EmuConfig.SPU2.FinalVolume = Host::GetIntSettingValue("SPU2/Mixing", "FinalVolume", DEFAULT_VOLUME);
SPU2::SetOutputVolume(EmuConfig.SPU2.FinalVolume);
});
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms expand, minimum output latency unknown)")
.arg(expand_buffer_ms + config_buffer_ms)
.arg(expand_buffer_ms));
}
else
{
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (minimum output latency unknown)").arg(config_buffer_ms));
}
}
updateVolumeLabel();
}
void AudioSettingsWidget::volumeContextMenuRequested(const QPoint& pt)
{
QMenu menu(m_ui.volume);
m_ui.volume->connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, this, [this]() {
const s32 global_value = Host::GetBaseIntSettingValue("SPU2/Mixing", "FinalVolume", DEFAULT_VOLUME);
{
QSignalBlocker sb(m_ui.volume);
m_ui.volume->setValue(global_value);
updateVolumeLabel();
}
if (m_ui.volumeLabel->font().bold())
{
QFont orig_font(m_ui.volumeLabel->font());
orig_font.setBold(false);
m_ui.volumeLabel->setFont(orig_font);
}
SettingsInterface* sif = m_dialog->getSettingsInterface();
if (sif->ContainsValue("SPU2/Mixing", "FinalVolume"))
{
sif->DeleteValue("SPU2/Mixing", "FinalVolume");
sif->Save();
g_emu_thread->reloadGameSettings();
}
});
menu.exec(m_ui.volume->mapToGlobal(pt));
}
void AudioSettingsWidget::updateVolumeLabel()
{
//: Variable value that indicates a percentage. Preserve the %1 variable, adapt the latter % (and/or any possible spaces) to your language's ruleset.
m_ui.volumeLabel->setText(tr("%1%").arg(m_ui.volume->value()));
m_ui.fastForwardVolumeLabel->setText(tr("%1%").arg(m_ui.fastForwardVolume->value()));
}
void AudioSettingsWidget::updateTargetLatencyRange()
void AudioSettingsWidget::onMinimalOutputLatencyChanged()
{
const Pcsx2Config::SPU2Options::SynchronizationMode sync_mode = static_cast<Pcsx2Config::SPU2Options::SynchronizationMode>(
m_dialog->getIntValue("SPU2/Output", "SynchMode", DEFAULT_SYNCHRONIZATION_MODE).value_or(DEFAULT_SYNCHRONIZATION_MODE));
m_ui.targetLatency->setMinimum((sync_mode == Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch) ?
Pcsx2Config::SPU2Options::MIN_LATENCY_TIMESTRETCH :
Pcsx2Config::SPU2Options::MIN_LATENCY);
m_ui.targetLatency->setMaximum(Pcsx2Config::SPU2Options::MAX_LATENCY);
const bool minimal = m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputLatencyMinimal", false);
m_ui.outputLatencyMS->setEnabled(!minimal);
updateLatencyLabel();
}
void AudioSettingsWidget::updateLatencyLabels()
void AudioSettingsWidget::onOutputVolumeChanged(int new_value)
{
const bool minimal_output = m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputLatencyMinimal", false);
// only called for base settings
pxAssert(!m_dialog->isPerGameSettings());
Host::SetBaseIntSettingValue("SPU2/Output", "OutputVolume", new_value);
Host::CommitBaseSettingChanges();
g_emu_thread->setAudioOutputVolume(new_value, m_ui.fastForwardVolume->value());
//: Preserve the %1 variable, adapt the latter ms (and/or any possible spaces in between) to your language's ruleset.
m_ui.outputLatencyLabel->setText(minimal_output ? tr("N/A") : tr("%1 ms").arg(m_ui.outputLatency->value()));
updateVolumeLabel();
}
const u32 output_latency_ms =
minimal_output ? (((m_output_device_latency * 1000u) + 47999u) / 48000u) : static_cast<u32>(m_ui.outputLatency->value());
const u32 buffer_ms = static_cast<u32>(m_ui.targetLatency->value());
if (output_latency_ms > 0)
void AudioSettingsWidget::onFastForwardVolumeChanged(int new_value)
{
// only called for base settings
pxAssert(!m_dialog->isPerGameSettings());
Host::SetBaseIntSettingValue("SPU2/Output", "FastForwardVolume", new_value);
Host::CommitBaseSettingChanges();
g_emu_thread->setAudioOutputVolume(m_ui.volume->value(), new_value);
updateVolumeLabel();
}
void AudioSettingsWidget::onOutputMutedChanged(int new_state)
{
// only called for base settings
pxAssert(!m_dialog->isPerGameSettings());
const bool muted = (new_state != 0);
Host::SetBaseBoolSettingValue("SPU2/Output", "OutputMuted", muted);
Host::CommitBaseSettingChanges();
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, "SPU2/Output", "ExpandBlockSize",
AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE, 0);
QtUtils::BindLabelToSlider(dlgui.blockSize, dlgui.blockSizeLabel);
SettingWidgetBinder::BindWidgetToFloatSetting(sif, dlgui.circularWrap, "SPU2/Output", "ExpandCircularWrap",
AudioStreamParameters::DEFAULT_EXPAND_CIRCULAR_WRAP);
QtUtils::BindLabelToSlider(dlgui.circularWrap, dlgui.circularWrapLabel);
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.shift, "SPU2/Output", "ExpandShift", 100.0f,
AudioStreamParameters::DEFAULT_EXPAND_SHIFT);
QtUtils::BindLabelToSlider(dlgui.shift, dlgui.shiftLabel, 100.0f);
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.depth, "SPU2/Output", "ExpandDepth", 10.0f,
AudioStreamParameters::DEFAULT_EXPAND_DEPTH);
QtUtils::BindLabelToSlider(dlgui.depth, dlgui.depthLabel, 10.0f);
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.focus, "SPU2/Output", "ExpandFocus", 100.0f,
AudioStreamParameters::DEFAULT_EXPAND_FOCUS);
QtUtils::BindLabelToSlider(dlgui.focus, dlgui.focusLabel, 100.0f);
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.centerImage, "SPU2/Output", "ExpandCenterImage", 100.0f,
AudioStreamParameters::DEFAULT_EXPAND_CENTER_IMAGE);
QtUtils::BindLabelToSlider(dlgui.centerImage, dlgui.centerImageLabel, 100.0f);
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.frontSeparation, "SPU2/Output", "ExpandFrontSeparation",
10.0f, AudioStreamParameters::DEFAULT_EXPAND_FRONT_SEPARATION);
QtUtils::BindLabelToSlider(dlgui.frontSeparation, dlgui.frontSeparationLabel, 10.0f);
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.rearSeparation, "SPU2/Output", "ExpandRearSeparation", 10.0f,
AudioStreamParameters::DEFAULT_EXPAND_REAR_SEPARATION);
QtUtils::BindLabelToSlider(dlgui.rearSeparation, dlgui.rearSeparationLabel, 10.0f);
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.lowCutoff, "SPU2/Output", "ExpandLowCutoff",
AudioStreamParameters::DEFAULT_EXPAND_LOW_CUTOFF);
QtUtils::BindLabelToSlider(dlgui.lowCutoff, dlgui.lowCutoffLabel);
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.highCutoff, "SPU2/Output", "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("SPU2/Output", "ExpandBlockSize",
m_dialog->isPerGameSettings() ?
std::nullopt :
std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE));
m_dialog->setFloatSettingValue("SPU2/Output", "ExpandCircularWrap",
m_dialog->isPerGameSettings() ?
std::nullopt :
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_CIRCULAR_WRAP));
m_dialog->setFloatSettingValue(
"SPU2/Output", "ExpandShift",
m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_SHIFT));
m_dialog->setFloatSettingValue(
"SPU2/Output", "ExpandDepth",
m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_DEPTH));
m_dialog->setFloatSettingValue(
"SPU2/Output", "ExpandFocus",
m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_FOCUS));
m_dialog->setFloatSettingValue("SPU2/Output", "ExpandCenterImage",
m_dialog->isPerGameSettings() ?
std::nullopt :
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_CENTER_IMAGE));
m_dialog->setFloatSettingValue("SPU2/Output", "ExpandFrontSeparation",
m_dialog->isPerGameSettings() ?
std::nullopt :
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_FRONT_SEPARATION));
m_dialog->setFloatSettingValue("SPU2/Output", "ExpandRearSeparation",
m_dialog->isPerGameSettings() ?
std::nullopt :
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_REAR_SEPARATION));
m_dialog->setIntSettingValue("SPU2/Output", "ExpandLowCutoff",
m_dialog->isPerGameSettings() ?
std::nullopt :
std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_LOW_CUTOFF));
m_dialog->setIntSettingValue("SPU2/Output", "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();
updateLatencyLabel();
}
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, "SPU2/Output", "StretchSequenceLengthMS",
AudioStreamParameters::DEFAULT_STRETCH_SEQUENCE_LENGTH, 0);
QtUtils::BindLabelToSlider(dlgui.sequenceLength, dlgui.sequenceLengthLabel);
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.seekWindowSize, "SPU2/Output", "StretchSeekWindowMS",
AudioStreamParameters::DEFAULT_STRETCH_SEEKWINDOW, 0);
QtUtils::BindLabelToSlider(dlgui.seekWindowSize, dlgui.seekWindowSizeLabel);
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.overlap, "SPU2/Output", "StretchOverlapMS",
AudioStreamParameters::DEFAULT_STRETCH_OVERLAP, 0);
QtUtils::BindLabelToSlider(dlgui.overlap, dlgui.overlapLabel);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.useQuickSeek, "SPU2/Output", "StretchUseQuickSeek",
AudioStreamParameters::DEFAULT_STRETCH_USE_QUICKSEEK);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.useAAFilter, "SPU2/Output", "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("SPU2/Output", "StretchSequenceLengthMS",
m_dialog->isPerGameSettings() ?
std::nullopt :
std::optional<int>(AudioStreamParameters::DEFAULT_STRETCH_SEQUENCE_LENGTH));
m_dialog->setIntSettingValue("SPU2/Output", "StretchSeekWindowMS",
m_dialog->isPerGameSettings() ?
std::nullopt :
std::optional<int>(AudioStreamParameters::DEFAULT_STRETCH_SEEKWINDOW));
m_dialog->setIntSettingValue("SPU2/Output", "StretchOverlapMS",
m_dialog->isPerGameSettings() ?
std::nullopt :
std::optional<int>(AudioStreamParameters::DEFAULT_STRETCH_OVERLAP));
m_dialog->setBoolSettingValue("SPU2/Output", "StretchUseQuickSeek",
m_dialog->isPerGameSettings() ?
std::nullopt :
std::optional<bool>(AudioStreamParameters::DEFAULT_STRETCH_USE_QUICKSEEK));
m_dialog->setBoolSettingValue("SPU2/Output", "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();
}
void AudioSettingsWidget::resetVolume(bool fast_forward)
{
const char* key = fast_forward ? "FastForwardVolume" : "OutputVolume";
QSlider* const slider = fast_forward ? m_ui.fastForwardVolume : m_ui.volume;
QLabel* const label = fast_forward ? m_ui.fastForwardVolumeLabel : m_ui.volumeLabel;
if (m_dialog->isPerGameSettings())
{
m_ui.latencySummary->setText(tr("Average Latency: %1 ms (%2 ms buffer + %3 ms output)")
.arg(buffer_ms + output_latency_ms)
.arg(buffer_ms)
.arg(output_latency_ms));
m_dialog->removeSettingValue("Audio", key);
const int value = m_dialog->getEffectiveIntValue("Audio", key, 100);
QSignalBlocker sb(slider);
slider->setValue(value);
label->setText(QStringLiteral("%1%2").arg(value).arg(tr("%")));
// remove bold font if it was previously overridden
QFont font(label->font());
font.setBold(false);
label->setFont(font);
}
else
{
m_ui.latencySummary->setText(tr("Average Latency: %1 ms (minimum output latency unknown)").arg(buffer_ms));
slider->setValue(100);
}
}
void AudioSettingsWidget::onMinimalOutputLatencyStateChanged()
{
m_ui.outputLatency->setEnabled(!m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputLatencyMinimal", false));
}
void AudioSettingsWidget::resetTimestretchDefaults()
{
m_ui.sequenceLength->setValue(DEFAULT_SOUNDTOUCH_SEQUENCE_LENGTH);
m_ui.seekWindowSize->setValue(DEFAULT_SOUNDTOUCH_SEEK_WINDOW);
m_ui.overlap->setValue(DEFAULT_SOUNDTOUCH_OVERLAP);
}

View File

@ -1,11 +1,16 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include "ui_AudioSettingsWidget.h"
#include "common/Pcsx2Defs.h"
#include <QtWidgets/QWidget>
#include "ui_AudioSettingsWidget.h"
enum class AudioBackend : u8;
enum class AudioExpansionMode : u8;
class SettingsWindow;
@ -18,22 +23,28 @@ public:
~AudioSettingsWidget();
private Q_SLOTS:
void expansionModeChanged();
void outputModuleChanged();
void outputBackendChanged();
void updateDevices();
void volumeChanged(int value);
void volumeContextMenuRequested(const QPoint& pt);
void updateTargetLatencyRange();
void updateLatencyLabels();
void onMinimalOutputLatencyStateChanged();
void resetTimestretchDefaults();
void onExpansionModeChanged();
void onSyncModeChanged();
void updateDriverNames();
void updateDeviceNames();
void updateLatencyLabel();
void updateVolumeLabel();
void onMinimalOutputLatencyChanged();
void onOutputVolumeChanged(int new_value);
void onFastForwardVolumeChanged(int new_value);
void onOutputMutedChanged(int new_state);
void onExpansionSettingsClicked();
void onStretchSettingsClicked();
private:
void populateOutputModules();
void updateVolumeLabel();
AudioBackend getEffectiveBackend() const;
AudioExpansionMode getEffectiveExpansionMode() const;
u32 getEffectiveExpansionBlockSize() const;
void resetVolume(bool fast_forward);
SettingsWindow* m_dialog;
Ui::AudioSettingsWidget m_ui;
SettingsWindow* m_dialog;
u32 m_output_device_latency = 0;
};

View File

@ -6,11 +6,11 @@
<rect>
<x>0</x>
<y>0</y>
<width>754</width>
<height>485</height>
<width>523</width>
<height>478</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@ -23,434 +23,133 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="2" column="0">
<widget class="QGroupBox" name="groupBox_4">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Timestretch Settings</string>
<string>Configuration</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Sequence Length:</string>
</property>
</widget>
</item>
<item row="0" 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::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::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="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Seekwindow Size:</string>
<string>Driver:</string>
</property>
</widget>
</item>
<item row="1" 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::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::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="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Overlap:</string>
</property>
</widget>
</item>
<item row="2" 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::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::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>
<widget class="QComboBox" name="driver"/>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
<widget class="QComboBox" name="expansionMode"/>
</item>
<item>
<widget class="QPushButton" name="resetTimestretchDefaults">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
<widget class="QToolButton" name="expansionSettings">
<property name="toolTip">
<string>Expansion Settings</string>
</property>
<property name="text">
<string>Restore Defaults</string>
<property name="icon">
<iconset theme="settings-3-line"/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="0" column="1" rowspan="4">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Volume</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSlider" name="volume">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="maximum">
<number>200</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBothSides</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="volumeLabel">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>100%</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Mixing Settings</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Synchronization:</string>
<item row="5" column="1">
<widget class="QSlider" name="bufferMS">
<property name="minimum">
<number>15</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="syncMode">
<item>
<property name="text">
<string>TimeStretch (Recommended)</string>
</property>
</item>
<item>
<property name="text">
<string>Async Mix (Breaks some games!)</string>
</property>
</item>
<item>
<property name="text">
<string>None (Audio can skip.)</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Expansion:</string>
<property name="maximum">
<number>500</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="expansionMode">
<item>
<property name="text">
<string>Stereo (None, Default)</string>
</property>
</item>
<item>
<property name="text">
<string>Quadraphonic</string>
</property>
</item>
<item>
<property name="text">
<string>Surround 5.1</string>
</property>
</item>
<item>
<property name="text">
<string>Surround 7.1</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3b">
<property name="text">
<string extracomment="ProLogic is a Dolby brand. Leave the name as-is unless there is an official translation for your language.">ProLogic Level:</string>
<property name="singleStep">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="dplLevel">
<item>
<property name="text">
<string>None (Default)</string>
</property>
</item>
<item>
<property name="text">
<string extracomment="ProLogic is a Dolby brand. Leave the name as-is unless there is an official translation for your language.">ProLogic Decoding (basic)</string>
</property>
</item>
<item>
<property name="text">
<string extracomment="ProLogic II is a Dolby brand. Leave the name as-is unless there is an official translation for your language. gigaherz is the nickname of one of PCSX2's developers. Leave as-is.">ProLogic II Decoding (gigaherz)</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Target Latency:</string>
<property name="pageStep">
<number>5</number>
</property>
<property name="value">
<number>50</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 row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,0">
<item>
<widget class="QSlider" name="targetLatency">
<widget class="QComboBox" name="syncMode"/>
</item>
<item>
<widget class="QToolButton" name="stretchSettings">
<property name="toolTip">
<string>Stretch Settings</string>
</property>
<property name="icon">
<iconset theme="settings-3-line"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Buffer Size:</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="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QSlider" name="outputLatencyMS">
<property name="minimum">
<number>15</number>
</property>
<property name="maximum">
<number>200</number>
</property>
<property name="value">
<number>60</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
<enum>QSlider::TickPosition::TicksBothSides</enum>
</property>
<property name="tickInterval">
<number>200</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="targetLatencyLabel">
<property name="text">
<string>60 ms</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Output Settings</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Output Module:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="outputModule"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Output Latency:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSlider" name="outputLatency">
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>200</number>
</property>
<property name="value">
<number>20</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>200</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="outputLatencyLabel">
<property name="text">
<string>20 ms</string>
<string>0 ms</string>
</property>
</widget>
</item>
@ -463,41 +162,188 @@
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Output Backend:</string>
</property>
</widget>
<item row="0" column="1">
<widget class="QComboBox" name="audioBackend"/>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="backend"/>
</item>
<item row="4" column="1">
<widget class="QLabel" name="latencySummary">
<item row="6" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Maximum Latency:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Output Device:</string>
<string>Output Latency:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="outputDevice"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Output Device:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Expansion:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Synchronization:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Controls</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Output Volume:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSlider" name="volume">
<property name="maximum">
<number>200</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBothSides</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="volumeLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>100%</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="resetVolume">
<property name="toolTip">
<string>Stretch Settings</string>
</property>
<property name="icon">
<iconset theme="refresh-line"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QSlider" name="fastForwardVolume">
<property name="maximum">
<number>200</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBothSides</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fastForwardVolumeLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>100%</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="resetFastForwardVolume">
<property name="toolTip">
<string>Stretch Settings</string>
</property>
<property name="icon">
<iconset theme="refresh-line"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Fast Forward Volume:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="muted">
<property name="text">
<string>Mute All Sound</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<resources>
<include location="../resources/resources.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Audio Stretch Settings&lt;/span&gt;&lt;br/&gt;These settings fine-tune the behavior of the SoundTouch audio time stretcher when running outside of 100% speed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>

View File

@ -432,6 +432,12 @@
<QtUi Include="Settings\ControllerMacroWidget.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Settings\AudioExpansionSettingsDialog.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Settings\AudioStretchSettingsDialog.ui">
<FileType>Document</FileType>
</QtUi>
<None Include="Settings\ControllerBindingWidget_Popn.ui" />
<None Include="Settings\ControllerMappingSettingsDialog.ui" />
<None Include="Settings\FolderSettingsWidget.ui" />

View File

@ -665,6 +665,12 @@
<QtUi Include="Settings\USBBindingWidget_GunCon2.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\AudioExpansionSettingsDialog.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\AudioStretchSettingsDialog.ui">
<Filter>Settings</Filter>
</QtUi>
</ItemGroup>
<ItemGroup>
<None Include="Settings\FolderSettingsWidget.ui">

View File

@ -247,15 +247,12 @@ set(pcsx2CDVDHeaders
set(pcsx2SPU2Sources
SPU2/ADSR.cpp
SPU2/Debug.cpp
SPU2/DplIIdecoder.cpp
SPU2/Dma.cpp
SPU2/Mixer.cpp
SPU2/spu2.cpp
SPU2/ReadInput.cpp
SPU2/RegTable.cpp
SPU2/Reverb.cpp
SPU2/SndOut.cpp
SPU2/SndOut_Cubeb.cpp
SPU2/spu2freeze.cpp
SPU2/spu2sys.cpp
SPU2/Wavedump_wav.cpp
@ -270,21 +267,12 @@ set(pcsx2SPU2Headers
SPU2/Debug.h
SPU2/defs.h
SPU2/Dma.h
SPU2/Global.h
SPU2/interpolate_table.h
SPU2/Mixer.h
SPU2/spu2.h
SPU2/regs.h
SPU2/SndOut.h
SPU2/spdif.h
)
if(WIN32)
list(APPEND pcsx2SPU2Sources
SPU2/SndOut_XAudio2.cpp
)
endif()
# DEV9 sources
set(pcsx2DEV9Sources
DEV9/AdapterUtils.cpp

View File

@ -3,8 +3,11 @@
#pragma once
#include "Host/AudioStreamTypes.h"
#include "common/Pcsx2Defs.h"
#include "common/FPControl.h"
#include <array>
#include <string>
#include <optional>
@ -765,28 +768,22 @@ struct Pcsx2Config
struct SPU2Options
{
enum class SynchronizationMode
enum class SPU2SyncMode : u8
{
Disabled,
TimeStretch,
ASync,
NoSync,
Count
};
static constexpr s32 MAX_VOLUME = 200;
static constexpr s32 MIN_LATENCY = 3;
static constexpr s32 MIN_LATENCY_TIMESTRETCH = 15;
static constexpr s32 MAX_LATENCY = 750;
static constexpr AudioBackend DEFAULT_BACKEND = AudioBackend::Cubeb;
static constexpr SPU2SyncMode DEFAULT_SYNC_MODE = SPU2SyncMode::TimeStretch;
static constexpr s32 MIN_SEQUENCE_LEN = 20;
static constexpr s32 MAX_SEQUENCE_LEN = 100;
static constexpr s32 MIN_SEEKWINDOW = 10;
static constexpr s32 MAX_SEEKWINDOW = 30;
static constexpr s32 MIN_OVERLAP = 5;
static constexpr s32 MAX_OVERLAP = 15;
static std::optional<SPU2SyncMode> ParseSyncMode(const char* str);
static const char* GetSyncModeName(SPU2SyncMode backend);
static const char* GetSyncModeDisplayName(SPU2SyncMode backend);
BITFIELD32()
bool OutputLatencyMinimal : 1;
bool
DebugEnabled : 1,
MsgToConsole : 1,
@ -794,7 +791,6 @@ struct Pcsx2Config
MsgVoiceOff : 1,
MsgDMA : 1,
MsgAutoDMA : 1,
MsgOverruns : 1,
MsgCache : 1,
AccessLog : 1,
DMALog : 1,
@ -805,26 +801,23 @@ struct Pcsx2Config
VisualDebugEnabled : 1;
BITFIELD_END
SynchronizationMode SynchMode = SynchronizationMode::TimeStretch;
u32 OutputVolume = 100;
u32 FastForwardVolume = 100;
bool OutputMuted = false;
s32 FinalVolume = 100;
s32 Latency = 60;
s32 OutputLatency = 20;
s32 SpeakerConfiguration = 0;
s32 DplDecodingLevel = 0;
AudioBackend Backend = DEFAULT_BACKEND;
SPU2SyncMode SyncMode = DEFAULT_SYNC_MODE;
AudioStreamParameters StreamParameters;
s32 SequenceLenMS = 30;
s32 SeekWindowMS = 20;
s32 OverlapMS = 10;
std::string OutputModule;
std::string BackendName;
std::string DriverName;
std::string DeviceName;
SPU2Options();
void LoadSave(SettingsWrapper& wrap);
bool IsTimeStretchEnabled() const { return (SyncMode == SPU2SyncMode::TimeStretch); }
bool operator==(const SPU2Options& right) const;
bool operator!=(const SPU2Options& right) const;
};

View File

@ -8,8 +8,8 @@
#include "GS/Renderers/Common/GSDevice.h"
#include "GS/Renderers/Common/GSTexture.h"
#include "SPU2/spu2.h"
#include "SPU2/SndOut.h"
#include "Host.h"
#include "Host/AudioStream.h"
#include "IconsFontAwesome5.h"
#include "common/Assertions.h"
#include "common/Console.h"
@ -133,7 +133,7 @@ namespace GSCapture
{
static constexpr u32 NUM_FRAMES_IN_FLIGHT = 3;
static constexpr u32 MAX_PENDING_FRAMES = NUM_FRAMES_IN_FLIGHT * 2;
static constexpr u32 AUDIO_BUFFER_SIZE = Common::AlignUpPow2((MAX_PENDING_FRAMES * 48000) / 60, SndOutPacketSize);
static constexpr u32 AUDIO_BUFFER_SIZE = Common::AlignUpPow2((MAX_PENDING_FRAMES * 48000) / 60, AudioStream::CHUNK_SIZE);
static constexpr u32 AUDIO_CHANNELS = 2;
struct PendingFrame
@ -729,7 +729,7 @@ bool GSCapture::BeginCapture(float fps, GSVector2i recommendedResolution, float
// Use packet size for frame if it supports it... but most don't.
if (acodec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
s_audio_frame_size = SndOutPacketSize;
s_audio_frame_size = AudioStream::CHUNK_SIZE;
else
s_audio_frame_size = s_audio_codec_context->frame_size;
if (s_audio_frame_size >= AUDIO_BUFFER_SIZE)
@ -1070,7 +1070,7 @@ void GSCapture::DeliverAudioPacket(const s16* frames)
// through and clear them out for the next capture. If we happen to fill the buffer, *then* we'll lock, and check if
// the capture has stopped.
static constexpr u32 num_frames = static_cast<u32>(SndOutPacketSize);
static constexpr u32 num_frames = AudioStream::CHUNK_SIZE;
if ((AUDIO_BUFFER_SIZE - s_audio_buffer_size.load(std::memory_order_acquire)) < num_frames)
{
@ -1113,7 +1113,7 @@ bool GSCapture::ProcessAudioPackets(s64 video_pts)
while (pending_frames > 0 && (!s_video_codec_context || wrap_av_compare_ts(video_pts, s_video_codec_context->time_base,
s_next_audio_pts, s_audio_codec_context->time_base) > 0))
{
pxAssert(pending_frames >= static_cast<u32>(SndOutPacketSize));
pxAssert(pending_frames >= AudioStream::CHUNK_SIZE);
// In case the encoder is still using it.
if (s_audio_frame_pos == 0)

View File

@ -21,7 +21,7 @@ namespace GSCapture
{
bool BeginCapture(float fps, GSVector2i recommendedResolution, float aspect, std::string filename);
bool DeliverVideoFrame(GSTexture* stex);
void DeliverAudioPacket(const s16* frames); // SndOutPacketSize
void DeliverAudioPacket(const s16* frames); // AudioStream::CHUNK_SIZE
void EndCapture();
bool IsCapturing();

View File

@ -342,15 +342,4 @@ std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const ch
}
return ret;
for (size_t i = 0; i < devices.count; i++)
{
const cubeb_device_info& di = devices.device[i];
if (!di.device_id)
continue;
ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id, 0);
}
return ret;
}

View File

@ -44,11 +44,11 @@ static void HotkeyAdjustVolume(s32 fixed, s32 delta)
if (!VMManager::HasValidVM())
return;
const s32 current_vol = SPU2::GetOutputVolume();
const s32 current_vol = static_cast<s32>(SPU2::GetOutputVolume());
const s32 new_volume =
std::clamp((fixed >= 0) ? fixed : (current_vol + delta), 0, Pcsx2Config::SPU2Options::MAX_VOLUME);
std::clamp((fixed >= 0) ? fixed : (current_vol + delta), 0, static_cast<s32>(Pcsx2Config::SPU2Options::MAX_VOLUME));
if (current_vol != new_volume)
SPU2::SetOutputVolume(new_volume);
SPU2::SetOutputVolume(static_cast<u32>(new_volume));
if (new_volume == 0)
{
@ -197,7 +197,7 @@ DEFINE_HOTKEY("DecreaseVolume", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_N
})
DEFINE_HOTKEY("Mute", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Toggle Mute"), [](s32 pressed) {
if (!pressed && VMManager::HasValidVM())
HotkeyAdjustVolume((SPU2::GetOutputVolume() == 0) ? EmuConfig.SPU2.FinalVolume : 0, 0);
HotkeyAdjustVolume((SPU2::GetOutputVolume() == 0) ? SPU2::GetResetVolume() : 0, 0);
})
DEFINE_HOTKEY(
"FrameAdvance", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Frame Advance"), [](s32 pressed) {

View File

@ -10,6 +10,7 @@
#include "CDVD/CDVDdiscReader.h"
#include "GameList.h"
#include "Host.h"
#include "Host/AudioStream.h"
#include "INISettingsInterface.h"
#include "ImGui/FullscreenUI.h"
#include "ImGui/ImGuiFullscreen.h"
@ -323,6 +324,7 @@ namespace FullscreenUI
static void SetSettingsChanged(SettingsInterface* bsi);
static bool GetEffectiveBoolSetting(SettingsInterface* bsi, const char* section, const char* key, bool default_value);
static s32 GetEffectiveIntSetting(SettingsInterface* bsi, const char* section, const char* key, s32 default_value);
static u32 GetEffectiveUIntSetting(SettingsInterface* bsi, const char* section, const char* key, u32 default_value);
static void DoCopyGameSettings();
static void DoClearGameSettings();
static void CopyGlobalControllerSettingsToGame();
@ -371,6 +373,14 @@ namespace FullscreenUI
float default_value, const char* const* options, const float* option_values, size_t option_count, bool translate_options,
bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
ImFont* summary_font = g_medium_font);
template <typename DataType, typename SizeType>
static void DrawEnumSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,
const char* key, DataType default_value,
std::optional<DataType> (*from_string_function)(const char* str),
const char* (*to_string_function)(DataType value),
const char* (*to_display_string_function)(DataType value), SizeType option_count,
bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT,
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
static void DrawFolderSetting(SettingsInterface* bsi, const char* title, const char* section, const char* key,
const std::string& runtime_var, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
ImFont* summary_font = g_medium_font);
@ -1441,6 +1451,18 @@ s32 FullscreenUI::GetEffectiveIntSetting(SettingsInterface* bsi, const char* sec
return Host::Internal::GetBaseSettingsLayer()->GetIntValue(section, key, default_value);
}
u32 FullscreenUI::GetEffectiveUIntSetting(SettingsInterface* bsi, const char* section, const char* key, u32 default_value)
{
if (IsEditingGameSettings(bsi))
{
std::optional<u32> value = bsi->GetOptionalUIntValue(section, key, std::nullopt);
if (value.has_value())
return value.value();
}
return Host::Internal::GetBaseSettingsLayer()->GetUIntValue(section, key, default_value);
}
void FullscreenUI::DrawInputBindingButton(
SettingsInterface* bsi, InputBindingInfo::Type type, const char* section, const char* name, const char* display_name, const char* icon_name, bool show_type)
{
@ -2443,6 +2465,57 @@ void FullscreenUI::DrawFloatListSetting(SettingsInterface* bsi, const char* titl
}
}
template <typename DataType, typename SizeType>
void FullscreenUI::DrawEnumSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,
const char* key, DataType default_value, std::optional<DataType> (*from_string_function)(const char* str),
const char* (*to_string_function)(DataType value), const char* (*to_display_string_function)(DataType value), SizeType option_count,
bool enabled, float height, ImFont* font, ImFont* summary_font)
{
const bool game_settings = IsEditingGameSettings(bsi);
const std::optional<SmallString> value(bsi->GetOptionalSmallStringValue(
section, key, game_settings ? std::nullopt : std::optional<const char*>(to_string_function(default_value))));
const std::optional<DataType> typed_value(value.has_value() ? from_string_function(value->c_str()) : std::nullopt);
if (MenuButtonWithValue(title, summary,
typed_value.has_value() ? to_display_string_function(typed_value.value()) :
FSUI_CSTR("Use Global Setting"),
enabled, height, font, summary_font))
{
ImGuiFullscreen::ChoiceDialogOptions cd_options;
cd_options.reserve(static_cast<u32>(option_count) + 1);
if (game_settings)
cd_options.emplace_back(FSUI_CSTR("Use Global Setting"), !value.has_value());
for (u32 i = 0; i < static_cast<u32>(option_count); i++)
cd_options.emplace_back(to_display_string_function(static_cast<DataType>(i)),
(typed_value.has_value() && i == static_cast<u32>(typed_value.value())));
OpenChoiceDialog(
title, false, std::move(cd_options),
[section, key, to_string_function, game_settings](s32 index, const std::string& title, bool checked) {
if (index >= 0)
{
auto lock = Host::GetSettingsLock();
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
if (game_settings)
{
if (index == 0)
bsi->DeleteValue(section, key);
else
bsi->SetStringValue(section, key, to_string_function(static_cast<DataType>(index - 1)));
}
else
{
bsi->SetStringValue(section, key, to_string_function(static_cast<DataType>(index)));
}
SetSettingsChanged(bsi);
}
CloseChoiceDialog();
});
}
}
void FullscreenUI::DrawFolderSetting(SettingsInterface* bsi, const char* title, const char* section, const char* key,
const std::string& runtime_var, float height /* = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT */, ImFont* font /* = g_large_font */,
ImFont* summary_font /* = g_medium_font */)
@ -3851,67 +3924,62 @@ void FullscreenUI::DrawGraphicsSettingsPage()
void FullscreenUI::DrawAudioSettingsPage()
{
static constexpr const char* synchronization_modes[] = {
FSUI_NSTR("TimeStretch (Recommended)"),
FSUI_NSTR("Async Mix (Breaks some games!)"),
FSUI_NSTR("None (Audio can skip.)"),
};
static constexpr const char* expansion_modes[] = {
FSUI_NSTR("Stereo (None, Default)"),
FSUI_NSTR("Quadraphonic"),
FSUI_NSTR("Surround 5.1"),
FSUI_NSTR("Surround 7.1"),
};
static constexpr const char* output_entries[] = {
FSUI_NSTR("No Sound (Emulate SPU2 only)"),
FSUI_NSTR("Cubeb (Cross-platform)"),
#ifdef _WIN32
FSUI_NSTR("XAudio2"),
#endif
};
static constexpr const char* output_values[] = {
"nullout",
"cubeb",
#ifdef _WIN32
"xaudio2",
#endif
};
static constexpr const char* default_output_module = "cubeb";
SettingsInterface* bsi = GetEditingSettingsInterface();
BeginMenuButtons();
MenuHeading(FSUI_CSTR("Runtime Settings"));
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_VOLUME_UP, "Output Volume"),
FSUI_CSTR("Applies a global volume modifier to all sound produced by the game."), "SPU2/Mixing", "FinalVolume", 100, 0, 200,
FSUI_CSTR("%d%%"));
MenuHeading(FSUI_CSTR("Audio Control"));
MenuHeading(FSUI_CSTR("Mixing Settings"));
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_SYNC_ALT, "Synchronization Mode"),
FSUI_CSTR("Changes when SPU samples are generated relative to system emulation."), "SPU2/Output", "SynchMode",
static_cast<int>(Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch), synchronization_modes,
std::size(synchronization_modes), true);
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_PF_SPEAKER_ALT, "Expansion Mode"),
FSUI_CSTR("Determines how the stereo output is transformed to greater speaker counts."), "SPU2/Output", "SpeakerConfiguration", 0,
expansion_modes, std::size(expansion_modes), true);
DrawIntRangeSetting(bsi, FSUI_CSTR("Output Volume"),
FSUI_CSTR("Controls the volume of the audio played on the host."), "SPU2/Output", "OutputVolume", 100,
0, 100, "%d%%");
DrawIntRangeSetting(bsi, FSUI_CSTR("Fast Forward Volume"),
FSUI_CSTR("Controls the volume of the audio played on the host when fast forwarding."), "SPU2/Output",
"FastForwardVolume", 100, 0, 100, "%d%%");
DrawToggleSetting(bsi, FSUI_CSTR("Mute All Sound"),
FSUI_CSTR("Prevents the emulator from producing any audible sound."), "SPU2/Output", "OutputMuted",
false);
MenuHeading(FSUI_CSTR("Output Settings"));
DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_PLAY_CIRCLE, "Output Module"),
FSUI_CSTR("Determines which API is used to play back audio samples on the host."), "SPU2/Output", "OutputModule",
default_output_module, output_entries, output_values, std::size(output_entries), true);
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_CLOCK, "Latency"),
FSUI_CSTR("Sets the average output latency when using the cubeb backend."), "SPU2/Output", "Latency", 100, 15, 200, FSUI_CSTR("%d ms (avg)"));
MenuHeading(FSUI_CSTR("Backend Settings"));
MenuHeading(FSUI_CSTR("Timestretch Settings"));
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_RULER_HORIZONTAL, "Sequence Length"),
FSUI_CSTR("Affects how the timestretcher operates when not running at 100% speed."), "Soundtouch", "SequenceLengthMS", 30, 20, 100,
FSUI_CSTR("%d ms"));
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_WINDOW_MAXIMIZE, "Seekwindow Size"),
FSUI_CSTR("Affects how the timestretcher operates when not running at 100% speed."), "Soundtouch", "SeekWindowMS", 20, 10, 30,
FSUI_CSTR("%d ms"));
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_RECEIPT, "Overlap"),
FSUI_CSTR("Affects how the timestretcher operates when not running at 100% speed."), "Soundtouch", "OverlapMS", 20, 5, 15, FSUI_CSTR("%d ms"));
DrawEnumSetting(
bsi, FSUI_CSTR("Audio Backend"),
FSUI_CSTR("The audio backend determines how frames produced by the emulator are submitted to the host."), "SPU2/Output",
"Backend", Pcsx2Config::SPU2Options::DEFAULT_BACKEND, &AudioStream::ParseBackendName, &AudioStream::GetBackendName,
&AudioStream::GetBackendDisplayName, AudioBackend::Count);
DrawEnumSetting(bsi, FSUI_CSTR("Expansion"),
FSUI_CSTR("Determines how audio is expanded from stereo to surround for supported games."), "SPU2/Output",
"ExpansionMode", AudioStreamParameters::DEFAULT_EXPANSION_MODE, &AudioStream::ParseExpansionMode,
&AudioStream::GetExpansionModeName, &AudioStream::GetExpansionModeDisplayName,
AudioExpansionMode::Count);
DrawEnumSetting(bsi, FSUI_CSTR("Synchronization"),
FSUI_CSTR("Changes when SPU samples are generated relative to system emulation."),
"SPU2/Output", "SyncMode", Pcsx2Config::SPU2Options::DEFAULT_SYNC_MODE,
&Pcsx2Config::SPU2Options::ParseSyncMode, &Pcsx2Config::SPU2Options::GetSyncModeName,
&Pcsx2Config::SPU2Options::GetSyncModeDisplayName, Pcsx2Config::SPU2Options::SPU2SyncMode::Count);
DrawIntRangeSetting(bsi, FSUI_CSTR("Buffer Size"),
FSUI_CSTR("Determines the amount of audio buffered before being pulled by the host API."),
"SPU2/Output", "BufferMS", AudioStreamParameters::DEFAULT_BUFFER_MS, 10, 500, "%d ms");
const u32 output_latency =
GetEffectiveUIntSetting(bsi, "SPU2/Output", "OutputLatencyMS", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS);
bool output_latency_minimal = (output_latency == 0);
if (ToggleButton(FSUI_CSTR("Minimal Output Latency"),
FSUI_CSTR("When enabled, the minimum supported output latency will be used for the host API."),
&output_latency_minimal))
{
bsi->SetUIntValue("SPU2/Output", "OutputLatencyMS",
output_latency_minimal ? 0 : AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS);
SetSettingsChanged(bsi);
}
if (!output_latency_minimal)
{
DrawIntRangeSetting(
bsi, FSUI_CSTR("Output Latency"),
FSUI_CSTR("Determines how much latency there is between the audio being picked up by the host API, and "
"played through speakers."),
"SPU2/Output", "OutputLatencyMS", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS, 1, 500, "%d ms");
}
EndMenuButtons();
}

View File

@ -489,8 +489,8 @@ ImFont* ImGuiManager::AddFixedFont(float size)
bool ImGuiManager::AddIconFonts(float size)
{
// clang-format off
static constexpr ImWchar range_fa[] = { 0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf023,0xf025,0xf025,0xf027,0xf028,0xf02b,0xf02b,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03e,0xf04b,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf05e,0xf05e,0xf063,0xf063,0xf067,0xf067,0xf06a,0xf06a,0xf06e,0xf06e,0xf071,0xf071,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf084,0xf091,0xf091,0xf0ac,0xf0ad,0xf0b0,0xf0b0,0xf0c5,0xf0c5,0xf0c7,0xf0c8,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf11b,0xf11c,0xf121,0xf121,0xf129,0xf12a,0xf140,0xf140,0xf144,0xf144,0xf14a,0xf14a,0xf15b,0xf15b,0xf15d,0xf15d,0xf187,0xf188,0xf191,0xf192,0xf1b3,0xf1b3,0xf1de,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf21e,0xf21e,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2bd,0xf2bd,0xf2d0,0xf2d0,0xf2f1,0xf2f2,0xf302,0xf302,0xf3c1,0xf3c1,0xf3fd,0xf3fd,0xf410,0xf410,0xf462,0xf462,0xf466,0xf466,0xf51f,0xf51f,0xf543,0xf543,0xf547,0xf547,0xf54c,0xf54c,0xf553,0xf553,0xf56d,0xf56d,0xf5a2,0xf5a2,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf756,0xf756,0xf794,0xf794,0xf815,0xf815,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 };
static constexpr ImWchar range_pf[] = { 0x2198,0x2199,0x219e,0x21a1,0x21b0,0x21b3,0x21ba,0x21c3,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21f3,0x21f3,0x21f7,0x21f8,0x21fa,0x21fb,0x221a,0x221a,0x227a,0x227f,0x2284,0x2284,0x22bf,0x22c8,0x2349,0x2349,0x235a,0x235e,0x2360,0x2361,0x2364,0x2367,0x237a,0x237b,0x237d,0x237d,0x237f,0x2380,0x23b2,0x23b5,0x23cc,0x23cc,0x23f4,0x23f7,0x2427,0x243a,0x243d,0x243d,0x2443,0x2443,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2605,0x2605,0x2699,0x2699,0x278a,0x278e,0xe001,0xe001,0xff21,0xff3a,0x0,0x0 };
static constexpr ImWchar range_fa[] = { 0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf023,0xf025,0xf025,0xf027,0xf028,0xf02b,0xf02b,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03e,0xf04b,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf05e,0xf05e,0xf063,0xf063,0xf067,0xf067,0xf06a,0xf06a,0xf06e,0xf06e,0xf071,0xf071,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf084,0xf091,0xf091,0xf0ac,0xf0ad,0xf0b0,0xf0b0,0xf0c5,0xf0c5,0xf0c7,0xf0c8,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf11b,0xf11c,0xf121,0xf121,0xf129,0xf12a,0xf140,0xf140,0xf14a,0xf14a,0xf15b,0xf15b,0xf15d,0xf15d,0xf187,0xf188,0xf191,0xf192,0xf1b3,0xf1b3,0xf1de,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf21e,0xf21e,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2bd,0xf2bd,0xf2f2,0xf2f2,0xf302,0xf302,0xf3c1,0xf3c1,0xf3fd,0xf3fd,0xf410,0xf410,0xf462,0xf462,0xf466,0xf466,0xf51f,0xf51f,0xf54c,0xf54c,0xf553,0xf553,0xf56d,0xf56d,0xf5a2,0xf5a2,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf756,0xf756,0xf794,0xf794,0xf815,0xf815,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 };
static constexpr ImWchar range_pf[] = { 0x2198,0x2199,0x219e,0x21a1,0x21b0,0x21b3,0x21ba,0x21c3,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21f3,0x21f3,0x21f7,0x21f8,0x21fa,0x21fb,0x221a,0x221a,0x227a,0x227f,0x2284,0x2284,0x22bf,0x22c8,0x2349,0x2349,0x235a,0x235e,0x2360,0x2361,0x2364,0x2366,0x237a,0x237b,0x237d,0x237d,0x237f,0x2380,0x23b2,0x23b5,0x23cc,0x23cc,0x23f4,0x23f7,0x2427,0x243a,0x243d,0x243d,0x2443,0x2443,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2605,0x2605,0x2699,0x2699,0x278a,0x278e,0xe001,0xe001,0xff21,0xff3a,0x0,0x0 };
// clang-format on
{

View File

@ -240,7 +240,8 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f
if (GSConfig.OsdShowIndicators)
{
const float target_speed = VMManager::GetTargetSpeed();
const bool is_normal_speed = (target_speed == EmuConfig.EmulationSpeed.NominalScalar);
const bool is_normal_speed = (target_speed == EmuConfig.EmulationSpeed.NominalScalar ||
VMManager::IsTargetSpeedAdjustedToHost());
if (!is_normal_speed)
{
if (target_speed == EmuConfig.EmulationSpeed.SlomoScalar) // Slow-Motion

View File

@ -10,12 +10,13 @@
#include "Config.h"
#include "GS.h"
#include "CDVD/CDVDcommon.h"
#include "Host.h"
#include "Host/AudioStream.h"
#include "SIO/Memcard/MemoryCardFile.h"
#include "SIO/Pad/Pad.h"
#include "USB/USB.h"
#include "fmt/format.h"
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#include <KnownFolders.h>
@ -1025,10 +1026,42 @@ bool Pcsx2Config::GSOptions::UseHardwareRenderer() const
return (Renderer != GSRendererType::Null && Renderer != GSRendererType::SW);
}
static constexpr const std::array s_spu2_sync_mode_names = {
"Disabled",
"TimeStretch"
};
static constexpr const std::array s_spu2_sync_mode_display_names = {
TRANSLATE_NOOP("Pcsx2Config", "Disabled (Noisy)"),
TRANSLATE_NOOP("Pcsx2Config", "TimeStretch (Recommended)"),
};
const char* Pcsx2Config::SPU2Options::GetSyncModeName(SPU2SyncMode mode)
{
return (static_cast<size_t>(mode) < s_spu2_sync_mode_names.size()) ? s_spu2_sync_mode_names[static_cast<size_t>(mode)] : "";
}
const char* Pcsx2Config::SPU2Options::GetSyncModeDisplayName(SPU2SyncMode mode)
{
return (static_cast<size_t>(mode) < s_spu2_sync_mode_display_names.size()) ?
Host::TranslateToCString("Pcsx2Config", s_spu2_sync_mode_display_names[static_cast<size_t>(mode)]) :
"";
}
std::optional<Pcsx2Config::SPU2Options::SPU2SyncMode> Pcsx2Config::SPU2Options::ParseSyncMode(const char* name)
{
for (u8 i = 0; i < static_cast<u8>(SPU2SyncMode::Count); i++)
{
if (std::strcmp(name, s_spu2_sync_mode_names[i]) == 0)
return static_cast<SPU2SyncMode>(i);
}
return std::nullopt;
}
Pcsx2Config::SPU2Options::SPU2Options()
{
bitset = 0;
OutputModule = "cubeb";
}
void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
@ -1042,7 +1075,6 @@ void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
SettingsWrapBitBoolEx(MsgVoiceOff, "Show_Messages_Voice_Off");
SettingsWrapBitBoolEx(MsgDMA, "Show_Messages_DMA_Transfer");
SettingsWrapBitBoolEx(MsgAutoDMA, "Show_Messages_AutoDMA");
SettingsWrapBitBoolEx(MsgOverruns, "Show_Messages_Overruns");
SettingsWrapBitBoolEx(MsgCache, "Show_Messages_CacheStats");
SettingsWrapBitBoolEx(AccessLog, "Log_Register_Access");
@ -1061,7 +1093,6 @@ void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
MsgVoiceOff = false;
MsgDMA = false;
MsgAutoDMA = false;
MsgOverruns = false;
MsgCache = false;
AccessLog = false;
DMALog = false;
@ -1071,28 +1102,19 @@ void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
RegDump = false;
}
}
{
SettingsWrapSection("SPU2/Mixing");
SettingsWrapEntry(FinalVolume);
}
{
SettingsWrapSection("SPU2/Output");
SettingsWrapEntry(OutputModule);
SettingsWrapEntry(BackendName);
SettingsWrapEntry(OutputVolume);
SettingsWrapEntry(FastForwardVolume);
SettingsWrapEntry(OutputMuted);
SettingsWrapParsedEnum(Backend, "Backend", &AudioStream::ParseBackendName, &AudioStream::GetBackendName);
SettingsWrapParsedEnum(SyncMode, "SyncMode", &ParseSyncMode, &GetSyncModeName);
SettingsWrapEntry(DriverName);
SettingsWrapEntry(DeviceName);
SettingsWrapEntry(Latency);
SettingsWrapEntry(OutputLatency);
SettingsWrapBitBool(OutputLatencyMinimal);
SynchMode = static_cast<SynchronizationMode>(wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, "SynchMode", static_cast<int>(SynchMode), static_cast<int>(SynchMode)));
SettingsWrapEntry(SpeakerConfiguration);
SettingsWrapEntry(DplDecodingLevel);
StreamParameters.LoadSave(wrap, CURRENT_SETTINGS_SECTION);
}
}
// clampy clamp
}
bool Pcsx2Config::SPU2Options::operator!=(const SPU2Options& right) const
{
@ -1102,21 +1124,12 @@ bool Pcsx2Config::SPU2Options::operator!=(const SPU2Options& right) const
bool Pcsx2Config::SPU2Options::operator==(const SPU2Options& right) const
{
return OpEqu(bitset) &&
OpEqu(SynchMode) &&
OpEqu(FinalVolume) &&
OpEqu(Latency) &&
OpEqu(OutputLatency) &&
OpEqu(SpeakerConfiguration) &&
OpEqu(DplDecodingLevel) &&
OpEqu(SequenceLenMS) &&
OpEqu(SeekWindowMS) &&
OpEqu(OverlapMS) &&
OpEqu(OutputModule) &&
OpEqu(BackendName) &&
OpEqu(OutputVolume) &&
OpEqu(FastForwardVolume) &&
OpEqu(OutputMuted) &&
OpEqu(Backend) &&
OpEqu(StreamParameters) &&
OpEqu(DriverName) &&
OpEqu(DeviceName);
}

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SPU2/Global.h"
#include "SPU2/defs.h"
#include "common/Assertions.h"

View File

@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SPU2/Global.h"
#include "SPU2/Debug.h"
#include "SPU2/regs.h"
#include "Config.h"
#include "common/Console.h"

View File

@ -5,6 +5,8 @@
#include "Config.h"
#include "SPU2/defs.h"
namespace SPU2
{
#ifdef PCSX2_DEVBUILD
@ -17,7 +19,6 @@ namespace SPU2
__fi static bool MsgVoiceOff() { return EmuConfig.SPU2.MsgVoiceOff; }
__fi static bool MsgDMA() { return EmuConfig.SPU2.MsgDMA; }
__fi static bool MsgAutoDMA() { return EmuConfig.SPU2.MsgAutoDMA; }
__fi static bool MsgOverruns() { return EmuConfig.SPU2.MsgOverruns; }
__fi static bool MsgCache() { return EmuConfig.SPU2.MsgCache; }
__fi static bool AccessLog() { return EmuConfig.SPU2.AccessLog; }
@ -93,7 +94,7 @@ namespace WaveDump
extern void Open();
extern void Close();
extern void WriteCore(uint coreidx, CoreSourceType src, s16 left, s16 right);
extern void WriteCore(uint coreidx, CoreSourceType src, const StereoOut16& sample);
extern void WriteCore(uint coreidx, CoreSourceType src, const StereoOut32& sample);
} // namespace WaveDump
using WaveDump::CoreSrc_DryVoiceMix;

View File

@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SPU2/Global.h"
#include "SPU2/defs.h"
#include "SPU2/Debug.h"
#include "SPU2/Dma.h"
#include "SPU2/spu2.h"
#include "R3000A.h"

View File

@ -1,158 +0,0 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "Global.h"
// FIXME Not yet used so let's comment it out.
/*static const u8 sLogTable[256] = {
0x00, 0x3C, 0x60, 0x78, 0x8C, 0x9C, 0xA8, 0xB4, 0xBE, 0xC8, 0xD0, 0xD8, 0xDE, 0xE4, 0xEA, 0xF0,
0xF6, 0xFA, 0xFE, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x16, 0x1A, 0x1E, 0x20, 0x24, 0x26, 0x2A, 0x2C,
0x2E, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3E, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E, 0x50,
0x50, 0x52, 0x54, 0x56, 0x58, 0x5A, 0x5A, 0x5C, 0x5E, 0x60, 0x60, 0x62, 0x64, 0x66, 0x66, 0x68,
0x6A, 0x6A, 0x6C, 0x6E, 0x6E, 0x70, 0x70, 0x72, 0x74, 0x74, 0x76, 0x76, 0x78, 0x7A, 0x7A, 0x7C,
0x7C, 0x7E, 0x7E, 0x80, 0x80, 0x82, 0x82, 0x84, 0x84, 0x86, 0x86, 0x88, 0x88, 0x8A, 0x8A, 0x8C,
0x8C, 0x8C, 0x8E, 0x8E, 0x90, 0x90, 0x92, 0x92, 0x92, 0x94, 0x94, 0x96, 0x96, 0x96, 0x98, 0x98,
0x9A, 0x9A, 0x9A, 0x9C, 0x9C, 0x9C, 0x9E, 0x9E, 0xA0, 0xA0, 0xA0, 0xA2, 0xA2, 0xA2, 0xA4, 0xA4,
0xA4, 0xA6, 0xA6, 0xA6, 0xA8, 0xA8, 0xA8, 0xAA, 0xAA, 0xAA, 0xAC, 0xAC, 0xAC, 0xAC, 0xAE, 0xAE,
0xAE, 0xB0, 0xB0, 0xB0, 0xB2, 0xB2, 0xB2, 0xB2, 0xB4, 0xB4, 0xB4, 0xB6, 0xB6, 0xB6, 0xB6, 0xB8,
0xB8, 0xB8, 0xB8, 0xBA, 0xBA, 0xBA, 0xBC, 0xBC, 0xBC, 0xBC, 0xBE, 0xBE, 0xBE, 0xBE, 0xC0, 0xC0,
0xC0, 0xC0, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC4, 0xC4, 0xC4, 0xC4, 0xC6, 0xC6, 0xC6, 0xC6, 0xC8,
0xC8, 0xC8, 0xC8, 0xC8, 0xCA, 0xCA, 0xCA, 0xCA, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCE, 0xCE, 0xCE,
0xCE, 0xCE, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD4, 0xD4, 0xD4, 0xD4,
0xD4, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xDA, 0xDA, 0xDA, 0xDA,
0xDA, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xE0, 0xE0, 0xE0,
};*/
static float Gfl = 0, Gfr = 0;
static float LMax = 0, RMax = 0;
static float AccL = 0;
static float AccR = 0;
constexpr float Scale = 4294967296.0f; // tweak this value to change the overall output volume
constexpr float GainL = 0.80f * Scale;
constexpr float GainR = 0.80f * Scale;
constexpr float GainC = 0.75f * Scale;
constexpr float GainSL = 0.90f * Scale;
constexpr float GainSR = 0.90f * Scale;
constexpr float GainLFE = 0.90f * Scale;
constexpr float AddCLR = 0.20f * Scale; // Stereo expansion
extern void ResetDplIIDecoder()
{
Gfl = 0;
Gfr = 0;
LMax = 0;
RMax = 0;
AccL = 0;
AccR = 0;
}
void ProcessDplIISample32(const StereoOut16& src, Stereo51Out32DplII* s)
{
const float IL = src.Left / static_cast<float>(1 << 16);
const float IR = src.Right / static_cast<float>(1 << 16);
// Calculate center channel and LFE
const float C = (IL + IR) * 0.5f;
const float SUB = C; // no need to lowpass, the speaker amplifier should take care of it
float L = IL - C; // Effective L/R data
float R = IR - C;
// Peak L/R
const float PL = std::abs(L);
const float PR = std::abs(R);
AccL += (PL - AccL) * 0.1f;
AccR += (PR - AccR) * 0.1f;
// Calculate power balance
const float Balance = (AccR - AccL); // -1 .. 1
// If the power levels are different, then the audio is meant for the front speakers
const float Frontness = std::abs(Balance);
const float Rearness = 1 - Frontness; // And the other way around
// Equalize the power levels for L/R
const float B = std::min(0.9f, std::max(-0.9f, Balance));
const float VL = L / (1 - B); // if B>0, it means R>L, so increase L, else decrease L
const float VR = R / (1 + B); // vice-versa
// 1.73+1.22 = 2.94; 2.94 = 0.34 = 0.9996; Close enough.
// The range for VL/VR is approximately 0..1,
// But in the cases where VL/VR are > 0.5, Rearness is 0 so it should never overflow.
constexpr float RearScale = 0.34f * 2;
const float SL = (VR * 1.73f - VL * 1.22f) * RearScale * Rearness;
const float SR = (VR * 1.22f - VL * 1.73f) * RearScale * Rearness;
// Possible experiment: Play with stereo expension levels on rear
// Adjust the volume of the front speakers based on what we calculated above
L *= Frontness;
R *= Frontness;
const s32 CX = static_cast<s32>(C * AddCLR);
s->Left = static_cast<s32>(L * GainL) + CX;
s->Right = static_cast<s32>(R * GainR) + CX;
s->Center = static_cast<s32>(C * GainC);
s->LFE = static_cast<s32>(SUB * GainLFE);
s->LeftBack = static_cast<s32>(SL * GainSL);
s->RightBack = static_cast<s32>(SR * GainSR);
}
void ProcessDplIISample16(const StereoOut16& src, Stereo51Out16DplII* s)
{
Stereo51Out32DplII ss;
ProcessDplIISample32(src, &ss);
s->Left = ss.Left >> 16;
s->Right = ss.Right >> 16;
s->Center = ss.Center >> 16;
s->LFE = ss.LFE >> 16;
s->LeftBack = ss.LeftBack >> 16;
s->RightBack = ss.RightBack >> 16;
}
void ProcessDplSample32(const StereoOut16& src, Stereo51Out32Dpl* s)
{
const float ValL = src.Left / static_cast<float>(1 << 16);
const float ValR = src.Right / static_cast<float>(1 << 16);
const float C = (ValL + ValR) * 0.5f; //+15.8
const float S = (ValL - ValR) * 0.5f;
const float L = ValL - C; //+15.8
const float R = ValR - C;
const float SUB = C;
const s32 CX = static_cast<s32>(C * AddCLR); // +15.16
s->Left = static_cast<s32>(L * GainL) + CX; // +15.16 = +31, can grow to +32 if (GainL + AddCLR)>255
s->Right = static_cast<s32>(R * GainR) + CX;
s->Center = static_cast<s32>(C * GainC); // +15.16 = +31
s->LFE = static_cast<s32>(SUB * GainLFE);
s->LeftBack = static_cast<s32>(S * GainSL);
s->RightBack = static_cast<s32>(S * GainSR);
}
void ProcessDplSample16(const StereoOut16& src, Stereo51Out16Dpl* s)
{
Stereo51Out32Dpl ss;
ProcessDplSample32(src, &ss);
s->Left = ss.Left >> 16;
s->Right = ss.Right >> 16;
s->Center = ss.Center >> 16;
s->LFE = ss.LFE >> 16;
s->LeftBack = ss.LeftBack >> 16;
s->RightBack = ss.RightBack >> 16;
}

View File

@ -1,17 +0,0 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
struct StereoOut16;
struct StereoOut32;
struct StereoOutFloat;
struct V_Core;
#include "defs.h"
#include "regs.h"
#include "Debug.h"
#include "Mixer.h"
#include "SndOut.h"

View File

@ -1,12 +1,14 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "common/Assertions.h"
#include "SPU2/Global.h"
#include "Host/AudioStream.h"
#include "SPU2/Debug.h"
#include "SPU2/defs.h"
#include "SPU2/spu2.h"
#include "SPU2/interpolate_table.h"
#include "common/Assertions.h"
static const s32 tbl_XA_Factor[16][2] =
{
{0, 0},
@ -565,16 +567,7 @@ static StereoOut32 DCFilter(StereoOut32 input) {
return output;
}
// used to throttle the output rate of cache stat reports
static int p_cachestat_counter = 0;
// Gcc does not want to inline it when lto is enabled because some functions growth too much.
// The function is big enought to see any speed impact. -- Gregory
#ifndef __POSIX__
__forceinline
#endif
void
Mix()
__forceinline void spu2Mix()
{
// Note: Playmode 4 is SPDIF, which overrides other inputs.
StereoOut32 InputData[2] =
@ -638,17 +631,23 @@ __forceinline
Out = ApplyVolume(Out, {0x4fff, 0x4fff});
Out = DCFilter(Out);
// Final clamp, take care not to exceed 16 bits from here on
Out = clamp_mix(Out);
SndBuffer::Write(StereoOut16(Out));
#ifdef PCSX2_DEVBUILD
// Log final output to wavefile.
WaveDump::WriteCore(1, CoreSrc_External, Out);
#endif
spu2Output(Out);
// Update AutoDMA output positioning
OutPos++;
if (OutPos >= 0x200)
OutPos = 0;
if (IsDevBuild)
if constexpr (IsDevBuild)
{
// used to throttle the output rate of cache stat reports
static int p_cachestat_counter = 0;
p_cachestat_counter++;
if (p_cachestat_counter > (48000 * 10))
{

View File

@ -1,6 +0,0 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
extern void Mix();

View File

@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "Global.h"
#include "Dma.h"
#include "IopDma.h"
#include "IopHw.h"
#include "spu2.h" // required for ENABLE_NEW_IOPDMA_SPU2 define
#include "SPU2/Debug.h"
#include "SPU2/defs.h"
#include "SPU2/spu2.h"
// Core 0 Input is "SPDIF mode" - Source audio is AC3 compressed.

View File

@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "Global.h"
#include "SPU2/defs.h"
#include "SPU2/regs.h"
#define U16P(x) ((u16*)&(x))

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "Global.h"
#include "SPU2/defs.h"
#include "GS/GSVector.h"
#include "common/Console.h"

View File

@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "GS/GSVector.h"
#include "Global.h"
#include "SPU2/defs.h"
MULTI_ISA_UNSHARED_START

File diff suppressed because it is too large Load Diff

View File

@ -1,334 +0,0 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include <algorithm>
#include <span>
#include <string>
#include <vector>
#include "common/Pcsx2Defs.h"
// Number of stereo samples per SndOut block.
// All drivers must work in units of this size when communicating with
// SndOut.
static constexpr int SndOutPacketSize = 64;
// Samplerate of the SPU2. For accurate playback we need to match this
// exactly. Trying to scale samplerates and maintain SPU2's Ts timing accuracy
// is too problematic. :)
extern int SampleRate;
// Returns a null-terminated list of backends for the specified module.
// nullptr is returned if the specified module does not have multiple backends.
extern const char* const* GetOutputModuleBackends(const char* omodid);
// Returns a list of output devices and their associated minimum latency.
struct SndOutDeviceInfo
{
std::string name;
std::string display_name;
u32 minimum_latency_frames;
SndOutDeviceInfo(std::string name_, std::string display_name_, u32 minimum_latency_);
~SndOutDeviceInfo();
};
std::vector<SndOutDeviceInfo> GetOutputDeviceList(const char* omodid, const char* driver);
struct StereoOut16;
struct Stereo51Out16DplII;
struct Stereo51Out32DplII;
struct Stereo51Out16Dpl; // similar to DplII but without rear balancing
struct Stereo51Out32Dpl;
extern void ResetDplIIDecoder();
extern void ProcessDplIISample16(const StereoOut16& src, Stereo51Out16DplII* s);
extern void ProcessDplIISample32(const StereoOut16& src, Stereo51Out32DplII* s);
extern void ProcessDplSample16(const StereoOut16& src, Stereo51Out16Dpl* s);
extern void ProcessDplSample32(const StereoOut16& src, Stereo51Out32Dpl* s);
struct StereoOut32
{
static const StereoOut32 Empty;
s32 Left;
s32 Right;
StereoOut32() = default;
StereoOut32(s32 left, s32 right)
: Left(left)
, Right(right)
{
}
StereoOut32 operator*(const int& factor) const
{
return StereoOut32(
Left * factor,
Right * factor);
}
StereoOut32& operator*=(const int& factor)
{
Left *= factor;
Right *= factor;
return *this;
}
StereoOut32 operator+(const StereoOut32& right) const
{
return StereoOut32(
Left + right.Left,
Right + right.Right);
}
StereoOut32 operator/(int src) const
{
return StereoOut32(Left / src, Right / src);
}
};
struct StereoOut16
{
s16 Left;
s16 Right;
StereoOut16() = default;
__fi StereoOut16(const StereoOut32& src)
: Left((s16)src.Left)
, Right((s16)src.Right)
{
}
__fi StereoOut16(s16 left, s16 right)
: Left(left)
, Right(right)
{
}
__fi StereoOut16 ApplyVolume(float volume)
{
return StereoOut16(
static_cast<s16>(std::clamp(static_cast<float>(Left) * volume, -32768.0f, 32767.0f)),
static_cast<s16>(std::clamp(static_cast<float>(Right) * volume, -32768.0f, 32767.0f))
);
}
__fi void SetFrom(const StereoOut16& src)
{
Left = src.Left;
Right = src.Right;
}
};
struct Stereo21Out16
{
s16 Left;
s16 Right;
s16 LFE;
__fi void SetFrom(const StereoOut16& src)
{
Left = src.Left;
Right = src.Right;
LFE = (src.Left + src.Right) >> 1;
}
};
struct Stereo40Out16
{
s16 Left;
s16 Right;
s16 LeftBack;
s16 RightBack;
__fi void SetFrom(const StereoOut16& src)
{
Left = src.Left;
Right = src.Right;
LeftBack = src.Left;
RightBack = src.Right;
}
};
struct Stereo41Out16
{
s16 Left;
s16 Right;
s16 LFE;
s16 LeftBack;
s16 RightBack;
__fi void SetFrom(const StereoOut16& src)
{
Left = src.Left;
Right = src.Right;
LFE = (src.Left + src.Right) >> 1;
LeftBack = src.Left;
RightBack = src.Right;
}
};
struct Stereo51Out16
{
s16 Left;
s16 Right;
s16 Center;
s16 LFE;
s16 LeftBack;
s16 RightBack;
// Implementation Note: Center and Subwoofer/LFE -->
// This method is simple and sounds nice. It relies on the speaker/soundcard
// systems do to their own low pass / crossover. Manual lowpass is wasted effort
// and can't match solid state results anyway.
__fi void SetFrom(const StereoOut16& src)
{
Left = src.Left;
Right = src.Right;
Center = (src.Left + src.Right) >> 1;
LFE = Center;
LeftBack = src.Left >> 1;
RightBack = src.Right >> 1;
}
};
struct Stereo51Out16DplII
{
s16 Left;
s16 Right;
s16 Center;
s16 LFE;
s16 LeftBack;
s16 RightBack;
__fi void SetFrom(const StereoOut16& src)
{
ProcessDplIISample16(src, this);
}
};
struct Stereo51Out32DplII
{
s32 Left;
s32 Right;
s32 Center;
s32 LFE;
s32 LeftBack;
s32 RightBack;
__fi void SetFrom(const StereoOut32& src)
{
ProcessDplIISample32(src, this);
}
};
struct Stereo51Out16Dpl
{
s16 Left;
s16 Right;
s16 Center;
s16 LFE;
s16 LeftBack;
s16 RightBack;
__fi void SetFrom(const StereoOut16& src)
{
ProcessDplSample16(src, this);
}
};
struct Stereo51Out32Dpl
{
s32 Left;
s32 Right;
s32 Center;
s32 LFE;
s32 LeftBack;
s32 RightBack;
__fi void SetFrom(const StereoOut32& src)
{
ProcessDplSample32(src, this);
}
};
struct Stereo71Out16
{
s16 Left;
s16 Right;
s16 Center;
s16 LFE;
s16 LeftBack;
s16 RightBack;
s16 LeftSide;
s16 RightSide;
__fi void SetFrom(const StereoOut16& src)
{
Left = src.Left;
Right = src.Right;
Center = (src.Left + src.Right) >> 1;
LFE = Center;
LeftBack = src.Left;
RightBack = src.Right;
LeftSide = src.Left >> 1;
RightSide = src.Right >> 1;
}
};
namespace SndBuffer
{
void UpdateTempoChangeAsyncMixing();
bool Init(const char* modname);
bool IsOpen();
void Cleanup();
void Write(StereoOut16 Sample);
void ClearContents();
void ResetBuffers();
// Note: When using with 32 bit output buffers, the user of this function is responsible
// for shifting the values to where they need to be manually. The fixed point depth of
// the sample output is determined by the SndOutVolumeShift, which is the number of bits
// to shift right to get a 16 bit result.
template <typename T>
void ReadSamples(T* bData, int nSamples = SndOutPacketSize);
}
class SndOutModule
{
public:
virtual ~SndOutModule() = default;
// Returns a unique identification string for this driver.
// (usually just matches the driver's cpp filename)
virtual const char* GetIdent() const = 0;
// Returns the full name for this driver, and can be translated.
virtual const char* GetDisplayName() const = 0;
// Returns a null-terminated list of backends, or nullptr.
virtual const char* const* GetBackendNames() const = 0;
// Returns a list of output devices and their associated minimum latency.
virtual std::vector<SndOutDeviceInfo> GetOutputDeviceList(const char* driver) const = 0;
virtual bool Init() = 0;
virtual void Close() = 0;
// Temporarily pauses the stream, preventing it from requesting data.
virtual void SetPaused(bool paused) = 0;
// Returns the number of empty samples in the output buffer.
// (which is effectively the amount of data played since the last update)
virtual int GetEmptySampleCount() = 0;
};
std::span<SndOutModule*> GetSndOutModules();

View File

@ -1,429 +0,0 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SPU2/Global.h"
#include "SPU2/SndOut.h"
#include "Host.h"
#include "IconsFontAwesome5.h"
#include "common/Assertions.h"
#include "common/Console.h"
#include "common/StringUtil.h"
#include "common/RedtapeWindows.h"
#include "common/ScopedGuard.h"
#include "cubeb/cubeb.h"
#ifdef _WIN32
#include <objbase.h>
#endif
class Cubeb : public SndOutModule
{
private:
//////////////////////////////////////////////////////////////////////////////////////////
// Stuff necessary for speaker expansion
class SampleReader
{
public:
virtual ~SampleReader() = default;
virtual void ReadSamples(void* outputBuffer, long frames) = 0;
};
template <class T>
class ConvertedSampleReader final : public SampleReader
{
u64* const written;
public:
ConvertedSampleReader() = delete;
explicit ConvertedSampleReader(u64* pWritten)
: written(pWritten)
{
}
void ReadSamples(void* outputBuffer, long frames) override
{
T* p1 = static_cast<T*>(outputBuffer);
while (frames > 0)
{
const long frames_to_read = std::min<long>(frames, SndOutPacketSize);
SndBuffer::ReadSamples(p1, frames_to_read);
p1 += frames_to_read;
frames -= frames_to_read;
}
(*written) += frames;
}
};
void DestroyContextAndStream()
{
if (stream)
{
cubeb_stream_stop(stream);
cubeb_stream_destroy(stream);
stream = nullptr;
}
if (m_context)
{
cubeb_destroy(m_context);
m_context = nullptr;
}
ActualReader.reset();
#ifdef _WIN32
if (m_COMInitializedByUs)
{
CoUninitialize();
m_COMInitializedByUs = false;
}
#endif
}
static void LogCallback(const char* fmt, ...)
{
std::va_list ap;
va_start(ap, fmt);
std::string msg(StringUtil::StdStringFromFormatV(fmt, ap));
va_end(ap);
Console.WriteLn("(Cubeb): %s", msg.c_str());
}
//////////////////////////////////////////////////////////////////////////////////////////
// Configuration Vars
#ifdef _WIN32
bool m_COMInitializedByUs = false;
#endif
//////////////////////////////////////////////////////////////////////////////////////////
// Instance vars
u64 writtenSoFar = 0;
u64 writtenLastTime = 0;
u64 positionLastTime = 0;
u32 channels = 0;
cubeb* m_context = nullptr;
cubeb_stream* stream = nullptr;
std::unique_ptr<SampleReader> ActualReader;
bool m_paused = false;
public:
Cubeb() = default;
~Cubeb()
{
DestroyContextAndStream();
}
bool Init() override
{
if (stream)
pxFailRel("Cubeb stream already open in Init()");
#ifdef _WIN32
const HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
m_COMInitializedByUs = SUCCEEDED(hr);
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE)
{
Host::ReportErrorAsync("Cubeb Error", "Failed to initialize COM");
return false;
}
#endif
#ifdef PCSX2_DEVBUILD
cubeb_set_log_callback(CUBEB_LOG_NORMAL, LogCallback);
#endif
int rv = cubeb_init(&m_context, "PCSX2", EmuConfig.SPU2.BackendName.empty() ? nullptr : EmuConfig.SPU2.BackendName.c_str());
if (rv != CUBEB_OK)
{
Host::ReportFormattedErrorAsync("Cubeb Error", "Could not initialize cubeb context: %d", rv);
return false;
}
switch (EmuConfig.SPU2.SpeakerConfiguration) // speakers = (numSpeakers + 1) *2; ?
{
case 1:
channels = 4;
break; // Quadrafonic
case 2:
channels = 6;
break; // Surround 5.1
case 3:
channels = 8;
break; // Surround 7.1
default:
channels = 2;
break; // Stereo
}
cubeb_channel_layout layout = CUBEB_LAYOUT_UNDEFINED;
switch (channels)
{
case 2:
Console.WriteLn("(Cubeb) Using normal 2 speaker stereo output.");
ActualReader = std::make_unique<ConvertedSampleReader<StereoOut16>>(&writtenSoFar);
break;
case 3:
Console.WriteLn("(Cubeb) 2.1 speaker expansion enabled.");
ActualReader = std::make_unique<ConvertedSampleReader<Stereo21Out16>>(&writtenSoFar);
layout = CUBEB_LAYOUT_STEREO_LFE;
break;
case 4:
Console.WriteLn("(Cubeb) 4 speaker expansion enabled [quadraphenia]");
ActualReader = std::make_unique<ConvertedSampleReader<Stereo40Out16>>(&writtenSoFar);
layout = CUBEB_LAYOUT_QUAD;
break;
case 5:
Console.WriteLn("(Cubeb) 4.1 speaker expansion enabled.");
ActualReader = std::make_unique<ConvertedSampleReader<Stereo41Out16>>(&writtenSoFar);
layout = CUBEB_LAYOUT_QUAD_LFE;
break;
case 6:
case 7:
switch (EmuConfig.SPU2.DplDecodingLevel)
{
case 1:
Console.WriteLn("(Cubeb) 5.1 speaker expansion with basic ProLogic dematrixing enabled.");
ActualReader = std::make_unique<ConvertedSampleReader<Stereo51Out16Dpl>>(&writtenSoFar); // basic Dpl decoder without rear stereo balancing
break;
case 2:
Console.WriteLn("(Cubeb) 5.1 speaker expansion with experimental ProLogicII dematrixing enabled.");
ActualReader = std::make_unique<ConvertedSampleReader<Stereo51Out16DplII>>(&writtenSoFar); //gigas PLII
break;
default:
Console.WriteLn("(Cubeb) 5.1 speaker expansion enabled.");
ActualReader = std::make_unique<ConvertedSampleReader<Stereo51Out16>>(&writtenSoFar); //"normal" stereo upmix
break;
}
channels = 6; // we do not support 7.0 or 6.2 configurations, downgrade to 5.1
layout = CUBEB_LAYOUT_3F2_LFE;
break;
default: // anything 8 or more gets the 7.1 treatment!
Console.WriteLn("(Cubeb) 7.1 speaker expansion enabled.");
ActualReader = std::make_unique<ConvertedSampleReader<Stereo71Out16>>(&writtenSoFar);
channels = 8; // we do not support 7.2 or more, downgrade to 7.1
layout = CUBEB_LAYOUT_3F4_LFE;
break;
}
cubeb_stream_params params = {};
params.format = CUBEB_SAMPLE_S16LE;
params.rate = SampleRate;
params.channels = channels;
params.layout = layout;
params.prefs = CUBEB_STREAM_PREF_NONE;
const u32 requested_latency_frames = static_cast<u32>((EmuConfig.SPU2.OutputLatency * SampleRate) / 1000u);
u32 latency_frames = 0;
rv = cubeb_get_min_latency(m_context, &params, &latency_frames);
if (rv == CUBEB_ERROR_NOT_SUPPORTED)
{
Console.WriteLn("(Cubeb) Cubeb backend does not support latency queries, using latency of %d ms (%u frames).",
EmuConfig.SPU2.OutputLatency, requested_latency_frames);
latency_frames = requested_latency_frames;
}
else
{
if (rv != CUBEB_OK)
{
Console.Error("(Cubeb) Could not get minimum latency: %d", rv);
DestroyContextAndStream();
return false;
}
const float minimum_latency_ms = static_cast<float>(latency_frames * 1000u) / static_cast<float>(SampleRate);
Console.WriteLn("(Cubeb) Minimum latency: %.2f ms (%u audio frames)", minimum_latency_ms, latency_frames);
if (!EmuConfig.SPU2.OutputLatencyMinimal)
{
if (latency_frames > requested_latency_frames)
{
Console.Warning("(Cubeb) Minimum latency is above requested latency: %u vs %u, adjusting to compensate.",
latency_frames, requested_latency_frames);
}
else
{
latency_frames = requested_latency_frames;
}
}
}
cubeb_devid selected_device = nullptr;
const std::string& selected_device_name = EmuConfig.SPU2.DeviceName;
cubeb_device_collection devices;
if (!selected_device_name.empty())
{
rv = cubeb_enumerate_devices(m_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
if (rv == CUBEB_OK)
{
for (size_t i = 0; i < devices.count; i++)
{
const cubeb_device_info& di = devices.device[i];
if (di.device_id && selected_device_name == di.device_id)
{
Console.WriteLn("Using output device '%s' (%s).", di.device_id, di.friendly_name ? di.friendly_name : di.device_id);
selected_device = di.devid;
break;
}
}
if (!selected_device)
{
Host::AddIconOSDMessage("CubebDeviceNotFound", ICON_FA_VOLUME_MUTE,
fmt::format(
TRANSLATE_FS("SPU2", "Requested audio output device '{}' not found, using default."),
selected_device_name),
Host::OSD_WARNING_DURATION);
}
}
else
{
Console.Error("cubeb_enumerate_devices() returned %d, using default device.", rv);
}
}
char stream_name[32];
std::snprintf(stream_name, sizeof(stream_name), "%p", this);
rv = cubeb_stream_init(m_context, &stream, stream_name, nullptr, nullptr, selected_device, &params,
latency_frames, &Cubeb::DataCallback, &Cubeb::StateCallback, this);
if (rv != CUBEB_OK)
{
Console.Error("(Cubeb) Could not create stream: %d", rv);
DestroyContextAndStream();
return false;
}
rv = cubeb_stream_start(stream);
if (rv != CUBEB_OK)
{
Console.Error("(Cubeb) Could not start stream: %d", rv);
DestroyContextAndStream();
return false;
}
m_paused = false;
return true;
}
void Close() override
{
DestroyContextAndStream();
}
static void StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state)
{
}
static long DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer, long nframes)
{
static_cast<Cubeb*>(user_ptr)->ActualReader->ReadSamples(output_buffer, nframes);
return nframes;
}
void SetPaused(bool paused) override
{
if (paused == m_paused || !stream)
return;
const int rv = paused ? cubeb_stream_stop(stream) : cubeb_stream_start(stream);
if (rv != CUBEB_OK)
{
Console.Error("(Cubeb) Could not %s stream: %d", paused ? "pause" : "resume", rv);
return;
}
m_paused = paused;
}
int GetEmptySampleCount() override
{
u64 pos;
if (cubeb_stream_get_position(stream, &pos) != CUBEB_OK)
pos = 0;
const int playedSinceLastTime = (writtenSoFar - writtenLastTime) + (pos - positionLastTime);
writtenLastTime = writtenSoFar;
positionLastTime = pos;
return playedSinceLastTime;
}
const char* GetIdent() const override
{
return "cubeb";
}
const char* GetDisplayName() const override
{
//: Cubeb is an audio engine name. Leave as-is.
return TRANSLATE_NOOP("SPU2", "Cubeb (Cross-platform)");
}
const char* const* GetBackendNames() const override
{
return cubeb_get_backend_names();
}
std::vector<SndOutDeviceInfo> GetOutputDeviceList(const char* driver) const override
{
std::vector<SndOutDeviceInfo> ret;
ret.emplace_back(std::string(), "Default", 0u);
cubeb* context;
int rv = cubeb_init(&context, "PCSX2", (driver && *driver) ? driver : nullptr);
if (rv != CUBEB_OK)
{
Console.Error("(GetOutputDeviceList) cubeb_init() failed: %d", rv);
return ret;
}
ScopedGuard context_cleanup([context]() { cubeb_destroy(context); });
cubeb_device_collection devices;
rv = cubeb_enumerate_devices(context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
if (rv != CUBEB_OK)
{
Console.Error("(GetOutputDeviceList) cubeb_enumerate_devices() failed: %d", rv);
return ret;
}
ScopedGuard devices_cleanup([context, &devices]() { cubeb_device_collection_destroy(context, &devices); });
// we need stream parameters to query latency
cubeb_stream_params params = {};
params.format = CUBEB_SAMPLE_S16LE;
params.rate = SampleRate;
params.channels = 2;
params.layout = CUBEB_LAYOUT_UNDEFINED;
params.prefs = CUBEB_STREAM_PREF_NONE;
u32 min_latency = 0;
cubeb_get_min_latency(context, &params, &min_latency);
ret[0].minimum_latency_frames = min_latency;
for (size_t i = 0; i < devices.count; i++)
{
const cubeb_device_info& di = devices.device[i];
if (!di.device_id)
continue;
ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id, min_latency);
}
return ret;
}
};
static Cubeb s_Cubeb;
SndOutModule* CubebOut = &s_Cubeb;

View File

@ -1,365 +0,0 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SPU2/Global.h"
#include "Host.h"
#include "common/Console.h"
#include "common/RedtapeWindows.h"
#include "common/RedtapeWilCom.h"
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <wil/resource.h>
#include <wil/win32_helpers.h>
#include <xaudio2.h>
//#define XAUDIO2_DEBUG
class XAudio2Mod final : public SndOutModule
{
private:
static const int PacketsPerBuffer = 8;
static const int MAX_BUFFER_COUNT = 3;
class BaseStreamingVoice : public IXAudio2VoiceCallback
{
protected:
IXAudio2SourceVoice* pSourceVoice = nullptr;
std::unique_ptr<s16[]> m_buffer;
const uint m_nBuffers;
const uint m_nChannels;
const uint m_BufferSize;
const uint m_BufferSizeBytes;
public:
virtual ~BaseStreamingVoice() = default;
int GetEmptySampleCount() const
{
XAUDIO2_VOICE_STATE state;
pSourceVoice->GetState(&state);
return state.SamplesPlayed & (m_BufferSize - 1);
}
explicit BaseStreamingVoice(uint numChannels)
: m_nBuffers(2)
, m_nChannels(numChannels)
, m_BufferSize(SndOutPacketSize * m_nChannels * PacketsPerBuffer)
, m_BufferSizeBytes(m_BufferSize * sizeof(s16))
{
}
bool Init(IXAudio2* pXAudio2)
{
DWORD chanMask = 0;
switch (m_nChannels)
{
case 1:
chanMask |= SPEAKER_FRONT_CENTER;
break;
case 2:
chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
break;
case 3:
chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY;
break;
case 4:
chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
break;
case 5:
chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
break;
case 6:
chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY;
break;
case 8:
chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT | SPEAKER_LOW_FREQUENCY;
break;
}
WAVEFORMATEXTENSIBLE wfx{};
wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfx.Format.nSamplesPerSec = SampleRate;
wfx.Format.nChannels = m_nChannels;
wfx.Format.wBitsPerSample = 16;
wfx.Format.nBlockAlign = wfx.Format.nChannels * wfx.Format.wBitsPerSample / 8;
wfx.Format.nAvgBytesPerSec = SampleRate * wfx.Format.nBlockAlign;
wfx.Format.cbSize = sizeof(wfx) - sizeof(WAVEFORMATEX);
wfx.Samples.wValidBitsPerSample = 16;
wfx.dwChannelMask = chanMask;
wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
const HRESULT hr = pXAudio2->CreateSourceVoice(&pSourceVoice, reinterpret_cast<WAVEFORMATEX*>(&wfx), XAUDIO2_VOICE_NOSRC, 1.0f, this);
if (FAILED(hr))
{
Console.Error("XAudio2 CreateSourceVoice failure: %08X", hr);
return false;
}
m_buffer = std::make_unique<s16[]>(m_nBuffers * m_BufferSize);
// Start some buffers.
for (size_t i = 0; i < m_nBuffers; i++)
{
XAUDIO2_BUFFER buf{};
buf.AudioBytes = m_BufferSizeBytes;
buf.pContext = &m_buffer[i * m_BufferSize];
buf.pAudioData = static_cast<BYTE*>(buf.pContext);
pSourceVoice->SubmitSourceBuffer(&buf);
}
Start();
return true;
}
void Stop()
{
pSourceVoice->Stop();
}
void Start()
{
pSourceVoice->Start(0, 0);
}
protected:
STDMETHOD_(void, OnVoiceProcessingPassStart)
(UINT32) override {}
STDMETHOD_(void, OnVoiceProcessingPassEnd)
() override {}
STDMETHOD_(void, OnStreamEnd)
() override {}
STDMETHOD_(void, OnBufferStart)
(void*) override {}
STDMETHOD_(void, OnLoopEnd)
(void*) override {}
STDMETHOD_(void, OnVoiceError)
(THIS_ void* pBufferContext, HRESULT Error) override {}
};
template <typename T>
class StreamingVoice final : public BaseStreamingVoice
{
public:
StreamingVoice()
: BaseStreamingVoice(sizeof(T) / sizeof(s16))
{
}
virtual ~StreamingVoice() override
{
// Must be done here and not BaseStreamingVoice, as else OnBufferEnd will not be callable anymore
// but it will be called by DestroyVoice.
if (pSourceVoice != nullptr)
{
pSourceVoice->Stop();
pSourceVoice->DestroyVoice();
}
}
protected:
STDMETHOD_(void, OnBufferEnd)
(void* context) override
{
T* qb = static_cast<T*>(context);
for (size_t p = 0; p < PacketsPerBuffer; p++, qb += SndOutPacketSize)
SndBuffer::ReadSamples(qb);
XAUDIO2_BUFFER buf{};
buf.AudioBytes = m_BufferSizeBytes;
buf.pAudioData = static_cast<BYTE*>(context);
buf.pContext = context;
pSourceVoice->SubmitSourceBuffer(&buf);
}
};
wil::unique_couninitialize_call xaudio2CoInitialize;
wil::unique_hmodule xAudio2DLL;
wil::com_ptr_nothrow<IXAudio2> pXAudio2;
IXAudio2MasteringVoice* pMasteringVoice = nullptr;
std::unique_ptr<BaseStreamingVoice> m_voiceContext;
bool m_paused = false;
public:
bool Init() override
{
xaudio2CoInitialize = wil::CoInitializeEx_failfast(COINIT_MULTITHREADED);
xAudio2DLL.reset(LoadLibraryEx(XAUDIO2_DLL, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32));
if (xAudio2DLL == nullptr)
{
Console.Error("Could not load %s. Error code: %d" XAUDIO2_DLL_A, GetLastError());
Close();
return false;
}
auto pXAudio2Create = GetProcAddressByFunctionDeclaration(xAudio2DLL.get(), XAudio2Create);
if (pXAudio2Create == nullptr)
{
Console.Error("XAudio2Create not found. Error code: %d", GetLastError());
Close();
return false;
}
HRESULT hr = pXAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR);
if (FAILED(hr))
{
Console.Error("Failed to init XAudio2 engine. Error Details: %08X", hr);
Close();
return false;
}
#ifdef XAUDIO2_DEBUG
XAUDIO2_DEBUG_CONFIGURATION debugConfig{};
debugConfig.BreakMask = XAUDIO2_LOG_ERRORS;
pXAudio2->SetDebugConfiguration(&debugConfig, nullptr);
#endif
// Stereo Expansion was planned to grab the currently configured number of
// Speakers from Windows's audio config.
// This doesn't always work though, so let it be a user configurable option.
int speakers;
// speakers = (numSpeakers + 1) *2; ?
switch (EmuConfig.SPU2.SpeakerConfiguration)
{
case 1: // Quadrafonic
speakers = 4;
break;
case 2: // Surround 5.1
speakers = 6;
break;
case 3: // Surround 7.1
speakers = 8;
break;
default: // Stereo
speakers = 2;
break;
}
hr = pXAudio2->CreateMasteringVoice(&pMasteringVoice, speakers, SampleRate);
if (FAILED(hr))
{
Console.Error("Failed creating mastering voice: %08X", hr);
Close();
return false;
}
switch (speakers)
{
case 2:
Console.WriteLn("* SPU2 > Using normal 2 speaker stereo output.");
m_voiceContext = std::make_unique<StreamingVoice<StereoOut16>>();
break;
case 3:
Console.WriteLn("* SPU2 > 2.1 speaker expansion enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo21Out16>>();
break;
case 4:
Console.WriteLn("* SPU2 > 4 speaker expansion enabled [quadraphenia]");
m_voiceContext = std::make_unique<StreamingVoice<Stereo40Out16>>();
break;
case 5:
Console.WriteLn("* SPU2 > 4.1 speaker expansion enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo41Out16>>();
break;
case 6:
case 7:
switch (EmuConfig.SPU2.DplDecodingLevel)
{
case 1: // basic Dpl decoder without rear stereo balancing
Console.WriteLn("* SPU2 > 5.1 speaker expansion with basic ProLogic dematrixing enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo51Out16Dpl>>();
break;
case 2: // gigas PLII
Console.WriteLn("* SPU2 > 5.1 speaker expansion with experimental ProLogicII dematrixing enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo51Out16DplII>>();
break;
default: // "normal" stereo upmix
Console.WriteLn("* SPU2 > 5.1 speaker expansion enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo51Out16>>();
break;
}
break;
default: // anything 8 or more gets the 7.1 treatment!
Console.WriteLn("* SPU2 > 7.1 speaker expansion enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo51Out16>>();
break;
}
if (!m_voiceContext->Init(pXAudio2.get()))
{
Close();
return false;
}
m_paused = false;
return true;
}
void Close() override
{
m_voiceContext.reset();
if (pMasteringVoice != nullptr)
{
pMasteringVoice->DestroyVoice();
pMasteringVoice = nullptr;
}
pXAudio2.reset();
xAudio2DLL.reset();
xaudio2CoInitialize.reset();
}
int GetEmptySampleCount() override
{
if (m_voiceContext == nullptr)
return 0;
return m_voiceContext->GetEmptySampleCount();
}
void SetPaused(bool paused) override
{
if (m_voiceContext == nullptr || m_paused == paused)
return;
if (paused)
m_voiceContext->Stop();
else
m_voiceContext->Start();
m_paused = paused;
}
const char* GetIdent() const override
{
return "xaudio2";
}
const char* GetDisplayName() const override
{
//: XAudio2 is an audio engine name. Leave as-is.
return TRANSLATE_NOOP("SPU2", "XAudio2");
}
const char* const* GetBackendNames() const override
{
return nullptr;
}
std::vector<SndOutDeviceInfo> GetOutputDeviceList(const char* driver) const override
{
return {};
}
} static XA2;
SndOutModule* XAudio2Out = &XA2;

View File

@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SPU2/Global.h"
#include "SPU2/Debug.h"
#include "SPU2/spu2.h"
#include "pcsx2/Config.h"
#include "fmt/format.h"
@ -30,8 +31,6 @@ namespace WaveDump
void Open()
{
if (!IsDevBuild)
return;
if (!SPU2::WaveLog())
return;
@ -40,10 +39,10 @@ namespace WaveDump
for (int srcidx = 0; srcidx < CoreSrc_Count; srcidx++)
{
m_CoreWav[cidx][srcidx].reset();
std::string wavfilename(Path::Combine(EmuFolders::Logs, fmt::format("spu2x-Core{}d-{}.wav", cidx, m_tbl_CoreOutputTypeNames[srcidx])));
m_CoreWav[cidx][srcidx] = std::make_unique<Common::WAVWriter>();
if (!m_CoreWav[cidx][srcidx]->Open(wavfilename.c_str(), SampleRate, 2))
if (!m_CoreWav[cidx][srcidx]->Open(wavfilename.c_str(), SPU2::GetConsoleSampleRate(), 2))
{
Console.Error(fmt::format("Failed to open '{}'. Wave Log for this core source disabled.", wavfilename));
m_CoreWav[cidx][srcidx].reset();
@ -54,8 +53,6 @@ namespace WaveDump
void Close()
{
if (!IsDevBuild)
return;
for (uint cidx = 0; cidx < 2; cidx++)
{
for (int srcidx = 0; srcidx < CoreSrc_Count; srcidx++)
@ -63,17 +60,22 @@ namespace WaveDump
}
}
void WriteCore(uint coreidx, CoreSourceType src, const StereoOut16& sample)
void WriteCore(uint coreidx, CoreSourceType src, const StereoOut32& sample)
{
if (!IsDevBuild)
if (!m_CoreWav[coreidx][src])
return;
if (m_CoreWav[coreidx][src] != nullptr)
m_CoreWav[coreidx][src]->WriteFrames(reinterpret_cast<const s16*>(&sample), 1);
const s16 frame[] = {static_cast<s16>(clamp_mix(sample.Left)), static_cast<s16>(clamp_mix(sample.Right))};
m_CoreWav[coreidx][src]->WriteFrames(frame, 1);
}
void WriteCore(uint coreidx, CoreSourceType src, s16 left, s16 right)
{
WriteCore(coreidx, src, StereoOut16(left, right));
if (!m_CoreWav[coreidx][src])
return;
const s16 frame[] = {left, right};
m_CoreWav[coreidx][src]->WriteFrames(frame, 1);
}
} // namespace WaveDump

View File

@ -3,10 +3,6 @@
#pragma once
#include "SPU2/Mixer.h"
#include "SPU2/SndOut.h"
#include "SPU2/Global.h"
#include "GS/MultiISA.h"
#include <array>
@ -23,10 +19,54 @@ extern const std::array<u16*, 0x401> regtable;
#define spu2Rs16(mmem) (*(s16*)((s8*)spu2regs + ((mmem)&0x1fff)))
#define spu2Ru16(mmem) (*(u16*)((s8*)spu2regs + ((mmem)&0x1fff)))
struct StereoOut32
{
static const StereoOut32 Empty;
s32 Left;
s32 Right;
StereoOut32() = default;
StereoOut32(s32 left, s32 right)
: Left(left)
, Right(right)
{
}
StereoOut32 operator*(const int& factor) const
{
return StereoOut32(
Left * factor,
Right * factor);
}
StereoOut32& operator*=(const int& factor)
{
Left *= factor;
Right *= factor;
return *this;
}
StereoOut32 operator+(const StereoOut32& right) const
{
return StereoOut32(
Left + right.Left,
Right + right.Right);
}
StereoOut32 operator/(int src) const
{
return StereoOut32(Left / src, Right / src);
}
};
extern s16* GetMemPtr(u32 addr);
extern s16 spu2M_Read(u32 addr);
extern void spu2M_Write(u32 addr, s16 value);
extern void spu2M_Write(u32 addr, u16 value);
extern void spu2Mix();
extern void spu2Output(StereoOut32 out);
static __forceinline s16 SignExtend16(u16 v)
{

View File

@ -1,31 +1,41 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SPU2/Global.h"
#include "SPU2/Debug.h"
#include "SPU2/spu2.h"
#include "SPU2/defs.h"
#include "SPU2/Debug.h"
#include "SPU2/Dma.h"
#include "Host/AudioStream.h"
#include "Host.h"
#include "GS/GSCapture.h"
#include "MTGS.h"
#include "R3000A.h"
#include "VMManager.h"
#include "common/Error.h"
const StereoOut32 StereoOut32::Empty(0, 0);
namespace SPU2
{
static void InitSndBuffer();
static void CreateOutputStream();
static void UpdateSampleRate();
static float GetNominalRate();
static void InternalReset(bool psxmode);
} // namespace SPU2
static double s_device_sample_rate_multiplier = 1.0;
static bool s_psxmode = false;
int SampleRate = 48000;
u32 lClocks = 0;
s32 SPU2::GetConsoleSampleRate()
static bool s_audio_capture_active = false;
static bool s_psxmode = false;
static std::unique_ptr<AudioStream> s_output_stream;
static std::array<s16, AudioStream::CHUNK_SIZE * 2> s_current_chunk;
static u32 s_current_chunk_pos;
u32 SPU2::GetConsoleSampleRate()
{
return s_psxmode ? 44100 : 48000;
return s_psxmode ? PSX_SAMPLE_RATE : SAMPLE_RATE;
}
// --------------------------------------------------------------------------------------
@ -85,38 +95,36 @@ void SPU2writeDMA7Mem(u16* pMem, u32 size)
Cores[1].DoDMAwrite(pMem, size);
}
void SPU2::InitSndBuffer()
void SPU2::CreateOutputStream()
{
Console.WriteLn("Initializing SndBuffer at sample rate of %u...", SampleRate);
if (SndBuffer::Init(EmuConfig.SPU2.OutputModule.c_str()))
return;
// Persist volume through stream recreates.
const u32 volume = s_output_stream ? s_output_stream->GetOutputVolume() : GetResetVolume();
const u32 sample_rate = GetConsoleSampleRate();
s_output_stream.reset();
if (SampleRate != GetConsoleSampleRate())
Error error;
s_output_stream = AudioStream::CreateStream(EmuConfig.SPU2.Backend, sample_rate, EmuConfig.SPU2.StreamParameters,
EmuConfig.SPU2.DriverName.c_str(), EmuConfig.SPU2.DeviceName.c_str(), EmuConfig.SPU2.IsTimeStretchEnabled(), &error);
if (!s_output_stream)
{
// It'll get stretched instead..
const int original_sample_rate = SampleRate;
Console.Error("Failed to init SPU2 at adjusted sample rate %u, trying console rate.", SampleRate);
SampleRate = GetConsoleSampleRate();
if (SndBuffer::Init(EmuConfig.SPU2.OutputModule.c_str()))
return;
Host::ReportErrorAsync("Error",
fmt::format("Failed to create or configure audio stream, falling back to null output. The error was:\n{}",
error.GetDescription()));
SampleRate = original_sample_rate;
s_output_stream = AudioStream::CreateNullStream(sample_rate, EmuConfig.SPU2.StreamParameters.buffer_ms);
}
// just use nullout
if (!SndBuffer::Init("nullout"))
pxFailRel("Failed to initialize nullout.");
s_output_stream->SetOutputVolume(volume);
s_output_stream->SetNominalRate(GetNominalRate());
s_output_stream->SetPaused(VMManager::GetState() == VMState::Paused);
}
void SPU2::UpdateSampleRate()
{
const int new_sample_rate = static_cast<int>(std::round(static_cast<double>(GetConsoleSampleRate()) * s_device_sample_rate_multiplier));
if (SampleRate == new_sample_rate)
if (s_output_stream && s_output_stream->GetSampleRate() == GetConsoleSampleRate())
return;
SndBuffer::Cleanup();
SampleRate = new_sample_rate;
InitSndBuffer();
CreateOutputStream();
// Can't be capturing when the sample rate changes.
if (IsAudioCaptureActive())
@ -126,8 +134,48 @@ void SPU2::UpdateSampleRate()
}
}
u32 SPU2::GetOutputVolume()
{
return s_output_stream->GetOutputVolume();
}
void SPU2::SetOutputVolume(u32 volume)
{
s_output_stream->SetOutputVolume(volume);
}
u32 SPU2::GetResetVolume()
{
return EmuConfig.SPU2.OutputMuted ? 0 :
((VMManager::GetTargetSpeed() != 1.0f) ?
EmuConfig.SPU2.FastForwardVolume :
EmuConfig.SPU2.OutputVolume);
}
float SPU2::GetNominalRate()
{
// Adjust nominal rate when syncing to host.
return VMManager::IsTargetSpeedAdjustedToHost() ? VMManager::GetTargetSpeed() : 1.0f;
}
void SPU2::SetOutputPaused(bool paused)
{
s_output_stream->SetPaused(paused);
}
void SPU2::SetAudioCaptureActive(bool active)
{
s_audio_capture_active = active;
}
bool SPU2::IsAudioCaptureActive()
{
return s_audio_capture_active;
}
void SPU2::InternalReset(bool psxmode)
{
s_current_chunk_pos = 0;
s_psxmode = psxmode;
if (!s_psxmode)
{
@ -151,18 +199,19 @@ void SPU2::Reset(bool psxmode)
void SPU2::OnTargetSpeedChanged()
{
if (EmuConfig.SPU2.SynchMode != Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch)
SndBuffer::ResetBuffers();
}
void SPU2::SetDeviceSampleRateMultiplier(double multiplier)
{
if (s_device_sample_rate_multiplier == multiplier)
if (!s_output_stream)
return;
s_device_sample_rate_multiplier = multiplier;
if (SndBuffer::IsOpen())
UpdateSampleRate();
if (!s_output_stream->IsStretchEnabled())
{
s_output_stream->EmptyBuffer();
s_current_chunk_pos = 0;
}
s_output_stream->SetNominalRate(GetNominalRate());
if (EmuConfig.SPU2.OutputVolume != EmuConfig.SPU2.FastForwardVolume && !EmuConfig.SPU2.OutputMuted)
s_output_stream->SetOutputVolume(GetResetVolume());
}
bool SPU2::Open()
@ -182,13 +231,11 @@ bool SPU2::Open()
InternalReset(false);
SampleRate = static_cast<int>(std::round(static_cast<double>(GetConsoleSampleRate()) * s_device_sample_rate_multiplier));
InitSndBuffer();
CreateOutputStream();
#ifdef PCSX2_DEVBUILD
WaveDump::Open();
#endif
SetOutputVolume(EmuConfig.SPU2.FinalVolume);
return true;
}
@ -196,7 +243,7 @@ void SPU2::Close()
{
FileLog("[%10d] SPU2 Close\n", Cycles);
SndBuffer::Cleanup();
s_output_stream.reset();
#ifdef PCSX2_DEVBUILD
WaveDump::Close();
@ -212,6 +259,44 @@ bool SPU2::IsRunningPSXMode()
return s_psxmode;
}
void SPU2::CheckForConfigChanges(const Pcsx2Config& old_config)
{
const Pcsx2Config::SPU2Options& opts = EmuConfig.SPU2;
const Pcsx2Config::SPU2Options& oldopts = old_config.SPU2;
// No need to reinit for volume change.
if ((opts.OutputVolume != oldopts.OutputVolume && VMManager::GetTargetSpeed() == 1.0f) ||
(opts.FastForwardVolume != oldopts.FastForwardVolume && VMManager::GetTargetSpeed() != 1.0f) ||
opts.OutputMuted != oldopts.OutputMuted)
{
SetOutputVolume(GetResetVolume());
}
// Things which require re-initialzing the output.
if (opts.Backend != oldopts.Backend ||
opts.StreamParameters != oldopts.StreamParameters ||
opts.DriverName != oldopts.DriverName ||
opts.DeviceName != oldopts.DeviceName)
{
CreateOutputStream();
}
else if (opts.IsTimeStretchEnabled() != oldopts.IsTimeStretchEnabled())
{
s_output_stream->SetStretchEnabled(opts.IsTimeStretchEnabled());
}
#ifdef PCSX2_DEVBUILD
// AccessLog controls file output.
if (opts.AccessLog != oldopts.AccessLog)
{
if (AccessLog())
OpenFileLog();
else
CloseFileLog();
}
#endif
}
void SPU2async()
{
TimeUpdate(psxRegs.cycle);
@ -327,47 +412,18 @@ s32 SPU2freeze(FreezeAction mode, freezeData* data)
return 0;
}
void SPU2::CheckForConfigChanges(const Pcsx2Config& old_config)
__forceinline void spu2Output(StereoOut32 out)
{
if (EmuConfig.SPU2 == old_config.SPU2)
return;
const Pcsx2Config::SPU2Options& opts = EmuConfig.SPU2;
const Pcsx2Config::SPU2Options& oldopts = old_config.SPU2;
// No need to reinit for volume change.
if (opts.FinalVolume != oldopts.FinalVolume)
SetOutputVolume(opts.FinalVolume);
// Wipe buffer out when changing sync mode, so e.g. TS->none doesn't have a huge delay.
if (opts.SynchMode != oldopts.SynchMode)
SndBuffer::ResetBuffers();
// Things which require re-initialzing the output.
if (opts.Latency != oldopts.Latency ||
opts.OutputLatency != oldopts.OutputLatency ||
opts.OutputLatencyMinimal != oldopts.OutputLatencyMinimal ||
opts.OutputModule != oldopts.OutputModule ||
opts.BackendName != oldopts.BackendName ||
opts.DeviceName != oldopts.DeviceName ||
opts.SpeakerConfiguration != oldopts.SpeakerConfiguration ||
opts.DplDecodingLevel != oldopts.DplDecodingLevel ||
opts.SequenceLenMS != oldopts.SequenceLenMS ||
opts.SeekWindowMS != oldopts.SeekWindowMS ||
opts.OverlapMS != oldopts.OverlapMS)
// Final clamp, take care not to exceed 16 bits from here on
s_current_chunk[s_current_chunk_pos++] = static_cast<s16>(clamp_mix(out.Left));
s_current_chunk[s_current_chunk_pos++] = static_cast<s16>(clamp_mix(out.Right));
if (s_current_chunk_pos == s_current_chunk.size())
{
SndBuffer::Cleanup();
InitSndBuffer();
}
s_current_chunk_pos = 0;
#ifdef PCSX2_DEVBUILD
// AccessLog controls file output.
if (opts.AccessLog != oldopts.AccessLog)
{
if (AccessLog())
OpenFileLog();
else
CloseFileLog();
s_output_stream->WriteChunk(s_current_chunk.data());
if (SPU2::IsAudioCaptureActive()) [[unlikely]]
GSCapture::DeliverAudioPacket(s_current_chunk.data());
}
#endif
}

View File

@ -1,16 +1,25 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include "SaveState.h"
#include "IopCounters.h"
#include <mutex>
#include <memory>
struct Pcsx2Config;
class AudioStream;
namespace SPU2
{
/// PS2/Native Sample Rate.
static constexpr u32 SAMPLE_RATE = 48000;
/// PSX Mode Sample Rate.
static constexpr u32 PSX_SAMPLE_RATE = 44100;
/// Open/close, call at VM startup/shutdown.
bool Open();
void Close();
@ -22,10 +31,13 @@ void Reset(bool psxmode);
void CheckForConfigChanges(const Pcsx2Config& old_config);
/// Returns the current output volume, irrespective of the configuration.
s32 GetOutputVolume();
u32 GetOutputVolume();
/// Directly updates the output volume without going through the configuration.
void SetOutputVolume(s32 volume);
void SetOutputVolume(u32 volume);
/// Returns the volume that we would reset the output to on startup.
u32 GetResetVolume();
/// Pauses/resumes the output stream.
void SetOutputPaused(bool paused);
@ -33,14 +45,11 @@ void SetOutputPaused(bool paused);
/// Clears output buffers in no-sync mode, prevents long delays after fast forwarding.
void OnTargetSpeedChanged();
/// Adjusts the premultiplier on the output sample rate. Used for syncing to host refresh rate.
void SetDeviceSampleRateMultiplier(double multiplier);
/// Returns true if we're currently running in PSX mode.
bool IsRunningPSXMode();
/// Returns the current sample rate the SPU2 is operating at.
s32 GetConsoleSampleRate();
u32 GetConsoleSampleRate();
/// Tells SPU2 to forward audio packets to GSCapture.
void SetAudioCaptureActive(bool active);

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SPU2/Global.h"
#include "SPU2/defs.h"
#include "SPU2/spu2.h" // hopefully temporary, until I resolve lClocks depdendency
#include "IopMem.h"
@ -110,8 +110,6 @@ s32 SPU2Savestate::ThawIt(DataBlock& spud)
}
else
{
SndBuffer::ClearContents();
memcpy(spu2regs, spud.unkregs, sizeof(spud.unkregs));
memcpy(_spu2mem, spud.mem, sizeof(spud.mem));

View File

@ -11,8 +11,10 @@
#include "IopDma.h"
#include "IopHw.h"
#include "R3000A.h"
#include "SPU2/Debug.h"
#include "SPU2/defs.h"
#include "SPU2/Dma.h"
#include "SPU2/Global.h"
#include "SPU2/regs.h"
#include "SPU2/spu2.h"
#include "common/Console.h"
@ -214,10 +216,10 @@ void V_Voice::Stop()
ADSR.Phase = V_ADSR::PHASE_STOPPED;
}
uint TickInterval = 768;
static const int SanityInterval = 4800;
static constexpr uint TickInterval = 768;
static constexpr int SanityInterval = 4800;
__forceinline bool StartQueuedVoice(uint coreidx, uint voiceidx)
__forceinline static bool StartQueuedVoice(uint coreidx, uint voiceidx)
{
V_Voice& vc(Cores[coreidx].Voices[voiceidx]);
@ -275,11 +277,6 @@ __forceinline void TimeUpdate(u32 cClocks)
lClocks = cClocks - dClocks;
}
if (EmuConfig.SPU2.SynchMode == Pcsx2Config::SPU2Options::SynchronizationMode::ASync)
SndBuffer::UpdateTempoChangeAsyncMixing();
else
TickInterval = 768; // Reset to default, in case the user hotswitched from async to something else.
//Update Mixing Progress
while (dClocks >= TickInterval)
{
@ -307,10 +304,8 @@ __forceinline void TimeUpdate(u32 cClocks)
if(Cores[c].KeyOn & (1 << v))
if(StartQueuedVoice(c, v))
Cores[c].KeyOn &= ~(1 << v);
// Note: IOP does not use MMX regs, so no need to save them.
//SaveMMXRegs();
Mix();
//RestoreMMXRegs();
spu2Mix();
}
//Update DMA4 interrupt delay counter

View File

@ -186,6 +186,7 @@ static LimiterModeType s_limiter_mode = LimiterModeType::Nominal;
static s64 s_limiter_ticks_per_frame = 0;
static u64 s_limiter_frame_start = 0;
static float s_target_speed = 0.0f;
static bool s_target_speed_synced_to_host = false;
static bool s_use_vsync_for_timing = false;
// Used to track play time. We use a monotonic timer here, in case of clock changes.
@ -1988,7 +1989,7 @@ double VMManager::AdjustToHostRefreshRate(float frame_rate, float target_speed)
{
if (!EmuConfig.EmulationSpeed.SyncToHostRefreshRate || target_speed != 1.0f)
{
SPU2::SetDeviceSampleRateMultiplier(1.0);
s_target_speed_synced_to_host = false;
s_use_vsync_for_timing = false;
return target_speed;
}
@ -1997,23 +1998,19 @@ double VMManager::AdjustToHostRefreshRate(float frame_rate, float target_speed)
if (!GSGetHostRefreshRate(&host_refresh_rate))
{
Console.Warning("Cannot sync to host refresh since the query failed.");
SPU2::SetDeviceSampleRateMultiplier(1.0);
s_target_speed_synced_to_host = false;
s_use_vsync_for_timing = false;
return target_speed;
}
const double ratio = host_refresh_rate / frame_rate;
const float ratio = host_refresh_rate / frame_rate;
const bool syncing_to_host = (ratio >= 0.95f && ratio <= 1.05f);
s_target_speed_synced_to_host = syncing_to_host;
s_use_vsync_for_timing = (syncing_to_host && !EmuConfig.GS.SkipDuplicateFrames && EmuConfig.GS.VsyncEnable != VsyncMode::Off);
Console.WriteLn("Refresh rate: Host=%fhz Guest=%fhz Ratio=%f - %s %s", host_refresh_rate, frame_rate, ratio,
syncing_to_host ? "can sync" : "can't sync", s_use_vsync_for_timing ? "and using vsync for pacing" : "and using sleep for pacing");
if (!syncing_to_host)
return target_speed;
target_speed *= ratio;
SPU2::SetDeviceSampleRateMultiplier(ratio);
return target_speed;
return syncing_to_host ? ratio : target_speed;
}
float VMManager::GetTargetSpeedForLimiterMode(LimiterModeType mode)
@ -2063,6 +2060,11 @@ void VMManager::UpdateTargetSpeed()
}
}
bool VMManager::IsTargetSpeedAdjustedToHost()
{
return s_target_speed_synced_to_host;
}
float VMManager::GetFrameRate()
{
return GetVerticalFrequency();
@ -2928,9 +2930,6 @@ void VMManager::EnforceAchievementsChallengeModeSettings()
EmuConfig.Speedhacks.EECycleRate =
std::max<decltype(EmuConfig.Speedhacks.EECycleRate)>(EmuConfig.Speedhacks.EECycleRate, 0);
EmuConfig.Speedhacks.EECycleSkip = 0;
// Async mix breaks games.
EmuConfig.SPU2.SynchMode = Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch;
}
void VMManager::LogUnsafeSettingsToConsole(const std::string& messages)
@ -2973,11 +2972,6 @@ void VMManager::WarnAboutUnsafeSettings()
append(ICON_FA_TACHOMETER_ALT,
TRANSLATE_SV("VMManager", "Cycle rate/skip is not at default, this may crash or make games run too slow."));
}
if (EmuConfig.SPU2.SynchMode == Pcsx2Config::SPU2Options::SynchronizationMode::ASync)
{
append(ICON_FA_VOLUME_MUTE,
TRANSLATE_SV("VMManager", "Audio is using async mix, expect desynchronization in FMVs."));
}
if (EmuConfig.GS.UpscaleMultiplier < 1.0f)
append(ICON_FA_TV, TRANSLATE_SV("VMManager", "Upscale multiplier is below native, this will break rendering."));
if (EmuConfig.GS.HWMipmap != HWMipmapLevel::Automatic)

View File

@ -150,6 +150,9 @@ namespace VMManager
/// EmuConfig.EmulationSpeed without going through the usual config apply.
void UpdateTargetSpeed();
/// Returns true if the target speed is being synchronized with the host's refresh rate.
bool IsTargetSpeedAdjustedToHost();
/// Returns the current frame rate of the virtual machine.
float GetFrameRate();

View File

@ -250,11 +250,7 @@
<ClCompile Include="SIO\Sio2.cpp" />
<ClCompile Include="SPU2\Debug.cpp" />
<ClCompile Include="SPU2\Dma.cpp" />
<ClCompile Include="SPU2\DplIIdecoder.cpp" />
<ClCompile Include="SPU2\SndOut_Cubeb.cpp" />
<ClCompile Include="SPU2\SndOut_XAudio2.cpp" />
<ClCompile Include="SPU2\wavedump_wav.cpp" />
<ClCompile Include="SPU2\SndOut.cpp" />
<ClCompile Include="SPU2\RegTable.cpp" />
<ClCompile Include="SPU2\spu2freeze.cpp" />
<ClCompile Include="SPU2\spu2sys.cpp" />
@ -603,13 +599,10 @@
<ClInclude Include="SIO\SioTypes.h" />
<ClInclude Include="SPU2\Debug.h" />
<ClInclude Include="SPU2\Dma.h" />
<ClInclude Include="SPU2\Global.h" />
<ClInclude Include="SPU2\interpolate_table.h" />
<ClInclude Include="SPU2\SndOut.h" />
<ClInclude Include="SPU2\spdif.h" />
<ClInclude Include="SPU2\defs.h" />
<ClInclude Include="SPU2\regs.h" />
<ClInclude Include="SPU2\Mixer.h" />
<ClInclude Include="SPU2\spu2.h" />
<ClInclude Include="GS\Renderers\OpenGL\GLState.h" />
<ClInclude Include="GS\GS.h" />

View File

@ -830,9 +830,6 @@
<ClCompile Include="SPU2\ADSR.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="SPU2\DplIIdecoder.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="SPU2\ReadInput.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
@ -842,9 +839,6 @@
<ClCompile Include="SPU2\Reverb.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="SPU2\SndOut.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="SPU2\Wavedump_wav.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
@ -1115,9 +1109,6 @@
<ClCompile Include="GS\Renderers\DX11\D3D.cpp">
<Filter>System\Ps2\GS\Renderers\Direct3D11</Filter>
</ClCompile>
<ClCompile Include="SPU2\SndOut_Cubeb.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="GS\Renderers\Vulkan\GSTextureVK.cpp">
<Filter>System\Ps2\GS\Renderers\Vulkan</Filter>
</ClCompile>
@ -1244,9 +1235,6 @@
<ClCompile Include="SPU2\Dma.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="SPU2\SndOut_XAudio2.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="GSDumpReplayer.cpp">
<Filter>Tools</Filter>
</ClCompile>
@ -1730,24 +1718,15 @@
<ClInclude Include="SPU2\spu2.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>
<ClInclude Include="SPU2\Mixer.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>
<ClInclude Include="SPU2\interpolate_table.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>
<ClInclude Include="SPU2\defs.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>
<ClInclude Include="SPU2\Global.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>
<ClInclude Include="SPU2\regs.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>
<ClInclude Include="SPU2\SndOut.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>
<ClInclude Include="SPU2\spdif.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>