From f5dd80bb5ba3a354cdae01715e0af4d61976442e Mon Sep 17 00:00:00 2001 From: Sepalani Date: Sat, 11 May 2024 02:11:45 +0400 Subject: [PATCH] IOS/USB: Try to fix the Wii Speak Channel record/playback feature 1. Fix Wii Speak SAMPLER_MUTE register: The register should be 12 (i.e. 0x0c) instead of 0xc0. 2. Fix Wii Speak buffer memcpy size parameter: It seems to fix random echoes and reduce noises when nobody is speaking. 3. Change the isochronous transfer timing: It is based on empirical testing. --- .../Core/Core/IOS/USB/Emulated/Microphone.cpp | 21 +++++++-- .../Core/Core/IOS/USB/Emulated/Microphone.h | 4 +- .../Core/Core/IOS/USB/Emulated/WiiSpeak.cpp | 43 ++++++++++--------- Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h | 2 +- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp b/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp index 49bd7cfcaa..e061012669 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp @@ -193,6 +193,7 @@ long Microphone::DataCallback(const s16* input_buffer, long nframes) m_samples_avail += nframes; if (m_samples_avail > STREAM_SIZE) { + WARN_LOG_FMT(IOS_USB, "Wii Speak ring buffer is full, data will be lost!"); m_samples_avail = STREAM_SIZE; } @@ -202,18 +203,30 @@ long Microphone::DataCallback(const s16* input_buffer, long nframes) void Microphone::ReadIntoBuffer(u8* dst, u32 size) { + static constexpr u32 SINGLE_READ_SIZE = BUFF_SIZE_SAMPLES * sizeof(SampleType); + + // Avoid buffer overflow during memcpy + static_assert((STREAM_SIZE % BUFF_SIZE_SAMPLES) == 0, + "The STREAM_SIZE isn't a multiple of BUFF_SIZE_SAMPLES"); + std::lock_guard lock(m_ring_lock); - if (m_samples_avail >= BUFF_SIZE_SAMPLES) + for (u8* end = dst + size; dst < end; dst += SINGLE_READ_SIZE, size -= SINGLE_READ_SIZE) { - u8* last_buffer = reinterpret_cast(&m_stream_buffer[m_stream_rpos]); - std::memcpy(dst, static_cast(last_buffer), size); + if (size < SINGLE_READ_SIZE || m_samples_avail < BUFF_SIZE_SAMPLES) + break; + + SampleType* last_buffer = &m_stream_buffer[m_stream_rpos]; + std::memcpy(dst, last_buffer, SINGLE_READ_SIZE); m_samples_avail -= BUFF_SIZE_SAMPLES; - m_stream_rpos += BUFF_SIZE_SAMPLES; m_stream_rpos %= STREAM_SIZE; } + if (size != 0) + { + std::memset(dst, 0, size); + } } bool Microphone::HasData() const diff --git a/Source/Core/Core/IOS/USB/Emulated/Microphone.h b/Source/Core/Core/IOS/USB/Emulated/Microphone.h index 3d5ae29e1a..58264eafd1 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Microphone.h +++ b/Source/Core/Core/IOS/USB/Emulated/Microphone.h @@ -42,11 +42,11 @@ private: void StreamStop(); static constexpr u32 SAMPLING_RATE = 8000; - static constexpr u32 BUFFER_SIZE = SAMPLING_RATE / 2; + using SampleType = s16; static constexpr u32 BUFF_SIZE_SAMPLES = 16; static constexpr u32 STREAM_SIZE = BUFF_SIZE_SAMPLES * 500; - std::array m_stream_buffer{}; + std::array m_stream_buffer{}; u32 m_stream_wpos = 0; u32 m_stream_rpos = 0; u32 m_samples_avail = 0; diff --git a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp index 05a574ac36..0e5ce62a2c 100644 --- a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp @@ -190,16 +190,35 @@ int WiiSpeak::SubmitTransfer(std::unique_ptr cmd) break; } - // Anything more causes the visual cue to not appear. - // Anything less is more choppy audio. + // Transferring too slow causes the visual cue to not appear, + // while transferring too fast results in more choppy audio. DEBUG_LOG_FMT(IOS_USB, "Wii Speak isochronous transfer: length={:04x} endpoint={:02x} num_packets={:02x}", cmd->length, cmd->endpoint, cmd->num_packets); // According to the Wii Speak specs on wiibrew, it's "USB 2.0 Full-speed Device Module", // so the length of a single frame should be 1 ms. - // TODO: Find a proper way to compute the transfer timing. - const u32 transfer_timing = 2500; // 2.5 ms + // + // Monster Hunter 3 and the Wii Speak Channel use cmd->length=0x100, allowing 256/2 samples + // (i.e. 128 samples in 16-bit mono) per frame transfer. The Microphone class is using cubeb + // configured with a sample rate of 8000. + // + // Based on these numbers, here are some theoretical speeds: + // - fastest transfer speed would be 8000 samples in 63 ms (i.e. 8000 * 1/128 = 62.5) + // * using a timing of 1 ms per frame of 128 samples + // * here cubeb sample rate is the bottleneck + // - slowest transfer speed would be 8000 samples in 1000 ms (i.e. 128 * 1000/8000 = 16) + // * using a timing of 16 ms per frame of 128 samples + // * matching cubeb sampling rate + // + // A decent timing would be 16ms. However, it seems that the Wii Speak Channel record feature is + // broken if the timing is less than 32ms and that the record playback is broken when around 34ms + // and above. Monster Hunter 3 doesn't seem affected by this timing issue. + // + // TODO: Investigate and ensure that's the proper way to fix it. + // Maybe it's related to the Wii DSP and its stereo/mono mode? + // If so, what would be the impact of using DSP LLE/HLE in mono/stereo? + const u32 transfer_timing = 32000; cmd->ScheduleTransferCompletion(IPC_SUCCESS, transfer_timing); return IPC_SUCCESS; } @@ -215,22 +234,6 @@ void WiiSpeak::SetRegister(const std::unique_ptr& cmd) DEBUG_LOG_FMT(IOS_USB, "Wii Speak register set (reg={:02x}, arg1={:04x}, arg2={:04x})", reg, arg1, arg2); - // TODO - // - // - On Wii Speak Channel start - // W[IOS_USB]: Wii Speak unsupported register set (reg=0c, arg1=0000, arg2=0000) - // W[IOS_USB]: Wii Speak unsupported register get (reg=0c, arg1=109091e2, arg2=109091e4) - // - // - On Wii Speak Channel close - // W[IOS_USB]: Wii Speak unsupported register set (reg=0c, arg1=0001, arg2=0000) - // W[IOS_USB]: Wii Speak unsupported register get (reg=0c, arg1=109091e2, arg2=109091e4) - // - // - On Monster Hunter 3 (RMHE08) online start - // N[OSREPORT_HLE]: 80450a20->80418adc| ok to call PMICStartAsync() -> 0 - // W[IOS_USB]: Wii Speak unsupported register set (reg=0c, arg1=0000, arg2=0000) - // W[IOS_USB]: Wii Speak unsupported register get (reg=0c, arg1=10037f62, arg2=10037f64) - // N[OSREPORT_HLE]: 80450a94->80418adc| ok to start P-Mic -> 0. - switch (reg) { case SAMPLER_STATE: diff --git a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h index bfd0a57563..6dc88e6456 100644 --- a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h +++ b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h @@ -49,7 +49,7 @@ private: enum Registers { SAMPLER_STATE = 0, - SAMPLER_MUTE = 0xc0, + SAMPLER_MUTE = 0x0c, SAMPLER_FREQ = 2, FREQ_8KHZ = 0,