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(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 +#include #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(num_samples) * 1000000) / sample_rate); } +static std::vector GetVolumeAdjustedSamples(const std::vector& data) { + std::vector 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(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(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 @@ Input - - - Graphics - - + + + Graphics + + + + + Audio + + Debug @@ -68,6 +73,12 @@
configuration/configure_system.h
1 + + ConfigureAudio + QWidget +
configuration/configure_audio.h
+ 1 +
ConfigureDebug QWidget 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 + +#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->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(&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(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 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 +#include + +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; +}; 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 @@ + + + ConfigureAudio + + + + 0 + 0 + 188 + 246 + + + + + + + Audio + + + + + + + + Output Engine: + + + + + + + + + + + + + + Audio Device: + + + + + + + + + + + + 0 + + + + + Volume: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + 100 + + + 10 + + + Qt::Horizontal + + + + + + + + 32 + 0 + + + + 0 % + + + Qt::AlignCenter + + + + + + + + + + + + Qt::Vertical + + + + 167 + 55 + + + + + + + + + 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