WiimoteAudio: Add individual Wiimote audio mixer

Added Platform checks, Incremented state ID

Removed semicolon from PR version comment

Riveting change

Changed Remote Names and References

Updated Wii remote Audio routing to only show
enabled when not using passthrough adapter,
enabling speaker data, and  Cubeb audio backend,
and show enable/disable per Remote when supported
This commit is contained in:
Weston Heard 2026-03-10 14:49:15 -05:00 committed by Weston Heard
parent f074cdb08b
commit 1301cec538
14 changed files with 376 additions and 16 deletions

View File

@ -9,6 +9,7 @@
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Core/Config/MainSettings.h"
#include "Core/System.h"
#ifdef _WIN32
#include <Objbase.h>
@ -34,6 +35,20 @@ void CubebStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_sta
{
}
long CubebStream::WiimoteDataCallback(cubeb_stream* stream, void* user_data,
const void* /*input_buffer*/, void* output_buffer,
long num_frames)
{
const auto* data = static_cast<const WiimoteStreamData*>(user_data);
data->self->m_mixer->MixWiimoteSpeaker(data->wiimote_index,
static_cast<short*>(output_buffer), num_frames);
return num_frames;
}
void CubebStream::WiimoteStateCallback(cubeb_stream* stream, void* user_data, cubeb_state state)
{
}
CubebStream::CubebStream()
#ifdef _WIN32
: m_work_queue("Cubeb Worker")
@ -86,6 +101,51 @@ bool CubebStream::Init()
cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr,
nullptr, &params, std::max(BUFFER_SAMPLES, minimum_latency),
DataCallback, StateCallback, this) == CUBEB_OK;
// Create per-wiimote streams for audio routing (Wii games only, when enabled)
if (return_value && Core::System::GetInstance().IsWii() &&
Config::Get(Config::MAIN_WIIMOTE_AUDIO_ROUTING_ENABLED))
{
static const char* const WIIMOTE_STREAM_NAMES[4] = {
"Dolphin Wiimote 1 Audio", "Dolphin Wiimote 2 Audio", "Dolphin Wiimote 3 Audio",
"Dolphin Wiimote 4 Audio"};
cubeb_stream_params wiimote_params{};
wiimote_params.rate = m_mixer->GetSampleRate();
wiimote_params.channels = 2;
wiimote_params.format = CUBEB_SAMPLE_S16NE;
wiimote_params.layout = CUBEB_LAYOUT_STEREO;
for (std::size_t i = 0; i < m_wiimote_streams.size(); ++i)
{
if (!Config::Get(Config::MAIN_WIIMOTE_AUDIO_OUTPUT_ENABLED[i]))
continue;
const std::string device_id_str =
Config::Get(Config::MAIN_WIIMOTE_AUDIO_OUTPUT_DEVICE[i]);
const cubeb_devid output_devid =
device_id_str.empty() ? nullptr
: static_cast<cubeb_devid>(
CubebUtils::GetOutputDeviceById(device_id_str));
u32 wiimote_min_latency = 0;
cubeb_get_min_latency(m_ctx.get(), &wiimote_params, &wiimote_min_latency);
m_wiimote_stream_data[i] = {this, i};
const int result =
cubeb_stream_init(m_ctx.get(), &m_wiimote_streams[i], WIIMOTE_STREAM_NAMES[i],
nullptr, nullptr, output_devid, &wiimote_params,
std::max(BUFFER_SAMPLES, wiimote_min_latency), WiimoteDataCallback,
WiimoteStateCallback, &m_wiimote_stream_data[i]);
if (result != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Failed to create Cubeb stream for Wiimote {} audio routing",
i + 1);
m_wiimote_streams[i] = nullptr;
}
}
}
}
#ifdef _WIN32
@ -105,9 +165,23 @@ bool CubebStream::SetRunning(bool running)
m_work_queue.PushBlocking([this, running, &return_value] {
#endif
if (running)
{
return_value = cubeb_stream_start(m_stream) == CUBEB_OK;
for (auto& ws : m_wiimote_streams)
{
if (ws)
cubeb_stream_start(ws);
}
}
else
{
return_value = cubeb_stream_stop(m_stream) == CUBEB_OK;
for (auto& ws : m_wiimote_streams)
{
if (ws)
cubeb_stream_stop(ws);
}
}
#ifdef _WIN32
});
#endif
@ -122,6 +196,15 @@ CubebStream::~CubebStream()
#endif
cubeb_stream_stop(m_stream);
cubeb_stream_destroy(m_stream);
for (auto& ws : m_wiimote_streams)
{
if (ws)
{
cubeb_stream_stop(ws);
cubeb_stream_destroy(ws);
ws = nullptr;
}
}
#ifdef _WIN32
if (m_should_couninit)
{

View File

@ -3,6 +3,7 @@
#pragma once
#include <array>
#include <memory>
#include <vector>
@ -31,9 +32,17 @@ public:
static bool IsValid() { return true; }
private:
struct WiimoteStreamData
{
CubebStream* self;
std::size_t wiimote_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::vector<short> m_short_buffer;
std::vector<float> m_floatstereo_buffer;
@ -47,5 +56,9 @@ private:
static long DataCallback(cubeb_stream* stream, void* user_data, const void* /*input_buffer*/,
void* output_buffer, long num_frames);
static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
static long WiimoteDataCallback(cubeb_stream* stream, void* user_data,
const void* /*input_buffer*/, void* output_buffer,
long num_frames);
static void WiimoteStateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
#endif
};

View File

@ -177,6 +177,76 @@ cubeb_devid GetInputDeviceById(std::string_view id)
return device_id;
}
std::vector<std::pair<std::string, std::string>> ListOutputDevices()
{
std::vector<std::pair<std::string, std::string>> devices;
cubeb_device_collection collection;
auto cubeb_ctx = GetContext();
if (!cubeb_ctx)
return devices;
const int r = cubeb_enumerate_devices(cubeb_ctx.get(), CUBEB_DEVICE_TYPE_OUTPUT, &collection);
if (r != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Error listing cubeb output devices");
return devices;
}
for (uint32_t i = 0; i < collection.count; i++)
{
const auto& info = collection.device[i];
if (info.device_id == nullptr)
continue;
if (info.state == CUBEB_DEVICE_STATE_ENABLED)
{
const char* name = (info.friendly_name != nullptr) ? info.friendly_name : info.device_id;
devices.emplace_back(info.device_id, name);
}
}
cubeb_device_collection_destroy(cubeb_ctx.get(), &collection);
return devices;
}
const void* GetOutputDeviceById(std::string_view id)
{
if (id.empty())
return nullptr;
cubeb_device_collection collection;
auto cubeb_ctx = GetContext();
if (!cubeb_ctx)
return nullptr;
const int r = cubeb_enumerate_devices(cubeb_ctx.get(), CUBEB_DEVICE_TYPE_OUTPUT, &collection);
if (r != CUBEB_OK)
{
ERROR_LOG_FMT(AUDIO, "Error enumerating cubeb output devices");
return nullptr;
}
cubeb_devid device_id = nullptr;
for (uint32_t i = 0; i < collection.count; i++)
{
const auto& info = collection.device[i];
if (info.device_id && id.compare(info.device_id) == 0)
{
device_id = info.devid;
break;
}
}
if (device_id == nullptr)
{
WARN_LOG_FMT(AUDIO, "Failed to find selected output device, defaulting to system preferences");
}
cubeb_device_collection_destroy(cubeb_ctx.get(), &collection);
return device_id;
}
CoInitSyncWorker::CoInitSyncWorker([[maybe_unused]] std::string worker_name)
#ifdef _WIN32
: m_work_queue{std::move(worker_name)}

View File

@ -21,6 +21,8 @@ namespace CubebUtils
std::shared_ptr<cubeb> GetContext();
std::vector<std::pair<std::string, std::string>> ListInputDevices();
const void* GetInputDeviceById(std::string_view id);
std::vector<std::pair<std::string, std::string>> ListOutputDevices();
const void* GetOutputDeviceById(std::string_view id);
// Helper used to handle Windows COM library for cubeb WASAPI backend
class CoInitSyncWorker

View File

@ -52,7 +52,8 @@ void Mixer::DoState(PointerWrap& p)
{
m_dma_mixer.DoState(p);
m_streaming_mixer.DoState(p);
m_wiimote_speaker_mixer.DoState(p);
for (auto& mixer : m_wiimote_speaker_mixers)
mixer.DoState(p);
m_skylander_portal_mixer.DoState(p);
for (auto& mixer : m_gba_mixers)
mixer.DoState(p);
@ -169,7 +170,11 @@ std::size_t Mixer::Mix(s16* samples, std::size_t num_samples)
m_dma_mixer.Mix(samples, num_samples);
m_streaming_mixer.Mix(samples, num_samples);
m_wiimote_speaker_mixer.Mix(samples, num_samples);
for (std::size_t i = 0; i < m_wiimote_speaker_mixers.size(); ++i)
{
if (!m_config_wiimote_routing_enabled || !m_config_wiimote_output_enabled[i])
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);
@ -258,10 +263,10 @@ void Mixer::PushStreamingSamples(const s16* samples, std::size_t num_samples)
}
}
void Mixer::PushWiimoteSpeakerSamples(const s16* samples, std::size_t num_samples,
u32 sample_rate_divisor)
void Mixer::PushWiimoteSpeakerSamples(std::size_t wiimote_index, const s16* samples,
std::size_t num_samples, u32 sample_rate_divisor)
{
if (!IsOutputSampleRateValid())
if (!IsOutputSampleRateValid() || wiimote_index >= m_wiimote_speaker_mixers.size())
return;
// Max 20 bytes/speaker report, may be 4-bit ADPCM so multiply by 2
@ -273,7 +278,7 @@ void Mixer::PushWiimoteSpeakerSamples(const s16* samples, std::size_t num_sample
MAX_SPEAKER_SAMPLES);
if (num_samples <= MAX_SPEAKER_SAMPLES)
{
m_wiimote_speaker_mixer.SetInputSampleRateDivisor(sample_rate_divisor);
m_wiimote_speaker_mixers[wiimote_index].SetInputSampleRateDivisor(sample_rate_divisor);
for (std::size_t i = 0; i < num_samples; ++i)
{
@ -281,10 +286,21 @@ void Mixer::PushWiimoteSpeakerSamples(const s16* samples, std::size_t num_sample
samples_stereo[i * 2 + 1] = samples[i];
}
m_wiimote_speaker_mixer.PushSamples(samples_stereo.data(), num_samples);
m_wiimote_speaker_mixers[wiimote_index].PushSamples(samples_stereo.data(), num_samples);
}
}
std::size_t Mixer::MixWiimoteSpeaker(std::size_t wiimote_index, s16* samples,
std::size_t num_samples)
{
if (!samples || wiimote_index >= m_wiimote_speaker_mixers.size())
return 0;
memset(samples, 0, num_samples * 2 * sizeof(s16));
m_wiimote_speaker_mixers[wiimote_index].Mix(samples, num_samples);
return num_samples;
}
void Mixer::PushSkylanderPortalSamples(const u8* samples, std::size_t num_samples)
{
if (!IsOutputSampleRateValid())
@ -342,9 +358,10 @@ void Mixer::SetStreamingVolume(u32 lvolume, u32 rvolume)
std::clamp<u32>(rvolume, 0x00, 0xff));
}
void Mixer::SetWiimoteSpeakerVolume(u32 lvolume, u32 rvolume)
void Mixer::SetWiimoteSpeakerVolume(std::size_t wiimote_index, u32 lvolume, u32 rvolume)
{
m_wiimote_speaker_mixer.SetVolume(lvolume, rvolume);
if (wiimote_index < m_wiimote_speaker_mixers.size())
m_wiimote_speaker_mixers[wiimote_index].SetVolume(lvolume, rvolume);
}
void Mixer::SetGBAVolume(std::size_t device_number, u32 lvolume, u32 rvolume)
@ -433,6 +450,9 @@ void Mixer::RefreshConfig()
m_config_audio_preserve_pitch = Config::Get(Config::MAIN_AUDIO_PRESERVE_PITCH);
m_config_fill_audio_gaps = Config::Get(Config::MAIN_AUDIO_FILL_GAPS);
m_config_audio_buffer_ms = Config::Get(Config::MAIN_AUDIO_BUFFER_SIZE);
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]);
}
void Mixer::MixerFifo::DoState(PointerWrap& p)

View File

@ -29,8 +29,8 @@ public:
// Called from main thread
void PushSamples(const s16* samples, std::size_t num_samples);
void PushStreamingSamples(const s16* samples, std::size_t num_samples);
void PushWiimoteSpeakerSamples(const s16* samples, std::size_t num_samples,
u32 sample_rate_divisor);
void PushWiimoteSpeakerSamples(std::size_t wiimote_index, const s16* samples,
std::size_t num_samples, u32 sample_rate_divisor);
void PushSkylanderPortalSamples(const u8* samples, std::size_t num_samples);
void PushGBASamples(std::size_t device_number, const s16* samples, std::size_t num_samples);
@ -45,7 +45,8 @@ public:
void SetGBAInputSampleRateDivisors(std::size_t device_number, u32 rate_divisor);
void SetStreamingVolume(u32 lvolume, u32 rvolume);
void SetWiimoteSpeakerVolume(u32 lvolume, u32 rvolume);
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);
void StartLogDTKAudio(const std::string& filename);
@ -146,7 +147,11 @@ private:
MixerFifo m_dma_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 32000, false};
MixerFifo m_streaming_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, false};
MixerFifo m_wiimote_speaker_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000, true};
std::array<MixerFifo, 4> m_wiimote_speaker_mixers{
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000, true},
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000, true},
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000, true},
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000, true}};
MixerFifo m_skylander_portal_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 8000, true};
std::array<MixerFifo, 4> m_gba_mixers{MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, true},
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, true},
@ -166,6 +171,8 @@ private:
bool m_config_audio_preserve_pitch;
bool m_config_fill_audio_gaps;
int m_config_audio_buffer_ms;
bool m_config_wiimote_routing_enabled = false;
std::array<bool, 4> m_config_wiimote_output_enabled{};
Config::ConfigChangedCallbackID m_config_changed_callback_id;
};

View File

@ -700,6 +700,21 @@ static std::string GetDefaultTriforceIPRedirections()
const Info<std::string> MAIN_TRIFORCE_IP_REDIRECTIONS{
{System::Main, "Core", "TriforceIPRedirections"}, GetDefaultTriforceIPRedirections()};
// Main.WiimoteAudioRouting
const Info<bool> MAIN_WIIMOTE_AUDIO_ROUTING_ENABLED{
{System::Main, "Core", "WiimoteAudioRoutingEnabled"}, false};
const std::array<Info<bool>, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_ENABLED{
Info<bool>{{System::Main, "Core", "Wiimote1AudioOutputEnabled"}, false},
Info<bool>{{System::Main, "Core", "Wiimote2AudioOutputEnabled"}, false},
Info<bool>{{System::Main, "Core", "Wiimote3AudioOutputEnabled"}, false},
Info<bool>{{System::Main, "Core", "Wiimote4AudioOutputEnabled"}, false}};
const std::array<Info<std::string>, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_DEVICE{
Info<std::string>{{System::Main, "Core", "Wiimote1AudioOutputDevice"}, ""},
Info<std::string>{{System::Main, "Core", "Wiimote2AudioOutputDevice"}, ""},
Info<std::string>{{System::Main, "Core", "Wiimote3AudioOutputDevice"}, ""},
Info<std::string>{{System::Main, "Core", "Wiimote4AudioOutputDevice"}, ""}};
// 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)

