Merge pull request #2846 from delroth/new-zelda-hle
Reimplement Zelda HLE
This commit is contained in:
commit
dae6fdc211
|
@ -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
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -108,9 +108,6 @@
|
|||
<ClCompile Include="HW\DSPHLE\UCodes\INIT.cpp" />
|
||||
<ClCompile Include="HW\DSPHLE\UCodes\ROM.cpp" />
|
||||
<ClCompile Include="HW\DSPHLE\UCodes\Zelda.cpp" />
|
||||
<ClCompile Include="HW\DSPHLE\UCodes\ZeldaADPCM.cpp" />
|
||||
<ClCompile Include="HW\DSPHLE\UCodes\ZeldaSynth.cpp" />
|
||||
<ClCompile Include="HW\DSPHLE\UCodes\ZeldaVoice.cpp" />
|
||||
<ClCompile Include="HW\DSPLLE\DSPDebugInterface.cpp" />
|
||||
<ClCompile Include="HW\DSPLLE\DSPHost.cpp" />
|
||||
<ClCompile Include="HW\DSPLLE\DSPLLE.cpp" />
|
||||
|
|
|
@ -336,15 +336,6 @@
|
|||
<ClCompile Include="HW\DSPHLE\UCodes\Zelda.cpp">
|
||||
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HW\DSPHLE\UCodes\ZeldaADPCM.cpp">
|
||||
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HW\DSPHLE\UCodes\ZeldaSynth.cpp">
|
||||
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HW\DSPHLE\UCodes\ZeldaVoice.cpp">
|
||||
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HW\DSPHLE\UCodes\UCodes.cpp">
|
||||
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
@ -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<u32> temp;
|
||||
std::queue<std::pair<u32, bool>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
#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<u32> m_Mails;
|
||||
std::queue<std::pair<u32, bool>> m_Mails;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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<s16, 0x80>&& sine_table) { m_sine_table = sine_table; }
|
||||
void SetConstPatterns(std::array<s16, 0x100>&& patterns) { m_const_patterns = patterns; }
|
||||
void SetResamplingCoeffs(std::array<s16, 0x100>&& coeffs) { m_resampling_coeffs = coeffs; }
|
||||
void SetAfcCoeffs(std::array<s16, 0x20>&& 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 <size_t N, size_t B>
|
||||
void ApplyVolumeInPlace(std::array<s16, N>* 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 <size_t N>
|
||||
void ApplyVolumeInPlace_1_15(std::array<s16, N>* 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<N, 1>(buf, vol);
|
||||
}
|
||||
template <size_t N>
|
||||
void ApplyVolumeInPlace_4_12(std::array<s16, N>* buf, u16 vol)
|
||||
{
|
||||
ApplyVolumeInPlace<N, 4>(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 <size_t N>
|
||||
s32 AddBuffersWithVolumeRamp(std::array<s16, N>* dst,
|
||||
const std::array<s16, N>& 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<s16, 0x50> 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<s16, 0x80> m_sine_table{};
|
||||
|
||||
// Const patterns used for some voice samples source. 4 x 0x40 samples.
|
||||
std::array<s16, 0x100> 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<s16, 0x100> 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 <typename T> 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<s16, 0x20> 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<u16, 4> m_reverb_pb_frames_count{};
|
||||
std::array<s16, 8> m_buf_unk0_reverb_last8{};
|
||||
std::array<s16, 8> m_buf_unk1_reverb_last8{};
|
||||
std::array<s16, 8> m_buf_front_left_reverb_last8{};
|
||||
std::array<s16, 8> 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<u16, 256> 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<u32, 64> 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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
// Copyright 2008 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
@ -1,790 +0,0 @@
|
|||
// Copyright 2009 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#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 <typename T>
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue