This commit is contained in:
Shrub 2026-03-21 11:24:15 -05:00 committed by GitHub
commit e37bcbce42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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{};
};