View File

@ -396,6 +396,14 @@ extern const std::array<Info<s16>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MI
extern const Info<std::string> MAIN_TRIFORCE_IP_REDIRECTIONS;
// Main.WiimoteAudioRouting
static constexpr std::size_t WIIMOTE_SPEAKER_COUNT = 4;
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;
// GameCube path utility functions
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions

View File

@ -129,12 +129,13 @@ void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan)
auto& system = Core::System::GetInstance();
SoundStream* sound_stream = system.GetSoundStream();
sound_stream->GetMixer()->SetWiimoteSpeakerVolume(l_volume, r_volume);
sound_stream->GetMixer()->SetWiimoteSpeakerVolume(m_wiimote_index, l_volume, r_volume);
// ADPCM sample rate is thought to be x2.(3000 x2 = 6000).
const unsigned int sample_rate = sample_rate_dividend / reg_data.sample_rate;
sound_stream->GetMixer()->PushWiimoteSpeakerSamples(
samples.data(), sample_length, Mixer::FIXED_SAMPLE_RATE_DIVIDEND / (sample_rate * 2));
m_wiimote_index, samples.data(), sample_length,
Mixer::FIXED_SAMPLE_RATE_DIVIDEND / (sample_rate * 2));
}
void SpeakerLogic::Reset()

View File

@ -30,6 +30,7 @@ public:
void DoState(PointerWrap& p);
void SetSpeakerEnabled(bool enabled);
void SetWiimoteIndex(u8 index) { m_wiimote_index = index; }
private:
// Pan is -1.0 to +1.0
@ -75,6 +76,7 @@ private:
ControllerEmu::SettingValue<double> m_speaker_pan_setting;
bool m_speaker_enabled = false;
u8 m_wiimote_index = 0;
};
} // namespace WiimoteEmu

View File

