AudioStream: Rewrite and integrate expander
This commit is contained in:
@@ -4987,11 +4987,6 @@ void FullscreenUI::DrawAudioSettingsPage()
|
||||
FSUI_CSTR("The audio backend determines how frames produced by the emulator are submitted to the host."), "Audio",
|
||||
"Backend", AudioStream::DEFAULT_BACKEND, &AudioStream::ParseBackendName, &AudioStream::GetBackendName,
|
||||
&AudioStream::GetBackendDisplayName, AudioBackend::Count);
|
||||
DrawEnumSetting(bsi, FSUI_CSTR("Expansion Mode"),
|
||||
FSUI_CSTR("Determines how audio is expanded from stereo to surround for supported games."), "Audio",
|
||||
"ExpansionMode", AudioStreamParameters::DEFAULT_EXPANSION_MODE, &AudioStream::ParseExpansionMode,
|
||||
&AudioStream::GetExpansionModeName, &AudioStream::GetExpansionModeDisplayName,
|
||||
AudioExpansionMode::Count);
|
||||
DrawEnumSetting(bsi, FSUI_CSTR("Stretch Mode"),
|
||||
FSUI_CSTR("Determines quality of audio when not running at 100% speed."), "Audio", "StretchMode",
|
||||
AudioStreamParameters::DEFAULT_STRETCH_MODE, &AudioStream::ParseStretchMode,
|
||||
@@ -7341,7 +7336,6 @@ TRANSLATE_NOOP("FullscreenUI", "Depth Clear Threshold");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Desktop Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Details");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Details unavailable for game not scanned in game list.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines how audio is expanded from stereo to surround for supported games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines how large the on-screen messages and monitor are.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines how much latency there is between the audio being picked up by the host API, and played through speakers.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines how much of the area typically not visible on a consumer TV set to crop/hide.");
|
||||
@@ -7419,7 +7413,6 @@ TRANSLATE_NOOP("FullscreenUI", "Exit And Save State");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Exit DuckStation");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Exit Without Saving");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Exits Big Picture mode, returning to the desktop interface.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Expansion Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "FMV Chroma Smoothing");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Failed to copy text to clipboard.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Failed to delete save state.");
|
||||
|
||||
@@ -19,7 +19,6 @@ class Error;
|
||||
class SettingsInterface;
|
||||
struct WindowInfo;
|
||||
enum class AudioBackend : u8;
|
||||
enum class AudioExpansionMode : u8;
|
||||
enum class AudioStretchMode : u8;
|
||||
enum class RenderAPI : u32;
|
||||
class AudioStream;
|
||||
|
||||
+1
-2
@@ -469,9 +469,8 @@ void SPU::Initialize()
|
||||
|
||||
void SPU::CreateOutputStream()
|
||||
{
|
||||
INFO_LOG("Creating '{}' audio stream, sample rate = {}, expansion = {}, buffer = {}, latency = {}{}, stretching = {}",
|
||||
INFO_LOG("Creating '{}' audio stream, sample rate = {}, buffer = {}, latency = {}{}, stretching = {}",
|
||||
AudioStream::GetBackendName(g_settings.audio_backend), static_cast<u32>(SAMPLE_RATE),
|
||||
AudioStream::GetExpansionModeName(g_settings.audio_stream_parameters.expansion_mode),
|
||||
g_settings.audio_stream_parameters.buffer_ms, g_settings.audio_stream_parameters.output_latency_ms,
|
||||
g_settings.audio_stream_parameters.output_latency_minimal ? " (or minimal)" : "",
|
||||
AudioStream::GetStretchModeName(g_settings.audio_stream_parameters.stretch_mode));
|
||||
|
||||
@@ -24,12 +24,6 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
|
||||
for (u32 i = 0; i < static_cast<u32>(AudioBackend::Count); i++)
|
||||
m_ui.audioBackend->addItem(QString::fromUtf8(AudioStream::GetBackendDisplayName(static_cast<AudioBackend>(i))));
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(AudioExpansionMode::Count); i++)
|
||||
{
|
||||
m_ui.expansionMode->addItem(
|
||||
QString::fromUtf8(AudioStream::GetExpansionModeDisplayName(static_cast<AudioExpansionMode>(i))));
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(AudioStretchMode::Count); i++)
|
||||
{
|
||||
m_ui.stretchMode->addItem(
|
||||
@@ -39,9 +33,6 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.audioBackend, "Audio", "Backend",
|
||||
&AudioStream::ParseBackendName, &AudioStream::GetBackendName,
|
||||
AudioStream::DEFAULT_BACKEND);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.expansionMode, "Audio", "ExpansionMode",
|
||||
&AudioStream::ParseExpansionMode, &AudioStream::GetExpansionModeName,
|
||||
AudioStreamParameters::DEFAULT_EXPANSION_MODE);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.stretchMode, "Audio", "StretchMode",
|
||||
&AudioStream::ParseStretchMode, &AudioStream::GetStretchModeName,
|
||||
AudioStreamParameters::DEFAULT_STRETCH_MODE);
|
||||
@@ -53,11 +44,8 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
|
||||
AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MINIMAL);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.muteCDAudio, "CDROM", "MuteCDAudio", 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.stretchMode, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::onStretchModeChanged);
|
||||
connect(m_ui.stretchSettings, &QToolButton::clicked, this, &AudioSettingsWidget::onStretchSettingsClicked);
|
||||
onExpansionModeChanged();
|
||||
onStretchModeChanged();
|
||||
updateDriverNames();
|
||||
|
||||
@@ -109,11 +97,6 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
|
||||
dialog->registerWidgetHelp(m_ui.muteCDAudio, tr("Mute CD Audio"), tr("Unchecked"),
|
||||
tr("Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable "
|
||||
"background music in some games."));
|
||||
dialog->registerWidgetHelp(m_ui.expansionMode, tr("Expansion Mode"), tr("Disabled (Stereo)"),
|
||||
tr("Determines how audio is expanded from stereo to surround for supported games. This "
|
||||
"includes games that support Dolby Pro Logic/Pro Logic II."));
|
||||
dialog->registerWidgetHelp(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.stretchMode, tr("Stretch Mode"), tr("Time Stretching"),
|
||||
tr("When running outside of 100% speed, adjusts the tempo on audio instead of dropping frames. Produces "
|
||||
@@ -131,34 +114,6 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
|
||||
|
||||
AudioSettingsWidget::~AudioSettingsWidget() = default;
|
||||
|
||||
AudioExpansionMode AudioSettingsWidget::getEffectiveExpansionMode() const
|
||||
{
|
||||
return AudioStream::ParseExpansionMode(
|
||||
m_dialog
|
||||
->getEffectiveStringValue("Audio", "ExpansionMode",
|
||||
AudioStream::GetExpansionModeName(AudioStreamParameters::DEFAULT_EXPANSION_MODE))
|
||||
.c_str())
|
||||
.value_or(AudioStreamParameters::DEFAULT_EXPANSION_MODE);
|
||||
}
|
||||
|
||||
u32 AudioSettingsWidget::getEffectiveExpansionBlockSize() const
|
||||
{
|
||||
const AudioExpansionMode expansion_mode = getEffectiveExpansionMode();
|
||||
if (expansion_mode == AudioExpansionMode::Disabled)
|
||||
return 0;
|
||||
|
||||
const u32 config_block_size =
|
||||
m_dialog->getEffectiveIntValue("Audio", "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::onExpansionModeChanged()
|
||||
{
|
||||
const AudioExpansionMode expansion_mode = getEffectiveExpansionMode();
|
||||
m_ui.expansionSettings->setEnabled(expansion_mode != AudioExpansionMode::Disabled);
|
||||
updateLatencyLabel();
|
||||
}
|
||||
|
||||
void AudioSettingsWidget::onStretchModeChanged()
|
||||
{
|
||||
const AudioStretchMode stretch_mode =
|
||||
@@ -253,7 +208,6 @@ void AudioSettingsWidget::updateDeviceNames()
|
||||
|
||||
void AudioSettingsWidget::updateLatencyLabel()
|
||||
{
|
||||
const u32 expand_buffer_ms = AudioStream::GetMSForBufferSize(SPU::SAMPLE_RATE, getEffectiveExpansionBlockSize());
|
||||
const u32 config_buffer_ms =
|
||||
m_dialog->getEffectiveIntValue("Audio", "BufferMS", AudioStreamParameters::DEFAULT_BUFFER_MS);
|
||||
const u32 config_output_latency_ms =
|
||||
@@ -270,34 +224,14 @@ void AudioSettingsWidget::updateLatencyLabel()
|
||||
config_output_latency_ms;
|
||||
if (output_latency_ms > 0)
|
||||
{
|
||||
if (expand_buffer_ms > 0)
|
||||
{
|
||||
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms buffer + %3 ms expand + %4 ms output)")
|
||||
.arg(config_buffer_ms + expand_buffer_ms + output_latency_ms)
|
||||
.arg(config_buffer_ms)
|
||||
.arg(expand_buffer_ms)
|
||||
.arg(output_latency_ms));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms buffer + %3 ms output)")
|
||||
.arg(config_buffer_ms + output_latency_ms)
|
||||
.arg(config_buffer_ms)
|
||||
.arg(output_latency_ms));
|
||||
}
|
||||
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));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (expand_buffer_ms > 0)
|
||||
{
|
||||
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms expand, minimum output latency unknown)")
|
||||
.arg(expand_buffer_ms + config_buffer_ms)
|
||||
.arg(expand_buffer_ms));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (minimum output latency unknown)").arg(config_buffer_ms));
|
||||
}
|
||||
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (minimum output latency unknown)").arg(config_buffer_ms));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,95 +281,6 @@ void AudioSettingsWidget::onOutputMutedChanged(int new_state)
|
||||
g_emu_thread->setAudioOutputMuted(muted);
|
||||
}
|
||||
|
||||
void AudioSettingsWidget::onExpansionSettingsClicked()
|
||||
{
|
||||
QDialog dlg(QtUtils::GetRootWidget(this));
|
||||
Ui::AudioExpansionSettingsDialog dlgui;
|
||||
dlgui.setupUi(&dlg);
|
||||
dlgui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("volume-up-line")).pixmap(32, 32));
|
||||
|
||||
SettingsInterface* sif = m_dialog->getSettingsInterface();
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.blockSize, "Audio", "ExpandBlockSize",
|
||||
AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE, 0);
|
||||
QtUtils::BindLabelToSlider(dlgui.blockSize, dlgui.blockSizeLabel);
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(sif, dlgui.circularWrap, "Audio", "ExpandCircularWrap",
|
||||
AudioStreamParameters::DEFAULT_EXPAND_CIRCULAR_WRAP);
|
||||
QtUtils::BindLabelToSlider(dlgui.circularWrap, dlgui.circularWrapLabel);
|
||||
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.shift, "Audio", "ExpandShift", 100.0f,
|
||||
AudioStreamParameters::DEFAULT_EXPAND_SHIFT);
|
||||
QtUtils::BindLabelToSlider(dlgui.shift, dlgui.shiftLabel, 100.0f);
|
||||
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.depth, "Audio", "ExpandDepth", 10.0f,
|
||||
AudioStreamParameters::DEFAULT_EXPAND_DEPTH);
|
||||
QtUtils::BindLabelToSlider(dlgui.depth, dlgui.depthLabel, 10.0f);
|
||||
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.focus, "Audio", "ExpandFocus", 100.0f,
|
||||
AudioStreamParameters::DEFAULT_EXPAND_FOCUS);
|
||||
QtUtils::BindLabelToSlider(dlgui.focus, dlgui.focusLabel, 100.0f);
|
||||
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.centerImage, "Audio", "ExpandCenterImage", 100.0f,
|
||||
AudioStreamParameters::DEFAULT_EXPAND_CENTER_IMAGE);
|
||||
QtUtils::BindLabelToSlider(dlgui.centerImage, dlgui.centerImageLabel, 100.0f);
|
||||
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.frontSeparation, "Audio", "ExpandFrontSeparation",
|
||||
10.0f, AudioStreamParameters::DEFAULT_EXPAND_FRONT_SEPARATION);
|
||||
QtUtils::BindLabelToSlider(dlgui.frontSeparation, dlgui.frontSeparationLabel, 10.0f);
|
||||
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.rearSeparation, "Audio", "ExpandRearSeparation", 10.0f,
|
||||
AudioStreamParameters::DEFAULT_EXPAND_REAR_SEPARATION);
|
||||
QtUtils::BindLabelToSlider(dlgui.rearSeparation, dlgui.rearSeparationLabel, 10.0f);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.lowCutoff, "Audio", "ExpandLowCutoff",
|
||||
AudioStreamParameters::DEFAULT_EXPAND_LOW_CUTOFF);
|
||||
QtUtils::BindLabelToSlider(dlgui.lowCutoff, dlgui.lowCutoffLabel);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.highCutoff, "Audio", "ExpandHighCutoff",
|
||||
AudioStreamParameters::DEFAULT_EXPAND_HIGH_CUTOFF);
|
||||
QtUtils::BindLabelToSlider(dlgui.highCutoff, dlgui.highCutoffLabel);
|
||||
|
||||
connect(dlgui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, &dlg, &QDialog::accept);
|
||||
connect(dlgui.buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [this, &dlg]() {
|
||||
m_dialog->setIntSettingValue("Audio", "ExpandBlockSize",
|
||||
m_dialog->isPerGameSettings() ?
|
||||
std::nullopt :
|
||||
std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE));
|
||||
|
||||
m_dialog->setFloatSettingValue("Audio", "ExpandCircularWrap",
|
||||
m_dialog->isPerGameSettings() ?
|
||||
std::nullopt :
|
||||
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_CIRCULAR_WRAP));
|
||||
m_dialog->setFloatSettingValue(
|
||||
"Audio", "ExpandShift",
|
||||
m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_SHIFT));
|
||||
m_dialog->setFloatSettingValue(
|
||||
"Audio", "ExpandDepth",
|
||||
m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_DEPTH));
|
||||
m_dialog->setFloatSettingValue(
|
||||
"Audio", "ExpandFocus",
|
||||
m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_FOCUS));
|
||||
m_dialog->setFloatSettingValue("Audio", "ExpandCenterImage",
|
||||
m_dialog->isPerGameSettings() ?
|
||||
std::nullopt :
|
||||
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_CENTER_IMAGE));
|
||||
m_dialog->setFloatSettingValue("Audio", "ExpandFrontSeparation",
|
||||
m_dialog->isPerGameSettings() ?
|
||||
std::nullopt :
|
||||
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_FRONT_SEPARATION));
|
||||
m_dialog->setFloatSettingValue("Audio", "ExpandRearSeparation",
|
||||
m_dialog->isPerGameSettings() ?
|
||||
std::nullopt :
|
||||
std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_REAR_SEPARATION));
|
||||
m_dialog->setIntSettingValue("Audio", "ExpandLowCutoff",
|
||||
m_dialog->isPerGameSettings() ?
|
||||
std::nullopt :
|
||||
std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_LOW_CUTOFF));
|
||||
m_dialog->setIntSettingValue("Audio", "ExpandHighCutoff",
|
||||
m_dialog->isPerGameSettings() ?
|
||||
std::nullopt :
|
||||
std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_HIGH_CUTOFF));
|
||||
|
||||
dlg.done(0);
|
||||
|
||||
QMetaObject::invokeMethod(this, &AudioSettingsWidget::onExpansionSettingsClicked, Qt::QueuedConnection);
|
||||
});
|
||||
|
||||
dlg.exec();
|
||||
updateLatencyLabel();
|
||||
}
|
||||
|
||||
void AudioSettingsWidget::onStretchSettingsClicked()
|
||||
{
|
||||
QDialog dlg(QtUtils::GetRootWidget(this));
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
enum class AudioBackend : u8;
|
||||
enum class AudioExpansionMode : u8;
|
||||
|
||||
class SettingsWindow;
|
||||
|
||||
@@ -23,7 +22,6 @@ public:
|
||||
~AudioSettingsWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onExpansionModeChanged();
|
||||
void onStretchModeChanged();
|
||||
|
||||
void updateDriverNames();
|
||||
@@ -35,13 +33,10 @@ private Q_SLOTS:
|
||||
void onFastForwardVolumeChanged(int new_value);
|
||||
void onOutputMutedChanged(int new_state);
|
||||
|
||||
void onExpansionSettingsClicked();
|
||||
void onStretchSettingsClicked();
|
||||
|
||||
private:
|
||||
AudioBackend getEffectiveBackend() const;
|
||||
AudioExpansionMode getEffectiveExpansionMode() const;
|
||||
u32 getEffectiveExpansionBlockSize() const;
|
||||
void resetVolume(bool fast_forward);
|
||||
|
||||
Ui::AudioSettingsWidget m_ui;
|
||||
|
||||
@@ -29,51 +29,17 @@
|
||||
<string>Configuration</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Output Latency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="outputDevice"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="audioBackend"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Driver:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QLabel" name="bufferingLabel">
|
||||
<property name="text">
|
||||
<string>Maximum latency: 0 frames (0.00ms)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Backend:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Stretch Mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Output Device:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="5" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QSlider" name="outputLatencyMS">
|
||||
@@ -113,62 +79,21 @@
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="driver"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Stretch Mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Buffer Size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Expansion Mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" 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_5" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QComboBox" name="expansionMode"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="expansionSettings">
|
||||
<property name="toolTip">
|
||||
<string>Expansion Settings</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="settings-3-line"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QComboBox" name="stretchMode"/>
|
||||
</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="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QSlider" name="bufferMS">
|
||||
@@ -207,6 +132,57 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QComboBox" name="stretchMode"/>
|
||||
</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="2" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Output Device:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="audioBackend"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Driver:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QLabel" name="bufferingLabel">
|
||||
<property name="text">
|
||||
<string>Maximum latency: 0 frames (0.00ms)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Backend:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -365,7 +341,7 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
<include location="resources/duckstation-qt.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -184,7 +184,6 @@ if(NOT ANDROID)
|
||||
)
|
||||
target_link_libraries(util PUBLIC
|
||||
cubeb
|
||||
freesurround
|
||||
SDL2::SDL2
|
||||
)
|
||||
# FFmpeg loaded dynamically on demand.
|
||||
|
||||
+99
-281
@@ -1,5 +1,5 @@
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "audio_stream.h"
|
||||
#include "host.h"
|
||||
@@ -15,10 +15,6 @@
|
||||
#include "soundtouch/SoundTouch.h"
|
||||
#include "soundtouch/SoundTouchDLL.h"
|
||||
|
||||
#ifndef __ANDROID__
|
||||
#include "freesurround_decoder.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
@@ -28,16 +24,6 @@ Log_SetChannel(AudioStream);
|
||||
|
||||
static constexpr bool LOG_TIMESTRETCH_STATS = false;
|
||||
|
||||
static constexpr const std::array<std::pair<u8, u8>, static_cast<size_t>(AudioExpansionMode::Count)>
|
||||
s_expansion_channel_count = {{
|
||||
{u8(2), u8(2)}, // Disabled
|
||||
{u8(3), u8(3)}, // StereoLFE
|
||||
{u8(5), u8(4)}, // Quadraphonic
|
||||
{u8(5), u8(5)}, // QuadraphonicLFE
|
||||
{u8(6), u8(6)}, // Surround51
|
||||
{u8(8), u8(8)}, // Surround71
|
||||
}};
|
||||
|
||||
AudioStream::DeviceInfo::DeviceInfo(std::string name_, std::string display_name_, u32 minimum_latency_)
|
||||
: name(std::move(name_)), display_name(std::move(display_name_)), minimum_latency_frames(minimum_latency_)
|
||||
{
|
||||
@@ -45,10 +31,70 @@ AudioStream::DeviceInfo::DeviceInfo(std::string name_, std::string display_name_
|
||||
|
||||
AudioStream::DeviceInfo::~DeviceInfo() = default;
|
||||
|
||||
void AudioStreamParameters::Load(SettingsInterface& si, const char* section)
|
||||
{
|
||||
stretch_mode =
|
||||
AudioStream::ParseStretchMode(
|
||||
si.GetStringValue(section, "StretchMode", AudioStream::GetStretchModeName(DEFAULT_STRETCH_MODE)).c_str())
|
||||
.value_or(DEFAULT_STRETCH_MODE);
|
||||
output_latency_ms = static_cast<u16>(std::min<u32>(
|
||||
si.GetUIntValue(section, "OutputLatencyMS", DEFAULT_OUTPUT_LATENCY_MS), std::numeric_limits<u16>::max()));
|
||||
output_latency_minimal = si.GetBoolValue(section, "OutputLatencyMinimal", DEFAULT_OUTPUT_LATENCY_MINIMAL);
|
||||
buffer_ms = static_cast<u16>(
|
||||
std::min<u32>(si.GetUIntValue(section, "BufferMS", DEFAULT_BUFFER_MS), std::numeric_limits<u16>::max()));
|
||||
|
||||
stretch_sequence_length_ms =
|
||||
static_cast<u16>(std::min<u32>(si.GetUIntValue(section, "StretchSequenceLengthMS", DEFAULT_STRETCH_SEQUENCE_LENGTH),
|
||||
std::numeric_limits<u16>::max()));
|
||||
stretch_seekwindow_ms = static_cast<u16>(std::min<u32>(
|
||||
si.GetUIntValue(section, "StretchSeekWindowMS", DEFAULT_STRETCH_SEEKWINDOW), std::numeric_limits<u16>::max()));
|
||||
stretch_overlap_ms = static_cast<u16>(std::min<u32>(
|
||||
si.GetUIntValue(section, "StretchOverlapMS", DEFAULT_STRETCH_OVERLAP), std::numeric_limits<u16>::max()));
|
||||
stretch_use_quickseek = si.GetBoolValue(section, "StretchUseQuickSeek", DEFAULT_STRETCH_USE_QUICKSEEK);
|
||||
stretch_use_aa_filter = si.GetBoolValue(section, "StretchUseAAFilter", DEFAULT_STRETCH_USE_AA_FILTER);
|
||||
}
|
||||
|
||||
void AudioStreamParameters::Save(SettingsInterface& si, const char* section) const
|
||||
{
|
||||
si.SetStringValue(section, "StretchMode", AudioStream::GetStretchModeName(stretch_mode));
|
||||
si.SetUIntValue(section, "BufferMS", buffer_ms);
|
||||
si.SetUIntValue(section, "OutputLatencyMS", output_latency_ms);
|
||||
si.SetBoolValue(section, "OutputLatencyMinimal", output_latency_minimal);
|
||||
|
||||
si.SetUIntValue(section, "StretchSequenceLengthMS", stretch_sequence_length_ms);
|
||||
si.SetUIntValue(section, "StretchSeekWindowMS", stretch_seekwindow_ms);
|
||||
si.SetUIntValue(section, "StretchOverlapMS", stretch_overlap_ms);
|
||||
si.SetBoolValue(section, "StretchUseQuickSeek", stretch_use_quickseek);
|
||||
si.SetBoolValue(section, "StretchUseAAFilter", stretch_use_aa_filter);
|
||||
}
|
||||
|
||||
void AudioStreamParameters::Clear(SettingsInterface& si, const char* section)
|
||||
{
|
||||
si.DeleteValue(section, "StretchMode");
|
||||
si.DeleteValue(section, "ExpansionMode");
|
||||
si.DeleteValue(section, "BufferMS");
|
||||
si.DeleteValue(section, "OutputLatencyMS");
|
||||
si.DeleteValue(section, "OutputLatencyMinimal");
|
||||
|
||||
si.DeleteValue(section, "StretchSequenceLengthMS");
|
||||
si.DeleteValue(section, "StretchSeekWindowMS");
|
||||
si.DeleteValue(section, "StretchOverlapMS");
|
||||
si.DeleteValue(section, "StretchUseQuickSeek");
|
||||
si.DeleteValue(section, "StretchUseAAFilter");
|
||||
}
|
||||
|
||||
bool AudioStreamParameters::operator!=(const AudioStreamParameters& rhs) const
|
||||
{
|
||||
return (std::memcmp(this, &rhs, sizeof(*this)) != 0);
|
||||
}
|
||||
|
||||
bool AudioStreamParameters::operator==(const AudioStreamParameters& rhs) const
|
||||
{
|
||||
return (std::memcmp(this, &rhs, sizeof(*this)) == 0);
|
||||
}
|
||||
|
||||
AudioStream::AudioStream(u32 sample_rate, const AudioStreamParameters& parameters)
|
||||
: m_sample_rate(sample_rate), m_parameters(parameters),
|
||||
m_internal_channels(s_expansion_channel_count[static_cast<size_t>(parameters.expansion_mode)].first),
|
||||
m_output_channels(s_expansion_channel_count[static_cast<size_t>(parameters.expansion_mode)].second)
|
||||
: m_sample_rate(sample_rate), m_parameters(parameters)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -62,12 +108,11 @@ std::unique_ptr<AudioStream> AudioStream::CreateNullStream(u32 sample_rate, u32
|
||||
{
|
||||
// no point stretching with no output
|
||||
AudioStreamParameters params;
|
||||
params.expansion_mode = AudioExpansionMode::Disabled;
|
||||
params.stretch_mode = AudioStretchMode::Off;
|
||||
params.buffer_ms = static_cast<u16>(buffer_ms);
|
||||
|
||||
std::unique_ptr<AudioStream> stream(new AudioStream(sample_rate, params));
|
||||
stream->BaseInitialize(&StereoSampleReaderImpl);
|
||||
stream->BaseInitialize();
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -199,38 +244,6 @@ const char* AudioStream::GetBackendDisplayName(AudioBackend backend)
|
||||
return Host::TranslateToCString("AudioStream", s_backend_display_names[static_cast<int>(backend)]);
|
||||
}
|
||||
|
||||
static constexpr const std::array s_expansion_mode_names = {
|
||||
"Disabled", "StereoLFE", "Quadraphonic", "QuadraphonicLFE", "Surround51", "Surround71",
|
||||
};
|
||||
static constexpr const std::array s_expansion_mode_display_names = {
|
||||
TRANSLATE_NOOP("AudioStream", "Disabled (Stereo)"), TRANSLATE_NOOP("AudioStream", "Stereo with LFE"),
|
||||
TRANSLATE_NOOP("AudioStream", "Quadraphonic"), TRANSLATE_NOOP("AudioStream", "Quadraphonic with LFE"),
|
||||
TRANSLATE_NOOP("AudioStream", "5.1 Surround"), TRANSLATE_NOOP("AudioStream", "7.1 Surround"),
|
||||
};
|
||||
|
||||
const char* AudioStream::GetExpansionModeName(AudioExpansionMode mode)
|
||||
{
|
||||
return (static_cast<u32>(mode) < s_expansion_mode_names.size()) ? s_expansion_mode_names[static_cast<u32>(mode)] : "";
|
||||
}
|
||||
|
||||
const char* AudioStream::GetExpansionModeDisplayName(AudioExpansionMode mode)
|
||||
{
|
||||
return (static_cast<u32>(mode) < s_expansion_mode_display_names.size()) ?
|
||||
Host::TranslateToCString("AudioStream", s_expansion_mode_display_names[static_cast<u32>(mode)]) :
|
||||
"";
|
||||
}
|
||||
|
||||
std::optional<AudioExpansionMode> AudioStream::ParseExpansionMode(const char* name)
|
||||
{
|
||||
for (u8 i = 0; i < static_cast<u8>(AudioExpansionMode::Count); i++)
|
||||
{
|
||||
if (std::strcmp(name, s_expansion_mode_names[i]) == 0)
|
||||
return static_cast<AudioExpansionMode>(i);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static constexpr const std::array s_stretch_mode_names = {
|
||||
"None",
|
||||
"Resample",
|
||||
@@ -316,7 +329,7 @@ void AudioStream::ReadFrames(SampleType* samples, u32 num_frames)
|
||||
// towards the end of the buffer
|
||||
if (end > 0)
|
||||
{
|
||||
m_sample_reader(samples, &m_buffer[rpos * m_internal_channels], end);
|
||||
std::memcpy(samples, &m_buffer[rpos * NUM_CHANNELS], end * NUM_CHANNELS * sizeof(SampleType));
|
||||
rpos += end;
|
||||
rpos = (rpos == m_buffer_size) ? 0 : rpos;
|
||||
}
|
||||
@@ -325,7 +338,7 @@ void AudioStream::ReadFrames(SampleType* samples, u32 num_frames)
|
||||
const u32 start = frames_to_read - end;
|
||||
if (start > 0)
|
||||
{
|
||||
m_sample_reader(&samples[end * m_output_channels], &m_buffer[0], start);
|
||||
std::memcpy(&samples[end * NUM_CHANNELS], &m_buffer[0], start * NUM_CHANNELS * sizeof(SampleType));
|
||||
rpos = start;
|
||||
}
|
||||
|
||||
@@ -342,19 +355,19 @@ void AudioStream::ReadFrames(SampleType* samples, u32 num_frames)
|
||||
static_cast<u32>(65536.0f * (static_cast<float>(frames_to_read) / static_cast<float>(num_frames)));
|
||||
|
||||
SampleType* resample_ptr =
|
||||
static_cast<SampleType*>(alloca(frames_to_read * m_output_channels * sizeof(SampleType)));
|
||||
std::memcpy(resample_ptr, samples, frames_to_read * m_output_channels * sizeof(SampleType));
|
||||
static_cast<SampleType*>(alloca(frames_to_read * NUM_CHANNELS * sizeof(SampleType)));
|
||||
std::memcpy(resample_ptr, samples, frames_to_read * NUM_CHANNELS * sizeof(SampleType));
|
||||
|
||||
SampleType* out_ptr = samples;
|
||||
const u32 copy_stride = sizeof(SampleType) * m_output_channels;
|
||||
const u32 copy_stride = sizeof(SampleType) * NUM_CHANNELS;
|
||||
u32 resample_subpos = 0;
|
||||
for (u32 i = 0; i < num_frames; i++)
|
||||
{
|
||||
std::memcpy(out_ptr, resample_ptr, copy_stride);
|
||||
out_ptr += m_output_channels;
|
||||
out_ptr += NUM_CHANNELS;
|
||||
|
||||
resample_subpos += increment;
|
||||
resample_ptr += (resample_subpos >> 16) * m_output_channels;
|
||||
resample_ptr += (resample_subpos >> 16) * NUM_CHANNELS;
|
||||
resample_subpos %= 65536u;
|
||||
}
|
||||
|
||||
@@ -363,13 +376,13 @@ void AudioStream::ReadFrames(SampleType* samples, u32 num_frames)
|
||||
else
|
||||
{
|
||||
// no data, fall back to silence
|
||||
std::memset(samples + (frames_to_read * m_output_channels), 0, silence_frames * m_output_channels * sizeof(s16));
|
||||
std::memset(samples + (frames_to_read * NUM_CHANNELS), 0, silence_frames * NUM_CHANNELS * sizeof(s16));
|
||||
}
|
||||
}
|
||||
|
||||
if (m_volume != 100)
|
||||
{
|
||||
u32 num_samples = num_frames * m_output_channels;
|
||||
u32 num_samples = num_frames * NUM_CHANNELS;
|
||||
|
||||
const u32 aligned_samples = Common::AlignDownPow2(num_samples, 8);
|
||||
num_samples -= aligned_samples;
|
||||
@@ -403,11 +416,6 @@ void AudioStream::ReadFrames(SampleType* samples, u32 num_frames)
|
||||
}
|
||||
}
|
||||
|
||||
void AudioStream::StereoSampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames)
|
||||
{
|
||||
std::memcpy(dest, src, num_frames * 2 * sizeof(SampleType));
|
||||
}
|
||||
|
||||
void AudioStream::InternalWriteFrames(s16* data, u32 num_frames)
|
||||
{
|
||||
const u32 free = m_buffer_size - GetBufferedFramesRelaxed();
|
||||
@@ -434,28 +442,25 @@ void AudioStream::InternalWriteFrames(s16* data, u32 num_frames)
|
||||
const u32 start = num_frames - end;
|
||||
|
||||
// start is zero when this chunk reaches exactly the end
|
||||
std::memcpy(&m_buffer[wpos * m_internal_channels], data, end * m_internal_channels * sizeof(SampleType));
|
||||
std::memcpy(&m_buffer[wpos * NUM_CHANNELS], data, end * NUM_CHANNELS * sizeof(SampleType));
|
||||
if (start > 0)
|
||||
std::memcpy(&m_buffer[0], data + end * m_internal_channels, start * m_internal_channels * sizeof(SampleType));
|
||||
std::memcpy(&m_buffer[0], data + end * NUM_CHANNELS, start * NUM_CHANNELS * sizeof(SampleType));
|
||||
|
||||
wpos = start;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no split
|
||||
std::memcpy(&m_buffer[wpos * m_internal_channels], data, num_frames * m_internal_channels * sizeof(SampleType));
|
||||
std::memcpy(&m_buffer[wpos * NUM_CHANNELS], data, num_frames * NUM_CHANNELS * sizeof(SampleType));
|
||||
wpos += num_frames;
|
||||
}
|
||||
|
||||
m_wpos.store(wpos, std::memory_order_release);
|
||||
}
|
||||
|
||||
void AudioStream::BaseInitialize(SampleReader sample_reader)
|
||||
void AudioStream::BaseInitialize()
|
||||
{
|
||||
m_sample_reader = sample_reader;
|
||||
|
||||
AllocateBuffer();
|
||||
ExpandAllocate();
|
||||
StretchAllocate();
|
||||
}
|
||||
|
||||
@@ -469,22 +474,16 @@ void AudioStream::AllocateBuffer()
|
||||
m_buffer_size = GetAlignedBufferSize(((m_parameters.buffer_ms * multiplier) * m_sample_rate) / 1000);
|
||||
m_target_buffer_size = GetAlignedBufferSize((m_sample_rate * m_parameters.buffer_ms) / 1000u);
|
||||
|
||||
m_buffer = std::make_unique<s16[]>(m_buffer_size * m_internal_channels);
|
||||
m_staging_buffer = std::make_unique<s16[]>(CHUNK_SIZE * m_internal_channels);
|
||||
m_float_buffer = std::make_unique<float[]>(CHUNK_SIZE * m_internal_channels);
|
||||
m_buffer = Common::make_unique_aligned_for_overwrite<s16[]>(VECTOR_ALIGNMENT, m_buffer_size * NUM_CHANNELS);
|
||||
m_staging_buffer = Common::make_unique_aligned_for_overwrite<s16[]>(VECTOR_ALIGNMENT, CHUNK_SIZE * NUM_CHANNELS);
|
||||
m_float_buffer = Common::make_unique_aligned_for_overwrite<float[]>(VECTOR_ALIGNMENT, CHUNK_SIZE * NUM_CHANNELS);
|
||||
|
||||
if (IsExpansionEnabled())
|
||||
m_expand_buffer = std::make_unique<float[]>(m_parameters.expand_block_size * NUM_INPUT_CHANNELS);
|
||||
|
||||
DEV_LOG(
|
||||
"Allocated buffer of {} frames for buffer of {} ms [expansion {} (block size {}), stretch {}, target size {}].",
|
||||
m_buffer_size, m_parameters.buffer_ms, GetExpansionModeName(m_parameters.expansion_mode),
|
||||
m_parameters.expand_block_size, GetStretchModeName(m_parameters.stretch_mode), m_target_buffer_size);
|
||||
DEV_LOG("Allocated buffer of {} frames for buffer of {} ms [stretch {}, target size {}].", m_buffer_size,
|
||||
m_parameters.buffer_ms, GetStretchModeName(m_parameters.stretch_mode), m_target_buffer_size);
|
||||
}
|
||||
|
||||
void AudioStream::DestroyBuffer()
|
||||
{
|
||||
m_expand_buffer.reset();
|
||||
m_staging_buffer.reset();
|
||||
m_float_buffer.reset();
|
||||
m_buffer.reset();
|
||||
@@ -495,15 +494,6 @@ void AudioStream::DestroyBuffer()
|
||||
|
||||
void AudioStream::EmptyBuffer()
|
||||
{
|
||||
#ifndef __ANDROID__
|
||||
if (IsExpansionEnabled())
|
||||
{
|
||||
m_expander->Flush();
|
||||
m_expand_output_buffer = nullptr;
|
||||
m_expand_buffer_pos = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (IsStretchEnabled())
|
||||
{
|
||||
soundtouch_clear(m_soundtouch);
|
||||
@@ -559,7 +549,7 @@ void AudioStream::BeginWrite(SampleType** buffer_ptr, u32* num_frames)
|
||||
{
|
||||
// TODO: Write directly to buffer when not using stretching.
|
||||
*buffer_ptr = &m_staging_buffer[m_staging_buffer_pos];
|
||||
*num_frames = CHUNK_SIZE - (m_staging_buffer_pos / NUM_INPUT_CHANNELS);
|
||||
*num_frames = CHUNK_SIZE - (m_staging_buffer_pos / NUM_CHANNELS);
|
||||
}
|
||||
|
||||
static void S16ChunkToFloat(const s16* src, float* dst, u32 num_samples)
|
||||
@@ -569,7 +559,7 @@ static void S16ChunkToFloat(const s16* src, float* dst, u32 num_samples)
|
||||
const u32 iterations = (num_samples + 7) / 8;
|
||||
for (u32 i = 0; i < iterations; i++)
|
||||
{
|
||||
const GSVector4i sv = GSVector4i::load<false>(src);
|
||||
const GSVector4i sv = GSVector4i::load<true>(src);
|
||||
src += 8;
|
||||
|
||||
GSVector4i iv1 = sv.upl16(sv); // [0, 0, 1, 1, 2, 2, 3, 3]
|
||||
@@ -581,8 +571,8 @@ static void S16ChunkToFloat(const s16* src, float* dst, u32 num_samples)
|
||||
fv1 = fv1 * S16_TO_FLOAT_V;
|
||||
fv2 = fv2 * S16_TO_FLOAT_V;
|
||||
|
||||
GSVector4::store<false>(dst + 0, fv1);
|
||||
GSVector4::store<false>(dst + 4, fv2);
|
||||
GSVector4::store<true>(dst + 0, fv1);
|
||||
GSVector4::store<true>(dst + 4, fv2);
|
||||
dst += 8;
|
||||
}
|
||||
}
|
||||
@@ -594,8 +584,8 @@ static void FloatChunkToS16(s16* dst, const float* src, u32 num_samples)
|
||||
const u32 iterations = (num_samples + 7) / 8;
|
||||
for (u32 i = 0; i < iterations; i++)
|
||||
{
|
||||
GSVector4 fv1 = GSVector4::load<false>(src + 0);
|
||||
GSVector4 fv2 = GSVector4::load<false>(src + 4);
|
||||
GSVector4 fv1 = GSVector4::load<true>(src + 0);
|
||||
GSVector4 fv2 = GSVector4::load<true>(src + 4);
|
||||
src += 8;
|
||||
|
||||
fv1 = fv1 * FLOAT_TO_S16_V;
|
||||
@@ -604,91 +594,32 @@ static void FloatChunkToS16(s16* dst, const float* src, u32 num_samples)
|
||||
GSVector4i iv2 = GSVector4i(fv2);
|
||||
|
||||
const GSVector4i iv = iv1.ps32(iv2);
|
||||
GSVector4i::store<false>(dst, iv);
|
||||
GSVector4i::store<true>(dst, iv);
|
||||
dst += 8;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioStream::ExpandAllocate()
|
||||
{
|
||||
DebugAssert(!m_expander);
|
||||
if (m_parameters.expansion_mode == AudioExpansionMode::Disabled)
|
||||
return;
|
||||
|
||||
#ifndef __ANDROID__
|
||||
static constexpr std::array<std::pair<FreeSurroundDecoder::ChannelSetup, bool>,
|
||||
static_cast<size_t>(AudioExpansionMode::Count)>
|
||||
channel_setup_mapping = {{
|
||||
{FreeSurroundDecoder::ChannelSetup::Stereo, false}, // Disabled
|
||||
{FreeSurroundDecoder::ChannelSetup::Stereo, true}, // StereoLFE
|
||||
{FreeSurroundDecoder::ChannelSetup::Surround41, false}, // Quadraphonic
|
||||
{FreeSurroundDecoder::ChannelSetup::Surround41, true}, // QuadraphonicLFE
|
||||
{FreeSurroundDecoder::ChannelSetup::Surround51, true}, // Surround51
|
||||
{FreeSurroundDecoder::ChannelSetup::Surround71, true}, // Surround71
|
||||
}};
|
||||
|
||||
const auto [fs_setup, fs_lfe] = channel_setup_mapping[static_cast<size_t>(m_parameters.expansion_mode)];
|
||||
|
||||
m_expander = std::make_unique<FreeSurroundDecoder>(fs_setup, m_parameters.expand_block_size);
|
||||
m_expander->SetBassRedirection(fs_lfe);
|
||||
m_expander->SetCircularWrap(m_parameters.expand_circular_wrap);
|
||||
m_expander->SetShift(m_parameters.expand_shift);
|
||||
m_expander->SetDepth(m_parameters.expand_depth);
|
||||
m_expander->SetFocus(m_parameters.expand_focus);
|
||||
m_expander->SetCenterImage(m_parameters.expand_center_image);
|
||||
m_expander->SetFrontSeparation(m_parameters.expand_front_separation);
|
||||
m_expander->SetRearSeparation(m_parameters.expand_rear_separation);
|
||||
m_expander->SetLowCutoff(static_cast<float>(m_parameters.expand_low_cutoff) / m_sample_rate * 2);
|
||||
m_expander->SetHighCutoff(static_cast<float>(m_parameters.expand_high_cutoff) / m_sample_rate * 2);
|
||||
#else
|
||||
Panic("Attempting to use expansion on Android.");
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioStream::EndWrite(u32 num_frames)
|
||||
{
|
||||
// don't bother committing anything when muted
|
||||
if (m_volume == 0)
|
||||
return;
|
||||
|
||||
m_staging_buffer_pos += num_frames * NUM_INPUT_CHANNELS;
|
||||
DebugAssert(m_staging_buffer_pos <= (CHUNK_SIZE * NUM_INPUT_CHANNELS));
|
||||
if ((m_staging_buffer_pos / NUM_INPUT_CHANNELS) < CHUNK_SIZE)
|
||||
m_staging_buffer_pos += num_frames * NUM_CHANNELS;
|
||||
DebugAssert(m_staging_buffer_pos <= (CHUNK_SIZE * NUM_CHANNELS));
|
||||
if ((m_staging_buffer_pos / NUM_CHANNELS) < CHUNK_SIZE)
|
||||
return;
|
||||
|
||||
m_staging_buffer_pos = 0;
|
||||
|
||||
if (!IsExpansionEnabled() && !IsStretchEnabled())
|
||||
if (!IsStretchEnabled())
|
||||
{
|
||||
InternalWriteFrames(m_staging_buffer.get(), CHUNK_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef __ANDROID__
|
||||
if (IsExpansionEnabled())
|
||||
{
|
||||
// StretchWriteBlock() overwrites the staging buffer on output, so we need to copy into the expand buffer first.
|
||||
S16ChunkToFloat(m_staging_buffer.get(), m_expand_buffer.get() + m_expand_buffer_pos * NUM_INPUT_CHANNELS,
|
||||
CHUNK_SIZE * NUM_INPUT_CHANNELS);
|
||||
|
||||
// Output the corresponding block.
|
||||
if (m_expand_output_buffer)
|
||||
StretchWriteBlock(m_expand_output_buffer + m_expand_buffer_pos * m_internal_channels);
|
||||
|
||||
// Decode the next block if we buffered enough.
|
||||
m_expand_buffer_pos += CHUNK_SIZE;
|
||||
if (m_expand_buffer_pos == m_parameters.expand_block_size)
|
||||
{
|
||||
m_expand_buffer_pos = 0;
|
||||
m_expand_output_buffer = m_expander->Decode(m_expand_buffer.get());
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
S16ChunkToFloat(m_staging_buffer.get(), m_float_buffer.get(), CHUNK_SIZE * NUM_INPUT_CHANNELS);
|
||||
StretchWriteBlock(m_float_buffer.get());
|
||||
}
|
||||
S16ChunkToFloat(m_staging_buffer.get(), m_float_buffer.get(), CHUNK_SIZE * NUM_CHANNELS);
|
||||
StretchWriteBlock(m_float_buffer.get());
|
||||
}
|
||||
|
||||
// Time stretching algorithm based on PCSX2 implementation.
|
||||
@@ -706,7 +637,7 @@ void AudioStream::StretchAllocate()
|
||||
|
||||
m_soundtouch = soundtouch_createInstance();
|
||||
soundtouch_setSampleRate(m_soundtouch, m_sample_rate);
|
||||
soundtouch_setChannels(m_soundtouch, m_internal_channels);
|
||||
soundtouch_setChannels(m_soundtouch, NUM_CHANNELS);
|
||||
|
||||
soundtouch_setSetting(m_soundtouch, SETTING_USE_QUICKSEEK, m_parameters.stretch_use_quickseek);
|
||||
soundtouch_setSetting(m_soundtouch, SETTING_USE_AA_FILTER, m_parameters.stretch_use_aa_filter);
|
||||
@@ -748,7 +679,7 @@ void AudioStream::StretchWriteBlock(const float* block)
|
||||
u32 tempProgress;
|
||||
while (tempProgress = soundtouch_receiveSamples(m_soundtouch, m_float_buffer.get(), CHUNK_SIZE), tempProgress != 0)
|
||||
{
|
||||
FloatChunkToS16(m_staging_buffer.get(), m_float_buffer.get(), tempProgress * m_internal_channels);
|
||||
FloatChunkToS16(m_staging_buffer.get(), m_float_buffer.get(), tempProgress * NUM_CHANNELS);
|
||||
InternalWriteFrames(m_staging_buffer.get(), tempProgress);
|
||||
}
|
||||
|
||||
@@ -757,7 +688,7 @@ void AudioStream::StretchWriteBlock(const float* block)
|
||||
}
|
||||
else
|
||||
{
|
||||
FloatChunkToS16(m_staging_buffer.get(), block, CHUNK_SIZE * m_internal_channels);
|
||||
FloatChunkToS16(m_staging_buffer.get(), block, CHUNK_SIZE * NUM_CHANNELS);
|
||||
InternalWriteFrames(m_staging_buffer.get(), CHUNK_SIZE);
|
||||
}
|
||||
}
|
||||
@@ -891,116 +822,3 @@ void AudioStream::StretchOverrun()
|
||||
const u32 discard = CHUNK_SIZE * 2;
|
||||
m_rpos.store((m_rpos.load(std::memory_order_acquire) + discard) % m_buffer_size, std::memory_order_release);
|
||||
}
|
||||
|
||||
void AudioStreamParameters::Load(SettingsInterface& si, const char* section)
|
||||
{
|
||||
stretch_mode =
|
||||
AudioStream::ParseStretchMode(
|
||||
si.GetStringValue(section, "StretchMode", AudioStream::GetStretchModeName(DEFAULT_STRETCH_MODE)).c_str())
|
||||
.value_or(DEFAULT_STRETCH_MODE);
|
||||
#ifndef __ANDROID__
|
||||
expansion_mode =
|
||||
AudioStream::ParseExpansionMode(
|
||||
si.GetStringValue(section, "ExpansionMode", AudioStream::GetExpansionModeName(DEFAULT_EXPANSION_MODE)).c_str())
|
||||
.value_or(DEFAULT_EXPANSION_MODE);
|
||||
#else
|
||||
expansion_mode = AudioExpansionMode::Disabled;
|
||||
#endif
|
||||
output_latency_ms = static_cast<u16>(std::min<u32>(
|
||||
si.GetUIntValue(section, "OutputLatencyMS", DEFAULT_OUTPUT_LATENCY_MS), std::numeric_limits<u16>::max()));
|
||||
output_latency_minimal = si.GetBoolValue(section, "OutputLatencyMinimal", DEFAULT_OUTPUT_LATENCY_MINIMAL);
|
||||
buffer_ms = static_cast<u16>(
|
||||
std::min<u32>(si.GetUIntValue(section, "BufferMS", DEFAULT_BUFFER_MS), std::numeric_limits<u16>::max()));
|
||||
|
||||
stretch_sequence_length_ms =
|
||||
static_cast<u16>(std::min<u32>(si.GetUIntValue(section, "StretchSequenceLengthMS", DEFAULT_STRETCH_SEQUENCE_LENGTH),
|
||||
std::numeric_limits<u16>::max()));
|
||||
stretch_seekwindow_ms = static_cast<u16>(std::min<u32>(
|
||||
si.GetUIntValue(section, "StretchSeekWindowMS", DEFAULT_STRETCH_SEEKWINDOW), std::numeric_limits<u16>::max()));
|
||||
stretch_overlap_ms = static_cast<u16>(std::min<u32>(
|
||||
si.GetUIntValue(section, "StretchOverlapMS", DEFAULT_STRETCH_OVERLAP), std::numeric_limits<u16>::max()));
|
||||
stretch_use_quickseek = si.GetBoolValue(section, "StretchUseQuickSeek", DEFAULT_STRETCH_USE_QUICKSEEK);
|
||||
stretch_use_aa_filter = si.GetBoolValue(section, "StretchUseAAFilter", DEFAULT_STRETCH_USE_AA_FILTER);
|
||||
|
||||
expand_block_size = static_cast<u16>(std::min<u32>(
|
||||
si.GetUIntValue(section, "ExpandBlockSize", DEFAULT_EXPAND_BLOCK_SIZE), std::numeric_limits<u16>::max()));
|
||||
expand_block_size = std::clamp<u16>(
|
||||
Common::IsPow2(expand_block_size) ? expand_block_size : Common::NextPow2(expand_block_size), 128, 8192);
|
||||
expand_circular_wrap =
|
||||
std::clamp(si.GetFloatValue(section, "ExpandCircularWrap", DEFAULT_EXPAND_CIRCULAR_WRAP), 0.0f, 360.0f);
|
||||
expand_shift = std::clamp(si.GetFloatValue(section, "ExpandShift", DEFAULT_EXPAND_SHIFT), -1.0f, 1.0f);
|
||||
expand_depth = std::clamp(si.GetFloatValue(section, "ExpandDepth", DEFAULT_EXPAND_DEPTH), 0.0f, 5.0f);
|
||||
expand_focus = std::clamp(si.GetFloatValue(section, "ExpandFocus", DEFAULT_EXPAND_FOCUS), -1.0f, 1.0f);
|
||||
expand_center_image =
|
||||
std::clamp(si.GetFloatValue(section, "ExpandCenterImage", DEFAULT_EXPAND_CENTER_IMAGE), 0.0f, 1.0f);
|
||||
expand_front_separation =
|
||||
std::clamp(si.GetFloatValue(section, "ExpandFrontSeparation", DEFAULT_EXPAND_FRONT_SEPARATION), 0.0f, 10.0f);
|
||||
expand_rear_separation =
|
||||
std::clamp(si.GetFloatValue(section, "ExpandRearSeparation", DEFAULT_EXPAND_REAR_SEPARATION), 0.0f, 10.0f);
|
||||
expand_low_cutoff =
|
||||
static_cast<u8>(std::min<u32>(si.GetUIntValue(section, "ExpandLowCutoff", DEFAULT_EXPAND_LOW_CUTOFF), 100));
|
||||
expand_high_cutoff =
|
||||
static_cast<u8>(std::min<u32>(si.GetUIntValue(section, "ExpandHighCutoff", DEFAULT_EXPAND_HIGH_CUTOFF), 100));
|
||||
}
|
||||
|
||||
void AudioStreamParameters::Save(SettingsInterface& si, const char* section) const
|
||||
{
|
||||
si.SetStringValue(section, "StretchMode", AudioStream::GetStretchModeName(stretch_mode));
|
||||
si.SetStringValue(section, "ExpansionMode", AudioStream::GetExpansionModeName(expansion_mode));
|
||||
si.SetUIntValue(section, "BufferMS", buffer_ms);
|
||||
si.SetUIntValue(section, "OutputLatencyMS", output_latency_ms);
|
||||
si.SetBoolValue(section, "OutputLatencyMinimal", output_latency_minimal);
|
||||
|
||||
si.SetUIntValue(section, "StretchSequenceLengthMS", stretch_sequence_length_ms);
|
||||
si.SetUIntValue(section, "StretchSeekWindowMS", stretch_seekwindow_ms);
|
||||
si.SetUIntValue(section, "StretchOverlapMS", stretch_overlap_ms);
|
||||
si.SetBoolValue(section, "StretchUseQuickSeek", stretch_use_quickseek);
|
||||
si.SetBoolValue(section, "StretchUseAAFilter", stretch_use_aa_filter);
|
||||
|
||||
si.SetUIntValue(section, "ExpandBlockSize", expand_block_size);
|
||||
si.SetFloatValue(section, "ExpandCircularWrap", expand_circular_wrap);
|
||||
si.SetFloatValue(section, "ExpandShift", expand_shift);
|
||||
si.SetFloatValue(section, "ExpandDepth", expand_depth);
|
||||
si.SetFloatValue(section, "ExpandFocus", expand_focus);
|
||||
si.SetFloatValue(section, "ExpandCenterImage", expand_center_image);
|
||||
si.SetFloatValue(section, "ExpandFrontSeparation", expand_front_separation);
|
||||
si.SetFloatValue(section, "ExpandRearSeparation", expand_rear_separation);
|
||||
si.SetUIntValue(section, "ExpandLowCutoff", expand_low_cutoff);
|
||||
si.SetUIntValue(section, "ExpandHighCutoff", expand_high_cutoff);
|
||||
}
|
||||
|
||||
void AudioStreamParameters::Clear(SettingsInterface& si, const char* section)
|
||||
{
|
||||
si.DeleteValue(section, "StretchMode");
|
||||
si.DeleteValue(section, "ExpansionMode");
|
||||
si.DeleteValue(section, "BufferMS");
|
||||
si.DeleteValue(section, "OutputLatencyMS");
|
||||
si.DeleteValue(section, "OutputLatencyMinimal");
|
||||
|
||||
si.DeleteValue(section, "StretchSequenceLengthMS");
|
||||
si.DeleteValue(section, "StretchSeekWindowMS");
|
||||
si.DeleteValue(section, "StretchOverlapMS");
|
||||
si.DeleteValue(section, "StretchUseQuickSeek");
|
||||
si.DeleteValue(section, "StretchUseAAFilter");
|
||||
|
||||
si.DeleteValue(section, "ExpandBlockSize");
|
||||
si.DeleteValue(section, "ExpandCircularWrap");
|
||||
si.DeleteValue(section, "ExpandShift");
|
||||
si.DeleteValue(section, "ExpandDepth");
|
||||
si.DeleteValue(section, "ExpandFocus");
|
||||
si.DeleteValue(section, "ExpandCenterImage");
|
||||
si.DeleteValue(section, "ExpandFrontSeparation");
|
||||
si.DeleteValue(section, "ExpandRearSeparation");
|
||||
si.DeleteValue(section, "ExpandLowCutoff");
|
||||
si.DeleteValue(section, "ExpandHighCutoff");
|
||||
}
|
||||
|
||||
bool AudioStreamParameters::operator!=(const AudioStreamParameters& rhs) const
|
||||
{
|
||||
return (std::memcmp(this, &rhs, sizeof(*this)) != 0);
|
||||
}
|
||||
|
||||
bool AudioStreamParameters::operator==(const AudioStreamParameters& rhs) const
|
||||
{
|
||||
return (std::memcmp(this, &rhs, sizeof(*this)) == 0);
|
||||
}
|
||||
|
||||
+10
-154
@@ -1,12 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/align.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <complex>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -15,11 +17,12 @@
|
||||
class Error;
|
||||
class SettingsInterface;
|
||||
|
||||
class FreeSurroundDecoder;
|
||||
namespace soundtouch {
|
||||
class SoundTouch;
|
||||
}
|
||||
|
||||
struct kiss_fftr_state;
|
||||
|
||||
enum class AudioBackend : u8
|
||||
{
|
||||
Null,
|
||||
@@ -41,21 +44,9 @@ enum class AudioStretchMode : u8
|
||||
Count
|
||||
};
|
||||
|
||||
enum class AudioExpansionMode : u8
|
||||
{
|
||||
Disabled,
|
||||
StereoLFE,
|
||||
Quadraphonic,
|
||||
QuadraphonicLFE,
|
||||
Surround51,
|
||||
Surround71,
|
||||
Count
|
||||
};
|
||||
|
||||
struct AudioStreamParameters
|
||||
{
|
||||
AudioStretchMode stretch_mode = DEFAULT_STRETCH_MODE;
|
||||
AudioExpansionMode expansion_mode = DEFAULT_EXPANSION_MODE;
|
||||
u16 buffer_ms = DEFAULT_BUFFER_MS;
|
||||
u16 output_latency_ms = DEFAULT_OUTPUT_LATENCY_MS;
|
||||
bool output_latency_minimal = DEFAULT_OUTPUT_LATENCY_MINIMAL;
|
||||
@@ -67,19 +58,7 @@ struct AudioStreamParameters
|
||||
bool stretch_use_quickseek = DEFAULT_STRETCH_USE_QUICKSEEK;
|
||||
bool stretch_use_aa_filter = DEFAULT_STRETCH_USE_AA_FILTER;
|
||||
|
||||
float expand_circular_wrap = DEFAULT_EXPAND_CIRCULAR_WRAP;
|
||||
float expand_shift = DEFAULT_EXPAND_SHIFT;
|
||||
float expand_depth = DEFAULT_EXPAND_DEPTH;
|
||||
float expand_focus = DEFAULT_EXPAND_FOCUS;
|
||||
float expand_center_image = DEFAULT_EXPAND_CENTER_IMAGE;
|
||||
float expand_front_separation = DEFAULT_EXPAND_FRONT_SEPARATION;
|
||||
float expand_rear_separation = DEFAULT_EXPAND_REAR_SEPARATION;
|
||||
u16 expand_block_size = DEFAULT_EXPAND_BLOCK_SIZE;
|
||||
u8 expand_low_cutoff = DEFAULT_EXPAND_LOW_CUTOFF;
|
||||
u8 expand_high_cutoff = DEFAULT_EXPAND_HIGH_CUTOFF;
|
||||
|
||||
static constexpr AudioStretchMode DEFAULT_STRETCH_MODE = AudioStretchMode::TimeStretch;
|
||||
static constexpr AudioExpansionMode DEFAULT_EXPANSION_MODE = AudioExpansionMode::Disabled;
|
||||
#ifndef __ANDROID__
|
||||
static constexpr u16 DEFAULT_BUFFER_MS = 50;
|
||||
static constexpr u16 DEFAULT_OUTPUT_LATENCY_MS = 20;
|
||||
@@ -88,16 +67,6 @@ struct AudioStreamParameters
|
||||
static constexpr u16 DEFAULT_OUTPUT_LATENCY_MS = 20;
|
||||
#endif
|
||||
static constexpr bool DEFAULT_OUTPUT_LATENCY_MINIMAL = false;
|
||||
static constexpr u16 DEFAULT_EXPAND_BLOCK_SIZE = 2048;
|
||||
static constexpr float DEFAULT_EXPAND_CIRCULAR_WRAP = 90.0f;
|
||||
static constexpr float DEFAULT_EXPAND_SHIFT = 0.0f;
|
||||
static constexpr float DEFAULT_EXPAND_DEPTH = 1.0f;
|
||||
static constexpr float DEFAULT_EXPAND_FOCUS = 0.0f;
|
||||
static constexpr float DEFAULT_EXPAND_CENTER_IMAGE = 1.0f;
|
||||
static constexpr float DEFAULT_EXPAND_FRONT_SEPARATION = 1.0f;
|
||||
static constexpr float DEFAULT_EXPAND_REAR_SEPARATION = 1.0f;
|
||||
static constexpr u8 DEFAULT_EXPAND_LOW_CUTOFF = 40;
|
||||
static constexpr u8 DEFAULT_EXPAND_HIGH_CUTOFF = 90;
|
||||
|
||||
static constexpr u16 DEFAULT_STRETCH_SEQUENCE_LENGTH = 30;
|
||||
static constexpr u16 DEFAULT_STRETCH_SEEKWINDOW = 20;
|
||||
@@ -119,11 +88,8 @@ class AudioStream
|
||||
public:
|
||||
using SampleType = s16;
|
||||
|
||||
static constexpr u32 NUM_INPUT_CHANNELS = 2;
|
||||
static constexpr u32 MAX_OUTPUT_CHANNELS = 8;
|
||||
static constexpr u32 NUM_CHANNELS = 2;
|
||||
static constexpr u32 CHUNK_SIZE = 64;
|
||||
static constexpr u32 MIN_EXPANSION_BLOCK_SIZE = 256;
|
||||
static constexpr u32 MAX_EXPANSION_BLOCK_SIZE = 4096;
|
||||
|
||||
#ifndef __ANDROID__
|
||||
static constexpr AudioBackend DEFAULT_BACKEND = AudioBackend::Cubeb;
|
||||
@@ -152,17 +118,11 @@ public:
|
||||
static const char* GetBackendName(AudioBackend backend);
|
||||
static const char* GetBackendDisplayName(AudioBackend backend);
|
||||
|
||||
static const char* GetExpansionModeName(AudioExpansionMode mode);
|
||||
static const char* GetExpansionModeDisplayName(AudioExpansionMode mode);
|
||||
static std::optional<AudioExpansionMode> ParseExpansionMode(const char* name);
|
||||
|
||||
static const char* GetStretchModeName(AudioStretchMode mode);
|
||||
static const char* GetStretchModeDisplayName(AudioStretchMode mode);
|
||||
static std::optional<AudioStretchMode> ParseStretchMode(const char* name);
|
||||
|
||||
ALWAYS_INLINE u32 GetSampleRate() const { return m_sample_rate; }
|
||||
ALWAYS_INLINE u32 GetInternalChannels() const { return m_internal_channels; }
|
||||
ALWAYS_INLINE u32 GetOutputChannels() const { return m_internal_channels; }
|
||||
ALWAYS_INLINE u32 GetBufferSize() const { return m_buffer_size; }
|
||||
ALWAYS_INLINE u32 GetTargetBufferSize() const { return m_target_buffer_size; }
|
||||
ALWAYS_INLINE u32 GetOutputVolume() const { return m_volume; }
|
||||
@@ -195,37 +155,14 @@ public:
|
||||
static std::unique_ptr<AudioStream> CreateNullStream(u32 sample_rate, u32 buffer_ms);
|
||||
|
||||
protected:
|
||||
enum ReadChannel : u8
|
||||
{
|
||||
READ_CHANNEL_FRONT_LEFT,
|
||||
READ_CHANNEL_FRONT_CENTER,
|
||||
READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_SIDE_LEFT,
|
||||
READ_CHANNEL_SIDE_RIGHT,
|
||||
READ_CHANNEL_REAR_LEFT,
|
||||
READ_CHANNEL_REAR_RIGHT,
|
||||
READ_CHANNEL_LFE,
|
||||
READ_CHANNEL_NONE
|
||||
};
|
||||
|
||||
using SampleReader = void (*)(SampleType* dest, const SampleType* src, u32 num_frames);
|
||||
|
||||
AudioStream(u32 sample_rate, const AudioStreamParameters& parameters);
|
||||
void BaseInitialize(SampleReader sample_reader);
|
||||
void BaseInitialize();
|
||||
|
||||
void ReadFrames(SampleType* samples, u32 num_frames);
|
||||
|
||||
template<AudioExpansionMode mode, ReadChannel c0 = READ_CHANNEL_NONE, ReadChannel c1 = READ_CHANNEL_NONE,
|
||||
ReadChannel c2 = READ_CHANNEL_NONE, ReadChannel c3 = READ_CHANNEL_NONE, ReadChannel c4 = READ_CHANNEL_NONE,
|
||||
ReadChannel c5 = READ_CHANNEL_NONE, ReadChannel c6 = READ_CHANNEL_NONE, ReadChannel c7 = READ_CHANNEL_NONE>
|
||||
static void SampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames);
|
||||
static void StereoSampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames);
|
||||
|
||||
u32 m_sample_rate = 0;
|
||||
u32 m_volume = 100;
|
||||
AudioStreamParameters m_parameters;
|
||||
u8 m_internal_channels = 0;
|
||||
u8 m_output_channels = 0;
|
||||
bool m_stretch_inactive = false;
|
||||
bool m_filling = false;
|
||||
bool m_paused = false;
|
||||
@@ -251,7 +188,6 @@ private:
|
||||
const AudioStreamParameters& parameters, Error* error);
|
||||
#endif
|
||||
|
||||
ALWAYS_INLINE bool IsExpansionEnabled() const { return m_parameters.expansion_mode != AudioExpansionMode::Disabled; }
|
||||
ALWAYS_INLINE bool IsStretchEnabled() const { return m_parameters.stretch_mode != AudioStretchMode::Off; }
|
||||
|
||||
void AllocateBuffer();
|
||||
@@ -259,10 +195,6 @@ private:
|
||||
|
||||
void InternalWriteFrames(SampleType* samples, u32 num_frames);
|
||||
|
||||
#ifndef __ANDROID__
|
||||
void ExpandAllocate();
|
||||
#endif
|
||||
|
||||
void StretchAllocate();
|
||||
void StretchDestroy();
|
||||
void StretchWriteBlock(const float* block);
|
||||
@@ -273,8 +205,7 @@ private:
|
||||
void UpdateStretchTempo();
|
||||
|
||||
u32 m_buffer_size = 0;
|
||||
std::unique_ptr<s16[]> m_buffer;
|
||||
SampleReader m_sample_reader = nullptr;
|
||||
Common::unique_aligned_ptr<s16[]> m_buffer;
|
||||
|
||||
std::atomic<u32> m_rpos{0};
|
||||
std::atomic<u32> m_wpos{0};
|
||||
@@ -295,83 +226,8 @@ private:
|
||||
std::array<float, AVERAGING_BUFFER_SIZE> m_average_fullness = {};
|
||||
|
||||
// temporary staging buffer, used for timestretching
|
||||
std::unique_ptr<s16[]> m_staging_buffer;
|
||||
Common::unique_aligned_ptr<s16[]> m_staging_buffer;
|
||||
|
||||
// float buffer, soundtouch only accepts float samples as input
|
||||
std::unique_ptr<float[]> m_float_buffer;
|
||||
|
||||
#ifndef __ANDROID__
|
||||
std::unique_ptr<FreeSurroundDecoder> m_expander;
|
||||
|
||||
// block buffer for expansion
|
||||
std::unique_ptr<float[]> m_expand_buffer;
|
||||
float* m_expand_output_buffer = nullptr;
|
||||
u32 m_expand_buffer_pos = 0;
|
||||
#endif
|
||||
Common::unique_aligned_ptr<float[]> m_float_buffer;
|
||||
};
|
||||
|
||||
template<AudioExpansionMode mode, AudioStream::ReadChannel c0, AudioStream::ReadChannel c1, AudioStream::ReadChannel c2,
|
||||
AudioStream::ReadChannel c3, AudioStream::ReadChannel c4, AudioStream::ReadChannel c5,
|
||||
AudioStream::ReadChannel c6, AudioStream::ReadChannel c7>
|
||||
void AudioStream::SampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames)
|
||||
{
|
||||
static_assert(READ_CHANNEL_NONE == MAX_OUTPUT_CHANNELS);
|
||||
static constexpr const std::array<std::pair<std::array<s8, MAX_OUTPUT_CHANNELS>, u8>,
|
||||
static_cast<size_t>(AudioExpansionMode::Count)>
|
||||
luts = {{
|
||||
// FL FC FR SL SR RL RR LFE
|
||||
{{0, -1, 1, -1, -1, -1, -1, -1}, 2}, // Disabled
|
||||
{{0, -1, 1, -1, -1, -1, -1, 2}, 3}, // StereoLFE
|
||||
{{0, -1, 1, -1, -1, 2, 3, -1}, 5}, // Quadraphonic
|
||||
{{0, -1, 2, -1, -1, 2, 3, 4}, 5}, // QuadraphonicLFE
|
||||
{{0, 1, 2, -1, -1, 3, 4, 5}, 6}, // Surround51
|
||||
{{0, 1, 2, 3, 4, 5, 6, 7}, 8}, // Surround71
|
||||
}};
|
||||
constexpr const auto& lut = luts[static_cast<size_t>(mode)].first;
|
||||
for (u32 i = 0; i < num_frames; i++)
|
||||
{
|
||||
if constexpr (c0 != READ_CHANNEL_NONE)
|
||||
{
|
||||
static_assert(lut[c0] >= 0 && lut[c0] < MAX_OUTPUT_CHANNELS);
|
||||
*(dest++) = src[lut[c0]];
|
||||
}
|
||||
if constexpr (c1 != READ_CHANNEL_NONE)
|
||||
{
|
||||
static_assert(lut[c1] >= 0 && lut[c1] < MAX_OUTPUT_CHANNELS);
|
||||
*(dest++) = src[lut[c1]];
|
||||
}
|
||||
if constexpr (c2 != READ_CHANNEL_NONE)
|
||||
{
|
||||
static_assert(lut[c2] >= 0 && lut[c2] < MAX_OUTPUT_CHANNELS);
|
||||
*(dest++) = src[lut[c2]];
|
||||
}
|
||||
if constexpr (c3 != READ_CHANNEL_NONE)
|
||||
{
|
||||
static_assert(lut[c3] >= 0 && lut[c3] < MAX_OUTPUT_CHANNELS);
|
||||
*(dest++) = src[lut[c3]];
|
||||
}
|
||||
if constexpr (c4 != READ_CHANNEL_NONE)
|
||||
{
|
||||
static_assert(lut[c4] >= 0 && lut[c4] < MAX_OUTPUT_CHANNELS);
|
||||
*(dest++) = src[lut[c4]];
|
||||
}
|
||||
if constexpr (c5 != READ_CHANNEL_NONE)
|
||||
{
|
||||
static_assert(lut[c5] >= 0 && lut[c5] < MAX_OUTPUT_CHANNELS);
|
||||
*(dest++) = src[lut[c5]];
|
||||
}
|
||||
if constexpr (c6 != READ_CHANNEL_NONE)
|
||||
{
|
||||
static_assert(lut[c6] >= 0 && lut[c6] < MAX_OUTPUT_CHANNELS);
|
||||
*(dest++) = src[lut[c6]];
|
||||
}
|
||||
if constexpr (c7 != READ_CHANNEL_NONE)
|
||||
{
|
||||
static_assert(lut[c7] >= 0 && lut[c7] < MAX_OUTPUT_CHANNELS);
|
||||
*(dest++) = src[lut[c7]];
|
||||
}
|
||||
|
||||
src += luts[static_cast<size_t>(mode)].second;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,37 +116,11 @@ bool CubebAudioStream::Initialize(const char* driver_name, const char* device_na
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr const std::array<std::pair<cubeb_channel_layout, SampleReader>,
|
||||
static_cast<size_t>(AudioExpansionMode::Count)>
|
||||
channel_setups = {{
|
||||
// Disabled
|
||||
{CUBEB_LAYOUT_STEREO, StereoSampleReaderImpl},
|
||||
// StereoLFE
|
||||
{CUBEB_LAYOUT_STEREO_LFE, &SampleReaderImpl<AudioExpansionMode::StereoLFE, READ_CHANNEL_FRONT_LEFT,
|
||||
READ_CHANNEL_FRONT_RIGHT, READ_CHANNEL_LFE>},
|
||||
// Quadraphonic
|
||||
{CUBEB_LAYOUT_QUAD, &SampleReaderImpl<AudioExpansionMode::Quadraphonic, READ_CHANNEL_FRONT_LEFT,
|
||||
READ_CHANNEL_FRONT_RIGHT, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
|
||||
// QuadraphonicLFE
|
||||
{CUBEB_LAYOUT_QUAD_LFE,
|
||||
&SampleReaderImpl<AudioExpansionMode::QuadraphonicLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
|
||||
// Surround51
|
||||
{CUBEB_LAYOUT_3F2_LFE_BACK,
|
||||
&SampleReaderImpl<AudioExpansionMode::Surround51, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
|
||||
// Surround71
|
||||
{CUBEB_LAYOUT_3F4_LFE,
|
||||
&SampleReaderImpl<AudioExpansionMode::Surround71, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT,
|
||||
READ_CHANNEL_SIDE_LEFT, READ_CHANNEL_SIDE_RIGHT>},
|
||||
}};
|
||||
|
||||
cubeb_stream_params params = {};
|
||||
params.format = CUBEB_SAMPLE_S16LE;
|
||||
params.rate = m_sample_rate;
|
||||
params.channels = m_output_channels;
|
||||
params.layout = channel_setups[static_cast<size_t>(m_parameters.expansion_mode)].first;
|
||||
params.channels = NUM_CHANNELS;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
u32 latency_frames = GetBufferSizeForMS(
|
||||
@@ -215,7 +189,7 @@ bool CubebAudioStream::Initialize(const char* driver_name, const char* device_na
|
||||
}
|
||||
}
|
||||
|
||||
BaseInitialize(channel_setups[static_cast<size_t>(m_parameters.expansion_mode)].second);
|
||||
BaseInitialize();
|
||||
|
||||
char stream_name[32];
|
||||
std::snprintf(stream_name, sizeof(stream_name), "%p", this);
|
||||
|
||||
@@ -79,30 +79,9 @@ bool SDLAudioStream::OpenDevice(Error* error)
|
||||
{
|
||||
DebugAssert(!IsOpen());
|
||||
|
||||
static constexpr const std::array<SampleReader, static_cast<size_t>(AudioExpansionMode::Count)> sample_readers = {{
|
||||
// Disabled
|
||||
&StereoSampleReaderImpl,
|
||||
// StereoLFE
|
||||
&SampleReaderImpl<AudioExpansionMode::StereoLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_LFE>,
|
||||
// Quadraphonic
|
||||
&SampleReaderImpl<AudioExpansionMode::Quadraphonic, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||
// QuadraphonicLFE
|
||||
&SampleReaderImpl<AudioExpansionMode::QuadraphonicLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||
// Surround51
|
||||
&SampleReaderImpl<AudioExpansionMode::Surround51, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||
// Surround71
|
||||
&SampleReaderImpl<AudioExpansionMode::Surround71, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_SIDE_LEFT, READ_CHANNEL_SIDE_RIGHT,
|
||||
READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||
}};
|
||||
|
||||
SDL_AudioSpec spec = {};
|
||||
spec.freq = m_sample_rate;
|
||||
spec.channels = m_output_channels;
|
||||
spec.channels = NUM_CHANNELS;
|
||||
spec.format = AUDIO_S16;
|
||||
spec.samples = static_cast<Uint16>(GetBufferSizeForMS(
|
||||
m_sample_rate, (m_parameters.output_latency_ms == 0) ? m_parameters.buffer_ms : m_parameters.output_latency_ms));
|
||||
@@ -119,7 +98,7 @@ bool SDLAudioStream::OpenDevice(Error* error)
|
||||
|
||||
DEV_LOG("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples);
|
||||
|
||||
BaseInitialize(sample_readers[static_cast<size_t>(m_parameters.expansion_mode)]);
|
||||
BaseInitialize();
|
||||
SDL_PauseAudioDevice(m_device_id, 0);
|
||||
|
||||
return true;
|
||||
@@ -143,7 +122,7 @@ void SDLAudioStream::CloseDevice()
|
||||
void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len)
|
||||
{
|
||||
SDLAudioStream* const this_ptr = static_cast<SDLAudioStream*>(userdata);
|
||||
const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_output_channels;
|
||||
const u32 num_frames = len / sizeof(SampleType) / NUM_CHANNELS;
|
||||
|
||||
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(stream), num_frames);
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>%(PreprocessorDefinitions);CPUINFO_SHARED=1;ENABLE_VULKAN=1</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(Platform)'!='ARM64'">%(PreprocessorDefinitions);ENABLE_OPENGL=1</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\freesurround\include;$(SolutionDir)dep\kissfft\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\d3d12ma\include;$(SolutionDir)dep\vulkan\include;$(SolutionDir)dep\ffmpeg\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\kissfft\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\d3d12ma\include;$(SolutionDir)dep\vulkan\include;$(SolutionDir)dep\ffmpeg\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'!='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\glad\include</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
@@ -211,9 +211,6 @@
|
||||
<ProjectReference Include="..\..\dep\d3d12ma\d3d12ma.vcxproj">
|
||||
<Project>{f351c4d8-594a-4850-b77b-3c1249812cce}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\freesurround\freesurround.vcxproj">
|
||||
<Project>{1b0366e5-6f82-47b4-9fdd-d699c86aa077}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
|
||||
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
Reference in New Issue
Block a user