summaryrefslogtreecommitdiff
path: root/Source/Core/AudioCommon
diff options
context:
space:
mode:
authorJMC47 <JMC4789@gmail.com>2026-05-14 18:36:49 -0400
committerGitHub <noreply@github.com>2026-05-14 18:36:49 -0400
commit604d13e1ccbb7a5dfd9f50d1b3a79ed64b033c59 (patch)
treeb08e40705d53ce535db8fc28a127f37368b0d501 /Source/Core/AudioCommon
parentd0d354fbed4999c1c7183d4593bab15601c3d5e9 (diff)
parentadcfbda2a3436151771c80f654c5c490ad9478a3 (diff)
Merge pull request #14448 from TheShrubMaster/main
AudioCommon: Add individual Wiimote audio mixer
Diffstat (limited to 'Source/Core/AudioCommon')
-rw-r--r--Source/Core/AudioCommon/CubebStream.cpp82
-rw-r--r--Source/Core/AudioCommon/CubebStream.h13
-rw-r--r--Source/Core/AudioCommon/CubebUtils.cpp70
-rw-r--r--Source/Core/AudioCommon/CubebUtils.h2
-rw-r--r--Source/Core/AudioCommon/Mixer.cpp38
-rw-r--r--Source/Core/AudioCommon/Mixer.h17
6 files changed, 208 insertions, 14 deletions
diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp
index 2a04985e3b..adc947b426 100644
--- a/Source/Core/AudioCommon/CubebStream.cpp
+++ b/Source/Core/AudioCommon/CubebStream.cpp
@@ -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,50 @@ 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 +164,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 +195,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)
{
diff --git a/Source/Core/AudioCommon/CubebStream.h b/Source/Core/AudioCommon/CubebStream.h
index db5d14b33e..1c95fd50d8 100644
--- a/Source/Core/AudioCommon/CubebStream.h
+++ b/Source/Core/AudioCommon/CubebStream.h
@@ -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
};
diff --git a/Source/Core/AudioCommon/CubebUtils.cpp b/Source/Core/AudioCommon/CubebUtils.cpp
index fda93ac4c6..85fb44767e 100644
--- a/Source/Core/AudioCommon/CubebUtils.cpp
+++ b/Source/Core/AudioCommon/CubebUtils.cpp
@@ -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)}
diff --git a/Source/Core/AudioCommon/CubebUtils.h b/Source/Core/AudioCommon/CubebUtils.h
index 35e3994e14..1e49b0c8bb 100644
--- a/Source/Core/AudioCommon/CubebUtils.h
+++ b/Source/Core/AudioCommon/CubebUtils.h
@@ -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
diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp
index 1229d8faa1..a57b6024b7 100644
--- a/Source/Core/AudioCommon/Mixer.cpp
+++ b/Source/Core/AudioCommon/Mixer.cpp
@@ -53,7 +53,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);
@@ -254,22 +259,33 @@ 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;
// WiimoteEmu produces host-endian mono samples.
- m_wiimote_speaker_mixer.SetInputSampleRateDivisor(sample_rate_divisor);
+ m_wiimote_speaker_mixers[wiimote_index].SetInputSampleRateDivisor(sample_rate_divisor);
for (const s16 sample : std::span{samples, num_samples})
{
- m_wiimote_speaker_mixer.PushSample(sample, sample);
+ m_wiimote_speaker_mixers[wiimote_index].PushSample(sample, sample);
}
}
+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())
@@ -322,9 +338,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)
@@ -413,6 +430,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)
diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h
index 6e5a52eecd..aca334bcf8 100644
--- a/Source/Core/AudioCommon/Mixer.h
+++ b/Source/Core/AudioCommon/Mixer.h
@@ -30,8 +30,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);
@@ -46,7 +46,8 @@ public:
void SetGBAInputSampleRate(std::size_t device_number, u32 sample_rate);
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);
@@ -166,7 +167,12 @@ private:
MixerFifo m_dma_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 32000};
MixerFifo m_streaming_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000};
- MixerFifo m_wiimote_speaker_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000};
+ std::array<MixerFifo, 4> m_wiimote_speaker_mixers{
+ MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000},
+ MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000},
+ MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000},
+ MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000},
+ };
MixerFifo m_skylander_portal_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 8000};
// GBAs generally use a 65536 sample rate which is not a factor of our FIXED_SAMPLE_RATE_DIVIDEND.
@@ -178,7 +184,6 @@ private:
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
};
-
u32 m_output_sample_rate;
AudioCommon::SurroundDecoder m_surround_decoder;
@@ -193,6 +198,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;
};