@ -205,6 +205,8 @@ void Wiimote::Reset()
Wiimote::Wiimote(const unsigned int index) : m_index(index), m_bt_device_index(index)
{
m_speaker_logic.SetWiimoteIndex(m_index);
using Translatability = ControllerEmu::Translatability;
// Buttons

View File

@ -95,7 +95,7 @@ struct CompressAndDumpStateArgs
static Common::WorkQueueThreadSP<CompressAndDumpStateArgs> s_compress_and_dump_thread;
// Don't forget to increase this after doing changes on the savestate system
constexpr u32 STATE_VERSION = 181; // Last changed in PR 14400
constexpr u32 STATE_VERSION = 182; // Last changed in PR 14448
// Increase this if the StateExtendedHeader definition changes
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217

View File

@ -17,12 +17,19 @@
#include <QSpacerItem>
#include <QString>
#include <QVBoxLayout>
#include <QWidget>
#include "AudioCommon/AudioCommon.h"
#include "AudioCommon/WASAPIStream.h"
#ifdef HAVE_CUBEB
#include "AudioCommon/CubebUtils.h"
#endif
#include "Core/Config/MainSettings.h"
#include "Core/Config/WiimoteSettings.h"
#include "Core/Core.h"
#include "Core/HW/Wiimote.h"
#include "Core/System.h"
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
#include "DolphinQt/Config/ConfigControls/ConfigChoice.h"
@ -200,11 +207,48 @@ void AudioPane::CreateWidgets()
playback_layout->setRowStretch(4, 1);
playback_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
// Wiimote Audio Routing
#ifdef HAVE_CUBEB
m_wiimote_routing_box = new QGroupBox(tr("Wii Remote Audio Routing"));
auto* wiimote_routing_layout = new QVBoxLayout;
m_wiimote_routing_box->setLayout(wiimote_routing_layout);
m_wiimote_routing_enable = new ConfigBool(tr("Enable Wii Remote Audio Routing"),
Config::MAIN_WIIMOTE_AUDIO_ROUTING_ENABLED);
wiimote_routing_layout->addWidget(m_wiimote_routing_enable);
// Build device list once for all wiimote dropdowns
std::vector<std::pair<QString, QString>> output_devices;
output_devices.emplace_back(tr("Default Device"), QStringLiteral(""));
for (const auto& [id, name] : CubebUtils::ListOutputDevices())
output_devices.emplace_back(QString::fromStdString(name), QString::fromStdString(id));
for (std::size_t i = 0; i < 4; ++i)
{
auto* row_widget = new QWidget;
auto* row_layout = new QHBoxLayout(row_widget);
row_layout->setContentsMargins(0, 0, 0, 0);
m_wiimote_output_enable[i] = new ConfigBool(tr("Wii Remote %1").arg(i + 1),
Config::MAIN_WIIMOTE_AUDIO_OUTPUT_ENABLED[i]);
m_wiimote_output_device[i] =
new ConfigStringChoice(output_devices, Config::MAIN_WIIMOTE_AUDIO_OUTPUT_DEVICE[i]);
row_layout->addWidget(m_wiimote_output_enable[i]);
row_layout->addWidget(m_wiimote_output_device[i], 1);
wiimote_routing_layout->addWidget(row_widget);
}
#endif
auto* const main_vbox_layout = new QVBoxLayout;
main_vbox_layout->addWidget(dsp_box);
main_vbox_layout->addWidget(backend_box);
main_vbox_layout->addWidget(playback_box);
#ifdef HAVE_CUBEB
main_vbox_layout->addWidget(m_wiimote_routing_box);
#endif
m_main_layout = new QHBoxLayout;
m_main_layout->addLayout(main_vbox_layout);
@ -227,6 +271,20 @@ void AudioPane::ConnectWidgets()
connect(m_latency_slider, &QSlider::valueChanged, this,
[this](int value) { m_latency_label->setText(tr("Latency: %1 ms").arg(value)); });
}
#ifdef HAVE_CUBEB
connect(m_wiimote_routing_enable, &ConfigBool::toggled, this,
[this](bool) { UpdateWiimoteRoutingEnabled(); });
for (std::size_t i = 0; i < 4; ++i)
{
connect(m_wiimote_output_enable[i], &ConfigBool::toggled, this,
[this](bool) { UpdateWiimoteRoutingEnabled(); });
}
// Also react to external config changes: wiimote source type, speaker data, BT passthrough.
connect(&Settings::Instance(), &Settings::ConfigChanged, this,
&AudioPane::UpdateWiimoteRoutingEnabled);
UpdateWiimoteRoutingEnabled();
#endif
}
void AudioPane::OnDspChanged()
@ -259,6 +317,10 @@ void AudioPane::OnBackendChanged()
m_volume_slider->setEnabled(AudioCommon::SupportsVolumeChanges(backend));
m_volume_indicator->setEnabled(AudioCommon::SupportsVolumeChanges(backend));
#ifdef HAVE_CUBEB
UpdateWiimoteRoutingEnabled();
#endif
}
void AudioPane::OnEmulationStateChanged(bool running)
@ -283,6 +345,41 @@ void AudioPane::OnEmulationStateChanged(bool running)
#ifdef _WIN32
m_wasapi_device_combo->setEnabled(!running);
#endif
#ifdef HAVE_CUBEB
UpdateWiimoteRoutingEnabled();
#endif
}
void AudioPane::UpdateWiimoteRoutingEnabled()
{
#ifdef HAVE_CUBEB
if (!m_wiimote_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 speaker_enabled = Config::Get(Config::MAIN_WIIMOTE_ENABLE_SPEAKER);
const bool bt_passthrough = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED);
// The entire group requires Cubeb backend, speaker data enabled, no BT passthrough, and
// emulation not running.
const bool group_usable = !running && is_cubeb && speaker_enabled && !bt_passthrough;
m_wiimote_routing_enable->setEnabled(group_usable);
const bool routing_on = group_usable && m_wiimote_routing_enable->isChecked();
for (std::size_t i = 0; i < 4; ++i)
{
const WiimoteSource source = Config::Get(Config::GetInfoForWiimoteSource(static_cast<int>(i)));
const bool is_emulated = source == WiimoteSource::Emulated;
m_wiimote_output_enable[i]->setEnabled(routing_on && is_emulated);
m_wiimote_output_device[i]->setEnabled(routing_on && is_emulated &&
m_wiimote_output_enable[i]->isChecked());
}
#endif
}
void AudioPane::CheckNeedForLatencyControl()
@ -368,4 +465,33 @@ void AudioPane::AddDescriptions()
m_audio_preserve_pitch->SetTitle(tr("Preserve Audio Pitch"));
m_audio_preserve_pitch->SetDescription(tr(TR_PRESERVE_AUDIO_PITCH_DESCRIPTION));
#ifdef HAVE_CUBEB
static const char TR_WIIMOTE_ROUTING_DESCRIPTION[] =
QT_TR_NOOP("Routes each Wii Remote's speaker audio to a separate audio output device. "
"The main audio output continues to work normally; only the Wii Remote speaker "
"audio is redirected. Requires a restart of emulation to take effect."
"<br><br>This setting is disabled when Enable Speaker Data is disabled, a "
"Passthrough a Bluetooth adapter is selected, or an audio backend other than "
"Cubeb is selected."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
static const char TR_WIIMOTE_OUTPUT_ENABLE_DESCRIPTION[] =
QT_TR_NOOP("Enables routing this Wii Remote's speaker audio to a separate output device.");
static const char TR_WIIMOTE_OUTPUT_DEVICE_DESCRIPTION[] =
QT_TR_NOOP("Selects the audio output device for this Wii Remote's speaker audio."
"<br><br>This setting is disabled when this Wii Remote is not set to Emulated "
"Wii Remote.");
if (m_wiimote_routing_box)
{
m_wiimote_routing_enable->SetTitle(tr("Enable Wii Remote Audio Routing"));
m_wiimote_routing_enable->SetDescription(tr(TR_WIIMOTE_ROUTING_DESCRIPTION));
for (std::size_t i = 0; i < 4; ++i)
{
m_wiimote_output_enable[i]->SetDescription(tr(TR_WIIMOTE_OUTPUT_ENABLE_DESCRIPTION));
m_wiimote_output_device[i]->SetTitle(tr("Wii Remote %1").arg(i + 1));
m_wiimote_output_device[i]->SetDescription(tr(TR_WIIMOTE_OUTPUT_DEVICE_DESCRIPTION));
}
}
#endif
}

View File

@ -3,6 +3,8 @@
#pragma once
#include <array>
#include <QWidget>
namespace AudioCommon
@ -16,6 +18,7 @@ class ConfigComplexChoice;
class ConfigRadioBool;
class ConfigSlider;
class ConfigStringChoice;
class QGroupBox;
class QHBoxLayout;
class QLabel;
class QRadioButton;
@ -37,6 +40,8 @@ private:
void OnBackendChanged();
void OnDspChanged();
void UpdateWiimoteRoutingEnabled();
void CheckNeedForLatencyControl();
bool m_latency_control_supported;
@ -68,4 +73,10 @@ private:
ConfigBool* m_audio_fill_gaps;
ConfigBool* m_audio_preserve_pitch;
ConfigBool* m_speed_up_mute_enable;
// Wiimote Audio Routing
QGroupBox* m_wiimote_routing_box = nullptr;
ConfigBool* m_wiimote_routing_enable = nullptr;
std::array<ConfigBool*, 4> m_wiimote_output_enable{};
std::array<ConfigStringChoice*, 4> m_wiimote_output_device{};
};