diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index 636d8fa928..207743bbd0 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -74,9 +74,6 @@ set(SRCS ActionReplay.cpp
HW/DSPHLE/UCodes/ROM.cpp
HW/DSPHLE/UCodes/UCodes.cpp
HW/DSPHLE/UCodes/Zelda.cpp
- HW/DSPHLE/UCodes/ZeldaADPCM.cpp
- HW/DSPHLE/UCodes/ZeldaSynth.cpp
- HW/DSPHLE/UCodes/ZeldaVoice.cpp
HW/DSPHLE/MailHandler.cpp
HW/DSPHLE/DSPHLE.cpp
HW/DSPLLE/DSPDebugInterface.cpp
diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp
index 3f1ba69e06..68aaada219 100644
--- a/Source/Core/Core/ConfigManager.cpp
+++ b/Source/Core/Core/ConfigManager.cpp
@@ -284,6 +284,7 @@ void SConfig::SaveDSPSettings(IniFile& ini)
dsp->Set("EnableJIT", m_DSPEnableJIT);
dsp->Set("DumpAudio", m_DumpAudio);
+ dsp->Set("DumpUCode", m_DumpUCode);
dsp->Set("Backend", sBackend);
dsp->Set("Volume", m_Volume);
dsp->Set("CaptureLog", m_DSPCaptureLog);
@@ -543,6 +544,7 @@ void SConfig::LoadDSPSettings(IniFile& ini)
dsp->Get("EnableJIT", &m_DSPEnableJIT, true);
dsp->Get("DumpAudio", &m_DumpAudio, false);
+ dsp->Get("DumpUCode", &m_DumpUCode, false);
#if defined __linux__ && HAVE_ALSA
dsp->Get("Backend", &sBackend, BACKEND_ALSA);
#elif defined __APPLE__
diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h
index b37b742e5e..caeec25152 100644
--- a/Source/Core/Core/ConfigManager.h
+++ b/Source/Core/Core/ConfigManager.h
@@ -255,6 +255,7 @@ struct SConfig : NonCopyable
bool m_DSPCaptureLog;
bool m_DumpAudio;
bool m_IsMuted;
+ bool m_DumpUCode;
int m_Volume;
std::string sBackend;
diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj
index eaba8a5f96..335d505c62 100644
--- a/Source/Core/Core/Core.vcxproj
+++ b/Source/Core/Core/Core.vcxproj
@@ -108,9 +108,6 @@
-
-
-
diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters
index b35c0caa9c..92d88bc479 100644
--- a/Source/Core/Core/Core.vcxproj.filters
+++ b/Source/Core/Core/Core.vcxproj.filters
@@ -336,15 +336,6 @@
HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
-
- HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
-
-
- HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
-
-
- HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
-
HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
diff --git a/Source/Core/Core/HW/DSPHLE/MailHandler.cpp b/Source/Core/Core/HW/DSPHLE/MailHandler.cpp
index 3334626ac0..18ab8ee4c2 100644
--- a/Source/Core/Core/HW/DSPHLE/MailHandler.cpp
+++ b/Source/Core/Core/HW/DSPHLE/MailHandler.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include "Common/ChunkFile.h"
+#include "Core/HW/DSP.h"
#include "Core/HW/DSPHLE/MailHandler.h"
CMailHandler::CMailHandler()
@@ -14,9 +15,20 @@ CMailHandler::~CMailHandler()
Clear();
}
-void CMailHandler::PushMail(u32 _Mail)
+void CMailHandler::PushMail(u32 _Mail, bool interrupt)
{
- m_Mails.push(_Mail);
+ if (interrupt)
+ {
+ if (m_Mails.empty())
+ {
+ DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
+ }
+ else
+ {
+ m_Mails.front().second = true;
+ }
+ }
+ m_Mails.emplace(_Mail, false);
DEBUG_LOG(DSP_MAIL, "DSP writes 0x%08x", _Mail);
}
@@ -25,7 +37,7 @@ u16 CMailHandler::ReadDSPMailboxHigh()
// check if we have a mail for the core
if (!m_Mails.empty())
{
- u16 result = (m_Mails.front() >> 16) & 0xFFFF;
+ u16 result = (m_Mails.front().first >> 16) & 0xFFFF;
return result;
}
return 0x00;
@@ -36,8 +48,15 @@ u16 CMailHandler::ReadDSPMailboxLow()
// check if we have a mail for the core
if (!m_Mails.empty())
{
- u16 result = m_Mails.front() & 0xFFFF;
+ u16 result = m_Mails.front().first & 0xFFFF;
+ bool generate_interrupt = m_Mails.front().second;
m_Mails.pop();
+
+ if (generate_interrupt)
+ {
+ DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
+ }
+
return result;
}
return 0x00;
@@ -59,7 +78,7 @@ void CMailHandler::Halt(bool _Halt)
if (_Halt)
{
Clear();
- m_Mails.push(0x80544348);
+ PushMail(0x80544348);
}
}
@@ -73,21 +92,25 @@ void CMailHandler::DoState(PointerWrap &p)
for (int i = 0; i < sz; i++)
{
u32 mail = 0;
+ bool interrupt = false;
p.Do(mail);
- m_Mails.push(mail);
+ p.Do(interrupt);
+ m_Mails.emplace(mail, interrupt);
}
}
else // WRITE and MEASURE
{
- std::queue temp;
+ std::queue> temp;
int sz = (int)m_Mails.size();
p.Do(sz);
for (int i = 0; i < sz; i++)
{
- u32 value = m_Mails.front();
+ u32 value = m_Mails.front().first;
+ bool interrupt = m_Mails.front().second;
m_Mails.pop();
p.Do(value);
- temp.push(value);
+ p.Do(interrupt);
+ temp.emplace(value, interrupt);
}
if (!m_Mails.empty())
PanicAlert("CMailHandler::DoState - WTF?");
@@ -95,9 +118,10 @@ void CMailHandler::DoState(PointerWrap &p)
// Restore queue.
for (int i = 0; i < sz; i++)
{
- u32 value = temp.front();
+ u32 value = temp.front().first;
+ bool interrupt = temp.front().second;
temp.pop();
- m_Mails.push(value);
+ m_Mails.emplace(value, interrupt);
}
}
}
diff --git a/Source/Core/Core/HW/DSPHLE/MailHandler.h b/Source/Core/Core/HW/DSPHLE/MailHandler.h
index 40ecaec953..41d5f77c89 100644
--- a/Source/Core/Core/HW/DSPHLE/MailHandler.h
+++ b/Source/Core/Core/HW/DSPHLE/MailHandler.h
@@ -5,6 +5,8 @@
#pragma once
#include
+#include
+
#include "Common/CommonTypes.h"
class PointerWrap;
@@ -15,7 +17,7 @@ public:
CMailHandler();
~CMailHandler();
- void PushMail(u32 _Mail);
+ void PushMail(u32 _Mail, bool interrupt = false);
void Clear();
void Halt(bool _Halt);
void DoState(PointerWrap &p);
@@ -24,20 +26,7 @@ public:
u16 ReadDSPMailboxHigh();
u16 ReadDSPMailboxLow();
- u32 GetNextMail() const
- {
- if (!m_Mails.empty())
- {
- return m_Mails.front();
- }
- else
- {
- // WARN_LOG(DSPHLE, "GetNextMail: No mails");
- return 0;
- }
- }
-
private:
// mail handler
- std::queue m_Mails;
+ std::queue> m_Mails;
};
diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/GBA.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/GBA.cpp
index a15f6e2af1..1c6d50674f 100644
--- a/Source/Core/Core/HW/DSPHLE/UCodes/GBA.cpp
+++ b/Source/Core/Core/HW/DSPHLE/UCodes/GBA.cpp
@@ -7,6 +7,81 @@
#include "Core/HW/DSPHLE/UCodes/GBA.h"
#include "Core/HW/DSPHLE/UCodes/UCodes.h"
+void ProcessGBACrypto(u32 address)
+{
+ struct sec_params_t
+ {
+ u16 key[2];
+ u16 unk1[2];
+ u16 unk2[2];
+ u32 length;
+ u32 dest_addr;
+ u32 pad[3];
+ } sec_params;
+
+ // 32 bytes from mram addr to DRAM @ 0
+ for (int i = 0; i < 8; i++, address += 4)
+ ((u32*)&sec_params)[i] = HLEMemory_Read_U32(address);
+
+ // This is the main decrypt routine
+ u16 x11 = 0, x12 = 0,
+ x20 = 0, x21 = 0, x22 = 0, x23 = 0;
+
+ x20 = Common::swap16(sec_params.key[0]) ^ 0x6f64;
+ x21 = Common::swap16(sec_params.key[1]) ^ 0x6573;
+
+ s16 unk2 = (s8)sec_params.unk2[0];
+ if (unk2 < 0)
+ {
+ x11 = ((~unk2 + 3) << 1) | (sec_params.unk1[0] << 4);
+ }
+ else if (unk2 == 0)
+ {
+ x11 = (sec_params.unk1[0] << 1) | 0x70;
+ }
+ else // unk2 > 0
+ {
+ x11 = ((unk2 - 1) << 1) | (sec_params.unk1[0] << 4);
+ }
+
+ s32 rounded_sub = ((sec_params.length + 7) & ~7) - 0x200;
+ u16 size = (rounded_sub < 0) ? 0 : rounded_sub >> 3;
+
+ u32 t = (((size << 16) | 0x3f80) & 0x3f80ffff) << 1;
+ s16 t_low = (s8)(t >> 8);
+ t += (t_low & size) << 16;
+ x12 = t >> 16;
+ x11 |= (size & 0x4000) >> 14; // this would be stored in ac0.h if we weren't constrained to 32bit :)
+ t = ((x11 & 0xff) << 16) + ((x12 & 0xff) << 16) + (x12 << 8);
+
+ u16 final11 = 0, final12 = 0;
+ final11 = x11 | ((t >> 8) & 0xff00) | 0x8080;
+ final12 = x12 | 0x8080;
+
+ if ((final12 & 0x200) != 0)
+ {
+ x22 = final11 ^ 0x6f64;
+ x23 = final12 ^ 0x6573;
+ }
+ else
+ {
+ x22 = final11 ^ 0x6177;
+ x23 = final12 ^ 0x614b;
+ }
+
+ // Send the result back to mram
+ *(u32*)HLEMemory_Get_Pointer(sec_params.dest_addr) = Common::swap32((x20 << 16) | x21);
+ *(u32*)HLEMemory_Get_Pointer(sec_params.dest_addr+4) = Common::swap32((x22 << 16) | x23);
+
+ // Done!
+ DEBUG_LOG(DSPHLE, "\n%08x -> key: %08x, len: %08x, dest_addr: %08x, unk1: %08x, unk2: %08x"
+ " 22: %04x, 23: %04x",
+ address,
+ *(u32*)sec_params.key, sec_params.length, sec_params.dest_addr,
+ *(u32*)sec_params.unk1, *(u32*)sec_params.unk2,
+ x22, x23);
+}
+
GBAUCode::GBAUCode(DSPHLE *dsphle, u32 crc)
: UCodeInterface(dsphle, crc)
{
@@ -48,79 +123,8 @@ void GBAUCode::HandleMail(u32 mail)
else if (nextmail_is_mramaddr)
{
nextmail_is_mramaddr = false;
- u32 mramaddr = mail;
- struct sec_params_t
- {
- u16 key[2];
- u16 unk1[2];
- u16 unk2[2];
- u32 length;
- u32 dest_addr;
- u32 pad[3];
- } sec_params;
-
- // 32 bytes from mram addr to DRAM @ 0
- for (int i = 0; i < 8; i++, mramaddr += 4)
- ((u32*)&sec_params)[i] = HLEMemory_Read_U32(mramaddr);
-
- // This is the main decrypt routine
- u16 x11 = 0, x12 = 0,
- x20 = 0, x21 = 0, x22 = 0, x23 = 0;
-
- x20 = Common::swap16(sec_params.key[0]) ^ 0x6f64;
- x21 = Common::swap16(sec_params.key[1]) ^ 0x6573;
-
- s16 unk2 = (s8)sec_params.unk2[0];
- if (unk2 < 0)
- {
- x11 = ((~unk2 + 3) << 1) | (sec_params.unk1[0] << 4);
- }
- else if (unk2 == 0)
- {
- x11 = (sec_params.unk1[0] << 1) | 0x70;
- }
- else // unk2 > 0
- {
- x11 = ((unk2 - 1) << 1) | (sec_params.unk1[0] << 4);
- }
-
- s32 rounded_sub = ((sec_params.length + 7) & ~7) - 0x200;
- u16 size = (rounded_sub < 0) ? 0 : rounded_sub >> 3;
-
- u32 t = (((size << 16) | 0x3f80) & 0x3f80ffff) << 1;
- s16 t_low = (s8)(t >> 8);
- t += (t_low & size) << 16;
- x12 = t >> 16;
- x11 |= (size & 0x4000) >> 14; // this would be stored in ac0.h if we weren't constrained to 32bit :)
- t = ((x11 & 0xff) << 16) + ((x12 & 0xff) << 16) + (x12 << 8);
-
- u16 final11 = 0, final12 = 0;
- final11 = x11 | ((t >> 8) & 0xff00) | 0x8080;
- final12 = x12 | 0x8080;
-
- if ((final12 & 0x200) != 0)
- {
- x22 = final11 ^ 0x6f64;
- x23 = final12 ^ 0x6573;
- }
- else
- {
- x22 = final11 ^ 0x6177;
- x23 = final12 ^ 0x614b;
- }
-
- // Send the result back to mram
- *(u32*)HLEMemory_Get_Pointer(sec_params.dest_addr) = Common::swap32((x20 << 16) | x21);
- *(u32*)HLEMemory_Get_Pointer(sec_params.dest_addr+4) = Common::swap32((x22 << 16) | x23);
-
- // Done!
- DEBUG_LOG(DSPHLE, "\n%08x -> key: %08x, len: %08x, dest_addr: %08x, unk1: %08x, unk2: %08x"
- " 22: %04x, 23: %04x",
- mramaddr,
- *(u32*)sec_params.key, sec_params.length, sec_params.dest_addr,
- *(u32*)sec_params.unk1, *(u32*)sec_params.unk2,
- x22, x23);
+ ProcessGBACrypto(mail);
calc_done = true;
m_mail_handler.PushMail(DSP_DONE);
diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/GBA.h b/Source/Core/Core/HW/DSPHLE/UCodes/GBA.h
index 50530134bf..751750cce1 100644
--- a/Source/Core/Core/HW/DSPHLE/UCodes/GBA.h
+++ b/Source/Core/Core/HW/DSPHLE/UCodes/GBA.h
@@ -6,6 +6,11 @@
#include "Core/HW/DSPHLE/UCodes/UCodes.h"
+// Computes two 32 bit integers to be returned to the game, based on the
+// provided crypto parameters at the provided MRAM address. The integers are
+// written back to RAM at the dest address provided in the crypto parameters.
+void ProcessGBACrypto(u32 address);
+
struct GBAUCode : public UCodeInterface
{
GBAUCode(DSPHLE *dsphle, u32 crc);
diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/INIT.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/INIT.cpp
index face5fb454..e6be9773bb 100644
--- a/Source/Core/Core/HW/DSPHLE/UCodes/INIT.cpp
+++ b/Source/Core/Core/HW/DSPHLE/UCodes/INIT.cpp
@@ -10,6 +10,7 @@ INITUCode::INITUCode(DSPHLE *dsphle, u32 crc)
: UCodeInterface(dsphle, crc)
{
DEBUG_LOG(DSPHLE, "INITUCode - initialized");
+ m_mail_handler.PushMail(0x80544348);
}
INITUCode::~INITUCode()
@@ -22,11 +23,6 @@ void INITUCode::Init()
void INITUCode::Update()
{
- if (m_mail_handler.IsEmpty())
- {
- m_mail_handler.PushMail(0x80544348);
- // HALT
- }
}
u32 INITUCode::GetUpdateMs()
diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/ROM.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/ROM.cpp
index 6492fd2d1d..0d54b6efa0 100644
--- a/Source/Core/Core/HW/DSPHLE/UCodes/ROM.cpp
+++ b/Source/Core/Core/HW/DSPHLE/UCodes/ROM.cpp
@@ -92,17 +92,18 @@ void ROMUCode::BootUCode()
(u8*)HLEMemory_Get_Pointer(m_current_ucode.m_ram_address),
m_current_ucode.m_length);
-#if defined(_DEBUG) || defined(DEBUGFAST)
- std::string ucode_dump_path = StringFromFormat(
- "%sDSP_UC_%08X.bin", File::GetUserPath(D_DUMPDSP_IDX).c_str(), ector_crc);
-
- File::IOFile fp(ucode_dump_path, "wb");
- if (fp)
+ if (SConfig::GetInstance().m_DumpUCode)
{
- fp.WriteArray((u8*)HLEMemory_Get_Pointer(m_current_ucode.m_ram_address),
- m_current_ucode.m_length);
+ std::string ucode_dump_path = StringFromFormat(
+ "%sDSP_UC_%08X.bin", File::GetUserPath(D_DUMPDSP_IDX).c_str(), ector_crc);
+
+ File::IOFile fp(ucode_dump_path, "wb");
+ if (fp)
+ {
+ fp.WriteArray((u8*)HLEMemory_Get_Pointer(m_current_ucode.m_ram_address),
+ m_current_ucode.m_length);
+ }
}
-#endif
DEBUG_LOG(DSPHLE, "CurrentUCode SOURCE Addr: 0x%08x", m_current_ucode.m_ram_address);
DEBUG_LOG(DSPHLE, "CurrentUCode Length: 0x%08x", m_current_ucode.m_length);
diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/UCodes.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/UCodes.cpp
index f3c17a69e2..2ecb0ec270 100644
--- a/Source/Core/Core/HW/DSPHLE/UCodes/UCodes.cpp
+++ b/Source/Core/Core/HW/DSPHLE/UCodes/UCodes.cpp
@@ -9,6 +9,7 @@
#include "Common/Hash.h"
#include "Common/StringUtil.h"
+#include "Core/ConfigManager.h"
#include "Core/HW/DSPHLE/UCodes/AX.h"
#include "Core/HW/DSPHLE/UCodes/AXWii.h"
#include "Core/HW/DSPHLE/UCodes/CARD.h"
@@ -51,27 +52,18 @@ UCodeInterface* UCodeFactory(u32 crc, DSPHLE* dsphle, bool wii)
INFO_LOG(DSPHLE, "CRC %08x: AX ucode chosen", crc);
return new AXUCode(dsphle, crc);
- case 0x6ba3b3ea: // IPL - PAL
- case 0x24b22038: // IPL - NTSC/NTSC-JAP
- case 0x42f64ac4: // Luigi's Mansion
- case 0x4be6a5cb: // AC, Pikmin
- INFO_LOG(DSPHLE, "CRC %08x: JAC (early Zelda) ucode chosen", crc);
- return new ZeldaUCode(dsphle, crc);
-
- case 0x6CA33A6D: // DK Jungle Beat
case 0x86840740: // Zelda WW - US
- case 0x56d36052: // Mario Sunshine
- case 0x2fcdf1ec: // Mario Kart, Zelda 4 Swords
- case 0x267fd05a: // Pikmin PAL
- INFO_LOG(DSPHLE, "CRC %08x: Zelda ucode chosen", crc);
- return new ZeldaUCode(dsphle, crc);
-
- // Wii CRCs
- case 0xb7eb9a9c: // Wii Pikmin - PAL
- case 0xeaeb38cc: // Wii Pikmin 2 - PAL
- case 0x6c3f6f94: // Zelda TP - PAL
- case 0xd643001f: // Mario Galaxy - PAL / Wii DK Jungle Beat - PAL
- INFO_LOG(DSPHLE, "CRC %08x: Zelda Wii ucode chosen\n", crc);
+ case 0x6ca33a6d: // Zelda TP GC - US
+ case 0xd643001f: // Super Mario Galaxy - US
+ case 0x6ba3b3ea: // GC IPL - PAL
+ case 0x24b22038: // GC IPL - US
+ case 0x2fcdf1ec: // Zelda FSA - US
+ case 0x4be6a5cb: // Pikmin 1 GC - US
+ case 0x267fd05a: // Pikmin 1 GC - PAL
+ case 0x42f64ac4: // Luigi's Mansion - US
+ case 0x56d36052: // Super Mario Sunshine - US
+ case 0x6c3f6f94: // Zelda TP Wii - US
+ case 0xeaeb38cc: // Pikmin 1/2 New Play Control Wii - US
return new ZeldaUCode(dsphle, crc);
case 0x2ea36ce6: // Some Wii demos
@@ -142,17 +134,18 @@ void UCodeInterface::PrepareBootUCode(u32 mail)
(u8*)HLEMemory_Get_Pointer(m_next_ucode.iram_mram_addr),
m_next_ucode.iram_size);
-#if defined(_DEBUG) || defined(DEBUGFAST)
- std::string ucode_dump_path = StringFromFormat(
- "%sDSP_UC_%08X.bin", File::GetUserPath(D_DUMPDSP_IDX).c_str(), ector_crc);
-
- File::IOFile fp(ucode_dump_path, "wb");
- if (fp)
+ if (SConfig::GetInstance().m_DumpUCode)
{
- fp.WriteArray((u8*)Memory::GetPointer(m_next_ucode.iram_mram_addr),
- m_next_ucode.iram_size);
+ std::string ucode_dump_path = StringFromFormat(
+ "%sDSP_UC_%08X.bin", File::GetUserPath(D_DUMPDSP_IDX).c_str(), ector_crc);
+
+ File::IOFile fp(ucode_dump_path, "wb");
+ if (fp)
+ {
+ fp.WriteArray((u8*)Memory::GetPointer(m_next_ucode.iram_mram_addr),
+ m_next_ucode.iram_size);
+ }
}
-#endif
DEBUG_LOG(DSPHLE, "PrepareBootUCode 0x%08x", ector_crc);
DEBUG_LOG(DSPHLE, "DRAM -> MRAM: src %04x dst %08x size %04x",
diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.cpp
index c18b8a6ce9..efebb44f8b 100644
--- a/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.cpp
+++ b/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.cpp
@@ -2,515 +2,135 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
-// Games that uses this UCode:
-// Zelda: The Windwaker, Mario Sunshine, Mario Kart, Twilight Princess,
-// Super Mario Galaxy
-
#include "Core/ConfigManager.h"
-#include "Core/HW/DSP.h"
#include "Core/HW/DSPHLE/MailHandler.h"
+#include "Core/HW/DSPHLE/UCodes/GBA.h"
#include "Core/HW/DSPHLE/UCodes/UCodes.h"
#include "Core/HW/DSPHLE/UCodes/Zelda.h"
+// Uncomment this to have a strict version of the HLE implementation, which
+// PanicAlerts on recoverable unknown behaviors instead of silently ignoring
+// them. Recommended for development.
+// #define STRICT_ZELDA_HLE 1
+
+// These flags modify the behavior of the HLE implementation based on the UCode
+// version. When introducing a new flag, please recheck the behavior of each
+// UCode version.
+enum ZeldaUCodeFlag
+{
+ // UCode for Wii where no ARAM is present. Instead of using ARAM, DMAs from
+ // MRAM are used to transfer sound data.
+ NO_ARAM = 0x00000001,
+
+ // Multiply by two the computed Dolby positional volumes. Some UCodes do
+ // not do that (Zelda TWW for example), others do (Zelda TP, SMG).
+ MAKE_DOLBY_LOUDER = 0x00000002,
+
+ // Light version of the UCode: no Dolby mixing, different synchronization
+ // protocol, etc.
+ LIGHT_PROTOCOL = 0x00000004,
+
+ // If set, only consider 4 of the 6 non-Dolby mixing outputs. Early
+ // versions of the Zelda UCode only had 4.
+ FOUR_MIXING_DESTS = 0x00000008,
+
+ // Handle smaller VPBs that are missing their 0x40-0x80 area. Very early
+ // versions of the Zelda UCode used 0x80 sized VPBs.
+ TINY_VPB = 0x00000010,
+
+ // If set, interpret non-Dolby mixing parameters as step/current volume
+ // instead of target/current volume.
+ VOLUME_EXPLICIT_STEP = 0x00000020,
+
+ // If set, handle synchronization per-frame instead of per-16-voices.
+ SYNC_PER_FRAME = 0x00000040,
+
+ // If set, does not support command 0D. TODO: rename.
+ NO_CMD_0D = 0x00000080,
+
+ // If set, command 0C is used for GBA crypto. This was used before the GBA
+ // UCode and UCode switching was available.
+ SUPPORTS_GBA_CRYPTO = 0x00000100,
+
+ // If set, command 0C is used for an unknown purpose. TODO: rename.
+ WEIRD_CMD_0C = 0x00000200,
+};
+
+static const std::map UCODE_FLAGS = {
+ // GameCube IPL/BIOS, NTSC.
+ { 0x24B22038, LIGHT_PROTOCOL | FOUR_MIXING_DESTS | TINY_VPB |
+ VOLUME_EXPLICIT_STEP | NO_CMD_0D | WEIRD_CMD_0C },
+ // GameCube IPL/BIOS, PAL.
+ { 0x6BA3B3EA, LIGHT_PROTOCOL | FOUR_MIXING_DESTS | NO_CMD_0D |
+ WEIRD_CMD_0C },
+ // Pikmin 1 GC NTSC.
+ // Animal Crossing.
+ { 0x4BE6A5CB, LIGHT_PROTOCOL | NO_CMD_0D | SUPPORTS_GBA_CRYPTO },
+ // Luigi's Mansion.
+ { 0x42F64AC4, LIGHT_PROTOCOL | NO_CMD_0D | WEIRD_CMD_0C },
+ // Pikmin 1 GC PAL.
+ { 0x267FD05A, SYNC_PER_FRAME | NO_CMD_0D },
+ // Super Mario Sunshine.
+ { 0x56D36052, SYNC_PER_FRAME | NO_CMD_0D },
+ // The Legend of Zelda: The Wind Waker.
+ { 0x86840740, 0 },
+ // The Legend of Zelda: Four Swords Adventures.
+ // Mario Kart: Double Dash.
+ // Pikmin 2 GC NTSC.
+ { 0x2FCDF1EC, MAKE_DOLBY_LOUDER },
+ // The Legend of Zelda: Twilight Princess / GC.
+ // Donkey Kong Jungle Beat.
+ //
+ // TODO: These do additional filtering at frame rendering time. We don't
+ // implement this yet.
+ { 0x6CA33A6D, MAKE_DOLBY_LOUDER },
+ // The Legend of Zelda: Twilight Princess / Wii.
+ { 0x6C3F6F94, NO_ARAM | MAKE_DOLBY_LOUDER },
+ // Super Mario Galaxy.
+ // Super Mario Galaxy 2.
+ { 0xD643001F, NO_ARAM | MAKE_DOLBY_LOUDER },
+ // Pikmin 1/2 New Play Control.
+ { 0xEAEB38CC, NO_ARAM | MAKE_DOLBY_LOUDER },
+
+ // TODO: Other games that use this UCode (exhaustive list):
+ // * Link's Crossbow Training
+ // * The Legend of Zelda: Collector's Edition
+ // * The Legend of Zelda: Twilight Princess / Wii (type ????, CRC ????)
+};
ZeldaUCode::ZeldaUCode(DSPHLE *dsphle, u32 crc)
- : UCodeInterface(dsphle, crc),
- m_sync_in_progress(false),
- m_max_voice(0),
- m_num_sync_mail(0),
- m_num_voices(0),
- m_sync_cmd_pending(false),
- m_current_voice(0),
- m_current_buffer(0),
- m_num_buffers(0),
- m_voice_pbs_addr(0),
- m_unk_table_addr(0),
- m_reverb_pbs_addr(0),
- m_right_buffers_addr(0),
- m_left_buffers_addr(0),
- m_pos(0),
- m_dma_base_addr(0),
- m_num_steps(0),
- m_list_in_progress(false),
- m_step(0),
- m_read_offset(0),
- m_mail_state(WaitForMail),
- m_num_pbs(0),
- m_pb_address(0),
- m_pb_address2(0)
+ : UCodeInterface(dsphle, crc)
{
- DEBUG_LOG(DSPHLE, "UCode_Zelda - add boot mails for handshake");
+ auto it = UCODE_FLAGS.find(crc);
+ if (it == UCODE_FLAGS.end())
+ PanicAlert("No flags definition found for Zelda CRC %08x", crc);
- if (IsLightVersion())
+ m_flags = it->second;
+ m_renderer.SetFlags(m_flags);
+
+ WARN_LOG(DSPHLE, "Zelda UCode loaded, crc=%08x, flags=%08x", crc, m_flags);
+
+ if (m_flags & LIGHT_PROTOCOL)
{
- DEBUG_LOG(DSPHLE, "Luigi Stylee!");
m_mail_handler.PushMail(0x88881111);
}
else
{
- m_mail_handler.PushMail(DSP_INIT);
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
+ m_mail_handler.PushMail(DSP_INIT, true);
m_mail_handler.PushMail(0xF3551111); // handshake
}
-
- m_voice_buffer = new s32[256 * 1024];
- m_resample_buffer = new s16[256 * 1024];
- m_left_buffer = new s32[256 * 1024];
- m_right_buffer = new s32[256 * 1024];
-
- memset(m_buffer, 0, sizeof(m_buffer));
- memset(m_sync_flags, 0, sizeof(m_sync_flags));
- memset(m_afc_coef_table, 0, sizeof(m_afc_coef_table));
- memset(m_pb_mask, 0, sizeof(m_pb_mask));
}
ZeldaUCode::~ZeldaUCode()
{
m_mail_handler.Clear();
-
- delete [] m_voice_buffer;
- delete [] m_resample_buffer;
- delete [] m_left_buffer;
- delete [] m_right_buffer;
-}
-
-u8 *ZeldaUCode::GetARAMPointer(u32 address)
-{
- if (IsDMAVersion())
- return Memory::GetPointer(m_dma_base_addr) + address;
- else
- return DSP::GetARAMPtr() + address;
}
void ZeldaUCode::Update()
{
- if (!IsLightVersion())
- {
- if (m_mail_handler.GetNextMail() == DSP_FRAME_END)
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
- }
-
if (NeedsResumeMail())
{
- m_mail_handler.PushMail(DSP_RESUME);
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
- }
-}
-
-void ZeldaUCode::HandleMail(u32 mail)
-{
- if (IsLightVersion())
- HandleMail_LightVersion(mail);
- else if (IsSMSVersion())
- HandleMail_SMSVersion(mail);
- else
- HandleMail_NormalVersion(mail);
-}
-
-void ZeldaUCode::HandleMail_LightVersion(u32 mail)
-{
- //ERROR_LOG(DSPHLE, "Light version mail %08X, list in progress: %s, step: %i/%i",
- // mail, m_list_in_progress ? "yes":"no", m_step, m_num_steps);
-
- if (m_sync_cmd_pending)
- {
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
-
- MixAudio();
-
- m_current_buffer++;
-
- if (m_current_buffer == m_num_buffers)
- {
- m_sync_cmd_pending = false;
- DEBUG_LOG(DSPHLE, "Update the SoundThread to be in sync");
- }
- return;
- }
-
- if (!m_list_in_progress)
- {
- switch ((mail >> 24) & 0x7F)
- {
- case 0x00: m_num_steps = 1; break; // dummy
- case 0x01: m_num_steps = 5; break; // DsetupTable
- case 0x02: m_num_steps = 3; break; // DsyncFrame
-
- default:
- {
- m_num_steps = 0;
- PanicAlert("Zelda uCode (light version): unknown/unsupported command %02X", (mail >> 24) & 0x7F);
- }
- return;
- }
-
- m_list_in_progress = true;
- m_step = 0;
- }
-
- if (m_step >= sizeof(m_buffer) / 4)
- PanicAlert("m_step out of range");
-
- ((u32*)m_buffer)[m_step] = mail;
- m_step++;
-
- if (m_step >= m_num_steps)
- {
- ExecuteList();
- m_list_in_progress = false;
- }
-}
-
-void ZeldaUCode::HandleMail_SMSVersion(u32 mail)
-{
- if (m_sync_in_progress)
- {
- if (m_sync_cmd_pending)
- {
- m_sync_flags[(m_num_sync_mail << 1) ] = mail >> 16;
- m_sync_flags[(m_num_sync_mail << 1) + 1] = mail & 0xFFFF;
-
- m_num_sync_mail++;
- if (m_num_sync_mail == 2)
- {
- m_num_sync_mail = 0;
- m_sync_in_progress = false;
-
- MixAudio();
-
- m_current_buffer++;
-
- m_mail_handler.PushMail(DSP_SYNC);
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
- m_mail_handler.PushMail(0xF355FF00 | m_current_buffer);
-
- if (m_current_buffer == m_num_buffers)
- {
- m_mail_handler.PushMail(DSP_FRAME_END);
- // DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
-
- m_sync_cmd_pending = false;
- }
- }
- }
- else
- {
- m_sync_in_progress = false;
- }
-
- return;
- }
-
- if (m_list_in_progress)
- {
- if (m_step >= sizeof(m_buffer) / 4)
- PanicAlert("m_step out of range");
-
- ((u32*)m_buffer)[m_step] = mail;
- m_step++;
-
- if (m_step >= m_num_steps)
- {
- ExecuteList();
- m_list_in_progress = false;
- }
-
- return;
- }
-
- // Here holds: m_sync_in_progress == false && m_list_in_progress == false
-
- if (mail == 0)
- {
- m_sync_in_progress = true;
- m_num_sync_mail = 0;
- }
- else if ((mail >> 16) == 0)
- {
- m_list_in_progress = true;
- m_num_steps = mail;
- m_step = 0;
- }
- else if ((mail >> 16) == 0xCDD1) // A 0xCDD1000X mail should come right after we send a DSP_SYNCEND mail
- {
- // The low part of the mail tells the operation to perform
- // Seeing as every possible operation number halts the uCode,
- // except 3, that thing seems to be intended for debugging
- switch (mail & 0xFFFF)
- {
- case 0x0003: // Do nothing
- return;
-
- case 0x0000: // Halt
- case 0x0001: // Dump memory? and halt
- case 0x0002: // Do something and halt
- WARN_LOG(DSPHLE, "Zelda uCode(SMS version): received halting operation %04X", mail & 0xFFFF);
- return;
-
- default: // Invalid (the real ucode would likely crash)
- WARN_LOG(DSPHLE, "Zelda uCode(SMS version): received invalid operation %04X", mail & 0xFFFF);
- return;
- }
- }
- else
- {
- WARN_LOG(DSPHLE, "Zelda uCode (SMS version): unknown mail %08X", mail);
- }
-}
-
-void ZeldaUCode::HandleMail_NormalVersion(u32 mail)
-{
- // WARN_LOG(DSPHLE, "Zelda uCode: Handle mail %08X", mail);
-
- if (m_upload_setup_in_progress) // evaluated first!
- {
- PrepareBootUCode(mail);
- return;
- }
-
- if (m_sync_in_progress)
- {
- if (m_sync_cmd_pending)
- {
- u32 n = (mail >> 16) & 0xF;
- m_max_voice = (n + 1) << 4;
- m_sync_flags[n] = mail & 0xFFFF;
- m_sync_in_progress = false;
-
- m_current_voice = m_max_voice;
-
- if (m_current_voice >= m_num_voices)
- {
- MixAudio();
-
- m_current_buffer++;
-
- m_mail_handler.PushMail(DSP_SYNC);
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
- m_mail_handler.PushMail(0xF355FF00 | m_current_buffer);
-
- m_current_voice = 0;
-
- if (m_current_buffer == m_num_buffers)
- {
- if (!IsDMAVersion()) // this is a hack... without it Pikmin 1 Wii/ Zelda TP Wii mail-s stopped
- m_mail_handler.PushMail(DSP_FRAME_END);
- //g_dspInitialize.pGenerateDSPInterrupt();
-
- m_sync_cmd_pending = false;
- }
- }
- }
- else
- {
- m_sync_in_progress = false;
- }
-
- return;
- }
-
- if (m_list_in_progress)
- {
- if (m_step >= sizeof(m_buffer) / 4)
- PanicAlert("m_step out of range");
-
- ((u32*)m_buffer)[m_step] = mail;
- m_step++;
-
- if (m_step >= m_num_steps)
- {
- ExecuteList();
- m_list_in_progress = false;
- }
-
- return;
- }
-
- // Here holds: m_sync_in_progress == false && m_list_in_progress == false
-
- // Zelda-only mails:
- // - 0000XXXX - Begin list
- // - 00000000, 000X0000 - Sync mails
- // - CDD1XXXX - comes after DsyncFrame completed, seems to be debugging stuff
-
- if (mail == 0)
- {
- m_sync_in_progress = true;
- }
- else if ((mail >> 16) == 0)
- {
- m_list_in_progress = true;
- m_num_steps = mail;
- m_step = 0;
- }
- else if ((mail >> 16) == 0xCDD1) // A 0xCDD1000X mail should come right after we send a DSP_FRAME_END mail
- {
- // The low part of the mail tells the operation to perform
- // Seeing as every possible operation number halts the uCode,
- // except 3, that thing seems to be intended for debugging
- switch (mail & 0xFFFF)
- {
- case 0x0003: // Do nothing - continue normally
- return;
-
- case 0x0001: // accepts params to either DMA to iram and/or DRAM (used for hotbooting a new ucode)
- // TODO find a better way to protect from HLEMixer?
- m_upload_setup_in_progress = true;
- return;
-
- case 0x0002: // Let IROM play us off
- m_dsphle->SetUCode(UCODE_ROM);
- return;
-
- case 0x0000: // Halt
- WARN_LOG(DSPHLE, "Zelda uCode: received halting operation %04X", mail & 0xFFFF);
- return;
-
- default: // Invalid (the real ucode would likely crash)
- WARN_LOG(DSPHLE, "Zelda uCode: received invalid operation %04X", mail & 0xFFFF);
- return;
- }
- }
- else
- {
- WARN_LOG(DSPHLE, "Zelda uCode: unknown mail %08X", mail);
- }
-}
-
-// zelda debug ..803F6418
-void ZeldaUCode::ExecuteList()
-{
- // begin with the list
- m_read_offset = 0;
-
- u32 cmd_mail = Read32();
- u32 command = (cmd_mail >> 24) & 0x7f;
- u32 sync;
- u32 extra_data = cmd_mail & 0xFFFF;
-
- if (IsLightVersion())
- sync = 0x62 + (command << 1); // seen in DSP_UC_Luigi.txt
- else
- sync = cmd_mail >> 16;
-
- DEBUG_LOG(DSPHLE, "==============================================================================");
- DEBUG_LOG(DSPHLE, "Zelda UCode - execute dlist (command: 0x%04x : sync: 0x%04x)", command, sync);
-
- switch (command)
- {
- // dummy
- case 0x00: break;
-
- // DsetupTable ... zelda ww jumps to 0x0095
- case 0x01:
- {
- m_num_voices = extra_data;
- m_voice_pbs_addr = Read32() & 0x7FFFFFFF;
- m_unk_table_addr = Read32() & 0x7FFFFFFF;
- m_afc_coef_table_addr = Read32() & 0x7FFFFFFF;
- m_reverb_pbs_addr = Read32() & 0x7FFFFFFF; // WARNING: reverb PBs are very different from voice PBs!
-
- // Read the other table
- u16 *tmp_ptr = (u16*)Memory::GetPointer(m_unk_table_addr);
- for (int i = 0; i < 0x280; i++)
- m_misc_table[i] = (s16)Common::swap16(tmp_ptr[i]);
-
- // Read AFC coef table
- tmp_ptr = (u16*)Memory::GetPointer(m_afc_coef_table_addr);
- for (int i = 0; i < 32; i++)
- m_afc_coef_table[i] = (s16)Common::swap16(tmp_ptr[i]);
-
- DEBUG_LOG(DSPHLE, "DsetupTable");
- DEBUG_LOG(DSPHLE, "Num voice param blocks: %i", m_num_voices);
- DEBUG_LOG(DSPHLE, "Voice param blocks address: 0x%08x", m_voice_pbs_addr);
-
- // This points to some strange data table. Don't know yet what it's for. Reverb coefs?
- DEBUG_LOG(DSPHLE, "DSPRES_FILTER (size: 0x40): 0x%08x", m_unk_table_addr);
-
- // Zelda WW: This points to a 64-byte array of coefficients, which are EXACTLY the same
- // as the AFC ADPCM coef array in decode.c of the in_cube winamp plugin,
- // which can play Zelda audio. So, these should definitely be used when decoding AFC.
- DEBUG_LOG(DSPHLE, "DSPADPCM_FILTER (size: 0x500): 0x%08x", m_afc_coef_table_addr);
- DEBUG_LOG(DSPHLE, "Reverb param blocks address: 0x%08x", m_reverb_pbs_addr);
- }
- break;
-
- // SyncFrame ... zelda ww jumps to 0x0243
- case 0x02:
- {
- m_sync_cmd_pending = true;
-
- m_current_buffer = 0;
- m_num_buffers = (cmd_mail >> 16) & 0xFF;
-
- // Addresses for right & left buffers in main memory
- // Each buffer is 160 bytes long. The number of (both left & right) buffers
- // is set by the first mail of the list.
- m_left_buffers_addr = Read32() & 0x7FFFFFFF;
- m_right_buffers_addr = Read32() & 0x7FFFFFFF;
-
- DEBUG_LOG(DSPHLE, "DsyncFrame");
- // These alternate between three sets of mixing buffers. They are all three fairly near,
- // but not at, the ADMA read addresses.
- DEBUG_LOG(DSPHLE, "Right buffer address: 0x%08x", m_right_buffers_addr);
- DEBUG_LOG(DSPHLE, "Left buffer address: 0x%08x", m_left_buffers_addr);
-
- if (IsLightVersion())
- break;
- else
- return;
- }
-
-
- // Simply sends the sync messages
- case 0x03: break;
-
-/* case 0x04: break; // dunno ... zelda ww jmps to 0x0580
- case 0x05: break; // dunno ... zelda ww jmps to 0x0592
- case 0x06: break; // dunno ... zelda ww jmps to 0x0469
- case 0x07: break; // dunno ... zelda ww jmps to 0x044d
- case 0x08: break; // Mixer ... zelda ww jmps to 0x0485
- case 0x09: break; // dunno ... zelda ww jmps to 0x044d
- */
-
- // DsetDolbyDelay ... zelda ww jumps to 0x00b2
- case 0x0d:
- {
- u32 tmp = Read32();
- DEBUG_LOG(DSPHLE, "DSetDolbyDelay");
- DEBUG_LOG(DSPHLE, "DOLBY2_DELAY_BUF (size 0x960): 0x%08x", tmp);
- }
- break;
-
- // This opcode, in the SMG ucode, sets the base address for audio data transfers from main memory (using DMA).
- // In the Zelda ucode, it is dummy, because this ucode uses accelerator for audio data transfers.
- case 0x0e:
- {
- m_dma_base_addr = Read32() & 0x7FFFFFFF;
- DEBUG_LOG(DSPHLE, "DsetDMABaseAddr");
- DEBUG_LOG(DSPHLE, "DMA base address: 0x%08x", m_dma_base_addr);
- }
- break;
-
- // default ... zelda ww jumps to 0x0043
- default:
- PanicAlert("Zelda UCode - unknown command: %x (size %i)", command, m_num_steps);
- break;
- }
-
- // sync, we are ready
- if (IsLightVersion())
- {
- if (m_sync_cmd_pending)
- m_mail_handler.PushMail(0x80000000 | m_num_buffers); // after CMD_2
- else
- m_mail_handler.PushMail(0x80000000 | sync); // after CMD_0, CMD_1
- }
- else
- {
- m_mail_handler.PushMail(DSP_SYNC);
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
- m_mail_handler.PushMail(0xF3550000 | sync);
+ m_mail_handler.PushMail(DSP_RESUME, true);
}
}
@@ -521,46 +141,1653 @@ u32 ZeldaUCode::GetUpdateMs()
void ZeldaUCode::DoState(PointerWrap &p)
{
- p.Do(m_afc_coef_table);
- p.Do(m_misc_table);
+ p.Do(m_flags);
+ p.Do(m_mail_current_state);
+ p.Do(m_mail_expected_cmd_mails);
- p.Do(m_sync_in_progress);
- p.Do(m_max_voice);
- p.Do(m_sync_flags);
-
- p.Do(m_num_sync_mail);
-
- p.Do(m_num_voices);
-
- p.Do(m_sync_cmd_pending);
- p.Do(m_current_voice);
- p.Do(m_current_buffer);
- p.Do(m_num_buffers);
-
- p.Do(m_voice_pbs_addr);
- p.Do(m_unk_table_addr);
- p.Do(m_afc_coef_table_addr);
- p.Do(m_reverb_pbs_addr);
-
- p.Do(m_right_buffers_addr);
- p.Do(m_left_buffers_addr);
- p.Do(m_pos);
-
- p.Do(m_dma_base_addr);
-
- p.Do(m_num_steps);
- p.Do(m_list_in_progress);
- p.Do(m_step);
- p.Do(m_buffer);
+ p.Do(m_sync_max_voice_id);
+ p.Do(m_sync_voice_skip_flags);
+ p.Do(m_sync_flags_second_half);
+ p.Do(m_cmd_buffer);
p.Do(m_read_offset);
+ p.Do(m_write_offset);
+ p.Do(m_pending_commands_count);
+ p.Do(m_cmd_can_execute);
- p.Do(m_mail_state);
- p.Do(m_pb_mask);
+ p.Do(m_rendering_requested_frames);
+ p.Do(m_rendering_voices_per_frame);
+ p.Do(m_rendering_curr_frame);
+ p.Do(m_rendering_curr_voice);
- p.Do(m_num_pbs);
- p.Do(m_pb_address);
- p.Do(m_pb_address2);
+ m_renderer.DoState(p);
DoStateShared(p);
}
+
+void ZeldaUCode::HandleMail(u32 mail)
+{
+ if (m_upload_setup_in_progress) // evaluated first!
+ {
+ PrepareBootUCode(mail);
+ return;
+ }
+
+ if (m_flags & LIGHT_PROTOCOL)
+ HandleMailLight(mail);
+ else
+ HandleMailDefault(mail);
+}
+
+void ZeldaUCode::HandleMailDefault(u32 mail)
+{
+ switch (m_mail_current_state)
+ {
+ case MailState::WAITING:
+ if (mail & 0x80000000)
+ {
+ if ((mail >> 16) != 0xCDD1)
+ {
+ PanicAlert("Rendering end mail without prefix CDD1: %08x",
+ mail);
+ }
+
+ switch (mail & 0xFFFF)
+ {
+ case 1:
+ NOTICE_LOG(DSPHLE, "UCode being replaced.");
+ m_upload_setup_in_progress = true;
+ SetMailState(MailState::HALTED);
+ break;
+
+ case 2:
+ NOTICE_LOG(DSPHLE, "UCode being rebooted to ROM.");
+ SetMailState(MailState::HALTED);
+ m_dsphle->SetUCode(UCODE_ROM);
+ break;
+
+ case 3:
+ m_cmd_can_execute = true;
+ RunPendingCommands();
+ break;
+
+ default:
+ NOTICE_LOG(DSPHLE, "Unknown end rendering action. Halting.");
+ case 0:
+ NOTICE_LOG(DSPHLE, "UCode asked to halt. Stopping any processing.");
+ SetMailState(MailState::HALTED);
+ break;
+ }
+ }
+ else if (!(mail & 0xFFFF))
+ {
+ if (RenderingInProgress())
+ {
+ SetMailState(MailState::RENDERING);
+ }
+ else
+ {
+ NOTICE_LOG(DSPHLE, "Sync mail (%08x) received when rendering was not active. Halting.",
+ mail);
+ SetMailState(MailState::HALTED);
+ }
+ }
+ else
+ {
+ SetMailState(MailState::WRITING_CMD);
+ m_mail_expected_cmd_mails = mail & 0xFFFF;
+ }
+ break;
+
+ case MailState::RENDERING:
+ if (m_flags & SYNC_PER_FRAME)
+ {
+ int base = m_sync_flags_second_half ? 2 : 0;
+ m_sync_voice_skip_flags[base] = mail >> 16;
+ m_sync_voice_skip_flags[base + 1] = mail & 0xFFFF;
+
+ if (m_sync_flags_second_half)
+ m_sync_max_voice_id = 0xFFFF;
+
+ RenderAudio();
+ if (m_sync_flags_second_half)
+ SetMailState(MailState::WAITING);
+ m_sync_flags_second_half = !m_sync_flags_second_half;
+ }
+ else
+ {
+ m_sync_max_voice_id = (((mail >> 16) & 0xF) + 1) << 4;
+ m_sync_voice_skip_flags[(mail >> 16) & 0xFF] = mail & 0xFFFF;
+ RenderAudio();
+ SetMailState(MailState::WAITING);
+ }
+ break;
+
+ case MailState::WRITING_CMD:
+ Write32(mail);
+
+ if (--m_mail_expected_cmd_mails == 0)
+ {
+ m_pending_commands_count += 1;
+ SetMailState(MailState::WAITING);
+ RunPendingCommands();
+ }
+ break;
+
+ case MailState::HALTED:
+ WARN_LOG(DSPHLE, "Received mail %08x while we're halted.", mail);
+ break;
+ }
+}
+
+void ZeldaUCode::HandleMailLight(u32 mail)
+{
+ bool add_command = true;
+
+ switch (m_mail_current_state)
+ {
+ case MailState::WAITING:
+ if (!(mail & 0x80000000))
+ PanicAlert("Mail received in waiting state has MSB=0: %08x", mail);
+
+ // Start of a command. We have to hardcode the number of mails required
+ // for each command - the alternative is to rewrite command handling as
+ // an asynchronous procedure, and we wouldn't want that, would we?
+ Write32(mail);
+
+ switch ((mail >> 24) & 0x7F)
+ {
+ case 0x00: m_mail_expected_cmd_mails = 0; break;
+ case 0x01: m_mail_expected_cmd_mails = 4; break;
+ case 0x02: m_mail_expected_cmd_mails = 2; break;
+ // Doesn't even register as a command, just rejumps to the dispatcher.
+ // TODO: That's true on 0x4BE6A5CB and 0x42F64AC4, what about others?
+ case 0x03: add_command = false; break;
+ case 0x0C:
+ if (m_flags & SUPPORTS_GBA_CRYPTO)
+ m_mail_expected_cmd_mails = 1;
+ else if (m_flags & WEIRD_CMD_0C)
+ m_mail_expected_cmd_mails = 2;
+ else
+ m_mail_expected_cmd_mails = 0;
+ break;
+
+ default:
+ PanicAlert("Received unknown command in light protocol: %08x", mail);
+ break;
+ }
+ if (m_mail_expected_cmd_mails)
+ {
+ SetMailState(MailState::WRITING_CMD);
+ }
+ else if (add_command)
+ {
+ m_pending_commands_count += 1;
+ RunPendingCommands();
+ }
+ break;
+
+ case MailState::WRITING_CMD:
+ Write32(mail);
+ if (--m_mail_expected_cmd_mails == 0)
+ {
+ m_pending_commands_count += 1;
+ SetMailState(MailState::WAITING);
+ RunPendingCommands();
+ }
+ break;
+
+ case MailState::RENDERING:
+ if (mail != 0)
+ PanicAlert("Sync mail is not zero: %08x", mail);
+
+ // No per-voice syncing in the light protocol.
+ m_sync_max_voice_id = 0xFFFFFFFF;
+ m_sync_voice_skip_flags.fill(0xFFFF);
+ RenderAudio();
+ DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
+ break;
+
+ case MailState::HALTED:
+ WARN_LOG(DSPHLE, "Received mail %08x while we're halted.", mail);
+ break;
+ }
+}
+
+void ZeldaUCode::RunPendingCommands()
+{
+ if (RenderingInProgress() || !m_cmd_can_execute)
+ {
+ // No commands can be ran while audio rendering is in progress or
+ // waiting for an ACK.
+ return;
+ }
+
+ while (m_pending_commands_count)
+ {
+ u32 cmd_mail = Read32();
+ if (!(cmd_mail & 0x80000000))
+ continue;
+
+ u32 command = (cmd_mail >> 24) & 0x7f;
+ u32 sync = cmd_mail >> 16;
+ u32 extra_data = cmd_mail & 0xFFFF;
+
+ m_pending_commands_count--;
+
+ switch (command)
+ {
+ case 0x00:
+ case 0x0A:
+ case 0x0B:
+ case 0x0F:
+ // NOP commands. Log anyway in case we encounter a new version
+ // where these are not NOPs anymore.
+ NOTICE_LOG(DSPHLE, "Received a NOP command: %d", command);
+ SendCommandAck(CommandAck::STANDARD, sync);
+ break;
+
+ case 0x03:
+ // NOP on standard protocol but shouldn't ever happen on light protocol
+ // since it's going directly back to the dispatcher with no ack.
+ if (m_flags & LIGHT_PROTOCOL)
+ {
+ PanicAlert("Received a 03 command on light protocol.");
+ break;
+ }
+ SendCommandAck(CommandAck::STANDARD, sync);
+ break;
+
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x08:
+ case 0x09:
+ // Commands that crash the DAC UCode on non-light protocols. Log and
+ // enter HALTED mode.
+ //
+ // TODO: These are not crashes on light protocol, however I've never seen
+ // them used so far.
+ NOTICE_LOG(DSPHLE, "Received a crashy command: %d", command);
+ SetMailState(MailState::HALTED);
+ return;
+
+ // Command 01: Setup/initialization command. Provides the address to
+ // voice parameter blocks (VPBs) as well as some array of coefficients
+ // used for mixing.
+ case 0x01:
+ {
+ m_rendering_voices_per_frame = extra_data;
+
+ m_renderer.SetVPBBaseAddress(Read32());
+
+ u16* data_ptr = (u16*)HLEMemory_Get_Pointer(Read32());
+
+ std::array resampling_coeffs;
+ for (size_t i = 0; i < 0x100; ++i)
+ resampling_coeffs[i] = Common::swap16(data_ptr[i]);
+ m_renderer.SetResamplingCoeffs(std::move(resampling_coeffs));
+
+ std::array const_patterns;
+ for (size_t i = 0; i < 0x100; ++i)
+ const_patterns[i] = Common::swap16(data_ptr[0x100 + i]);
+ m_renderer.SetConstPatterns(std::move(const_patterns));
+
+ std::array sine_table;
+ for (size_t i = 0; i < 0x80; ++i)
+ sine_table[i] = Common::swap16(data_ptr[0x200 + i]);
+ m_renderer.SetSineTable(std::move(sine_table));
+
+ u16* afc_coeffs_ptr = (u16*)HLEMemory_Get_Pointer(Read32());
+ std::array afc_coeffs;
+ for (size_t i = 0; i < 0x20; ++i)
+ afc_coeffs[i] = Common::swap16(afc_coeffs_ptr[i]);
+ m_renderer.SetAfcCoeffs(std::move(afc_coeffs));
+
+ m_renderer.SetReverbPBBaseAddress(Read32());
+
+ SendCommandAck(CommandAck::STANDARD, sync);
+ break;
+ }
+
+ // Command 02: starts audio processing. NOTE: this handler uses return,
+ // not break. This is because it hijacks the mail control flow and
+ // stops processing of further commands until audio processing is done.
+ case 0x02:
+ m_rendering_requested_frames = (cmd_mail >> 16) & 0xFF;
+ m_renderer.SetOutputVolume(cmd_mail & 0xFFFF);
+ m_renderer.SetOutputLeftBufferAddr(Read32());
+ m_renderer.SetOutputRightBufferAddr(Read32());
+
+ m_rendering_curr_frame = 0;
+ m_rendering_curr_voice = 0;
+
+ if (m_flags & LIGHT_PROTOCOL)
+ {
+ SendCommandAck(CommandAck::STANDARD, m_rendering_requested_frames);
+ SetMailState(MailState::RENDERING);
+ }
+ else
+ {
+ RenderAudio();
+ }
+ return;
+
+ // Command 0C: used for multiple purpose depending on the UCode version:
+ // * IPL NTSC/PAL, Luigi's Mansion: TODO (unknown as of now).
+ // * Pikmin/AC: GBA crypto.
+ // * SMS and onwards: NOP.
+ case 0x0C:
+ if (m_flags & SUPPORTS_GBA_CRYPTO)
+ {
+ ProcessGBACrypto(Read32());
+ }
+ else if (m_flags & WEIRD_CMD_0C)
+ {
+ // TODO
+ NOTICE_LOG(DSPHLE, "Received an unhandled 0C command, params: %08x %08x", Read32(), Read32());
+ }
+ else
+ {
+ WARN_LOG(DSPHLE, "Received a NOP 0C command. Flags=%08x", m_flags);
+ }
+ SendCommandAck(CommandAck::STANDARD, sync);
+ break;
+
+ // Command 0D: TODO: find a name and implement.
+ case 0x0D:
+ if (m_flags & NO_CMD_0D)
+ {
+ WARN_LOG(DSPHLE, "Received a 0D command which is NOP'd on this UCode.");
+ SendCommandAck(CommandAck::STANDARD, sync);
+ break;
+ }
+
+ WARN_LOG(DSPHLE, "CMD0D: %08x", Read32());
+ SendCommandAck(CommandAck::STANDARD, sync);
+ break;
+
+ // Command 0E: Sets the base address of the ARAM for Wii UCodes. Used
+ // because the Wii does not have an ARAM, so it simulates it with MRAM
+ // and DMAs.
+ case 0x0E:
+ if (!(m_flags & NO_ARAM))
+ PanicAlert("Setting base ARAM addr on non Wii DAC.");
+ m_renderer.SetARAMBaseAddr(Read32());
+ SendCommandAck(CommandAck::STANDARD, sync);
+ break;
+
+ default:
+ NOTICE_LOG(DSPHLE, "Received a non-existing command (%d), halting.", command);
+ SetMailState(MailState::HALTED);
+ return;
+ }
+ }
+}
+
+void ZeldaUCode::SendCommandAck(CommandAck ack_type, u16 sync_value)
+{
+ if (m_flags & LIGHT_PROTOCOL)
+ {
+ // The light protocol uses the address of the command handler in the
+ // DSP code instead of the command id... go figure.
+ sync_value = 2 * ((sync_value >> 8) & 0x7F) + 0x62;
+ m_mail_handler.PushMail(0x80000000 | sync_value);
+ }
+ else
+ {
+ u32 ack_mail = 0;
+ switch (ack_type)
+ {
+ case CommandAck::STANDARD: ack_mail = DSP_SYNC; break;
+ case CommandAck::DONE_RENDERING: ack_mail = DSP_FRAME_END; break;
+ }
+ m_mail_handler.PushMail(ack_mail, true);
+
+ if (ack_type == CommandAck::STANDARD)
+ m_mail_handler.PushMail(0xF3550000 | sync_value);
+ }
+}
+
+void ZeldaUCode::RenderAudio()
+{
+ if (!RenderingInProgress())
+ {
+ WARN_LOG(DSPHLE, "Trying to render audio while no rendering should be happening.");
+ return;
+ }
+
+ while (m_rendering_curr_frame < m_rendering_requested_frames)
+ {
+ if (m_rendering_curr_voice == 0)
+ m_renderer.PrepareFrame();
+
+ while (m_rendering_curr_voice < m_rendering_voices_per_frame)
+ {
+ // If we are not meant to render this voice yet, go back to message
+ // processing.
+ if (m_rendering_curr_voice >= m_sync_max_voice_id)
+ return;
+
+ // Test the sync flag for this voice, skip it if not set.
+ u16 flags = m_sync_voice_skip_flags[m_rendering_curr_voice >> 4];
+ u8 bit = 0xF - (m_rendering_curr_voice & 0xF);
+ if (flags & (1 << bit))
+ m_renderer.AddVoice(m_rendering_curr_voice);
+
+ m_rendering_curr_voice++;
+ }
+
+ if (!(m_flags & LIGHT_PROTOCOL))
+ SendCommandAck(CommandAck::STANDARD, 0xFF00 | m_rendering_curr_frame);
+
+ m_renderer.FinalizeFrame();
+
+ m_rendering_curr_voice = 0;
+ m_sync_max_voice_id = 0;
+ m_rendering_curr_frame++;
+ }
+
+ if (!(m_flags & LIGHT_PROTOCOL))
+ {
+ SendCommandAck(CommandAck::DONE_RENDERING, 0);
+ m_cmd_can_execute = false; // Block command execution until ACK is received.
+ }
+ else
+ {
+ SetMailState(MailState::WAITING);
+ }
+}
+
+// Utility to define 32 bit accessors/modifiers methods based on two 16 bit
+// fields named _l and _h.
+#define DEFINE_32BIT_ACCESSOR(field_name, name) \
+ u32 Get##name() const { return (field_name##_h << 16) | field_name##_l; } \
+ void Set##name(u32 v) \
+ { \
+ field_name##_h = v >> 16; \
+ field_name##_l = v & 0xFFFF; \
+ }
+
+#pragma pack(push, 1)
+struct ZeldaAudioRenderer::VPB
+{
+ // If zero, skip processing this voice.
+ u16 enabled;
+
+ // If non zero, skip processing this voice.
+ u16 done;
+
+ // In 4.12 format. 1.0 (0x1000) means 0x50 raw samples from RAM/accelerator
+ // will be "resampled" to 0x50 input samples. 2.0 (0x2000) means 2 raw
+ // samples for one input samples. 0.5 (0x800) means one raw sample for 2
+ // input samples.
+ u16 resampling_ratio;
+
+ u16 unk_03;
+
+ // If non zero, reset some value in the VPB when processing it.
+ u16 reset_vpb;
+
+ // If non zero, tells PCM8/PCM16 sample sources that the end of the voice
+ // has been reached and looping should be considered if enabled.
+ u16 end_reached;
+
+ // If non zero, input samples to this VPB will be the fixed value from
+ // VPB[33] (constant_sample_value). This is used when a voice is being
+ // terminated in order to force silence.
+ u16 use_constant_sample;
+
+ // Number of samples that should be saved in the VPB for processing during
+ // future frames. Should be at most TODO.
+ u16 samples_to_keep_count;
+
+ // Channel mixing information. Each voice can be mixed to 6 different
+ // channels, with separate volume information.
+ //
+ // Used only if VPB[2C] (use_dolby_volume) is not set. Otherwise, the
+ // values from VPB[0x20:0x2C] are used to mix to all available channels.
+ struct Channel
+ {
+ // Can be treated as an ID, but in the real world this is actually the
+ // address in DRAM of a DSP buffer. The game passes that information to
+ // the DSP, which means the game must know the memory layout of the DSP
+ // UCode... that's terrible.
+ u16 id;
+
+ s16 target_volume;
+ s16 current_volume;
+
+ u16 unk;
+ };
+ Channel channels[6];
+
+ u16 unk_20_28[0x8];
+
+ // When using Dolby voice mixing (see VPB[2C] use_dolby_volume), the X
+ // (left/right) and Y (front/back) coordinates of the sound. 0x00 is all
+ // right/back, 0x7F is all left/front. Format is 0XXXXXXX0YYYYYYY.
+ u16 dolby_voice_position;
+ u8 GetDolbyVoiceX() const { return (dolby_voice_position >> 8) & 0x7F; }
+ u8 GetDolbyVoiceY() const { return dolby_voice_position & 0x7F; }
+
+ // How much reverbation to apply to the Dolby mixed voice. 0 is none,
+ // 0x7FFF is the maximum value.
+ s16 dolby_reverb_factor;
+
+ // The volume for the 0x50 samples being mixed will ramp between current
+ // and target. After the ramping is done, the current value is updated (to
+ // match target, usually).
+ s16 dolby_volume_current;
+ s16 dolby_volume_target;
+
+ // If non zero, use positional audio mixing. Instead of using the channels
+ // information, use the 4 Dolby related VPB fields defined above.
+ u16 use_dolby_volume;
+
+ u16 unk_2D;
+ u16 unk_2E;
+ u16 unk_2F;
+
+ // Fractional part of the current sample position, in 0.12 format (all
+ // decimal part, 0x0800 = 0.5). The 4 top bits are unused.
+ u16 current_pos_frac;
+
+ u16 unk_31;
+
+ // Number of remaining decoded AFC samples in the VPB internal buffer (see
+ // VPB[0x58]) that haven't been output yet.
+ u16 afc_remaining_decoded_samples;
+
+ // Value used as the constant sample value if VPB[6] (use_constant_sample)
+ // is set. Reset to the last sample value after each round of resampling.
+ s16 constant_sample;
+
+ // Current position in the voice. Not needed for accelerator based voice
+ // types since the accelerator exposes a streaming based interface, but DMA
+ // based voice types (PCM16_FROM_MRAM for example) require it to know where
+ // to seek in the MRAM buffer.
+ u16 current_position_h;
+ u16 current_position_l;
+ DEFINE_32BIT_ACCESSOR(current_position, CurrentPosition)
+
+ // Number of samples that will be processed before the loop point of the
+ // voice is reached. Maintained by the UCode and used by the game to
+ // schedule some parameters updates.
+ u16 samples_before_loop;
+
+ u16 unk_37;
+
+ // Current address used to stream samples for the ARAM sample source types.
+ u16 current_aram_addr_h;
+ u16 current_aram_addr_l;
+ DEFINE_32BIT_ACCESSOR(current_aram_addr, CurrentARAMAddr)
+
+ // Remaining number of samples to load before considering the voice
+ // rendering complete and setting the done flag. Note that this is an
+ // absolute value that does not take into account loops. If a loop of 100
+ // samples is played 4 times, remaining_length will have decreased by 400.
+ u16 remaining_length_h;
+ u16 remaining_length_l;
+ DEFINE_32BIT_ACCESSOR(remaining_length, RemainingLength)
+
+ // Stores the last 4 resampled input samples after each frame, so that they
+ // can be used for future linear interpolation.
+ s16 resample_buffer[4];
+
+ // TODO: document and implement.
+ s16 prev_input_samples[0x18];
+
+ // Values from the last decoded AFC block. The last two values are
+ // especially important since AFC decoding - as a variant of ADPCM -
+ // requires the two latest sample values to be able to decode future
+ // samples.
+ s16 afc_remaining_samples[0x10];
+ s16* AFCYN2() { return &afc_remaining_samples[0xE]; }
+ s16* AFCYN1() { return &afc_remaining_samples[0xF]; }
+
+ u16 unk_68_80[0x80 - 0x68];
+
+ enum SamplesSourceType
+ {
+ // Simple square wave at 50% amplitude and frequency controlled via the
+ // resampling ratio.
+ SRC_SQUARE_WAVE = 0,
+ // Simple saw wave at 100% amplitude and frequency controlled via the
+ // resampling ratio.
+ SRC_SAW_WAVE = 1,
+ // Same "square" wave as SRC_SQUARE_WAVE but using a 0.25 duty cycle
+ // instead of 0.5.
+ SRC_SQUARE_WAVE_25PCT = 3,
+ // Breaking the numerical ordering for these, but they are all related.
+ // Simple pattern stored in the data downloaded by command 01. Playback
+ // frequency is controlled by the resampling ratio.
+ SRC_CONST_PATTERN_0 = 7,
+ SRC_CONST_PATTERN_0_VARIABLE_STEP = 10,
+ SRC_CONST_PATTERN_1 = 4,
+ SRC_CONST_PATTERN_2 = 11,
+ SRC_CONST_PATTERN_3 = 12,
+ // Samples stored in ARAM at a rate of 16 samples/5 bytes, AFC encoded,
+ // at an arbitrary sample rate (resampling is applied).
+ SRC_AFC_LQ_FROM_ARAM = 5,
+ // Samples stored in ARAM in PCM8 format, at an arbitrary sampling rate
+ // (resampling is applied).
+ SRC_PCM8_FROM_ARAM = 8,
+ // Samples stored in ARAM at a rate of 16 samples/9 bytes, AFC encoded,
+ // at an arbitrary sample rate (resampling is applied).
+ SRC_AFC_HQ_FROM_ARAM = 9,
+ // Samples stored in ARAM in PCM16 format, at an arbitrary sampling
+ // rate (resampling is applied).
+ SRC_PCM16_FROM_ARAM = 16,
+ // Samples stored in MRAM at an arbitrary sample rate (resampling is
+ // applied, unlike PCM16_FROM_MRAM_RAW).
+ SRC_PCM16_FROM_MRAM = 33,
+ };
+ u16 samples_source_type;
+
+ // If non zero, indicates that the sound should loop.
+ u16 is_looping;
+
+ // For AFC looping voices, the values of the last 2 samples before the
+ // start of the loop, in order to be able to decode samples after looping.
+ s16 loop_yn1;
+ s16 loop_yn2;
+
+ u16 unk_84;
+
+ // If true, ramp down quickly to a volume of zero, and end the voice (by
+ // setting VPB[1] done) when it reaches zero.
+ u16 end_requested;
+
+ u16 unk_86;
+ u16 unk_87;
+
+ // Base address used to download samples data after the loop point of the
+ // voice has been reached.
+ u16 loop_address_h;
+ u16 loop_address_l;
+ DEFINE_32BIT_ACCESSOR(loop_address, LoopAddress)
+
+ // Offset (in number of raw samples) of the start of the loop area in the
+ // voice. Note: some sample sources only use the _h part of this.
+ //
+ // TODO: rename to length? confusion with remaining_length...
+ u16 loop_start_position_h;
+ u16 loop_start_position_l;
+ DEFINE_32BIT_ACCESSOR(loop_start_position, LoopStartPosition)
+
+ // Base address used to download samples data before the loop point of the
+ // voice has been reached.
+ u16 base_address_h;
+ u16 base_address_l;
+ DEFINE_32BIT_ACCESSOR(base_address, BaseAddress)
+
+ u16 padding[0xC0];
+
+ // These next two functions are terrible hacks used in order to support two
+ // different VPB sizes.
+
+ // Transforms from an NTSC-IPL type 0x80-sized VPB to a full size VPB.
+ void Uncompress()
+ {
+ u16* words = (u16*)this;
+ // RO part of the VPB is from 0x40-0x80 instead of 0x80-0xC0.
+ for (int i = 0; i < 0x40; ++i)
+ {
+ words[0x80 + i] = words[0x40 + i];
+ words[0x40 + i] = 0;
+ }
+ // AFC decoded samples are offset by 0x28.
+ for (int i = 0; i < 0x10; ++i)
+ {
+ words[0x58 + i] = words[0x30 + i];
+ words[0x30 + i] = 0;
+ }
+ // Most things are offset by 0x18 because no Dolby mixing.
+ for (int i = 0; i < 0x18; ++i)
+ {
+ words[0x30 + i] = words[0x18 + i];
+ words[0x18 + i] = 0;
+ }
+ }
+
+ // Transforms from a full size VPB to an NTSC-IPL 0x80-sized VPB.
+ void Compress()
+ {
+ u16* words = (u16*)this;
+ for (int i = 0; i < 0x18; ++i)
+ {
+ words[0x18 + i] = words[0x30 + i];
+ words[0x30 + i] = 0;
+ }
+ for (int i = 0; i < 0x10; ++i)
+ {
+ words[0x30 + i] = words[0x58 + i];
+ words[0x58 + i] = 0;
+ }
+ for (int i = 0; i < 0x40; ++i)
+ {
+ words[0x40 + i] = words[0x80 + i];
+ words[0x80 + i] = 0;
+ }
+ }
+};
+
+struct ReverbPB
+{
+ // If zero, skip this reverb PB.
+ u16 enabled;
+ // Size of the circular buffer in MRAM, expressed in number of 0x50 samples
+ // blocks (0xA0 bytes).
+ u16 circular_buffer_size;
+ // Base address of the circular buffer in MRAM.
+ u16 circular_buffer_base_h;
+ u16 circular_buffer_base_l;
+
+ struct Destination
+ {
+ u16 buffer_id; // See VPB::Channel::id.
+ u16 volume; // 1.15 format.
+ };
+ Destination dest[2];
+
+ // Coefficients for an 8-tap filter applied to each reverb buffer before
+ // adding its data to the destination.
+ s16 filter_coeffs[8];
+};
+#pragma pack(pop)
+
+void ZeldaAudioRenderer::PrepareFrame()
+{
+ if (m_prepared)
+ return;
+
+ m_buf_front_left.fill(0);
+ m_buf_front_right.fill(0);
+
+ ApplyVolumeInPlace_1_15(&m_buf_back_left, 0x6784);
+ ApplyVolumeInPlace_1_15(&m_buf_back_right, 0x6784);
+
+ // TODO: Back left and back right should have a filter applied to them,
+ // then get mixed into front left and front right. In practice, TWW never
+ // uses this AFAICT. PanicAlert to help me find places that use this.
+#ifdef STRICT_ZELDA_HLE
+ if (!(m_flags & LIGHT_PROTOCOL) && (m_buf_back_left[0] != 0 || m_buf_back_right[0] != 0))
+ PanicAlert("Zelda HLE using back mixing buffers");
+#endif
+
+ // Add reverb data from previous frame.
+ ApplyReverb(false);
+ AddBuffersWithVolume(m_buf_front_left_reverb.data(),
+ m_buf_back_left_reverb.data(),
+ 0x50, 0x7FFF);
+ AddBuffersWithVolume(m_buf_front_right_reverb.data(),
+ m_buf_back_left_reverb.data(),
+ 0x50, 0xB820);
+ AddBuffersWithVolume(m_buf_front_left_reverb.data(),
+ m_buf_back_right_reverb.data() + 0x28,
+ 0x28, 0xB820);
+ AddBuffersWithVolume(m_buf_front_right_reverb.data(),
+ m_buf_back_left_reverb.data() + 0x28,
+ 0x28, 0x7FFF);
+ m_buf_back_left_reverb.fill(0);
+ m_buf_back_right_reverb.fill(0);
+
+ // Prepare patterns 2/3 - they are not constant unlike 0/1.
+ s16* pattern2 = m_const_patterns.data() + 2 * 0x40;
+ s32 yn2 = pattern2[0x40 - 2], yn1 = pattern2[0x40 - 1], v;
+ for (int i = 0; i < 0x40; i += 2)
+ {
+ v = yn2 * yn1 - (pattern2[i] << 16);
+ yn2 = yn1; yn1 = pattern2[i]; pattern2[i] = v >> 16;
+
+ v = 2 * (yn2 * yn1 + (pattern2[i + 1] << 16));
+ yn2 = yn1; yn1 = pattern2[i + 1]; pattern2[i + 1] = v >> 16;
+ }
+ s16* pattern3 = m_const_patterns.data() + 3 * 0x40;
+ yn2 = pattern3[0x40 - 2]; yn1 = pattern3[0x40 - 1];
+ s16 acc = yn1;
+ s16 step = pattern3[0] + ((yn1 * yn2 + ((yn2 << 16) + yn1)) >> 16);
+ step = (step & 0x1FF) | 0x2000;
+ for (s32 i = 0; i < 0x40; ++i)
+ pattern3[i] = acc + (i + 1) * step;
+
+ m_prepared = true;
+}
+
+void ZeldaAudioRenderer::ApplyReverb(bool post_rendering)
+{
+ if (!m_reverb_pb_base_addr)
+ {
+#ifdef STRICT_ZELDA_HLE
+ PanicAlert("Trying to apply reverb without available parameters.");
+#endif
+ return;
+ }
+
+ // Each of the 4 RPBs maps to one of these buffers.
+ MixingBuffer* reverb_buffers[4] = {
+ &m_buf_unk0_reverb,
+ &m_buf_unk1_reverb,
+ &m_buf_front_left_reverb,
+ &m_buf_front_right_reverb,
+ };
+ std::array* last8_samples_buffers[4] = {
+ &m_buf_unk0_reverb_last8,
+ &m_buf_unk1_reverb_last8,
+ &m_buf_front_left_reverb_last8,
+ &m_buf_front_right_reverb_last8,
+ };
+
+ u16* rpb_base_ptr = (u16*)HLEMemory_Get_Pointer(m_reverb_pb_base_addr);
+ for (u16 rpb_idx = 0; rpb_idx < 4; ++rpb_idx)
+ {
+ ReverbPB rpb;
+ u16* rpb_raw_ptr = reinterpret_cast(&rpb);
+ for (size_t i = 0; i < sizeof (ReverbPB) / 2; ++i)
+ rpb_raw_ptr[i] = Common::swap16(rpb_base_ptr[rpb_idx * sizeof (ReverbPB) / 2 + i]);
+
+ if (!rpb.enabled)
+ continue;
+
+ u16 mram_buffer_idx = m_reverb_pb_frames_count[rpb_idx];
+
+ u32 mram_addr = ((rpb.circular_buffer_base_h << 16) |
+ rpb.circular_buffer_base_l) +
+ mram_buffer_idx * 0x50 * sizeof (s16);
+ s16* mram_ptr = (s16*)HLEMemory_Get_Pointer(mram_addr);
+
+ if (!post_rendering)
+ {
+ // 8 more samples because of the filter order. The first 8 samples
+ // are the last 8 samples of the previous frame.
+ std::array buffer;
+ for (u16 i = 0; i < 8; ++i)
+ buffer[i] = (*last8_samples_buffers[rpb_idx])[i];
+
+ for (u16 i = 0; i < 0x50; ++i)
+ buffer[8 + i] = Common::swap16(mram_ptr[i]);
+
+ for (u16 i = 0; i < 8; ++i)
+ (*last8_samples_buffers[rpb_idx])[i] = buffer[0x50 + i];
+
+ auto ApplyFilter = [&]() {
+ // Filter the buffer using provided coefficients.
+ for (u16 i = 0; i < 0x50; ++i)
+ {
+ s32 sample = 0;
+ for (u16 j = 0; j < 8; ++j)
+ sample += (s32)buffer[i + j] * rpb.filter_coeffs[j];
+ sample >>= 15;
+ MathUtil::Clamp(&sample, -0x8000, 0x7fff);
+ buffer[i] = sample;
+ }
+ };
+
+ // LSB set -> pre-filtering.
+ if (rpb.enabled & 1)
+ ApplyFilter();
+
+ for (const auto& dest : rpb.dest)
+ {
+ if (dest.buffer_id == 0)
+ continue;
+
+ MixingBuffer* dest_buffer = BufferForID(dest.buffer_id);
+ if (!dest_buffer)
+ {
+#ifdef STRICT_ZELDA_HLE
+ PanicAlert("RPB mixing to an unknown buffer: %04x", dest.buffer_id);
+#endif
+ continue;
+ }
+ AddBuffersWithVolume(dest_buffer->data(), buffer.data(),
+ 0x50, dest.volume);
+ }
+
+ // LSB not set, bit 1 set -> post-filtering.
+ if (rpb.enabled & 2)
+ ApplyFilter();
+
+ for (u16 i = 0; i < 0x50; ++i)
+ (*reverb_buffers[rpb_idx])[i] = buffer[i];
+ }
+ else
+ {
+ MixingBuffer* buffer = reverb_buffers[rpb_idx];
+
+ // Upload the reverb data to RAM.
+ for (auto sample : *buffer)
+ *mram_ptr++ = Common::swap16(sample);
+
+ mram_buffer_idx = (mram_buffer_idx + 1) % rpb.circular_buffer_size;
+ m_reverb_pb_frames_count[rpb_idx] = mram_buffer_idx;
+ }
+ }
+}
+
+ZeldaAudioRenderer::MixingBuffer* ZeldaAudioRenderer::BufferForID(u16 buffer_id)
+{
+ switch (buffer_id)
+ {
+ case 0x0D00: return &m_buf_front_left;
+ case 0x0D60: return &m_buf_front_right;
+ case 0x0F40: return &m_buf_back_left;
+ case 0x0CA0: return &m_buf_back_right;
+ case 0x0E80: return &m_buf_front_left_reverb;
+ case 0x0EE0: return &m_buf_front_right_reverb;
+ case 0x0C00: return &m_buf_back_left_reverb;
+ case 0x0C50: return &m_buf_back_right_reverb;
+ case 0x0DC0: return &m_buf_unk0_reverb;
+ case 0x0E20: return &m_buf_unk1_reverb;
+ case 0x09A0: return &m_buf_unk0; // Used by the GC IPL as a reverb dest.
+ case 0x0FA0: return &m_buf_unk1; // Used by the GC IPL as a mixing dest.
+ case 0x0B00: return &m_buf_unk2; // Used by Pikmin 1 as a mixing dest.
+ default: return nullptr;
+ }
+}
+
+void ZeldaAudioRenderer::AddVoice(u16 voice_id)
+{
+ VPB vpb;
+ FetchVPB(voice_id, &vpb);
+
+ if (!vpb.enabled || vpb.done)
+ return;
+
+ MixingBuffer input_samples;
+ LoadInputSamples(&input_samples, &vpb);
+
+ // TODO: In place effects.
+
+ // TODO: IIR filter.
+
+ if (vpb.use_dolby_volume)
+ {
+ if (vpb.end_requested)
+ {
+ vpb.dolby_volume_target = vpb.dolby_volume_current / 2;
+ if (vpb.dolby_volume_target == 0)
+ vpb.done = true;
+ }
+
+ // Each of these volumes is in 1.15 fixed format.
+ s16 right_volume = m_sine_table[vpb.GetDolbyVoiceX()];
+ s16 back_volume = m_sine_table[vpb.GetDolbyVoiceY()];
+ s16 left_volume = m_sine_table[vpb.GetDolbyVoiceX() ^ 0x7F];
+ s16 front_volume = m_sine_table[vpb.GetDolbyVoiceY() ^ 0x7F];
+
+ // Compute volume for each quadrant.
+ u16 shift_factor = (m_flags & MAKE_DOLBY_LOUDER) ? 15 : 16;
+ s16 quadrant_volumes[4] = {
+ (s16)((left_volume * front_volume) >> shift_factor),
+ (s16)((left_volume * back_volume) >> shift_factor),
+ (s16)((right_volume * front_volume) >> shift_factor),
+ (s16)((right_volume * back_volume) >> shift_factor),
+ };
+
+ // Compute the volume delta for each sample to match the difference
+ // between current and target volume.
+ s16 delta = vpb.dolby_volume_target - vpb.dolby_volume_current;
+ s16 volume_deltas[4];
+ for (size_t i = 0; i < 4; ++i)
+ volume_deltas[i] = ((u16)quadrant_volumes[i] * delta) >> shift_factor;
+
+ // Apply master volume to each quadrant.
+ for (size_t i = 0; i < 4; ++i)
+ quadrant_volumes[i] = (quadrant_volumes[i] * vpb.dolby_volume_current) >> shift_factor;
+
+ // Compute reverb volume and ramp deltas.
+ s16 reverb_volumes[4], reverb_volume_deltas[4];
+ s16 reverb_volume_factor = (vpb.dolby_volume_current * vpb.dolby_reverb_factor) >> (shift_factor - 1);
+ for (size_t i = 0; i < 4; ++i)
+ {
+ reverb_volumes[i] = (quadrant_volumes[i] * reverb_volume_factor) >> shift_factor;
+ reverb_volume_deltas[i] = (volume_deltas[i] * vpb.dolby_reverb_factor) >> shift_factor;
+ }
+
+ struct {
+ MixingBuffer* buffer;
+ s16 volume;
+ s16 volume_delta;
+ } buffers[8] = {
+ { &m_buf_front_left, quadrant_volumes[0], volume_deltas[0] },
+ { &m_buf_back_left, quadrant_volumes[1], volume_deltas[1] },
+ { &m_buf_front_right, quadrant_volumes[2], volume_deltas[2] },
+ { &m_buf_back_right, quadrant_volumes[3], volume_deltas[3] },
+
+ { &m_buf_front_left_reverb, reverb_volumes[0], reverb_volume_deltas[0] },
+ { &m_buf_back_left_reverb, reverb_volumes[1], reverb_volume_deltas[1] },
+ { &m_buf_front_right_reverb, reverb_volumes[2], reverb_volume_deltas[2] },
+ { &m_buf_back_right_reverb, reverb_volumes[3], reverb_volume_deltas[3] },
+ };
+ for (const auto& buffer : buffers)
+ {
+ AddBuffersWithVolumeRamp(buffer.buffer, input_samples, buffer.volume << 16,
+ (buffer.volume_delta << 16) / (s32)buffer.buffer->size());
+ }
+
+ vpb.dolby_volume_current = vpb.dolby_volume_target;
+ }
+ else
+ {
+ // TODO: Store input samples if requested by the VPB.
+
+ int num_channels = (m_flags & FOUR_MIXING_DESTS) ? 4 : 6;
+ if (vpb.end_requested)
+ {
+ bool all_mute = true;
+ for (int i = 0; i < num_channels; ++i)
+ {
+ vpb.channels[i].target_volume = vpb.channels[i].current_volume / 2;
+ all_mute &= (vpb.channels[i].target_volume == 0);
+ }
+ if (all_mute)
+ vpb.done = true;
+ }
+
+ for (int i = 0; i < num_channels; ++i)
+ {
+ if (!vpb.channels[i].id)
+ continue;
+
+ // Some UCode versions provide the delta directly instead of
+ // providing a target volume.
+ s16 volume_delta;
+ if (m_flags & VOLUME_EXPLICIT_STEP)
+ volume_delta = (vpb.channels[i].target_volume << 16);
+ else
+ volume_delta = vpb.channels[i].target_volume - vpb.channels[i].current_volume;
+
+ s32 volume_step = (volume_delta << 16) / (s32)input_samples.size(); // In 1.31 format.
+
+ // TODO: The last value of each channel structure is used to
+ // determine whether a channel should be skipped or not. Not
+ // implemented yet.
+
+ if (!vpb.channels[i].current_volume && !volume_step)
+ continue;
+
+ MixingBuffer* dst_buffer = BufferForID(vpb.channels[i].id);
+ if (!dst_buffer)
+ {
+#ifdef STRICT_ZELDA_HLE
+ PanicAlert("Mixing to an unmapped buffer: %04x", vpb.channels[i].id);
+#endif
+ continue;
+ }
+
+ s32 new_volume = AddBuffersWithVolumeRamp(
+ dst_buffer, input_samples, vpb.channels[i].current_volume << 16,
+ volume_step);
+ vpb.channels[i].current_volume = new_volume >> 16;
+ }
+ }
+
+ // By then the VPB has been reset, unless we're in the "constant sample" /
+ // silence mode.
+ if (!vpb.use_constant_sample)
+ vpb.reset_vpb = false;
+
+ StoreVPB(voice_id, &vpb);
+}
+
+void ZeldaAudioRenderer::FinalizeFrame()
+{
+ // TODO: Dolby mixing.
+
+ ApplyVolumeInPlace_4_12(&m_buf_front_left, m_output_volume);
+ ApplyVolumeInPlace_4_12(&m_buf_front_right, m_output_volume);
+
+ u16* ram_left_buffer = (u16*)HLEMemory_Get_Pointer(m_output_lbuf_addr);
+ u16* ram_right_buffer = (u16*)HLEMemory_Get_Pointer(m_output_rbuf_addr);
+ for (size_t i = 0; i < m_buf_front_left.size(); ++i)
+ {
+ ram_left_buffer[i] = Common::swap16(m_buf_front_left[i]);
+ ram_right_buffer[i] = Common::swap16(m_buf_front_right[i]);
+ }
+ m_output_lbuf_addr += sizeof (u16) * (u32)m_buf_front_left.size();
+ m_output_rbuf_addr += sizeof (u16) * (u32)m_buf_front_right.size();
+
+ // TODO: Some more Dolby mixing.
+
+ ApplyReverb(true);
+
+ m_prepared = false;
+}
+
+void ZeldaAudioRenderer::FetchVPB(u16 voice_id, VPB* vpb)
+{
+ u16* vpb_words = (u16*)vpb;
+ u16* ram_vpbs = (u16*)HLEMemory_Get_Pointer(m_vpb_base_addr);
+
+ // A few versions of the UCode have VPB of size 0x80 (vs. the standard
+ // 0xC0). The whole 0x40-0x80 part is gone. Handle that by moving things
+ // around.
+ size_t vpb_size = (m_flags & TINY_VPB) ? 0x80 : 0xC0;
+
+ size_t base_idx = voice_id * vpb_size;
+ for (size_t i = 0; i < vpb_size; ++i)
+ vpb_words[i] = Common::swap16(ram_vpbs[base_idx + i]);
+
+ if (m_flags & TINY_VPB)
+ vpb->Uncompress();
+}
+
+void ZeldaAudioRenderer::StoreVPB(u16 voice_id, VPB* vpb)
+{
+ u16* vpb_words = (u16*)vpb;
+ u16* ram_vpbs = (u16*)HLEMemory_Get_Pointer(m_vpb_base_addr);
+
+ size_t vpb_size = (m_flags & TINY_VPB) ? 0x80 : 0xC0;
+ size_t base_idx = voice_id * vpb_size;
+
+ if (m_flags & TINY_VPB)
+ vpb->Compress();
+
+ // Only the first 0x80 words are transferred back - the rest is read-only.
+ for (size_t i = 0; i < vpb_size - 0x40; ++i)
+ ram_vpbs[base_idx + i] = Common::swap16(vpb_words[i]);
+}
+
+void ZeldaAudioRenderer::LoadInputSamples(MixingBuffer* buffer, VPB* vpb)
+{
+ // Input data pre-resampling. Resampled into the mixing buffer parameter at
+ // the end of processing, if needed.
+ //
+ // Maximum of 0x500 samples here - see NeededRawSamplesCount to understand
+ // this practical limit (resampling_ratio = 0xFFFF -> 0x500 samples). Add a
+ // margin of 4 that is needed for samples source that do resampling.
+ std::array raw_input_samples;
+ for (size_t i = 0; i < 4; ++i)
+ raw_input_samples[i] = vpb->resample_buffer[i];
+
+ if (vpb->use_constant_sample)
+ {
+ buffer->fill(vpb->constant_sample);
+ return;
+ }
+
+ switch (vpb->samples_source_type)
+ {
+ case VPB::SRC_SQUARE_WAVE:
+ case VPB::SRC_SQUARE_WAVE_25PCT:
+ {
+ u32 shift;
+ if (vpb->samples_source_type == VPB::SRC_SQUARE_WAVE)
+ shift = 1;
+ else
+ shift = 2;
+ u32 mask = (1 << shift) - 1;
+
+ u32 pos = vpb->current_pos_frac << shift;
+ for (size_t i = 0; i < buffer->size(); ++i)
+ {
+ (*buffer)[i] = ((pos >> 16) & mask) ? 0xC000 : 0x4000;
+ pos += vpb->resampling_ratio;
+ }
+ vpb->current_pos_frac = (pos >> shift) & 0xFFFF;
+ break;
+ }
+
+ case VPB::SRC_SAW_WAVE:
+ {
+ u32 pos = vpb->current_pos_frac;
+ for (size_t i = 0; i < buffer->size(); ++i)
+ {
+ (*buffer)[i] = pos & 0xFFFF;
+ pos += (vpb->resampling_ratio) >> 1;
+ }
+ vpb->current_pos_frac = pos & 0xFFFF;
+ break;
+ }
+
+ case VPB::SRC_CONST_PATTERN_0:
+ case VPB::SRC_CONST_PATTERN_0_VARIABLE_STEP:
+ case VPB::SRC_CONST_PATTERN_1:
+ case VPB::SRC_CONST_PATTERN_2:
+ case VPB::SRC_CONST_PATTERN_3:
+ {
+ const u16 PATTERN_SIZE = 0x40;
+
+ struct PatternInfo { u16 idx; bool variable_step; };
+ std::map samples_source_to_pattern = {
+ { VPB::SRC_CONST_PATTERN_0, {0, false} },
+ { VPB::SRC_CONST_PATTERN_0_VARIABLE_STEP, {0, true} },
+ { VPB::SRC_CONST_PATTERN_1, {1, false} },
+ { VPB::SRC_CONST_PATTERN_2, {2, false} },
+ { VPB::SRC_CONST_PATTERN_3, {3, false} },
+ };
+ auto& pattern_info = samples_source_to_pattern[vpb->samples_source_type];
+ u16 pattern_offset = pattern_info.idx * PATTERN_SIZE;
+ s16* pattern = m_const_patterns.data() + pattern_offset;
+
+ u32 pos = vpb->current_pos_frac << 6; // log2(PATTERN_SIZE)
+ u32 step = vpb->resampling_ratio << 5;
+
+ for (size_t i = 0; i < buffer->size(); ++i)
+ {
+ (*buffer)[i] = pattern[pos >> 16];
+ pos = (pos + step) % (PATTERN_SIZE << 16);
+ if (pattern_info.variable_step)
+ pos = ((pos << 10) + m_buf_back_right[i] * vpb->resampling_ratio) >> 10;
+ }
+
+ vpb->current_pos_frac = pos >> 6;
+ break;
+ }
+
+
+
+ case VPB::SRC_PCM8_FROM_ARAM:
+ DownloadPCMSamplesFromARAM(raw_input_samples.data() + 4, vpb,
+ NeededRawSamplesCount(*vpb));
+ Resample(vpb, raw_input_samples.data(), buffer);
+ break;
+
+ case VPB::SRC_AFC_HQ_FROM_ARAM:
+ case VPB::SRC_AFC_LQ_FROM_ARAM:
+ DownloadAFCSamplesFromARAM(raw_input_samples.data() + 4, vpb,
+ NeededRawSamplesCount(*vpb));
+ Resample(vpb, raw_input_samples.data(), buffer);
+ break;
+
+ case VPB::SRC_PCM16_FROM_ARAM:
+ DownloadPCMSamplesFromARAM(raw_input_samples.data() + 4, vpb,
+ NeededRawSamplesCount(*vpb));
+ Resample(vpb, raw_input_samples.data(), buffer);
+ break;
+
+ case VPB::SRC_PCM16_FROM_MRAM:
+ DownloadRawSamplesFromMRAM(raw_input_samples.data() + 4, vpb,
+ NeededRawSamplesCount(*vpb));
+ Resample(vpb, raw_input_samples.data(), buffer);
+ break;
+
+ default:
+ PanicAlert("Using an unknown/unimplemented sample source: %04x", vpb->samples_source_type);
+ buffer->fill(0);
+ return;
+ }
+}
+
+u16 ZeldaAudioRenderer::NeededRawSamplesCount(const VPB& vpb)
+{
+ // Both of these are 4.12 fixed point, so shift by 12 to get the int part.
+ return (vpb.current_pos_frac + 0x50 * vpb.resampling_ratio) >> 12;
+}
+
+void ZeldaAudioRenderer::Resample(VPB* vpb, const s16* src, MixingBuffer* dst)
+{
+ // Both in 20.12 format.
+ u32 ratio = vpb->resampling_ratio;
+ u32 pos = vpb->current_pos_frac;
+
+ // Check if we need to do some interpolation. If the resampling ratio is
+ // more than 4:1, it's not worth it.
+ if ((ratio >> 12) >= 4)
+ {
+ for (s16& dst_sample : *dst)
+ {
+ pos += ratio;
+ dst_sample = src[pos >> 12];
+ }
+ }
+ else
+ {
+ for (auto& dst_sample : *dst)
+ {
+ // We have 0x40 * 4 coeffs that need to be selected based on the
+ // most significant bits of the fractional part of the position. 12
+ // bits >> 6 = 6 bits = 0x40. Multiply by 4 since there are 4
+ // consecutive coeffs.
+ u32 coeffs_idx = ((pos & 0xFFF) >> 6) * 4;
+ const s16* coeffs = &m_resampling_coeffs[coeffs_idx];
+ const s16* input = &src[pos >> 12];
+
+ s64 dst_sample_unclamped = 0;
+ for (size_t i = 0; i < 4; ++i)
+ dst_sample_unclamped += (s64)2 * coeffs[i] * input[i];
+ dst_sample_unclamped >>= 16;
+ MathUtil::Clamp(&dst_sample_unclamped, (s64)-0x8000, (s64)0x7fff);
+ dst_sample = (s16)dst_sample_unclamped;
+
+ pos += ratio;
+ }
+ }
+
+ for (u32 i = 0; i < 4; ++i)
+ vpb->resample_buffer[i] = src[(pos >> 12) + i];
+ vpb->constant_sample = (*dst)[dst->size() - 1];
+ vpb->current_pos_frac = pos & 0xFFF;
+}
+
+void* ZeldaAudioRenderer::GetARAMPtr() const
+{
+ if (m_aram_base_addr)
+ return HLEMemory_Get_Pointer(m_aram_base_addr);
+ else
+ return DSP::GetARAMPtr();
+}
+
+template
+void ZeldaAudioRenderer::DownloadPCMSamplesFromARAM(s16* dst, VPB* vpb, u16 requested_samples_count)
+{
+ if (vpb->done)
+ {
+ for (u16 i = 0; i < requested_samples_count; ++i)
+ dst[i] = 0;
+ return;
+ }
+
+ if (vpb->reset_vpb)
+ {
+ vpb->SetRemainingLength(
+ vpb->GetLoopStartPosition() - vpb->GetCurrentPosition());
+ vpb->SetCurrentARAMAddr(
+ vpb->GetBaseAddress() + vpb->GetCurrentPosition() * sizeof (T));
+ }
+
+ vpb->end_reached = false;
+ while (requested_samples_count)
+ {
+ if (vpb->end_reached)
+ {
+ vpb->end_reached = false;
+ if (!vpb->is_looping)
+ {
+ for (u16 i = 0; i < requested_samples_count; ++i)
+ dst[i] = 0;
+ vpb->done = true;
+ break;
+ }
+ vpb->SetCurrentPosition(vpb->GetLoopAddress());
+ vpb->SetRemainingLength(
+ vpb->GetLoopStartPosition() - vpb->GetCurrentPosition());
+ vpb->SetCurrentARAMAddr(
+ vpb->GetBaseAddress() + vpb->GetCurrentPosition() * sizeof (T));
+ }
+
+ T* src_ptr = (T*)((u8*)GetARAMPtr() + vpb->GetCurrentARAMAddr());
+ u16 samples_to_download = std::min(vpb->GetRemainingLength(),
+ (u32)requested_samples_count);
+
+ for (u16 i = 0; i < samples_to_download; ++i)
+ *dst++ = Common::FromBigEndian(*src_ptr++) << (16 - 8 * sizeof (T));
+
+ vpb->SetRemainingLength(vpb->GetRemainingLength() - samples_to_download);
+ vpb->SetCurrentARAMAddr(vpb->GetCurrentARAMAddr() + samples_to_download * sizeof (T));
+ requested_samples_count -= samples_to_download;
+ if (!vpb->GetRemainingLength())
+ vpb->end_reached = true;
+ }
+}
+
+void ZeldaAudioRenderer::DownloadAFCSamplesFromARAM(
+ s16* dst, VPB* vpb, u16 requested_samples_count)
+{
+ if (vpb->reset_vpb)
+ {
+ *vpb->AFCYN1() = 0;
+ *vpb->AFCYN2() = 0;
+ vpb->afc_remaining_decoded_samples = 0;
+ vpb->SetRemainingLength(vpb->GetLoopStartPosition());
+ vpb->SetCurrentARAMAddr(vpb->GetBaseAddress());
+ }
+
+ if (vpb->done)
+ {
+ for (u16 i = 0; i < requested_samples_count; ++i)
+ dst[i] = 0;
+ return;
+ }
+
+ // Try several things until we have output enough samples.
+ while (true)
+ {
+ // Try to push currently cached/already decoded samples.
+ u16 remaining_to_output = std::min(vpb->afc_remaining_decoded_samples,
+ requested_samples_count);
+ s16* base = &vpb->afc_remaining_samples[0x10 - vpb->afc_remaining_decoded_samples];
+ for (size_t i = 0; i < remaining_to_output; ++i)
+ *dst++ = base[i];
+
+ vpb->afc_remaining_decoded_samples -= remaining_to_output;
+ requested_samples_count -= remaining_to_output;
+
+ if (requested_samples_count == 0)
+ {
+ return; // We have output everything we needed.
+ }
+ else if (requested_samples_count <= vpb->GetRemainingLength())
+ {
+ // Each AFC block is 16 samples.
+ u16 requested_blocks_count = (requested_samples_count + 0xF) >> 4;
+ u16 decoded_samples_count = requested_blocks_count << 4;
+
+ if (decoded_samples_count < vpb->GetRemainingLength())
+ {
+ vpb->afc_remaining_decoded_samples =
+ decoded_samples_count - requested_samples_count;
+ vpb->SetRemainingLength(vpb->GetRemainingLength() - decoded_samples_count);
+ }
+ else
+ {
+ vpb->afc_remaining_decoded_samples =
+ vpb->GetRemainingLength() - requested_samples_count;
+ vpb->SetRemainingLength(0);
+ }
+
+ DecodeAFC(vpb, dst, requested_blocks_count);
+
+ if (vpb->afc_remaining_decoded_samples)
+ {
+ for (size_t i = 0; i < 0x10; ++i)
+ vpb->afc_remaining_samples[i] = dst[decoded_samples_count - 0x10 + i];
+
+ if (!vpb->GetRemainingLength() && vpb->GetLoopStartPosition())
+ {
+ // Adjust remaining samples to account for the future loop iteration.
+ base = vpb->afc_remaining_samples + ((vpb->GetLoopStartPosition() + 0xF) & 0xF);
+ for (size_t i = 0; i < vpb->afc_remaining_decoded_samples; ++i)
+ vpb->afc_remaining_samples[0x10 - i - 1] = *base--;
+ }
+ }
+
+ return;
+ }
+ else
+ {
+ // More samples asked than available. Either complete the sound, or
+ // start looping.
+ if (vpb->GetRemainingLength()) // Skip if we cannot load anything.
+ {
+ requested_samples_count -= vpb->GetRemainingLength();
+ u16 requested_blocks_count = (vpb->GetRemainingLength() + 0xF) >> 4;
+ DecodeAFC(vpb, dst, requested_blocks_count);
+ dst += vpb->GetRemainingLength();
+ }
+
+ if (!vpb->is_looping)
+ {
+ vpb->done = true;
+ for (size_t i = 0; i < requested_samples_count; ++i)
+ *dst++ = 0;
+ return;
+ }
+ else
+ {
+ // We need to loop. Compute the new position, decode a block,
+ // and loop back to the beginning of the download logic.
+
+ // Use the fact that the sample source number also represents
+ // the number of bytes per 16 samples.
+ u32 loop_off_in_bytes =
+ (vpb->GetLoopAddress() >> 4) * vpb->samples_source_type;
+ u32 loop_start_addr = vpb->GetBaseAddress() + loop_off_in_bytes;
+ vpb->SetCurrentARAMAddr(loop_start_addr);
+
+ *vpb->AFCYN2() = vpb->loop_yn2;
+ *vpb->AFCYN1() = vpb->loop_yn1;
+
+ DecodeAFC(vpb, vpb->afc_remaining_samples, 1);
+
+ // Realign and recompute the number of internally cached
+ // samples and the current position.
+ vpb->afc_remaining_decoded_samples =
+ 0x10 - (vpb->GetLoopAddress() & 0xF);
+
+ u32 remaining_length = vpb->GetLoopStartPosition();
+ remaining_length -= vpb->afc_remaining_decoded_samples;
+ remaining_length -= vpb->GetLoopAddress();
+ vpb->SetRemainingLength(remaining_length);
+ continue;
+ }
+ }
+ }
+}
+
+void ZeldaAudioRenderer::DecodeAFC(VPB* vpb, s16* dst, size_t block_count)
+{
+ u32 addr = vpb->GetCurrentARAMAddr();
+ u8* src = (u8*)GetARAMPtr() + addr;
+ vpb->SetCurrentARAMAddr(addr + (u32)block_count * vpb->samples_source_type);
+
+ for (size_t b = 0; b < block_count; ++b)
+ {
+ s16 nibbles[16];
+ s16 delta = 1 << ((*src >> 4) & 0xF);
+ s16 idx = (*src & 0xF);
+ src++;
+
+ if (vpb->samples_source_type == VPB::SRC_AFC_HQ_FROM_ARAM)
+ {
+ for (size_t i = 0; i < 16; i += 2)
+ {
+ nibbles[i + 0] = *src >> 4;
+ nibbles[i + 1] = *src & 0xF;
+ src++;
+ }
+ for (auto& nibble : nibbles)
+ {
+ if (nibble >= 8)
+ nibble -= 16;
+ nibble <<= 11;
+ }
+ }
+ else
+ {
+ for (size_t i = 0; i < 16; i += 4)
+ {
+ nibbles[i + 0] = (*src >> 6) & 3;
+ nibbles[i + 1] = (*src >> 4) & 3;
+ nibbles[i + 2] = (*src >> 2) & 3;
+ nibbles[i + 3] = (*src >> 0) & 3;
+ src++;
+ }
+ for (auto& nibble : nibbles)
+ {
+ if (nibble >= 2)
+ nibble -= 4;
+ nibble <<= 13;
+ }
+ }
+
+ s32 yn1 = *vpb->AFCYN1(), yn2 = *vpb->AFCYN2();
+ for (size_t i = 0; i < 16; ++i)
+ {
+ s32 sample = delta * nibbles[i] +
+ yn1 * m_afc_coeffs[idx * 2] +
+ yn2 * m_afc_coeffs[idx * 2 + 1];
+ sample >>= 11;
+ MathUtil::Clamp(&sample, -0x8000, 0x7fff);
+ *dst++ = (s16)sample;
+ yn2 = yn1;
+ yn1 = sample;
+ }
+
+ *vpb->AFCYN2() = yn2;
+ *vpb->AFCYN1() = yn1;
+ }
+}
+
+void ZeldaAudioRenderer::DownloadRawSamplesFromMRAM(
+ s16* dst, VPB* vpb, u16 requested_samples_count)
+{
+ u32 addr = vpb->GetBaseAddress() + vpb->current_position_h * sizeof (u16);
+ s16* src_ptr = (s16*)HLEMemory_Get_Pointer(addr);
+
+ if (requested_samples_count > vpb->GetRemainingLength())
+ {
+ s16 last_sample = 0;
+ for (u16 i = 0; i < vpb->GetRemainingLength(); ++i)
+ *dst++ = last_sample = Common::swap16(*src_ptr++);
+ for (u16 i = vpb->GetRemainingLength(); i < requested_samples_count; ++i)
+ *dst++ = last_sample;
+
+ vpb->current_position_h += vpb->GetRemainingLength();
+ vpb->SetRemainingLength(0);
+ vpb->done = true;
+ }
+ else
+ {
+ vpb->SetRemainingLength(vpb->GetRemainingLength() - requested_samples_count);
+ vpb->samples_before_loop = vpb->loop_start_position_h - vpb->current_position_h;
+ if (requested_samples_count <= vpb->samples_before_loop)
+ {
+ for (u16 i = 0; i < requested_samples_count; ++i)
+ *dst++ = Common::swap16(*src_ptr++);
+ vpb->current_position_h += requested_samples_count;
+ }
+ else
+ {
+ for (u16 i = 0; i < vpb->samples_before_loop; ++i)
+ *dst++ = Common::swap16(*src_ptr++);
+ vpb->SetBaseAddress(vpb->GetLoopAddress());
+ src_ptr = (s16*)HLEMemory_Get_Pointer(vpb->GetLoopAddress());
+ for (u16 i = vpb->samples_before_loop; i < requested_samples_count; ++i)
+ *dst++ = Common::swap16(*src_ptr++);
+ vpb->current_position_h = requested_samples_count - vpb->samples_before_loop;
+ }
+ }
+}
+
+void ZeldaAudioRenderer::DoState(PointerWrap& p)
+{
+ p.Do(m_flags);
+ p.Do(m_prepared);
+
+ p.Do(m_output_lbuf_addr);
+ p.Do(m_output_rbuf_addr);
+ p.Do(m_output_volume);
+
+ p.Do(m_buf_front_left);
+ p.Do(m_buf_front_right);
+ p.Do(m_buf_back_left);
+ p.Do(m_buf_back_right);
+ p.Do(m_buf_front_left_reverb);
+ p.Do(m_buf_front_right_reverb);
+ p.Do(m_buf_back_left_reverb);
+ p.Do(m_buf_back_right_reverb);
+ p.Do(m_buf_unk0_reverb);
+ p.Do(m_buf_unk1_reverb);
+ p.Do(m_buf_unk0);
+ p.Do(m_buf_unk1);
+ p.Do(m_buf_unk2);
+
+ p.Do(m_resampling_coeffs);
+ p.Do(m_const_patterns);
+ p.Do(m_sine_table);
+ p.Do(m_afc_coeffs);
+
+ p.Do(m_aram_base_addr);
+ p.Do(m_vpb_base_addr);
+ p.Do(m_reverb_pb_base_addr);
+
+ p.Do(m_reverb_pb_frames_count);
+ p.Do(m_buf_unk0_reverb_last8);
+ p.Do(m_buf_unk1_reverb_last8);
+ p.Do(m_buf_front_left_reverb_last8);
+ p.Do(m_buf_front_right_reverb_last8);
+}
diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.h b/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.h
index 6785f52b70..8f62aaa376 100644
--- a/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.h
+++ b/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.h
@@ -5,113 +5,183 @@
#pragma once
#include "Common/CommonTypes.h"
+#include "Common/MathUtil.h"
#include "Core/HW/DSPHLE/UCodes/UCodes.h"
-// Obviously missing things that must be in here, somewhere among the "unknown":
-// * Volume
-// * L/R Pan
-// * (probably) choice of resampling algorithm (point, linear, cubic)
-
-union ZeldaVoicePB
+class ZeldaAudioRenderer
{
- struct
+public:
+ void PrepareFrame();
+ void AddVoice(u16 voice_id);
+ void FinalizeFrame();
+
+ void SetFlags(u32 flags) { m_flags = flags; }
+ void SetSineTable(std::array&& sine_table) { m_sine_table = sine_table; }
+ void SetConstPatterns(std::array&& patterns) { m_const_patterns = patterns; }
+ void SetResamplingCoeffs(std::array&& coeffs) { m_resampling_coeffs = coeffs; }
+ void SetAfcCoeffs(std::array&& coeffs) { m_afc_coeffs = coeffs; }
+ void SetVPBBaseAddress(u32 addr) { m_vpb_base_addr = addr; }
+ void SetReverbPBBaseAddress(u32 addr) { m_reverb_pb_base_addr = addr; }
+ void SetOutputVolume(u16 volume) { m_output_volume = volume; }
+ void SetOutputLeftBufferAddr(u32 addr) { m_output_lbuf_addr = addr; }
+ void SetOutputRightBufferAddr(u32 addr) { m_output_rbuf_addr = addr; }
+ void SetARAMBaseAddr(u32 addr) { m_aram_base_addr = addr; }
+
+ void DoState(PointerWrap& p);
+
+private:
+ struct VPB;
+
+ // See Zelda.cpp for the list of possible flags.
+ u32 m_flags;
+
+ // Utility functions for audio operations.
+
+ // Apply volume to a buffer. The volume is a fixed point integer, usually
+ // 1.15 or 4.12 in the DAC UCode.
+ template
+ void ApplyVolumeInPlace(std::array* buf, u16 vol)
{
- // Read-Write part
- u16 Status; // 0x00 | 1 = play, 0 = stop
- u16 KeyOff; // 0x01 | writing 1 stops voice?
- u16 RatioInt; // 0x02 | Position delta (playback speed)
- u16 Unk03; // 0x03 | unknown
- u16 NeedsReset; // 0x04 | indicates if some values in PB need to be reset
- u16 ReachedEnd; // 0x05 | set to 1 when end reached
- u16 IsBlank; // 0x06 | 0 = normal sound, 1 = samples are always the same
- u16 Unk07; // 0x07 | unknown, in zelda always 0x0010. Something to do with number of saved samples (0x68)?
-
- u16 SoundType; // 0x08 | "Sound type": so far in zww: 0x0d00 for music (volume mode 0), 0x4861 for sfx (volume mode 1)
- u16 volumeLeft1; // 0x09 | Left Volume 1 // There's probably two of each because they should be ramped within each frame.
- u16 volumeLeft2; // 0x0A | Left Volume 2
- u16 Unk0B; // 0x0B | unknown
-
- u16 SoundType2; // 0x0C | "Sound type" 2 (not really sound type)
- u16 volumeRight1; // 0x0D | Right Volume 1
- u16 volumeRight2; // 0x0E | Right Volume 2
- u16 Unk0F; // 0x0F | unknown
-
- u16 SoundType3; // 0x10 | "Sound type" 3 (not really sound type)
- u16 volumeUnknown1_1; // 0x11 | Unknown Volume 1
- u16 volumeUnknown1_2; // 0x12 | Unknown Volume 1
- u16 Unk13; // 0x13 | unknown
-
- u16 SoundType4; // 0x14 | "Sound type" 4 (not really sound type)
- u16 volumeUnknown2_1; // 0x15 | Unknown Volume 2
- u16 volumeUnknown2_2; // 0x16 | Unknown Volume 2
- u16 Unk17; // 0x17 | unknown
-
- u16 Unk18[0x10]; // 0x18 | unknown
- u16 Unk28; // 0x28 | unknown
- u16 Unk29; // 0x29 | unknown // multiplied by 0x2a @ 0d21/ZWW
- u16 Unk2a; // 0x2A | unknown // loaded at 0d2e/ZWW
- u16 Unk2b; // 0x2B | unknown
- u16 VolumeMode; // 0x2C | unknown // See 0337/ZWW
- u16 Unk2D; // 0x2D | unknown
- u16 Unk2E; // 0x2E | unknown
- u16 Unk2F; // 0x2F | unknown
- u16 CurSampleFrac; // 0x30 | Fractional part of the current sample position
- u16 Unk31; // 0x31 | unknown / unused
- u16 CurBlock; // 0x32 | current block? used by zelda's AFC decoder. we don't need it.
- u16 FixedSample; // 0x33 | sample value for "blank" voices
- u32 RestartPos; // 0x34 | restart pos / "loop start offset"
- u16 Unk36[2]; // 0x36 | unknown // loaded at 0adc/ZWW in 0x21 decoder
- u32 CurAddr; // 0x38 | current address
- u32 RemLength; // 0x3A | remaining length
- u16 ResamplerOldData[4]; // 0x3C | The resampler stores the last 4 decoded samples here from the previous frame, so that the filter kernel has something to read before the start of the buffer.
- u16 Unk40[0x10]; // 0x40 | Used as some sort of buffer by IIR
- u16 Unk50[0x8]; // 0x50 | Used as some sort of buffer by 06ff/ZWW
- u16 Unk58[0x8]; // 0x58 |
- u16 Unk60[0x6]; // 0x60 |
- u16 YN2; // 0x66 | YN2
- u16 YN1; // 0x67 | YN1
- u16 Unk68[0x10]; // 0x68 | Saved samples from last decode?
- u16 FilterState1; // 0x78 | unknown // ZWW: 0c84_FilterBufferInPlace loads and stores. Simply, the filter state.
- u16 FilterState2; // 0x79 | unknown // ZWW: same as above. these two are active if 0x04a8 != 0.
- u16 Unk7A; // 0x7A | unknown
- u16 Unk7B; // 0x7B | unknown
- u16 Unk7C; // 0x7C | unknown
- u16 Unk7D; // 0x7D | unknown
- u16 Unk7E; // 0x7E | unknown
- u16 Unk7F; // 0x7F | unknown
-
- // Read-only part
- u16 Format; // 0x80 | audio format
- u16 RepeatMode; // 0x81 | 0 = one-shot, non zero = loop
- u16 LoopYN1; // 0x82 | YN1 reload (when AFC loops)
- u16 LoopYN2; // 0x83 | YN2 reload (when AFC loops)
- u16 Unk84; // 0x84 | IIR Filter # coefs?
- u16 StopOnSilence; // 0x85 | Stop on silence? (Flag for something volume related. Decides the weird stuff at 035a/ZWW, alco 0cd3)
- u16 Unk86; // 0x86 | unknown
- u16 Unk87; // 0x87 | unknown
- u32 LoopStartPos; // 0x88 | loopstart pos
- u32 Length; // 0x8A | sound length
- u32 StartAddr; // 0x8C | sound start address
- u32 UnkAddr; // 0x8E | ???
- u16 Padding[0x10]; // 0x90 | padding
- u16 Padding2[0x8]; // 0xa0 | FIR filter coefs of some sort (0xa4 controls the appearance of 0xa5-0xa7 and is almost always 0x7FFF)
- u16 FilterEnable; // 0xa8 | FilterBufferInPlace enable
- u16 Padding3[0x7]; // 0xa9 | padding
- u16 Padding4[0x10]; // 0xb0 | padding
- };
- u16 raw[0xc0]; // WARNING-do not use on parts of the 32-bit values - they are swapped!
-};
-
-union ZeldaUnkPB
-{
- struct
+ for (size_t i = 0; i < N; ++i)
+ {
+ s32 tmp = (u32)(*buf)[i] * (u32)vol;
+ tmp >>= 16 - B;
+ MathUtil::Clamp(&tmp, -0x8000, 0x7fff);
+ (*buf)[i] = (s16)tmp;
+ }
+ }
+ template
+ void ApplyVolumeInPlace_1_15(std::array* buf, u16 vol)
{
- u16 Control; // 0x00 | control
- u16 Unk01; // 0x01 | unknown
- u32 SrcAddr; // 0x02 | some address
- u16 Unk04[0xC]; // 0x04 | unknown
- };
- u16 raw[16];
+ ApplyVolumeInPlace(buf, vol);
+ }
+ template
+ void ApplyVolumeInPlace_4_12(std::array* buf, u16 vol)
+ {
+ ApplyVolumeInPlace(buf, vol);
+ }
+
+ // Mixes two buffers together while applying a volume to one of them. The
+ // volume ramps up/down in N steps using the provided step delta value.
+ //
+ // Note: On a real GC, the stepping happens in 32 steps instead. But hey,
+ // we can do better here with very low risk. Why not? :)
+ template
+ s32 AddBuffersWithVolumeRamp(std::array* dst,
+ const std::array& src,
+ s32 vol, s32 step)
+ {
+ if (!vol && !step)
+ return vol;
+
+ for (size_t i = 0; i < N; ++i)
+ {
+ (*dst)[i] += ((vol >> 16) * src[i]) >> 16;
+ vol += step;
+ }
+
+ return vol;
+ }
+
+ // Does not use std::array because it needs to be able to process partial
+ // buffers. Volume is in 1.15 format.
+ void AddBuffersWithVolume(s16* dst, const s16* src, size_t count, u16 vol)
+ {
+ while (count--)
+ {
+ s32 vol_src = ((s32)*src++ * (s32)vol) >> 15;
+ MathUtil::Clamp(&vol_src, -0x8000, 0x7fff);
+ *dst++ += vol_src;
+ }
+ }
+
+ // Whether the frame needs to be prepared or not.
+ bool m_prepared = false;
+
+ // MRAM addresses where output samples should be copied.
+ u32 m_output_lbuf_addr = 0;
+ u32 m_output_rbuf_addr = 0;
+
+ // Output volume applied to buffers before being uploaded to RAM.
+ u16 m_output_volume = 0;
+
+ // Mixing buffers.
+ typedef std::array MixingBuffer;
+ MixingBuffer m_buf_front_left{};
+ MixingBuffer m_buf_front_right{};
+ MixingBuffer m_buf_back_left{};
+ MixingBuffer m_buf_back_right{};
+ MixingBuffer m_buf_front_left_reverb{};
+ MixingBuffer m_buf_front_right_reverb{};
+ MixingBuffer m_buf_back_left_reverb{};
+ MixingBuffer m_buf_back_right_reverb{};
+ MixingBuffer m_buf_unk0_reverb{};
+ MixingBuffer m_buf_unk1_reverb{};
+ MixingBuffer m_buf_unk0{};
+ MixingBuffer m_buf_unk1{};
+ MixingBuffer m_buf_unk2{};
+
+ // Maps a buffer "ID" (really, their address in the DSP DRAM...) to our
+ // buffers. Returns nullptr if no match is found.
+ MixingBuffer* BufferForID(u16 buffer_id);
+
+ // Base address where VPBs are stored linearly in RAM.
+ u32 m_vpb_base_addr;
+ void FetchVPB(u16 voice_id, VPB* vpb);
+ void StoreVPB(u16 voice_id, VPB* vpb);
+
+ // Sine table transferred from MRAM. Contains sin(x) values for x in
+ // [0.0;pi/4] (sin(x) in [1.0;0.0]), in 1.15 fixed format.
+ std::array m_sine_table{};
+
+ // Const patterns used for some voice samples source. 4 x 0x40 samples.
+ std::array m_const_patterns{};
+
+ // Fills up a buffer with the input samples for a voice, represented by its
+ // VPB.
+ void LoadInputSamples(MixingBuffer* buffer, VPB* vpb);
+
+ // Raw samples (pre-resampling) that need to be generated to result in 0x50
+ // post-resampling input samples.
+ u16 NeededRawSamplesCount(const VPB& vpb);
+
+ // Resamples raw samples to 0x50 input samples, using the resampling ratio
+ // and current position information from the VPB.
+ void Resample(VPB* vpb, const s16* src, MixingBuffer* dst);
+
+ // Coefficients used for resampling.
+ std::array m_resampling_coeffs{};
+
+ // If non zero, base MRAM address for sound data transfers from ARAM. On
+ // the Wii, this points to some MRAM location since there is no ARAM to be
+ // used. If zero, use the top of ARAM.
+ u32 m_aram_base_addr = 0;
+ void* GetARAMPtr() const;
+
+ // Downloads PCM encoded samples from ARAM. Handles looping and other
+ // parameters appropriately.
+ template void DownloadPCMSamplesFromARAM(s16* dst, VPB* vpb, u16 requested_samples_count);
+
+ // Downloads AFC encoded samples from ARAM and decode them. Handles looping
+ // and other parameters appropriately.
+ void DownloadAFCSamplesFromARAM(s16* dst, VPB* vpb, u16 requested_samples_count);
+ void DecodeAFC(VPB* vpb, s16* dst, size_t block_count);
+ std::array m_afc_coeffs{};
+
+ // Downloads samples from MRAM while handling appropriate length / looping
+ // behavior.
+ void DownloadRawSamplesFromMRAM(s16* dst, VPB* vpb, u16 requested_samples_count);
+
+ // Applies the reverb effect to Dolby mixed voices based on a set of
+ // per-buffer parameters. Is called twice: once before frame rendering and
+ // once after.
+ void ApplyReverb(bool post_rendering);
+ std::array m_reverb_pb_frames_count{};
+ std::array m_buf_unk0_reverb_last8{};
+ std::array m_buf_unk1_reverb_last8{};
+ std::array m_buf_front_left_reverb_last8{};
+ std::array m_buf_front_right_reverb_last8{};
+ u32 m_reverb_pb_base_addr = 0;
};
class ZeldaUCode : public UCodeInterface
@@ -122,173 +192,107 @@ public:
u32 GetUpdateMs() override;
void HandleMail(u32 mail) override;
- void HandleMail_LightVersion(u32 mail);
- void HandleMail_SMSVersion(u32 mail);
- void HandleMail_NormalVersion(u32 mail);
void Update() override;
- void CopyPBsFromRAM();
- void CopyPBsToRAM();
-
void DoState(PointerWrap &p) override;
- int *templbuffer;
- int *temprbuffer;
+private:
+ // Flags that alter the behavior of the UCode. See Zelda.cpp for complete
+ // list and explanation.
+ u32 m_flags;
- // Simple dump ...
- int DumpAFC(u8* pIn, const int size, const int srate);
+ // Different mail handlers for different protocols.
+ void HandleMailDefault(u32 mail);
+ void HandleMailLight(u32 mail);
+ // UCode state machine. The control flow in the Zelda UCode family is quite
+ // complex, using interrupt handlers heavily to handle incoming messages
+ // which, depending on the type, get handled immediately or are queued in a
+ // command buffer. In this implementation, the synchronous+interrupts flow
+ // of the original DSP implementation is rewritten in an asynchronous/coro
+ // + state machine style. It is less readable, but the best we can do given
+ // our constraints.
+ enum class MailState : u32
+ {
+ WAITING,
+ RENDERING,
+ WRITING_CMD,
+ HALTED,
+ };
+ MailState m_mail_current_state = MailState::WAITING;
+ u32 m_mail_expected_cmd_mails = 0;
+
+ // Utility function to set the current state. Useful for debugging and
+ // logging as a hook point.
+ void SetMailState(MailState new_state)
+ {
+ // WARN_LOG(DSPHLE, "MailState %d -> %d", m_mail_current_state, new_state);
+ m_mail_current_state = new_state;
+ }
+
+ // Voice synchronization / audio rendering flow control. When rendering an
+ // audio frame, only voices up to max_voice_id will be rendered until a
+ // sync mail arrives, increasing the value of max_voice_id. Additionally,
+ // these sync mails contain 16 bit values that are used as bitfields to
+ // control voice skipping on a voice per voice level.
+ u32 m_sync_max_voice_id = 0;
+ std::array m_sync_voice_skip_flags{};
+ bool m_sync_flags_second_half = false;
+
+ // Command buffer (circular queue with r/w indices). Filled by HandleMail
+ // when the state machine is in WRITING_CMD state. Commands get executed
+ // when entering WAITING state and we are not rendering audio.
+ std::array m_cmd_buffer{};
+ u32 m_read_offset = 0;
+ u32 m_write_offset = 0;
+ u32 m_pending_commands_count = 0;
+ bool m_cmd_can_execute = true;
+
+ // Reads a 32 bit value from the command buffer. Advances the read pointer.
u32 Read32()
{
- u32 res = *(u32*)&m_buffer[m_read_offset];
- m_read_offset += 4;
+ if (m_read_offset == m_write_offset)
+ {
+ ERROR_LOG(DSPHLE, "Reading too many command params");
+ return 0;
+ }
+
+ u32 res = m_cmd_buffer[m_read_offset];
+ m_read_offset = (m_read_offset + 1) % (sizeof (m_cmd_buffer) / sizeof (u32));
return res;
}
-private:
- // These map CRC to behavior.
-
- // DMA version
- // - sound data transferred using DMA instead of accelerator
- bool IsDMAVersion() const
+ // Writes a 32 bit value to the command buffer. Advances the write pointer.
+ void Write32(u32 val)
{
- switch (m_crc)
- {
- case 0xb7eb9a9c: // Wii Pikmin - PAL
- case 0xeaeb38cc: // Wii Pikmin 2 - PAL
- case 0x6c3f6f94: // Wii Zelda TP - PAL
- case 0xD643001F: // Super Mario Galaxy
- return true;
- default:
- return false;
- }
+ m_cmd_buffer[m_write_offset] = val;
+ m_write_offset = (m_write_offset + 1) % (sizeof (m_cmd_buffer) / sizeof (u32));
}
- // Light version
- // - slightly different communication protocol (no list begin mail)
- // - exceptions and interrupts not used
- bool IsLightVersion() const
+ // Tries to run as many commands as possible until either the command
+ // buffer is empty (pending_commands == 0) or we reached a long lived
+ // command that needs to hijack the mail control flow.
+ //
+ // Might change the current state to indicate crashy commands.
+ void RunPendingCommands();
+
+ // Sends the two mails from DSP to CPU to ack the command execution.
+ enum class CommandAck : u32
{
- switch (m_crc)
- {
- case 0x6ba3b3ea: // IPL - PAL
- case 0x24b22038: // IPL - NTSC/NTSC-JAP
- case 0x42f64ac4: // Luigi's Mansion
- case 0x4be6a5cb: // AC, Pikmin NTSC
- return true;
- default:
- return false;
- }
- }
-
- // SMS version
- // - sync mails are sent every frame, not every 16 PBs
- // (named SMS because it's used by Super Mario Sunshine
- // and I couldn't find a better name)
- bool IsSMSVersion() const
- {
- switch (m_crc)
- {
- case 0x56d36052: // Super Mario Sunshine
- case 0x267fd05a: // Pikmin PAL
- return true;
- default:
- return false;
- }
- }
-
- // These are the only dynamically allocated things allowed in the ucode.
- s32* m_voice_buffer;
- s16* m_resample_buffer;
- s32* m_left_buffer;
- s32* m_right_buffer;
-
- // If you add variables, remember to keep DoState() and the constructor up to date.
-
- s16 m_afc_coef_table[32];
- s16 m_misc_table[0x280];
-
- bool m_sync_in_progress;
- u32 m_max_voice;
- u32 m_sync_flags[16];
-
- // Used by SMS version
- u32 m_num_sync_mail;
-
- u32 m_num_voices;
-
- bool m_sync_cmd_pending;
- u32 m_current_voice;
- u32 m_current_buffer;
- u32 m_num_buffers;
-
- // Those are set by command 0x1 (DsetupTable)
- u32 m_voice_pbs_addr;
- u32 m_unk_table_addr;
- u32 m_afc_coef_table_addr;
- u32 m_reverb_pbs_addr;
-
- u32 m_right_buffers_addr;
- u32 m_left_buffers_addr;
- //u32 m_unkAddr;
- u32 m_pos;
-
- // Only in SMG ucode
- // Set by command 0xE (DsetDMABaseAddr)
- u32 m_dma_base_addr;
-
- // List, buffer management =====================
- u32 m_num_steps;
- bool m_list_in_progress;
- u32 m_step;
- u8 m_buffer[1024];
-
- u32 m_read_offset;
-
- enum EMailState
- {
- WaitForMail,
- ReadingFrameSync,
- ReadingMessage,
- ReadingSystemMsg
+ STANDARD,
+ DONE_RENDERING,
};
+ void SendCommandAck(CommandAck ack_type, u16 sync_value);
- EMailState m_mail_state;
- u16 m_pb_mask[0x10];
+ // Audio rendering flow control state.
+ u32 m_rendering_requested_frames = 0;
+ u16 m_rendering_voices_per_frame = 0;
+ u32 m_rendering_curr_frame = 0;
+ u32 m_rendering_curr_voice = 0;
- u32 m_num_pbs;
- u32 m_pb_address; // The main param block array
- u32 m_pb_address2; // 4 smaller param blocks
+ bool RenderingInProgress() const { return m_rendering_curr_frame != m_rendering_requested_frames; }
+ void RenderAudio();
- void ExecuteList();
-
- u8 *GetARAMPointer(u32 address);
-
- // AFC decoder
- static void AFCdecodebuffer(const s16 *coef, const char *input, signed short *out, short *histp, short *hist2p, int type);
-
- void ReadVoicePB(u32 _Addr, ZeldaVoicePB& PB);
- void WritebackVoicePB(u32 _Addr, ZeldaVoicePB& PB);
-
- // Voice formats
- void RenderSynth_Constant(ZeldaVoicePB &PB, s32* _Buffer, int _Size);
- void RenderSynth_RectWave(ZeldaVoicePB &PB, s32* _Buffer, int _Size);
- void RenderSynth_SawWave(ZeldaVoicePB &PB, s32* _Buffer, int _Size);
- void RenderSynth_WaveTable(ZeldaVoicePB &PB, s32* _Buffer, int _Size);
-
- void RenderVoice_PCM8(ZeldaVoicePB& PB, s16* _Buffer, int _Size);
- void RenderVoice_PCM16(ZeldaVoicePB& PB, s16* _Buffer, int _Size);
-
- void RenderVoice_AFC(ZeldaVoicePB& PB, s16* _Buffer, int _Size);
- void RenderVoice_Raw(ZeldaVoicePB& PB, s16* _Buffer, int _Size);
-
- void Resample(ZeldaVoicePB &PB, int size, s16 *in, s32 *out, bool do_resample = false);
-
- int ConvertRatio(int pb_ratio);
- int SizeForResampling(ZeldaVoicePB &PB, int size);
-
- // Renders a voice and mixes it into LeftBuffer, RightBuffer
- void RenderAddVoice(ZeldaVoicePB& PB, s32* _LeftBuffer, s32* _RightBuffer, int _Size);
-
- void MixAudio();
+ // Main object handling audio rendering logic and state.
+ ZeldaAudioRenderer m_renderer;
};
diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/ZeldaADPCM.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/ZeldaADPCM.cpp
deleted file mode 100644
index 52e7ae44bb..0000000000
--- a/Source/Core/Core/HW/DSPHLE/UCodes/ZeldaADPCM.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2009 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-#include "Common/CommonTypes.h"
-#include "Common/MathUtil.h"
-#include "Core/HW/DSPHLE/UCodes/Zelda.h"
-
-void ZeldaUCode::AFCdecodebuffer(const s16 *coef, const char *src, signed short *out, short *histp, short *hist2p, int type)
-{
- // First 2 nibbles are ADPCM scale etc.
- short delta = 1 << (((*src) >> 4) & 0xf);
- short idx = (*src) & 0xf;
- src++;
-
- short nibbles[16];
- if (type == 9)
- {
- for (int i = 0; i < 16; i += 2)
- {
- nibbles[i + 0] = *src >> 4;
- nibbles[i + 1] = *src & 15;
- src++;
- }
-
- for (auto& nibble : nibbles)
- {
- if (nibble >= 8)
- nibble = nibble - 16;
- nibble <<= 11;
- }
- }
- else
- {
- // In Pikmin, Dolphin's engine sound is using AFC type 5, even though such a sound is hard
- // to compare, it seems like to sound exactly like a real GC
- // In Super Mario Sunshine, you can get such a sound by talking to/jumping on anyone
- for (int i = 0; i < 16; i += 4)
- {
- nibbles[i + 0] = (*src >> 6) & 0x03;
- nibbles[i + 1] = (*src >> 4) & 0x03;
- nibbles[i + 2] = (*src >> 2) & 0x03;
- nibbles[i + 3] = (*src >> 0) & 0x03;
- src++;
- }
-
- for (auto& nibble : nibbles)
- {
- if (nibble >= 2)
- nibble = nibble - 4;
- nibble <<= 13;
- }
- }
-
- short hist = *histp;
- short hist2 = *hist2p;
- for (int i = 0; i < 16; i++)
- {
- int sample = delta * nibbles[i] + ((int)hist * coef[idx * 2]) + ((int)hist2 * coef[idx * 2 + 1]);
- sample >>= 11;
- MathUtil::Clamp(&sample, -32768, 32767);
- out[i] = sample;
- hist2 = hist;
- hist = (short)sample;
- }
- *histp = hist;
- *hist2p = hist2;
-}
diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/ZeldaSynth.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/ZeldaSynth.cpp
deleted file mode 100644
index 054efeaddd..0000000000
--- a/Source/Core/Core/HW/DSPHLE/UCodes/ZeldaSynth.cpp
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2008 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-#include
-
-#include "Core/HW/DSPHLE/UCodes/UCodes.h"
-#include "Core/HW/DSPHLE/UCodes/Zelda.h"
-
-void ZeldaUCode::RenderSynth_RectWave(ZeldaVoicePB &PB, s32* _Buffer, int _Size)
-{
- s64 ratio = ((s64)PB.RatioInt << 16) * 16;
- s64 TrueSamplePosition = PB.CurSampleFrac;
-
- // PB.Format == 0x3 -> Rectangular Wave, 0x0 -> Square Wave
- unsigned int mask = PB.Format ? 3 : 1;
- // int shift = PB.Format ? 2 : 1; // Unused?
-
- u32 pos[2] = {0, 0};
- int i = 0;
-
- if (PB.KeyOff != 0)
- return;
-
- if (PB.NeedsReset)
- {
- PB.RemLength = PB.Length - PB.RestartPos;
- PB.CurAddr = PB.StartAddr + (PB.RestartPos << 1);
- PB.ReachedEnd = 0;
- }
-
-_lRestart:
- if (PB.ReachedEnd)
- {
- PB.ReachedEnd = 0;
-
- if (PB.RepeatMode == 0)
- {
- PB.KeyOff = 1;
- PB.RemLength = 0;
- PB.CurAddr = PB.StartAddr + (PB.RestartPos << 1) + PB.Length;
- return;
- }
- else
- {
- PB.RestartPos = PB.LoopStartPos;
- PB.RemLength = PB.Length - PB.RestartPos;
- PB.CurAddr = PB.StartAddr + (PB.RestartPos << 1);
- pos[1] = 0; pos[0] = 0;
- }
- }
-
- while (i < _Size)
- {
- s16 sample = ((pos[1] & mask) == mask) ? 0xc000 : 0x4000;
-
- TrueSamplePosition += (ratio >> 16);
-
- _Buffer[i++] = (s32)sample;
-
- (*(u64*)&pos) += ratio;
- if ((pos[1] + ((PB.CurAddr - PB.StartAddr) >> 1)) >= PB.Length)
- {
- PB.ReachedEnd = 1;
- goto _lRestart;
- }
- }
-
- if (PB.RemLength < pos[1])
- {
- PB.RemLength = 0;
- PB.ReachedEnd = 1;
- }
- else
- {
- PB.RemLength -= pos[1];
- }
-
- PB.CurSampleFrac = TrueSamplePosition & 0xFFFF;
-}
-
-void ZeldaUCode::RenderSynth_SawWave(ZeldaVoicePB &PB, s32* _Buffer, int _Size)
-{
- s32 ratio = (s32)ceil((float)PB.RatioInt / 3);
- s64 pos = PB.CurSampleFrac;
-
- for (int i = 0; i < _Size; i++)
- {
- pos += ratio;
- _Buffer[i] = pos & 0xFFFF;
- }
-
- PB.CurSampleFrac = pos & 0xFFFF;
-}
-
-void ZeldaUCode::RenderSynth_Constant(ZeldaVoicePB &PB, s32* _Buffer, int _Size)
-{
- // TODO: Header, footer
- for (int i = 0; i < _Size; i++)
- _Buffer[i] = (s32)PB.RatioInt;
-}
-
-// A piece of code from LLE so we can see how the wrap register affects the sound
-
-inline u16 AddValueToReg(u32 ar, s32 ix)
-{
- u32 wr = 0x3f;
- u32 mx = (wr | 1) << 1;
- u32 nar = ar + ix;
- u32 dar = (nar ^ ar ^ ix) & mx;
-
- if (ix >= 0)
- {
- if (dar > wr) //overflow
- nar -= wr + 1;
- }
- else
- {
- if ((((nar + wr + 1) ^ nar) & dar) <= wr) //underflow or below min for mask
- nar += wr + 1;
- }
- return nar;
-}
-
-void ZeldaUCode::RenderSynth_WaveTable(ZeldaVoicePB &PB, s32* _Buffer, int _Size)
-{
- u16 address;
-
- switch (PB.Format)
- {
- default:
- case 0x0004:
- address = 0x140;
- break;
-
- case 0x0007:
- address = 0x100;
- break;
-
- case 0x000b:
- address = 0x180;
- break;
-
- case 0x000c:
- address = 0x1c0;
- break;
- }
-
- // TODO: Resample this!
- INFO_LOG(DSPHLE, "Synthesizing the incomplete format 0x%04x", PB.Format);
-
- u64 ACC0 = PB.CurSampleFrac << 6;
-
- ACC0 &= 0xffff003fffffULL;
-
- address = AddValueToReg(address, ((ACC0 >> 16) & 0xffff));
- ACC0 &= 0xffff0000ffffULL;
-
- for (int i = 0; i < 0x50; i++)
- {
- _Buffer[i] = m_misc_table[address];
-
- ACC0 += PB.RatioInt << 5;
- address = AddValueToReg(address, ((ACC0 >> 16) & 0xffff));
-
- ACC0 &= 0xffff0000ffffULL;
- }
-
- ACC0 += address << 16;
- PB.CurSampleFrac = (ACC0 >> 6) & 0xffff;
-}
-
-
diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/ZeldaVoice.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/ZeldaVoice.cpp
deleted file mode 100644
index e6db1b0d85..0000000000
--- a/Source/Core/Core/HW/DSPHLE/UCodes/ZeldaVoice.cpp
+++ /dev/null
@@ -1,790 +0,0 @@
-// Copyright 2009 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-#include
-
-#include "Common/CommonFuncs.h"
-#include "Common/MathUtil.h"
-
-#include "Core/HW/DSP.h"
-#include "Core/HW/Memmap.h"
-#include "Core/HW/DSPHLE/UCodes/UCodes.h"
-#include "Core/HW/DSPHLE/UCodes/Zelda.h"
-
-void ZeldaUCode::ReadVoicePB(u32 _Addr, ZeldaVoicePB& PB)
-{
- u16 *memory = (u16*)Memory::GetPointer(_Addr);
-
- // Perform byteswap
- for (int i = 0; i < (0x180 / 2); i++)
- ((u16*)&PB)[i] = Common::swap16(memory[i]);
-
- // Word swap all 32-bit variables.
- PB.RestartPos = (PB.RestartPos << 16) | (PB.RestartPos >> 16);
- PB.CurAddr = (PB.CurAddr << 16) | (PB.CurAddr >> 16);
- PB.RemLength = (PB.RemLength << 16) | (PB.RemLength >> 16);
- // Read only part
- PB.LoopStartPos = (PB.LoopStartPos << 16) | (PB.LoopStartPos >> 16);
- PB.Length = (PB.Length << 16) | (PB.Length >> 16);
- PB.StartAddr = (PB.StartAddr << 16) | (PB.StartAddr >> 16);
- PB.UnkAddr = (PB.UnkAddr << 16) | (PB.UnkAddr >> 16);
-}
-
-void ZeldaUCode::WritebackVoicePB(u32 _Addr, ZeldaVoicePB& PB)
-{
- u16 *memory = (u16*)Memory::GetPointer(_Addr);
-
- // Word swap all 32-bit variables.
- PB.RestartPos = (PB.RestartPos << 16) | (PB.RestartPos >> 16);
- PB.CurAddr = (PB.CurAddr << 16) | (PB.CurAddr >> 16);
- PB.RemLength = (PB.RemLength << 16) | (PB.RemLength >> 16);
-
- // Perform byteswap
- // Only the first 0x100 bytes are written back
- for (int i = 0; i < (0x100 / 2); i++)
- memory[i] = Common::swap16(((u16*)&PB)[i]);
-}
-
-int ZeldaUCode::ConvertRatio(int pb_ratio)
-{
- return pb_ratio * 16;
-}
-
-int ZeldaUCode::SizeForResampling(ZeldaVoicePB &PB, int size)
-{
- // This is the little calculation at the start of every sample decoder
- // in the ucode.
- return (PB.CurSampleFrac + size * ConvertRatio(PB.RatioInt)) >> 16;
-}
-
-// Simple resampler, linear interpolation.
-// Any future state should be stored in PB.raw[0x3c to 0x3f].
-// In must point 4 samples into a buffer.
-void ZeldaUCode::Resample(ZeldaVoicePB &PB, int size, s16 *in, s32 *out, bool do_resample)
-{
- if (!do_resample)
- {
- memcpy(out, in, size * sizeof(int));
- return;
- }
-
- for (int i = 0; i < 4; i++)
- {
- in[i - 4] = (s16)PB.ResamplerOldData[i];
- }
-
- int ratio = ConvertRatio(PB.RatioInt);
- int in_size = SizeForResampling(PB, size);
-
- int position = PB.CurSampleFrac;
- for (int i = 0; i < size; i++)
- {
- int int_pos = (position >> 16);
- int frac = ((position & 0xFFFF) >> 1);
- out[i] = (in[int_pos - 3] * (frac ^ 0x7FFF) + in[int_pos - 2] * frac) >> 15;
- position += ratio;
- }
-
- for (int i = 0; i < 4; i++)
- {
- PB.ResamplerOldData[i] = (u16)(s16)in[in_size - 4 + i];
- }
- PB.CurSampleFrac = position & 0xFFFF;
-}
-
-static void UpdateSampleCounters10(ZeldaVoicePB &PB)
-{
- PB.RemLength = PB.Length - PB.RestartPos;
- PB.CurAddr = PB.StartAddr + (PB.RestartPos << 1);
- PB.ReachedEnd = 0;
-}
-
-void ZeldaUCode::RenderVoice_PCM16(ZeldaVoicePB &PB, s16 *_Buffer, int _Size)
-{
- int _RealSize = SizeForResampling(PB, _Size);
- u32 rem_samples = _RealSize;
- if (PB.KeyOff)
- goto clear_buffer;
- if (PB.NeedsReset)
- {
- UpdateSampleCounters10(PB);
- for (int i = 0; i < 4; i++)
- PB.ResamplerOldData[i] = 0; // Doesn't belong here, but dunno where to do it.
- }
- if (PB.ReachedEnd)
- {
- PB.ReachedEnd = 0;
-reached_end:
- if (!PB.RepeatMode)
- {
- // One shot - play zeros the rest of the buffer.
-clear_buffer:
- for (u32 i = 0; i < rem_samples; i++)
- *_Buffer++ = 0;
- PB.KeyOff = 1;
- return;
- }
- else
- {
- PB.RestartPos = PB.LoopStartPos;
- UpdateSampleCounters10(PB);
- }
- }
- // SetupAccelerator
- const s16 *read_ptr = (s16*)GetARAMPointer(PB.CurAddr);
- if (PB.RemLength < (u32)rem_samples)
- {
- // finish-up loop
- for (u32 i = 0; i < PB.RemLength; i++)
- *_Buffer++ = Common::swap16(*read_ptr++);
- rem_samples -= PB.RemLength;
- goto reached_end;
- }
- // main render loop
- for (u32 i = 0; i < rem_samples; i++)
- *_Buffer++ = Common::swap16(*read_ptr++);
-
- PB.RemLength -= rem_samples;
- if (PB.RemLength == 0)
- PB.ReachedEnd = 1;
- PB.CurAddr += rem_samples << 1;
-}
-
-static void UpdateSampleCounters8(ZeldaVoicePB &PB)
-{
- PB.RemLength = PB.Length - PB.RestartPos;
- PB.CurAddr = PB.StartAddr + PB.RestartPos;
- PB.ReachedEnd = 0;
-}
-
-void ZeldaUCode::RenderVoice_PCM8(ZeldaVoicePB &PB, s16 *_Buffer, int _Size)
-{
- int _RealSize = SizeForResampling(PB, _Size);
- u32 rem_samples = _RealSize;
- if (PB.KeyOff)
- goto clear_buffer;
- if (PB.NeedsReset)
- {
- UpdateSampleCounters8(PB);
- for (int i = 0; i < 4; i++)
- PB.ResamplerOldData[i] = 0; // Doesn't belong here, but dunno where to do it.
- }
- if (PB.ReachedEnd)
- {
-reached_end:
- PB.ReachedEnd = 0;
- if (!PB.RepeatMode)
- {
- // One shot - play zeros the rest of the buffer.
-clear_buffer:
- for (u32 i = 0; i < rem_samples; i++)
- *_Buffer++ = 0;
- PB.KeyOff = 1;
- return;
- }
- else
- {
- PB.RestartPos = PB.LoopStartPos;
- UpdateSampleCounters8(PB);
- }
- }
-
- // SetupAccelerator
- const s8 *read_ptr = (s8*)GetARAMPointer(PB.CurAddr);
- if (PB.RemLength < (u32)rem_samples)
- {
- // finish-up loop
- for (u32 i = 0; i < PB.RemLength; i++)
- *_Buffer++ = (s8)(*read_ptr++) << 8;
- rem_samples -= PB.RemLength;
- goto reached_end;
- }
- // main render loop
- for (u32 i = 0; i < rem_samples; i++)
- *_Buffer++ = (s8)(*read_ptr++) << 8;
-
- PB.RemLength -= rem_samples;
- if (PB.RemLength == 0)
- PB.ReachedEnd = 1;
- PB.CurAddr += rem_samples;
-}
-
-template
-void PrintObject(const T &Obj)
-{
- std::stringstream ss;
- u8 *o = (u8 *)&Obj;
-
- // If this miscompiles, adjust the size of
- // ZeldaVoicePB to 0x180 bytes (0xc0 shorts).
- static_assert(sizeof(ZeldaVoicePB) == 0x180, "ZeldaVoicePB incorrectly defined.");
-
- ss << std::hex;
- for (size_t i = 0; i < sizeof(T); i++)
- {
- if ((i & 1) == 0)
- ss << ' ';
- ss.width(2);
- ss.fill('0');
- ss << Common::swap16(o[i]);
- }
-
- DEBUG_LOG(DSPHLE, "AFC PB:%s", ss.str().c_str());
-}
-
-void ZeldaUCode::RenderVoice_AFC(ZeldaVoicePB &PB, s16 *_Buffer, int _Size)
-{
- // TODO: Compare mono, stereo and surround samples
-#if defined DEBUG || defined DEBUGFAST
- PrintObject(PB);
-#endif
-
- int _RealSize = SizeForResampling(PB, _Size);
-
- // initialize "decoder" if the sample is played the first time
- if (PB.NeedsReset != 0)
- {
- // This is 0717_ReadOutPBStuff
- // increment 4fb
- // zelda:
- // perhaps init or "has played before"
- PB.CurBlock = 0x00;
- PB.YN2 = 0x00; // history1
- PB.YN1 = 0x00; // history2
-
- // Length in samples.
- PB.RemLength = PB.Length;
- // Copy ARAM addr from r to rw area.
- PB.CurAddr = PB.StartAddr;
- PB.ReachedEnd = 0;
- PB.CurSampleFrac = 0;
-
- for (int i = 0; i < 4; i++)
- PB.ResamplerOldData[i] = 0;
- }
-
- if (PB.KeyOff != 0) // 0747 early out... i dunno if this can happen because we filter it above
- {
- for (int i = 0; i < _RealSize; i++)
- *_Buffer++ = 0;
- return;
- }
-
- // Round upwards how many samples we need to copy, 0759
- // u32 frac = NumberOfSamples & 0xF;
- // NumberOfSamples = (NumberOfSamples + 0xf) >> 4; // i think the lower 4 are the fraction
-
- const u8 *source;
- u32 ram_mask = 1024 * 1024 * 16 - 1;
- if (IsDMAVersion())
- {
- source = Memory::GetPointer(m_dma_base_addr);
- ram_mask = 1024 * 1024 * 64 - 1;
- }
- else
- {
- source = DSP::GetARAMPtr();
- }
-
- int sampleCount = 0; // must be above restart.
-
-restart:
- if (PB.ReachedEnd)
- {
- PB.ReachedEnd = 0;
-
- if ((PB.RepeatMode == 0) || (PB.StopOnSilence != 0))
- {
- PB.KeyOff = 1;
- PB.RemLength = 0;
- PB.CurAddr = PB.StartAddr + PB.RestartPos + PB.Length;
-
- while (sampleCount < _RealSize)
- _Buffer[sampleCount++] = 0;
- return;
- }
- else
- {
- //AFC looping
- // The loop start pos is incorrect? (Fixed?), so samples will loop a bit wrong.
- // this fixes the intro music in ZTP.
- PB.RestartPos = PB.LoopStartPos;
- PB.RemLength = PB.Length - PB.RestartPos;
- // see DSP_UC_Zelda.txt line 2817
- PB.CurAddr = ((((((PB.LoopStartPos >> 4) & 0xffff0000)*PB.Format)<<16)+
- (((PB.LoopStartPos >> 4) & 0xffff)*PB.Format))+PB.StartAddr) & 0xffffffff;
-
- // Hmm, this shouldn't be reversed .. or should it? Is it different between versions of the ucode?
- // -> it has to be reversed in ZTP, otherwise intro music is broken...
- PB.YN1 = PB.LoopYN2;
- PB.YN2 = PB.LoopYN1;
- }
- }
-
- short outbuf[16] = {0};
- u16 prev_yn1 = PB.YN1;
- u16 prev_yn2 = PB.YN2;
- u32 prev_addr = PB.CurAddr;
-
- // Prefill the decode buffer.
- AFCdecodebuffer(m_afc_coef_table, (char*)(source + (PB.CurAddr & ram_mask)), outbuf, (short*)&PB.YN2, (short*)&PB.YN1, PB.Format);
- PB.CurAddr += PB.Format; // 9 or 5
-
- u32 SamplePosition = PB.Length - PB.RemLength;
- while (sampleCount < _RealSize)
- {
- _Buffer[sampleCount] = outbuf[SamplePosition & 15];
- sampleCount++;
-
- SamplePosition++;
- PB.RemLength--;
- if (PB.RemLength == 0)
- {
- PB.ReachedEnd = 1;
- goto restart;
- }
-
- // Need new samples!
- if ((SamplePosition & 15) == 0)
- {
- prev_yn1 = PB.YN1;
- prev_yn2 = PB.YN2;
- prev_addr = PB.CurAddr;
-
- AFCdecodebuffer(m_afc_coef_table, (char*)(source + (PB.CurAddr & ram_mask)), outbuf, (short*)&PB.YN2, (short*)&PB.YN1, PB.Format);
- PB.CurAddr += PB.Format; // 9 or 5
- }
- }
-
- // Here we should back off to the previous addr/yn1/yn2, since we didn't consume the full last block.
- // We'll re-decode it the next time around.
- PB.YN2 = prev_yn2;
- PB.YN1 = prev_yn1;
- PB.CurAddr = prev_addr;
-
- PB.NeedsReset = 0;
- // PB.CurBlock = 0x10 - (PB.LoopStartPos & 0xf);
- // write back
- // NumberOfSamples = (NumberOfSamples << 4) | frac; // missing fraction
-
- // i think pTest[0x3a] and pTest[0x3b] got an update after you have decoded some samples...
- // just decrement them with the number of samples you have played
- // and increase the ARAM Offset in pTest[0x38], pTest[0x39]
-
- // end of block (Zelda 03b2)
-}
-
-void Decoder21_ReadAudio(ZeldaVoicePB &PB, int size, s16 *_Buffer);
-
-// Researching what's actually inside the mysterious 0x21 case
-// 0x21 seems to really just be reading raw 16-bit audio from RAM (not ARAM).
-// The rules seem to be quite different, though.
-// It's used for streaming, not for one-shot or looped sample playback.
-void ZeldaUCode::RenderVoice_Raw(ZeldaVoicePB &PB, s16 *_Buffer, int _Size)
-{
- // Decoder0x21 starts here.
- u32 _RealSize = SizeForResampling(PB, _Size);
-
- // Decoder0x21Core starts here.
- u32 AX0 = _RealSize;
-
- // ERROR_LOG(DSPHLE, "0x21 volume mode: %i , stop: %i ", PB.VolumeMode, PB.StopOnSilence);
-
- // The PB.StopOnSilence check is a hack, we should check the buffers and enter this
- // only when the buffer is completely 0 (i.e. when the music has finished fading out)
- if (PB.StopOnSilence || PB.RemLength < (u32)_RealSize)
- {
- WARN_LOG(DSPHLE, "Raw: END");
- // Let's ignore this entire case since it doesn't seem to happen
- // in Zelda, since Length is set to 0xF0000000
- // blah
- // blah
- // readaudio
- // blah
- PB.RemLength = 0;
- PB.KeyOff = 1;
- }
-
- PB.RemLength -= _RealSize;
-
- u64 ACC0 = (u32)(PB.raw[0x8a ^ 1] << 16); // 0x8a 0ad5, yes it loads a, not b
- u64 ACC1 = (u32)(PB.raw[0x34 ^ 1] << 16); // 0x34
-
- // ERROR_LOG(DSPHLE, "%08x %08x", (u32)ACC0, (u32)ACC1);
-
- ACC0 -= ACC1;
-
- PB.Unk36[0] = (u16)(ACC0 >> 16);
-
- ACC0 -= AX0 << 16;
-
- if ((s64)ACC0 < 0)
- {
- // ERROR_LOG(DSPHLE, "Raw loop: ReadAudio size = %04x 34:%04x %08x", PB.Unk36[0], PB.raw[0x34 ^ 1], (int)ACC0);
- Decoder21_ReadAudio(PB, PB.Unk36[0], _Buffer);
-
- ACC0 = -(s64)ACC0;
- _Buffer += PB.Unk36[0];
-
- PB.raw[0x34 ^ 1] = 0;
-
- PB.StartAddr = PB.LoopStartPos;
-
- Decoder21_ReadAudio(PB, (int)(ACC0 >> 16), _Buffer);
- return;
- }
-
- Decoder21_ReadAudio(PB, _RealSize, _Buffer);
-}
-
-void Decoder21_ReadAudio(ZeldaVoicePB &PB, int size, s16* _Buffer)
-{
- // 0af6
- if (!size)
- return;
-
-#if 0
- // 0afa
- u32 AX1 = (PB.RestartPos >> 16) & 1; // PB.raw[0x34], except that it's part of a dword
- // 0b00 - Eh, WTF.
- u32 ACC0 = PB.StartAddr + ((PB.RestartPos >> 16) << 1) - 2*AX1;
- u32 ACC1 = (size << 16) + 0x20000;
- // All this trickery, and more, seems to be to align the DMA, which
- // we really don't care about. So let's skip it. See the #else.
-
-#else
- // ERROR_LOG(DSPHLE, "ReadAudio: %08x %08x", PB.StartAddr, PB.raw[0x34 ^ 1]);
- u32 ACC0 = PB.StartAddr + (PB.raw[0x34 ^ 1] << 1);
- u32 ACC1 = (size << 16);
-#endif
- // ACC0 is the address
- // ACC1 is the read size
-
- const u16* src = (u16*)Memory::GetPointer(ACC0 & Memory::RAM_MASK);
-
- for (u32 i = 0; i < (ACC1 >> 16); i++)
- {
- _Buffer[i] = Common::swap16(src[i]);
- }
-
- PB.raw[0x34 ^ 1] += size;
-}
-
-
-void ZeldaUCode::RenderAddVoice(ZeldaVoicePB &PB, s32* _LeftBuffer, s32* _RightBuffer, int _Size)
-{
- if (PB.IsBlank)
- {
- s32 sample = (s32)(s16)PB.FixedSample;
- for (int i = 0; i < _Size; i++)
- m_voice_buffer[i] = sample;
-
- goto ContinueWithBlock; // Yes, a goto. Yes, it's evil, but it makes the flow look much more like the DSP code.
- }
-
- // XK: Use this to disable MIDI music (GREAT for testing). Also kills some sound FX.
- //if (PB.SoundType == 0x0d00)
- //{
- // PB.NeedsReset = 0;
- // return;
- //}
-
- // The Resample calls actually don't resample yet.
-
- // ResampleBuffer corresponds to 0x0580 in ZWW ucode.
- // VoiceBuffer corresponds to 0x0520.
-
- // First jump table at ZWW: 2a6
- switch (PB.Format)
- {
- case 0x0005: // AFC with extra low bitrate (32:5 compression).
- case 0x0009: // AFC with normal bitrate (32:9 compression).
- RenderVoice_AFC(PB, m_resample_buffer + 4, _Size);
- Resample(PB, _Size, m_resample_buffer + 4, m_voice_buffer, true);
- break;
-
- case 0x0008: // PCM8 - normal PCM 8-bit audio. Used in Mario Kart DD + very little in Zelda WW.
- RenderVoice_PCM8(PB, m_resample_buffer + 4, _Size);
- Resample(PB, _Size, m_resample_buffer + 4, m_voice_buffer, true);
- break;
-
- case 0x0010: // PCM16 - normal PCM 16-bit audio.
- RenderVoice_PCM16(PB, m_resample_buffer + 4, _Size);
- Resample(PB, _Size, m_resample_buffer + 4, m_voice_buffer, true);
- break;
-
- case 0x0020:
- // Normally, this shouldn't resample, it should just decode directly
- // to the output buffer. However, (if we ever see this sound type), we'll
- // have to resample anyway since we're running at a different sample rate.
-
- RenderVoice_Raw(PB, m_resample_buffer + 4, _Size);
- Resample(PB, _Size, m_resample_buffer + 4, m_voice_buffer, true);
- break;
-
- case 0x0021:
- // Raw sound from RAM. Important for Zelda WW. Cutscenes use the music
- // to let the game know they ended
- RenderVoice_Raw(PB, m_resample_buffer + 4, _Size);
- Resample(PB, _Size, m_resample_buffer + 4, m_voice_buffer, true);
- break;
-
- default:
- // Second jump table
- // TODO: Cases to find examples of:
- // -0x0002
- // -0x0003
- // -0x0006
- // -0x000a
- switch (PB.Format)
- {
- // Synthesized sounds
- case 0x0003: WARN_LOG(DSPHLE, "PB Format 0x03 used!");
- case 0x0000: // Example: Magic meter filling up in ZWW
- RenderSynth_RectWave(PB, m_voice_buffer, _Size);
- break;
-
- case 0x0001: // Example: "Denied" sound when trying to pull out a sword indoors in ZWW
- RenderSynth_SawWave(PB, m_voice_buffer, _Size);
- break;
-
- case 0x0006:
- WARN_LOG(DSPHLE, "Synthesizing 0x0006 (constant sound)");
- RenderSynth_Constant(PB, m_voice_buffer, _Size);
- break;
-
- // These are more "synth" formats - square wave, saw wave etc.
- case 0x0002:
- WARN_LOG(DSPHLE, "PB Format 0x02 used!");
- break;
-
- case 0x0004: // Example: Big Pikmin onion mothership landing/building a bridge in Pikmin
- case 0x0007: // Example: "success" SFX in Pikmin 1, Pikmin 2 in a cave, not sure what sound it is.
- case 0x000b: // Example: SFX in area selection menu in Pikmin
- case 0x000c: // Example: beam of death/yellow force-field in Temple of the Gods, ZWW
- RenderSynth_WaveTable(PB, m_voice_buffer, _Size);
- break;
-
- default:
- // TODO: Implement general decoder here
- memset(m_voice_buffer, 0, _Size * sizeof(s32));
- ERROR_LOG(DSPHLE, "Unknown MixAddVoice format in zelda %04x", PB.Format);
- break;
- }
- }
-
-ContinueWithBlock:
-
- if (PB.FilterEnable)
- { // 0x04a8
- for (int i = 0; i < _Size; i++)
- {
- // TODO: Apply filter from ZWW: 0c84_FilterBufferInPlace
- }
- }
-
- for (int i = 0; i < _Size; i++)
- {
- // TODO?
- }
-
- // Apply volume. There are two different modes.
- if (PB.VolumeMode != 0)
- {
- // Complex volume mode. Let's see what we can do.
- if (PB.StopOnSilence)
- {
- PB.raw[0x2b] = PB.raw[0x2a] >> 1;
- if (PB.raw[0x2b] == 0)
- {
- PB.KeyOff = 1;
- }
- }
-
- short AX0L = PB.raw[0x28] >> 8;
- short AX0H = PB.raw[0x28] & 0x7F;
- short AX1L = AX0L ^ 0x7F;
- short AX1H = AX0H ^ 0x7F;
- AX0L = m_misc_table[0x200 + AX0L];
- AX0H = m_misc_table[0x200 + AX0H];
- AX1L = m_misc_table[0x200 + AX1L];
- AX1H = m_misc_table[0x200 + AX1H];
-
- short b00[20];
- b00[0] = AX1L * AX1H >> 16;
- b00[1] = AX0L * AX1H >> 16;
- b00[2] = AX0H * AX1L >> 16;
- b00[3] = AX0L * AX0H >> 16;
-
- for (int i = 0; i < 4; i++)
- {
- b00[i + 4] = (s16)b00[i] * (s16)PB.raw[0x2a] >> 16;
- }
-
- int prod = ((s16)PB.raw[0x2a] * (s16)PB.raw[0x29] * 2) >> 16;
- for (int i = 0; i < 4; i++)
- {
- b00[i + 8] = (s16)b00[i + 4] * prod;
- }
-
- // ZWW 0d34
-
- int diff = (s16)PB.raw[0x2b] - (s16)PB.raw[0x2a];
- PB.raw[0x2a] = PB.raw[0x2b];
-
- for (int i = 0; i < 4; i++)
- {
- b00[i + 0xc] = (unsigned short)b00[i] * diff >> 16;
- }
-
- for (int i = 0; i < 4; i++)
- {
- b00[i + 0x10] = (s16)b00[i + 0xc] * PB.raw[0x29];
- }
-
- for (int count = 0; count < 8; count++)
- {
- // The 8 buffers to mix to: 0d00, 0d60, 0f40 0ca0 0e80 0ee0 0c00 0c50
- // We just mix to the first two and call it stereo :p
- int value = b00[0x4 + count];
- //int delta = b00[0xC + count] << 11; // Unused?
-
- int ramp = value << 16;
- for (int i = 0; i < _Size; i++)
- {
- int unmixed_audio = m_voice_buffer[i];
- switch (count)
- {
- case 0: _LeftBuffer[i] += (u64)unmixed_audio * ramp >> 29; break;
- case 1: _RightBuffer[i] += (u64)unmixed_audio * ramp >> 29; break;
- }
- }
- }
- }
- else
- {
- // ZWW 0355
- if (PB.StopOnSilence)
- {
- int sum = 0;
- int addr = 0x0a;
- for (int i = 0; i < 6; i++)
- {
- u16 value = PB.raw[addr];
- addr--;
- value >>= 1;
- PB.raw[addr] = value;
- sum += value;
- addr += 5;
- }
-
- if (sum == 0)
- {
- PB.KeyOff = 1;
- }
- }
-
- // Seems there are 6 temporary output buffers.
- for (int count = 0; count < 6; count++)
- {
- int addr = 0x08;
-
- // we'll have to keep a map of buffers I guess...
- u16 dest_buffer_address = PB.raw[addr++];
-
- bool mix = dest_buffer_address ? true : false;
-
- u16 vol2 = PB.raw[addr++];
- u16 vol1 = PB.raw[addr++];
-
- int delta = (vol2 - vol1) << 11;
-
- addr--;
-
- u32 ramp = vol1 << 16;
- if (mix)
- {
- // 0ca9_RampedMultiplyAddBuffer
- for (int i = 0; i < _Size; i++)
- {
- int value = m_voice_buffer[i];
-
- // TODO - add to buffer specified by dest_buffer_address
- switch (count)
- {
- // These really should be 32.
- case 0: _LeftBuffer[i] += (u64)value * ramp >> 29; break;
- case 1: _RightBuffer[i] += (u64)value * ramp >> 29; break;
- }
-
- if (((i & 1) == 0) && i < 64)
- {
- ramp += delta;
- }
- }
- if (_Size < 32)
- {
- ramp += delta * (_Size - 32);
- }
- }
- // Update the PB with the volume actually reached.
- PB.raw[addr++] = ramp >> 16;
-
- addr++;
- }
- }
- // 03b2, this is the reason of using PB.NeedsReset. Seems to be necessary for SMG, and maybe other games.
- if (PB.IsBlank == 0)
- {
- PB.NeedsReset = 0;
- }
-}
-
-void ZeldaUCode::MixAudio()
-{
- const int BufferSamples = 5 * 16;
-
- // Final mix buffers
- memset(m_left_buffer, 0, BufferSamples * sizeof(s32));
- memset(m_right_buffer, 0, BufferSamples * sizeof(s32));
-
- // For each PB...
- for (u32 i = 0; i < m_num_voices; i++)
- {
- if (!IsLightVersion())
- {
- u32 flags = m_sync_flags[(i >> 4) & 0xF];
- if (!(flags & 1 << (15 - (i & 0xF))))
- continue;
- }
-
- ZeldaVoicePB pb;
- ReadVoicePB(m_voice_pbs_addr + (i * 0x180), pb);
-
- if (pb.Status == 0)
- continue;
- if (pb.KeyOff != 0)
- continue;
-
- RenderAddVoice(pb, m_left_buffer, m_right_buffer, BufferSamples);
- WritebackVoicePB(m_voice_pbs_addr + (i * 0x180), pb);
- }
-
- // Post processing, final conversion.
- s16* left_buffer = (s16*)HLEMemory_Get_Pointer(m_left_buffers_addr);
- s16* right_buffer = (s16*)HLEMemory_Get_Pointer(m_right_buffers_addr);
- left_buffer += m_current_buffer * BufferSamples;
- right_buffer += m_current_buffer * BufferSamples;
- for (int i = 0; i < BufferSamples; i++)
- {
- s32 left = m_left_buffer[i];
- s32 right = m_right_buffer[i];
-
- MathUtil::Clamp(&left, -32768, 32767);
- left_buffer[i] = Common::swap16((short)left);
-
- MathUtil::Clamp(&right, -32768, 32767);
- right_buffer[i] = Common::swap16((short)right);
- }
-}
diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp
index 1ace08f922..d60c6aabac 100644
--- a/Source/Core/Core/State.cpp
+++ b/Source/Core/Core/State.cpp
@@ -66,7 +66,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
-static const u32 STATE_VERSION = 44; // Last changed in PR 2464
+static const u32 STATE_VERSION = 45; // Last changed in PR 2846
// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,