Merge pull request #14880 from ICEREG1992/GBARouting

Integrated GBA Audio Routing
This commit is contained in:
JMC47
2026-10-02 01:58:20 -04:00
committed by GitHub
8 changed files with 237 additions and 3 deletions
+74
View File
@@ -49,6 +49,19 @@ void CubebStream::WiimoteStateCallback(cubeb_stream* stream, void* user_data, cu
{
}
long CubebStream::GBADataCallback(cubeb_stream* stream, void* user_data,
const void* /*input_buffer*/, void* output_buffer,
long num_frames)
{
const auto* data = static_cast<const GBAStreamData*>(user_data);
data->self->m_mixer->MixGBA(data->gba_index, static_cast<short*>(output_buffer), num_frames);
return num_frames;
}
void CubebStream::GBAStateCallback(cubeb_stream* stream, void* user_data, cubeb_state state)
{
}
CubebStream::CubebStream()
#ifdef _WIN32
: m_work_queue("Cubeb Worker")
@@ -145,6 +158,48 @@ bool CubebStream::Init()
}
}
}
// Create per-GBA streams for audio routing when enabled
if (return_value && Config::Get(Config::MAIN_GBA_AUDIO_ROUTING_ENABLED))
{
static const char* const GBA_STREAM_NAMES[4] = {
"Dolphin GBA 1 Audio", "Dolphin GBA 2 Audio", "Dolphin GBA 3 Audio",
"Dolphin GBA 4 Audio"};
cubeb_stream_params gba_params{};
gba_params.rate = m_mixer->GetSampleRate();
gba_params.channels = 2;
gba_params.format = CUBEB_SAMPLE_S16NE;
gba_params.layout = CUBEB_LAYOUT_STEREO;
for (std::size_t i = 0; i < m_gba_streams.size(); ++i)
{
if (!Config::Get(Config::MAIN_GBA_AUDIO_OUTPUT_ENABLED[i]))
continue;
const std::string device_id_str = Config::Get(Config::MAIN_GBA_AUDIO_OUTPUT_DEVICE[i]);
const cubeb_devid output_devid =
device_id_str.empty() ?
nullptr :
static_cast<cubeb_devid>(CubebUtils::GetOutputDeviceById(device_id_str));
u32 gba_min_latency = 0;
cubeb_get_min_latency(m_ctx.get(), &gba_params, &gba_min_latency);
m_gba_stream_data[i] = {this, i};
const int result = cubeb_stream_init(
m_ctx.get(), &m_gba_streams[i], GBA_STREAM_NAMES[i], nullptr, nullptr, output_devid,
&gba_params, std::max(BUFFER_SAMPLES, gba_min_latency), GBADataCallback,
GBAStateCallback, &m_gba_stream_data[i]);
if (result != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Failed to create Cubeb stream for GBA {} audio split", i + 1);
m_gba_streams[i] = nullptr;
}
}
}
}
#ifdef _WIN32
@@ -171,6 +226,11 @@ bool CubebStream::SetRunning(bool running)
if (ws)
cubeb_stream_start(ws);
}
for (auto& gs : m_gba_streams)
{
if (gs)
cubeb_stream_start(gs);
}
}
else
{
@@ -180,6 +240,11 @@ bool CubebStream::SetRunning(bool running)
if (ws)
cubeb_stream_stop(ws);
}
for (auto& gs : m_gba_streams)
{
if (gs)
cubeb_stream_stop(gs);
}
}
#ifdef _WIN32
});
@@ -204,6 +269,15 @@ CubebStream::~CubebStream()
ws = nullptr;
}
}
for (auto& gs : m_gba_streams)
{
if (gs)
{
cubeb_stream_stop(gs);
cubeb_stream_destroy(gs);
gs = nullptr;
}
}
#ifdef _WIN32
if (m_should_couninit)
{
+13
View File
@@ -16,6 +16,8 @@
#include <cubeb/cubeb.h>
#endif
#include "Core/Config/MainSettings.h"
class CubebStream final : public SoundStream
{
#ifdef HAVE_CUBEB
@@ -38,11 +40,19 @@ private:
std::size_t wiimote_index;
};
struct GBAStreamData
{
CubebStream* self;
std::size_t gba_index;
};
bool m_stereo = false;
std::shared_ptr<cubeb> m_ctx;
cubeb_stream* m_stream = nullptr;
std::array<WiimoteStreamData, 4> m_wiimote_stream_data{};
std::array<cubeb_stream*, 4> m_wiimote_streams{};
std::array<GBAStreamData, Config::GBA_SPEAKER_COUNT> m_gba_stream_data{};
std::array<cubeb_stream*, Config::GBA_SPEAKER_COUNT> m_gba_streams{};
std::vector<short> m_short_buffer;
std::vector<float> m_floatstereo_buffer;
@@ -60,5 +70,8 @@ private:
const void* /*input_buffer*/, void* output_buffer,
long num_frames);
static void WiimoteStateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
static long GBADataCallback(cubeb_stream* stream, void* user_data, const void* /*input_buffer*/,
void* output_buffer, long num_frames);
static void GBAStateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
#endif
};
+17 -2
View File
@@ -176,8 +176,11 @@ std::size_t Mixer::Mix(s16* samples, std::size_t num_samples)
m_wiimote_speaker_mixers[i].Mix(samples, num_samples);
}
m_skylander_portal_mixer.Mix(samples, num_samples);
for (auto& mixer : m_gba_mixers)
mixer.Mix(samples, num_samples);
for (std::size_t i = 0; i < m_gba_mixers.size(); ++i)
{
if (!m_config_gba_routing_enabled || !m_config_gba_output_enabled[i])
m_gba_mixers[i].Mix(samples, num_samples);
}
return num_samples;
}
@@ -317,6 +320,15 @@ void Mixer::PushGBASamples(std::size_t device_number, const s16* samples, std::s
}
}
std::size_t Mixer::MixGBA(std::size_t device_number, s16* samples, std::size_t num_samples)
{
if (!samples || device_number >= m_gba_mixers.size())
return 0;
memset(samples, 0, num_samples * 2 * sizeof(s16));
m_gba_mixers[device_number].Mix(samples, num_samples);
return num_samples;
}
void Mixer::SetDMAInputSampleRateDivisor(u32 rate_divisor)
{
m_dma_mixer.SetInputSampleRateDivisor(rate_divisor);
@@ -433,6 +445,9 @@ void Mixer::RefreshConfig()
m_config_wiimote_routing_enabled = Config::Get(Config::MAIN_WIIMOTE_AUDIO_ROUTING_ENABLED);
for (std::size_t i = 0; i < m_config_wiimote_output_enabled.size(); ++i)
m_config_wiimote_output_enabled[i] = Config::Get(Config::MAIN_WIIMOTE_AUDIO_OUTPUT_ENABLED[i]);
m_config_gba_routing_enabled = Config::Get(Config::MAIN_GBA_AUDIO_ROUTING_ENABLED);
for (std::size_t i = 0; i < m_config_gba_output_enabled.size(); ++i)
m_config_gba_output_enabled[i] = Config::Get(Config::MAIN_GBA_AUDIO_OUTPUT_ENABLED[i]);
}
void Mixer::MixerFifo::DoState(PointerWrap& p)
+5 -1
View File
@@ -12,6 +12,7 @@
#include "Common/CommonTypes.h"
#include "Common/Config/Config.h"
#include "Common/Inline.h"
#include "Core/Config/MainSettings.h"
class PointerWrap;
@@ -49,6 +50,7 @@ public:
void SetWiimoteSpeakerVolume(std::size_t wiimote_index, u32 lvolume, u32 rvolume);
std::size_t MixWiimoteSpeaker(std::size_t wiimote_index, s16* samples, std::size_t num_samples);
void SetGBAVolume(std::size_t device_number, u32 lvolume, u32 rvolume);
std::size_t MixGBA(std::size_t device_number, s16* samples, std::size_t num_samples);
void StartLogDTKAudio(const std::string& filename);
void StopLogDTKAudio();
@@ -178,7 +180,7 @@ private:
// GBAs generally use a 65536 sample rate which is not a factor of our FIXED_SAMPLE_RATE_DIVIDEND.
static constexpr u32 GBA_SAMPLE_RATE_DIVIDEND = 0x1000000;
std::array<MixerFifo, 4> m_gba_mixers{
std::array<MixerFifo, Config::GBA_SPEAKER_COUNT> m_gba_mixers{
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
@@ -200,6 +202,8 @@ private:
int m_config_audio_buffer_ms;
bool m_config_wiimote_routing_enabled = false;
std::array<bool, 4> m_config_wiimote_output_enabled{};
bool m_config_gba_routing_enabled = false;
std::array<bool, Config::GBA_SPEAKER_COUNT> m_config_gba_output_enabled{};
Config::ConfigChangedCallbackID m_config_changed_callback_id;
};
+17
View File
@@ -716,6 +716,23 @@ const std::array<Info<std::string>, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OU
Info<std::string>{{System::Main, "Core", "Wiimote3AudioOutputDevice"}, ""},
Info<std::string>{{System::Main, "Core", "Wiimote4AudioOutputDevice"}, ""}};
// Main.GBAAudioRouting
const Info<bool> MAIN_GBA_AUDIO_ROUTING_ENABLED{{System::Main, "Core", "GBAAudioRoutingEnabled"},
false};
const std::array<Info<bool>, GBA_SPEAKER_COUNT> MAIN_GBA_AUDIO_OUTPUT_ENABLED = {
Info<bool>{{System::Main, "Core", "GBA1AudioOutputEnabled"}, false},
Info<bool>{{System::Main, "Core", "GBA2AudioOutputEnabled"}, false},
Info<bool>{{System::Main, "Core", "GBA3AudioOutputEnabled"}, false},
Info<bool>{{System::Main, "Core", "GBA4AudioOutputEnabled"}, false},
};
const std::array<Info<std::string>, GBA_SPEAKER_COUNT> MAIN_GBA_AUDIO_OUTPUT_DEVICE = {
Info<std::string>{{System::Main, "Core", "GBA1AudioOutputDevice"}, ""},
Info<std::string>{{System::Main, "Core", "GBA2AudioOutputDevice"}, ""},
Info<std::string>{{System::Main, "Core", "GBA3AudioOutputDevice"}, ""},
Info<std::string>{{System::Main, "Core", "GBA4AudioOutputDevice"}, ""},
};
// The reason we need this function is because some memory card code
// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii.
DiscIO::Region ToGameCubeRegion(DiscIO::Region region)
+8
View File
@@ -405,6 +405,14 @@ extern const Info<bool> MAIN_WIIMOTE_AUDIO_ROUTING_ENABLED;
extern const std::array<Info<bool>, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_ENABLED;
extern const std::array<Info<std::string>, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_DEVICE;
// Main.GBAAudioRouting
static constexpr std::size_t GBA_SPEAKER_COUNT = 4;
extern const Info<bool> MAIN_GBA_AUDIO_ROUTING_ENABLED;
extern const std::array<Info<bool>, GBA_SPEAKER_COUNT> MAIN_GBA_AUDIO_OUTPUT_ENABLED;
extern const std::array<Info<std::string>, GBA_SPEAKER_COUNT> MAIN_GBA_AUDIO_OUTPUT_DEVICE;
// GameCube path utility functions
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions
@@ -29,6 +29,7 @@
#include "Core/Config/MainSettings.h"
#include "Core/Config/WiimoteSettings.h"
#include "Core/Core.h"
#include "Core/HW/SI/SI_Device.h"
#include "Core/HW/Wiimote.h"
#include "Core/System.h"
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
@@ -242,6 +243,34 @@ void AudioPane::CreateWidgets()
}
#endif
// GBA Audio Routing
#ifdef HAVE_CUBEB
m_gba_routing_box = new QGroupBox(tr("GBA Audio Routing"));
auto* gba_routing_layout = new QVBoxLayout;
m_gba_routing_box->setLayout(gba_routing_layout);
m_gba_routing_enable =
new ConfigBool(tr("Enable GBA Audio Routing"), Config::MAIN_GBA_AUDIO_ROUTING_ENABLED);
gba_routing_layout->addWidget(m_gba_routing_enable);
for (std::size_t i = 0; i < Config::GBA_SPEAKER_COUNT; ++i)
{
auto* row_widget = new QWidget;
auto* row_layout = new QHBoxLayout(row_widget);
row_layout->setContentsMargins(0, 0, 0, 0);
m_gba_output_enable[i] =
new ConfigBool(tr("GBA %1").arg(i + 1), Config::MAIN_GBA_AUDIO_OUTPUT_ENABLED[i]);
m_gba_output_device[i] =
new ConfigStringChoice(output_devices, Config::MAIN_GBA_AUDIO_OUTPUT_DEVICE[i]);
row_layout->addWidget(m_gba_output_enable[i]);
row_layout->addWidget(m_gba_output_device[i], 1);
gba_routing_layout->addWidget(row_widget);
}
#endif
auto* const main_vbox_layout = new QVBoxLayout;
main_vbox_layout->addWidget(dsp_box);
@@ -249,6 +278,7 @@ void AudioPane::CreateWidgets()
main_vbox_layout->addWidget(playback_box);
#ifdef HAVE_CUBEB
main_vbox_layout->addWidget(m_wiimote_routing_box);
main_vbox_layout->addWidget(m_gba_routing_box);
#endif
m_main_layout = new QHBoxLayout;
@@ -276,15 +306,25 @@ void AudioPane::ConnectWidgets()
#ifdef HAVE_CUBEB
connect(m_wiimote_routing_enable, &ConfigBool::toggled, this,
[this](bool) { UpdateWiimoteRoutingEnabled(); });
connect(m_gba_routing_enable, &ConfigBool::toggled, this,
[this](bool) { UpdateGBARoutingEnabled(); });
for (std::size_t i = 0; i < 4; ++i)
{
connect(m_wiimote_output_enable[i], &ConfigBool::toggled, this,
[this](bool) { UpdateWiimoteRoutingEnabled(); });
}
for (std::size_t i = 0; i < Config::GBA_SPEAKER_COUNT; ++i)
{
connect(m_gba_output_enable[i], &ConfigBool::toggled, this,
[this](bool) { UpdateGBARoutingEnabled(); });
}
// Also react to external config changes: wiimote source type, speaker data, BT passthrough.
connect(&Settings::Instance(), &Settings::ConfigChanged, this,
&AudioPane::UpdateWiimoteRoutingEnabled);
connect(&Settings::Instance(), &Settings::ConfigChanged, this,
&AudioPane::UpdateGBARoutingEnabled);
UpdateWiimoteRoutingEnabled();
UpdateGBARoutingEnabled();
#endif
}
@@ -321,6 +361,7 @@ void AudioPane::OnBackendChanged()
#ifdef HAVE_CUBEB
UpdateWiimoteRoutingEnabled();
UpdateGBARoutingEnabled();
#endif
}
@@ -349,6 +390,7 @@ void AudioPane::OnEmulationStateChanged(bool running)
#ifdef HAVE_CUBEB
UpdateWiimoteRoutingEnabled();
UpdateGBARoutingEnabled();
#endif
}
@@ -383,6 +425,33 @@ void AudioPane::UpdateWiimoteRoutingEnabled()
#endif
}
void AudioPane::UpdateGBARoutingEnabled()
{
#ifdef HAVE_CUBEB
if (!m_gba_routing_box)
return;
const bool running = Core::GetState(Core::System::GetInstance()) != Core::State::Uninitialized;
const bool is_cubeb = Config::Get(Config::MAIN_AUDIO_BACKEND) == BACKEND_CUBEB;
const bool group_usable = !running && is_cubeb;
m_gba_routing_enable->setEnabled(group_usable);
const bool routing_on = group_usable && m_gba_routing_enable->isChecked();
for (std::size_t i = 0; i < Config::GBA_SPEAKER_COUNT; ++i)
{
const u32 device_type = Config::Get(Config::GetInfoForSIDevice(static_cast<u32>(i)));
const bool is_gba_connected = static_cast<SerialInterface::SIDevices>(device_type) ==
SerialInterface::SIDEVICE_GC_GBA_EMULATED;
m_gba_output_enable[i]->setEnabled(routing_on && is_gba_connected);
m_gba_output_device[i]->setEnabled(routing_on && is_gba_connected &&
m_gba_output_enable[i]->isChecked());
}
#endif
}
void AudioPane::CheckNeedForLatencyControl()
{
std::vector<std::string> backends = AudioCommon::GetSoundBackends();
@@ -493,5 +562,30 @@ void AudioPane::AddDescriptions()
m_wiimote_output_device[i]->SetDescription(tr(TR_WIIMOTE_OUTPUT_DEVICE_DESCRIPTION));
}
}
static const char TR_GBA_ROUTING_DESCRIPTION[] =
QT_TR_NOOP("Routes each GBA's speaker audio to a separate audio output device. "
"This setting cannot be changed while emulation is active."
"<br><br>This setting is disabled when an audio backend other than "
"Cubeb is selected."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
static const char TR_GBA_OUTPUT_ENABLE_DESCRIPTION[] =
QT_TR_NOOP("Enables routing this GBA's speaker audio to a separate output device.");
static const char TR_GBA_OUTPUT_DEVICE_DESCRIPTION[] =
QT_TR_NOOP("Selects the audio output device for this GBA's speaker audio."
"<br><br>This setting is disabled when this port is not set to GBA "
"(Integrated).");
if (m_gba_routing_box)
{
m_gba_routing_enable->SetTitle(tr("Enable GBA Audio Routing"));
m_gba_routing_enable->SetDescription(tr(TR_GBA_ROUTING_DESCRIPTION));
for (std::size_t i = 0; i < Config::GBA_SPEAKER_COUNT; ++i)
{
m_gba_output_enable[i]->SetDescription(tr(TR_GBA_OUTPUT_ENABLE_DESCRIPTION));
m_gba_output_device[i]->SetTitle(tr("GBA %1").arg(i + 1));
m_gba_output_device[i]->SetDescription(tr(TR_GBA_OUTPUT_DEVICE_DESCRIPTION));
}
}
#endif
}
@@ -7,6 +7,8 @@
#include <QWidget>
#include "Core/Config/MainSettings.h"
namespace AudioCommon
{
enum class DPL2Quality;
@@ -41,6 +43,7 @@ private:
void OnDspChanged();
void UpdateWiimoteRoutingEnabled();
void UpdateGBARoutingEnabled();
void CheckNeedForLatencyControl();
bool m_latency_control_supported;
@@ -79,4 +82,10 @@ private:
ConfigBool* m_wiimote_routing_enable = nullptr;
std::array<ConfigBool*, 4> m_wiimote_output_enable{};
std::array<ConfigStringChoice*, 4> m_wiimote_output_device{};
// GBA Audio Routing
QGroupBox* m_gba_routing_box = nullptr;
ConfigBool* m_gba_routing_enable = nullptr;
std::array<ConfigBool*, Config::GBA_SPEAKER_COUNT> m_gba_output_enable{};
std::array<ConfigStringChoice*, Config::GBA_SPEAKER_COUNT> m_gba_output_device{};
};