Merge pull request #10663 from Tilka/ax_biquad

AX: add support for biquad filtering
This commit is contained in:
Tilka 2024-08-24 22:31:05 +01:00 committed by GitHub
commit cc256ef16d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 82 additions and 23 deletions

View File

@ -137,7 +137,7 @@ void DolphinAnalytics::ReportGameStart()
} }
// Keep in sync with enum class GameQuirk definition. // Keep in sync with enum class GameQuirk definition.
constexpr std::array<const char*, 32> GAME_QUIRKS_NAMES{ constexpr std::array<const char*, 34> GAME_QUIRKS_NAMES{
"directly-reads-wiimote-input", "directly-reads-wiimote-input",
"uses-DVDLowStopLaser", "uses-DVDLowStopLaser",
"uses-DVDLowOffset", "uses-DVDLowOffset",
@ -158,6 +158,8 @@ constexpr std::array<const char*, 32> GAME_QUIRKS_NAMES{
"uses-cp-perf-command", "uses-cp-perf-command",
"uses-unimplemented-ax-command", "uses-unimplemented-ax-command",
"uses-ax-initial-time-delay", "uses-ax-initial-time-delay",
"uses-ax-wiimote-lowpass",
"uses-ax-wiimote-biquad",
"sets-xf-clipdisable-bit-0", "sets-xf-clipdisable-bit-0",
"sets-xf-clipdisable-bit-1", "sets-xf-clipdisable-bit-1",
"sets-xf-clipdisable-bit-2", "sets-xf-clipdisable-bit-2",

View File

@ -72,6 +72,8 @@ enum class GameQuirk
// We don't implement all AX features yet. // We don't implement all AX features yet.
USES_UNIMPLEMENTED_AX_COMMAND, USES_UNIMPLEMENTED_AX_COMMAND,
USES_AX_INITIAL_TIME_DELAY, USES_AX_INITIAL_TIME_DELAY,
USES_AX_WIIMOTE_LOWPASS,
USES_AX_WIIMOTE_BIQUAD,
// We don't implement XFMEM_CLIPDISABLE yet. // We don't implement XFMEM_CLIPDISABLE yet.
SETS_XF_CLIPDISABLE_BIT_0, SETS_XF_CLIPDISABLE_BIT_0,

View File

@ -438,7 +438,7 @@ void AXUCode::ProcessPBList(u32 pb_addr)
ProcessVoice(static_cast<HLEAccelerator*>(m_accelerator.get()), pb, buffers, spms, ProcessVoice(static_cast<HLEAccelerator*>(m_accelerator.get()), pb, buffers, spms,
ConvertMixerControl(pb.mixer_control), ConvertMixerControl(pb.mixer_control),
m_coeffs_checksum ? m_coeffs.data() : nullptr); m_coeffs_checksum ? m_coeffs.data() : nullptr, false);
// Forward the buffers // Forward the buffers
for (auto& ptr : buffers.ptrs) for (auto& ptr : buffers.ptrs)

View File

@ -177,7 +177,7 @@ struct PBADPCMLoopInfo
struct PBLowPassFilter struct PBLowPassFilter
{ {
u16 enabled; u16 on;
s16 yn1; s16 yn1;
u16 a0; u16 a0;
u16 b0; u16 b0;
@ -215,20 +215,21 @@ struct AXPB
struct PBBiquadFilter struct PBBiquadFilter
{ {
u16 on; // on = 2, off = 0 u16 on;
u16 xn1; // History data s16 xn1; // History data
u16 xn2; s16 xn2;
u16 yn1; s16 yn1;
u16 yn2; s16 yn2;
u16 b0; // Filter coefficients s16 b0; // Filter coefficients
u16 b1; s16 b1;
u16 b2; s16 b2;
u16 a1; s16 a1;
u16 a2; s16 a2;
}; };
union PBInfImpulseResponseWM union PBInfImpulseResponseWM
{ {
u16 on; // 0: off, 2: biquad, other: low-pass
PBLowPassFilter lpf; PBLowPassFilter lpf;
PBBiquadFilter biquad; PBBiquadFilter biquad;
}; };

View File

@ -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 // Execute a low pass filter on the samples using one history value. Returns
// the new history value. // 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) for (u32 i = 0; i < count; ++i)
yn1 = samples[i] = (a0 * (s32)samples[i] + b0 * (s32)yn1) >> 15; f.yn1 = samples[i] = (f.a0 * (s32)samples[i] + f.b0 * (s32)f.yn1) >> 15;
return yn1;
} }
#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 // Process 1ms of audio (for AX GC) or 3ms of audio (for AX Wii) from a PB and
// mix it to the output buffers. // mix it to the output buffers.
void ProcessVoice(HLEAccelerator* accelerator, PB_TYPE& pb, const AXBuffers& buffers, u16 count, 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 the voice is not running, nothing to do.
if (pb.running != 1) 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; pb.vol_env.cur_volume += pb.vol_env.cur_volume_delta;
} }
// Optionally, execute a low pass filter // Optionally, execute a low-pass and/or biquad filter.
if (pb.lpf.enabled) 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 // Mix LRS, AUXA and AUXB depending on mixer_control
// TODO: Handle DPL2 on AUXB. // TODO: Handle DPL2 on AUXB.
@ -527,6 +562,21 @@ void ProcessVoice(HLEAccelerator* accelerator, PB_TYPE& pb, const AXBuffers& buf
// Wiimote mixing. // Wiimote mixing.
if (pb.remote) 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. // Old AXWii versions process ms per ms.
u16 wm_count = count == 96 ? 18 : 6; u16 wm_count = count == 96 ? 18 : 6;

View File

@ -28,7 +28,8 @@ AXWiiUCode::AXWiiUCode(DSPHLE* dsphle, u32 crc)
for (u16& volume : m_last_aux_volumes) for (u16& volume : m_last_aux_volumes)
volume = 0x8000; 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<HLEAccelerator>(dsphle->GetSystem().GetDSP()); m_accelerator = std::make_unique<HLEAccelerator>(dsphle->GetSystem().GetDSP());
} }
@ -450,7 +451,7 @@ void AXWiiUCode::ProcessPBList(u32 pb_addr)
ApplyUpdatesForMs(curr_ms, pb, num_updates, updates); ApplyUpdatesForMs(curr_ms, pb, num_updates, updates);
ProcessVoice(static_cast<HLEAccelerator*>(m_accelerator.get()), pb, buffers, spms, ProcessVoice(static_cast<HLEAccelerator*>(m_accelerator.get()), pb, buffers, spms,
ConvertMixerControl(HILO_TO_32(pb.mixer_control)), 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 // Forward the buffers
for (auto& ptr : buffers.ptrs) for (auto& ptr : buffers.ptrs)
@ -462,7 +463,7 @@ void AXWiiUCode::ProcessPBList(u32 pb_addr)
{ {
ProcessVoice(static_cast<HLEAccelerator*>(m_accelerator.get()), pb, buffers, 96, ProcessVoice(static_cast<HLEAccelerator*>(m_accelerator.get()), pb, buffers, 96,
ConvertMixerControl(HILO_TO_32(pb.mixer_control)), 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); WritePB(memory, pb_addr, pb, m_crc);

View File

@ -38,6 +38,9 @@ protected:
// Are we implementing an old version of AXWii which still has updates? // Are we implementing an old version of AXWii which still has updates?
bool m_old_axwii = false; 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 // Last volume values for MAIN and AUX. Used to generate volume ramps to
// interpolate nicely between old and new volume values. // interpolate nicely between old and new volume values.
u16 m_last_main_volume = 0; u16 m_last_main_volume = 0;