Skip to content

Commit

Permalink
DSPHLE/AXWii: fix PB layout differences
Browse files Browse the repository at this point in the history
Two Wii-specific bugs:
- PB updates on Wii were being byte-swapped twice. Not sure whether this
  affected any games.
- Wiimote sound was broken for early Wii games because the size
  difference between high-pass and biquad filters was not accounted for.

This fixes wiimote sounds in early Wii games like:
- WarioWare Smooth Moves (issue 11725)
- Ice Age 2: The Meltdown
- Excite Truck
- ...
  • Loading branch information
Tilka committed Oct 5, 2024
1 parent e6f222c commit 1d8932f
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 105 deletions.
3 changes: 1 addition & 2 deletions Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,7 @@ void AXUCode::ProcessPBList(u32 pb_addr)

ReadPB(memory, pb_addr, pb, m_crc);

u32 updates_addr = HILO_TO_32(pb.updates.data);
u16* updates = (u16*)HLEMemory_Get_Pointer(memory, updates_addr);
PBUpdateData updates = LoadPBUpdates(memory, pb);

for (int curr_ms = 0; curr_ms < 5; ++curr_ms)
{
Expand Down
21 changes: 0 additions & 21 deletions Source/Core/Core/HW/DSPHLE/UCodes/AX.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,27 +125,6 @@ class AXUCode /* not final: subclassed by AXWiiUCode */ : public UCodeInterface
// versions of AX.
AXMixControl ConvertMixerControl(u32 mixer_control);

// Apply updates to a PB. Generic, used in AX GC and AX Wii.
template <typename PBType>
void ApplyUpdatesForMs(int curr_ms, PBType& pb, u16* num_updates, u16* updates)
{
auto pb_mem = Common::BitCastToArray<u16>(pb);

u32 start_idx = 0;
for (int i = 0; i < curr_ms; ++i)
start_idx += num_updates[i];

for (u32 i = start_idx; i < start_idx + num_updates[curr_ms]; ++i)
{
u16 update_off = Common::swap16(updates[2 * i]);
u16 update_val = Common::swap16(updates[2 * i + 1]);

pb_mem[update_off] = update_val;
}

pb = std::bit_cast<PBType>(pb_mem);
}

virtual void HandleCommandList();
void SignalWorkEnd();

Expand Down
32 changes: 29 additions & 3 deletions Source/Core/Core/HW/DSPHLE/UCodes/AXStructs.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,29 @@ struct PBInitialTimeDelay
u16 targetRight;
};

struct PBUpdate
{
u16 pb_offset;
u16 new_value;
};
using PBUpdateData = std::array<PBUpdate, 32>;

// Update data - read these each 1ms subframe and use them!
// It seems that to provide higher time precisions for MIDI events, some games
// use this thing to update the parameter blocks per 1ms sub-block (a block is 5ms).
// Using this data should fix games that are missing MIDI notes.
struct PBUpdates
{
u16 num_updates[5];
u16 data_hi; // These point to main RAM. Not sure about the structure of the data.
u16 data_hi; // These point to main RAM.
u16 data_lo;
};

// Same for Wii, where frames are only 3 ms.
struct PBUpdatesWii
{
u16 num_updates[3];
u16 data_hi;
u16 data_lo;
};

Expand Down Expand Up @@ -213,6 +228,12 @@ struct AXPB
u16 padding[24];
};

struct PBHighPassFilter
{
u16 on;
u16 unk[3];
};

struct PBBiquadFilter
{
u16 on;
Expand Down Expand Up @@ -252,13 +273,18 @@ struct AXPBWii
PBMixerWii mixer;
PBInitialTimeDelay initial_time_delay;
PBDpopWii dpop;
PBUpdatesWii updates; // Not present in all versions of the struct.
PBVolumeEnvelope vol_env;
PBAudioAddr audio_addr;
PBADPCMInfo adpcm;
PBSampleRateConverter src;
PBADPCMLoopInfo adpcm_loop_info;
PBLowPassFilter lpf;
PBBiquadFilter biquad;
union
{
PBHighPassFilter hpf;
PBBiquadFilter biquad;
};

// WIIMOTE :D
u16 remote;
Expand All @@ -269,7 +295,7 @@ struct AXPBWii
PBSampleRateConverterWM remote_src;
PBInfImpulseResponseWM remote_iir;

u16 pad[12]; // align us, captain! (32B)
u16 pad[2]; // align us, captain! (32B)
};

// TODO: All these enums have changed a lot for Wii
Expand Down
146 changes: 139 additions & 7 deletions Source/Core/Core/HW/DSPHLE/UCodes/AXVoice.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,34 @@ namespace
// Useful macro to convert xxx_hi + xxx_lo to xxx for 32 bits.
#define HILO_TO_32(name) ((u32(name##_hi) << 16) | name##_lo)

PBUpdateData LoadPBUpdates(Memory::MemoryManager& memory, const PB_TYPE& pb)
{
PBUpdateData updates;
u32 updates_addr = HILO_TO_32(pb.updates.data);
memory.CopyFromEmuSwapped((u16*)updates.data(), updates_addr, sizeof(updates));
return updates;
}

