48 Commits

Author SHA1 Message Date
Pierre Bourdon 8bcd94d8e9 Zelda HLE: Add support for the ZTP Wii UCode. 2014-12-27 20:33:34 +01:00
Pierre Bourdon c069158115 Zelda HLE: Update the CRC to games mapping (documentation only). 2014-12-27 14:41:10 +01:00
Pierre Bourdon 28c766b1b8 Zelda HLE: Support the per-frame sync protocol used by SMS.
Didn't test if SMS sounds right (travelling with no headphones \o/) but the
waveform looks ok and the mails are flowing as expected.
2014-12-27 12:15:27 +01:00
Pierre Bourdon 1e6fd16def Merge remote-tracking branch 'origin/master' into new-zelda-hle 2014-12-27 11:25:58 +01:00
Pierre Bourdon d7110f1041 Zelda HLE: Make the implementation less strict for recoverable errors.
It kind of sucks that we don't emulate some behaviors properly, but there is
very little ROI for some of these features and I'm not going to spend time
implementing them any time soon. Making the PanicAlerts optional allows for
more testing of the core features on more games while "just" breaking less
important features like reverb.
2014-12-26 19:17:35 +01:00
Pierre Bourdon d3e4b2a3fe Zelda HLE: Only alert for mixing into back buffers for recent UCode versions.
On older versions there is no such concept of a "back buffer" since positional
audio is not supported. These buffers are just used for temp mixing, and while
it would be really nice if we could support more of that inter-buffer mixing
(TODO :) ) it does not warrant a PanicAlert at this time.
2014-12-24 04:29:13 +01:00
Pierre Bourdon d471be6ccc Zelda HLE: Properly implement LQ AFC decoding.
Typos and stupid mistakes + untested code = dirty Git history.
2014-12-24 04:26:05 +01:00
Pierre Bourdon 64f5425e80 Zelda HLE: Implement LQ AFC decoding (samples source 0005). 2014-12-24 03:29:26 +01:00
Pierre Bourdon c51f65c499 Zelda HLE: Implement sample source 3.
"Square" wave at a 0.25 duty cycle (25% up 75% down).
2014-12-24 00:55:56 +01:00
Pierre Bourdon 9063dbab73 Zelda HLE: Add basic support for Luigi's Mansion.
Fails ingame because it mixes to some buffers that are considered the back
buffers for Dolby games. That check might need to be less restrictive for games
that don't use Dolby mixing.
2014-12-23 21:39:57 +01:00
Pierre Bourdon ec7efc52ef Zelda HLE: Log the UCode version being used (CRC/flags).
Also tidy up the versions list a bit.
2014-12-23 21:29:18 +01:00
Pierre Bourdon 90057cdaaa Zelda HLE: Add support for the Pikmin 1 NTSC version of the UCode. 2014-12-23 21:12:35 +01:00
Pierre Bourdon 2ef1642a9e Zelda HLE: Add support for the Zelda: FSA UCode. 2014-12-23 19:39:10 +01:00
Pierre Bourdon 44f88cf3d1 Zelda HLE: NTSC IPL provides the volume stepping explicitly.
Support that through a new behavior flag. Now the only remaining known bug in
the NTSC IPL is the reverb not fading out.
2014-12-23 19:14:29 +01:00
Pierre Bourdon 77c8e53c19 Merge remote-tracking branch 'upstream/master' into new-zelda-hle 2014-12-23 18:41:32 +01:00
Pierre Bourdon d1c272c0e7 Revert "Revert "Removed the DSP LLE on separate thread option.""
This reverts commit 7d22e829a7.

This wasn't supposed to be pushed as part of this branch.
2014-12-23 18:40:23 +01:00
Pierre Bourdon e2021803e1 Zelda HLE: Handle the smaller VPBs used by the NTSC IPL.
Damn you, Nintendo.
2014-12-23 18:40:23 +01:00
Pierre Bourdon 6f2c205492 Zelda HLE: Support both NTSC and PAL IPL.
Add a flag for UCodes that only have four non-Dolby mixing destinations
(instead of the standard six destinations).

