From ca84b530a376e826c1a90d7c7e402d4a3bce00b7 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Mon, 30 Jul 2018 23:57:53 -0400
Subject: [PATCH] audio_core: Add configuration settings.

---
 src/audio_core/audio_out.cpp                |   5 +-
 src/audio_core/stream.cpp                   |  35 ++++--
 src/core/settings.h                         |   5 +
 src/yuzu/CMakeLists.txt                     |   3 +
 src/yuzu/configuration/config.cpp           |  13 ++
 src/yuzu/configuration/configure.ui         |  21 +++-
 src/yuzu/configuration/configure_audio.cpp  |  90 ++++++++++++++
 src/yuzu/configuration/configure_audio.h    |  31 +++++
 src/yuzu/configuration/configure_audio.ui   | 130 ++++++++++++++++++++
 src/yuzu/configuration/configure_dialog.cpp |   1 +
 src/yuzu_cmd/config.cpp                     |   5 +
 src/yuzu_cmd/default_ini.h                  |  12 +-
 12 files changed, 330 insertions(+), 21 deletions(-)
 create mode 100644 src/yuzu/configuration/configure_audio.cpp
 create mode 100644 src/yuzu/configuration/configure_audio.h
 create mode 100644 src/yuzu/configuration/configure_audio.ui

diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp
index 77cedb6ba3..0cabaa3544 100644
--- a/src/audio_core/audio_out.cpp
+++ b/src/audio_core/audio_out.cpp
@@ -7,6 +7,7 @@
 #include "audio_core/sink_details.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
+#include "core/settings.h"
 
 namespace AudioCore {
 
@@ -29,8 +30,8 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
 StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels,
                                Stream::ReleaseCallback&& release_callback) {
     if (!sink) {
-        const SinkDetails& sink_details = GetSinkDetails("auto");
-        sink = sink_details.factory("");
+        const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id);
+        sink = sink_details.factory(Settings::values.audio_device_id);
     }
 
     return std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels),
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index 689f51a1d7..a0045b7a12 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -2,14 +2,17 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "core/core_timing.h"
-#include "core/core_timing_util.h"
+#include <algorithm>
+#include <cmath>
 
 #include "audio_core/sink.h"
 #include "audio_core/sink_details.h"
 #include "audio_core/stream.h"
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/settings.h"
 
 namespace AudioCore {
 
@@ -56,6 +59,24 @@ s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
     return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
 }
 
