From 7005b1929236037f05f08b9332227658c6cfde5a Mon Sep 17 00:00:00 2001 From: Sepalani Date: Thu, 9 May 2024 14:51:30 +0400 Subject: [PATCH] IOS/USB: Emulate Wii Speak using cubeb Based on @noahpistilli (Sketch) PR: https://github.com/dolphin-emu/dolphin/pull/12567 Fixed the Windows support and the haisenbug caused by uninitialized members. Config system integration finalized. --- Source/Core/AudioCommon/CubebUtils.cpp | 87 +++++++ Source/Core/AudioCommon/CubebUtils.h | 5 + Source/Core/Core/Config/MainSettings.cpp | 7 +- Source/Core/Core/Config/MainSettings.h | 1 + .../Core/Core/IOS/USB/Emulated/Microphone.cpp | 212 ++++++++++++++---- .../Core/Core/IOS/USB/Emulated/Microphone.h | 126 +++-------- .../Core/Core/IOS/USB/Emulated/WiiSpeak.cpp | 142 ++++-------- Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h | 37 +-- Source/Core/Core/IOS/USB/Host.cpp | 8 +- .../DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp | 74 ++++-- .../DolphinQt/EmulatedUSB/WiiSpeakWindow.h | 6 +- 11 files changed, 429 insertions(+), 276 deletions(-) diff --git a/Source/Core/AudioCommon/CubebUtils.cpp b/Source/Core/AudioCommon/CubebUtils.cpp index 75b78f687f51..c9de37226166 100644 --- a/Source/Core/AudioCommon/CubebUtils.cpp +++ b/Source/Core/AudioCommon/CubebUtils.cpp @@ -82,3 +82,90 @@ std::shared_ptr CubebUtils::GetContext() weak = shared = {ctx, DestroyContext}; return shared; } + +std::vector> CubebUtils::ListInputDevices() +{ + std::vector> devices; + + cubeb_device_collection collection; + auto cubeb_ctx = CubebUtils::GetContext(); + int r = cubeb_enumerate_devices(cubeb_ctx.get(), CUBEB_DEVICE_TYPE_INPUT, &collection); + + if (r != CUBEB_OK) + { + ERROR_LOG_FMT(AUDIO, "Error listing cubeb input devices"); + return devices; + } + + INFO_LOG_FMT(AUDIO, "Listing cubeb input devices:"); + for (uint32_t i = 0; i < collection.count; i++) + { + auto& info = collection.device[i]; + auto& device_state = info.state; + const char* state_name = [device_state] { + switch (device_state) + { + case CUBEB_DEVICE_STATE_DISABLED: + return "disabled"; + case CUBEB_DEVICE_STATE_UNPLUGGED: + return "unplugged"; + case CUBEB_DEVICE_STATE_ENABLED: + return "enabled"; + default: + return "unknown?"; + } + }(); + + INFO_LOG_FMT(AUDIO, + "[{}] Device ID: {}\n" + "\tName: {}\n" + "\tGroup ID: {}\n" + "\tVendor: {}\n" + "\tState: {}", + i, info.device_id, info.friendly_name, info.group_id, + (info.vendor_name == nullptr) ? "(null)" : info.vendor_name, state_name); + if (info.state == CUBEB_DEVICE_STATE_ENABLED) + { + devices.emplace_back(info.device_id, info.friendly_name); + } + } + + cubeb_device_collection_destroy(cubeb_ctx.get(), &collection); + + return devices; +} + +cubeb_devid CubebUtils::GetInputDeviceById(const std::string& id) +{ + if (id.empty()) + return nullptr; + + cubeb_device_collection collection; + auto cubeb_ctx = CubebUtils::GetContext(); + int r = cubeb_enumerate_devices(cubeb_ctx.get(), CUBEB_DEVICE_TYPE_INPUT, &collection); + + if (r != CUBEB_OK) + { + ERROR_LOG_FMT(AUDIO, "Error enumerating cubeb input devices"); + return nullptr; + } + + cubeb_devid device_id = nullptr; + for (uint32_t i = 0; i < collection.count; i++) + { + auto& info = collection.device[i]; + if (id.compare(info.device_id) == 0) + { + device_id = info.devid; + break; + } + } + if (device_id == nullptr) + { + WARN_LOG_FMT(AUDIO, "Failed to find selected input device, defaulting to system preferences"); + } + + cubeb_device_collection_destroy(cubeb_ctx.get(), &collection); + + return device_id; +} diff --git a/Source/Core/AudioCommon/CubebUtils.h b/Source/Core/AudioCommon/CubebUtils.h index f0effc2e8f49..148a136c1b60 100644 --- a/Source/Core/AudioCommon/CubebUtils.h +++ b/Source/Core/AudioCommon/CubebUtils.h @@ -5,10 +5,15 @@ #include #include +#include +#include +#include struct cubeb; namespace CubebUtils { std::shared_ptr GetContext(); +std::vector> ListInputDevices(); +const void* GetInputDeviceById(const std::string& id); } // namespace CubebUtils diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 56ed1676c1c5..74363e5133c3 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -575,8 +575,11 @@ const Info MAIN_EMULATE_INFINITY_BASE{ const Info MAIN_EMULATE_WII_SPEAK{{System::Main, "EmulatedUSBDevices", "EmulateWiiSpeak"}, false}; -const Info MAIN_WII_SPEAK_MICROPHONE{{System::Main, "General", "WiiSpeakMicrophone"}, - ""}; +const Info MAIN_WII_SPEAK_MICROPHONE{ + {System::Main, "EmulatedUSBDevices", "WiiSpeakMicrophone"}, ""}; + +const Info MAIN_WII_SPEAK_CONNECTED{ + {System::Main, "EmulatedUSBDevices", "WiiSpeakConnected"}, false}; // 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. diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 18eba5b15cc7..dae372b15144 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -356,6 +356,7 @@ extern const Info MAIN_EMULATE_SKYLANDER_PORTAL; extern const Info MAIN_EMULATE_INFINITY_BASE; extern const Info MAIN_EMULATE_WII_SPEAK; extern const Info MAIN_WII_SPEAK_MICROPHONE; +extern const Info MAIN_WII_SPEAK_CONNECTED; // GameCube path utility functions diff --git a/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp b/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp index 3c7fe78d5c6c..e7a7e63232f3 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp @@ -3,89 +3,215 @@ #include "Core/IOS/USB/Emulated/Microphone.h" +#include + +#include + +#include "AudioCommon/CubebUtils.h" +#include "Common/Event.h" +#include "Common/Logging/Log.h" +#include "Common/ScopeGuard.h" #include "Common/Swap.h" +#include "Core/Config/MainSettings.h" -#include +#ifdef _WIN32 +#include +#endif namespace IOS::HLE::USB { -std::vector Microphone::ListDevices() +Microphone::Microphone() +{ +#ifdef _WIN32 + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); + auto result = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); + m_coinit_success = result == S_OK; + m_should_couninit = result == S_OK || result == S_FALSE; + }); + sync_event.Wait(); +#endif + + StreamInit(); +} + +Microphone::~Microphone() { - std::vector devices{}; - const ALchar* pDeviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); - while (*pDeviceList) + StreamTerminate(); + +#ifdef _WIN32 + if (m_should_couninit) { - devices.emplace_back(pDeviceList); - pDeviceList += strlen(pDeviceList) + 1; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); + m_should_couninit = false; + CoUninitialize(); + }); + sync_event.Wait(); } - - return devices; + m_coinit_success = false; +#endif } -int Microphone::OpenMicrophone() +void Microphone::StreamInit() { - m_device = alcCaptureOpenDevice(nullptr, SAMPLING_RATE, AL_FORMAT_MONO16, BUFFER_SIZE); - m_dsp_data.resize(BUFFER_SIZE, 0); - m_temp_buffer.resize(BUFFER_SIZE, 0); - return static_cast(alcGetError(m_device)); +#ifdef _WIN32 + if (!m_coinit_success) + { + ERROR_LOG_FMT(IOS_USB, "Failed to init Wii Speak stream"); + return; + } + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + m_cubeb_ctx = CubebUtils::GetContext(); +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif + + // TODO: Not here but rather inside the WiiSpeak device if possible? + StreamStart(); } -int Microphone::StartCapture() +void Microphone::StreamTerminate() { - alcCaptureStart(m_device); - return static_cast(alcGetError(m_device)); + StopStream(); + + if (m_cubeb_ctx) + { +#ifdef _WIN32 + if (!m_coinit_success) + return; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + m_cubeb_ctx.reset(); +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif + } } -void Microphone::StopCapture() +static void state_callback(cubeb_stream* stream, void* user_data, cubeb_state state) { - alcCaptureStop(m_device); } -void Microphone::PerformAudioCapture() +void Microphone::StreamStart() { - m_num_of_samples = BUFFER_SIZE / 2; - - ALCint samples_in{}; - alcGetIntegerv(m_device, ALC_CAPTURE_SAMPLES, 1, &samples_in); - m_num_of_samples = std::min(m_num_of_samples, static_cast(samples_in)); + if (!m_cubeb_ctx) + return; - if (m_num_of_samples == 0) +#ifdef _WIN32 + if (!m_coinit_success) return; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + + cubeb_stream_params params{}; + params.format = CUBEB_SAMPLE_S16LE; + params.rate = SAMPLING_RATE; + params.channels = 1; + params.layout = CUBEB_LAYOUT_MONO; + + u32 minimum_latency; + if (cubeb_get_min_latency(m_cubeb_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) + { + WARN_LOG_FMT(IOS_USB, "Error getting minimum latency"); + } - alcCaptureSamples(m_device, m_dsp_data.data(), m_num_of_samples); + cubeb_devid input_device = + CubebUtils::GetInputDeviceById(Config::Get(Config::MAIN_WII_SPEAK_MICROPHONE)); + if (cubeb_stream_init(m_cubeb_ctx.get(), &m_cubeb_stream, "Dolphin Emulated Wii Speak", + input_device, ¶ms, nullptr, nullptr, + std::max(16, minimum_latency), DataCallback, state_callback, + this) != CUBEB_OK) + { + ERROR_LOG_FMT(IOS_USB, "Error initializing cubeb stream"); + return; + } + + if (cubeb_stream_start(m_cubeb_stream) != CUBEB_OK) + { + ERROR_LOG_FMT(IOS_USB, "Error starting cubeb stream"); + return; + } + + INFO_LOG_FMT(IOS_USB, "started cubeb stream"); +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif } -void Microphone::ByteSwap(const void* src, void* dst) const +void Microphone::StopStream() { - *static_cast(dst) = Common::swap16(*static_cast(src)); + if (m_cubeb_stream) + { +#ifdef _WIN32 + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + if (cubeb_stream_stop(m_cubeb_stream) != CUBEB_OK) + ERROR_LOG_FMT(IOS_USB, "Error stopping cubeb stream"); + cubeb_stream_destroy(m_cubeb_stream); + m_cubeb_stream = nullptr; +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif + } } -void Microphone::GetSoundData() +long Microphone::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, + void* /*output_buffer*/, long nframes) { - if (m_num_of_samples == 0) - return; + auto* mic = static_cast(user_data); - u8* ptr = const_cast(m_temp_buffer.data()); - // Convert LE to BE - for (u32 i = 0; i < m_num_of_samples; i++) + std::lock_guard lk(mic->m_ring_lock); + + const s16* buff_in = static_cast(input_buffer); + for (long i = 0; i < nframes; i++) { - for (u32 indchan = 0; indchan < 1; indchan++) - { - const u32 curindex = (i * 2) + indchan * (16 / 8); - ByteSwap(m_dsp_data.data() + curindex, ptr + curindex); - } + mic->m_stream_buffer[mic->m_stream_wpos] = Common::swap16(buff_in[i]); + mic->m_stream_wpos = (mic->m_stream_wpos + 1) % mic->STREAM_SIZE; } - m_rbuf_dsp.write_bytes(ptr, m_num_of_samples * 2); + mic->m_samples_avail += nframes; + if (mic->m_samples_avail > mic->STREAM_SIZE) + { + mic->m_samples_avail = 0; + } + + return nframes; } void Microphone::ReadIntoBuffer(u8* dst, u32 size) { - m_rbuf_dsp.read_bytes(dst, size); + std::lock_guard lk(m_ring_lock); + + if (m_samples_avail >= BUFF_SIZE_SAMPLES) + { + u8* last_buffer = reinterpret_cast(&m_stream_buffer[m_stream_rpos]); + std::memcpy(dst, static_cast(last_buffer), size); + + m_samples_avail -= BUFF_SIZE_SAMPLES; + + m_stream_rpos += BUFF_SIZE_SAMPLES; + m_stream_rpos %= STREAM_SIZE; + } } bool Microphone::HasData() const { - return m_num_of_samples != 0; + return m_samples_avail > 0 && Config::Get(Config::MAIN_WII_SPEAK_CONNECTED); } } // namespace IOS::HLE::USB diff --git a/Source/Core/Core/IOS/USB/Emulated/Microphone.h b/Source/Core/Core/IOS/USB/Emulated/Microphone.h index f5acc52e9fbb..9f4cee4523a5 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Microphone.h +++ b/Source/Core/Core/IOS/USB/Emulated/Microphone.h @@ -3,111 +3,55 @@ #pragma once -#include -#include - -#include -#include +#include +#include +#include #include "Common/CommonTypes.h" +#include "Common/WorkQueueThread.h" + +struct cubeb; +struct cubeb_stream; namespace IOS::HLE::USB { -template -class simple_ringbuf -{ -public: - simple_ringbuf() { m_container.resize(S); } - - bool has_data() const { return m_used != 0; } - - u32 read_bytes(u8* buf, const u32 size) - { - u32 to_read = size > m_used ? m_used : size; - if (!to_read) - return 0; - - u8* data = m_container.data(); - u32 new_tail = m_tail + to_read; - - if (new_tail >= S) - { - u32 first_chunk_size = S - m_tail; - std::memcpy(buf, data + m_tail, first_chunk_size); - std::memcpy(buf + first_chunk_size, data, to_read - first_chunk_size); - m_tail = (new_tail - S); - } - else - { - std::memcpy(buf, data + m_tail, to_read); - m_tail = new_tail; - } - - m_used -= to_read; - - return to_read; - } - - void write_bytes(const u8* buf, const u32 size) - { - if (u32 over_size = m_used + size; over_size > S) - { - m_tail += (over_size - S); - if (m_tail > S) - m_tail -= S; - - m_used = S; - } - else - { - m_used = over_size; - } - - u8* data = m_container.data(); - u32 new_head = m_head + size; - - if (new_head >= S) - { - u32 first_chunk_size = S - m_head; - std::memcpy(data + m_head, buf, first_chunk_size); - std::memcpy(data, buf + first_chunk_size, size - first_chunk_size); - m_head = (new_head - S); - } - else - { - std::memcpy(data + m_head, buf, size); - m_head = new_head; - } - } - -protected: - std::vector m_container; - u32 m_head = 0, m_tail = 0, m_used = 0; -}; - class Microphone final { public: - static std::vector ListDevices(); + Microphone(); + ~Microphone(); - int OpenMicrophone(); - int StartCapture(); - void StopCapture(); - void PerformAudioCapture(); - void GetSoundData(); - void ReadIntoBuffer(u8* dst, u32 size); bool HasData() const; + void ReadIntoBuffer(u8* dst, u32 size); private: - void ByteSwap(const void* src, void* dst) const; + static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, + void* output_buffer, long nframes); + + void StreamInit(); + void StreamTerminate(); + void StreamStart(); + void StopStream(); static constexpr u32 SAMPLING_RATE = 8000; static constexpr u32 BUFFER_SIZE = SAMPLING_RATE / 2; - - ALCdevice* m_device; - u32 m_num_of_samples{}; - std::vector m_dsp_data{}; - std::vector m_temp_buffer{}; - simple_ringbuf m_rbuf_dsp; + static constexpr u32 BUFF_SIZE_SAMPLES = 16; + static constexpr u32 STREAM_SIZE = BUFF_SIZE_SAMPLES * 500; + + std::array m_stream_buffer{}; + int m_stream_wpos = 0; + int m_stream_rpos = 0; + int m_samples_avail = 0; + + std::mutex m_ring_lock; + std::shared_ptr m_cubeb_ctx = nullptr; + cubeb_stream* m_cubeb_stream = nullptr; + +#ifdef _WIN32 + Common::WorkQueueThread> m_work_queue{ + "Wii Speak Worker", [](const std::function& func) { func(); }}; + bool m_coinit_success = false; + bool m_should_couninit = false; +#endif }; } // namespace IOS::HLE::USB diff --git a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp index e3003b0bd313..d2417a54fd11 100644 --- a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp @@ -3,74 +3,17 @@ #include "Core/IOS/USB/Emulated/WiiSpeak.h" +#include "Core/Config/MainSettings.h" #include "Core/HW/Memmap.h" - namespace IOS::HLE::USB { -WiiSpeak::WiiSpeak(IOS::HLE::EmulationKernel& ios, const std::string& device_name) : m_ios(ios) +WiiSpeak::WiiSpeak(IOS::HLE::EmulationKernel& ios) : m_ios(ios) { - m_vid = 0x57E; - m_pid = 0x0308; - m_id = (u64(m_vid) << 32 | u64(m_pid) << 16 | u64(9) << 8 | u64(1)); - m_device_descriptor = - DeviceDescriptor{0x12, 0x1, 0x200, 0, 0, 0, 0x10, 0x57E, 0x0308, 0x0214, 0x1, 0x2, 0x0, 0x1}; - m_config_descriptor.emplace_back(ConfigDescriptor{0x9, 0x2, 0x0030, 0x1, 0x1, 0x0, 0x80, 0x32}); - m_interface_descriptor.emplace_back( - InterfaceDescriptor{0x9, 0x4, 0x0, 0x0, 0x0, 0xFF, 0xFF, 0xFF, 0x0}); - m_interface_descriptor.emplace_back( - InterfaceDescriptor{0x9, 0x4, 0x0, 0x01, 0x03, 0xFF, 0xFF, 0xFF, 0x0}); - m_endpoint_descriptor.emplace_back(EndpointDescriptor{0x7, 0x5, 0x81, 0x1, 0x0020, 0x1}); - m_endpoint_descriptor.emplace_back(EndpointDescriptor{0x7, 0x5, 0x2, 0x2, 0x0020, 0}); - m_endpoint_descriptor.emplace_back(EndpointDescriptor{0x7, 0x5, 0x3, 0x1, 0x0040, 1}); - - m_microphone = Microphone(); - if (m_microphone.OpenMicrophone() != 0) - { - ERROR_LOG_FMT(IOS_USB, "Error opening the microphone."); - b_is_mic_connected = false; - return; - } - - if (m_microphone.StartCapture() != 0) - { - ERROR_LOG_FMT(IOS_USB, "Error starting captures."); - b_is_mic_connected = false; - return; - } - - m_microphone_thread = std::thread([this] { - u64 timeout{}; - constexpr u64 TIMESTEP = 256ull * 1'000'000ull / 48000ull; - while (true) - { - if (m_shutdown_event.WaitFor(std::chrono::microseconds{timeout})) - return; - - std::lock_guard lg(m_mutex); - timeout = TIMESTEP - (std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count() % - TIMESTEP); - m_microphone.PerformAudioCapture(); - m_microphone.GetSoundData(); - } - }); + m_id = u64(m_vid) << 32 | u64(m_pid) << 16 | u64(9) << 8 | u64(1); } -WiiSpeak::~WiiSpeak() -{ - { - std::lock_guard lg(m_mutex); - if (!m_microphone_thread.joinable()) - return; - - m_shutdown_event.Set(); - } - - m_microphone_thread.join(); - m_microphone.StopCapture(); -} +WiiSpeak::~WiiSpeak() = default; DeviceDescriptor WiiSpeak::GetDeviceDescriptor() const { @@ -98,6 +41,8 @@ bool WiiSpeak::Attach() return true; DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening device", m_vid, m_pid); + if (!m_microphone) + m_microphone = std::make_unique(); m_device_attached = true; return true; } @@ -147,14 +92,17 @@ int WiiSpeak::SubmitTransfer(std::unique_ptr cmd) m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value, cmd->index, cmd->length); - if (!b_is_mic_connected) - return IPC_ENOENT; + // Without a proper way to reconnect the emulated Wii Speak, + // this error after being raised prevents some games to use the microphone later. + // + // if (!IsMicrophoneConnected()) + // return IPC_ENOENT; switch (cmd->request_type << 8 | cmd->request) { case USBHDR(DIR_DEVICE2HOST, TYPE_STANDARD, REC_INTERFACE, REQUEST_GET_INTERFACE): { - std::array data{1}; + const std::array data{1}; cmd->FillBuffer(data.data(), 1); cmd->ScheduleTransferCompletion(1, 100); break; @@ -169,14 +117,14 @@ int WiiSpeak::SubmitTransfer(std::unique_ptr cmd) { if (!init) { - std::array data{0}; + const std::array data{0}; cmd->FillBuffer(data.data(), 1); m_ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS); init = true; } else { - std::array data{1}; + const std::array data{1}; cmd->FillBuffer(data.data(), 1); m_ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS); } @@ -196,22 +144,24 @@ int WiiSpeak::SubmitTransfer(std::unique_ptr cmd) } return IPC_SUCCESS; -}; +} + int WiiSpeak::SubmitTransfer(std::unique_ptr cmd) { m_ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS); return IPC_SUCCESS; -}; +} + int WiiSpeak::SubmitTransfer(std::unique_ptr cmd) { m_ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS); return IPC_SUCCESS; -}; +} int WiiSpeak::SubmitTransfer(std::unique_ptr cmd) { - if (!b_is_mic_connected) - return IPC_ENOENT; + // if (!IsMicrophoneConnected()) + // return IPC_ENOENT; auto& system = m_ios.GetSystem(); auto& memory = system.GetMemory(); @@ -222,17 +172,16 @@ int WiiSpeak::SubmitTransfer(std::unique_ptr cmd) ERROR_LOG_FMT(IOS_USB, "Wii Speak command invalid"); return IPC_EINVAL; } - - if (cmd->endpoint == 0x81 && m_microphone.HasData()) - m_microphone.ReadIntoBuffer(packets, cmd->length); + if (cmd->endpoint == 0x81 && m_microphone && m_microphone->HasData()) + m_microphone->ReadIntoBuffer(packets, cmd->length); // Anything more causes the visual cue to not appear. // Anything less is more choppy audio. - cmd->ScheduleTransferCompletion(IPC_SUCCESS, 20000); + cmd->ScheduleTransferCompletion(IPC_SUCCESS, 2500); return IPC_SUCCESS; -}; +} -void WiiSpeak::SetRegister(std::unique_ptr& cmd) +void WiiSpeak::SetRegister(const std::unique_ptr& cmd) { auto& system = m_ios.GetSystem(); auto& memory = system.GetMemory(); @@ -243,21 +192,21 @@ void WiiSpeak::SetRegister(std::unique_ptr& cmd) switch (reg) { case SAMPLER_STATE: - sampler.sample_on = !!arg1; + m_sampler.sample_on = !!arg1; break; case SAMPLER_FREQ: switch (arg1) { case FREQ_8KHZ: - sampler.freq = 8000; + m_sampler.freq = 8000; break; case FREQ_11KHZ: - sampler.freq = 11025; + m_sampler.freq = 11025; break; case FREQ_RESERVED: case FREQ_16KHZ: default: - sampler.freq = 16000; + m_sampler.freq = 16000; break; } break; @@ -265,28 +214,28 @@ void WiiSpeak::SetRegister(std::unique_ptr& cmd) switch (arg1 & ~0x300) { case GAIN_00dB: - sampler.gain = 0; + m_sampler.gain = 0; break; case GAIN_15dB: - sampler.gain = 15; + m_sampler.gain = 15; break; case GAIN_30dB: - sampler.gain = 30; + m_sampler.gain = 30; break; case GAIN_36dB: default: - sampler.gain = 36; + m_sampler.gain = 36; break; } break; case EC_STATE: - sampler.ec_reset = !!arg1; + m_sampler.ec_reset = !!arg1; break; case SP_STATE: switch (arg1) { case SP_ENABLE: - sampler.sp_on = arg2 == 0; + m_sampler.sp_on = arg2 == 0; break; case SP_SIN: case SP_SOUT: @@ -295,12 +244,12 @@ void WiiSpeak::SetRegister(std::unique_ptr& cmd) } break; case SAMPLER_MUTE: - sampler.mute = !!arg1; + m_sampler.mute = !!arg1; break; } } -void WiiSpeak::GetRegister(std::unique_ptr& cmd) +void WiiSpeak::GetRegister(const std::unique_ptr& cmd) const { auto& system = m_ios.GetSystem(); auto& memory = system.GetMemory(); @@ -311,10 +260,10 @@ void WiiSpeak::GetRegister(std::unique_ptr& cmd) switch (reg) { case SAMPLER_STATE: - memory.Write_U16(sampler.sample_on ? 1 : 0, arg1); + memory.Write_U16(m_sampler.sample_on ? 1 : 0, arg1); break; case SAMPLER_FREQ: - switch (sampler.freq) + switch (m_sampler.freq) { case 8000: memory.Write_U16(FREQ_8KHZ, arg1); @@ -329,7 +278,7 @@ void WiiSpeak::GetRegister(std::unique_ptr& cmd) } break; case SAMPLER_GAIN: - switch (sampler.gain) + switch (m_sampler.gain) { case 0: memory.Write_U16(0x300 | GAIN_00dB, arg1); @@ -347,7 +296,7 @@ void WiiSpeak::GetRegister(std::unique_ptr& cmd) } break; case EC_STATE: - memory.Write_U16(sampler.ec_reset ? 1 : 0, arg1); + memory.Write_U16(m_sampler.ec_reset ? 1 : 0, arg1); break; case SP_STATE: switch (memory.Read_U16(arg1)) @@ -365,8 +314,13 @@ void WiiSpeak::GetRegister(std::unique_ptr& cmd) } break; case SAMPLER_MUTE: - memory.Write_U16(sampler.mute ? 1 : 0, arg1); + memory.Write_U16(m_sampler.mute ? 1 : 0, arg1); break; } } + +bool WiiSpeak::IsMicrophoneConnected() const +{ + return Config::Get(Config::MAIN_WII_SPEAK_CONNECTED); +} } // namespace IOS::HLE::USB diff --git a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h index e7f044bce2ea..efa12e0eb530 100644 --- a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h +++ b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h @@ -4,12 +4,9 @@ #pragma once #include -#include -#include #include #include "Common/CommonTypes.h" -#include "Common/Event.h" #include "Core/IOS/USB/Common.h" #include "Core/IOS/USB/Emulated/Microphone.h" #include "Core/System.h" @@ -19,8 +16,9 @@ namespace IOS::HLE::USB class WiiSpeak final : public Device { public: - WiiSpeak(EmulationKernel& ios, const std::string& device_name); + WiiSpeak(EmulationKernel& ios); ~WiiSpeak(); + DeviceDescriptor GetDeviceDescriptor() const override; std::vector GetConfigurations() const override; std::vector GetInterfaces(u8 config) const override; @@ -47,7 +45,7 @@ class WiiSpeak final : public Device bool sp_on; }; - WSState sampler{}; + WSState m_sampler{}; enum Registers { @@ -75,23 +73,26 @@ class WiiSpeak final : public Device SP_RIN = 0x200d }; - void GetRegister(std::unique_ptr& cmd); - void SetRegister(std::unique_ptr& cmd); + void GetRegister(const std::unique_ptr& cmd) const; + void SetRegister(const std::unique_ptr& cmd); + bool IsMicrophoneConnected() const; EmulationKernel& m_ios; - u16 m_vid = 0; - u16 m_pid = 0; + const u16 m_vid = 0x57E; + const u16 m_pid = 0x0308; u8 m_active_interface = 0; bool m_device_attached = false; bool init = false; - bool b_is_mic_connected = true; - Microphone m_microphone; - DeviceDescriptor m_device_descriptor{}; - std::vector m_config_descriptor; - std::vector m_interface_descriptor; - std::vector m_endpoint_descriptor; - std::thread m_microphone_thread; - std::mutex m_mutex; - Common::Event m_shutdown_event; + std::unique_ptr m_microphone{}; + const DeviceDescriptor m_device_descriptor{0x12, 0x1, 0x200, 0, 0, 0, 0x10, + 0x57E, 0x0308, 0x0214, 0x1, 0x2, 0x0, 0x1}; + const std::vector m_config_descriptor{ + {0x9, 0x2, 0x0030, 0x1, 0x1, 0x0, 0x80, 0x32}}; + const std::vector m_interface_descriptor{ + {0x9, 0x4, 0x0, 0x0, 0x0, 0xFF, 0xFF, 0xFF, 0x0}, + {0x9, 0x4, 0x0, 0x01, 0x03, 0xFF, 0xFF, 0xFF, 0x0}}; + const std::vector m_endpoint_descriptor{{0x7, 0x5, 0x81, 0x1, 0x0020, 0x1}, + {0x7, 0x5, 0x2, 0x2, 0x0020, 0}, + {0x7, 0x5, 0x3, 0x1, 0x0040, 1}}; }; } // namespace IOS::HLE::USB diff --git a/Source/Core/Core/IOS/USB/Host.cpp b/Source/Core/Core/IOS/USB/Host.cpp index 330000ef74a5..25b0fe6b4fd9 100644 --- a/Source/Core/Core/IOS/USB/Host.cpp +++ b/Source/Core/Core/IOS/USB/Host.cpp @@ -197,9 +197,11 @@ void USBHost::AddEmulatedDevices(std::set& new_devices, DeviceChangeHooks& auto infinity_base = std::make_unique(GetEmulationKernel(), "Infinity Base"); CheckAndAddDevice(std::move(infinity_base), new_devices, hooks, always_add_hooks); } - - auto wii_speak = std::make_unique(GetEmulationKernel(), "Wii Speak"); - CheckAndAddDevice(std::move(wii_speak), new_devices, hooks, always_add_hooks); + if (Config::Get(Config::MAIN_EMULATE_WII_SPEAK) && !NetPlay::IsNetPlayRunning()) + { + auto wii_speak = std::make_unique(GetEmulationKernel()); + CheckAndAddDevice(std::move(wii_speak), new_devices, hooks, always_add_hooks); + } } void USBHost::CheckAndAddDevice(std::unique_ptr device, std::set& new_devices, diff --git a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp index 1ecea4b16345..dc4680dfbd14 100644 --- a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp +++ b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp @@ -3,33 +3,22 @@ #include "DolphinQt/EmulatedUSB/WiiSpeakWindow.h" -#include - #include #include -#include -#include #include -#include -#include -#include -#include -#include +#include #include #include -#include "Common/IOFile.h" - +#include "AudioCommon/CubebUtils.h" #include "Core/Config/MainSettings.h" #include "Core/Core.h" -#include "Core/IOS/USB/Emulated/Microphone.h" #include "Core/System.h" - -#include "DolphinQt/QtUtils/DolphinFileDialog.h" #include "DolphinQt/Settings.h" WiiSpeakWindow::WiiSpeakWindow(QWidget* parent) : QWidget(parent) { + // i18n: Window for managing the Wii Speak microphone setWindowTitle(tr("Wii Speak Manager")); setObjectName(QStringLiteral("wii_speak_manager")); setMinimumSize(QSize(700, 200)); @@ -52,33 +41,70 @@ void WiiSpeakWindow::CreateMainWindow() auto* checkbox_group = new QGroupBox(); auto* checkbox_layout = new QHBoxLayout(); - checkbox_layout->setAlignment(Qt::AlignLeft); - m_checkbox = new QCheckBox(tr("Emulate Wii Speak"), this); - m_checkbox->setChecked(Config::Get(Config::MAIN_EMULATE_WII_SPEAK)); - connect(m_checkbox, &QCheckBox::toggled, this, &WiiSpeakWindow::EmulateWiiSpeak); - checkbox_layout->addWidget(m_checkbox); + checkbox_layout->setAlignment(Qt::AlignHCenter); + m_checkbox_enabled = new QCheckBox(tr("Emulate Wii Speak"), this); + m_checkbox_enabled->setChecked(Config::Get(Config::MAIN_EMULATE_WII_SPEAK)); + connect(m_checkbox_enabled, &QCheckBox::toggled, this, &WiiSpeakWindow::EmulateWiiSpeak); + checkbox_layout->addWidget(m_checkbox_enabled); checkbox_group->setLayout(checkbox_layout); + main_layout->addWidget(checkbox_group); + + m_config_group = new QGroupBox(tr("Microphone configuration")); + auto* config_layout = new QHBoxLayout(); + + auto checkbox_mic_connected = new QCheckBox(tr("Connect"), this); + checkbox_mic_connected->setChecked(Config::Get(Config::MAIN_WII_SPEAK_CONNECTED)); + connect(checkbox_mic_connected, &QCheckBox::toggled, this, + &WiiSpeakWindow::SetWiiSpeakConnectionState); + config_layout->addWidget(checkbox_mic_connected); m_combobox_microphones = new QComboBox(); - for (const std::string& device : IOS::HLE::USB::Microphone::ListDevices()) + m_combobox_microphones->addItem(QLatin1String("(%1)").arg(tr("Autodetect preferred microphone")), + QString{}); + for (auto& [device_id, device_name] : CubebUtils::ListInputDevices()) { - m_combobox_microphones->addItem(QString::fromStdString(device)); + const auto user_data = QString::fromStdString(device_id); + m_combobox_microphones->addItem(QString::fromStdString(device_name), user_data); } + connect(m_combobox_microphones, &QComboBox::currentIndexChanged, this, + &WiiSpeakWindow::OnInputDeviceChange); - checkbox_layout->addWidget(m_combobox_microphones); + auto current_device_id = QString::fromStdString(Config::Get(Config::MAIN_WII_SPEAK_MICROPHONE)); + m_combobox_microphones->setCurrentIndex(m_combobox_microphones->findData(current_device_id)); + config_layout->addWidget(m_combobox_microphones); + + m_config_group->setLayout(config_layout); + m_config_group->setVisible(Config::Get(Config::MAIN_EMULATE_WII_SPEAK)); + main_layout->addWidget(m_config_group); - main_layout->addWidget(checkbox_group); setLayout(main_layout); } void WiiSpeakWindow::EmulateWiiSpeak(bool emulate) { Config::SetBaseOrCurrent(Config::MAIN_EMULATE_WII_SPEAK, emulate); + m_config_group->setVisible(emulate); +} + +void WiiSpeakWindow::SetWiiSpeakConnectionState(bool connected) +{ + Config::SetBaseOrCurrent(Config::MAIN_WII_SPEAK_CONNECTED, connected); } void WiiSpeakWindow::OnEmulationStateChanged(Core::State state) { const bool running = state != Core::State::Uninitialized; - m_checkbox->setEnabled(!running); + m_checkbox_enabled->setEnabled(!running); + m_combobox_microphones->setEnabled(!running); +} + +void WiiSpeakWindow::OnInputDeviceChange() +{ + auto user_data = m_combobox_microphones->currentData(); + if (!user_data.isValid()) + return; + + const std::string device_id = user_data.toString().toStdString(); + Config::SetBaseOrCurrent(Config::MAIN_WII_SPEAK_MICROPHONE, device_id); } diff --git a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h index 71693aadd220..1a6d155583aa 100644 --- a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h +++ b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h @@ -10,6 +10,7 @@ class QCheckBox; class QComboBox; +class QGroupBox; class WiiSpeakWindow : public QWidget { @@ -22,7 +23,10 @@ class WiiSpeakWindow : public QWidget void CreateMainWindow(); void OnEmulationStateChanged(Core::State state); void EmulateWiiSpeak(bool emulate); + void SetWiiSpeakConnectionState(bool connected); + void OnInputDeviceChange(); - QCheckBox* m_checkbox; + QCheckBox* m_checkbox_enabled; QComboBox* m_combobox_microphones; + QGroupBox* m_config_group; };