diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index 3182d248f8..734d966781 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -74,7 +74,6 @@ set(SRCS Src/ActionReplay.cpp
Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp
Src/HW/DSPHLE/UCodes/UCode_CARD.cpp
Src/HW/DSPHLE/UCodes/UCode_InitAudioSystem.cpp
- Src/HW/DSPHLE/UCodes/UCode_NewAX.cpp
Src/HW/DSPHLE/UCodes/UCode_ROM.cpp
Src/HW/DSPHLE/UCodes/UCodes.cpp
Src/HW/DSPHLE/UCodes/UCode_GBA.cpp
diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj
index caab0a1e6d..d83fa7fcc7 100644
--- a/Source/Core/Core/Core.vcxproj
+++ b/Source/Core/Core/Core.vcxproj
@@ -265,7 +265,6 @@
-
@@ -467,8 +466,6 @@
-
-
diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters
index e0df3bd31f..8e2592450c 100644
--- a/Source/Core/Core/Core.vcxproj.filters
+++ b/Source/Core/Core/Core.vcxproj.filters
@@ -206,9 +206,6 @@
HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
-
- HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
-
HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
@@ -751,12 +748,6 @@
HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
-
- HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
-
-
- HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
-
HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.cpp b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.cpp
index d990d282d7..8c2e05dce4 100644
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.cpp
+++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.cpp
@@ -12,480 +12,488 @@
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
-// Official SVN repository and contact information can be found at
+// Official Git repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
-#include "FileUtil.h" // For IsDirectory()
-#include "StringUtil.h" // For StringFromFormat()
-#include
-
-#include "Mixer.h"
-#include "../MailHandler.h"
-#include "../../DSP.h"
-#include "UCodes.h"
-#include "UCode_AXStructs.h"
#include "UCode_AX.h"
#include "UCode_AX_Voice.h"
+#include "../../DSP.h"
-CUCode_AX::CUCode_AX(DSPHLE *dsp_hle, u32 l_CRC)
- : IUCode(dsp_hle, l_CRC)
- , m_addressPBs(0xFFFFFFFF)
+CUCode_AX::CUCode_AX(DSPHLE* dsp_hle, u32 crc)
+ : IUCode(dsp_hle, crc)
+ , m_cmdlist_size(0)
+ , m_axthread(&SpawnAXThread, this)
{
- // we got loaded
+ WARN_LOG(DSPHLE, "Instantiating CUCode_AX: crc=%08x", crc);
m_rMailHandler.PushMail(DSP_INIT);
-
- templbuffer = new int[1024 * 1024];
- temprbuffer = new int[1024 * 1024];
+ DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
}
CUCode_AX::~CUCode_AX()
{
+ m_cmdlist_size = (u16)-1; // Special value to signal end
+ NotifyAXThread();
+ m_axthread.join();
+
m_rMailHandler.Clear();
- delete [] templbuffer;
- delete [] temprbuffer;
}
-// Needs A LOT of love!
-static void ProcessUpdates(AXPB &PB)
+void CUCode_AX::SpawnAXThread(CUCode_AX* self)
{
- // Make the updates we are told to do. When there are multiple updates for a block they
- // are placed in memory directly following updaddr. They are mostly for initial time
- // delays, sometimes for the FIR filter or channel volumes. We do all of them at once here.
- // If we get both an on and an off update we chose on. Perhaps that makes the RE1 music
- // work better.
- int numupd = PB.updates.num_updates[0]
- + PB.updates.num_updates[1]
- + PB.updates.num_updates[2]
- + PB.updates.num_updates[3]
- + PB.updates.num_updates[4];
- if (numupd > 64) numupd = 64; // prevent crazy values TODO: LOL WHAT
- const u32 updaddr = (u32)(PB.updates.data_hi << 16) | PB.updates.data_lo;
- int on = 0, off = 0;
- for (int j = 0; j < numupd; j++)
+ self->AXThread();
+}
+
+void CUCode_AX::AXThread()
+{
+ while (true)
{
- const u16 updpar = HLEMemory_Read_U16(updaddr + j*4);
- const u16 upddata = HLEMemory_Read_U16(updaddr + j*4 + 2);
- // some safety checks, I hope it's enough
- if (updaddr > 0x80000000 && updaddr < 0x817fffff
- && updpar < 63 && updpar > 3 // updpar > 3 because we don't want to change
- // 0-3, those are important
- //&& (upd0 || upd1 || upd2 || upd3 || upd4) // We should use these in some way to I think
- // but I don't know how or when
- )
{
- ((u16*)&PB)[updpar] = upddata; // WTF ABOUNDS!
+ std::unique_lock lk(m_cmdlist_mutex);
+ while (m_cmdlist_size == 0)
+ m_cmdlist_cv.wait(lk);
}
- if (updpar == 7 && upddata != 0) on++;
- if (updpar == 7 && upddata == 0) off++;
- }
- // hack: if we get both an on and an off select on rather than off
- if (on > 0 && off > 0) PB.running = 1;
-}
-static void VoiceHacks(AXPB &pb)
-{
- // get necessary values
- const u32 sampleEnd = (pb.audio_addr.end_addr_hi << 16) | pb.audio_addr.end_addr_lo;
- const u32 loopPos = (pb.audio_addr.loop_addr_hi << 16) | pb.audio_addr.loop_addr_lo;
- // const u32 updaddr = (u32)(pb.updates.data_hi << 16) | pb.updates.data_lo;
- // const u16 updpar = HLEMemory_Read_U16(updaddr);
- // const u16 upddata = HLEMemory_Read_U16(updaddr + 2);
+ if (m_cmdlist_size == (u16)-1) // End of thread signal
+ break;
- // =======================================================================================
- /* Fix problems introduced with the SSBM fix. Sometimes when a music stream ended sampleEnd
- would end up outside of bounds while the block was still playing resulting in noise
- a strange noise. This should take care of that.
- */
- if ((sampleEnd > (0x017fffff * 2) || loopPos > (0x017fffff * 2))) // ARAM bounds in nibbles
- {
- pb.running = 0;
+ m_processing.lock();
+ HandleCommandList();
+ m_cmdlist_size = 0;
- // also reset all values if it makes any difference
- pb.audio_addr.cur_addr_hi = 0; pb.audio_addr.cur_addr_lo = 0;
- pb.audio_addr.end_addr_hi = 0; pb.audio_addr.end_addr_lo = 0;
- pb.audio_addr.loop_addr_hi = 0; pb.audio_addr.loop_addr_lo = 0;
-
- pb.src.cur_addr_frac = 0; pb.src.ratio_hi = 0; pb.src.ratio_lo = 0;
- pb.adpcm.pred_scale = 0; pb.adpcm.yn1 = 0; pb.adpcm.yn2 = 0;
-
- pb.audio_addr.looping = 0;
- pb.adpcm_loop_info.pred_scale = 0;
- pb.adpcm_loop_info.yn1 = 0; pb.adpcm_loop_info.yn2 = 0;
- }
-
- /*
- // the fact that no settings are reset (except running) after a SSBM type music stream or another
- looping block (for example in Battle Stadium DON) has ended could cause loud garbled sound to be
- played from one or more blocks. Perhaps it was in conjunction with the old sequenced music fix below,
- I'm not sure. This was an attempt to prevent that anyway by resetting all. But I'm not sure if this
- is needed anymore. Please try to play SSBM without it and see if it works anyway.
- */
- if (
- // detect blocks that have recently been running that we should reset
- pb.running == 0 && pb.audio_addr.looping == 1
- //pb.running == 0 && pb.adpcm_loop_info.pred_scale
-
- // this prevents us from ruining sequenced music blocks, may not be needed
- /*
- && !(pb.updates.num_updates[0] || pb.updates.num_updates[1] || pb.updates.num_updates[2]
- || pb.updates.num_updates[3] || pb.updates.num_updates[4])
- */
- //&& !(updpar || upddata)
-
- && pb.mixer_control == 0 // only use this in SSBM
- )
- {
- // reset the detection values
- pb.audio_addr.looping = 0;
- pb.adpcm_loop_info.pred_scale = 0;
- pb.adpcm_loop_info.yn1 = 0; pb.adpcm_loop_info.yn2 = 0;
-
- //pb.audio_addr.cur_addr_hi = 0; pb.audio_addr.cur_addr_lo = 0;
- //pb.audio_addr.end_addr_hi = 0; pb.audio_addr.end_addr_lo = 0;
- //pb.audio_addr.loop_addr_hi = 0; pb.audio_addr.loop_addr_lo = 0;
-
- //pb.src.cur_addr_frac = 0; PBs[i].src.ratio_hi = 0; PBs[i].src.ratio_lo = 0;
- //pb.adpcm.pred_scale = 0; pb.adpcm.yn1 = 0; pb.adpcm.yn2 = 0;
+ // Signal end of processing
+ m_rMailHandler.PushMail(DSP_YIELD);
+ DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
+ m_processing.unlock();
}
}
-void CUCode_AX::MixAdd(short* _pBuffer, int _iSize)
+void CUCode_AX::NotifyAXThread()
{
- if (_iSize > 1024 * 1024)
- _iSize = 1024 * 1024;
+ std::unique_lock lk(m_cmdlist_mutex);
+ m_cmdlist_cv.notify_one();
+}
- memset(templbuffer, 0, _iSize * sizeof(int));
- memset(temprbuffer, 0, _iSize * sizeof(int));
+void CUCode_AX::HandleCommandList()
+{
+ // Temp variables for addresses computation
+ u16 addr_hi, addr_lo;
+ u16 addr2_hi, addr2_lo;
+ u16 size;
- AXPB PB;
- AXBuffers buffers = {{
- templbuffer,
- temprbuffer,
- NULL
- }};
+ u32 pb_addr = 0;
- for (int x = 0; x < numPBaddr; x++)
+// WARN_LOG(DSPHLE, "Command list:");
+// for (u32 i = 0; m_cmdlist[i] != CMD_END; ++i)
+// WARN_LOG(DSPHLE, "%04x", m_cmdlist[i]);
+// WARN_LOG(DSPHLE, "-------------");
+
+ u32 curr_idx = 0;
+ bool end = false;
+ while (!end)
{
- //u32 blockAddr = m_addressPBs;
- u32 blockAddr = PBaddr[x];
+ u16 cmd = m_cmdlist[curr_idx++];
- if (!blockAddr)
- return;
-
- for (int i = 0; i < NUMBER_OF_PBS; i++)
+ switch (cmd)
{
- if (!ReadPB(blockAddr, PB))
+ // Some of these commands are unknown, or unused in this AX HLE.
+ // We still need to skip their arguments using "curr_idx += N".
+
+ case CMD_SETUP:
+ addr_hi = m_cmdlist[curr_idx++];
+ addr_lo = m_cmdlist[curr_idx++];
+ SetupProcessing(HILO_TO_32(addr));
break;
- if (m_CRC != 0x3389a79e)
- VoiceHacks(PB);
+ case CMD_UNK_01: curr_idx += 5; break;
- MixAddVoice(PB, buffers, _iSize);
-
- if (!WritePB(blockAddr, PB))
+ case CMD_PB_ADDR:
+ addr_hi = m_cmdlist[curr_idx++];
+ addr_lo = m_cmdlist[curr_idx++];
+ pb_addr = HILO_TO_32(addr);
break;
- // next PB, or done
- blockAddr = (PB.next_pb_hi << 16) | PB.next_pb_lo;
- if (!blockAddr)
+ case CMD_PROCESS:
+ ProcessPBList(pb_addr);
+ break;
+
+ case CMD_MIX_AUXA:
+ case CMD_MIX_AUXB:
+ // These two commands are handled almost the same internally.
+ addr_hi = m_cmdlist[curr_idx++];
+ addr_lo = m_cmdlist[curr_idx++];
+ addr2_hi = m_cmdlist[curr_idx++];
+ addr2_lo = m_cmdlist[curr_idx++];
+ MixAUXSamples(cmd == CMD_MIX_AUXA, HILO_TO_32(addr), HILO_TO_32(addr2));
+ break;
+
+ case CMD_UPLOAD_LRS:
+ addr_hi = m_cmdlist[curr_idx++];
+ addr_lo = m_cmdlist[curr_idx++];
+ UploadLRS(HILO_TO_32(addr));
+ break;
+
+ case CMD_SBUFFER_ADDR: curr_idx += 2; break;
+ case CMD_UNK_08: curr_idx += 10; break; // TODO: check
+
+ case CMD_MIX_AUXB_NOWRITE:
+ addr_hi = m_cmdlist[curr_idx++];
+ addr_lo = m_cmdlist[curr_idx++];
+ MixAUXSamples(false, 0, HILO_TO_32(addr));
+ break;
+
+ case CMD_COMPRESSOR_TABLE_ADDR: curr_idx += 2; break;
+ case CMD_UNK_0B: break; // TODO: check other versions
+ case CMD_UNK_0C: break; // TODO: check other versions
+
+ case CMD_MORE:
+ addr_hi = m_cmdlist[curr_idx++];
+ addr_lo = m_cmdlist[curr_idx++];
+ size = m_cmdlist[curr_idx++];
+
+ CopyCmdList(HILO_TO_32(addr), size);
+ curr_idx = 0;
+ break;
+
+ case CMD_OUTPUT:
+ // Skip the first address, it is used for surround audio
+ // output, which we don't support yet.
+ curr_idx += 2;
+
+ addr_hi = m_cmdlist[curr_idx++];
+ addr_lo = m_cmdlist[curr_idx++];
+ OutputSamples(HILO_TO_32(addr));
+ break;
+
+ case CMD_END:
+ end = true;
+ break;
+
+ case CMD_UNK_10: curr_idx += 4; break;
+ case CMD_UNK_11: curr_idx += 2; break;
+ case CMD_UNK_12: curr_idx += 1; break;
+ case CMD_UNK_13: curr_idx += 12; break;
+
+ default:
+ ERROR_LOG(DSPHLE, "Unknown command in AX cmdlist: %04x", cmd);
+ end = true;
break;
}
}
+}
- if (_pBuffer)
+static void ApplyUpdatesForMs(AXPB& pb, int curr_ms)
+{
+ u32 start_idx = 0;
+ for (int i = 0; i < curr_ms; ++i)
+ start_idx += pb.updates.num_updates[i];
+
+ u32 update_addr = HILO_TO_32(pb.updates.data);
+ for (u32 i = start_idx; i < start_idx + pb.updates.num_updates[curr_ms]; ++i)
{
- for (int i = 0; i < _iSize; i++)
- {
- // Clamp into 16-bit. Maybe we should add a volume compressor here.
- int left = templbuffer[i] + _pBuffer[0];
- int right = temprbuffer[i] + _pBuffer[1];
- if (left < -32767) left = -32767;
- if (left > 32767) left = 32767;
- if (right < -32767) right = -32767;
- if (right > 32767) right = 32767;
- *_pBuffer++ = left;
- *_pBuffer++ = right;
- }
+ u16 update_off = HLEMemory_Read_U16(update_addr + 4 * i);
+ u16 update_val = HLEMemory_Read_U16(update_addr + 4 * i + 2);
+
+ ((u16*)&pb)[update_off] = update_val;
}
}
-
-// ------------------------------------------------------------------------------
-// Handle incoming mail
-void CUCode_AX::HandleMail(u32 _uMail)
+AXMixControl CUCode_AX::ConvertMixerControl(u32 mixer_control)
{
- if (m_UploadSetupInProgress)
+ u32 ret = 0;
+
+ // TODO: find other UCode versions with different mixer_control values
+ if (m_CRC == 0x4e8a8b21)
{
- PrepareBootUCode(_uMail);
- return;
+ ret |= MIX_L | MIX_R;
+ if (mixer_control & 0x0001) ret |= MIX_AUXA_L | MIX_AUXA_R;
+ if (mixer_control & 0x0002) ret |= MIX_AUXB_L | MIX_AUXB_R;
+ if (mixer_control & 0x0004)
+ {
+ ret |= MIX_S;
+ if (ret & MIX_AUXA_L) ret |= MIX_AUXA_S;
+ if (ret & MIX_AUXB_L) ret |= MIX_AUXB_S;
+ }
+ if (mixer_control & 0x0008)
+ {
+ ret |= MIX_L_RAMP | MIX_R_RAMP;
+ if (ret & MIX_AUXA_L) ret |= MIX_AUXA_L_RAMP | MIX_AUXA_R_RAMP;
+ if (ret & MIX_AUXB_L) ret |= MIX_AUXB_L_RAMP | MIX_AUXB_R_RAMP;
+ if (ret & MIX_AUXA_S) ret |= MIX_AUXA_S_RAMP;
+ if (ret & MIX_AUXB_S) ret |= MIX_AUXB_S_RAMP;
+ }
}
- else {
- if ((_uMail & 0xFFFF0000) == MAIL_AX_ALIST)
- {
- // We are expected to get a new CmdBlock
- DEBUG_LOG(DSPHLE, "GetNextCmdBlock (%ibytes)", (u16)_uMail);
- }
- else if (_uMail == 0xCDD10000) // Action 0 - AX_ResumeTask();
- {
- m_rMailHandler.PushMail(DSP_RESUME);
- }
- else if (_uMail == 0xCDD10001) // Action 1 - new ucode upload ( GC: BayBlade S.T.B,...)
- {
- DEBUG_LOG(DSPHLE,"DSP IROM - New Ucode!");
- // TODO find a better way to protect from HLEMixer?
- soundStream->GetMixer()->SetHLEReady(false);
- m_UploadSetupInProgress = true;
- }
- else if (_uMail == 0xCDD10002) // Action 2 - IROM_Reset(); ( GC: NFS Carbon, FF Crystal Chronicles,...)
- {
- DEBUG_LOG(DSPHLE,"DSP IROM - Reset!");
- m_DSPHLE->SetUCode(UCODE_ROM);
- return;
- }
- else if (_uMail == 0xCDD10003) // Action 3 - AX_GetNextCmdBlock();
- {
- }
+ else
+ {
+ if (mixer_control & 0x0001) ret |= MIX_L;
+ if (mixer_control & 0x0002) ret |= MIX_R;
+ if (mixer_control & 0x0004) ret |= MIX_S;
+ if (mixer_control & 0x0008) ret |= MIX_L_RAMP | MIX_R_RAMP | MIX_S_RAMP;
+ if (mixer_control & 0x0010) ret |= MIX_AUXA_L;
+ if (mixer_control & 0x0020) ret |= MIX_AUXA_R;
+ if (mixer_control & 0x0040) ret |= MIX_AUXA_L_RAMP | MIX_AUXA_R_RAMP;
+ if (mixer_control & 0x0080) ret |= MIX_AUXA_S;
+ if (mixer_control & 0x0100) ret |= MIX_AUXA_S_RAMP;
+ if (mixer_control & 0x0200) ret |= MIX_AUXB_L;
+ if (mixer_control & 0x0400) ret |= MIX_AUXB_R;
+ if (mixer_control & 0x0800) ret |= MIX_AUXB_L_RAMP | MIX_AUXB_R_RAMP;
+ if (mixer_control & 0x1000) ret |= MIX_AUXB_S;
+ if (mixer_control & 0x2000) ret |= MIX_AUXB_S_RAMP;
+
+ // TODO: 0x4000 is used for Dolby Pro 2 sound mixing
+ }
+
+ return (AXMixControl)ret;
+}
+
+void CUCode_AX::SetupProcessing(u32 init_addr)
+{
+ u16 init_data[0x20];
+
+ for (u32 i = 0; i < 0x20; ++i)
+ init_data[i] = HLEMemory_Read_U16(init_addr + 2 * i);
+
+ // List of all buffers we have to initialize
+ int* buffers[] = {
+ m_samples_left,
+ m_samples_right,
+ m_samples_surround,
+ m_samples_auxA_left,
+ m_samples_auxA_right,
+ m_samples_auxA_surround,
+ m_samples_auxB_left,
+ m_samples_auxB_right,
+ m_samples_auxB_surround
+ };
+
+ u32 init_idx = 0;
+ for (u32 i = 0; i < sizeof (buffers) / sizeof (buffers[0]); ++i)
+ {
+ s32 init_val = (s32)((init_data[init_idx] << 16) | init_data[init_idx + 1]);
+ s16 delta = (s16)init_data[init_idx + 2];
+
+ init_idx += 3;
+
+ if (!init_val)
+ memset(buffers[i], 0, 5 * 32 * sizeof (int));
else
{
- DEBUG_LOG(DSPHLE, " >>>> u32 MAIL : AXTask Mail (%08x)", _uMail);
- AXTask(_uMail);
+ for (u32 j = 0; j < 32 * 5; ++j)
+ {
+ buffers[i][j] = init_val;
+ init_val += delta;
+ }
}
}
}
+void CUCode_AX::ProcessPBList(u32 pb_addr)
+{
+ // Samples per millisecond. In theory DSP sampling rate can be changed from
+ // 32KHz to 48KHz, but AX always process at 32KHz.
+ const u32 spms = 32;
+
+ AXPB pb;
+
+ while (pb_addr)
+ {
+ AXBuffers buffers = {{
+ m_samples_left,
+ m_samples_right,
+ m_samples_surround,
+ m_samples_auxA_left,
+ m_samples_auxA_right,
+ m_samples_auxA_surround,
+ m_samples_auxB_left,
+ m_samples_auxB_right,
+ m_samples_auxB_surround
+ }};
+
+ if (!ReadPB(pb_addr, pb))
+ break;
+
+ for (int curr_ms = 0; curr_ms < 5; ++curr_ms)
+ {
+ ApplyUpdatesForMs(pb, curr_ms);
+
+ Process1ms(pb, buffers, ConvertMixerControl(pb.mixer_control));
+
+ // Forward the buffers
+ for (u32 i = 0; i < sizeof (buffers.ptrs) / sizeof (buffers.ptrs[0]); ++i)
+ buffers.ptrs[i] += spms;
+ }
+
+ WritePB(pb_addr, pb);
+ pb_addr = HILO_TO_32(pb.next_pb);
+ }
+}
+
+void CUCode_AX::MixAUXSamples(bool AUXA, u32 write_addr, u32 read_addr)
+{
+ int buffers[3][5 * 32];
+
+ // First, we need to send the contents of our AUX buffers to the CPU.
+ if (write_addr)
+ {
+ for (u32 i = 0; i < 5 * 32; ++i)
+ {
+ if (AUXA)
+ {
+ buffers[0][i] = Common::swap32(m_samples_auxA_left[i]);
+ buffers[1][i] = Common::swap32(m_samples_auxA_right[i]);
+ buffers[2][i] = Common::swap32(m_samples_auxA_surround[i]);
+ }
+ else
+ {
+ buffers[0][i] = Common::swap32(m_samples_auxB_left[i]);
+ buffers[1][i] = Common::swap32(m_samples_auxB_right[i]);
+ buffers[2][i] = Common::swap32(m_samples_auxB_surround[i]);
+ }
+ }
+ memcpy(HLEMemory_Get_Pointer(write_addr), buffers, sizeof (buffers));
+ }
+
+ // Then, we read the new buffers from the CPU and add to our current
+ // buffers.
+ memcpy(buffers, HLEMemory_Get_Pointer(read_addr), sizeof (buffers));
+ for (u32 i = 0; i < 5 * 32; ++i)
+ {
+ m_samples_left[i] += Common::swap32(buffers[0][i]);
+ m_samples_right[i] += Common::swap32(buffers[1][i]);
+ m_samples_surround[i] += Common::swap32(buffers[2][i]);
+ }
+}
+
+void CUCode_AX::UploadLRS(u32 dst_addr)
+{
+ int buffers[3][5 * 32];
+
+ for (u32 i = 0; i < 5 * 32; ++i)
+ {
+ buffers[0][i] = Common::swap32(m_samples_left[i]);
+ buffers[1][i] = Common::swap32(m_samples_right[i]);
+ buffers[2][i] = Common::swap32(m_samples_surround[i]);
+ }
+ memcpy(HLEMemory_Get_Pointer(dst_addr), buffers, sizeof (buffers));
+}
+
+void CUCode_AX::OutputSamples(u32 out_addr)
+{
+ // 32 samples per ms, 5 ms, 2 channels
+ short buffer[5 * 32 * 2];
+
+ // Clamp internal buffers to 16 bits.
+ for (u32 i = 0; i < 5 * 32; ++i)
+ {
+ int left = m_samples_left[i];
+ int right = m_samples_right[i];
+
+ if (left < -32767) left = -32767;
+ if (left > 32767) left = 32767;
+ if (right < -32767) right = -32767;
+ if (right > 32767) right = 32767;
+
+ m_samples_left[i] = left;
+ m_samples_right[i] = right;
+ }
+
+ for (u32 i = 0; i < 5 * 32; ++i)
+ {
+ buffer[2 * i] = Common::swap16(m_samples_left[i]);
+ buffer[2 * i + 1] = Common::swap16(m_samples_right[i]);
+ }
+
+ memcpy(HLEMemory_Get_Pointer(out_addr), buffer, sizeof (buffer));
+}
+
+void CUCode_AX::HandleMail(u32 mail)
+{
+ // Indicates if the next message is a command list address.
+ static bool next_is_cmdlist = false;
+ static u16 cmdlist_size = 0;
+
+ bool set_next_is_cmdlist = false;
+
+ // Wait for DSP processing to be done before answering any mail. This is
+ // safe to do because it matches what the DSP does on real hardware: there
+ // is no interrupt when a mail from CPU is received.
+ m_processing.lock();
+
+ if (next_is_cmdlist)
+ {
+ CopyCmdList(mail, cmdlist_size);
+ NotifyAXThread();
+ }
+ else if (m_UploadSetupInProgress)
+ {
+ PrepareBootUCode(mail);
+ }
+ else if (mail == MAIL_RESUME)
+ {
+ // Acknowledge the resume request
+ m_rMailHandler.PushMail(DSP_RESUME);
+ DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
+ }
+ else if (mail == MAIL_NEW_UCODE)
+ {
+ soundStream->GetMixer()->SetHLEReady(false);
+ m_UploadSetupInProgress = true;
+ }
+ else if (mail == MAIL_RESET)
+ {
+ m_DSPHLE->SetUCode(UCODE_ROM);
+ }
+ else if (mail == MAIL_CONTINUE)
+ {
+ // We don't have to do anything here - the CPU does not wait for a ACK
+ // and sends a cmdlist mail just after.
+ }
+ else if ((mail & MAIL_CMDLIST_MASK) == MAIL_CMDLIST)
+ {
+ // A command list address is going to be sent next.
+ set_next_is_cmdlist = true;
+ cmdlist_size = (u16)(mail & ~MAIL_CMDLIST_MASK);
+ }
+ else
+ {
+ ERROR_LOG(DSPHLE, "Unknown mail sent to AX::HandleMail: %08x", mail);
+ }
+
+ m_processing.unlock();
+ next_is_cmdlist = set_next_is_cmdlist;
+}
+
+void CUCode_AX::CopyCmdList(u32 addr, u16 size)
+{
+ if (size >= (sizeof (m_cmdlist) / sizeof (u16)))
+ {
+ ERROR_LOG(DSPHLE, "Command list at %08x is too large: size=%d", addr, size);
+ return;
+ }
+
+ for (u32 i = 0; i < size; ++i, addr += 2)
+ m_cmdlist[i] = HLEMemory_Read_U16(addr);
+ m_cmdlist_size = size;
+}
+
+void CUCode_AX::MixAdd(short* out_buffer, int nsamples)
+{
+ // Should never be called: we do not set HLE as ready.
+ // We accurately send samples to RAM instead of directly to the mixer.
+}
-// ------------------------------------------------------------------------------
-// Update with DSP Interrupt
void CUCode_AX::Update(int cycles)
{
+ // Used for UCode switching.
if (NeedsResumeMail())
{
m_rMailHandler.PushMail(DSP_RESUME);
DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
}
- // check if we have to send something
- else if (!m_rMailHandler.IsEmpty())
- {
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
- }
}
-// ============================================
-// AX seems to bootup one task only and waits for resume-callbacks
-// everytime the DSP has "spare time" it sends a resume-mail to the CPU
-// and the __DSPHandler calls a AX-Callback which generates a new AXFrame
-bool CUCode_AX::AXTask(u32& _uMail)
+void CUCode_AX::DoState(PointerWrap& p)
{
- u32 uAddress = _uMail;
- DEBUG_LOG(DSPHLE, "Begin");
- DEBUG_LOG(DSPHLE, "=====================================================================");
- DEBUG_LOG(DSPHLE, "%08x : AXTask - AXCommandList-Addr:", uAddress);
-
- u32 Addr__AXStudio;
- u32 Addr__AXOutSBuffer;
- u32 Addr__AXOutSBuffer_1;
- u32 Addr__AXOutSBuffer_2;
- u32 Addr__A;
- u32 Addr__12;
- u32 Addr__4_1;
- u32 Addr__4_2;
- //u32 Addr__4_3;
- //u32 Addr__4_4;
- u32 Addr__5_1;
- u32 Addr__5_2;
- u32 Addr__6;
- u32 Addr__9;
-
- bool bExecuteList = true;
-
- numPBaddr = 0;
-
- while (bExecuteList)
- {
- static int last_valid_command = 0;
- u16 iCommand = HLEMemory_Read_U16(uAddress);
- uAddress += 2;
-
- switch (iCommand)
- {
- case AXLIST_STUDIOADDR: //00
- Addr__AXStudio = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- DEBUG_LOG(DSPHLE, "%08x : AXLIST studio address: %08x", uAddress, Addr__AXStudio);
- break;
-
- case 0x001: // 2byte x 10
- {
- u32 address = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- u16 param1 = HLEMemory_Read_U16(uAddress);
- uAddress += 2;
- u16 param2 = HLEMemory_Read_U16(uAddress);
- uAddress += 2;
- u16 param3 = HLEMemory_Read_U16(uAddress);
- uAddress += 2;
- DEBUG_LOG(DSPHLE, "%08x : AXLIST 1: %08x, %04x, %04x, %04x", uAddress, address, param1, param2, param3);
- }
- break;
-
- //
- // Somewhere we should be getting a bitmask of AX_SYNC values
- // that tells us what has been updated
- // Dunno if important
- //
- case AXLIST_PBADDR: //02
- {
- PBaddr[numPBaddr] = HLEMemory_Read_U32(uAddress);
- numPBaddr++;
-
- // HACK: process updates right now instead of waiting until
- // Premix is called. Some games using sequenced music (Tales of
- // Symphonia for example) thought PBs were unused because we
- // were too slow to update them and set them as running. This
- // happens because Premix is basically completely desync-ed
- // from the emulation core (it's running in the audio thread).
- // Fixing this would require rewriting most of the AX HLE.
- u32 block_addr = uAddress;
- AXPB pb;
- for (int i = 0; block_addr && i < NUMBER_OF_PBS; i++)
- {
- if (!ReadPB(block_addr, pb))
- break;
- ProcessUpdates(pb);
- WritePB(block_addr, pb);
- block_addr = (pb.next_pb_hi << 16) | pb.next_pb_lo;
- }
-
- m_addressPBs = HLEMemory_Read_U32(uAddress); // left in for now
- uAddress += 4;
- soundStream->GetMixer()->SetHLEReady(true);
- DEBUG_LOG(DSPHLE, "%08x : AXLIST PB address: %08x", uAddress, m_addressPBs);
- }
- break;
-
- case 0x0003:
- DEBUG_LOG(DSPHLE, "%08x : AXLIST command 0x0003 ????", uAddress);
- break;
-
- case 0x0004: // AUX?
- Addr__4_1 = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- Addr__4_2 = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- DEBUG_LOG(DSPHLE, "%08x : AXLIST 4_1 4_2 addresses: %08x %08x", uAddress, Addr__4_1, Addr__4_2);
- break;
-
- case 0x0005:
- Addr__5_1 = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- Addr__5_2 = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- DEBUG_LOG(DSPHLE, "%08x : AXLIST 5_1 5_2 addresses: %08x %08x", uAddress, Addr__5_1, Addr__5_2);
- break;
-
- case 0x0006:
- Addr__6 = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- DEBUG_LOG(DSPHLE, "%08x : AXLIST 6 address: %08x", uAddress, Addr__6);
- break;
-
- case AXLIST_SBUFFER:
- Addr__AXOutSBuffer = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- DEBUG_LOG(DSPHLE, "%08x : AXLIST OutSBuffer address: %08x", uAddress, Addr__AXOutSBuffer);
- break;
-
- case 0x0009:
- Addr__9 = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- DEBUG_LOG(DSPHLE, "%08x : AXLIST 6 address: %08x", uAddress, Addr__9);
- break;
-
- case AXLIST_COMPRESSORTABLE: // 0xa
- Addr__A = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- DEBUG_LOG(DSPHLE, "%08x : AXLIST CompressorTable address: %08x", uAddress, Addr__A);
- break;
-
- case 0x000e:
- Addr__AXOutSBuffer_1 = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
-
- // Addr__AXOutSBuffer_2 is the address in RAM that we are supposed to mix to.
- // Although we don't, currently.
- Addr__AXOutSBuffer_2 = HLEMemory_Read_U32(uAddress);
- uAddress += 4;
- DEBUG_LOG(DSPHLE, "%08x : AXLIST sbuf2 addresses: %08x %08x", uAddress, Addr__AXOutSBuffer_1, Addr__AXOutSBuffer_2);
- break;
-
- case AXLIST_END:
- bExecuteList = false;
- DEBUG_LOG(DSPHLE, "%08x : AXLIST end", uAddress);
- break;
-
- case 0x0010: //Super Monkey Ball 2
- DEBUG_LOG(DSPHLE, "%08x : AXLIST 0x0010", uAddress);
- //should probably read/skip stuff here
- uAddress += 8;
- break;
-
- case 0x0011:
- uAddress += 4;
- break;
-
- case 0x0012:
- Addr__12 = HLEMemory_Read_U16(uAddress);
- uAddress += 2;
- break;
-
- case 0x0013:
- uAddress += 6 * 4; // 6 Addresses.
- break;
-
- default:
- {
- static bool bFirst = true;
- if (bFirst)
- {
- char szTemp[2048];
- sprintf(szTemp, "Unknown AX-Command 0x%x (address: 0x%08x). Last valid: %02x\n",
- iCommand, uAddress - 2, last_valid_command);
- int num = -32;
- while (num < 64+32)
- {
- char szTemp2[128] = "";
- sprintf(szTemp2, "%s0x%04x\n", num == 0 ? ">>" : " ", HLEMemory_Read_U16(uAddress + num));
- strcat(szTemp, szTemp2);
- num += 2;
- }
-
- PanicAlert("%s", szTemp);
- // bFirst = false;
- }
-
- // unknown command so stop the execution of this TaskList
- bExecuteList = false;
- }
- break;
- }
- if (bExecuteList)
- last_valid_command = iCommand;
- }
- DEBUG_LOG(DSPHLE, "AXTask - done, send resume");
- DEBUG_LOG(DSPHLE, "=====================================================================");
- DEBUG_LOG(DSPHLE, "End");
-
- m_rMailHandler.PushMail(DSP_YIELD);
- return true;
-}
-
-void CUCode_AX::DoState(PointerWrap &p)
-{
- std::lock_guard lk(m_csMix);
-
- p.Do(numPBaddr);
- p.Do(m_addressPBs);
- p.Do(PBaddr);
+ std::lock_guard lk(m_processing);
DoStateShared(p);
}
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.h b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.h
index edb52a2801..29509661ed 100644
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.h
+++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.h
@@ -12,52 +12,119 @@
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
-// Official SVN repository and contact information can be found at
+// Official Git repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
-#ifndef _UCODE_AX
-#define _UCODE_AX
+// High-level emulation for the AX Gamecube UCode.
+//
+// TODO:
+// * Depop support
+// * ITD support
+// * Polyphase sample interpolation support (not very useful)
+// * Surround sound mixing
+// * Dolby Pro 2 mixing with recent AX versions
-#include
+#ifndef _UCODE_AX_H
+#define _UCODE_AX_H
+
+#include "UCodes.h"
#include "UCode_AXStructs.h"
+#include "UCode_AX_Voice.h"
-enum
-{
- NUMBER_OF_PBS = 128
-};
-
-class CUCode_AX : public IUCode
+class CUCode_AX : public IUCode
{
public:
- CUCode_AX(DSPHLE *dsp_hle, u32 _CRC);
+ CUCode_AX(DSPHLE* dsp_hle, u32 crc);
virtual ~CUCode_AX();
- void HandleMail(u32 _uMail);
- void MixAdd(short* _pBuffer, int _iSize);
+ void HandleMail(u32 mail);
+ void MixAdd(short* out_buffer, int nsamples);
void Update(int cycles);
- void DoState(PointerWrap &p);
+ void DoState(PointerWrap& p);
- // PBs
- u8 numPBaddr;
- u32 PBaddr[8]; //2 needed for MP2
- u32 m_addressPBs;
+ // Needed because StdThread.h std::thread implem does not support member
+ // pointers.
+ static void SpawnAXThread(CUCode_AX* self);
private:
- enum
+ enum MailType
{
- MAIL_AX_ALIST = 0xBABE0000,
- AXLIST_STUDIOADDR = 0x0000,
- AXLIST_PBADDR = 0x0002,
- AXLIST_SBUFFER = 0x0007,
- AXLIST_COMPRESSORTABLE = 0x000A,
- AXLIST_END = 0x000F
+ MAIL_RESUME = 0xCDD10000,
+ MAIL_NEW_UCODE = 0xCDD10001,
+ MAIL_RESET = 0xCDD10002,
+ MAIL_CONTINUE = 0xCDD10003,
+
+ // CPU sends 0xBABE0000 | cmdlist_size to the DSP
+ MAIL_CMDLIST = 0xBABE0000,
+ MAIL_CMDLIST_MASK = 0xFFFF0000
};
- int *templbuffer;
- int *temprbuffer;
+ enum CmdType
+ {
+ CMD_SETUP = 0x00,
+ CMD_UNK_01 = 0x01,
+ CMD_PB_ADDR = 0x02,
+ CMD_PROCESS = 0x03,
+ CMD_MIX_AUXA = 0x04,
+ CMD_MIX_AUXB = 0x05,
+ CMD_UPLOAD_LRS = 0x06,
+ CMD_SBUFFER_ADDR = 0x07,
+ CMD_UNK_08 = 0x08,
+ CMD_MIX_AUXB_NOWRITE = 0x09,
+ CMD_COMPRESSOR_TABLE_ADDR = 0x0A,
+ CMD_UNK_0B = 0x0B,
+ CMD_UNK_0C = 0x0C,
+ CMD_MORE = 0x0D,
+ CMD_OUTPUT = 0x0E,
+ CMD_END = 0x0F,
+ CMD_UNK_10 = 0x10,
+ CMD_UNK_11 = 0x11,
+ CMD_UNK_12 = 0x12,
+ CMD_UNK_13 = 0x13,
+ };
- // ax task message handler
- bool AXTask(u32& _uMail);
+ // 32 * 5 because 32 samples per millisecond, for 5 milliseconds.
+ int m_samples_left[32 * 5];
+ int m_samples_right[32 * 5];
+ int m_samples_surround[32 * 5];
+ int m_samples_auxA_left[32 * 5];
+ int m_samples_auxA_right[32 * 5];
+ int m_samples_auxA_surround[32 * 5];
+ int m_samples_auxB_left[32 * 5];
+ int m_samples_auxB_right[32 * 5];
+ int m_samples_auxB_surround[32 * 5];
+
+ // Volatile because it's set by HandleMail and accessed in
+ // HandleCommandList, which are running in two different threads.
+ volatile u16 m_cmdlist[512];
+ volatile u32 m_cmdlist_size;
+
+ std::thread m_axthread;
+
+ // Sync objects
+ std::mutex m_processing;
+ std::condition_variable m_cmdlist_cv;
+ std::mutex m_cmdlist_mutex;
+
+ // Copy a command list from memory to our temp buffer
+ void CopyCmdList(u32 addr, u16 size);
+
+ // Convert a mixer_control bitfield to our internal representation for that
+ // value. Required because that bitfield has a different meaning in some
+ // versions of AX.
+ AXMixControl ConvertMixerControl(u32 mixer_control);
+
+ // Send a notification to the AX thread to tell him a new cmdlist addr is
+ // available for processing.
+ void NotifyAXThread();
+
+ void AXThread();
+ void HandleCommandList();
+ void SetupProcessing(u32 studio_addr);
+ void ProcessPBList(u32 pb_addr);
+ void MixAUXSamples(bool AUXA, u32 write_addr, u32 read_addr);
+ void UploadLRS(u32 dst_addr);
+ void OutputSamples(u32 out_addr);
};
-#endif // _UCODE_AX
+#endif // !_UCODE_AX_H
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXStructs.h b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXStructs.h
index c9751afec2..e3c6b7165b 100644
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXStructs.h
+++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXStructs.h
@@ -285,7 +285,8 @@ struct AXPBWii
u16 src_type; // Type of sample rate converter (none, 4-tap, linear)
u16 coef_select; // coef for the 4-tap src
- u32 mixer_control;
+ u16 mixer_control_hi;
+ u16 mixer_control_lo;
u16 running; // 1=RUN 0=STOP
u16 is_stream; // 1 = stream, 0 = one shot
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp
index 62d3bac6e4..73abc392df 100644
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp
+++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp
@@ -22,7 +22,6 @@
#include "UCodes.h"
#include "UCode_AXStructs.h"
-#include "UCode_AX.h" // for some functions in CUCode_AX
#include "UCode_AXWii.h"
#include "UCode_AX_Voice.h"
@@ -112,10 +111,10 @@ void CUCode_AXWii::MixAdd(short* _pBuffer, int _iSize)
if (!ReadPB(blockAddr, PB))
break;
- if (wiisportsHack)
- MixAddVoice(*(AXPBWiiSports*)&PB, buffers, _iSize);
- else
- MixAddVoice(PB, buffers, _iSize);
+// if (wiisportsHack)
+// MixAddVoice(*(AXPBWiiSports*)&PB, buffers, _iSize);
+// else
+// MixAddVoice(PB, buffers, _iSize);
if (!WritePB(blockAddr, PB))
break;
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h
index 8315b702db..20fea41677 100644
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h
+++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h
@@ -12,297 +12,369 @@
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
-// Official SVN repository and contact information can be found at
+// Official Git repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _UCODE_AX_VOICE_H
#define _UCODE_AX_VOICE_H
-#include "UCodes.h"
-#include "UCode_AX_ADPCM.h"
-#include "UCode_AX.h"
-#include "Mixer.h"
-#include "../../AudioInterface.h"
+#include "Common.h"
+#include "UCode_AXStructs.h"
+#include "../../DSP.h"
-// MRAM -> ARAM for GC
-inline bool ReadPB(u32 addr, AXPB &PB)
-{
- const u16* PB_in_mram = (const u16*)Memory::GetPointer(addr);
- if (PB_in_mram == NULL)
- return false;
- u16* PB_in_aram = (u16*)&PB;
-
- for (size_t p = 0; p < (sizeof(AXPB) >> 1); p++)
- {
- PB_in_aram[p] = Common::swap16(PB_in_mram[p]);
- }
-
- return true;
-}
-
-// MRAM -> ARAM for Wii
-inline bool ReadPB(u32 addr, AXPBWii &PB)
-{
- const u16* PB_in_mram = (const u16*)Memory::GetPointer(addr);
- if (PB_in_mram == NULL)
- return false;
- u16* PB_in_aram = (u16*)&PB;
-
- // preswap the mixer_control
- PB.mixer_control = ((u32)PB_in_mram[7] << 16) | ((u32)PB_in_mram[6] >> 16);
-
- for (size_t p = 0; p < (sizeof(AXPBWii) >> 1); p++)
- {
- PB_in_aram[p] = Common::swap16(PB_in_mram[p]);
- }
-
- return true;
-}
-
-// ARAM -> MRAM for GC
-inline bool WritePB(u32 addr, AXPB &PB)
-{
- const u16* PB_in_aram = (const u16*)&PB;
- u16* PB_in_mram = (u16*)Memory::GetPointer(addr);
- if (PB_in_mram == NULL)
- return false;
-
- for (size_t p = 0; p < (sizeof(AXPB) >> 1); p++)
- {
- PB_in_mram[p] = Common::swap16(PB_in_aram[p]);
- }
-
- return true;
-}
-
-// ARAM -> MRAM for Wii
-inline bool WritePB(u32 addr, AXPBWii &PB)
-{
- const u16* PB_in_aram = (const u16*)&PB;
- u16* PB_in_mram = (u16*)Memory::GetPointer(addr);
- if (PB_in_mram == NULL)
- return false;
-
- // preswap the mixer_control
- *(u32*)&PB_in_mram[6] = (PB.mixer_control << 16) | (PB.mixer_control >> 16);
-
- for (size_t p = 0; p < (sizeof(AXPBWii) >> 1); p++)
- {
- PB_in_mram[p] = Common::swap16(PB_in_aram[p]);
- }
-
- return true;
-}
+// Useful macro to convert xxx_hi + xxx_lo to xxx for 32 bits.
+#define HILO_TO_32(name) \
+ ((name##_hi << 16) | name##_lo)
+// Used to pass a large amount of buffers to the mixing function.
union AXBuffers
{
struct
{
int* left;
int* right;
+ int* surround;
+
int* auxA_left;
int* auxA_right;
+ int* auxA_surround;
+
int* auxB_left;
int* auxB_right;
+ int* auxB_surround;
};
- int* ptrs[6];
+ int* ptrs[9];
};
-//////////////////////////////////////////////////////////////////////////
-// TODO: fix handling of gc/wii PB differences
-// TODO: generally fix up the mess - looks crazy and kinda wrong
-template
-inline void MixAddVoice(ParamBlockType &pb, const AXBuffers& buffers,
- int _iSize, bool resample = true)
+// We can't directly use the mixer_control field from the PB because it does
+// not mean the same in all AX versions. The AX UCode converts the
+// mixer_control value to an AXMixControl bitfield.
+enum AXMixControl
{
- if (pb.running)
- {
- float ratioFactor;
- if (resample)
- ratioFactor = (float)AudioInterface::GetAIDSampleRate() / (float)soundStream->GetMixer()->GetSampleRate();
- else
- ratioFactor = 1.0;
+ MIX_L = 0x00001,
+ MIX_L_RAMP = 0x00002,
+ MIX_R = 0x00004,
+ MIX_R_RAMP = 0x00008,
+ MIX_S = 0x00010,
+ MIX_S_RAMP = 0x00020,
- const u32 ratio = (u32)(((pb.src.ratio_hi << 16) + pb.src.ratio_lo) * ratioFactor);
- u32 sampleEnd = (pb.audio_addr.end_addr_hi << 16) | pb.audio_addr.end_addr_lo;
- u32 loopPos = (pb.audio_addr.loop_addr_hi << 16) | pb.audio_addr.loop_addr_lo;
+ MIX_AUXA_L = 0x00040,
+ MIX_AUXA_L_RAMP = 0x00080,
+ MIX_AUXA_R = 0x00100,
+ MIX_AUXA_R_RAMP = 0x00200,
+ MIX_AUXA_S = 0x00400,
+ MIX_AUXA_S_RAMP = 0x00800,
- u32 samplePos = (pb.audio_addr.cur_addr_hi << 16) | pb.audio_addr.cur_addr_lo;
- u32 frac = pb.src.cur_addr_frac;
+ MIX_AUXB_L = 0x01000,
+ MIX_AUXB_L_RAMP = 0x02000,
+ MIX_AUXB_R = 0x04000,
+ MIX_AUXB_R_RAMP = 0x08000,
+ MIX_AUXB_S = 0x10000,
+ MIX_AUXB_S_RAMP = 0x20000
+};
- // =======================================================================================
- // Handle No-SRC streams - No src streams have pb.src_type == 2 and have pb.src.ratio_hi = 0
- // and pb.src.ratio_lo = 0. We handle that by setting the sampling ratio integer to 1. This
- // makes samplePos update in the correct way. I'm unsure how we are actually supposed to
- // detect that this setting. Updates did not fix this automatically.
- // ---------------------------------------------------------------------------------------
- // Stream settings
- // src_type = 2 (most other games have src_type = 0)
- // Affected games:
- // Baten Kaitos - Eternal Wings (2003)
- // Baten Kaitos - Origins (2006)?
- // Soul Calibur 2: The movie music use src_type 2 but it needs no adjustment, perhaps
- // the sound format plays in to, Baten use ADPCM, SC2 use PCM16
- //if (pb.src_type == 2 && (pb.src.ratio_hi == 0 && pb.src.ratio_lo == 0))
- if (pb.running && (pb.src.ratio_hi == 0 && pb.src.ratio_lo == 0))
- {
- pb.src.ratio_hi = 1;
- }
+// Read a PB from MRAM/ARAM
+template
+bool ReadPB(u32 addr, PBType& pb)
+{
+ u16* dst = (u16*)&pb;
+ const u16* src = (const u16*)Memory::GetPointer(addr);
+ if (!src)
+ return false;
- // =======================================================================================
- // Games that use looping to play non-looping music streams - SSBM has info in all
- // pb.adpcm_loop_info parameters but has pb.audio_addr.looping = 0. If we treat these streams
- // like any other looping streams the music works. I'm unsure how we are actually supposed to
- // detect that these kinds of blocks should be looping. It seems like pb.mixer_control == 0 may
- // identify these types of blocks. Updates did not write any looping values.
- if (
- (pb.adpcm_loop_info.pred_scale || pb.adpcm_loop_info.yn1 || pb.adpcm_loop_info.yn2)
- && pb.mixer_control == 0 && pb.adpcm_loop_info.pred_scale <= 0x7F
- )
- {
- pb.audio_addr.looping = 1;
- }
+ for (u32 i = 0; i < sizeof (pb) / sizeof (u16); ++i)
+ dst[i] = Common::swap16(src[i]);
-
-
- // Top Spin 3 Wii
- if (pb.audio_addr.sample_format > 25)
- pb.audio_addr.sample_format = 0;
-
- // =======================================================================================
- // Walk through _iSize. _iSize = numSamples. If the game goes slow _iSize will be higher to
- // compensate for that. _iSize can be as low as 100 or as high as 2000 some cases.
- for (int s = 0; s < _iSize; s++)
- {
- int sample = 0;
- u32 oldFrac = frac;
- frac += ratio;
- u32 newSamplePos = samplePos + (frac >> 16); //whole number of frac
-
- // =======================================================================================
- // Process sample format
- switch (pb.audio_addr.sample_format)
- {
- case AUDIOFORMAT_PCM8:
- pb.adpcm.yn2 = ((s8)DSP::ReadARAM(samplePos)) << 8; //current sample
- pb.adpcm.yn1 = ((s8)DSP::ReadARAM(samplePos + 1)) << 8; //next sample
-
- if (pb.src_type == SRCTYPE_NEAREST)
- sample = pb.adpcm.yn2;
- else // linear interpolation
- sample = (pb.adpcm.yn1 * (u16)oldFrac + pb.adpcm.yn2 * (u16)(0xFFFF - oldFrac) + pb.adpcm.yn2) >> 16;
-
- samplePos = newSamplePos;
- break;
-
- case AUDIOFORMAT_PCM16:
- pb.adpcm.yn2 = (s16)(u16)((DSP::ReadARAM(samplePos * 2) << 8) | (DSP::ReadARAM((samplePos * 2 + 1)))); //current sample
- pb.adpcm.yn1 = (s16)(u16)((DSP::ReadARAM((samplePos + 1) * 2) << 8) | (DSP::ReadARAM(((samplePos + 1) * 2 + 1)))); //next sample
-
- if (pb.src_type == SRCTYPE_NEAREST)
- sample = pb.adpcm.yn2;
- else // linear interpolation
- sample = (pb.adpcm.yn1 * (u16)oldFrac + pb.adpcm.yn2 * (u16)(0xFFFF - oldFrac) + pb.adpcm.yn2) >> 16;
-
- samplePos = newSamplePos;
- break;
-
- case AUDIOFORMAT_ADPCM:
- ADPCM_Step(pb.adpcm, samplePos, newSamplePos, frac);
-
- if (pb.src_type == SRCTYPE_NEAREST)
- sample = pb.adpcm.yn2;
- else // linear interpolation
- sample = (pb.adpcm.yn1 * (u16)frac + pb.adpcm.yn2 * (u16)(0xFFFF - frac) + pb.adpcm.yn2) >> 16; //adpcm moves on frac
-
- break;
-
- default:
- break;
- }
-
- // ===================================================================
- // Overall volume control. In addition to this there is also separate volume settings to
- // different channels (left, right etc).
- frac &= 0xffff;
-
- int vol = pb.vol_env.cur_volume >> 9;
- sample = sample * vol >> 8;
-
- if (pb.mixer_control & 8)
- {
- int x = pb.vol_env.cur_volume;
- x += pb.vol_env.cur_volume_delta; // I'm not sure about this, can anybody find a game
- // that use this? Or how does it work?
- if (x < 0)
- x = 0;
- if (x >= 0x7fff)
- x = 0x7fff;
- pb.vol_env.cur_volume = x; // maybe not per sample?? :P
- }
-
- int leftmix = pb.mixer.left >> 5;
- int rightmix = pb.mixer.right >> 5;
- int auxAleftmix = pb.mixer.auxA_left >> 5;
- int auxArightmix= pb.mixer.auxA_right >> 5;
- int auxBleftmix = pb.mixer.auxB_left >> 5;
- int auxBrightmix= pb.mixer.auxB_right >> 5;
-
- int left = sample * leftmix >> 8;
- int right = sample * rightmix >> 8;
- int auxAleft = sample * auxAleftmix >> 8;
- int auxAright = sample * auxArightmix >> 8;
- int auxBleft = sample * auxBleftmix >> 8;
- int auxBright = sample * auxBrightmix >> 8;
-
- // adpcm has to walk from oldSamplePos to samplePos here
- if ((pb.mixer_control & 1) && buffers.left) buffers.left[s] += left;
- if ((pb.mixer_control & 2) && buffers.right) buffers.right [s] += right;
- if ((pb.mixer_control & 16) && buffers.auxA_left) buffers.auxA_left[s] += auxAleft;
- if ((pb.mixer_control & 32) && buffers.auxA_right) buffers.auxA_right[s] += auxAright;
- if ((pb.mixer_control & 512) && buffers.auxB_left) buffers.auxB_left[s] += auxBleft;
- if ((pb.mixer_control & 1024) && buffers.auxB_right) buffers.auxB_right[s] += auxBright;
-
- // Control the behavior when we reach the end of the sample
- if (samplePos >= sampleEnd)
- {
- if (pb.audio_addr.looping == 1)
- {
- if ((samplePos & ~0x1f) == (sampleEnd & ~0x1f) || (pb.audio_addr.sample_format != AUDIOFORMAT_ADPCM))
- samplePos = loopPos;
- if ((!pb.is_stream) && (pb.audio_addr.sample_format == AUDIOFORMAT_ADPCM))
- {
- pb.adpcm.yn1 = pb.adpcm_loop_info.yn1;
- pb.adpcm.yn2 = pb.adpcm_loop_info.yn2;
- pb.adpcm.pred_scale = pb.adpcm_loop_info.pred_scale;
- }
- }
- else
- {
- pb.running = 0;
- samplePos = loopPos;
- //samplePos = samplePos - sampleEnd + loopPos;
- memset(&pb.dpop, 0, sizeof(pb.dpop));
- memset(pb.src.last_samples, 0, 8);
- break;
- }
- }
- } // end of the _iSize loop
-
- // Update volume
- pb.mixer.left = ADPCM_Vol(pb.mixer.left, pb.mixer.left_delta);
- pb.mixer.right = ADPCM_Vol(pb.mixer.right, pb.mixer.right_delta);
- pb.mixer.auxA_left = ADPCM_Vol(pb.mixer.auxA_left, pb.mixer.auxA_left_delta);
- pb.mixer.auxA_right = ADPCM_Vol(pb.mixer.auxA_right, pb.mixer.auxA_right_delta);
- pb.mixer.auxB_left = ADPCM_Vol(pb.mixer.auxB_left, pb.mixer.auxB_left_delta);
- pb.mixer.auxB_right = ADPCM_Vol(pb.mixer.auxB_right, pb.mixer.auxB_right_delta);
-
- pb.src.cur_addr_frac = (u16)frac;
- pb.audio_addr.cur_addr_hi = samplePos >> 16;
- pb.audio_addr.cur_addr_lo = (u16)samplePos;
-
- } // if (pb.running)
+ return true;
}
-#endif
+// Write a PB back to MRAM/ARAM
+template
+inline bool WritePB(u32 addr, const PBType& pb)
+{
+ const u16* src = (const u16*)&pb;
+ u16* dst = (u16*)Memory::GetPointer(addr);
+ if (!dst)
+ return false;
+
+ for (u32 i = 0; i < sizeof (pb) / sizeof (u16); ++i)
+ dst[i] = Common::swap16(src[i]);
+
+ return true;
+}
+
+// Simulated accelerator state.
+static u32 acc_loop_addr, acc_end_addr;
+static u32* acc_cur_addr;
+static AXPB* acc_pb;
+
+// Sets up the simulated accelerator.
+inline void AcceleratorSetup(AXPB* pb, u32* cur_addr)
+{
+ acc_pb = pb;
+ acc_loop_addr = HILO_TO_32(pb->audio_addr.loop_addr);
+ acc_end_addr = HILO_TO_32(pb->audio_addr.end_addr);
+ acc_cur_addr = cur_addr;
+}
+
+// Reads a sample from the simulated accelerator. Also handles looping and
+// disabling streams that reached the end (this is done by an exception raised
+// by the accelerator on real hardware).
+inline u16 AcceleratorGetSample()
+{
+ u16 ret;
+
+ switch (acc_pb->audio_addr.sample_format)
+ {
+ case 0x00: // ADPCM
+ {
+ // ADPCM decoding, not much to explain here.
+ if ((*acc_cur_addr & 15) == 0)
+ {
+ acc_pb->adpcm.pred_scale = DSP::ReadARAM((*acc_cur_addr & ~15) >> 1);
+ *acc_cur_addr += 2;
+ }
+
+ int scale = 1 << (acc_pb->adpcm.pred_scale & 0xF);
+ int coef_idx = (acc_pb->adpcm.pred_scale >> 4) & 0x7;
+
+ s32 coef1 = acc_pb->adpcm.coefs[coef_idx * 2 + 0];
+ s32 coef2 = acc_pb->adpcm.coefs[coef_idx * 2 + 1];
+
+ int temp = (*acc_cur_addr & 1) ?
+ (DSP::ReadARAM(*acc_cur_addr >> 1) & 0xF) :
+ (DSP::ReadARAM(*acc_cur_addr >> 1) >> 4);
+
+ if (temp >= 8)
+ temp -= 16;
+
+ int val = (scale * temp) + ((0x400 + coef1 * acc_pb->adpcm.yn1 + coef2 * acc_pb->adpcm.yn2) >> 11);
+
+ if (val > 0x7FFF) val = 0x7FFF;
+ else if (val < -0x7FFF) val = -0x7FFF;
+
+ acc_pb->adpcm.yn2 = acc_pb->adpcm.yn1;
+ acc_pb->adpcm.yn1 = val;
+ *acc_cur_addr += 1;
+ ret = val;
+ break;
+ }
+
+ case 0x0A: // 16-bit PCM audio
+ ret = (DSP::ReadARAM(*acc_cur_addr * 2) << 8) | DSP::ReadARAM(*acc_cur_addr * 2 + 1);
+ acc_pb->adpcm.yn2 = acc_pb->adpcm.yn1;
+ acc_pb->adpcm.yn1 = ret;
+ *acc_cur_addr += 1;
+ break;
+
+ case 0x19: // 8-bit PCM audio
+ ret = DSP::ReadARAM(*acc_cur_addr) << 8;
+ acc_pb->adpcm.yn2 = acc_pb->adpcm.yn1;
+ acc_pb->adpcm.yn1 = ret;
+ *acc_cur_addr += 1;
+ break;
+
+ default:
+ ERROR_LOG(DSPHLE, "Unknown sample format: %d", acc_pb->audio_addr.sample_format);
+ return 0;
+ }
+
+ // Have we reached the end address?
+ //
+ // On real hardware, this would raise an interrupt that is handled by the
+ // UCode. We simulate what this interrupt does here.
+ if (*acc_cur_addr >= acc_end_addr)
+ {
+ // If we are really at the end (and we don't simply have cur_addr >
+ // end_addr all the time), loop back to loop_addr.
+ if ((*acc_cur_addr & ~0x1F) == (acc_end_addr & ~0x1F))
+ *acc_cur_addr = acc_loop_addr;
+
+ if (acc_pb->audio_addr.looping)
+ {
+ // Set the ADPCM infos to continue processing at loop_addr.
+ //
+ // For some reason, yn1 and yn2 aren't set if the voice is not of
+ // stream type. This is what the AX UCode does and I don't really
+ // know why.
+ acc_pb->adpcm.pred_scale = acc_pb->adpcm_loop_info.pred_scale;
+ if (!acc_pb->is_stream)
+ {
+ acc_pb->adpcm.yn1 = acc_pb->adpcm_loop_info.yn1;
+ acc_pb->adpcm.yn2 = acc_pb->adpcm_loop_info.yn2;
+ }
+ }
+ else
+ {
+ // Non looping voice reached the end -> running = 0.
+ acc_pb->running = 0;
+ }
+ }
+
+ return ret;
+}
+
+// Read 32 input samples from ARAM, decoding and converting rate if required.
+inline void GetInputSamples(AXPB& pb, s16* samples)
+{
+ u32 cur_addr = HILO_TO_32(pb.audio_addr.cur_addr);
+ AcceleratorSetup(&pb, &cur_addr);
+
+ // TODO: support polyphase interpolation if coefficients are available.
+ if (pb.src_type == SRCTYPE_POLYPHASE || pb.src_type == SRCTYPE_LINEAR)
+ {
+ // Convert the input to a higher or lower sample rate using a linear
+ // interpolation algorithm. The input to output ratio is set in
+ // pb.src.ratio, which is a floating point num stored as a 32b integer:
+ // * Upper 16 bits of the ratio are the integer part
+ // * Lower 16 bits are the decimal part
+ u32 ratio = HILO_TO_32(pb.src.ratio);
+
+ // We start getting samples not from sample 0, but 0..
+ // This avoids discontinuties in the audio stream, especially with very
+ // low ratios which interpolate a lot of values between two "real"
+ // samples.
+ u32 curr_pos = pb.src.cur_addr_frac;
+
+ // Compute the number of real samples we will need to read from the
+ // data source. We need to output 32 samples, so we need to read
+ // 32 * ratio + curr_pos samples. The maximum possible ratio available
+ // on the DSP is 4.0, so at most we will read 128 real samples.
+ s16 real_samples[130];
+ u32 real_samples_needed = (32 * ratio + curr_pos) >> 16;
+
+ // The first two real samples are the ones we read at the previous
+ // iteration. That way we can interpolate before having read 2 new
+ // samples from the accelerator.
+ //
+ // The next real samples are read from the accelerator.
+ real_samples[0] = pb.src.last_samples[2];
+ real_samples[1] = pb.src.last_samples[3];
+ for (u32 i = 0; i < real_samples_needed; ++i)
+ real_samples[i + 2] = AcceleratorGetSample();
+
+ for (u32 i = 0; i < 32; ++i)
+ {
+ // Get our current integer and fractional position. The integer
+ // position is used to get the two samples around us. The
+ // fractional position is used to compute the linear interpolation
+ // between these two samples.
+ u32 curr_int_pos = (curr_pos >> 16);
+ s32 curr_frac_pos = curr_pos & 0xFFFF;
+ s16 samp1 = real_samples[curr_int_pos];
+ s16 samp2 = real_samples[curr_int_pos + 1];
+
+ // Linear interpolation: s1 + (s2 - s1) * pos
+ s16 sample = samp1 + (s16)(((samp2 - samp1) * (s32)curr_frac_pos) >> 16);
+ samples[i] = sample;
+
+ curr_pos += ratio;
+ }
+
+ // Update the last_samples array. A bit tricky because we can't know
+ // for sure we have more than 4 real samples in our array.
+ if (real_samples_needed >= 2)
+ memcpy(pb.src.last_samples, &real_samples[real_samples_needed + 2 - 4], 4 * sizeof (u16));
+ else
+ {
+ memmove(pb.src.last_samples, &pb.src.last_samples[real_samples_needed], (4 - real_samples_needed) * sizeof (u16));
+ memcpy(&pb.src.last_samples[4 - real_samples_needed], &real_samples[2], real_samples_needed * sizeof (u16));
+ }
+ pb.src.cur_addr_frac = curr_pos & 0xFFFF;
+ }
+ else // SRCTYPE_NEAREST
+ {
+ // No sample rate conversion here: simply read 32 samples from the
+ // accelerator to the output buffer.
+ for (u32 i = 0; i < 32; ++i)
+ samples[i] = AcceleratorGetSample();
+
+ memcpy(pb.src.last_samples, samples + 28, 4 * sizeof (u16));
+ }
+
+ // Update current position in the PB.
+ pb.audio_addr.cur_addr_hi = (u16)(cur_addr >> 16);
+ pb.audio_addr.cur_addr_lo = (u16)(cur_addr & 0xFFFF);
+}
+
+// Add samples to an output buffer, with optional volume ramping.
+inline void MixAdd(int* out, const s16* input, u16* pvol, bool ramp)
+{
+ u16& volume = pvol[0];
+ u16 volume_delta = pvol[1];
+
+ // If volume ramping is disabled, set volume_delta to 0. That way, the
+ // mixing loop can avoid testing if volume ramping is enabled at each step,
+ // and just add volume_delta.
+ if (!ramp)
+ volume_delta = 0;
+
+ for (u32 i = 0; i < 32; ++i)
+ {
+ s64 sample = 2 * (s16)input[i] * (s16)volume;
+ out[i] += (s32)(sample >> 16);
+ volume += volume_delta;
+ }
+}
+
+// Process 1ms of audio (32 samples) from a PB and mix it to the buffers.
+inline void Process1ms(AXPB& pb, const AXBuffers& buffers, AXMixControl mctrl)
+{
+ // If the voice is not running, nothing to do.
+ if (!pb.running)
+ return;
+
+ // Read input samples, performing sample rate conversion if needed.
+ s16 samples[32];
+ GetInputSamples(pb, samples);
+
+ // Apply a global volume ramp using the volume envelope parameters.
+ for (u32 i = 0; i < 32; ++i)
+ {
+ s64 sample = 2 * (s16)samples[i] * (s16)pb.vol_env.cur_volume;
+ samples[i] = (s16)(sample >> 16);
+ pb.vol_env.cur_volume += pb.vol_env.cur_volume_delta;
+ }
+
+ // Optionally, execute a low pass filter
+ if (pb.lpf.enabled)
+ {
+ // TODO
+ }
+
+ // Mix LRS, AUXA and AUXB depending on mixer_control
+ // TODO: Handle DPL2 on AUXB.
+
+ // HACK: at the moment we don't mix surround into left and right, so always
+ // mix left and right in order to have sound even if a game uses surround
+ // only.
+ if (mctrl & MIX_L)
+ MixAdd(buffers.left, samples, &pb.mixer.left, mctrl & MIX_L_RAMP);
+ if (mctrl & MIX_R)
+ MixAdd(buffers.right, samples, &pb.mixer.right, mctrl & MIX_R_RAMP);
+ if (mctrl & MIX_S)
+ MixAdd(buffers.surround, samples, &pb.mixer.surround, mctrl & MIX_S_RAMP);
+
+ if (mctrl & MIX_AUXA_L)
+ MixAdd(buffers.auxA_left, samples, &pb.mixer.auxA_left, mctrl & MIX_AUXA_L_RAMP);
+ if (mctrl & MIX_AUXA_R)
+ MixAdd(buffers.auxA_right, samples, &pb.mixer.auxA_right, mctrl & MIX_AUXA_R_RAMP);
+ if (mctrl & MIX_AUXA_S)
+ MixAdd(buffers.auxA_surround, samples, &pb.mixer.auxA_surround, mctrl & MIX_AUXA_S_RAMP);
+
+ if (mctrl & MIX_AUXB_L)
+ MixAdd(buffers.auxB_left, samples, &pb.mixer.auxB_left, mctrl & MIX_AUXB_L_RAMP);
+ if (mctrl & MIX_AUXB_R)
+ MixAdd(buffers.auxB_right, samples, &pb.mixer.auxB_right, mctrl & MIX_AUXB_R_RAMP);
+ if (mctrl & MIX_AUXB_S)
+ MixAdd(buffers.auxB_surround, samples, &pb.mixer.auxB_surround, mctrl & MIX_AUXB_S_RAMP);
+
+ // Optionally, phase shift left or right channel to simulate 3D sound.
+ if (pb.initial_time_delay.on)
+ {
+ // TODO
+ }
+}
+
+#endif // !_UCODE_AX_VOICE_H
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.cpp b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.cpp
deleted file mode 100644
index 4279fabc29..0000000000
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.cpp
+++ /dev/null
@@ -1,499 +0,0 @@
-// Copyright (C) 2003 Dolphin Project.
-
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 2.0.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License 2.0 for more details.
-
-// A copy of the GPL 2.0 should have been included with the program.
-// If not, see http://www.gnu.org/licenses/
-
-// Official Git repository and contact information can be found at
-// http://code.google.com/p/dolphin-emu/
-
-#include "UCode_NewAX.h"
-#include "UCode_NewAX_Voice.h"
-#include "../../DSP.h"
-
-CUCode_NewAX::CUCode_NewAX(DSPHLE* dsp_hle, u32 crc)
- : IUCode(dsp_hle, crc)
- , m_cmdlist_size(0)
- , m_axthread(&SpawnAXThread, this)
-{
- WARN_LOG(DSPHLE, "Instantiating CUCode_NewAX: crc=%08x", crc);
- m_rMailHandler.PushMail(DSP_INIT);
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
-}
-
-CUCode_NewAX::~CUCode_NewAX()
-{
- m_cmdlist_size = (u16)-1; // Special value to signal end
- NotifyAXThread();
- m_axthread.join();
-
- m_rMailHandler.Clear();
-}
-
-void CUCode_NewAX::SpawnAXThread(CUCode_NewAX* self)
-{
- self->AXThread();
-}
-
-void CUCode_NewAX::AXThread()
-{
- while (true)
- {
- {
- std::unique_lock lk(m_cmdlist_mutex);
- while (m_cmdlist_size == 0)
- m_cmdlist_cv.wait(lk);
- }
-
- if (m_cmdlist_size == (u16)-1) // End of thread signal
- break;
-
- m_processing.lock();
- HandleCommandList();
- m_cmdlist_size = 0;
-
- // Signal end of processing
- m_rMailHandler.PushMail(DSP_YIELD);
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
- m_processing.unlock();
- }
-}
-
-void CUCode_NewAX::NotifyAXThread()
-{
- std::unique_lock lk(m_cmdlist_mutex);
- m_cmdlist_cv.notify_one();
-}
-
-void CUCode_NewAX::HandleCommandList()
-{
- // Temp variables for addresses computation
- u16 addr_hi, addr_lo;
- u16 addr2_hi, addr2_lo;
- u16 size;
-
- u32 pb_addr = 0;
-
-// WARN_LOG(DSPHLE, "Command list:");
-// for (u32 i = 0; m_cmdlist[i] != CMD_END; ++i)
-// WARN_LOG(DSPHLE, "%04x", m_cmdlist[i]);
-// WARN_LOG(DSPHLE, "-------------");
-
- u32 curr_idx = 0;
- bool end = false;
- while (!end)
- {
- u16 cmd = m_cmdlist[curr_idx++];
-
- switch (cmd)
- {
- // Some of these commands are unknown, or unused in this AX HLE.
- // We still need to skip their arguments using "curr_idx += N".
-
- case CMD_SETUP:
- addr_hi = m_cmdlist[curr_idx++];
- addr_lo = m_cmdlist[curr_idx++];
- SetupProcessing(HILO_TO_32(addr));
- break;
-
- case CMD_UNK_01: curr_idx += 5; break;
-
- case CMD_PB_ADDR:
- addr_hi = m_cmdlist[curr_idx++];
- addr_lo = m_cmdlist[curr_idx++];
- pb_addr = HILO_TO_32(addr);
- break;
-
- case CMD_PROCESS:
- ProcessPBList(pb_addr);
- break;
-
- case CMD_MIX_AUXA:
- case CMD_MIX_AUXB:
- // These two commands are handled almost the same internally.
- addr_hi = m_cmdlist[curr_idx++];
- addr_lo = m_cmdlist[curr_idx++];
- addr2_hi = m_cmdlist[curr_idx++];
- addr2_lo = m_cmdlist[curr_idx++];
- MixAUXSamples(cmd == CMD_MIX_AUXA, HILO_TO_32(addr), HILO_TO_32(addr2));
- break;
-
- case CMD_UPLOAD_LRS:
- addr_hi = m_cmdlist[curr_idx++];
- addr_lo = m_cmdlist[curr_idx++];
- UploadLRS(HILO_TO_32(addr));
- break;
-
- case CMD_SBUFFER_ADDR: curr_idx += 2; break;
- case CMD_UNK_08: curr_idx += 10; break; // TODO: check
-
- case CMD_MIX_AUXB_NOWRITE:
- addr_hi = m_cmdlist[curr_idx++];
- addr_lo = m_cmdlist[curr_idx++];
- MixAUXSamples(false, 0, HILO_TO_32(addr));
- break;
-
- case CMD_COMPRESSOR_TABLE_ADDR: curr_idx += 2; break;
- case CMD_UNK_0B: break; // TODO: check other versions
- case CMD_UNK_0C: break; // TODO: check other versions
-
- case CMD_MORE:
- addr_hi = m_cmdlist[curr_idx++];
- addr_lo = m_cmdlist[curr_idx++];
- size = m_cmdlist[curr_idx++];
-
- CopyCmdList(HILO_TO_32(addr), size);
- curr_idx = 0;
- break;
-
- case CMD_OUTPUT:
- // Skip the first address, it is used for surround audio
- // output, which we don't support yet.
- curr_idx += 2;
-
- addr_hi = m_cmdlist[curr_idx++];
- addr_lo = m_cmdlist[curr_idx++];
- OutputSamples(HILO_TO_32(addr));
- break;
-
- case CMD_END:
- end = true;
- break;
-
- case CMD_UNK_10: curr_idx += 4; break;
- case CMD_UNK_11: curr_idx += 2; break;
- case CMD_UNK_12: curr_idx += 1; break;
- case CMD_UNK_13: curr_idx += 12; break;
-
- default:
- ERROR_LOG(DSPHLE, "Unknown command in AX cmdlist: %04x", cmd);
- end = true;
- break;
- }
- }
-}
-
-static void ApplyUpdatesForMs(AXPB& pb, int curr_ms)
-{
- u32 start_idx = 0;
- for (int i = 0; i < curr_ms; ++i)
- start_idx += pb.updates.num_updates[i];
-
- u32 update_addr = HILO_TO_32(pb.updates.data);
- for (u32 i = start_idx; i < start_idx + pb.updates.num_updates[curr_ms]; ++i)
- {
- u16 update_off = HLEMemory_Read_U16(update_addr + 4 * i);
- u16 update_val = HLEMemory_Read_U16(update_addr + 4 * i + 2);
-
- ((u16*)&pb)[update_off] = update_val;
- }
-}
-
-AXMixControl CUCode_NewAX::ConvertMixerControl(u32 mixer_control)
-{
- u32 ret = 0;
-
- // TODO: find other UCode versions with different mixer_control values
- if (m_CRC == 0x4e8a8b21)
- {
- ret |= MIX_L | MIX_R;
- if (mixer_control & 0x0001) ret |= MIX_AUXA_L | MIX_AUXA_R;
- if (mixer_control & 0x0002) ret |= MIX_AUXB_L | MIX_AUXB_R;
- if (mixer_control & 0x0004)
- {
- ret |= MIX_S;
- if (ret & MIX_AUXA_L) ret |= MIX_AUXA_S;
- if (ret & MIX_AUXB_L) ret |= MIX_AUXB_S;
- }
- if (mixer_control & 0x0008)
- {
- ret |= MIX_L_RAMP | MIX_R_RAMP;
- if (ret & MIX_AUXA_L) ret |= MIX_AUXA_L_RAMP | MIX_AUXA_R_RAMP;
- if (ret & MIX_AUXB_L) ret |= MIX_AUXB_L_RAMP | MIX_AUXB_R_RAMP;
- if (ret & MIX_AUXA_S) ret |= MIX_AUXA_S_RAMP;
- if (ret & MIX_AUXB_S) ret |= MIX_AUXB_S_RAMP;
- }
- }
- else
- {
- if (mixer_control & 0x0001) ret |= MIX_L;
- if (mixer_control & 0x0002) ret |= MIX_R;
- if (mixer_control & 0x0004) ret |= MIX_S;
- if (mixer_control & 0x0008) ret |= MIX_L_RAMP | MIX_R_RAMP | MIX_S_RAMP;
- if (mixer_control & 0x0010) ret |= MIX_AUXA_L;
- if (mixer_control & 0x0020) ret |= MIX_AUXA_R;
- if (mixer_control & 0x0040) ret |= MIX_AUXA_L_RAMP | MIX_AUXA_R_RAMP;
- if (mixer_control & 0x0080) ret |= MIX_AUXA_S;
- if (mixer_control & 0x0100) ret |= MIX_AUXA_S_RAMP;
- if (mixer_control & 0x0200) ret |= MIX_AUXB_L;
- if (mixer_control & 0x0400) ret |= MIX_AUXB_R;
- if (mixer_control & 0x0800) ret |= MIX_AUXB_L_RAMP | MIX_AUXB_R_RAMP;
- if (mixer_control & 0x1000) ret |= MIX_AUXB_S;
- if (mixer_control & 0x2000) ret |= MIX_AUXB_S_RAMP;
-
- // TODO: 0x4000 is used for Dolby Pro 2 sound mixing
- }
-
- return (AXMixControl)ret;
-}
-
-void CUCode_NewAX::SetupProcessing(u32 init_addr)
-{
- u16 init_data[0x20];
-
- for (u32 i = 0; i < 0x20; ++i)
- init_data[i] = HLEMemory_Read_U16(init_addr + 2 * i);
-
- // List of all buffers we have to initialize
- int* buffers[] = {
- m_samples_left,
- m_samples_right,
- m_samples_surround,
- m_samples_auxA_left,
- m_samples_auxA_right,
- m_samples_auxA_surround,
- m_samples_auxB_left,
- m_samples_auxB_right,
- m_samples_auxB_surround
- };
-
- u32 init_idx = 0;
- for (u32 i = 0; i < sizeof (buffers) / sizeof (buffers[0]); ++i)
- {
- s32 init_val = (s32)((init_data[init_idx] << 16) | init_data[init_idx + 1]);
- s16 delta = (s16)init_data[init_idx + 2];
-
- init_idx += 3;
-
- if (!init_val)
- memset(buffers[i], 0, 5 * 32 * sizeof (int));
- else
- {
- for (u32 j = 0; j < 32 * 5; ++j)
- {
- buffers[i][j] = init_val;
- init_val += delta;
- }
- }
- }
-}
-
-void CUCode_NewAX::ProcessPBList(u32 pb_addr)
-{
- // Samples per millisecond. In theory DSP sampling rate can be changed from
- // 32KHz to 48KHz, but AX always process at 32KHz.
- const u32 spms = 32;
-
- AXPB pb;
-
- while (pb_addr)
- {
- AXBuffers buffers = {{
- m_samples_left,
- m_samples_right,
- m_samples_surround,
- m_samples_auxA_left,
- m_samples_auxA_right,
- m_samples_auxA_surround,
- m_samples_auxB_left,
- m_samples_auxB_right,
- m_samples_auxB_surround
- }};
-
- if (!ReadPB(pb_addr, pb))
- break;
-
- for (int curr_ms = 0; curr_ms < 5; ++curr_ms)
- {
- ApplyUpdatesForMs(pb, curr_ms);
-
- Process1ms(pb, buffers, ConvertMixerControl(pb.mixer_control));
-
- // Forward the buffers
- for (u32 i = 0; i < sizeof (buffers.ptrs) / sizeof (buffers.ptrs[0]); ++i)
- buffers.ptrs[i] += spms;
- }
-
- WritePB(pb_addr, pb);
- pb_addr = HILO_TO_32(pb.next_pb);
- }
-}
-
-void CUCode_NewAX::MixAUXSamples(bool AUXA, u32 write_addr, u32 read_addr)
-{
- int buffers[3][5 * 32];
-
- // First, we need to send the contents of our AUX buffers to the CPU.
- if (write_addr)
- {
- for (u32 i = 0; i < 5 * 32; ++i)
- {
- if (AUXA)
- {
- buffers[0][i] = Common::swap32(m_samples_auxA_left[i]);
- buffers[1][i] = Common::swap32(m_samples_auxA_right[i]);
- buffers[2][i] = Common::swap32(m_samples_auxA_surround[i]);
- }
- else
- {
- buffers[0][i] = Common::swap32(m_samples_auxB_left[i]);
- buffers[1][i] = Common::swap32(m_samples_auxB_right[i]);
- buffers[2][i] = Common::swap32(m_samples_auxB_surround[i]);
- }
- }
- memcpy(HLEMemory_Get_Pointer(write_addr), buffers, sizeof (buffers));
- }
-
- // Then, we read the new buffers from the CPU and add to our current
- // buffers.
- memcpy(buffers, HLEMemory_Get_Pointer(read_addr), sizeof (buffers));
- for (u32 i = 0; i < 5 * 32; ++i)
- {
- m_samples_left[i] += Common::swap32(buffers[0][i]);
- m_samples_right[i] += Common::swap32(buffers[1][i]);
- m_samples_surround[i] += Common::swap32(buffers[2][i]);
- }
-}
-
-void CUCode_NewAX::UploadLRS(u32 dst_addr)
-{
- int buffers[3][5 * 32];
-
- for (u32 i = 0; i < 5 * 32; ++i)
- {
- buffers[0][i] = Common::swap32(m_samples_left[i]);
- buffers[1][i] = Common::swap32(m_samples_right[i]);
- buffers[2][i] = Common::swap32(m_samples_surround[i]);
- }
- memcpy(HLEMemory_Get_Pointer(dst_addr), buffers, sizeof (buffers));
-}
-
-void CUCode_NewAX::OutputSamples(u32 out_addr)
-{
- // 32 samples per ms, 5 ms, 2 channels
- short buffer[5 * 32 * 2];
-
- // Clamp internal buffers to 16 bits.
- for (u32 i = 0; i < 5 * 32; ++i)
- {
- int left = m_samples_left[i];
- int right = m_samples_right[i];
-
- if (left < -32767) left = -32767;
- if (left > 32767) left = 32767;
- if (right < -32767) right = -32767;
- if (right > 32767) right = 32767;
-
- m_samples_left[i] = left;
- m_samples_right[i] = right;
- }
-
- for (u32 i = 0; i < 5 * 32; ++i)
- {
- buffer[2 * i] = Common::swap16(m_samples_left[i]);
- buffer[2 * i + 1] = Common::swap16(m_samples_right[i]);
- }
-
- memcpy(HLEMemory_Get_Pointer(out_addr), buffer, sizeof (buffer));
-}
-
-void CUCode_NewAX::HandleMail(u32 mail)
-{
- // Indicates if the next message is a command list address.
- static bool next_is_cmdlist = false;
- static u16 cmdlist_size = 0;
-
- bool set_next_is_cmdlist = false;
-
- // Wait for DSP processing to be done before answering any mail. This is
- // safe to do because it matches what the DSP does on real hardware: there
- // is no interrupt when a mail from CPU is received.
- m_processing.lock();
-
- if (next_is_cmdlist)
- {
- CopyCmdList(mail, cmdlist_size);
- NotifyAXThread();
- }
- else if (m_UploadSetupInProgress)
- {
- PrepareBootUCode(mail);
- }
- else if (mail == MAIL_RESUME)
- {
- // Acknowledge the resume request
- m_rMailHandler.PushMail(DSP_RESUME);
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
- }
- else if (mail == MAIL_NEW_UCODE)
- {
- soundStream->GetMixer()->SetHLEReady(false);
- m_UploadSetupInProgress = true;
- }
- else if (mail == MAIL_RESET)
- {
- m_DSPHLE->SetUCode(UCODE_ROM);
- }
- else if (mail == MAIL_CONTINUE)
- {
- // We don't have to do anything here - the CPU does not wait for a ACK
- // and sends a cmdlist mail just after.
- }
- else if ((mail & MAIL_CMDLIST_MASK) == MAIL_CMDLIST)
- {
- // A command list address is going to be sent next.
- set_next_is_cmdlist = true;
- cmdlist_size = (u16)(mail & ~MAIL_CMDLIST_MASK);
- }
- else
- {
- ERROR_LOG(DSPHLE, "Unknown mail sent to AX::HandleMail: %08x", mail);
- }
-
- m_processing.unlock();
- next_is_cmdlist = set_next_is_cmdlist;
-}
-
-void CUCode_NewAX::CopyCmdList(u32 addr, u16 size)
-{
- if (size >= (sizeof (m_cmdlist) / sizeof (u16)))
- {
- ERROR_LOG(DSPHLE, "Command list at %08x is too large: size=%d", addr, size);
- return;
- }
-
- for (u32 i = 0; i < size; ++i, addr += 2)
- m_cmdlist[i] = HLEMemory_Read_U16(addr);
- m_cmdlist_size = size;
-}
-
-void CUCode_NewAX::MixAdd(short* out_buffer, int nsamples)
-{
- // Should never be called: we do not set HLE as ready.
- // We accurately send samples to RAM instead of directly to the mixer.
-}
-
-void CUCode_NewAX::Update(int cycles)
-{
- // Used for UCode switching.
- if (NeedsResumeMail())
- {
- m_rMailHandler.PushMail(DSP_RESUME);
- DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
- }
-}
-
-void CUCode_NewAX::DoState(PointerWrap& p)
-{
- std::lock_guard lk(m_processing);
-
- DoStateShared(p);
-}
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.h b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.h
deleted file mode 100644
index 2614ea1389..0000000000
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.h
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright (C) 2003 Dolphin Project.
-
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 2.0.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License 2.0 for more details.
-
-// A copy of the GPL 2.0 should have been included with the program.
-// If not, see http://www.gnu.org/licenses/
-
-// Official Git repository and contact information can be found at
-// http://code.google.com/p/dolphin-emu/
-
-// High-level emulation for the AX Gamecube UCode.
-//
-// TODO:
-// * Depop support
-// * ITD support
-// * Polyphase sample interpolation support (not very useful)
-// * Surround sound mixing
-// * Dolby Pro 2 mixing with recent AX versions
-
-#ifndef _UCODE_NEWAX_H
-#define _UCODE_NEWAX_H
-
-#include "UCodes.h"
-#include "UCode_AXStructs.h"
-#include "UCode_NewAX_Voice.h"
-
-class CUCode_NewAX : public IUCode
-{
-public:
- CUCode_NewAX(DSPHLE* dsp_hle, u32 crc);
- virtual ~CUCode_NewAX();
-
- void HandleMail(u32 mail);
- void MixAdd(short* out_buffer, int nsamples);
- void Update(int cycles);
- void DoState(PointerWrap& p);
-
- // Needed because StdThread.h std::thread implem does not support member
- // pointers.
- static void SpawnAXThread(CUCode_NewAX* self);
-
-private:
- enum MailType
- {
- MAIL_RESUME = 0xCDD10000,
- MAIL_NEW_UCODE = 0xCDD10001,
- MAIL_RESET = 0xCDD10002,
- MAIL_CONTINUE = 0xCDD10003,
-
- // CPU sends 0xBABE0000 | cmdlist_size to the DSP
- MAIL_CMDLIST = 0xBABE0000,
- MAIL_CMDLIST_MASK = 0xFFFF0000
- };
-
- enum CmdType
- {
- CMD_SETUP = 0x00,
- CMD_UNK_01 = 0x01,
- CMD_PB_ADDR = 0x02,
- CMD_PROCESS = 0x03,
- CMD_MIX_AUXA = 0x04,
- CMD_MIX_AUXB = 0x05,
- CMD_UPLOAD_LRS = 0x06,
- CMD_SBUFFER_ADDR = 0x07,
- CMD_UNK_08 = 0x08,
- CMD_MIX_AUXB_NOWRITE = 0x09,
- CMD_COMPRESSOR_TABLE_ADDR = 0x0A,
- CMD_UNK_0B = 0x0B,
- CMD_UNK_0C = 0x0C,
- CMD_MORE = 0x0D,
- CMD_OUTPUT = 0x0E,
- CMD_END = 0x0F,
- CMD_UNK_10 = 0x10,
- CMD_UNK_11 = 0x11,
- CMD_UNK_12 = 0x12,
- CMD_UNK_13 = 0x13,
- };
-
- // 32 * 5 because 32 samples per millisecond, for 5 milliseconds.
- int m_samples_left[32 * 5];
- int m_samples_right[32 * 5];
- int m_samples_surround[32 * 5];
- int m_samples_auxA_left[32 * 5];
- int m_samples_auxA_right[32 * 5];
- int m_samples_auxA_surround[32 * 5];
- int m_samples_auxB_left[32 * 5];
- int m_samples_auxB_right[32 * 5];
- int m_samples_auxB_surround[32 * 5];
-
- // Volatile because it's set by HandleMail and accessed in
- // HandleCommandList, which are running in two different threads.
- volatile u16 m_cmdlist[512];
- volatile u32 m_cmdlist_size;
-
- std::thread m_axthread;
-
- // Sync objects
- std::mutex m_processing;
- std::condition_variable m_cmdlist_cv;
- std::mutex m_cmdlist_mutex;
-
- // Copy a command list from memory to our temp buffer
- void CopyCmdList(u32 addr, u16 size);
-
- // Convert a mixer_control bitfield to our internal representation for that
- // value. Required because that bitfield has a different meaning in some
- // versions of AX.
- AXMixControl ConvertMixerControl(u32 mixer_control);
-
- // Send a notification to the AX thread to tell him a new cmdlist addr is
- // available for processing.
- void NotifyAXThread();
-
- void AXThread();
- void HandleCommandList();
- void SetupProcessing(u32 studio_addr);
- void ProcessPBList(u32 pb_addr);
- void MixAUXSamples(bool AUXA, u32 write_addr, u32 read_addr);
- void UploadLRS(u32 dst_addr);
- void OutputSamples(u32 out_addr);
-};
-
-#endif // !_UCODE_NEWAX_H
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX_Voice.h b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX_Voice.h
deleted file mode 100644
index 82e749dbf0..0000000000
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX_Voice.h
+++ /dev/null
@@ -1,378 +0,0 @@
-// Copyright (C) 2003 Dolphin Project.
-
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 2.0.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License 2.0 for more details.
-
-// A copy of the GPL 2.0 should have been included with the program.
-// If not, see http://www.gnu.org/licenses/
-
-// Official Git repository and contact information can be found at
-// http://code.google.com/p/dolphin-emu/
-
-#ifndef _UCODE_NEWAX_VOICE_H
-#define _UCODE_NEWAX_VOICE_H
-
-#include "Common.h"
-#include "UCode_AXStructs.h"
-#include "../../DSP.h"
-
-// Useful macro to convert xxx_hi + xxx_lo to xxx for 32 bits.
-#define HILO_TO_32(name) \
- ((name##_hi << 16) | name##_lo)
-
-// Used to pass a large amount of buffers to the mixing function.
-union AXBuffers
-{
- struct
- {
- int* left;
- int* right;
- int* surround;
-
- int* auxA_left;
- int* auxA_right;
- int* auxA_surround;
-
- int* auxB_left;
- int* auxB_right;
- int* auxB_surround;
- };
-
- int* ptrs[9];
-};
-
-// We can't directly use the mixer_control field from the PB because it does
-// not mean the same in all AX versions. The AX UCode converts the
-// mixer_control value to an AXMixControl bitfield.
-enum AXMixControl
-{
- MIX_L = 0x00001,
- MIX_L_RAMP = 0x00002,
- MIX_R = 0x00004,
- MIX_R_RAMP = 0x00008,
- MIX_S = 0x00010,
- MIX_S_RAMP = 0x00020,
-
- MIX_AUXA_L = 0x00040,
- MIX_AUXA_L_RAMP = 0x00080,
- MIX_AUXA_R = 0x00100,
- MIX_AUXA_R_RAMP = 0x00200,
- MIX_AUXA_S = 0x00400,
- MIX_AUXA_S_RAMP = 0x00800,
-
- MIX_AUXB_L = 0x01000,
- MIX_AUXB_L_RAMP = 0x02000,
- MIX_AUXB_R = 0x04000,
- MIX_AUXB_R_RAMP = 0x08000,
- MIX_AUXB_S = 0x10000,
- MIX_AUXB_S_RAMP = 0x20000
-};
-
-// Read a PB from MRAM/ARAM
-inline bool ReadPB(u32 addr, AXPB& pb)
-{
- u16* dst = (u16*)&pb;
- const u16* src = (const u16*)Memory::GetPointer(addr);
- if (!src)
- return false;
-
- for (u32 i = 0; i < sizeof (pb) / sizeof (u16); ++i)
- dst[i] = Common::swap16(src[i]);
-
- return true;
-}
-
-// Write a PB back to MRAM/ARAM
-inline bool WritePB(u32 addr, const AXPB& pb)
-{
- const u16* src = (const u16*)&pb;
- u16* dst = (u16*)Memory::GetPointer(addr);
- if (!dst)
- return false;
-
- for (u32 i = 0; i < sizeof (pb) / sizeof (u16); ++i)
- dst[i] = Common::swap16(src[i]);
-
- return true;
-}
-
-// Simulated accelerator state.
-static u32 acc_loop_addr, acc_end_addr;
-static u32* acc_cur_addr;
-static AXPB* acc_pb;
-
-// Sets up the simulated accelerator.
-inline void AcceleratorSetup(AXPB* pb, u32* cur_addr)
-{
- acc_pb = pb;
- acc_loop_addr = HILO_TO_32(pb->audio_addr.loop_addr);
- acc_end_addr = HILO_TO_32(pb->audio_addr.end_addr);
- acc_cur_addr = cur_addr;
-}
-
-// Reads a sample from the simulated accelerator. Also handles looping and
-// disabling streams that reached the end (this is done by an exception raised
-// by the accelerator on real hardware).
-inline u16 AcceleratorGetSample()
-{
- u16 ret;
-
- switch (acc_pb->audio_addr.sample_format)
- {
- case 0x00: // ADPCM
- {
- // ADPCM decoding, not much to explain here.
- if ((*acc_cur_addr & 15) == 0)
- {
- acc_pb->adpcm.pred_scale = DSP::ReadARAM((*acc_cur_addr & ~15) >> 1);
- *acc_cur_addr += 2;
- }
-
- int scale = 1 << (acc_pb->adpcm.pred_scale & 0xF);
- int coef_idx = (acc_pb->adpcm.pred_scale >> 4) & 0x7;
-
- s32 coef1 = acc_pb->adpcm.coefs[coef_idx * 2 + 0];
- s32 coef2 = acc_pb->adpcm.coefs[coef_idx * 2 + 1];
-
- int temp = (*acc_cur_addr & 1) ?
- (DSP::ReadARAM(*acc_cur_addr >> 1) & 0xF) :
- (DSP::ReadARAM(*acc_cur_addr >> 1) >> 4);
-
- if (temp >= 8)
- temp -= 16;
-
- int val = (scale * temp) + ((0x400 + coef1 * acc_pb->adpcm.yn1 + coef2 * acc_pb->adpcm.yn2) >> 11);
-
- if (val > 0x7FFF) val = 0x7FFF;
- else if (val < -0x7FFF) val = -0x7FFF;
-
- acc_pb->adpcm.yn2 = acc_pb->adpcm.yn1;
- acc_pb->adpcm.yn1 = val;
- *acc_cur_addr += 1;
- ret = val;
- break;
- }
-
- case 0x0A: // 16-bit PCM audio
- ret = (DSP::ReadARAM(*acc_cur_addr * 2) << 8) | DSP::ReadARAM(*acc_cur_addr * 2 + 1);
- acc_pb->adpcm.yn2 = acc_pb->adpcm.yn1;
- acc_pb->adpcm.yn1 = ret;
- *acc_cur_addr += 1;
- break;
-
- case 0x19: // 8-bit PCM audio
- ret = DSP::ReadARAM(*acc_cur_addr) << 8;
- acc_pb->adpcm.yn2 = acc_pb->adpcm.yn1;
- acc_pb->adpcm.yn1 = ret;
- *acc_cur_addr += 1;
- break;
-
- default:
- ERROR_LOG(DSPHLE, "Unknown sample format: %d", acc_pb->audio_addr.sample_format);
- return 0;
- }
-
- // Have we reached the end address?
- //
- // On real hardware, this would raise an interrupt that is handled by the
- // UCode. We simulate what this interrupt does here.
- if (*acc_cur_addr >= acc_end_addr)
- {
- // If we are really at the end (and we don't simply have cur_addr >
- // end_addr all the time), loop back to loop_addr.
- if ((*acc_cur_addr & ~0x1F) == (acc_end_addr & ~0x1F))
- *acc_cur_addr = acc_loop_addr;
-
- if (acc_pb->audio_addr.looping)
- {
- // Set the ADPCM infos to continue processing at loop_addr.
- //
- // For some reason, yn1 and yn2 aren't set if the voice is not of
- // stream type. This is what the AX UCode does and I don't really
- // know why.
- acc_pb->adpcm.pred_scale = acc_pb->adpcm_loop_info.pred_scale;
- if (!acc_pb->is_stream)
- {
- acc_pb->adpcm.yn1 = acc_pb->adpcm_loop_info.yn1;
- acc_pb->adpcm.yn2 = acc_pb->adpcm_loop_info.yn2;
- }
- }
- else
- {
- // Non looping voice reached the end -> running = 0.
- acc_pb->running = 0;
- }
- }
-
- return ret;
-}
-
-// Read 32 input samples from ARAM, decoding and converting rate if required.
-inline void GetInputSamples(AXPB& pb, s16* samples)
-{
- u32 cur_addr = HILO_TO_32(pb.audio_addr.cur_addr);
- AcceleratorSetup(&pb, &cur_addr);
-
- // TODO: support polyphase interpolation if coefficients are available.
- if (pb.src_type == SRCTYPE_POLYPHASE || pb.src_type == SRCTYPE_LINEAR)
- {
- // Convert the input to a higher or lower sample rate using a linear
- // interpolation algorithm. The input to output ratio is set in
- // pb.src.ratio, which is a floating point num stored as a 32b integer:
- // * Upper 16 bits of the ratio are the integer part
- // * Lower 16 bits are the decimal part
- u32 ratio = HILO_TO_32(pb.src.ratio);
-
- // We start getting samples not from sample 0, but 0..
- // This avoids discontinuties in the audio stream, especially with very
- // low ratios which interpolate a lot of values between two "real"
- // samples.
- u32 curr_pos = pb.src.cur_addr_frac;
-
- // Compute the number of real samples we will need to read from the
- // data source. We need to output 32 samples, so we need to read
- // 32 * ratio + curr_pos samples. The maximum possible ratio available
- // on the DSP is 4.0, so at most we will read 128 real samples.
- s16 real_samples[130];
- u32 real_samples_needed = (32 * ratio + curr_pos) >> 16;
-
- // The first two real samples are the ones we read at the previous
- // iteration. That way we can interpolate before having read 2 new
- // samples from the accelerator.
- //
- // The next real samples are read from the accelerator.
- real_samples[0] = pb.src.last_samples[2];
- real_samples[1] = pb.src.last_samples[3];
- for (u32 i = 0; i < real_samples_needed; ++i)
- real_samples[i + 2] = AcceleratorGetSample();
-
- for (u32 i = 0; i < 32; ++i)
- {
- // Get our current integer and fractional position. The integer
- // position is used to get the two samples around us. The
- // fractional position is used to compute the linear interpolation
- // between these two samples.
- u32 curr_int_pos = (curr_pos >> 16);
- s32 curr_frac_pos = curr_pos & 0xFFFF;
- s16 samp1 = real_samples[curr_int_pos];
- s16 samp2 = real_samples[curr_int_pos + 1];
-
- // Linear interpolation: s1 + (s2 - s1) * pos
- s16 sample = samp1 + (s16)(((samp2 - samp1) * (s32)curr_frac_pos) >> 16);
- samples[i] = sample;
-
- curr_pos += ratio;
- }
-
- // Update the last_samples array. A bit tricky because we can't know
- // for sure we have more than 4 real samples in our array.
- if (real_samples_needed >= 2)
- memcpy(pb.src.last_samples, &real_samples[real_samples_needed + 2 - 4], 4 * sizeof (u16));
- else
- {
- memmove(pb.src.last_samples, &pb.src.last_samples[real_samples_needed], (4 - real_samples_needed) * sizeof (u16));
- memcpy(&pb.src.last_samples[4 - real_samples_needed], &real_samples[2], real_samples_needed * sizeof (u16));
- }
- pb.src.cur_addr_frac = curr_pos & 0xFFFF;
- }
- else // SRCTYPE_NEAREST
- {
- // No sample rate conversion here: simply read 32 samples from the
- // accelerator to the output buffer.
- for (u32 i = 0; i < 32; ++i)
- samples[i] = AcceleratorGetSample();
-
- memcpy(pb.src.last_samples, samples + 28, 4 * sizeof (u16));
- }
-
- // Update current position in the PB.
- pb.audio_addr.cur_addr_hi = (u16)(cur_addr >> 16);
- pb.audio_addr.cur_addr_lo = (u16)(cur_addr & 0xFFFF);
-}
-
-// Add samples to an output buffer, with optional volume ramping.
-inline void MixAdd(int* out, const s16* input, u16* pvol, bool ramp)
-{
- u16& volume = pvol[0];
- u16 volume_delta = pvol[1];
-
- // If volume ramping is disabled, set volume_delta to 0. That way, the
- // mixing loop can avoid testing if volume ramping is enabled at each step,
- // and just add volume_delta.
- if (!ramp)
- volume_delta = 0;
-
- for (u32 i = 0; i < 32; ++i)
- {
- s64 sample = 2 * (s16)input[i] * (s16)volume;
- out[i] += (s32)(sample >> 16);
- volume += volume_delta;
- }
-}
-
-// Process 1ms of audio (32 samples) from a PB and mix it to the buffers.
-inline void Process1ms(AXPB& pb, const AXBuffers& buffers, AXMixControl mctrl)
-{
- // If the voice is not running, nothing to do.
- if (!pb.running)
- return;
-
- // Read input samples, performing sample rate conversion if needed.
- s16 samples[32];
- GetInputSamples(pb, samples);
-
- // Apply a global volume ramp using the volume envelope parameters.
- for (u32 i = 0; i < 32; ++i)
- {
- s64 sample = 2 * (s16)samples[i] * (s16)pb.vol_env.cur_volume;
- samples[i] = (s16)(sample >> 16);
- pb.vol_env.cur_volume += pb.vol_env.cur_volume_delta;
- }
-
- // Optionally, execute a low pass filter
- if (pb.lpf.enabled)
- {
- // TODO
- }
-
- // Mix LRS, AUXA and AUXB depending on mixer_control
- // TODO: Handle DPL2 on AUXB.
-
- // HACK: at the moment we don't mix surround into left and right, so always
- // mix left and right in order to have sound even if a game uses surround
- // only.
- if (mctrl & MIX_L)
- MixAdd(buffers.left, samples, &pb.mixer.left, mctrl & MIX_L_RAMP);
- if (mctrl & MIX_R)
- MixAdd(buffers.right, samples, &pb.mixer.right, mctrl & MIX_R_RAMP);
- if (mctrl & MIX_S)
- MixAdd(buffers.surround, samples, &pb.mixer.surround, mctrl & MIX_S_RAMP);
-
- if (mctrl & MIX_AUXA_L)
- MixAdd(buffers.auxA_left, samples, &pb.mixer.auxA_left, mctrl & MIX_AUXA_L_RAMP);
- if (mctrl & MIX_AUXA_R)
- MixAdd(buffers.auxA_right, samples, &pb.mixer.auxA_right, mctrl & MIX_AUXA_R_RAMP);
- if (mctrl & MIX_AUXA_S)
- MixAdd(buffers.auxA_surround, samples, &pb.mixer.auxA_surround, mctrl & MIX_AUXA_S_RAMP);
-
- if (mctrl & MIX_AUXB_L)
- MixAdd(buffers.auxB_left, samples, &pb.mixer.auxB_left, mctrl & MIX_AUXB_L_RAMP);
- if (mctrl & MIX_AUXB_R)
- MixAdd(buffers.auxB_right, samples, &pb.mixer.auxB_right, mctrl & MIX_AUXB_R_RAMP);
- if (mctrl & MIX_AUXB_S)
- MixAdd(buffers.auxB_surround, samples, &pb.mixer.auxB_surround, mctrl & MIX_AUXB_S_RAMP);
-
- // Optionally, phase shift left or right channel to simulate 3D sound.
- if (pb.initial_time_delay.on)
- {
- // TODO
- }
-}
-
-#endif // !_UCODE_NEWAX_VOICE_H
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCodes.cpp b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCodes.cpp
index 58e6198205..c04bf41403 100644
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCodes.cpp
+++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCodes.cpp
@@ -19,7 +19,6 @@
#include "UCode_AX.h"
#include "UCode_AXWii.h"
-#include "UCode_NewAX.h"
#include "UCode_Zelda.h"
#include "UCode_ROM.h"
#include "UCode_CARD.h"
@@ -58,7 +57,7 @@ IUCode* UCodeFactory(u32 _CRC, DSPHLE *dsp_hle, bool bWii)
case 0xe2136399: // billy hatcher, dragonballz, mario party 5, TMNT, ava1080
case 0x3389a79e: // MP1/MP2 Wii (Metroid Prime Trilogy)
INFO_LOG(DSPHLE, "CRC %08x: AX ucode chosen", _CRC);
- return new CUCode_NewAX(dsp_hle, _CRC);
+ return new CUCode_AX(dsp_hle, _CRC);
case 0x6ba3b3ea: // IPL - PAL
case 0x24b22038: // IPL - NTSC/NTSC-JAP