+static std::vector<s16> GetVolumeAdjustedSamples(const std::vector<u8>& data) {
+    std::vector<s16> samples(data.size() / sizeof(s16));
+    std::memcpy(samples.data(), data.data(), data.size());
+    const float volume{std::clamp(Settings::values.volume, 0.0f, 1.0f)};
+
+    if (volume == 1.0f) {
+        return samples;
+    }
+
+    // Implementation of a volume slider with a dynamic range of 60 dB
+    const float volume_scale_factor{std::exp(6.90775f * volume) * 0.001f};
+    for (auto& sample : samples) {
+        sample = static_cast<s16>(sample * volume_scale_factor);
+    }
+
+    return samples;
+}
+
 void Stream::PlayNextBuffer() {
     if (!IsPlaying()) {
         // Ensure we are in playing state before playing the next buffer
@@ -75,9 +96,9 @@ void Stream::PlayNextBuffer() {
     active_buffer = queued_buffers.front();
     queued_buffers.pop();
 
-    sink_stream.EnqueueSamples(GetNumChannels(),
-                               reinterpret_cast<const s16*>(active_buffer->GetData().data()),
-                               active_buffer->GetData().size() / GetSampleSize());
+    const size_t sample_count{active_buffer->GetData().size() / GetSampleSize()};
+    sink_stream.EnqueueSamples(
+        GetNumChannels(), GetVolumeAdjustedSamples(active_buffer->GetData()).data(), sample_count);
 
     CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
 }
diff --git a/src/core/settings.h b/src/core/settings.h
index 7150d97555..8cc65e4347 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -137,6 +137,11 @@ struct Values {
 
     std::string log_filter;
 
+    // Audio
+    std::string sink_id;
+    std::string audio_device_id;
+    float volume;
+
     // Debugging
     bool use_gdbstub;
     u16 gdbstub_port;
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 7de919a8eb..4755568066 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -11,6 +11,8 @@ add_executable(yuzu
     bootmanager.h
     configuration/config.cpp
     configuration/config.h
+    configuration/configure_audio.cpp
+    configuration/configure_audio.h
     configuration/configure_debug.cpp
     configuration/configure_debug.h
     configuration/configure_dialog.cpp
@@ -55,6 +57,7 @@ add_executable(yuzu
 set(UIS
     aboutdialog.ui
     configuration/configure.ui
+    configuration/configure_audio.ui
     configuration/configure_debug.ui
     configuration/configure_general.ui
     configuration/configure_graphics.ui
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 98969fe10b..e8b3a9866a 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -92,6 +92,13 @@ void Config::ReadValues() {
     Settings::values.bg_blue = qt_config->value("bg_blue", 0.0).toFloat();
     qt_config->endGroup();
 
+    qt_config->beginGroup("Audio");
+    Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
+    Settings::values.audio_device_id =
+        qt_config->value("output_device", "auto").toString().toStdString();
+    Settings::values.volume = qt_config->value("volume", 1).toFloat();
+    qt_config->endGroup();
+
     qt_config->beginGroup("Data Storage");
     Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
     qt_config->endGroup();
@@ -195,6 +202,12 @@ void Config::SaveValues() {
     qt_config->setValue("bg_blue", (double)Settings::values.bg_blue);
     qt_config->endGroup();
 
+    qt_config->beginGroup("Audio");
+    qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id));
+    qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id));
+    qt_config->setValue("volume", Settings::values.volume);
+    qt_config->endGroup();
+
     qt_config->beginGroup("Data Storage");
     qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
     qt_config->endGroup();
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index c5303851c7..c8e0b88af9 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -34,11 +34,16 @@
        <string>Input</string>
       </attribute>
      </widget>
-      <widget class="ConfigureGraphics" name="graphicsTab">
-        <attribute name="title">
-          <string>Graphics</string>
-        </attribute>
-      </widget>
+     <widget class="ConfigureGraphics" name="graphicsTab">
+      <attribute name="title">
+       <string>Graphics</string>
+      </attribute>
+     </widget>
+     <widget class="ConfigureAudio" name="audioTab">
+      <attribute name="title">
+       <string>Audio</string>
+      </attribute>
+     </widget>
      <widget class="ConfigureDebug" name="debugTab">
       <attribute name="title">
        <string>Debug</string>
@@ -68,6 +73,12 @@
    <header>configuration/configure_system.h</header>
    <container>1</container>
   </customwidget>
+  <customwidget>
+   <class>ConfigureAudio</class>
+   <extends>QWidget</extends>
+   <header>configuration/configure_audio.h</header>
+   <container>1</container>
+  </customwidget>
   <customwidget>
    <class>ConfigureDebug</class>
    <extends>QWidget</extends>
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
new file mode 100644
index 0000000000..fbb813f6c6
--- /dev/null
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -0,0 +1,90 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+
+#include "audio_core/sink.h"
+#include "audio_core/sink_details.h"
+#include "core/core.h"
+#include "core/settings.h"
+#include "ui_configure_audio.h"
+#include "yuzu/configuration/configure_audio.h"
+
+ConfigureAudio::ConfigureAudio(QWidget* parent)
+    : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()) {
+    ui->setupUi(this);
+
+    ui->output_sink_combo_box->clear();
+    ui->output_sink_combo_box->addItem("auto");
+    for (const auto& sink_detail : AudioCore::g_sink_details) {
+        ui->output_sink_combo_box->addItem(sink_detail.id);
+    }
+
+    connect(ui->volume_slider, &QSlider::valueChanged, [this] {
+        ui->volume_indicator->setText(tr("%1 %").arg(ui->volume_slider->sliderPosition()));
+    });
+
+    this->setConfiguration();
+    connect(ui->output_sink_combo_box,
+            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
+            &ConfigureAudio::updateAudioDevices);
+
+    ui->output_sink_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+    ui->audio_device_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+}
+
+ConfigureAudio::~ConfigureAudio() = default;
+
+void ConfigureAudio::setConfiguration() {
+    int new_sink_index = 0;
+    for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
+        if (ui->output_sink_combo_box->itemText(index).toStdString() == Settings::values.sink_id) {
+            new_sink_index = index;
+            break;
+        }
+    }
+    ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
+
+    // The device list cannot be pre-populated (nor listed) until the output sink is known.
+    updateAudioDevices(new_sink_index);
+
+    int new_device_index = -1;
+    for (int index = 0; index < ui->audio_device_combo_box->count(); index++) {
+        if (ui->audio_device_combo_box->itemText(index).toStdString() ==
+            Settings::values.audio_device_id) {
+            new_device_index = index;
+            break;
+        }
+    }
+    ui->audio_device_combo_box->setCurrentIndex(new_device_index);
+
+    ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum());
+    ui->volume_indicator->setText(tr("%1 %").arg(ui->volume_slider->sliderPosition()));
+}
+
+void ConfigureAudio::applyConfiguration() {
+    Settings::values.sink_id =
+        ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
+            .toStdString();
+    Settings::values.audio_device_id =
+        ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
+            .toStdString();
+    Settings::values.volume =
+        static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum();
+}
+
+void ConfigureAudio::updateAudioDevices(int sink_index) {
+    ui->audio_device_combo_box->clear();
+    ui->audio_device_combo_box->addItem(AudioCore::auto_device_name);
+
+    std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
+    std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices();
+    for (const auto& device : device_list) {
+        ui->audio_device_combo_box->addItem(device.c_str());
+    }
+}
+
+void ConfigureAudio::retranslateUi() {
+    ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h
new file mode 100644
index 0000000000..4f0af41630
--- /dev/null
+++ b/src/yuzu/configuration/configure_audio.h
@@ -0,0 +1,31 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWidget>
+
+namespace Ui {
+class ConfigureAudio;
+}
+
+class ConfigureAudio : public QWidget {
+    Q_OBJECT
+
+public:
+    explicit ConfigureAudio(QWidget* parent = nullptr);
+    ~ConfigureAudio();
+
+    void applyConfiguration();
+    void retranslateUi();
+
+public slots:
+    void updateAudioDevices(int sink_index);
+
+private:
+    void setConfiguration();
+
+    std::unique_ptr<Ui::ConfigureAudio> ui;
+};
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
new file mode 100644
index 0000000000..ef67890dc8
--- /dev/null
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureAudio</class>
+ <widget class="QWidget" name="ConfigureAudio">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>188</width>
+    <height>246</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout">
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Audio</string>
+     </property>
+     <layout class="QVBoxLayout">
+      <item>
+       <layout class="QHBoxLayout">
+        <item>
+         <widget class="QLabel" name="label">
+          <property name="text">
+           <string>Output Engine:</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QComboBox" name="output_sink_combo_box"/>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QHBoxLayout">
+        <item>
+         <widget class="QLabel" name="label">
+          <property name="text">
+           <string>Audio Device:</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QComboBox" name="audio_device_combo_box"/>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_2">
+        <property name="topMargin">
+         <number>0</number>
+        </property>
+        <item>
+         <widget class="QLabel" name="label">
+          <property name="text">
+           <string>Volume:</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <spacer name="horizontalSpacer">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>40</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item>
+         <widget class="QSlider" name="volume_slider">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="maximum">
+           <number>100</number>
+          </property>
+          <property name="pageStep">
+           <number>10</number>
+          </property>
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLabel" name="volume_indicator">
+          <property name="minimumSize">
+           <size>
+            <width>32</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="text">
+           <string>0 %</string>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignCenter</set>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <spacer>
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>167</width>
+       <height>55</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 358f33005c..f66abf8704 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -21,6 +21,7 @@ void ConfigureDialog::applyConfiguration() {
     ui->systemTab->applyConfiguration();
     ui->inputTab->applyConfiguration();
     ui->graphicsTab->applyConfiguration();
+    ui->audioTab->applyConfiguration();
     ui->debugTab->applyConfiguration();
     Settings::Apply();
 }
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index cea1a5e623..c581e96997 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -105,6 +105,11 @@ void Config::ReadValues() {
     Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);
     Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0);
 
+    // Audio
+    Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
+    Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto");
+    Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1);
+
     // Data Storage
     Settings::values.use_virtual_sd =
         sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 567f23417e..6553c7814e 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -143,19 +143,17 @@ swap_screen =
 
 [Audio]
 # Which audio output engine to use.
-# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available)
+# auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available)
 output_engine =
 
-# Whether or not to enable the audio-stretching post-processing effect.
-# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter,
-# at the cost of increasing audio latency.
-# 0: No, 1 (default): Yes
-enable_audio_stretching =
-
 # Which audio device to use.
 # auto (default): Auto-select
 output_device =
 
+# Output volume.
+# 1.0 (default): 100%, 0.0; mute
+volume =
+
 [Data Storage]
 # Whether to create a virtual SD card.
 # 1 (default): Yes, 0: No
-- 
GitLab