From 178e60258922b670a96559ed26042d75918f40d8 Mon Sep 17 00:00:00 2001
From: Steveice10 <1269164+Steveice10@users.noreply.github.com>
Date: Fri, 22 Dec 2023 11:38:06 -0800
Subject: [PATCH] misc: Improve defaults for macOS and handling of missing
 audio backends. (#7273)

* misc: Improve backend defaults for macOS.

* audio_core: Improve handling of missing audio backends.
---
 CMakeLists.txt                                |  3 +-
 src/CMakeLists.txt                            |  4 ++
 src/audio_core/dsp_interface.cpp              |  2 +-
 src/audio_core/input_details.cpp              | 43 +++----------
 src/audio_core/input_details.h                | 27 ++++++---
 src/audio_core/sink_details.cpp               | 35 ++---------
 src/audio_core/sink_details.h                 | 25 +++++---
 src/citra_qt/CMakeLists.txt                   |  4 --
 .../configuration/configure_audio.cpp         | 60 +++++++++++++------
 src/common/settings.h                         |  9 ++-
 src/core/hle/service/mic/mic_u.cpp            |  4 +-
 11 files changed, 109 insertions(+), 107 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1344d06e5..d2bf7ae8a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -74,7 +74,8 @@ CMAKE_DEPENDENT_OPTION(ENABLE_DEDICATED_ROOM "Enable generating dedicated room e
 option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
 option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
 
-CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
+# TODO: cubeb currently causes issues on macOS, see: https://github.com/mozilla/cubeb/issues/771
+CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT APPLE" OFF)
 option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
 
 CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support" ON "NOT IOS" OFF)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e67b76828..37030b88a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -155,6 +155,10 @@ else()
     endif()
 endif()
 
+if (NOT APPLE)
+    add_compile_definitions(HAS_OPENGL)
+endif()
+
 add_subdirectory(common)
 add_subdirectory(core)
 add_subdirectory(video_core)
diff --git a/src/audio_core/dsp_interface.cpp b/src/audio_core/dsp_interface.cpp
index d633a89a1..5f85b67e4 100644
--- a/src/audio_core/dsp_interface.cpp
+++ b/src/audio_core/dsp_interface.cpp
@@ -21,7 +21,7 @@ void DspInterface::SetSink(AudioCore::SinkType sink_type, std::string_view audio
     // Dispose of the current sink first to avoid contention.
     sink.reset();
 
-    sink = CreateSinkFromID(sink_type, audio_device);
+    sink = AudioCore::GetSinkDetails(sink_type).create_sink(audio_device);
     sink->SetCallback(
         [this](s16* buffer, std::size_t num_frames) { OutputCallback(buffer, num_frames); });
     time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate());
diff --git a/src/audio_core/input_details.cpp b/src/audio_core/input_details.cpp
index b3367b7cc..d6b2dc5c9 100644
--- a/src/audio_core/input_details.cpp
+++ b/src/audio_core/input_details.cpp
@@ -20,24 +20,10 @@
 
 namespace AudioCore {
 namespace {
-struct InputDetails {
-    using FactoryFn = std::unique_ptr<Input> (*)(std::string_view);
-    using ListDevicesFn = std::vector<std::string> (*)();
-
-    /// Type of this input.
-    InputType type;
-    /// Name for this input.
-    std::string_view name;
-    /// A method to call to construct an instance of this type of input.
-    FactoryFn factory;
-    /// A method to call to list available devices.
-    ListDevicesFn list_devices;
-};
-
 // input_details is ordered in terms of desirability, with the best choice at the top.
 constexpr std::array input_details = {
 #ifdef HAVE_CUBEB
-    InputDetails{InputType::Cubeb, "Real Device (Cubeb)",
+    InputDetails{InputType::Cubeb, "Real Device (Cubeb)", true,
                  [](std::string_view device_id) -> std::unique_ptr<Input> {
                      if (!Core::System::GetInstance().HasMicPermission()) {
                          LOG_WARNING(Audio,
@@ -49,7 +35,7 @@ constexpr std::array input_details = {
                  &ListCubebInputDevices},
 #endif
 #ifdef HAVE_OPENAL
-    InputDetails{InputType::OpenAL, "Real Device (OpenAL)",
+    InputDetails{InputType::OpenAL, "Real Device (OpenAL)", true,
                  [](std::string_view device_id) -> std::unique_ptr<Input> {
                      if (!Core::System::GetInstance().HasMicPermission()) {
                          LOG_WARNING(Audio,
@@ -60,17 +46,22 @@ constexpr std::array input_details = {
                  },
                  &ListOpenALInputDevices},
 #endif
-    InputDetails{InputType::Static, "Static Noise",
+    InputDetails{InputType::Static, "Static Noise", false,
                  [](std::string_view device_id) -> std::unique_ptr<Input> {
                      return std::make_unique<StaticInput>();
                  },
                  [] { return std::vector<std::string>{"Static Noise"}; }},
-    InputDetails{InputType::Null, "None",
+    InputDetails{InputType::Null, "None", false,
                  [](std::string_view device_id) -> std::unique_ptr<Input> {
                      return std::make_unique<NullInput>();
                  },
                  [] { return std::vector<std::string>{"None"}; }},
 };
+} // Anonymous namespace
+
+std::vector<InputDetails> ListInputs() {
+    return {input_details.begin(), input_details.end()};
+}
 
 const InputDetails& GetInputDetails(InputType input_type) {
     auto iter = std::find_if(
@@ -88,21 +79,5 @@ const InputDetails& GetInputDetails(InputType input_type) {
 
     return *iter;
 }
-} // Anonymous namespace
-
-std::string_view GetInputName(InputType input_type) {
-    if (input_type == InputType::Auto) {
-        return "Auto";
-    }
-    return GetInputDetails(input_type).name;
-}
-
-std::vector<std::string> GetDeviceListForInput(InputType input_type) {
-    return GetInputDetails(input_type).list_devices();
-}
-
-std::unique_ptr<Input> CreateInputFromID(InputType input_type, std::string_view device_id) {
-    return GetInputDetails(input_type).factory(device_id);
-}
 
 } // namespace AudioCore
diff --git a/src/audio_core/input_details.h b/src/audio_core/input_details.h
index e5b7b9820..f1be87572 100644
--- a/src/audio_core/input_details.h
+++ b/src/audio_core/input_details.h
@@ -20,17 +20,28 @@ enum class InputType : u32 {
     Static = 2,
     Cubeb = 3,
     OpenAL = 4,
-
-    NumInputTypes,
 };
 
-/// Gets the name of a input type.
-std::string_view GetInputName(InputType input_type);
+struct InputDetails {
+    using FactoryFn = std::unique_ptr<Input> (*)(std::string_view device_id);
+    using ListDevicesFn = std::vector<std::string> (*)();
 
-/// Gets the list of devices for a particular input identified by the given ID.
-std::vector<std::string> GetDeviceListForInput(InputType input_type);
+    /// Type of this input.
+    InputType type;
+    /// Name for this input.
+    std::string_view name;
+    /// Whether the input is backed by real devices.
+    bool real;
+    /// A method to call to construct an instance of this type of input.
+    FactoryFn create_input;
+    /// A method to call to list available devices.
+    ListDevicesFn list_devices;
+};
 
-/// Creates an audio input identified by the given device ID.
-std::unique_ptr<Input> CreateInputFromID(InputType input_type, std::string_view device_id);
+/// Lists all available input types.
+std::vector<InputDetails> ListInputs();
+
+/// Gets the details of an input type.
+const InputDetails& GetInputDetails(InputType input_type);
 
 } // namespace AudioCore
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
index 9d547d656..961e040b3 100644
--- a/src/audio_core/sink_details.cpp
+++ b/src/audio_core/sink_details.cpp
@@ -21,20 +21,6 @@
 
 namespace AudioCore {
 namespace {
-struct SinkDetails {
-    using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
-    using ListDevicesFn = std::vector<std::string> (*)();
-
-    /// Type of this sink.
-    SinkType type;
-    /// Name for this sink.
-    std::string_view name;
-    /// A method to call to construct an instance of this type of sink.
-    FactoryFn factory;
-    /// A method to call to list available devices.
-    ListDevicesFn list_devices;
-};
-
 // sink_details is ordered in terms of desirability, with the best choice at the top.
 constexpr std::array sink_details = {
 #ifdef HAVE_CUBEB
@@ -64,6 +50,11 @@ constexpr std::array sink_details = {
                 },
                 [] { return std::vector<std::string>{"None"}; }},
 };
+} // Anonymous namespace
+
+std::vector<SinkDetails> ListSinks() {
+    return {sink_details.begin(), sink_details.end()};
+}
 
 const SinkDetails& GetSinkDetails(SinkType sink_type) {
     auto iter = std::find_if(
@@ -81,21 +72,5 @@ const SinkDetails& GetSinkDetails(SinkType sink_type) {
 
     return *iter;
 }
-} // Anonymous namespace
-
-std::string_view GetSinkName(SinkType sink_type) {
-    if (sink_type == SinkType::Auto) {
-        return "Auto";
-    }
-    return GetSinkDetails(sink_type).name;
-}
-
-std::vector<std::string> GetDeviceListForSink(SinkType sink_type) {
-    return GetSinkDetails(sink_type).list_devices();
-}
-
-std::unique_ptr<Sink> CreateSinkFromID(SinkType sink_type, std::string_view device_id) {
-    return GetSinkDetails(sink_type).factory(device_id);
-}
 
 } // namespace AudioCore
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h
index 4d0929a6b..2a0d202a7 100644
--- a/src/audio_core/sink_details.h
+++ b/src/audio_core/sink_details.h
@@ -20,17 +20,26 @@ enum class SinkType : u32 {
     Cubeb = 2,
     OpenAL = 3,
     SDL2 = 4,
-
-    NumSinkTypes,
 };
 
-/// Gets the name of a sink type.
-std::string_view GetSinkName(SinkType sink_type);
+struct SinkDetails {
+    using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
+    using ListDevicesFn = std::vector<std::string> (*)();
 
-/// Gets the list of devices for a particular sink identified by the given ID.
-std::vector<std::string> GetDeviceListForSink(SinkType sink_type);
+    /// Type of this sink.
+    SinkType type;
+    /// Name for this sink.
+    std::string_view name;
+    /// A method to call to construct an instance of this type of sink.
+    FactoryFn create_sink;
+    /// A method to call to list available devices.
+    ListDevicesFn list_devices;
+};
 
-/// Creates an audio sink identified by the given device ID.
-std::unique_ptr<Sink> CreateSinkFromID(SinkType sink_type, std::string_view device_id);
+/// Lists all available sink types.
+std::vector<SinkDetails> ListSinks();
+
+/// Gets the details of an sink type.
+const SinkDetails& GetSinkDetails(SinkType input_type);
 
 } // namespace AudioCore
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index a783f121a..8edba366b 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -351,10 +351,6 @@ if(UNIX AND NOT APPLE)
     install(TARGETS citra-qt RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
 endif()
 
-if (NOT APPLE)
-    target_compile_definitions(citra-qt PRIVATE HAS_OPENGL)
-endif()
-
 if (CITRA_USE_PRECOMPILED_HEADERS)
     target_precompile_headers(citra-qt PRIVATE precompiled_headers.h)
 endif()
diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp
index f921c3a62..bf301d41e 100644
--- a/src/citra_qt/configuration/configure_audio.cpp
+++ b/src/citra_qt/configuration/configure_audio.cpp
@@ -21,9 +21,10 @@ ConfigureAudio::ConfigureAudio(bool is_powered_on, QWidget* parent)
     ui->setupUi(this);
 
     ui->output_type_combo_box->clear();
-    for (u32 type = 0; type < static_cast<u32>(AudioCore::SinkType::NumSinkTypes); type++) {
-        ui->output_type_combo_box->addItem(QString::fromUtf8(
-            AudioCore::GetSinkName(static_cast<AudioCore::SinkType>(type)).data()));
+    ui->output_type_combo_box->addItem(tr("Auto"), QVariant::fromValue(AudioCore::SinkType::Auto));
+    for (const auto& sink : AudioCore::ListSinks()) {
+        ui->output_type_combo_box->addItem(QString::fromUtf8(sink.name),
+                                           QVariant::fromValue(sink.type));
     }
 
     ui->emulation_combo_box->setEnabled(!is_powered_on);
@@ -32,9 +33,10 @@ ConfigureAudio::ConfigureAudio(bool is_powered_on, QWidget* parent)
             &ConfigureAudio::SetVolumeIndicatorText);
 
     ui->input_type_combo_box->clear();
-    for (u32 type = 0; type < static_cast<u32>(AudioCore::InputType::NumInputTypes); type++) {
-        ui->input_type_combo_box->addItem(QString::fromUtf8(
-            AudioCore::GetInputName(static_cast<AudioCore::InputType>(type)).data()));
+    ui->input_type_combo_box->addItem(tr("Auto"), QVariant::fromValue(AudioCore::InputType::Auto));
+    for (const auto& input : AudioCore::ListInputs()) {
+        ui->input_type_combo_box->addItem(QString::fromUtf8(input.name),
+                                          QVariant::fromValue(input.type));
     }
 
     ui->volume_label->setVisible(Settings::IsConfiguringGlobal());
@@ -89,8 +91,18 @@ void ConfigureAudio::SetConfiguration() {
 }
 
 void ConfigureAudio::SetOutputTypeFromSinkType() {
-    ui->output_type_combo_box->setCurrentIndex(
-        static_cast<int>(Settings::values.output_type.GetValue()));
+    int new_index = -1;
+
+    for (int index = 0; index < ui->output_type_combo_box->count(); index++) {
+        const auto sink_type =
+            static_cast<AudioCore::SinkType>(ui->output_type_combo_box->itemData(index).toUInt());
+        if (Settings::values.output_type.GetValue() == sink_type) {
+            new_index = index;
+            break;
+        }
+    }
+
+    ui->output_type_combo_box->setCurrentIndex(new_index);
 }
 
 void ConfigureAudio::SetOutputDeviceFromDeviceID() {
@@ -108,8 +120,18 @@ void ConfigureAudio::SetOutputDeviceFromDeviceID() {
 }
 
 void ConfigureAudio::SetInputTypeFromInputType() {
-    ui->input_type_combo_box->setCurrentIndex(
-        static_cast<int>(Settings::values.input_type.GetValue()));
+    int new_index = -1;
+
+    for (int index = 0; index < ui->input_type_combo_box->count(); index++) {
+        const auto input_type =
+            static_cast<AudioCore::InputType>(ui->input_type_combo_box->itemData(index).toUInt());
+        if (Settings::values.input_type.GetValue() == input_type) {
+            new_index = index;
+            break;
+        }
+    }
+
+    ui->input_type_combo_box->setCurrentIndex(new_index);
 }
 
 void ConfigureAudio::SetInputDeviceFromDeviceID() {
@@ -142,30 +164,34 @@ void ConfigureAudio::ApplyConfiguration() {
 
     if (Settings::IsConfiguringGlobal()) {
         Settings::values.output_type =
-            static_cast<AudioCore::SinkType>(ui->output_type_combo_box->currentIndex());
+            static_cast<AudioCore::SinkType>(ui->output_type_combo_box->currentData().toUInt());
         Settings::values.output_device = ui->output_device_combo_box->currentText().toStdString();
         Settings::values.input_type =
-            static_cast<AudioCore::InputType>(ui->input_type_combo_box->currentIndex());
+            static_cast<AudioCore::InputType>(ui->input_type_combo_box->currentData().toUInt());
         Settings::values.input_device = ui->input_device_combo_box->currentText().toStdString();
     }
 }
 
 void ConfigureAudio::UpdateAudioOutputDevices(int sink_index) {
-    auto sink_type = static_cast<AudioCore::SinkType>(sink_index);
+    auto sink_type =
+        static_cast<AudioCore::SinkType>(ui->output_type_combo_box->itemData(sink_index).toUInt());
+    auto& sink_details = AudioCore::GetSinkDetails(sink_type);
 
     ui->output_device_combo_box->clear();
     ui->output_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
 
-    for (const auto& device : AudioCore::GetDeviceListForSink(sink_type)) {
+    for (const auto& device : sink_details.list_devices()) {
         ui->output_device_combo_box->addItem(QString::fromStdString(device));
     }
 }
 
 void ConfigureAudio::UpdateAudioInputDevices(int input_index) {
-    auto input_type = static_cast<AudioCore::InputType>(input_index);
+    auto input_type =
+        static_cast<AudioCore::InputType>(ui->input_type_combo_box->itemData(input_index).toUInt());
+    auto& input_details = AudioCore::GetInputDetails(input_type);
 
 #if defined(__APPLE__)
-    if (input_type != AudioCore::InputType::Null && input_type != AudioCore::InputType::Static) {
+    if (input_details.real) {
         AppleAuthorization::CheckAuthorizationForMicrophone();
     }
 #endif
@@ -173,7 +199,7 @@ void ConfigureAudio::UpdateAudioInputDevices(int input_index) {
     ui->input_device_combo_box->clear();
     ui->input_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
 
-    for (const auto& device : AudioCore::GetDeviceListForInput(input_type)) {
+    for (const auto& device : input_details.list_devices()) {
         ui->input_device_combo_box->addItem(QString::fromStdString(device));
     }
 }
diff --git a/src/common/settings.h b/src/common/settings.h
index ee3cb177f..4f308a7c3 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -441,8 +441,13 @@ struct Values {
     Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"};
 
     // Renderer
-    SwitchableSetting<GraphicsAPI, true> graphics_api{GraphicsAPI::OpenGL, GraphicsAPI::Software,
-                                                      GraphicsAPI::Vulkan, "graphics_api"};
+    SwitchableSetting<GraphicsAPI, true> graphics_api{
+#ifdef HAS_OPENGL
+        GraphicsAPI::OpenGL,
+#else
+        GraphicsAPI::Vulkan,
+#endif
+        GraphicsAPI::Software, GraphicsAPI::Vulkan, "graphics_api"};
     SwitchableSetting<u32> physical_device{0, "physical_device"};
     Setting<bool> use_gles{false, "use_gles"};
     Setting<bool> renderer_debug{false, "renderer_debug"};
diff --git a/src/core/hle/service/mic/mic_u.cpp b/src/core/hle/service/mic/mic_u.cpp
index 04c80521b..fab204fda 100644
--- a/src/core/hle/service/mic/mic_u.cpp
+++ b/src/core/hle/service/mic/mic_u.cpp
@@ -375,8 +375,8 @@ struct MIC_U::Impl {
             mic.reset();
         }
 
-        mic = AudioCore::CreateInputFromID(Settings::values.input_type.GetValue(),
-                                           Settings::values.input_device.GetValue());
+        mic = AudioCore::GetInputDetails(Settings::values.input_type.GetValue())
+                  .create_input(Settings::values.input_device.GetValue());
         if (was_sampling) {
             StartSampling();
         }