Rename NewAX -> AX and remove the old code. Time to work on AXWii.

This commit is contained in:
Pierre Bourdon 2012-11-19 22:03:56 +01:00
parent 4f88fee560
commit e750bed2a9
12 changed files with 859 additions and 1733 deletions

View file

@ -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

View file

@ -265,7 +265,6 @@
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_CARD.cpp" />
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_GBA.cpp" />
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_InitAudioSystem.cpp" />
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_NewAX.cpp" />
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_ROM.cpp" />
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_Zelda.cpp" />
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_Zelda_ADPCM.cpp" />
@ -467,8 +466,6 @@
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_CARD.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_GBA.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_InitAudioSystem.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_NewAX.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_NewAX_Voice.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_ROM.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_Zelda.h" />
<ClInclude Include="Src\HW\DSPLLE\DSPDebugInterface.h" />

View file

@ -206,9 +206,6 @@
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_InitAudioSystem.cpp">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClCompile>
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_NewAX.cpp">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClCompile>
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_ROM.cpp">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClCompile>
@ -751,12 +748,6 @@
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_InitAudioSystem.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_NewAX.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_NewAX_Voice.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_ROM.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>

View file

@ -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 <sstream>
#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<std::mutex> 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<std::mutex> 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<std::mutex> lk(m_csMix);
p.Do(numPBaddr);
p.Do(m_addressPBs);
p.Do(PBaddr);
std::lock_guard<std::mutex> lk(m_processing);
DoStateShared(p);
}

View file

@ -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 <iostream>
#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

View file

@ -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

View file

@ -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;

View file

@ -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<class ParamBlockType>
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 <typename PBType>
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 <typename PBType>
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.<cur_addr_frac>.
// 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

View file

@ -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<std::mutex> 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<std::mutex> 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<std::mutex> lk(m_processing);
DoStateShared(p);
}

View file

@ -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

View file

@ -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.<cur_addr_frac>.
// 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

View file

@ -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