diff --git a/Source/Core/Core/DolphinAnalytics.cpp b/Source/Core/Core/DolphinAnalytics.cpp index cc74b4d8af..5f54420013 100644 --- a/Source/Core/Core/DolphinAnalytics.cpp +++ b/Source/Core/Core/DolphinAnalytics.cpp @@ -137,7 +137,7 @@ void DolphinAnalytics::ReportGameStart() } // Keep in sync with enum class GameQuirk definition. -constexpr std::array GAME_QUIRKS_NAMES{ +constexpr std::array GAME_QUIRKS_NAMES{ "directly-reads-wiimote-input", "uses-DVDLowStopLaser", "uses-DVDLowOffset", @@ -158,6 +158,8 @@ constexpr std::array GAME_QUIRKS_NAMES{ "uses-cp-perf-command", "uses-unimplemented-ax-command", "uses-ax-initial-time-delay", + "uses-ax-wiimote-lowpass", + "uses-ax-wiimote-biquad", "sets-xf-clipdisable-bit-0", "sets-xf-clipdisable-bit-1", "sets-xf-clipdisable-bit-2", diff --git a/Source/Core/Core/DolphinAnalytics.h b/Source/Core/Core/DolphinAnalytics.h index b200bf8ea4..83a5a45822 100644 --- a/Source/Core/Core/DolphinAnalytics.h +++ b/Source/Core/Core/DolphinAnalytics.h @@ -72,6 +72,8 @@ enum class GameQuirk // We don't implement all AX features yet. USES_UNIMPLEMENTED_AX_COMMAND, USES_AX_INITIAL_TIME_DELAY, + USES_AX_WIIMOTE_LOWPASS, + USES_AX_WIIMOTE_BIQUAD, // We don't implement XFMEM_CLIPDISABLE yet. SETS_XF_CLIPDISABLE_BIT_0, diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp index 2dc000dbfe..3b9161a52d 100644 --- a/Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp +++ b/Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp @@ -438,7 +438,7 @@ void AXUCode::ProcessPBList(u32 pb_addr) ProcessVoice(static_cast(m_accelerator.get()), pb, buffers, spms, ConvertMixerControl(pb.mixer_control), - m_coeffs_checksum ? m_coeffs.data() : nullptr); + m_coeffs_checksum ? m_coeffs.data() : nullptr, false); // Forward the buffers for (auto& ptr : buffers.ptrs) diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/AXStructs.h b/Source/Core/Core/HW/DSPHLE/UCodes/AXStructs.h index 8c6c6aad90..f819c066aa 100644 --- a/Source/Core/Core/HW/DSPHLE/UCodes/AXStructs.h +++ b/Source/Core/Core/HW/DSPHLE/UCodes/AXStructs.h @@ -177,7 +177,7 @@ struct PBADPCMLoopInfo struct PBLowPassFilter { - u16 enabled; + u16 on; s16 yn1; u16 a0; u16 b0; @@ -215,20 +215,21 @@ struct AXPB struct PBBiquadFilter { - u16 on; // on = 2, off = 0 - u16 xn1; // History data - u16 xn2; - u16 yn1; - u16 yn2; - u16 b0; // Filter coefficients - u16 b1; - u16 b2; - u16 a1; - u16 a2; + u16 on; + s16 xn1; // History data + s16 xn2; + s16 yn1; + s16 yn2; + s16 b0; // Filter coefficients + s16 b1; + s16 b2; + s16 a1; + s16 a2; }; union PBInfImpulseResponseWM { + u16 on; // 0: off, 2: biquad, other: low-pass PBLowPassFilter lpf; PBBiquadFilter biquad; }; diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/AXVoice.h b/Source/Core/Core/HW/DSPHLE/UCodes/AXVoice.h index 4c9eec6df1..ad491196e2 100644 --- a/Source/Core/Core/HW/DSPHLE/UCodes/AXVoice.h +++ b/Source/Core/Core/HW/DSPHLE/UCodes/AXVoice.h @@ -400,17 +400,45 @@ void MixAdd(int* out, const s16* input, u32 count, VolumeData* vd, s16* dpop, bo // Execute a low pass filter on the samples using one history value. Returns // the new history value. -s16 LowPassFilter(s16* samples, u32 count, s16 yn1, u16 a0, u16 b0) +static void LowPassFilter(s16* samples, u32 count, PBLowPassFilter& f) { for (u32 i = 0; i < count; ++i) - yn1 = samples[i] = (a0 * (s32)samples[i] + b0 * (s32)yn1) >> 15; - return yn1; + f.yn1 = samples[i] = (f.a0 * (s32)samples[i] + f.b0 * (s32)f.yn1) >> 15; } +#ifdef AX_WII +static void BiquadFilter(s16* samples, u32 count, PBBiquadFilter& f) +{ + for (u32 i = 0; i < count; ++i) + { + s16 xn0 = samples[i]; + s64 tmp = 0; + tmp += f.b0 * s32(xn0); + tmp += f.b1 * s32(f.xn1); + tmp += f.b2 * s32(f.xn2); + tmp += f.a1 * s32(f.yn1); + tmp += f.a2 * s32(f.yn2); + tmp <<= 2; + // CLRL + if (tmp & 0x10000) + tmp += 0x8000; + else + tmp += 0x7FFF; + tmp >>= 16; + s16 yn0 = s16(tmp); + f.xn2 = f.xn1; + f.yn2 = f.yn1; + f.xn1 = xn0; + f.yn1 = yn0; + samples[i] = yn0; + } +} +#endif + // Process 1ms of audio (for AX GC) or 3ms of audio (for AX Wii) from a PB and // mix it to the output buffers. void ProcessVoice(HLEAccelerator* accelerator, PB_TYPE& pb, const AXBuffers& buffers, u16 count, - AXMixControl mctrl, const s16* coeffs) + AXMixControl mctrl, const s16* coeffs, bool new_filter) { // If the voice is not running, nothing to do. if (pb.running != 1) @@ -435,12 +463,19 @@ void ProcessVoice(HLEAccelerator* accelerator, PB_TYPE& pb, const AXBuffers& buf pb.vol_env.cur_volume += pb.vol_env.cur_volume_delta; } - // Optionally, execute a low pass filter - if (pb.lpf.enabled) + // Optionally, execute a low-pass and/or biquad filter. + if (pb.lpf.on != 0) { - pb.lpf.yn1 = LowPassFilter(samples, count, pb.lpf.yn1, pb.lpf.a0, pb.lpf.b0); + LowPassFilter(samples, count, pb.lpf); } +#ifdef AX_WII + if (new_filter && pb.biquad.on != 0) + { + BiquadFilter(samples, count, pb.biquad); + } +#endif + // Mix LRS, AUXA and AUXB depending on mixer_control // TODO: Handle DPL2 on AUXB. @@ -527,6 +562,21 @@ void ProcessVoice(HLEAccelerator* accelerator, PB_TYPE& pb, const AXBuffers& buf // Wiimote mixing. if (pb.remote) { + if (new_filter && pb.remote_iir.on != 0) + { + // Only one filter at most for Wiimotes. + if (pb.remote_iir.on == 2) + { + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_AX_WIIMOTE_BIQUAD); + BiquadFilter(samples, count, pb.remote_iir.biquad); + } + else + { + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_AX_WIIMOTE_LOWPASS); + LowPassFilter(samples, count, pb.remote_iir.lpf); + } + } + // Old AXWii versions process ms per ms. u16 wm_count = count == 96 ? 18 : 6; diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/AXWii.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/AXWii.cpp index 53be74371d..8ddd119517 100644 --- a/Source/Core/Core/HW/DSPHLE/UCodes/AXWii.cpp +++ b/Source/Core/Core/HW/DSPHLE/UCodes/AXWii.cpp @@ -28,7 +28,8 @@ AXWiiUCode::AXWiiUCode(DSPHLE* dsphle, u32 crc) for (u16& volume : m_last_aux_volumes) volume = 0x8000; - m_old_axwii = (crc == 0xfa450138) || (crc == 0x7699af32); + m_old_axwii = crc == 0xfa450138 || crc == 0x7699af32; + m_new_filter = crc == 0x347112ba || crc == 0x4cc52064; m_accelerator = std::make_unique(dsphle->GetSystem().GetDSP()); } @@ -450,7 +451,7 @@ void AXWiiUCode::ProcessPBList(u32 pb_addr) ApplyUpdatesForMs(curr_ms, pb, num_updates, updates); ProcessVoice(static_cast(m_accelerator.get()), pb, buffers, spms, ConvertMixerControl(HILO_TO_32(pb.mixer_control)), - m_coeffs_checksum ? m_coeffs.data() : nullptr); + m_coeffs_checksum ? m_coeffs.data() : nullptr, m_new_filter); // Forward the buffers for (auto& ptr : buffers.ptrs) @@ -462,7 +463,7 @@ void AXWiiUCode::ProcessPBList(u32 pb_addr) { ProcessVoice(static_cast(m_accelerator.get()), pb, buffers, 96, ConvertMixerControl(HILO_TO_32(pb.mixer_control)), - m_coeffs_checksum ? m_coeffs.data() : nullptr); + m_coeffs_checksum ? m_coeffs.data() : nullptr, m_new_filter); } WritePB(memory, pb_addr, pb, m_crc); diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/AXWii.h b/Source/Core/Core/HW/DSPHLE/UCodes/AXWii.h index aa69ae9637..85d3300384 100644 --- a/Source/Core/Core/HW/DSPHLE/UCodes/AXWii.h +++ b/Source/Core/Core/HW/DSPHLE/UCodes/AXWii.h @@ -38,6 +38,9 @@ protected: // Are we implementing an old version of AXWii which still has updates? bool m_old_axwii = false; + // Late AXWii versions support Wiimote filtering and a biquad filter. + bool m_new_filter = false; + // Last volume values for MAIN and AUX. Used to generate volume ramps to // interpolate nicely between old and new volume values. u16 m_last_main_volume = 0;