// Apply updates to a PB.
void ApplyUpdatesForMs(int curr_ms, PB_TYPE& pb, u16* num_updates, const PBUpdateData& updates)
{
auto pb_mem = Common::BitCastToArray<u16>(pb);

u32 start_idx = 0;
for (int i = 0; i < curr_ms; ++i)
start_idx += num_updates[i];

for (u32 i = start_idx; i < start_idx + num_updates[curr_ms]; ++i)
{
u16 update_off = updates[i].pb_offset;
u16 update_val = updates[i].new_value;

pb_mem[update_off] = update_val;
}

pb = std::bit_cast<PB_TYPE>(pb_mem);
}

// Used to pass a large amount of buffers to the mixing function.
union AXBuffers
{
Expand Down Expand Up @@ -83,7 +111,11 @@ union AXBuffers
#ifdef AX_GC
int* ptrs[9];
#else
int* ptrs[20];
struct
{
int* regular_ptrs[12];
int* wiimote_ptrs[8];
};
#endif
};

Expand All @@ -99,18 +131,78 @@ bool HasLpf(u32 crc)
}
}

#ifdef AX_WII
bool HasHpf(u32 crc)
{
switch (crc)
{
case 0x7699af32:
case 0xfa450138:
case 0xd9c4bf34:
case 0xadbc06bd:
return true;
default:
return false;
}
}

bool HasUpdates(u32 crc)
{
switch (crc)
{
case 0x7699af32:
case 0xfa450138:
return true;
default:
return false;
}
}
#endif

// Read a PB from MRAM/ARAM
void ReadPB(Memory::MemoryManager& memory, u32 addr, PB_TYPE& pb, u32 crc)
{
if (HasLpf(crc))
// The PB memory layout changed several times.
// For HLE, we use the largest struct version.
#ifdef AX_WII
if (HasHpf(crc))
{
u16* dst = (u16*)&pb;
memory.CopyFromEmuSwapped<u16>(dst, addr, sizeof(pb));
if (HasUpdates(crc))
{
// The hpf field is a bit smaller than the biquad. Skip the difference.
char* dst = (char*)&pb;

constexpr size_t gap_begin = offsetof(AXPBWii, hpf) + sizeof(PBHighPassFilter);
constexpr size_t gap_end = offsetof(AXPBWii, biquad) + sizeof(PBBiquadFilter);

memory.CopyFromEmuSwapped<u16>((u16*)dst, addr, gap_begin);
memset(dst + gap_begin, 0, gap_end - gap_begin);
memory.CopyFromEmuSwapped<u16>((u16*)(dst + gap_end), addr + gap_begin, sizeof(pb) - gap_end);
}
else
{
// Skip updates field and gap after hpf field.
char* dst = (char*)&pb;

constexpr size_t updates_begin = offsetof(AXPBWii, updates);
constexpr size_t updates_end = offsetof(AXPBWii, updates) + sizeof(PBUpdatesWii);
constexpr size_t gap_begin = offsetof(AXPBWii, hpf) + sizeof(PBHighPassFilter);
constexpr size_t gap_end = offsetof(AXPBWii, biquad) + sizeof(PBBiquadFilter);

memory.CopyFromEmuSwapped<u16>((u16*)dst, addr, updates_begin);
memset(dst + updates_begin, 0, sizeof(PBUpdatesWii));
memory.CopyFromEmuSwapped<u16>((u16*)(dst + updates_end), addr + updates_begin,
gap_begin - updates_end);
memset(dst + gap_begin, 0, gap_end - gap_begin);
memory.CopyFromEmuSwapped<u16>((u16*)(dst + gap_end), addr + gap_begin, sizeof(pb) - gap_end);
}
}
else
#endif
#ifdef AX_GC
if (!HasLpf(crc))
{
// The below is a terrible hack in order to support two different AXPB layouts.
// We skip lpf in this layout.
// Skip lpf field.

char* dst = (char*)&pb;

Expand All @@ -121,12 +213,52 @@ void ReadPB(Memory::MemoryManager& memory, u32 addr, PB_TYPE& pb, u32 crc)
memset(dst + lpf_off, 0, lc_off - lpf_off);
memory.CopyFromEmuSwapped<u16>((u16*)(dst + lc_off), addr + lpf_off, sizeof(pb) - lc_off);
}
else
#endif
{
u16* dst = (u16*)&pb;
memory.CopyFromEmuSwapped<u16>(dst, addr, sizeof(pb));
}
}