NTSC IPL is still hopelessly broken.
2014-12-22 06:20:48 +01:00
Pierre Bourdon 4cab785575 Zelda HLE: Add a missing mixing buffer used by GC IPL.
Not completely sure what it's used for yet, but TWW has references to it so
I'll get to it at some point.
2014-12-22 05:50:34 +01:00
Pierre Bourdon 0e9b1d9616 Zelda HLE: Fix reverb in the GC IPL.
Adds a missing destination buffer, and support post-filtering.
2014-12-22 05:44:04 +01:00
Pierre Bourdon 1703c772b9 Zelda HLE: Add support for the light protocol.
Used by a few titles (Luigi's Mansion, Animal Crossing) as well as the GameCube
IPL/BIOS.

Note that the IPL does not work yet because it mixes to unknown buffers.
2014-12-22 05:44:04 +01:00
Pierre Bourdon 2faab4b66b Zelda HLE: Introduce behavior flags to handle UCode version differences.
MAKE_DOLBY_LOUDER solves some of the volume issues that were happening in Zelda
Twilight Princess and SMG1.
2014-12-22 02:22:26 +01:00
Pierre Bourdon 7d22e829a7 Revert "Removed the DSP LLE on separate thread option."
This reverts commit 2140ac15e4.

Conflicts:
	Source/Core/Core/HW/DSPLLE/DSPLLE.cpp
2014-12-22 01:27:20 +01:00
Pierre Bourdon b96e069eca Zelda HLE: Fix and genericize PCM ARAM loading functions.
Now also handles PCM16, and works for longer sounds. SMG1 main menu outputs
some sensible audio now, though volume seems slightly off.
2014-12-22 01:26:55 +01:00
Pierre Bourdon 3a950271a3 Zelda HLE: Add initial support for Wii DAC and SMG1.
SMG1 boots but quickly PanicAlerts due to an unimplemented sample source.
2014-12-21 18:45:34 +01:00
Pierre Bourdon b49a6bd782 Zelda HLE: Add save state support.
Also fix an inconsistency in the spelling of "coeffs".
2014-12-21 18:11:33 +01:00
Pierre Bourdon fdfc1d1bb8 Zelda HLE: Implement sample sources 4, 7, 11 and 12.
Just 4 different versions of the same sample source, using a different constant
pattern uploaded to the DSP during command 01.
2014-12-21 17:09:54 +01:00
Pierre Bourdon 39a877deeb Merge remote-tracking branch 'origin/master' into new-zelda-hle 2014-12-21 06:23:06 +01:00
Pierre Bourdon 11de407c62 Zelda HLE: Initial support for Zelda Twilight Princess (GC)
Very close to the TWW version of the UCode, haven't determined any differences
yet (but I'm sure that will come soon). Works well enough to reach ingame
without any errors other than a few volume issues.
2014-12-21 06:21:59 +01:00
Pierre Bourdon 577e36ef1b Zelda HLE: Skip command words that are not commands.
Zelda Twilight Princess (GC) seems to push null words into the command buffer.
The command handler in the UCode ignores initial command mails that do not have
the MSB set.
2014-12-21 06:20:28 +01:00
Pierre Bourdon 92a46f560f Zelda HLE: Add reverb emulation. 2014-12-21 02:19:34 +01:00
Pierre Bourdon 672aa721ed Zelda HLE: Implement the PCM8 sample source.
Reorder some things in the source code to match the sample source definition
order. Add and remove some TODOs.
2014-12-20 14:59:21 +01:00
Pierre Bourdon 162c2bd254 Merge remote-tracking branch 'upstream/master' into new-zelda-hle 2014-12-20 06:31:18 +01:00
Pierre Bourdon ba0a80d81c Zelda HLE: Value-initialize the std::arrays.
I was under the wrong impression that std::array's default constructor
performed value initialization. Turns out it does not, so an array of POD will
not be initialized.
2014-12-20 04:24:43 +01:00
Pierre Bourdon 01dff0a346 Zelda HLE: Implement saw wave generation (sample source 0001).
Fixes issue 7961.
2014-12-20 04:11:10 +01:00
Pierre Bourdon 402d4146cf Zelda HLE: Properly implement the square wave generation.
Zelda TWW magic meter works properly now.
2014-12-20 03:42:40 +01:00
Pierre Bourdon 9f2442726b Zelda HLE: Convert some ERROR_LOG to PanicAlert.
While these are not really unrecoverable errors, while Zelda HLE is in a
testing / development phase it is useful to notice these "unexpected" cases (or
expected without known ways to reproduce) by making them as hard as possible to
ignore.
2014-12-20 03:41:23 +01:00
Pierre Bourdon 8b48d65382 Zelda HLE: Implement square wave sample source.
Also fix Windows build.
2014-12-13 17:15:31 +01:00
Pierre Bourdon b4a741e5f1 Zelda HLE: Implement HQ AFC samples decoding. 2014-12-13 16:56:50 +01:00
Pierre Bourdon d9836c8dec ZeldaHLE: fix windows build again 2014-11-30 02:23:55 +01:00
Pierre Bourdon ce5a6a4538 ZeldaHLE: fix windows build 2014-11-30 02:22:28 +01:00
Pierre Bourdon d12a86040e ZeldaHLE: Implement more infrastructure and one sample source.
Now renders some of the cutscene audio in Zelda TWW. Most of the work around
sample sources is in a good enough state, even though it is still missing
features like Dolby mixing, IIR, etc.
2014-11-30 02:18:01 +01:00
Pierre Bourdon f87dea4c54 Zelda HLE: Rewrite the control flow.
Now accurate control flow for DAC. Voice rendering is not implemented yet, but
framing seems correct. Not expected to work for anything than DAC UCode games.
2014-11-20 05:11:47 +01:00
Pierre Bourdon 0db819d887 INIT UCode: reduce DSP initialization time
Push the mail at UCode boot time, not after the first update. This avoids a lot
of crazy busy-looping on the CPU side while waiting for the DSP to be
initialized.
2014-11-20 05:10:50 +01:00
Pierre Bourdon 75302f4807 DSPHLE MailHandler: Synchronize reads and interrupts
This change is meant to solve the following problem: how to translate the
following snippet to DSPHLE:
    SendInterruptAndWaitRead(MAIL_A);
    SendAndWaitRead(MAIL_B);

    SendInterruptAndWaitRead(MAIL_C);

This should cause the following actions on the CPU side:
    ---> Woken up by interrupt
    Reads MAIL_A
    Reads MAIL_B
    <--- Exits interrupt handler

    ---> Woken up by interrupt
    Reads MAIL_C
    <---

But with the current DSPHLE mail support, the following would happen because
the "AndWaitRead" part is not supported:
    ---> Woken up by interrupt
    Reads MAIL_A
    Reads MAIL_B
    <--- Exits interrupt handler

    [Never gets the second interrupt since it was triggered at the same time as
    the first one! Misses MAIL_C.]

This changes fixes the issue by storing two values in the mail queue on the DSP
side: the value of the mail itself, and whether a read of that mail should
trigger a DSP interrupt. If nothing is in the queue yet and an interrupt is
requested, just trigger the interrupt. In the present example, the queue will
look like this:
    Mail value       Interrupt requested
     MAIL_A                  No           <-- Interrupt was triggered when
                                              pushing the mail to the queue.
     MAIL_B                  Yes
     MAIL_C                  No

When the CPU will read MAIL_B, this will cause MailHandler to trigger the
interrupt, which will be handled by the CPU when coming back from the exception
handler. MAIL_C is then successfully read.
2014-11-20 05:02:09 +01:00
Pierre Bourdon af9f8dde1f ZeldaHLE: Rip out more code, only keep normal version support and one CRC 2014-11-16 00:10:56 +01:00
Pierre Bourdon 6752f1e654 ZeldaHLE: Rip out the whole voice processing code. Still boots games, but no sound at all. 2014-11-16 00:09:34 +01:00
Pierre Bourdon a2f4f9833b DSPHLE: Add a config parameter to dump UCode to disk 2014-11-16 00:08:07 +01:00
15 changed files with 1961 additions and 1884 deletions
-3
View File
@@ -75,9 +75,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
+2
View File
@@ -360,6 +360,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);
@@ -594,6 +595,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__
+1
View File
@@ -102,6 +102,7 @@ struct SConfig : NonCopyable
bool m_DSPEnableJIT;
bool m_DSPCaptureLog;
bool m_DumpAudio;
bool m_DumpUCode;
int m_Volume;
std::string sBackend;
-3
View File
@@ -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" />
-9
View File
@@ -331,15 +331,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>
+35 -11
View File
@@ -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.size() == 0)
{
DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
}
else
{
m_Mails.front().second = true;
}
}
m_Mails.push(std::make_pair(_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.push(std::make_pair(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.push(std::make_pair(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.push(std::make_pair(value, interrupt));
}
}
}
+4 -15
View File
@@ -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()
{
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;
};
+1 -5
View File
@@ -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()
+10 -9
View File
@@ -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);
+20 -29
View File
@@ -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,16 @@ 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: // Pimin 1 GC - US
case 0x42f64ac4: // Luigi's Mansion - US
case 0x56d36052: // Super Mario Sunshine - US
case 0x6c3f6f94: // Zelda TP Wii - US
return new ZeldaUCode(dsphle, crc);
case 0x2ea36ce6: // Some Wii demos
@@ -140,17 +130,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
+256 -252
View File
@@ -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 2013 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 2013 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 2013 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);
}
}