diff options
| author | JMC47 <JMC4789@gmail.com> | 2026-05-14 18:36:49 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-14 18:36:49 -0400 |
| commit | 604d13e1ccbb7a5dfd9f50d1b3a79ed64b033c59 (patch) | |
| tree | b08e40705d53ce535db8fc28a127f37368b0d501 /Source/Core/AudioCommon | |
| parent | d0d354fbed4999c1c7183d4593bab15601c3d5e9 (diff) | |
| parent | adcfbda2a3436151771c80f654c5c490ad9478a3 (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.cpp | 82 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/CubebStream.h | 13 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/CubebUtils.cpp | 70 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/CubebUtils.h | 2 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/Mixer.cpp | 38 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/Mixer.h | 17 |
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, ¶ms, 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; }; |
