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,