duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

audiosettingswidget.cpp (27320B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "audiosettingswidget.h"
      5 #include "qtutils.h"
      6 #include "settingswindow.h"
      7 #include "settingwidgetbinder.h"
      8 #include "ui_audioexpansionsettingsdialog.h"
      9 #include "ui_audiostretchsettingsdialog.h"
     10 
     11 #include "core/spu.h"
     12 
     13 #include "util/audio_stream.h"
     14 
     15 #include <bit>
     16 #include <cmath>
     17 
     18 AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QWidget(parent), m_dialog(dialog)
     19 {
     20   SettingsInterface* sif = dialog->getSettingsInterface();
     21 
     22   m_ui.setupUi(this);
     23 
     24   for (u32 i = 0; i < static_cast<u32>(AudioBackend::Count); i++)
     25     m_ui.audioBackend->addItem(QString::fromUtf8(AudioStream::GetBackendDisplayName(static_cast<AudioBackend>(i))));
     26 
     27   for (u32 i = 0; i < static_cast<u32>(AudioExpansionMode::Count); i++)
     28   {
     29     m_ui.expansionMode->addItem(
     30       QString::fromUtf8(AudioStream::GetExpansionModeDisplayName(static_cast<AudioExpansionMode>(i))));
     31   }
     32 
     33   for (u32 i = 0; i < static_cast<u32>(AudioStretchMode::Count); i++)
     34   {
     35     m_ui.stretchMode->addItem(
     36       QString::fromUtf8(AudioStream::GetStretchModeDisplayName(static_cast<AudioStretchMode>(i))));
     37   }
     38 
     39   SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.audioBackend, "Audio", "Backend",
     40                                                &AudioStream::ParseBackendName, &AudioStream::GetBackendName,
     41                                                AudioStream::DEFAULT_BACKEND);
     42   SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.expansionMode, "Audio", "ExpansionMode",
     43                                                &AudioStream::ParseExpansionMode, &AudioStream::GetExpansionModeName,
     44                                                AudioStreamParameters::DEFAULT_EXPANSION_MODE);
     45   SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.stretchMode, "Audio", "StretchMode",
     46                                                &AudioStream::ParseStretchMode, &AudioStream::GetStretchModeName,
     47                                                AudioStreamParameters::DEFAULT_STRETCH_MODE);
     48   SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.bufferMS, "Audio", "BufferMS",
     49                                               AudioStreamParameters::DEFAULT_BUFFER_MS);
     50   SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.outputLatencyMS, "Audio", "OutputLatencyMS",
     51                                               AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS);
     52   SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.outputLatencyMinimal, "Audio", "OutputLatencyMinimal",
     53                                                AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MINIMAL);
     54   SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.muteCDAudio, "CDROM", "MuteCDAudio", false);
     55   connect(m_ui.audioBackend, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::updateDriverNames);
     56   connect(m_ui.expansionMode, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::onExpansionModeChanged);
     57   connect(m_ui.expansionSettings, &QToolButton::clicked, this, &AudioSettingsWidget::onExpansionSettingsClicked);
     58   connect(m_ui.stretchMode, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::onStretchModeChanged);
     59   connect(m_ui.stretchSettings, &QToolButton::clicked, this, &AudioSettingsWidget::onStretchSettingsClicked);
     60   onExpansionModeChanged();
     61   onStretchModeChanged();
     62   updateDriverNames();
     63 
     64   connect(m_ui.bufferMS, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabel);
     65   connect(m_ui.outputLatencyMS, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabel);
     66   connect(m_ui.outputLatencyMinimal, &QCheckBox::checkStateChanged, this,
     67           &AudioSettingsWidget::onMinimalOutputLatencyChecked);
     68   updateLatencyLabel();
     69 
     70   // for per-game, just use the normal path, since it needs to re-read/apply
     71   if (!dialog->isPerGameSettings())
     72   {
     73     m_ui.volume->setValue(m_dialog->getEffectiveIntValue("Audio", "OutputVolume", 100));
     74     m_ui.fastForwardVolume->setValue(m_dialog->getEffectiveIntValue("Audio", "FastForwardVolume", 100));
     75     m_ui.muted->setChecked(m_dialog->getEffectiveBoolValue("Audio", "OutputMuted", false));
     76     connect(m_ui.volume, &QSlider::valueChanged, this, &AudioSettingsWidget::onOutputVolumeChanged);
     77     connect(m_ui.fastForwardVolume, &QSlider::valueChanged, this, &AudioSettingsWidget::onFastForwardVolumeChanged);
     78     connect(m_ui.muted, &QCheckBox::checkStateChanged, this, &AudioSettingsWidget::onOutputMutedChanged);
     79     updateVolumeLabel();
     80   }
     81   else
     82   {
     83     SettingWidgetBinder::BindWidgetAndLabelToIntSetting(sif, m_ui.volume, m_ui.volumeLabel, tr("%"), "Audio",
     84                                                         "OutputVolume", 100);
     85     SettingWidgetBinder::BindWidgetAndLabelToIntSetting(sif, m_ui.fastForwardVolume, m_ui.fastForwardVolumeLabel,
     86                                                         tr("%"), "Audio", "FastForwardVolume", 100);
     87     SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.muted, "Audio", "OutputMuted", false);
     88   }
     89   connect(m_ui.resetVolume, &QToolButton::clicked, this, [this]() { resetVolume(false); });
     90   connect(m_ui.resetFastForwardVolume, &QToolButton::clicked, this, [this]() { resetVolume(true); });
     91 
     92   dialog->registerWidgetHelp(
     93     m_ui.audioBackend, tr("Audio Backend"), QStringLiteral("Cubeb"),
     94     tr("The audio backend determines how frames produced by the emulator are submitted to the host. Cubeb provides the "
     95        "lowest latency, if you encounter issues, try the SDL backend. The null backend disables all host audio "
     96        "output."));
     97   dialog->registerWidgetHelp(
     98     m_ui.outputLatencyMS, tr("Output Latency"), tr("%1 ms").arg(AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS),
     99     tr("The buffer size determines the size of the chunks of audio which will be pulled by the "
    100        "host. Smaller values reduce the output latency, but may cause hitches if the emulation "
    101        "speed is inconsistent. Note that the Cubeb backend uses smaller chunks regardless of "
    102        "this value, so using a low value here may not significantly change latency."));
    103   dialog->registerWidgetHelp(m_ui.volume, tr("Output Volume"), "100%",
    104                              tr("Controls the volume of the audio played on the host."));
    105   dialog->registerWidgetHelp(m_ui.fastForwardVolume, tr("Fast Forward Volume"), "100%",
    106                              tr("Controls the volume of the audio played on the host when fast forwarding."));
    107   dialog->registerWidgetHelp(m_ui.muted, tr("Mute All Sound"), tr("Unchecked"),
    108                              tr("Prevents the emulator from producing any audible sound."));
    109   dialog->registerWidgetHelp(m_ui.muteCDAudio, tr("Mute CD Audio"), tr("Unchecked"),
    110                              tr("Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable "
    111                                 "background music in some games."));
    112   dialog->registerWidgetHelp(m_ui.expansionMode, tr("Expansion Mode"), tr("Disabled (Stereo)"),
    113                              tr("Determines how audio is expanded from stereo to surround for supported games. This "
    114                                 "includes games that support Dolby Pro Logic/Pro Logic II."));
    115   dialog->registerWidgetHelp(m_ui.expansionSettings, tr("Expansion Settings"), tr("N/A"),
    116                              tr("These settings fine-tune the behavior of the FreeSurround-based channel expander."));
    117   dialog->registerWidgetHelp(
    118     m_ui.stretchMode, tr("Stretch Mode"), tr("Time Stretching"),
    119     tr("When running outside of 100% speed, adjusts the tempo on audio instead of dropping frames. Produces "
    120        "much nicer fast forward/slowdown audio at a small cost to performance."));
    121   dialog->registerWidgetHelp(m_ui.stretchSettings, tr("Stretch Settings"), tr("N/A"),
    122                              tr("These settings fine-tune the behavior of the SoundTouch audio time stretcher when "
    123                                 "running outside of 100% speed."));
    124   dialog->registerWidgetHelp(m_ui.resetVolume, tr("Reset Volume"), tr("N/A"),
    125                              m_dialog->isPerGameSettings() ? tr("Resets volume back to the global/inherited setting.") :
    126                                                              tr("Resets volume back to the default, i.e. full."));
    127   dialog->registerWidgetHelp(m_ui.resetFastForwardVolume, tr("Reset Fast Forward Volume"), tr("N/A"),
    128                              m_dialog->isPerGameSettings() ? tr("Resets volume back to the global/inherited setting.") :
    129                                                              tr("Resets volume back to the default, i.e. full."));
    130 }
    131 
    132 AudioSettingsWidget::~AudioSettingsWidget() = default;
    133 
    134 AudioExpansionMode AudioSettingsWidget::getEffectiveExpansionMode() const
    135 {
    136   return AudioStream::ParseExpansionMode(
    137            m_dialog
    138              ->getEffectiveStringValue("Audio", "ExpansionMode",
    139                                        AudioStream::GetExpansionModeName(AudioStreamParameters::DEFAULT_EXPANSION_MODE))
    140              .c_str())
    141     .value_or(AudioStreamParameters::DEFAULT_EXPANSION_MODE);
    142 }
    143 
    144 u32 AudioSettingsWidget::getEffectiveExpansionBlockSize() const
    145 {
    146   const AudioExpansionMode expansion_mode = getEffectiveExpansionMode();
    147   if (expansion_mode == AudioExpansionMode::Disabled)
    148     return 0;
    149 
    150   const u32 config_block_size =
    151     m_dialog->getEffectiveIntValue("Audio", "ExpandBlockSize", AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE);
    152   return std::has_single_bit(config_block_size) ? config_block_size : std::bit_ceil(config_block_size);
    153 }
    154 
    155 void AudioSettingsWidget::onExpansionModeChanged()
    156 {
    157   const AudioExpansionMode expansion_mode = getEffectiveExpansionMode();
    158   m_ui.expansionSettings->setEnabled(expansion_mode != AudioExpansionMode::Disabled);
    159   updateLatencyLabel();
    160 }
    161 
    162 void AudioSettingsWidget::onStretchModeChanged()
    163 {
    164   const AudioStretchMode stretch_mode =
    165     AudioStream::ParseStretchMode(
    166       m_dialog
    167         ->getEffectiveStringValue("Audio", "StretchMode",
    168                                   AudioStream::GetStretchModeName(AudioStreamParameters::DEFAULT_STRETCH_MODE))
    169         .c_str())
    170       .value_or(AudioStreamParameters::DEFAULT_STRETCH_MODE);
    171   m_ui.stretchSettings->setEnabled(stretch_mode != AudioStretchMode::Off);
    172 }
    173 
    174 AudioBackend AudioSettingsWidget::getEffectiveBackend() const
    175 {
    176   return AudioStream::ParseBackendName(
    177            m_dialog
    178              ->getEffectiveStringValue("Audio", "Backend", AudioStream::GetBackendName(AudioStream::DEFAULT_BACKEND))
    179              .c_str())
    180     .value_or(AudioStream::DEFAULT_BACKEND);
    181 }
    182 
    183 void AudioSettingsWidget::updateDriverNames()
    184 {
    185   const AudioBackend backend = getEffectiveBackend();
    186   const std::vector<std::pair<std::string, std::string>> names = AudioStream::GetDriverNames(backend);
    187 
    188   m_ui.driver->disconnect();
    189   m_ui.driver->clear();
    190   if (names.empty())
    191   {
    192     m_ui.driver->addItem(tr("Default"));
    193     m_ui.driver->setEnabled(false);
    194   }
    195   else
    196   {
    197     m_ui.driver->setEnabled(true);
    198     for (const auto& [name, display_name] : names)
    199       m_ui.driver->addItem(QString::fromStdString(display_name), QString::fromStdString(name));
    200 
    201     SettingWidgetBinder::BindWidgetToStringSetting(m_dialog->getSettingsInterface(), m_ui.driver, "Audio", "Driver",
    202                                                    std::move(names.front().first));
    203     connect(m_ui.driver, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::updateDeviceNames);
    204   }
    205 
    206   updateDeviceNames();
    207 }
    208 
    209 void AudioSettingsWidget::updateDeviceNames()
    210 {
    211   const AudioBackend backend = getEffectiveBackend();
    212   const std::string driver_name = m_dialog->getEffectiveStringValue("Audio", "Driver", "");
    213   const std::string current_device = m_dialog->getEffectiveStringValue("Audio", "Device", "");
    214   const std::vector<AudioStream::DeviceInfo> devices =
    215     AudioStream::GetOutputDevices(backend, driver_name.c_str(), SPU::SAMPLE_RATE);
    216 
    217   m_ui.outputDevice->disconnect();
    218   m_ui.outputDevice->clear();
    219   m_output_device_latency = 0;
    220 
    221   if (devices.empty())
    222   {
    223     m_ui.outputDevice->addItem(tr("Default"));
    224     m_ui.outputDevice->setEnabled(false);
    225   }
    226   else
    227   {
    228     m_ui.outputDevice->setEnabled(true);
    229 
    230     bool is_known_device = false;
    231     for (const AudioStream::DeviceInfo& di : devices)
    232     {
    233       m_ui.outputDevice->addItem(QString::fromStdString(di.display_name), QString::fromStdString(di.name));
    234       if (di.name == current_device)
    235       {
    236         m_output_device_latency = di.minimum_latency_frames;
    237         is_known_device = true;
    238       }
    239     }
    240 
    241     if (!is_known_device)
    242     {
    243       m_ui.outputDevice->addItem(tr("Unknown Device \"%1\"").arg(QString::fromStdString(current_device)),
    244                                  QString::fromStdString(current_device));
    245     }
    246 
    247     SettingWidgetBinder::BindWidgetToStringSetting(m_dialog->getSettingsInterface(), m_ui.outputDevice, "Audio",
    248                                                    "OutputDevice", std::move(devices.front().name));
    249   }
    250 
    251   updateLatencyLabel();
    252 }
    253 
    254 void AudioSettingsWidget::updateLatencyLabel()
    255 {
    256   const u32 expand_buffer_ms = AudioStream::GetMSForBufferSize(SPU::SAMPLE_RATE, getEffectiveExpansionBlockSize());
    257   const u32 config_buffer_ms =
    258     m_dialog->getEffectiveIntValue("Audio", "BufferMS", AudioStreamParameters::DEFAULT_BUFFER_MS);
    259   const u32 config_output_latency_ms =
    260     m_dialog->getEffectiveIntValue("Audio", "OutputLatencyMS", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS);
    261   const bool minimal_output = m_dialog->getEffectiveBoolValue("Audio", "OutputLatencyMinimal",
    262                                                               AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MINIMAL);
    263 
    264   //: Preserve the %1 variable, adapt the latter ms (and/or any possible spaces in between) to your language's ruleset.
    265   m_ui.outputLatencyLabel->setText(minimal_output ? tr("N/A") : tr("%1 ms").arg(config_output_latency_ms));
    266   m_ui.bufferMSLabel->setText(tr("%1 ms").arg(config_buffer_ms));
    267 
    268   const u32 output_latency_ms = minimal_output ?
    269                                   AudioStream::GetMSForBufferSize(SPU::SAMPLE_RATE, m_output_device_latency) :
    270                                   config_output_latency_ms;
    271   if (output_latency_ms > 0)
    272   {
    273     if (expand_buffer_ms > 0)
    274     {
    275       m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms buffer + %3 ms expand + %4 ms output)")
    276                                      .arg(config_buffer_ms + expand_buffer_ms + output_latency_ms)
    277                                      .arg(config_buffer_ms)
    278                                      .arg(expand_buffer_ms)
    279                                      .arg(output_latency_ms));
    280     }
    281     else
    282     {
    283       m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms buffer + %3 ms output)")
    284                                      .arg(config_buffer_ms + output_latency_ms)
    285                                      .arg(config_buffer_ms)
    286                                      .arg(output_latency_ms));
    287     }
    288   }
    289   else
    290   {
    291     if (expand_buffer_ms > 0)
    292     {
    293       m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms expand, minimum output latency unknown)")
    294                                      .arg(expand_buffer_ms + config_buffer_ms)
    295                                      .arg(expand_buffer_ms));
    296     }
    297     else
    298     {
    299       m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (minimum output latency unknown)").arg(config_buffer_ms));
    300     }
    301   }
    302 }
    303 
    304 void AudioSettingsWidget::updateVolumeLabel()
    305 {
    306   m_ui.volumeLabel->setText(tr("%1%").arg(m_ui.volume->value()));
    307   m_ui.fastForwardVolumeLabel->setText(tr("%1%").arg(m_ui.fastForwardVolume->value()));
    308 }
    309 
    310 void AudioSettingsWidget::onMinimalOutputLatencyChecked(Qt::CheckState state)
    311 {
    312   const bool minimal = m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputLatencyMinimal", false);
    313   m_ui.outputLatencyMS->setEnabled(!minimal);
    314   updateLatencyLabel();
    315 }
    316 
    317 void AudioSettingsWidget::onOutputVolumeChanged(int new_value)
    318 {
    319   // only called for base settings
    320   DebugAssert(!m_dialog->isPerGameSettings());
    321   Host::SetBaseIntSettingValue("Audio", "OutputVolume", new_value);
    322   Host::CommitBaseSettingChanges();
    323   g_emu_thread->setAudioOutputVolume(new_value, m_ui.fastForwardVolume->value());
    324 
    325   updateVolumeLabel();
    326 }
    327 
    328 void AudioSettingsWidget::onFastForwardVolumeChanged(int new_value)
    329 {
    330   // only called for base settings
    331   DebugAssert(!m_dialog->isPerGameSettings());
    332   Host::SetBaseIntSettingValue("Audio", "FastForwardVolume", new_value);
    333   Host::CommitBaseSettingChanges();
    334   g_emu_thread->setAudioOutputVolume(m_ui.volume->value(), new_value);
    335 
    336   updateVolumeLabel();
    337 }
    338 
    339 void AudioSettingsWidget::onOutputMutedChanged(int new_state)
    340 {
    341   // only called for base settings
    342   DebugAssert(!m_dialog->isPerGameSettings());
    343 
    344   const bool muted = (new_state != 0);
    345   Host::SetBaseBoolSettingValue("Audio", "OutputMuted", muted);
    346   Host::CommitBaseSettingChanges();
    347   g_emu_thread->setAudioOutputMuted(muted);
    348 }
    349 
    350 void AudioSettingsWidget::onExpansionSettingsClicked()
    351 {
    352   QDialog dlg(QtUtils::GetRootWidget(this));
    353   Ui::AudioExpansionSettingsDialog dlgui;
    354   dlgui.setupUi(&dlg);
    355   dlgui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("volume-up-line")).pixmap(32, 32));
    356 
    357   SettingsInterface* sif = m_dialog->getSettingsInterface();
    358   SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.blockSize, "Audio", "ExpandBlockSize",
    359                                               AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE, 0);
    360   QtUtils::BindLabelToSlider(dlgui.blockSize, dlgui.blockSizeLabel);
    361   SettingWidgetBinder::BindWidgetToFloatSetting(sif, dlgui.circularWrap, "Audio", "ExpandCircularWrap",
    362                                                 AudioStreamParameters::DEFAULT_EXPAND_CIRCULAR_WRAP);
    363   QtUtils::BindLabelToSlider(dlgui.circularWrap, dlgui.circularWrapLabel);
    364   SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.shift, "Audio", "ExpandShift", 100.0f,
    365                                                      AudioStreamParameters::DEFAULT_EXPAND_SHIFT);
    366   QtUtils::BindLabelToSlider(dlgui.shift, dlgui.shiftLabel, 100.0f);
    367   SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.depth, "Audio", "ExpandDepth", 10.0f,
    368                                                      AudioStreamParameters::DEFAULT_EXPAND_DEPTH);
    369   QtUtils::BindLabelToSlider(dlgui.depth, dlgui.depthLabel, 10.0f);
    370   SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.focus, "Audio", "ExpandFocus", 100.0f,
    371                                                      AudioStreamParameters::DEFAULT_EXPAND_FOCUS);
    372   QtUtils::BindLabelToSlider(dlgui.focus, dlgui.focusLabel, 100.0f);
    373   SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.centerImage, "Audio", "ExpandCenterImage", 100.0f,
    374                                                      AudioStreamParameters::DEFAULT_EXPAND_CENTER_IMAGE);
    375   QtUtils::BindLabelToSlider(dlgui.centerImage, dlgui.centerImageLabel, 100.0f);
    376   SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.frontSeparation, "Audio", "ExpandFrontSeparation",
    377                                                      10.0f, AudioStreamParameters::DEFAULT_EXPAND_FRONT_SEPARATION);
    378   QtUtils::BindLabelToSlider(dlgui.frontSeparation, dlgui.frontSeparationLabel, 10.0f);
    379   SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, dlgui.rearSeparation, "Audio", "ExpandRearSeparation", 10.0f,
    380                                                      AudioStreamParameters::DEFAULT_EXPAND_REAR_SEPARATION);
    381   QtUtils::BindLabelToSlider(dlgui.rearSeparation, dlgui.rearSeparationLabel, 10.0f);
    382   SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.lowCutoff, "Audio", "ExpandLowCutoff",
    383                                               AudioStreamParameters::DEFAULT_EXPAND_LOW_CUTOFF);
    384   QtUtils::BindLabelToSlider(dlgui.lowCutoff, dlgui.lowCutoffLabel);
    385   SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.highCutoff, "Audio", "ExpandHighCutoff",
    386                                               AudioStreamParameters::DEFAULT_EXPAND_HIGH_CUTOFF);
    387   QtUtils::BindLabelToSlider(dlgui.highCutoff, dlgui.highCutoffLabel);
    388 
    389   connect(dlgui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, &dlg, &QDialog::accept);
    390   connect(dlgui.buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [this, &dlg]() {
    391     m_dialog->setIntSettingValue("Audio", "ExpandBlockSize",
    392                                  m_dialog->isPerGameSettings() ?
    393                                    std::nullopt :
    394                                    std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE));
    395 
    396     m_dialog->setFloatSettingValue("Audio", "ExpandCircularWrap",
    397                                    m_dialog->isPerGameSettings() ?
    398                                      std::nullopt :
    399                                      std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_CIRCULAR_WRAP));
    400     m_dialog->setFloatSettingValue(
    401       "Audio", "ExpandShift",
    402       m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_SHIFT));
    403     m_dialog->setFloatSettingValue(
    404       "Audio", "ExpandDepth",
    405       m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_DEPTH));
    406     m_dialog->setFloatSettingValue(
    407       "Audio", "ExpandFocus",
    408       m_dialog->isPerGameSettings() ? std::nullopt : std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_FOCUS));
    409     m_dialog->setFloatSettingValue("Audio", "ExpandCenterImage",
    410                                    m_dialog->isPerGameSettings() ?
    411                                      std::nullopt :
    412                                      std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_CENTER_IMAGE));
    413     m_dialog->setFloatSettingValue("Audio", "ExpandFrontSeparation",
    414                                    m_dialog->isPerGameSettings() ?
    415                                      std::nullopt :
    416                                      std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_FRONT_SEPARATION));
    417     m_dialog->setFloatSettingValue("Audio", "ExpandRearSeparation",
    418                                    m_dialog->isPerGameSettings() ?
    419                                      std::nullopt :
    420                                      std::optional<float>(AudioStreamParameters::DEFAULT_EXPAND_REAR_SEPARATION));
    421     m_dialog->setIntSettingValue("Audio", "ExpandLowCutoff",
    422                                  m_dialog->isPerGameSettings() ?
    423                                    std::nullopt :
    424                                    std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_LOW_CUTOFF));
    425     m_dialog->setIntSettingValue("Audio", "ExpandHighCutoff",
    426                                  m_dialog->isPerGameSettings() ?
    427                                    std::nullopt :
    428                                    std::optional<int>(AudioStreamParameters::DEFAULT_EXPAND_HIGH_CUTOFF));
    429 
    430     dlg.done(0);
    431 
    432     QMetaObject::invokeMethod(this, &AudioSettingsWidget::onExpansionSettingsClicked, Qt::QueuedConnection);
    433   });
    434 
    435   dlg.exec();
    436   updateLatencyLabel();
    437 }
    438 
    439 void AudioSettingsWidget::onStretchSettingsClicked()
    440 {
    441   QDialog dlg(QtUtils::GetRootWidget(this));
    442   Ui::AudioStretchSettingsDialog dlgui;
    443   dlgui.setupUi(&dlg);
    444   dlgui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("volume-up-line")).pixmap(32, 32));
    445 
    446   SettingsInterface* sif = m_dialog->getSettingsInterface();
    447   SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.sequenceLength, "Audio", "StretchSequenceLengthMS",
    448                                               AudioStreamParameters::DEFAULT_STRETCH_SEQUENCE_LENGTH, 0);
    449   QtUtils::BindLabelToSlider(dlgui.sequenceLength, dlgui.sequenceLengthLabel);
    450   SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.seekWindowSize, "Audio", "StretchSeekWindowMS",
    451                                               AudioStreamParameters::DEFAULT_STRETCH_SEEKWINDOW, 0);
    452   QtUtils::BindLabelToSlider(dlgui.seekWindowSize, dlgui.seekWindowSizeLabel);
    453   SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.overlap, "Audio", "StretchOverlapMS",
    454                                               AudioStreamParameters::DEFAULT_STRETCH_OVERLAP, 0);
    455   QtUtils::BindLabelToSlider(dlgui.overlap, dlgui.overlapLabel);
    456   SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.useQuickSeek, "Audio", "StretchUseQuickSeek",
    457                                                AudioStreamParameters::DEFAULT_STRETCH_USE_QUICKSEEK);
    458   SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.useAAFilter, "Audio", "StretchUseAAFilter",
    459                                                AudioStreamParameters::DEFAULT_STRETCH_USE_AA_FILTER);
    460 
    461   connect(dlgui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, &dlg, &QDialog::accept);
    462   connect(dlgui.buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [this, &dlg]() {
    463     m_dialog->setIntSettingValue("Audio", "StretchSequenceLengthMS",
    464                                  m_dialog->isPerGameSettings() ?
    465                                    std::nullopt :
    466                                    std::optional<int>(AudioStreamParameters::DEFAULT_STRETCH_SEQUENCE_LENGTH));
    467     m_dialog->setIntSettingValue("Audio", "StretchSeekWindowMS",
    468                                  m_dialog->isPerGameSettings() ?
    469                                    std::nullopt :
    470                                    std::optional<int>(AudioStreamParameters::DEFAULT_STRETCH_SEEKWINDOW));
    471     m_dialog->setIntSettingValue("Audio", "StretchOverlapMS",
    472                                  m_dialog->isPerGameSettings() ?
    473                                    std::nullopt :
    474                                    std::optional<int>(AudioStreamParameters::DEFAULT_STRETCH_OVERLAP));
    475     m_dialog->setBoolSettingValue("Audio", "StretchUseQuickSeek",
    476                                   m_dialog->isPerGameSettings() ?
    477                                     std::nullopt :
    478                                     std::optional<bool>(AudioStreamParameters::DEFAULT_STRETCH_USE_QUICKSEEK));
    479     m_dialog->setBoolSettingValue("Audio", "StretchUseAAFilter",
    480                                   m_dialog->isPerGameSettings() ?
    481                                     std::nullopt :
    482                                     std::optional<bool>(AudioStreamParameters::DEFAULT_STRETCH_USE_AA_FILTER));
    483 
    484     dlg.done(0);
    485 
    486     QMetaObject::invokeMethod(this, &AudioSettingsWidget::onStretchSettingsClicked, Qt::QueuedConnection);
    487   });
    488 
    489   dlg.exec();
    490 }
    491 
    492 void AudioSettingsWidget::resetVolume(bool fast_forward)
    493 {
    494   const char* key = fast_forward ? "FastForwardVolume" : "OutputVolume";
    495   QSlider* const slider = fast_forward ? m_ui.fastForwardVolume : m_ui.volume;
    496   QLabel* const label = fast_forward ? m_ui.fastForwardVolumeLabel : m_ui.volumeLabel;
    497 
    498   if (m_dialog->isPerGameSettings())
    499   {
    500     m_dialog->removeSettingValue("Audio", key);
    501 
    502     const int value = m_dialog->getEffectiveIntValue("Audio", key, 100);
    503     QSignalBlocker sb(slider);
    504     slider->setValue(value);
    505     label->setText(QStringLiteral("%1%2").arg(value).arg(tr("%")));
    506 
    507     // remove bold font if it was previously overridden
    508     QFont font(label->font());
    509     font.setBold(false);
    510     label->setFont(font);
    511   }
    512   else
    513   {
    514     slider->setValue(100);
    515   }
    516 }