// Write a PB back to MRAM/ARAM
void WritePB(Memory::MemoryManager& memory, u32 addr, const PB_TYPE& pb, u32 crc)
{
if (HasLpf(crc))
#ifdef AX_WII
if (HasHpf(crc))
{
if (HasUpdates(crc))
{
// Skip gap between end of hpf and end of biquad fields.
const char* src = (const char*)&pb;

constexpr size_t gap_begin = offsetof(AXPBWii, hpf) + sizeof(PBHighPassFilter);
constexpr size_t gap_end = offsetof(AXPBWii, biquad) + sizeof(PBBiquadFilter);

memory.CopyToEmuSwapped<u16>(addr, (const u16*)src, gap_begin);
memory.CopyToEmuSwapped<u16>(addr + gap_begin, (const u16*)(src + gap_end),
sizeof(pb) - gap_end);
}
else
{
// Skip updates field and gap after hpf field.
const char* src = (const char*)&pb;

constexpr size_t updates_begin = offsetof(AXPBWii, updates);
constexpr size_t updates_end = offsetof(AXPBWii, updates) + sizeof(PBUpdatesWii);
constexpr size_t gap_begin = offsetof(AXPBWii, hpf) + sizeof(PBHighPassFilter);
constexpr size_t gap_end = offsetof(AXPBWii, biquad) + sizeof(PBBiquadFilter);

memory.CopyToEmuSwapped<u16>(addr, (const u16*)src, updates_begin);
memory.CopyToEmuSwapped<u16>(addr + updates_begin, (const u16*)(src + updates_end),
sizeof(PBUpdatesWii));
memory.CopyToEmuSwapped<u16>(addr + gap_begin, (const u16*)(src + gap_end),
sizeof(pb) - gap_end);
}
}
else
#endif
if (HasLpf(crc))
{
const u16* src = (const u16*)&pb;
memory.CopyToEmuSwapped<u16>(addr, src, sizeof(pb));
Expand Down
73 changes: 6 additions & 67 deletions Source/Core/Core/HW/DSPHLE/UCodes/AXWii.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,66 +360,6 @@ void AXWiiUCode::GenerateVolumeRamp(u16* output, u16 vol1, u16 vol2, size_t nval
}
}

bool AXWiiUCode::ExtractUpdatesFields(AXPBWii& pb, u16* num_updates, u16* updates,
u32* updates_addr)
{
auto pb_mem = Common::BitCastToArray<u16>(pb);

if (!m_old_axwii)
return false;

// Copy the num_updates field.
memcpy(num_updates, &pb_mem[41], 6);

// Get the address of the updates data
u16 addr_hi = pb_mem[44];
u16 addr_lo = pb_mem[45];
u32 addr = HILO_TO_32(addr);
auto& memory = m_dsphle->GetSystem().GetMemory();
u16* ptr = (u16*)HLEMemory_Get_Pointer(memory, addr);

*updates_addr = addr;

// Copy the updates data and change the offset to match a PB without
// updates data.
u32 updates_count = num_updates[0] + num_updates[1] + num_updates[2];
for (u32 i = 0; i < updates_count; ++i)
{
u16 update_off = Common::swap16(ptr[2 * i]);
u16 update_val = Common::swap16(ptr[2 * i + 1]);

if (update_off > 45)
update_off -= 5;

updates[2 * i] = update_off;
updates[2 * i + 1] = update_val;
}

// Remove the updates data from the PB
memmove(&pb_mem[41], &pb_mem[46], sizeof(pb) - 2 * 46);

pb = std::bit_cast<AXPBWii>(pb_mem);

return true;
}

void AXWiiUCode::ReinjectUpdatesFields(AXPBWii& pb, u16* num_updates, u32 updates_addr)
{
auto pb_mem = Common::BitCastToArray<u16>(pb);

// Make some space
memmove(&pb_mem[46], &pb_mem[41], sizeof(pb) - 2 * 46);

// Reinsert previous values
pb_mem[41] = num_updates[0];
pb_mem[42] = num_updates[1];
pb_mem[43] = num_updates[2];
pb_mem[44] = updates_addr >> 16;
pb_mem[45] = updates_addr & 0xFFFF;

pb = std::bit_cast<AXPBWii>(pb_mem);
}

void AXWiiUCode::ProcessPBList(u32 pb_addr)
{
// Samples per millisecond. In theory DSP sampling rate can be changed from
Expand All @@ -441,23 +381,22 @@ void AXWiiUCode::ProcessPBList(u32 pb_addr)

ReadPB(memory, pb_addr, pb, m_crc);

u16 num_updates[3];
u16 updates[1024];
u32 updates_addr;
if (ExtractUpdatesFields(pb, num_updates, updates, &updates_addr))
if (m_old_axwii)
{
PBUpdateData updates = LoadPBUpdates(memory, pb);
for (int curr_ms = 0; curr_ms < 3; ++curr_ms)
{
ApplyUpdatesForMs(curr_ms, pb, num_updates, updates);
ApplyUpdatesForMs(curr_ms, pb, pb.updates.num_updates, updates);
ProcessVoice(static_cast<HLEAccelerator*>(m_accelerator.get()), pb, buffers, spms,
ConvertMixerControl(HILO_TO_32(pb.mixer_control)),
m_coeffs_checksum ? m_coeffs.data() : nullptr, m_new_filter);

// Forward the buffers
for (auto& ptr : buffers.ptrs)
for (auto& ptr : buffers.regular_ptrs)
ptr += spms;
for (auto& ptr : buffers.wiimote_ptrs)
ptr += 6;
}
ReinjectUpdatesFields(pb, num_updates, updates_addr);
}
else
{
Expand Down
Loading

0 comments on commit 1d8932f

Please sign in to comment.