mirror of https://github.com/PCSX2/pcsx2.git
SPU2: Use AudioStream for output
This commit is contained in:
parent
ca091eeea9
commit
0f5e7355ff
|
@ -57,9 +57,11 @@ target_sources(pcsx2-qt PRIVATE
|
||||||
Settings/AdvancedSettingsWidget.cpp
|
Settings/AdvancedSettingsWidget.cpp
|
||||||
Settings/AdvancedSettingsWidget.h
|
Settings/AdvancedSettingsWidget.h
|
||||||
Settings/AdvancedSettingsWidget.ui
|
Settings/AdvancedSettingsWidget.ui
|
||||||
|
Settings/AudioExpansionSettingsDialog.ui
|
||||||
Settings/AudioSettingsWidget.cpp
|
Settings/AudioSettingsWidget.cpp
|
||||||
Settings/AudioSettingsWidget.h
|
Settings/AudioSettingsWidget.h
|
||||||
Settings/AudioSettingsWidget.ui
|
Settings/AudioSettingsWidget.ui
|
||||||
|
Settings/AudioStretchSettingsDialog.ui
|
||||||
Settings/BIOSSettingsWidget.cpp
|
Settings/BIOSSettingsWidget.cpp
|
||||||
Settings/BIOSSettingsWidget.h
|
Settings/BIOSSettingsWidget.h
|
||||||
Settings/BIOSSettingsWidget.ui
|
Settings/BIOSSettingsWidget.ui
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include "pcsx2/Input/InputManager.h"
|
#include "pcsx2/Input/InputManager.h"
|
||||||
#include "pcsx2/MTGS.h"
|
#include "pcsx2/MTGS.h"
|
||||||
#include "pcsx2/PerformanceMetrics.h"
|
#include "pcsx2/PerformanceMetrics.h"
|
||||||
|
#include "pcsx2/SPU2/spu2.h"
|
||||||
#include "pcsx2/VMManager.h"
|
#include "pcsx2/VMManager.h"
|
||||||
|
|
||||||
#include "common/Assertions.h"
|
#include "common/Assertions.h"
|
||||||
|
@ -889,6 +890,38 @@ void EmuThread::endCapture()
|
||||||
MTGS::RunOnGSThread(&GSEndCapture);
|
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)
|
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.
|
// 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.
|
||||||
|
|
|
@ -112,6 +112,8 @@ public Q_SLOTS:
|
||||||
void queueSnapshot(quint32 gsdump_frames);
|
void queueSnapshot(quint32 gsdump_frames);
|
||||||
void beginCapture(const QString& path);
|
void beginCapture(const QString& path);
|
||||||
void endCapture();
|
void endCapture();
|
||||||
|
void setAudioOutputVolume(int volume, int fast_forward_volume);
|
||||||
|
void setAudioOutputMuted(bool muted);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
bool messageConfirmed(const QString& title, const QString& message);
|
bool messageConfirmed(const QString& title, const QString& message);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "QtUtils.h"
|
#include "QtUtils.h"
|
||||||
|
@ -14,11 +14,13 @@
|
||||||
#include <QtWidgets/QComboBox>
|
#include <QtWidgets/QComboBox>
|
||||||
#include <QtWidgets/QDialog>
|
#include <QtWidgets/QDialog>
|
||||||
#include <QtWidgets/QHeaderView>
|
#include <QtWidgets/QHeaderView>
|
||||||
|
#include <QtWidgets/QLabel>
|
||||||
#include <QtWidgets/QInputDialog>
|
#include <QtWidgets/QInputDialog>
|
||||||
#include <QtWidgets/QMainWindow>
|
#include <QtWidgets/QMainWindow>
|
||||||
#include <QtWidgets/QMessageBox>
|
#include <QtWidgets/QMessageBox>
|
||||||
#include <QtWidgets/QScrollBar>
|
#include <QtWidgets/QScrollBar>
|
||||||
#include <QtWidgets/QStatusBar>
|
#include <QtWidgets/QStatusBar>
|
||||||
|
#include <QtWidgets/QSlider>
|
||||||
#include <QtWidgets/QStyle>
|
#include <QtWidgets/QStyle>
|
||||||
#include <QtWidgets/QTableView>
|
#include <QtWidgets/QTableView>
|
||||||
#include <QtWidgets/QTreeView>
|
#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)
|
void SetWindowResizeable(QWidget* widget, bool resizeable)
|
||||||
{
|
{
|
||||||
if (QMainWindow* window = qobject_cast<QMainWindow*>(widget); window)
|
if (QMainWindow* window = qobject_cast<QMainWindow*>(widget); window)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
@ -20,7 +20,9 @@ class QAction;
|
||||||
class QComboBox;
|
class QComboBox;
|
||||||
class QFileInfo;
|
class QFileInfo;
|
||||||
class QFrame;
|
class QFrame;
|
||||||
|
class QLabel;
|
||||||
class QKeyEvent;
|
class QKeyEvent;
|
||||||
|
class QSlider;
|
||||||
class QTableView;
|
class QTableView;
|
||||||
class QTreeView;
|
class QTreeView;
|
||||||
class QVariant;
|
class QVariant;
|
||||||
|
@ -71,6 +73,9 @@ namespace QtUtils
|
||||||
/// Sets a widget to italics if the setting value is inherited.
|
/// Sets a widget to italics if the setting value is inherited.
|
||||||
void SetWidgetFontForInheritedSetting(QWidget* widget, bool inherited);
|
void SetWidgetFontForInheritedSetting(QWidget* widget, bool inherited);
|
||||||
|
|
||||||
|
/// Binds a label to a slider's value.
|
||||||
|
void BindLabelToSlider(QSlider* slider, QLabel* label, float range = 1.0f);
|
||||||
|
|
||||||
/// Changes whether a window is resizable.
|
/// Changes whether a window is resizable.
|
||||||
void SetWindowResizeable(QWidget* widget, bool resizeable);
|
void SetWindowResizeable(QWidget* widget, bool resizeable);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,476 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AudioExpansionSettingsDialog</class>
|
||||||
|
<widget class="QDialog" name="AudioExpansionSettingsDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>614</width>
|
||||||
|
<height>371</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Audio Expansion Settings</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Circular Wrap:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="circularWrap">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>360</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>90</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="circularWrapLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>30</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>Shift:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="shift">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-100</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="shiftLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>Depth:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="depth">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>50</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="depthLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>10</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Focus:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="focus">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-100</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="focusLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Center Image:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="centerImage">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="centerImageLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Front Separation:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="frontSeparation">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="frontSeparationLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Rear Separation:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="rearSeparation">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="rearSeparationLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="0">
|
||||||
|
<widget class="QLabel" name="label_9">
|
||||||
|
<property name="text">
|
||||||
|
<string>Low Cutoff:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="lowCutoff">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lowCutoffLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="0">
|
||||||
|
<widget class="QLabel" name="label_10">
|
||||||
|
<property name="text">
|
||||||
|
<string>High Cutoff:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="highCutoff">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="highCutoffLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="11" column="0" colspan="2">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::RestoreDefaults</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="icon">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string><html><head/><body><p><span style=" font-weight:700;">Audio Expansion Settings</span><br/>These settings fine-tune the behavior of the FreeSurround-based channel expander.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::TextFormat::RichText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_11">
|
||||||
|
<property name="text">
|
||||||
|
<string>Block Size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="blockSize">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>8192</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>16</number>
|
||||||
|
</property>
|
||||||
|
<property name="pageStep">
|
||||||
|
<number>128</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>1024</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>128</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="blockSizeLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>30</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -1,29 +1,22 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// 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 "AudioSettingsWidget.h"
|
||||||
#include "QtHost.h"
|
#include "QtHost.h"
|
||||||
#include "QtUtils.h"
|
#include "QtUtils.h"
|
||||||
#include "SettingWidgetBinder.h"
|
#include "SettingWidgetBinder.h"
|
||||||
#include "SettingsWindow.h"
|
#include "SettingsWindow.h"
|
||||||
|
|
||||||
static constexpr s32 DEFAULT_SYNCHRONIZATION_MODE = 0;
|
#include "ui_AudioExpansionSettingsDialog.h"
|
||||||
static constexpr s32 DEFAULT_EXPANSION_MODE = 0;
|
#include "ui_AudioStretchSettingsDialog.h"
|
||||||
static constexpr s32 DEFAULT_DPL_DECODING_LEVEL = 0;
|
|
||||||
static const char* DEFAULT_OUTPUT_MODULE = "cubeb";
|
#include "pcsx2/Host/AudioStream.h"
|
||||||
static constexpr s32 DEFAULT_TARGET_LATENCY = 60;
|
#include "pcsx2/SPU2/spu2.h"
|
||||||
static constexpr s32 DEFAULT_OUTPUT_LATENCY = 20;
|
#include "pcsx2/VMManager.h"
|
||||||
static constexpr s32 DEFAULT_VOLUME = 100;
|
|
||||||
static constexpr s32 DEFAULT_SOUNDTOUCH_SEQUENCE_LENGTH = 30;
|
#include <QtWidgets/QMessageBox>
|
||||||
static constexpr s32 DEFAULT_SOUNDTOUCH_SEEK_WINDOW = 20;
|
#include <algorithm>
|
||||||
static constexpr s32 DEFAULT_SOUNDTOUCH_OVERLAP = 10;
|
#include <bit>
|
||||||
|
|
||||||
AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent)
|
AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
|
@ -32,317 +25,481 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
|
||||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||||
|
|
||||||
m_ui.setupUi(this);
|
m_ui.setupUi(this);
|
||||||
populateOutputModules();
|
|
||||||
|
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.syncMode, "SPU2/Output", "SynchMode", DEFAULT_SYNCHRONIZATION_MODE);
|
for (u32 i = 0; i < static_cast<u32>(AudioBackend::Count); i++)
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.expansionMode, "SPU2/Output", "SpeakerConfiguration", DEFAULT_EXPANSION_MODE);
|
m_ui.audioBackend->addItem(QString::fromUtf8(AudioStream::GetBackendDisplayName(static_cast<AudioBackend>(i))));
|
||||||
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();
|
|
||||||
|
|
||||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.outputModule, "SPU2/Output", "OutputModule", DEFAULT_OUTPUT_MODULE);
|
for (u32 i = 0; i < static_cast<u32>(AudioExpansionMode::Count); i++)
|
||||||
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())
|
|
||||||
{
|
{
|
||||||
connect(m_ui.volume, &QSlider::customContextMenuRequested, this, &AudioSettingsWidget::volumeContextMenuRequested);
|
m_ui.expansionMode->addItem(
|
||||||
m_ui.volume->setContextMenuPolicy(Qt::CustomContextMenu);
|
QString::fromUtf8(AudioStream::GetExpansionModeDisplayName(static_cast<AudioExpansionMode>(i))));
|
||||||
if (sif->ContainsValue("SPU2/Mixing", "FinalVolume"))
|
|
||||||
{
|
|
||||||
QFont bold_font(m_ui.volume->font());
|
|
||||||
bold_font.setBold(true);
|
|
||||||
m_ui.volumeLabel->setFont(bold_font);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(sif, m_ui.sequenceLength, m_ui.sequenceLengthLabel, tr(" ms"), "Soundtouch",
|
for (u32 i = 0; i < static_cast<u32>(Pcsx2Config::SPU2Options::SPU2SyncMode::Count); i++)
|
||||||
"SequenceLengthMS", DEFAULT_SOUNDTOUCH_SEQUENCE_LENGTH);
|
{
|
||||||
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(
|
m_ui.syncMode->addItem(
|
||||||
sif, m_ui.seekWindowSize, m_ui.seekWindowSizeLabel, tr(" ms"), "Soundtouch", "SeekWindowMS", DEFAULT_SOUNDTOUCH_SEEK_WINDOW);
|
QString::fromUtf8(Pcsx2Config::SPU2Options::GetSyncModeDisplayName(
|
||||||
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(
|
static_cast<Pcsx2Config::SPU2Options::SPU2SyncMode>(i))));
|
||||||
sif, m_ui.overlap, m_ui.overlapLabel, tr(" ms"), "Soundtouch", "OverlapMS", DEFAULT_SOUNDTOUCH_OVERLAP);
|
}
|
||||||
connect(m_ui.resetTimestretchDefaults, &QPushButton::clicked, this, &AudioSettingsWidget::resetTimestretchDefaults);
|
|
||||||
|
|
||||||
m_ui.label_3b->setVisible(false);
|
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.audioBackend, "SPU2/Output", "Backend",
|
||||||
m_ui.dplLevel->setVisible(false);
|
&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();
|
connect(m_ui.bufferMS, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabel);
|
||||||
updateLatencyLabels();
|
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)"),
|
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."));
|
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.stretchSettings, tr("Stretch Settings"), tr("N/A"),
|
||||||
dialog->registerWidgetHelp(m_ui.expansionMode, tr("Expansion"), tr("Stereo (None, Default)"),
|
tr("These settings fine-tune the behavior of the SoundTouch audio time stretcher when running outside of 100% speed."));
|
||||||
tr("Determines how the stereo output from the emulated system is upmixed into a greater number of the output speakers."));
|
dialog->registerWidgetHelp(m_ui.resetVolume, tr("Reset Volume"), tr("N/A"),
|
||||||
|
m_dialog->isPerGameSettings() ? tr("Resets volume back to the global/inherited setting.") :
|
||||||
//: Cubeb is an audio engine name. Leave as-is.
|
tr("Resets volume back to the default, i.e. full."));
|
||||||
dialog->registerWidgetHelp(m_ui.outputModule, tr("Output Module"), tr("Cubeb (Cross-platform)"),
|
dialog->registerWidgetHelp(m_ui.resetFastForwardVolume, tr("Reset Fast Forward Volume"), tr("N/A"),
|
||||||
tr("Selects the library to be used for audio output."));
|
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.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."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioSettingsWidget::~AudioSettingsWidget() = default;
|
AudioSettingsWidget::~AudioSettingsWidget() = default;
|
||||||
|
|
||||||
void AudioSettingsWidget::expansionModeChanged()
|
AudioExpansionMode AudioSettingsWidget::getEffectiveExpansionMode() const
|
||||||
{
|
{
|
||||||
const bool expansion51 = m_dialog->getEffectiveIntValue("SPU2/Output", "SpeakerConfiguration", 0) == 2;
|
return AudioStream::ParseExpansionMode(
|
||||||
m_ui.dplLevel->setDisabled(!expansion51);
|
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())
|
const AudioExpansionMode expansion_mode = getEffectiveExpansionMode();
|
||||||
m_ui.outputModule->addItem(qApp->translate("SPU2", mod->GetDisplayName()), QString::fromUtf8(mod->GetIdent()));
|
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 AudioExpansionMode expansion_mode = getEffectiveExpansionMode();
|
||||||
const char* const* backend_names = GetOutputModuleBackends(module_name.c_str());
|
m_ui.expansionSettings->setEnabled(expansion_mode != AudioExpansionMode::Disabled);
|
||||||
|
updateLatencyLabel();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSettingsWidget::outputBackendChanged()
|
void AudioSettingsWidget::onSyncModeChanged()
|
||||||
{
|
{
|
||||||
int index = m_ui.backend->currentIndex();
|
const Pcsx2Config::SPU2Options::SPU2SyncMode sync_mode =
|
||||||
if (m_dialog->isPerGameSettings())
|
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_ui.driver->addItem(tr("Default"), QString());
|
||||||
{
|
m_ui.driver->setEnabled(false);
|
||||||
m_dialog->setStringSettingValue("SPU2/Output", "BackendName", std::nullopt);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
index--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index == 0)
|
|
||||||
m_dialog->setStringSettingValue("SPU2/Output", "BackendName", "");
|
|
||||||
else
|
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 AudioBackend backend = getEffectiveBackend();
|
||||||
const std::string backend_name(m_dialog->getEffectiveStringValue("SPU2/Output", "BackendName", ""));
|
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->disconnect();
|
||||||
m_ui.outputDevice->clear();
|
m_ui.outputDevice->clear();
|
||||||
m_output_device_latency = 0;
|
m_output_device_latency = 0;
|
||||||
|
|
||||||
std::vector<SndOutDeviceInfo> devices(GetOutputDeviceList(module_name.c_str(), backend_name.c_str()));
|
|
||||||
if (devices.empty())
|
if (devices.empty())
|
||||||
{
|
{
|
||||||
m_ui.outputDevice->addItem(tr("Default"));
|
m_ui.outputDevice->addItem(tr("Default"), QString());
|
||||||
m_ui.outputDevice->setEnabled(false);
|
m_ui.outputDevice->setEnabled(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const std::string current_device(m_dialog->getEffectiveStringValue("SPU2/Output", "DeviceName", ""));
|
|
||||||
|
|
||||||
m_ui.outputDevice->setEnabled(true);
|
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));
|
m_ui.outputDevice->addItem(QString::fromStdString(di.display_name), QString::fromStdString(di.name));
|
||||||
if (devi.name == current_device)
|
if (di.name == current_device)
|
||||||
m_output_device_latency = devi.minimum_latency_frames;
|
{
|
||||||
|
m_output_device_latency = di.minimum_latency_frames;
|
||||||
|
is_known_device = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingWidgetBinder::BindWidgetToStringSetting(
|
if (!is_known_device)
|
||||||
m_dialog->getSettingsInterface(), m_ui.outputDevice, "SPU2/Output", "DeviceName", std::move(devices.front().name));
|
{
|
||||||
|
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.
|
const u32 expand_buffer_ms = AudioStream::GetMSForBufferSize(SPU2::SAMPLE_RATE, getEffectiveExpansionBlockSize());
|
||||||
if (SettingsInterface* sif = m_dialog->getSettingsInterface())
|
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());
|
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms buffer + %3 ms expand + %4 ms output)")
|
||||||
bold_font.setBold(true);
|
.arg(config_buffer_ms + expand_buffer_ms + output_latency_ms)
|
||||||
m_ui.volumeLabel->setFont(bold_font);
|
.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
|
else
|
||||||
{
|
{
|
||||||
Host::SetBaseIntSettingValue("SPU2/Mixing", "FinalVolume", value);
|
if (expand_buffer_ms > 0)
|
||||||
Host::CommitBaseSettingChanges();
|
|
||||||
|
|
||||||
// Push through to emu thread since we're not applying.
|
|
||||||
if (QtHost::IsVMValid())
|
|
||||||
{
|
{
|
||||||
Host::RunOnCPUThread([]() {
|
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms expand, minimum output latency unknown)")
|
||||||
if (!VMManager::HasValidVM())
|
.arg(expand_buffer_ms + config_buffer_ms)
|
||||||
return;
|
.arg(expand_buffer_ms));
|
||||||
|
}
|
||||||
EmuConfig.SPU2.FinalVolume = Host::GetIntSettingValue("SPU2/Mixing", "FinalVolume", DEFAULT_VOLUME);
|
else
|
||||||
SPU2::SetOutputVolume(EmuConfig.SPU2.FinalVolume);
|
{
|
||||||
});
|
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()
|
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.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>(
|
const bool minimal = m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputLatencyMinimal", false);
|
||||||
m_dialog->getIntValue("SPU2/Output", "SynchMode", DEFAULT_SYNCHRONIZATION_MODE).value_or(DEFAULT_SYNCHRONIZATION_MODE));
|
m_ui.outputLatencyMS->setEnabled(!minimal);
|
||||||
|
updateLatencyLabel();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
updateVolumeLabel();
|
||||||
m_ui.outputLatencyLabel->setText(minimal_output ? tr("N/A") : tr("%1 ms").arg(m_ui.outputLatency->value()));
|
}
|
||||||
|
|
||||||
const u32 output_latency_ms =
|
void AudioSettingsWidget::onFastForwardVolumeChanged(int new_value)
|
||||||
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());
|
// only called for base settings
|
||||||
if (output_latency_ms > 0)
|
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)")
|
m_dialog->removeSettingValue("Audio", key);
|
||||||
.arg(buffer_ms + output_latency_ms)
|
|
||||||
.arg(buffer_ms)
|
const int value = m_dialog->getEffectiveIntValue("Audio", key, 100);
|
||||||
.arg(output_latency_ms));
|
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
|
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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_AudioSettingsWidget.h"
|
||||||
|
|
||||||
|
#include "common/Pcsx2Defs.h"
|
||||||
|
|
||||||
#include <QtWidgets/QWidget>
|
#include <QtWidgets/QWidget>
|
||||||
|
|
||||||
#include "ui_AudioSettingsWidget.h"
|
enum class AudioBackend : u8;
|
||||||
|
enum class AudioExpansionMode : u8;
|
||||||
|
|
||||||
class SettingsWindow;
|
class SettingsWindow;
|
||||||
|
|
||||||
|
@ -18,22 +23,28 @@ public:
|
||||||
~AudioSettingsWidget();
|
~AudioSettingsWidget();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void expansionModeChanged();
|
void onExpansionModeChanged();
|
||||||
void outputModuleChanged();
|
void onSyncModeChanged();
|
||||||
void outputBackendChanged();
|
|
||||||
void updateDevices();
|
void updateDriverNames();
|
||||||
void volumeChanged(int value);
|
void updateDeviceNames();
|
||||||
void volumeContextMenuRequested(const QPoint& pt);
|
void updateLatencyLabel();
|
||||||
void updateTargetLatencyRange();
|
void updateVolumeLabel();
|
||||||
void updateLatencyLabels();
|
void onMinimalOutputLatencyChanged();
|
||||||
void onMinimalOutputLatencyStateChanged();
|
void onOutputVolumeChanged(int new_value);
|
||||||
void resetTimestretchDefaults();
|
void onFastForwardVolumeChanged(int new_value);
|
||||||
|
void onOutputMutedChanged(int new_state);
|
||||||
|
|
||||||
|
void onExpansionSettingsClicked();
|
||||||
|
void onStretchSettingsClicked();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void populateOutputModules();
|
AudioBackend getEffectiveBackend() const;
|
||||||
void updateVolumeLabel();
|
AudioExpansionMode getEffectiveExpansionMode() const;
|
||||||
|
u32 getEffectiveExpansionBlockSize() const;
|
||||||
|
void resetVolume(bool fast_forward);
|
||||||
|
|
||||||
SettingsWindow* m_dialog;
|
|
||||||
Ui::AudioSettingsWidget m_ui;
|
Ui::AudioSettingsWidget m_ui;
|
||||||
|
SettingsWindow* m_dialog;
|
||||||
u32 m_output_device_latency = 0;
|
u32 m_output_device_latency = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>754</width>
|
<width>523</width>
|
||||||
<height>485</height>
|
<height>478</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
@ -23,434 +23,133 @@
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item row="2" column="0">
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_4">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Timestretch Settings</string>
|
<string>Configuration</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QFormLayout" name="formLayout_3">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_6">
|
<widget class="QLabel" name="label_6">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Sequence Length:</string>
|
<string>Driver:</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>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
<widget class="QComboBox" name="driver"/>
|
||||||
<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>
|
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="3" column="1">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0">
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<widget class="QComboBox" name="expansionMode"/>
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="resetTimestretchDefaults">
|
<widget class="QToolButton" name="expansionSettings">
|
||||||
<property name="minimumSize">
|
<property name="toolTip">
|
||||||
<size>
|
<string>Expansion Settings</string>
|
||||||
<width>120</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="icon">
|
||||||
<string>Restore Defaults</string>
|
<iconset theme="settings-3-line"/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
<item row="5" column="1">
|
||||||
</widget>
|
<widget class="QSlider" name="bufferMS">
|
||||||
</item>
|
<property name="minimum">
|
||||||
<item row="0" column="1" rowspan="4">
|
<number>15</number>
|
||||||
<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>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<property name="maximum">
|
||||||
</item>
|
<number>500</number>
|
||||||
<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>
|
</property>
|
||||||
</widget>
|
<property name="singleStep">
|
||||||
</item>
|
<number>1</number>
|
||||||
<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>
|
</property>
|
||||||
</widget>
|
<property name="pageStep">
|
||||||
</item>
|
<number>5</number>
|
||||||
<item row="3" column="1">
|
</property>
|
||||||
<widget class="QComboBox" name="dplLevel">
|
<property name="value">
|
||||||
<item>
|
<number>50</number>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>None (Default)</string>
|
<property name="orientation">
|
||||||
</property>
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
<property name="tickPosition">
|
||||||
<property name="text">
|
<enum>QSlider::TickPosition::TicksBothSides</enum>
|
||||||
<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>
|
||||||
</property>
|
<property name="tickInterval">
|
||||||
</item>
|
<number>20</number>
|
||||||
<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>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
<layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,0">
|
||||||
<item>
|
<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">
|
<property name="minimum">
|
||||||
<number>15</number>
|
<number>15</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>200</number>
|
<number>200</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="value">
|
|
||||||
<number>60</number>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="tickPosition">
|
<property name="tickPosition">
|
||||||
<enum>QSlider::TicksBelow</enum>
|
<enum>QSlider::TickPosition::TicksBothSides</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="tickInterval">
|
<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>
|
<number>20</number>
|
||||||
</property>
|
</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>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="outputLatencyLabel">
|
<widget class="QLabel" name="outputLatencyLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>20 ms</string>
|
<string>0 ms</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -463,41 +162,188 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="0" column="1">
|
||||||
<widget class="QLabel" name="label_9">
|
<widget class="QComboBox" name="audioBackend"/>
|
||||||
<property name="text">
|
|
||||||
<string>Output Backend:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="6" column="0">
|
||||||
<widget class="QComboBox" name="backend"/>
|
<widget class="QLabel" name="label_5">
|
||||||
</item>
|
|
||||||
<item row="4" column="1">
|
|
||||||
<widget class="QLabel" name="latencySummary">
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Maximum Latency:</string>
|
<string>Output 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>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QComboBox" name="outputDevice"/>
|
<widget class="QComboBox" name="outputDevice"/>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources>
|
||||||
|
<include location="../resources/resources.qrc"/>
|
||||||
|
</resources>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AudioStretchSettingsDialog</class>
|
||||||
|
<widget class="QDialog" name="AudioStretchSettingsDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>501</width>
|
||||||
|
<height>248</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Audio Stretch Settings</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Sequence Length:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="sequenceLength">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="sequenceLengthLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>30</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>Seekwindow Size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="seekWindowSize">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="seekWindowSizeLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>20</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>Overlap:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="overlap">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>15</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::TickPosition::TicksBelow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickInterval">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="overlapLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>10</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0" colspan="2">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::RestoreDefaults</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="icon">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string><html><head/><body><p><span style=" font-weight:700;">Audio Stretch Settings</span><br/>These settings fine-tune the behavior of the SoundTouch audio time stretcher when running outside of 100% speed.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::TextFormat::RichText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="useQuickSeek">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use Quickseek</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="useAAFilter">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use Anti-Aliasing Filter</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -432,6 +432,12 @@
|
||||||
<QtUi Include="Settings\ControllerMacroWidget.ui">
|
<QtUi Include="Settings\ControllerMacroWidget.ui">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtUi>
|
</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\ControllerBindingWidget_Popn.ui" />
|
||||||
<None Include="Settings\ControllerMappingSettingsDialog.ui" />
|
<None Include="Settings\ControllerMappingSettingsDialog.ui" />
|
||||||
<None Include="Settings\FolderSettingsWidget.ui" />
|
<None Include="Settings\FolderSettingsWidget.ui" />
|
||||||
|
|
|
@ -665,6 +665,12 @@
|
||||||
<QtUi Include="Settings\USBBindingWidget_GunCon2.ui">
|
<QtUi Include="Settings\USBBindingWidget_GunCon2.ui">
|
||||||
<Filter>Settings</Filter>
|
<Filter>Settings</Filter>
|
||||||
</QtUi>
|
</QtUi>
|
||||||
|
<QtUi Include="Settings\AudioExpansionSettingsDialog.ui">
|
||||||
|
<Filter>Settings</Filter>
|
||||||
|
</QtUi>
|
||||||
|
<QtUi Include="Settings\AudioStretchSettingsDialog.ui">
|
||||||
|
<Filter>Settings</Filter>
|
||||||
|
</QtUi>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="Settings\FolderSettingsWidget.ui">
|
<None Include="Settings\FolderSettingsWidget.ui">
|
||||||
|
|
|
@ -247,15 +247,12 @@ set(pcsx2CDVDHeaders
|
||||||
set(pcsx2SPU2Sources
|
set(pcsx2SPU2Sources
|
||||||
SPU2/ADSR.cpp
|
SPU2/ADSR.cpp
|
||||||
SPU2/Debug.cpp
|
SPU2/Debug.cpp
|
||||||
SPU2/DplIIdecoder.cpp
|
|
||||||
SPU2/Dma.cpp
|
SPU2/Dma.cpp
|
||||||
SPU2/Mixer.cpp
|
SPU2/Mixer.cpp
|
||||||
SPU2/spu2.cpp
|
SPU2/spu2.cpp
|
||||||
SPU2/ReadInput.cpp
|
SPU2/ReadInput.cpp
|
||||||
SPU2/RegTable.cpp
|
SPU2/RegTable.cpp
|
||||||
SPU2/Reverb.cpp
|
SPU2/Reverb.cpp
|
||||||
SPU2/SndOut.cpp
|
|
||||||
SPU2/SndOut_Cubeb.cpp
|
|
||||||
SPU2/spu2freeze.cpp
|
SPU2/spu2freeze.cpp
|
||||||
SPU2/spu2sys.cpp
|
SPU2/spu2sys.cpp
|
||||||
SPU2/Wavedump_wav.cpp
|
SPU2/Wavedump_wav.cpp
|
||||||
|
@ -270,21 +267,12 @@ set(pcsx2SPU2Headers
|
||||||
SPU2/Debug.h
|
SPU2/Debug.h
|
||||||
SPU2/defs.h
|
SPU2/defs.h
|
||||||
SPU2/Dma.h
|
SPU2/Dma.h
|
||||||
SPU2/Global.h
|
|
||||||
SPU2/interpolate_table.h
|
SPU2/interpolate_table.h
|
||||||
SPU2/Mixer.h
|
|
||||||
SPU2/spu2.h
|
SPU2/spu2.h
|
||||||
SPU2/regs.h
|
SPU2/regs.h
|
||||||
SPU2/SndOut.h
|
|
||||||
SPU2/spdif.h
|
SPU2/spdif.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
list(APPEND pcsx2SPU2Sources
|
|
||||||
SPU2/SndOut_XAudio2.cpp
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# DEV9 sources
|
# DEV9 sources
|
||||||
set(pcsx2DEV9Sources
|
set(pcsx2DEV9Sources
|
||||||
DEV9/AdapterUtils.cpp
|
DEV9/AdapterUtils.cpp
|
||||||
|
|
|
@ -3,8 +3,11 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Host/AudioStreamTypes.h"
|
||||||
|
|
||||||
#include "common/Pcsx2Defs.h"
|
#include "common/Pcsx2Defs.h"
|
||||||
#include "common/FPControl.h"
|
#include "common/FPControl.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -765,28 +768,22 @@ struct Pcsx2Config
|
||||||
|
|
||||||
struct SPU2Options
|
struct SPU2Options
|
||||||
{
|
{
|
||||||
enum class SynchronizationMode
|
enum class SPU2SyncMode : u8
|
||||||
{
|
{
|
||||||
|
Disabled,
|
||||||
TimeStretch,
|
TimeStretch,
|
||||||
ASync,
|
Count
|
||||||
NoSync,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr s32 MAX_VOLUME = 200;
|
static constexpr s32 MAX_VOLUME = 200;
|
||||||
|
static constexpr AudioBackend DEFAULT_BACKEND = AudioBackend::Cubeb;
|
||||||
static constexpr s32 MIN_LATENCY = 3;
|
static constexpr SPU2SyncMode DEFAULT_SYNC_MODE = SPU2SyncMode::TimeStretch;
|
||||||
static constexpr s32 MIN_LATENCY_TIMESTRETCH = 15;
|
|
||||||
static constexpr s32 MAX_LATENCY = 750;
|
|
||||||
|
|
||||||
static constexpr s32 MIN_SEQUENCE_LEN = 20;
|
static std::optional<SPU2SyncMode> ParseSyncMode(const char* str);
|
||||||
static constexpr s32 MAX_SEQUENCE_LEN = 100;
|
static const char* GetSyncModeName(SPU2SyncMode backend);
|
||||||
static constexpr s32 MIN_SEEKWINDOW = 10;
|
static const char* GetSyncModeDisplayName(SPU2SyncMode backend);
|
||||||
static constexpr s32 MAX_SEEKWINDOW = 30;
|
|
||||||
static constexpr s32 MIN_OVERLAP = 5;
|
|
||||||
static constexpr s32 MAX_OVERLAP = 15;
|
|
||||||
|
|
||||||
BITFIELD32()
|
BITFIELD32()
|
||||||
bool OutputLatencyMinimal : 1;
|
|
||||||
bool
|
bool
|
||||||
DebugEnabled : 1,
|
DebugEnabled : 1,
|
||||||
MsgToConsole : 1,
|
MsgToConsole : 1,
|
||||||
|
@ -794,7 +791,6 @@ struct Pcsx2Config
|
||||||
MsgVoiceOff : 1,
|
MsgVoiceOff : 1,
|
||||||
MsgDMA : 1,
|
MsgDMA : 1,
|
||||||
MsgAutoDMA : 1,
|
MsgAutoDMA : 1,
|
||||||
MsgOverruns : 1,
|
|
||||||
MsgCache : 1,
|
MsgCache : 1,
|
||||||
AccessLog : 1,
|
AccessLog : 1,
|
||||||
DMALog : 1,
|
DMALog : 1,
|
||||||
|
@ -805,26 +801,23 @@ struct Pcsx2Config
|
||||||
VisualDebugEnabled : 1;
|
VisualDebugEnabled : 1;
|
||||||
BITFIELD_END
|
BITFIELD_END
|
||||||
|
|
||||||
SynchronizationMode SynchMode = SynchronizationMode::TimeStretch;
|
u32 OutputVolume = 100;
|
||||||
|
u32 FastForwardVolume = 100;
|
||||||
|
bool OutputMuted = false;
|
||||||
|
|
||||||
s32 FinalVolume = 100;
|
AudioBackend Backend = DEFAULT_BACKEND;
|
||||||
s32 Latency = 60;
|
SPU2SyncMode SyncMode = DEFAULT_SYNC_MODE;
|
||||||
s32 OutputLatency = 20;
|
AudioStreamParameters StreamParameters;
|
||||||
s32 SpeakerConfiguration = 0;
|
|
||||||
s32 DplDecodingLevel = 0;
|
|
||||||
|
|
||||||
s32 SequenceLenMS = 30;
|
std::string DriverName;
|
||||||
s32 SeekWindowMS = 20;
|
|
||||||
s32 OverlapMS = 10;
|
|
||||||
|
|
||||||
std::string OutputModule;
|
|
||||||
std::string BackendName;
|
|
||||||
std::string DeviceName;
|
std::string DeviceName;
|
||||||
|
|
||||||
SPU2Options();
|
SPU2Options();
|
||||||
|
|
||||||
void LoadSave(SettingsWrapper& wrap);
|
void LoadSave(SettingsWrapper& wrap);
|
||||||
|
|
||||||
|
bool IsTimeStretchEnabled() const { return (SyncMode == SPU2SyncMode::TimeStretch); }
|
||||||
|
|
||||||
bool operator==(const SPU2Options& right) const;
|
bool operator==(const SPU2Options& right) const;
|
||||||
bool operator!=(const SPU2Options& right) const;
|
bool operator!=(const SPU2Options& right) const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
#include "GS/Renderers/Common/GSDevice.h"
|
#include "GS/Renderers/Common/GSDevice.h"
|
||||||
#include "GS/Renderers/Common/GSTexture.h"
|
#include "GS/Renderers/Common/GSTexture.h"
|
||||||
#include "SPU2/spu2.h"
|
#include "SPU2/spu2.h"
|
||||||
#include "SPU2/SndOut.h"
|
|
||||||
#include "Host.h"
|
#include "Host.h"
|
||||||
|
#include "Host/AudioStream.h"
|
||||||
#include "IconsFontAwesome5.h"
|
#include "IconsFontAwesome5.h"
|
||||||
#include "common/Assertions.h"
|
#include "common/Assertions.h"
|
||||||
#include "common/Console.h"
|
#include "common/Console.h"
|
||||||
|
@ -133,7 +133,7 @@ namespace GSCapture
|
||||||
{
|
{
|
||||||
static constexpr u32 NUM_FRAMES_IN_FLIGHT = 3;
|
static constexpr u32 NUM_FRAMES_IN_FLIGHT = 3;
|
||||||
static constexpr u32 MAX_PENDING_FRAMES = NUM_FRAMES_IN_FLIGHT * 2;
|
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;
|
static constexpr u32 AUDIO_CHANNELS = 2;
|
||||||
|
|
||||||
struct PendingFrame
|
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.
|
// Use packet size for frame if it supports it... but most don't.
|
||||||
if (acodec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
|
if (acodec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
|
||||||
s_audio_frame_size = SndOutPacketSize;
|
s_audio_frame_size = AudioStream::CHUNK_SIZE;
|
||||||
else
|
else
|
||||||
s_audio_frame_size = s_audio_codec_context->frame_size;
|
s_audio_frame_size = s_audio_codec_context->frame_size;
|
||||||
if (s_audio_frame_size >= AUDIO_BUFFER_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
|
// 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.
|
// 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)
|
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,
|
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))
|
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.
|
// In case the encoder is still using it.
|
||||||
if (s_audio_frame_pos == 0)
|
if (s_audio_frame_pos == 0)
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace GSCapture
|
||||||
{
|
{
|
||||||
bool BeginCapture(float fps, GSVector2i recommendedResolution, float aspect, std::string filename);
|
bool BeginCapture(float fps, GSVector2i recommendedResolution, float aspect, std::string filename);
|
||||||
bool DeliverVideoFrame(GSTexture* stex);
|
bool DeliverVideoFrame(GSTexture* stex);
|
||||||
void DeliverAudioPacket(const s16* frames); // SndOutPacketSize
|
void DeliverAudioPacket(const s16* frames); // AudioStream::CHUNK_SIZE
|
||||||
void EndCapture();
|
void EndCapture();
|
||||||
|
|
||||||
bool IsCapturing();
|
bool IsCapturing();
|
||||||
|
|
|
@ -342,15 +342,4 @@ std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const ch
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,11 +44,11 @@ static void HotkeyAdjustVolume(s32 fixed, s32 delta)
|
||||||
if (!VMManager::HasValidVM())
|
if (!VMManager::HasValidVM())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const s32 current_vol = SPU2::GetOutputVolume();
|
const s32 current_vol = static_cast<s32>(SPU2::GetOutputVolume());
|
||||||
const s32 new_volume =
|
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)
|
if (current_vol != new_volume)
|
||||||
SPU2::SetOutputVolume(new_volume);
|
SPU2::SetOutputVolume(static_cast<u32>(new_volume));
|
||||||
|
|
||||||
if (new_volume == 0)
|
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) {
|
DEFINE_HOTKEY("Mute", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Toggle Mute"), [](s32 pressed) {
|
||||||
if (!pressed && VMManager::HasValidVM())
|
if (!pressed && VMManager::HasValidVM())
|
||||||
HotkeyAdjustVolume((SPU2::GetOutputVolume() == 0) ? EmuConfig.SPU2.FinalVolume : 0, 0);
|
HotkeyAdjustVolume((SPU2::GetOutputVolume() == 0) ? SPU2::GetResetVolume() : 0, 0);
|
||||||
})
|
})
|
||||||
DEFINE_HOTKEY(
|
DEFINE_HOTKEY(
|
||||||
"FrameAdvance", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Frame Advance"), [](s32 pressed) {
|
"FrameAdvance", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Frame Advance"), [](s32 pressed) {
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "CDVD/CDVDdiscReader.h"
|
#include "CDVD/CDVDdiscReader.h"
|
||||||
#include "GameList.h"
|
#include "GameList.h"
|
||||||
#include "Host.h"
|
#include "Host.h"
|
||||||
|
#include "Host/AudioStream.h"
|
||||||
#include "INISettingsInterface.h"
|
#include "INISettingsInterface.h"
|
||||||
#include "ImGui/FullscreenUI.h"
|
#include "ImGui/FullscreenUI.h"
|
||||||
#include "ImGui/ImGuiFullscreen.h"
|
#include "ImGui/ImGuiFullscreen.h"
|
||||||
|
@ -323,6 +324,7 @@ namespace FullscreenUI
|
||||||
static void SetSettingsChanged(SettingsInterface* bsi);
|
static void SetSettingsChanged(SettingsInterface* bsi);
|
||||||
static bool GetEffectiveBoolSetting(SettingsInterface* bsi, const char* section, const char* key, bool default_value);
|
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 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 DoCopyGameSettings();
|
||||||
static void DoClearGameSettings();
|
static void DoClearGameSettings();
|
||||||
static void CopyGlobalControllerSettingsToGame();
|
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,
|
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,
|
bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
|
||||||
ImFont* summary_font = g_medium_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,
|
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,
|
const std::string& runtime_var, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
|
||||||
ImFont* summary_font = g_medium_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);
|
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(
|
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)
|
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,
|
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 */,
|
const std::string& runtime_var, float height /* = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT */, ImFont* font /* = g_large_font */,
|
||||||
ImFont* summary_font /* = g_medium_font */)
|
ImFont* summary_font /* = g_medium_font */)
|
||||||
|
@ -3851,67 +3924,62 @@ void FullscreenUI::DrawGraphicsSettingsPage()
|
||||||
|
|
||||||
void FullscreenUI::DrawAudioSettingsPage()
|
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();
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
||||||
|
|
||||||
BeginMenuButtons();
|
BeginMenuButtons();
|
||||||
|
|
||||||
MenuHeading(FSUI_CSTR("Runtime Settings"));
|
MenuHeading(FSUI_CSTR("Audio Control"));
|
||||||
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("Mixing Settings"));
|
DrawIntRangeSetting(bsi, FSUI_CSTR("Output Volume"),
|
||||||
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_SYNC_ALT, "Synchronization Mode"),
|
FSUI_CSTR("Controls the volume of the audio played on the host."), "SPU2/Output", "OutputVolume", 100,
|
||||||
FSUI_CSTR("Changes when SPU samples are generated relative to system emulation."), "SPU2/Output", "SynchMode",
|
0, 100, "%d%%");
|
||||||
static_cast<int>(Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch), synchronization_modes,
|
DrawIntRangeSetting(bsi, FSUI_CSTR("Fast Forward Volume"),
|
||||||
std::size(synchronization_modes), true);
|
FSUI_CSTR("Controls the volume of the audio played on the host when fast forwarding."), "SPU2/Output",
|
||||||
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_PF_SPEAKER_ALT, "Expansion Mode"),
|
"FastForwardVolume", 100, 0, 100, "%d%%");
|
||||||
FSUI_CSTR("Determines how the stereo output is transformed to greater speaker counts."), "SPU2/Output", "SpeakerConfiguration", 0,
|
DrawToggleSetting(bsi, FSUI_CSTR("Mute All Sound"),
|
||||||
expansion_modes, std::size(expansion_modes), true);
|
FSUI_CSTR("Prevents the emulator from producing any audible sound."), "SPU2/Output", "OutputMuted",
|
||||||
|
false);
|
||||||
|
|
||||||
MenuHeading(FSUI_CSTR("Output Settings"));
|
MenuHeading(FSUI_CSTR("Backend 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("Timestretch Settings"));
|
DrawEnumSetting(
|
||||||
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_RULER_HORIZONTAL, "Sequence Length"),
|
bsi, FSUI_CSTR("Audio Backend"),
|
||||||
FSUI_CSTR("Affects how the timestretcher operates when not running at 100% speed."), "Soundtouch", "SequenceLengthMS", 30, 20, 100,
|
FSUI_CSTR("The audio backend determines how frames produced by the emulator are submitted to the host."), "SPU2/Output",
|
||||||
FSUI_CSTR("%d ms"));
|
"Backend", Pcsx2Config::SPU2Options::DEFAULT_BACKEND, &AudioStream::ParseBackendName, &AudioStream::GetBackendName,
|
||||||
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_WINDOW_MAXIMIZE, "Seekwindow Size"),
|
&AudioStream::GetBackendDisplayName, AudioBackend::Count);
|
||||||
FSUI_CSTR("Affects how the timestretcher operates when not running at 100% speed."), "Soundtouch", "SeekWindowMS", 20, 10, 30,
|
DrawEnumSetting(bsi, FSUI_CSTR("Expansion"),
|
||||||
FSUI_CSTR("%d ms"));
|
FSUI_CSTR("Determines how audio is expanded from stereo to surround for supported games."), "SPU2/Output",
|
||||||
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_RECEIPT, "Overlap"),
|
"ExpansionMode", AudioStreamParameters::DEFAULT_EXPANSION_MODE, &AudioStream::ParseExpansionMode,
|
||||||
FSUI_CSTR("Affects how the timestretcher operates when not running at 100% speed."), "Soundtouch", "OverlapMS", 20, 5, 15, FSUI_CSTR("%d ms"));
|
&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();
|
EndMenuButtons();
|
||||||
}
|
}
|
||||||
|
|
|
@ -489,8 +489,8 @@ ImFont* ImGuiManager::AddFixedFont(float size)
|
||||||
bool ImGuiManager::AddIconFonts(float size)
|
bool ImGuiManager::AddIconFonts(float size)
|
||||||
{
|
{
|
||||||
// clang-format off
|
// 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_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,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_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
|
// clang-format on
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -240,7 +240,8 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f
|
||||||
if (GSConfig.OsdShowIndicators)
|
if (GSConfig.OsdShowIndicators)
|
||||||
{
|
{
|
||||||
const float target_speed = VMManager::GetTargetSpeed();
|
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 (!is_normal_speed)
|
||||||
{
|
{
|
||||||
if (target_speed == EmuConfig.EmulationSpeed.SlomoScalar) // Slow-Motion
|
if (target_speed == EmuConfig.EmulationSpeed.SlomoScalar) // Slow-Motion
|
||||||
|
|
|
@ -10,12 +10,13 @@
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "GS.h"
|
#include "GS.h"
|
||||||
#include "CDVD/CDVDcommon.h"
|
#include "CDVD/CDVDcommon.h"
|
||||||
|
#include "Host.h"
|
||||||
|
#include "Host/AudioStream.h"
|
||||||
#include "SIO/Memcard/MemoryCardFile.h"
|
#include "SIO/Memcard/MemoryCardFile.h"
|
||||||
#include "SIO/Pad/Pad.h"
|
#include "SIO/Pad/Pad.h"
|
||||||
#include "USB/USB.h"
|
#include "USB/USB.h"
|
||||||
|
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include "common/RedtapeWindows.h"
|
#include "common/RedtapeWindows.h"
|
||||||
#include <KnownFolders.h>
|
#include <KnownFolders.h>
|
||||||
|
@ -1025,10 +1026,42 @@ bool Pcsx2Config::GSOptions::UseHardwareRenderer() const
|
||||||
return (Renderer != GSRendererType::Null && Renderer != GSRendererType::SW);
|
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()
|
Pcsx2Config::SPU2Options::SPU2Options()
|
||||||
{
|
{
|
||||||
bitset = 0;
|
bitset = 0;
|
||||||
OutputModule = "cubeb";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
|
void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
|
||||||
|
@ -1042,7 +1075,6 @@ void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
|
||||||
SettingsWrapBitBoolEx(MsgVoiceOff, "Show_Messages_Voice_Off");
|
SettingsWrapBitBoolEx(MsgVoiceOff, "Show_Messages_Voice_Off");
|
||||||
SettingsWrapBitBoolEx(MsgDMA, "Show_Messages_DMA_Transfer");
|
SettingsWrapBitBoolEx(MsgDMA, "Show_Messages_DMA_Transfer");
|
||||||
SettingsWrapBitBoolEx(MsgAutoDMA, "Show_Messages_AutoDMA");
|
SettingsWrapBitBoolEx(MsgAutoDMA, "Show_Messages_AutoDMA");
|
||||||
SettingsWrapBitBoolEx(MsgOverruns, "Show_Messages_Overruns");
|
|
||||||
SettingsWrapBitBoolEx(MsgCache, "Show_Messages_CacheStats");
|
SettingsWrapBitBoolEx(MsgCache, "Show_Messages_CacheStats");
|
||||||
|
|
||||||
SettingsWrapBitBoolEx(AccessLog, "Log_Register_Access");
|
SettingsWrapBitBoolEx(AccessLog, "Log_Register_Access");
|
||||||
|
@ -1061,7 +1093,6 @@ void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
|
||||||
MsgVoiceOff = false;
|
MsgVoiceOff = false;
|
||||||
MsgDMA = false;
|
MsgDMA = false;
|
||||||
MsgAutoDMA = false;
|
MsgAutoDMA = false;
|
||||||
MsgOverruns = false;
|
|
||||||
MsgCache = false;
|
MsgCache = false;
|
||||||
AccessLog = false;
|
AccessLog = false;
|
||||||
DMALog = false;
|
DMALog = false;
|
||||||
|
@ -1071,28 +1102,19 @@ void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
|
||||||
RegDump = false;
|
RegDump = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
SettingsWrapSection("SPU2/Mixing");
|
|
||||||
|
|
||||||
SettingsWrapEntry(FinalVolume);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
SettingsWrapSection("SPU2/Output");
|
SettingsWrapSection("SPU2/Output");
|
||||||
|
SettingsWrapEntry(OutputVolume);
|
||||||
SettingsWrapEntry(OutputModule);
|
SettingsWrapEntry(FastForwardVolume);
|
||||||
SettingsWrapEntry(BackendName);
|
SettingsWrapEntry(OutputMuted);
|
||||||
|
SettingsWrapParsedEnum(Backend, "Backend", &AudioStream::ParseBackendName, &AudioStream::GetBackendName);
|
||||||
|
SettingsWrapParsedEnum(SyncMode, "SyncMode", &ParseSyncMode, &GetSyncModeName);
|
||||||
|
SettingsWrapEntry(DriverName);
|
||||||
SettingsWrapEntry(DeviceName);
|
SettingsWrapEntry(DeviceName);
|
||||||
SettingsWrapEntry(Latency);
|
StreamParameters.LoadSave(wrap, CURRENT_SETTINGS_SECTION);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// clampy clamp
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Pcsx2Config::SPU2Options::operator!=(const SPU2Options& right) const
|
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
|
bool Pcsx2Config::SPU2Options::operator==(const SPU2Options& right) const
|
||||||
{
|
{
|
||||||
return OpEqu(bitset) &&
|
return OpEqu(bitset) &&
|
||||||
|
OpEqu(OutputVolume) &&
|
||||||
OpEqu(SynchMode) &&
|
OpEqu(FastForwardVolume) &&
|
||||||
|
OpEqu(OutputMuted) &&
|
||||||
OpEqu(FinalVolume) &&
|
OpEqu(Backend) &&
|
||||||
OpEqu(Latency) &&
|
OpEqu(StreamParameters) &&
|
||||||
OpEqu(OutputLatency) &&
|
OpEqu(DriverName) &&
|
||||||
OpEqu(SpeakerConfiguration) &&
|
|
||||||
OpEqu(DplDecodingLevel) &&
|
|
||||||
|
|
||||||
OpEqu(SequenceLenMS) &&
|
|
||||||
OpEqu(SeekWindowMS) &&
|
|
||||||
OpEqu(OverlapMS) &&
|
|
||||||
|
|
||||||
OpEqu(OutputModule) &&
|
|
||||||
OpEqu(BackendName) &&
|
|
||||||
OpEqu(DeviceName);
|
OpEqu(DeviceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "SPU2/Global.h"
|
#include "SPU2/defs.h"
|
||||||
|
|
||||||
#include "common/Assertions.h"
|
#include "common/Assertions.h"
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "SPU2/Global.h"
|
#include "SPU2/Debug.h"
|
||||||
|
#include "SPU2/regs.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
#include "common/Console.h"
|
#include "common/Console.h"
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
|
#include "SPU2/defs.h"
|
||||||
|
|
||||||
namespace SPU2
|
namespace SPU2
|
||||||
{
|
{
|
||||||
#ifdef PCSX2_DEVBUILD
|
#ifdef PCSX2_DEVBUILD
|
||||||
|
@ -17,7 +19,6 @@ namespace SPU2
|
||||||
__fi static bool MsgVoiceOff() { return EmuConfig.SPU2.MsgVoiceOff; }
|
__fi static bool MsgVoiceOff() { return EmuConfig.SPU2.MsgVoiceOff; }
|
||||||
__fi static bool MsgDMA() { return EmuConfig.SPU2.MsgDMA; }
|
__fi static bool MsgDMA() { return EmuConfig.SPU2.MsgDMA; }
|
||||||
__fi static bool MsgAutoDMA() { return EmuConfig.SPU2.MsgAutoDMA; }
|
__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 MsgCache() { return EmuConfig.SPU2.MsgCache; }
|
||||||
|
|
||||||
__fi static bool AccessLog() { return EmuConfig.SPU2.AccessLog; }
|
__fi static bool AccessLog() { return EmuConfig.SPU2.AccessLog; }
|
||||||
|
@ -93,7 +94,7 @@ namespace WaveDump
|
||||||
extern void Open();
|
extern void Open();
|
||||||
extern void Close();
|
extern void Close();
|
||||||
extern void WriteCore(uint coreidx, CoreSourceType src, s16 left, s16 right);
|
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
|
} // namespace WaveDump
|
||||||
|
|
||||||
using WaveDump::CoreSrc_DryVoiceMix;
|
using WaveDump::CoreSrc_DryVoiceMix;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "SPU2/Global.h"
|
#include "SPU2/defs.h"
|
||||||
|
#include "SPU2/Debug.h"
|
||||||
#include "SPU2/Dma.h"
|
#include "SPU2/Dma.h"
|
||||||
#include "SPU2/spu2.h"
|
#include "SPU2/spu2.h"
|
||||||
#include "R3000A.h"
|
#include "R3000A.h"
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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"
|
|
|
@ -1,12 +1,14 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "common/Assertions.h"
|
#include "Host/AudioStream.h"
|
||||||
|
#include "SPU2/Debug.h"
|
||||||
#include "SPU2/Global.h"
|
#include "SPU2/defs.h"
|
||||||
#include "SPU2/spu2.h"
|
#include "SPU2/spu2.h"
|
||||||
#include "SPU2/interpolate_table.h"
|
#include "SPU2/interpolate_table.h"
|
||||||
|
|
||||||
|
#include "common/Assertions.h"
|
||||||
|
|
||||||
static const s32 tbl_XA_Factor[16][2] =
|
static const s32 tbl_XA_Factor[16][2] =
|
||||||
{
|
{
|
||||||
{0, 0},
|
{0, 0},
|
||||||
|
@ -565,16 +567,7 @@ static StereoOut32 DCFilter(StereoOut32 input) {
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
// used to throttle the output rate of cache stat reports
|
__forceinline void spu2Mix()
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
// Note: Playmode 4 is SPDIF, which overrides other inputs.
|
// Note: Playmode 4 is SPDIF, which overrides other inputs.
|
||||||
StereoOut32 InputData[2] =
|
StereoOut32 InputData[2] =
|
||||||
|
@ -638,17 +631,23 @@ __forceinline
|
||||||
Out = ApplyVolume(Out, {0x4fff, 0x4fff});
|
Out = ApplyVolume(Out, {0x4fff, 0x4fff});
|
||||||
Out = DCFilter(Out);
|
Out = DCFilter(Out);
|
||||||
|
|
||||||
// Final clamp, take care not to exceed 16 bits from here on
|
#ifdef PCSX2_DEVBUILD
|
||||||
Out = clamp_mix(Out);
|
// Log final output to wavefile.
|
||||||
SndBuffer::Write(StereoOut16(Out));
|
WaveDump::WriteCore(1, CoreSrc_External, Out);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
spu2Output(Out);
|
||||||
|
|
||||||
// Update AutoDMA output positioning
|
// Update AutoDMA output positioning
|
||||||
OutPos++;
|
OutPos++;
|
||||||
if (OutPos >= 0x200)
|
if (OutPos >= 0x200)
|
||||||
OutPos = 0;
|
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++;
|
p_cachestat_counter++;
|
||||||
if (p_cachestat_counter > (48000 * 10))
|
if (p_cachestat_counter > (48000 * 10))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
extern void Mix();
|
|
|
@ -1,12 +1,12 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "Global.h"
|
|
||||||
#include "Dma.h"
|
#include "Dma.h"
|
||||||
#include "IopDma.h"
|
#include "IopDma.h"
|
||||||
#include "IopHw.h"
|
#include "IopHw.h"
|
||||||
|
#include "SPU2/Debug.h"
|
||||||
#include "spu2.h" // required for ENABLE_NEW_IOPDMA_SPU2 define
|
#include "SPU2/defs.h"
|
||||||
|
#include "SPU2/spu2.h"
|
||||||
|
|
||||||
// Core 0 Input is "SPDIF mode" - Source audio is AC3 compressed.
|
// Core 0 Input is "SPDIF mode" - Source audio is AC3 compressed.
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "Global.h"
|
#include "SPU2/defs.h"
|
||||||
|
#include "SPU2/regs.h"
|
||||||
|
|
||||||
#define U16P(x) ((u16*)&(x))
|
#define U16P(x) ((u16*)&(x))
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "Global.h"
|
#include "SPU2/defs.h"
|
||||||
#include "GS/GSVector.h"
|
#include "GS/GSVector.h"
|
||||||
|
|
||||||
#include "common/Console.h"
|
#include "common/Console.h"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "GS/GSVector.h"
|
#include "GS/GSVector.h"
|
||||||
#include "Global.h"
|
#include "SPU2/defs.h"
|
||||||
|
|
||||||
MULTI_ISA_UNSHARED_START
|
MULTI_ISA_UNSHARED_START
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
|
|
@ -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, ¶ms, &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, ¶ms,
|
|
||||||
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, ¶ms, &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;
|
|
|
@ -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;
|
|
|
@ -1,7 +1,8 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "SPU2/Global.h"
|
#include "SPU2/Debug.h"
|
||||||
|
#include "SPU2/spu2.h"
|
||||||
#include "pcsx2/Config.h"
|
#include "pcsx2/Config.h"
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
|
|
||||||
|
@ -30,8 +31,6 @@ namespace WaveDump
|
||||||
|
|
||||||
void Open()
|
void Open()
|
||||||
{
|
{
|
||||||
if (!IsDevBuild)
|
|
||||||
return;
|
|
||||||
if (!SPU2::WaveLog())
|
if (!SPU2::WaveLog())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -40,10 +39,10 @@ namespace WaveDump
|
||||||
for (int srcidx = 0; srcidx < CoreSrc_Count; srcidx++)
|
for (int srcidx = 0; srcidx < CoreSrc_Count; srcidx++)
|
||||||
{
|
{
|
||||||
m_CoreWav[cidx][srcidx].reset();
|
m_CoreWav[cidx][srcidx].reset();
|
||||||
|
|
||||||
std::string wavfilename(Path::Combine(EmuFolders::Logs, fmt::format("spu2x-Core{}d-{}.wav", cidx, m_tbl_CoreOutputTypeNames[srcidx])));
|
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>();
|
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));
|
Console.Error(fmt::format("Failed to open '{}'. Wave Log for this core source disabled.", wavfilename));
|
||||||
m_CoreWav[cidx][srcidx].reset();
|
m_CoreWav[cidx][srcidx].reset();
|
||||||
|
@ -54,8 +53,6 @@ namespace WaveDump
|
||||||
|
|
||||||
void Close()
|
void Close()
|
||||||
{
|
{
|
||||||
if (!IsDevBuild)
|
|
||||||
return;
|
|
||||||
for (uint cidx = 0; cidx < 2; cidx++)
|
for (uint cidx = 0; cidx < 2; cidx++)
|
||||||
{
|
{
|
||||||
for (int srcidx = 0; srcidx < CoreSrc_Count; srcidx++)
|
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;
|
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)
|
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
|
} // namespace WaveDump
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "SPU2/Mixer.h"
|
|
||||||
#include "SPU2/SndOut.h"
|
|
||||||
#include "SPU2/Global.h"
|
|
||||||
|
|
||||||
#include "GS/MultiISA.h"
|
#include "GS/MultiISA.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
@ -23,10 +19,54 @@ extern const std::array<u16*, 0x401> regtable;
|
||||||
#define spu2Rs16(mmem) (*(s16*)((s8*)spu2regs + ((mmem)&0x1fff)))
|
#define spu2Rs16(mmem) (*(s16*)((s8*)spu2regs + ((mmem)&0x1fff)))
|
||||||
#define spu2Ru16(mmem) (*(u16*)((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* GetMemPtr(u32 addr);
|
||||||
extern s16 spu2M_Read(u32 addr);
|
extern s16 spu2M_Read(u32 addr);
|
||||||
extern void spu2M_Write(u32 addr, s16 value);
|
extern void spu2M_Write(u32 addr, s16 value);
|
||||||
extern void spu2M_Write(u32 addr, u16 value);
|
extern void spu2M_Write(u32 addr, u16 value);
|
||||||
|
extern void spu2Mix();
|
||||||
|
extern void spu2Output(StereoOut32 out);
|
||||||
|
|
||||||
static __forceinline s16 SignExtend16(u16 v)
|
static __forceinline s16 SignExtend16(u16 v)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,31 +1,41 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "SPU2/Global.h"
|
|
||||||
#include "SPU2/Debug.h"
|
|
||||||
#include "SPU2/spu2.h"
|
#include "SPU2/spu2.h"
|
||||||
|
#include "SPU2/defs.h"
|
||||||
|
#include "SPU2/Debug.h"
|
||||||
#include "SPU2/Dma.h"
|
#include "SPU2/Dma.h"
|
||||||
|
#include "Host/AudioStream.h"
|
||||||
|
#include "Host.h"
|
||||||
#include "GS/GSCapture.h"
|
#include "GS/GSCapture.h"
|
||||||
#include "MTGS.h"
|
#include "MTGS.h"
|
||||||
#include "R3000A.h"
|
#include "R3000A.h"
|
||||||
|
#include "VMManager.h"
|
||||||
|
|
||||||
|
#include "common/Error.h"
|
||||||
|
|
||||||
|
const StereoOut32 StereoOut32::Empty(0, 0);
|
||||||
|
|
||||||
namespace SPU2
|
namespace SPU2
|
||||||
{
|
{
|
||||||
static void InitSndBuffer();
|
static void CreateOutputStream();
|
||||||
static void UpdateSampleRate();
|
static void UpdateSampleRate();
|
||||||
|
static float GetNominalRate();
|
||||||
static void InternalReset(bool psxmode);
|
static void InternalReset(bool psxmode);
|
||||||
} // namespace SPU2
|
} // namespace SPU2
|
||||||
|
|
||||||
static double s_device_sample_rate_multiplier = 1.0;
|
|
||||||
static bool s_psxmode = false;
|
|
||||||
|
|
||||||
int SampleRate = 48000;
|
|
||||||
|
|
||||||
u32 lClocks = 0;
|
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);
|
Cores[1].DoDMAwrite(pMem, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SPU2::InitSndBuffer()
|
void SPU2::CreateOutputStream()
|
||||||
{
|
{
|
||||||
Console.WriteLn("Initializing SndBuffer at sample rate of %u...", SampleRate);
|
// Persist volume through stream recreates.
|
||||||
if (SndBuffer::Init(EmuConfig.SPU2.OutputModule.c_str()))
|
const u32 volume = s_output_stream ? s_output_stream->GetOutputVolume() : GetResetVolume();
|
||||||
return;
|
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..
|
Host::ReportErrorAsync("Error",
|
||||||
const int original_sample_rate = SampleRate;
|
fmt::format("Failed to create or configure audio stream, falling back to null output. The error was:\n{}",
|
||||||
Console.Error("Failed to init SPU2 at adjusted sample rate %u, trying console rate.", SampleRate);
|
error.GetDescription()));
|
||||||
SampleRate = GetConsoleSampleRate();
|
|
||||||
if (SndBuffer::Init(EmuConfig.SPU2.OutputModule.c_str()))
|
|
||||||
return;
|
|
||||||
|
|
||||||
SampleRate = original_sample_rate;
|
s_output_stream = AudioStream::CreateNullStream(sample_rate, EmuConfig.SPU2.StreamParameters.buffer_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
// just use nullout
|
s_output_stream->SetOutputVolume(volume);
|
||||||
if (!SndBuffer::Init("nullout"))
|
s_output_stream->SetNominalRate(GetNominalRate());
|
||||||
pxFailRel("Failed to initialize nullout.");
|
s_output_stream->SetPaused(VMManager::GetState() == VMState::Paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SPU2::UpdateSampleRate()
|
void SPU2::UpdateSampleRate()
|
||||||
{
|
{
|
||||||
const int new_sample_rate = static_cast<int>(std::round(static_cast<double>(GetConsoleSampleRate()) * s_device_sample_rate_multiplier));
|
if (s_output_stream && s_output_stream->GetSampleRate() == GetConsoleSampleRate())
|
||||||
if (SampleRate == new_sample_rate)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SndBuffer::Cleanup();
|
CreateOutputStream();
|
||||||
SampleRate = new_sample_rate;
|
|
||||||
InitSndBuffer();
|
|
||||||
|
|
||||||
// Can't be capturing when the sample rate changes.
|
// Can't be capturing when the sample rate changes.
|
||||||
if (IsAudioCaptureActive())
|
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)
|
void SPU2::InternalReset(bool psxmode)
|
||||||
{
|
{
|
||||||
|
s_current_chunk_pos = 0;
|
||||||
s_psxmode = psxmode;
|
s_psxmode = psxmode;
|
||||||
if (!s_psxmode)
|
if (!s_psxmode)
|
||||||
{
|
{
|
||||||
|
@ -151,18 +199,19 @@ void SPU2::Reset(bool psxmode)
|
||||||
|
|
||||||
void SPU2::OnTargetSpeedChanged()
|
void SPU2::OnTargetSpeedChanged()
|
||||||
{
|
{
|
||||||
if (EmuConfig.SPU2.SynchMode != Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch)
|
if (!s_output_stream)
|
||||||
SndBuffer::ResetBuffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SPU2::SetDeviceSampleRateMultiplier(double multiplier)
|
|
||||||
{
|
|
||||||
if (s_device_sample_rate_multiplier == multiplier)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
s_device_sample_rate_multiplier = multiplier;
|
if (!s_output_stream->IsStretchEnabled())
|
||||||
if (SndBuffer::IsOpen())
|
{
|
||||||
UpdateSampleRate();
|
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()
|
bool SPU2::Open()
|
||||||
|
@ -182,13 +231,11 @@ bool SPU2::Open()
|
||||||
|
|
||||||
InternalReset(false);
|
InternalReset(false);
|
||||||
|
|
||||||
SampleRate = static_cast<int>(std::round(static_cast<double>(GetConsoleSampleRate()) * s_device_sample_rate_multiplier));
|
CreateOutputStream();
|
||||||
InitSndBuffer();
|
|
||||||
#ifdef PCSX2_DEVBUILD
|
#ifdef PCSX2_DEVBUILD
|
||||||
WaveDump::Open();
|
WaveDump::Open();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SetOutputVolume(EmuConfig.SPU2.FinalVolume);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +243,7 @@ void SPU2::Close()
|
||||||
{
|
{
|
||||||
FileLog("[%10d] SPU2 Close\n", Cycles);
|
FileLog("[%10d] SPU2 Close\n", Cycles);
|
||||||
|
|
||||||
SndBuffer::Cleanup();
|
s_output_stream.reset();
|
||||||
|
|
||||||
#ifdef PCSX2_DEVBUILD
|
#ifdef PCSX2_DEVBUILD
|
||||||
WaveDump::Close();
|
WaveDump::Close();
|
||||||
|
@ -212,6 +259,44 @@ bool SPU2::IsRunningPSXMode()
|
||||||
return s_psxmode;
|
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()
|
void SPU2async()
|
||||||
{
|
{
|
||||||
TimeUpdate(psxRegs.cycle);
|
TimeUpdate(psxRegs.cycle);
|
||||||
|
@ -327,47 +412,18 @@ s32 SPU2freeze(FreezeAction mode, freezeData* data)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SPU2::CheckForConfigChanges(const Pcsx2Config& old_config)
|
__forceinline void spu2Output(StereoOut32 out)
|
||||||
{
|
{
|
||||||
if (EmuConfig.SPU2 == old_config.SPU2)
|
// Final clamp, take care not to exceed 16 bits from here on
|
||||||
return;
|
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));
|
||||||
const Pcsx2Config::SPU2Options& opts = EmuConfig.SPU2;
|
if (s_current_chunk_pos == s_current_chunk.size())
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
SndBuffer::Cleanup();
|
s_current_chunk_pos = 0;
|
||||||
InitSndBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef PCSX2_DEVBUILD
|
s_output_stream->WriteChunk(s_current_chunk.data());
|
||||||
// AccessLog controls file output.
|
|
||||||
if (opts.AccessLog != oldopts.AccessLog)
|
if (SPU2::IsAudioCaptureActive()) [[unlikely]]
|
||||||
{
|
GSCapture::DeliverAudioPacket(s_current_chunk.data());
|
||||||
if (AccessLog())
|
|
||||||
OpenFileLog();
|
|
||||||
else
|
|
||||||
CloseFileLog();
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,25 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "SaveState.h"
|
#include "SaveState.h"
|
||||||
#include "IopCounters.h"
|
#include "IopCounters.h"
|
||||||
#include <mutex>
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
struct Pcsx2Config;
|
struct Pcsx2Config;
|
||||||
|
|
||||||
|
class AudioStream;
|
||||||
|
|
||||||
namespace SPU2
|
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.
|
/// Open/close, call at VM startup/shutdown.
|
||||||
bool Open();
|
bool Open();
|
||||||
void Close();
|
void Close();
|
||||||
|
@ -22,10 +31,13 @@ void Reset(bool psxmode);
|
||||||
void CheckForConfigChanges(const Pcsx2Config& old_config);
|
void CheckForConfigChanges(const Pcsx2Config& old_config);
|
||||||
|
|
||||||
/// Returns the current output volume, irrespective of the configuration.
|
/// Returns the current output volume, irrespective of the configuration.
|
||||||
s32 GetOutputVolume();
|
u32 GetOutputVolume();
|
||||||
|
|
||||||
/// Directly updates the output volume without going through the configuration.
|
/// 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.
|
/// Pauses/resumes the output stream.
|
||||||
void SetOutputPaused(bool paused);
|
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.
|
/// Clears output buffers in no-sync mode, prevents long delays after fast forwarding.
|
||||||
void OnTargetSpeedChanged();
|
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.
|
/// Returns true if we're currently running in PSX mode.
|
||||||
bool IsRunningPSXMode();
|
bool IsRunningPSXMode();
|
||||||
|
|
||||||
/// Returns the current sample rate the SPU2 is operating at.
|
/// Returns the current sample rate the SPU2 is operating at.
|
||||||
s32 GetConsoleSampleRate();
|
u32 GetConsoleSampleRate();
|
||||||
|
|
||||||
/// Tells SPU2 to forward audio packets to GSCapture.
|
/// Tells SPU2 to forward audio packets to GSCapture.
|
||||||
void SetAudioCaptureActive(bool active);
|
void SetAudioCaptureActive(bool active);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// 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 "SPU2/spu2.h" // hopefully temporary, until I resolve lClocks depdendency
|
||||||
#include "IopMem.h"
|
#include "IopMem.h"
|
||||||
|
|
||||||
|
@ -110,8 +110,6 @@ s32 SPU2Savestate::ThawIt(DataBlock& spud)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SndBuffer::ClearContents();
|
|
||||||
|
|
||||||
memcpy(spu2regs, spud.unkregs, sizeof(spud.unkregs));
|
memcpy(spu2regs, spud.unkregs, sizeof(spud.unkregs));
|
||||||
memcpy(_spu2mem, spud.mem, sizeof(spud.mem));
|
memcpy(_spu2mem, spud.mem, sizeof(spud.mem));
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,10 @@
|
||||||
#include "IopDma.h"
|
#include "IopDma.h"
|
||||||
#include "IopHw.h"
|
#include "IopHw.h"
|
||||||
#include "R3000A.h"
|
#include "R3000A.h"
|
||||||
|
#include "SPU2/Debug.h"
|
||||||
|
#include "SPU2/defs.h"
|
||||||
#include "SPU2/Dma.h"
|
#include "SPU2/Dma.h"
|
||||||
#include "SPU2/Global.h"
|
#include "SPU2/regs.h"
|
||||||
#include "SPU2/spu2.h"
|
#include "SPU2/spu2.h"
|
||||||
|
|
||||||
#include "common/Console.h"
|
#include "common/Console.h"
|
||||||
|
@ -214,10 +216,10 @@ void V_Voice::Stop()
|
||||||
ADSR.Phase = V_ADSR::PHASE_STOPPED;
|
ADSR.Phase = V_ADSR::PHASE_STOPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint TickInterval = 768;
|
static constexpr uint TickInterval = 768;
|
||||||
static const int SanityInterval = 4800;
|
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]);
|
V_Voice& vc(Cores[coreidx].Voices[voiceidx]);
|
||||||
|
|
||||||
|
@ -275,11 +277,6 @@ __forceinline void TimeUpdate(u32 cClocks)
|
||||||
lClocks = cClocks - dClocks;
|
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
|
//Update Mixing Progress
|
||||||
while (dClocks >= TickInterval)
|
while (dClocks >= TickInterval)
|
||||||
{
|
{
|
||||||
|
@ -307,10 +304,8 @@ __forceinline void TimeUpdate(u32 cClocks)
|
||||||
if(Cores[c].KeyOn & (1 << v))
|
if(Cores[c].KeyOn & (1 << v))
|
||||||
if(StartQueuedVoice(c, v))
|
if(StartQueuedVoice(c, v))
|
||||||
Cores[c].KeyOn &= ~(1 << v);
|
Cores[c].KeyOn &= ~(1 << v);
|
||||||
// Note: IOP does not use MMX regs, so no need to save them.
|
|
||||||
//SaveMMXRegs();
|
spu2Mix();
|
||||||
Mix();
|
|
||||||
//RestoreMMXRegs();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Update DMA4 interrupt delay counter
|
//Update DMA4 interrupt delay counter
|
||||||
|
|
|
@ -186,6 +186,7 @@ static LimiterModeType s_limiter_mode = LimiterModeType::Nominal;
|
||||||
static s64 s_limiter_ticks_per_frame = 0;
|
static s64 s_limiter_ticks_per_frame = 0;
|
||||||
static u64 s_limiter_frame_start = 0;
|
static u64 s_limiter_frame_start = 0;
|
||||||
static float s_target_speed = 0.0f;
|
static float s_target_speed = 0.0f;
|
||||||
|
static bool s_target_speed_synced_to_host = false;
|
||||||
static bool s_use_vsync_for_timing = 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.
|
// 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)
|
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;
|
s_use_vsync_for_timing = false;
|
||||||
return target_speed;
|
return target_speed;
|
||||||
}
|
}
|
||||||
|
@ -1997,23 +1998,19 @@ double VMManager::AdjustToHostRefreshRate(float frame_rate, float target_speed)
|
||||||
if (!GSGetHostRefreshRate(&host_refresh_rate))
|
if (!GSGetHostRefreshRate(&host_refresh_rate))
|
||||||
{
|
{
|
||||||
Console.Warning("Cannot sync to host refresh since the query failed.");
|
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;
|
s_use_vsync_for_timing = false;
|
||||||
return target_speed;
|
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);
|
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);
|
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,
|
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");
|
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 syncing_to_host ? ratio : target_speed;
|
||||||
return target_speed;
|
|
||||||
|
|
||||||
target_speed *= ratio;
|
|
||||||
SPU2::SetDeviceSampleRateMultiplier(ratio);
|
|
||||||
return target_speed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float VMManager::GetTargetSpeedForLimiterMode(LimiterModeType mode)
|
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()
|
float VMManager::GetFrameRate()
|
||||||
{
|
{
|
||||||
return GetVerticalFrequency();
|
return GetVerticalFrequency();
|
||||||
|
@ -2928,9 +2930,6 @@ void VMManager::EnforceAchievementsChallengeModeSettings()
|
||||||
EmuConfig.Speedhacks.EECycleRate =
|
EmuConfig.Speedhacks.EECycleRate =
|
||||||
std::max<decltype(EmuConfig.Speedhacks.EECycleRate)>(EmuConfig.Speedhacks.EECycleRate, 0);
|
std::max<decltype(EmuConfig.Speedhacks.EECycleRate)>(EmuConfig.Speedhacks.EECycleRate, 0);
|
||||||
EmuConfig.Speedhacks.EECycleSkip = 0;
|
EmuConfig.Speedhacks.EECycleSkip = 0;
|
||||||
|
|
||||||
// Async mix breaks games.
|
|
||||||
EmuConfig.SPU2.SynchMode = Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMManager::LogUnsafeSettingsToConsole(const std::string& messages)
|
void VMManager::LogUnsafeSettingsToConsole(const std::string& messages)
|
||||||
|
@ -2973,11 +2972,6 @@ void VMManager::WarnAboutUnsafeSettings()
|
||||||
append(ICON_FA_TACHOMETER_ALT,
|
append(ICON_FA_TACHOMETER_ALT,
|
||||||
TRANSLATE_SV("VMManager", "Cycle rate/skip is not at default, this may crash or make games run too slow."));
|
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)
|
if (EmuConfig.GS.UpscaleMultiplier < 1.0f)
|
||||||
append(ICON_FA_TV, TRANSLATE_SV("VMManager", "Upscale multiplier is below native, this will break rendering."));
|
append(ICON_FA_TV, TRANSLATE_SV("VMManager", "Upscale multiplier is below native, this will break rendering."));
|
||||||
if (EmuConfig.GS.HWMipmap != HWMipmapLevel::Automatic)
|
if (EmuConfig.GS.HWMipmap != HWMipmapLevel::Automatic)
|
||||||
|
|
|
@ -150,6 +150,9 @@ namespace VMManager
|
||||||
/// EmuConfig.EmulationSpeed without going through the usual config apply.
|
/// EmuConfig.EmulationSpeed without going through the usual config apply.
|
||||||
void UpdateTargetSpeed();
|
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.
|
/// Returns the current frame rate of the virtual machine.
|
||||||
float GetFrameRate();
|
float GetFrameRate();
|
||||||
|
|
||||||
|
|
|
@ -250,11 +250,7 @@
|
||||||
<ClCompile Include="SIO\Sio2.cpp" />
|
<ClCompile Include="SIO\Sio2.cpp" />
|
||||||
<ClCompile Include="SPU2\Debug.cpp" />
|
<ClCompile Include="SPU2\Debug.cpp" />
|
||||||
<ClCompile Include="SPU2\Dma.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\wavedump_wav.cpp" />
|
||||||
<ClCompile Include="SPU2\SndOut.cpp" />
|
|
||||||
<ClCompile Include="SPU2\RegTable.cpp" />
|
<ClCompile Include="SPU2\RegTable.cpp" />
|
||||||
<ClCompile Include="SPU2\spu2freeze.cpp" />
|
<ClCompile Include="SPU2\spu2freeze.cpp" />
|
||||||
<ClCompile Include="SPU2\spu2sys.cpp" />
|
<ClCompile Include="SPU2\spu2sys.cpp" />
|
||||||
|
@ -603,13 +599,10 @@
|
||||||
<ClInclude Include="SIO\SioTypes.h" />
|
<ClInclude Include="SIO\SioTypes.h" />
|
||||||
<ClInclude Include="SPU2\Debug.h" />
|
<ClInclude Include="SPU2\Debug.h" />
|
||||||
<ClInclude Include="SPU2\Dma.h" />
|
<ClInclude Include="SPU2\Dma.h" />
|
||||||
<ClInclude Include="SPU2\Global.h" />
|
|
||||||
<ClInclude Include="SPU2\interpolate_table.h" />
|
<ClInclude Include="SPU2\interpolate_table.h" />
|
||||||
<ClInclude Include="SPU2\SndOut.h" />
|
|
||||||
<ClInclude Include="SPU2\spdif.h" />
|
<ClInclude Include="SPU2\spdif.h" />
|
||||||
<ClInclude Include="SPU2\defs.h" />
|
<ClInclude Include="SPU2\defs.h" />
|
||||||
<ClInclude Include="SPU2\regs.h" />
|
<ClInclude Include="SPU2\regs.h" />
|
||||||
<ClInclude Include="SPU2\Mixer.h" />
|
|
||||||
<ClInclude Include="SPU2\spu2.h" />
|
<ClInclude Include="SPU2\spu2.h" />
|
||||||
<ClInclude Include="GS\Renderers\OpenGL\GLState.h" />
|
<ClInclude Include="GS\Renderers\OpenGL\GLState.h" />
|
||||||
<ClInclude Include="GS\GS.h" />
|
<ClInclude Include="GS\GS.h" />
|
||||||
|
|
|
@ -830,9 +830,6 @@
|
||||||
<ClCompile Include="SPU2\ADSR.cpp">
|
<ClCompile Include="SPU2\ADSR.cpp">
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
<Filter>System\Ps2\SPU2</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="SPU2\DplIIdecoder.cpp">
|
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="SPU2\ReadInput.cpp">
|
<ClCompile Include="SPU2\ReadInput.cpp">
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
<Filter>System\Ps2\SPU2</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -842,9 +839,6 @@
|
||||||
<ClCompile Include="SPU2\Reverb.cpp">
|
<ClCompile Include="SPU2\Reverb.cpp">
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
<Filter>System\Ps2\SPU2</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="SPU2\SndOut.cpp">
|
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="SPU2\Wavedump_wav.cpp">
|
<ClCompile Include="SPU2\Wavedump_wav.cpp">
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
<Filter>System\Ps2\SPU2</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -1115,9 +1109,6 @@
|
||||||
<ClCompile Include="GS\Renderers\DX11\D3D.cpp">
|
<ClCompile Include="GS\Renderers\DX11\D3D.cpp">
|
||||||
<Filter>System\Ps2\GS\Renderers\Direct3D11</Filter>
|
<Filter>System\Ps2\GS\Renderers\Direct3D11</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="SPU2\SndOut_Cubeb.cpp">
|
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="GS\Renderers\Vulkan\GSTextureVK.cpp">
|
<ClCompile Include="GS\Renderers\Vulkan\GSTextureVK.cpp">
|
||||||
<Filter>System\Ps2\GS\Renderers\Vulkan</Filter>
|
<Filter>System\Ps2\GS\Renderers\Vulkan</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -1244,9 +1235,6 @@
|
||||||
<ClCompile Include="SPU2\Dma.cpp">
|
<ClCompile Include="SPU2\Dma.cpp">
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
<Filter>System\Ps2\SPU2</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="SPU2\SndOut_XAudio2.cpp">
|
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="GSDumpReplayer.cpp">
|
<ClCompile Include="GSDumpReplayer.cpp">
|
||||||
<Filter>Tools</Filter>
|
<Filter>Tools</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -1730,24 +1718,15 @@
|
||||||
<ClInclude Include="SPU2\spu2.h">
|
<ClInclude Include="SPU2\spu2.h">
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
<Filter>System\Ps2\SPU2</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="SPU2\Mixer.h">
|
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="SPU2\interpolate_table.h">
|
<ClInclude Include="SPU2\interpolate_table.h">
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
<Filter>System\Ps2\SPU2</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="SPU2\defs.h">
|
<ClInclude Include="SPU2\defs.h">
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
<Filter>System\Ps2\SPU2</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="SPU2\Global.h">
|
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="SPU2\regs.h">
|
<ClInclude Include="SPU2\regs.h">
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
<Filter>System\Ps2\SPU2</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="SPU2\SndOut.h">
|
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="SPU2\spdif.h">
|
<ClInclude Include="SPU2\spdif.h">
|
||||||
<Filter>System\Ps2\SPU2</Filter>
|
<Filter>System\Ps2\SPU2</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|
Loading…
Reference in New Issue