From e750bed2a996bfe13cc4f33656f967209b94c5c5 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Mon, 19 Nov 2012 22:03:56 +0100 Subject: [PATCH] Rename NewAX -> AX and remove the old code. Time to work on AXWii. --- Source/Core/Core/CMakeLists.txt | 1 - Source/Core/Core/Core.vcxproj | 3 - Source/Core/Core/Core.vcxproj.filters | 9 - .../Core/Src/HW/DSPHLE/UCodes/UCode_AX.cpp | 828 +++++++++--------- .../Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.h | 127 ++- .../Src/HW/DSPHLE/UCodes/UCode_AXStructs.h | 3 +- .../Core/Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp | 9 +- .../Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h | 602 +++++++------ .../Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.cpp | 499 ----------- .../Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.h | 130 --- .../Src/HW/DSPHLE/UCodes/UCode_NewAX_Voice.h | 378 -------- .../Core/Core/Src/HW/DSPHLE/UCodes/UCodes.cpp | 3 +- 12 files changed, 859 insertions(+), 1733 deletions(-) delete mode 100644 Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.cpp delete mode 100644 Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.h delete mode 100644 Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX_Voice.h 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