Merge branch 'new-ax-hle'

GC and Wii games using the AX UCode should now work almost perfectly with DSP
HLE. If you get any issue, make sure the "DSP on dedicated thread" option is
disabled, and try setting framelimit to "Audio".

As a side effect, DSP HLE should not desync anymore (making it usable in
netplay and TAS) with AX games.

Conflicts:
	Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.h
	Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp
This commit is contained in:
Pierre Bourdon 2013-04-02 20:58:32 +02:00
commit eb06c62a6e
17 changed files with 1127 additions and 1629 deletions

View File

@ -69,8 +69,7 @@ set(SRCS Src/ActionReplay.cpp
Src/HW/CPU.cpp
Src/HW/DSP.cpp
Src/HW/DSPHLE/UCodes/UCode_AX.cpp
Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp
Src/HW/DSPHLE/UCodes/UCode_NewAXWii.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_ROM.cpp

View File

@ -261,7 +261,6 @@
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCodes.cpp" />
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_AX.cpp" />
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_AXWii.cpp" />
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_NewAXWii.cpp" />
<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" />
@ -465,11 +464,8 @@
<ClInclude Include="Src\HW\DSPHLE\MailHandler.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCodes.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AX.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AX_Structs.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AXStructs.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AXWii.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AXWii_Structs.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AXWii_Voice.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_NewAXWii.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AX_Voice.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_CARD.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_GBA.h" />
@ -602,4 +598,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -196,9 +196,6 @@
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_AXWii.cpp">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClCompile>
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_NewAXWii.cpp">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClCompile>
<ClCompile Include="Src\HW\DSPHLE\UCodes\UCode_CARD.cpp">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClCompile>
@ -738,21 +735,12 @@
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AX_Voice.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AX_Structs.h">
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AXStructs.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AXWii.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AXWii_Structs.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_AXWii_Voice.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_NewAXWii.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_CARD.h">
<Filter>HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes</Filter>
</ClInclude>

View File

@ -273,7 +273,7 @@ u16 DSPHLE::DSP_WriteControlRegister(unsigned short _Value)
UDSPControl Temp(_Value);
if (!m_InitMixer)
{
if (!Temp.DSPHalt && Temp.DSPInit)
if (!Temp.DSPHalt)
{
InitMixer();
}

View File

@ -17,29 +17,78 @@
#include "UCode_AX.h"
#include "../../DSP.h"
#include "FileUtil.h"
#include "ConfigManager.h"
#define AX_GC
#include "UCode_AX_Voice.h"
CUCode_AX::CUCode_AX(DSPHLE* dsp_hle, u32 crc)
: IUCode(dsp_hle, crc)
, m_work_available(false)
, m_cmdlist_size(0)
, m_axthread(&SpawnAXThread, this)
, m_run_on_thread(SConfig::GetInstance().m_LocalCoreStartupParameter.bDSPThread)
{
WARN_LOG(DSPHLE, "Instantiating CUCode_AX: crc=%08x", crc);
m_rMailHandler.PushMail(DSP_INIT);
DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
LoadResamplingCoefficients();
if (m_run_on_thread)
m_axthread = std::thread(SpawnAXThread, this);
}
CUCode_AX::~CUCode_AX()
{
m_cmdlist_size = (u16)-1; // Special value to signal end
NotifyAXThread();
m_axthread.join();
if (m_run_on_thread)
{
m_cmdlist_size = (u16)-1; // Special value to signal end
NotifyAXThread();
m_axthread.join();
}
m_rMailHandler.Clear();
}
void CUCode_AX::LoadResamplingCoefficients()
{
m_coeffs_available = false;
std::string filenames[] = {
File::GetUserPath(D_GCUSER_IDX) + "dsp_coef.bin",
File::GetSysDirectory() + "/GC/dsp_coef.bin"
};
size_t fidx;
std::string filename;
for (fidx = 0; fidx < sizeof (filenames) / sizeof (filenames[0]); ++fidx)
{
filename = filenames[fidx];
if (!File::Exists(filename))
continue;
if (File::GetSize(filename) != 0x1000)
continue;
break;
}
if (fidx >= sizeof (filenames) / sizeof (filenames[0]))
return;
WARN_LOG(DSPHLE, "Loading polyphase resampling coeffs from %s", filename.c_str());
FILE* fp = fopen(filename.c_str(), "rb");
fread(m_coeffs, 1, 0x1000, fp);
fclose(fp);
for (u32 i = 0; i < 0x800; ++i)
m_coeffs[i] = Common::swap16(m_coeffs[i]);
m_coeffs_available = true;
}
void CUCode_AX::SpawnAXThread(CUCode_AX* self)
{
self->AXThread();
@ -61,20 +110,32 @@ void CUCode_AX::AXThread()
m_processing.lock();
HandleCommandList();
m_cmdlist_size = 0;
// Signal end of processing
m_rMailHandler.PushMail(DSP_YIELD);
DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
SignalWorkEnd();
m_processing.unlock();
}
}
void CUCode_AX::SignalWorkEnd()
{
// Signal end of processing
m_rMailHandler.PushMail(DSP_YIELD);
DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
}
void CUCode_AX::NotifyAXThread()
{
std::unique_lock<std::mutex> lk(m_cmdlist_mutex);
m_cmdlist_cv.notify_one();
}
void CUCode_AX::StartWorking()
{
if (m_run_on_thread)
NotifyAXThread();
else
m_work_available = true;
}
void CUCode_AX::HandleCommandList()
{
// Temp variables for addresses computation
@ -200,11 +261,7 @@ void CUCode_AX::HandleCommandList()
u16 idx = m_cmdlist[curr_idx++];
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
// TODO
(void)samp_val;
(void)idx;
break;
}
@ -250,19 +307,18 @@ void CUCode_AX::HandleCommandList()
}
}
static void ApplyUpdatesForMs(AXPB& pb, int curr_ms)
void CUCode_AX::ApplyUpdatesForMs(int curr_ms, u16* pb, u16* num_updates, u16* updates)
{
u32 start_idx = 0;
for (int i = 0; i < curr_ms; ++i)
start_idx += pb.updates.num_updates[i];
start_idx += 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 (u32 i = start_idx; i < start_idx + 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 update_off = Common::swap16(updates[2 * i]);
u16 update_val = Common::swap16(updates[2 * i + 1]);
((u16*)&pb)[update_off] = update_val;
pb[update_off] = update_val;
}
}
@ -405,11 +461,15 @@ void CUCode_AX::ProcessPBList(u32 pb_addr)
if (!ReadPB(pb_addr, pb))
break;
u32 updates_addr = HILO_TO_32(pb.updates.data);
u16* updates = (u16*)HLEMemory_Get_Pointer(updates_addr);
for (int curr_ms = 0; curr_ms < 5; ++curr_ms)
{
ApplyUpdatesForMs(pb, curr_ms);
ApplyUpdatesForMs(curr_ms, (u16*)&pb, pb.updates.num_updates, updates);
Process1ms(pb, buffers, ConvertMixerControl(pb.mixer_control));
ProcessVoice(pb, buffers, spms, ConvertMixerControl(pb.mixer_control),
m_coeffs_available ? m_coeffs : NULL);
// Forward the buffers
for (u32 i = 0; i < sizeof (buffers.ptrs) / sizeof (buffers.ptrs[0]); ++i)
@ -599,6 +659,7 @@ void CUCode_AX::HandleMail(u32 mail)
if (next_is_cmdlist)
{
CopyCmdList(mail, cmdlist_size);
StartWorking();
NotifyAXThread();
}
else if (m_UploadSetupInProgress)
@ -667,6 +728,12 @@ void CUCode_AX::Update(int cycles)
m_rMailHandler.PushMail(DSP_RESUME);
DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
}
else if (m_work_available)
{
HandleCommandList();
m_cmdlist_size = 0;
SignalWorkEnd();
}
}
void CUCode_AX::DoAXState(PointerWrap& p)

View File

@ -27,7 +27,7 @@
#define _UCODE_AX_H
#include "UCodes.h"
#include "UCode_AX_Structs.h"
#include "UCode_AXStructs.h"
// 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
@ -102,11 +102,16 @@ protected:
int m_samples_auxB_right[32 * 5];
int m_samples_auxB_surround[32 * 5];
// This flag is set if there is anything to process.
bool m_work_available;
// 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;
bool m_run_on_thread;
// Sync objects
std::mutex m_processing;
std::condition_variable m_cmdlist_cv;
@ -114,6 +119,14 @@ protected:
std::thread m_axthread;
// Table of coefficients for polyphase sample rate conversion.
// The coefficients aren't always available (they are part of the DSP DROM)
// so we also need to know if they are valid or not.
bool m_coeffs_available;
s16 m_coeffs[0x800];
void LoadResamplingCoefficients();
// Copy a command list from memory to our temp buffer
void CopyCmdList(u32 addr, u16 size);
@ -122,13 +135,21 @@ protected:
// versions of AX.
AXMixControl ConvertMixerControl(u32 mixer_control);
// Send a notification to the AX thread to tell him a new cmdlist addr is
// Apply updates to a PB. Generic, used in AX GC and AX Wii.
void ApplyUpdatesForMs(int curr_ms, u16* pb, u16* num_updates, u16* updates);
// Signal that we should start handling a command list. Dispatches to the
// AX thread if using a thread, else just sets a boolean flag.
void StartWorking();
// Send a notification to the AX thread to tell it a new cmdlist addr is
// available for processing.
void NotifyAXThread();
void AXThread();
virtual void HandleCommandList();
void SignalWorkEnd();
void SetupProcessing(u32 init_addr);
void DownloadAndMixWithVolume(u32 addr, u16 vol_main, u16 vol_auxa, u16 vol_auxb);

View File

@ -161,15 +161,15 @@ struct PBDpopWii
struct PBDpopWM
{
s16 aMain0;
s16 aMain1;
s16 aMain2;
s16 aMain3;
s16 main0;
s16 main1;
s16 main2;
s16 main3;
s16 aAux0;
s16 aAux1;
s16 aAux2;
s16 aAux3;
s16 aux0;
s16 aux1;
s16 aux2;
s16 aux3;
};
struct PBVolumeEnvelope
@ -211,13 +211,13 @@ struct PBSampleRateConverter
u16 ratio_hi; // integer part of sampling ratio
u16 ratio_lo; // fraction part of sampling ratio
u16 cur_addr_frac;
u16 last_samples[4];
s16 last_samples[4];
};
struct PBSampleRateConverterWM
{
u16 currentAddressFrac;
u16 last_samples[4];
u16 cur_addr_frac;
s16 last_samples[4];
};
struct PBADPCMLoopInfo
@ -230,7 +230,7 @@ struct PBADPCMLoopInfo
struct PBLowPassFilter
{
u16 enabled;
u16 yn1;
s16 yn1;
u16 a0;
u16 b0;
};
@ -324,52 +324,6 @@ struct AXPBWii
u16 pad[12]; // align us, captain! (32B)
};
// Seems like nintendo used an early version of AXWii and forgot to remove the update functionality ;p
struct PBUpdatesWiiSports
{
u16 num_updates[3];
u16 data_hi;
u16 data_lo;
};
struct AXPBWiiSports
{
u16 next_pb_hi;
u16 next_pb_lo;
u16 this_pb_hi;
u16 this_pb_lo;
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 running; // 1=RUN 0=STOP
u16 is_stream; // 1 = stream, 0 = one shot
PBMixerWii mixer;
PBInitialTimeDelay initial_time_delay;
PBUpdatesWiiSports updates;
PBDpopWii dpop;
PBVolumeEnvelope vol_env;
PBAudioAddr audio_addr;
PBADPCMInfo adpcm;
PBSampleRateConverter src;
PBADPCMLoopInfo adpcm_loop_info;
PBLowPassFilter lpf;
PBBiquadFilter biquad;
// WIIMOTE :D
u16 remote;
u16 remote_mixer_control;
PBMixerWM remote_mixer;
PBDpopWM remote_dpop;
PBSampleRateConverterWM remote_src;
PBInfImpulseResponseWM remote_iir;
u16 pad[7]; // align us, captain! (32B)
};
// TODO: All these enums have changed a lot for wii
enum {
AUDIOFORMAT_ADPCM = 0,

View File

@ -21,247 +21,678 @@
#include "Mixer.h"
#include "UCodes.h"
#include "UCode_AXWii_Structs.h"
#include "UCode_AX.h" // for some functions in CUCode_AX
#include "UCode_AXStructs.h"
#include "UCode_AXWii.h"
#include "UCode_AXWii_Voice.h"
#define AX_WII
#include "UCode_AX_Voice.h"
CUCode_AXWii::CUCode_AXWii(DSPHLE *dsp_hle, u32 l_CRC)
: IUCode(dsp_hle, l_CRC)
, m_addressPBs(0xFFFFFFFF)
: CUCode_AX(dsp_hle, l_CRC),
m_last_main_volume(0x8000)
{
// we got loaded
m_rMailHandler.PushMail(DSP_INIT);
for (int i = 0; i < 3; ++i)
m_last_aux_volumes[i] = 0x8000;
WARN_LOG(DSPHLE, "Instantiating CUCode_AXWii");
templbuffer = new int[1024 * 1024];
temprbuffer = new int[1024 * 1024];
wiisportsHack = m_CRC == 0xfa450138;
m_old_axwii = (l_CRC == 0xfa450138);
}
CUCode_AXWii::~CUCode_AXWii()
{
m_rMailHandler.Clear();
delete [] templbuffer;
delete [] temprbuffer;
}
void CUCode_AXWii::HandleMail(u32 _uMail)
void CUCode_AXWii::HandleCommandList()
{
if (m_UploadSetupInProgress)
// Temp variables for addresses computation
u16 addr_hi, addr_lo;
u16 addr2_hi, addr2_lo;
u16 volume;
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)
{
PrepareBootUCode(_uMail);
return;
}
else if ((_uMail & 0xFFFF0000) == MAIL_AX_ALIST)
{
// We are expected to get a new CmdBlock
DEBUG_LOG(DSPHLE, "GetNextCmdBlock (%ibytes)", (u16)_uMail);
}
else switch(_uMail)
{
case 0xCDD10000: // Action 0 - AX_ResumeTask()
m_rMailHandler.PushMail(DSP_RESUME);
break;
u16 cmd = m_cmdlist[curr_idx++];
case 0xCDD10001: // Action 1 - new ucode upload
DEBUG_LOG(DSPHLE,"DSP IROM - New Ucode!");
// TODO find a better way to protect from HLEMixer?
soundStream->GetMixer()->SetHLEReady(false);
m_UploadSetupInProgress = true;
break;
if (m_old_axwii)
{
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 0xCDD10002: // Action 2 - IROM_Reset(); ( WII: De Blob, Cursed Mountain,...)
DEBUG_LOG(DSPHLE,"DSP IROM - Reset!");
m_DSPHLE->SetUCode(UCODE_ROM);
return;
case CMD_SETUP_OLD:
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
SetupProcessing(HILO_TO_32(addr));
break;
case 0xCDD10003: // Action 3 - AX_GetNextCmdBlock()
break;
case CMD_ADD_TO_LR_OLD:
case CMD_SUB_TO_LR_OLD:
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
AddToLR(HILO_TO_32(addr), cmd == CMD_SUB_TO_LR_OLD);
break;
default:
DEBUG_LOG(DSPHLE, " >>>> u32 MAIL : AXTask Mail (%08x)", _uMail);
AXTask(_uMail);
break;
}
}
case CMD_ADD_SUB_TO_LR_OLD:
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
AddSubToLR(HILO_TO_32(addr));
break;
void CUCode_AXWii::MixAdd(short* _pBuffer, int _iSize)
{
AXPBWii PB;
case CMD_PB_ADDR_OLD:
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
pb_addr = HILO_TO_32(addr);
break;
if (_iSize > 1024 * 1024)
_iSize = 1024 * 1024;
case CMD_PROCESS_OLD:
ProcessPBList(pb_addr);
break;
memset(templbuffer, 0, _iSize * sizeof(int));
memset(temprbuffer, 0, _iSize * sizeof(int));
case CMD_MIX_AUXA_OLD:
case CMD_MIX_AUXB_OLD:
case CMD_MIX_AUXC_OLD:
volume = m_cmdlist[curr_idx++];
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_OLD, HILO_TO_32(addr), HILO_TO_32(addr2), volume);
break;
u32 blockAddr = m_addressPBs;
if (!blockAddr)
return;
case CMD_UPL_AUXA_MIX_LRSC_OLD:
case CMD_UPL_AUXB_MIX_LRSC_OLD:
{
volume = m_cmdlist[curr_idx++];
u32 addresses[6] = {
(u32)(m_cmdlist[curr_idx + 0] << 16) | m_cmdlist[curr_idx + 1],
(u32)(m_cmdlist[curr_idx + 2] << 16) | m_cmdlist[curr_idx + 3],
(u32)(m_cmdlist[curr_idx + 4] << 16) | m_cmdlist[curr_idx + 5],
(u32)(m_cmdlist[curr_idx + 6] << 16) | m_cmdlist[curr_idx + 7],
(u32)(m_cmdlist[curr_idx + 8] << 16) | m_cmdlist[curr_idx + 9],
(u32)(m_cmdlist[curr_idx + 10] << 16) | m_cmdlist[curr_idx + 11],
};
curr_idx += 12;
UploadAUXMixLRSC(cmd == CMD_UPL_AUXB_MIX_LRSC_OLD, addresses, volume);
break;
}
for (int i = 0; i < NUMBER_OF_PBS; i++)
{
if (!ReadPB(blockAddr, PB))
break;
// TODO(delroth): figure this one out, it's used by almost every
// game I've tested so far.
case CMD_UNK_0B_OLD: curr_idx += 4; break;
if (wiisportsHack)
MixAddVoice(*(AXPBWiiSports*)&PB, templbuffer, temprbuffer, _iSize);
case CMD_OUTPUT_OLD:
case CMD_OUTPUT_DPL2_OLD:
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
addr2_hi = m_cmdlist[curr_idx++];
addr2_lo = m_cmdlist[curr_idx++];
OutputSamples(HILO_TO_32(addr2), HILO_TO_32(addr), 0x8000,
cmd == CMD_OUTPUT_DPL2_OLD);
break;
case CMD_WM_OUTPUT_OLD:
{
u32 addresses[4] = {
(u32)(m_cmdlist[curr_idx + 0] << 16) | m_cmdlist[curr_idx + 1],
(u32)(m_cmdlist[curr_idx + 2] << 16) | m_cmdlist[curr_idx + 3],
(u32)(m_cmdlist[curr_idx + 4] << 16) | m_cmdlist[curr_idx + 5],
(u32)(m_cmdlist[curr_idx + 6] << 16) | m_cmdlist[curr_idx + 7],
};
curr_idx += 8;
OutputWMSamples(addresses);
break;
}
case CMD_END_OLD:
end = true;
break;
}
}
else
MixAddVoice(PB, templbuffer, temprbuffer, _iSize);
if (!WritePB(blockAddr, PB))
break;
// next PB, or done
blockAddr = (PB.next_pb_hi << 16) | PB.next_pb_lo;
if (!blockAddr)
break;
}
// We write the sound to _pBuffer
if (_pBuffer)
{
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;
else if (left > 32767) left = 32767;
if (right < -32767) right = -32767;
else if (right > 32767) right = 32767;
*_pBuffer++ = left;
*_pBuffer++ = right;
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_ADD_TO_LR:
case CMD_SUB_TO_LR:
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
AddToLR(HILO_TO_32(addr), cmd == CMD_SUB_TO_LR);
break;
case CMD_ADD_SUB_TO_LR:
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
AddSubToLR(HILO_TO_32(addr));
break;
case CMD_PROCESS:
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
ProcessPBList(HILO_TO_32(addr));
break;
case CMD_MIX_AUXA:
case CMD_MIX_AUXB:
case CMD_MIX_AUXC:
volume = m_cmdlist[curr_idx++];
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), volume);
break;
case CMD_UPL_AUXA_MIX_LRSC:
case CMD_UPL_AUXB_MIX_LRSC:
{
volume = m_cmdlist[curr_idx++];
u32 addresses[6] = {
(u32)(m_cmdlist[curr_idx + 0] << 16) | m_cmdlist[curr_idx + 1],
(u32)(m_cmdlist[curr_idx + 2] << 16) | m_cmdlist[curr_idx + 3],
(u32)(m_cmdlist[curr_idx + 4] << 16) | m_cmdlist[curr_idx + 5],
(u32)(m_cmdlist[curr_idx + 6] << 16) | m_cmdlist[curr_idx + 7],
(u32)(m_cmdlist[curr_idx + 8] << 16) | m_cmdlist[curr_idx + 9],
(u32)(m_cmdlist[curr_idx + 10] << 16) | m_cmdlist[curr_idx + 11],
};
curr_idx += 12;
UploadAUXMixLRSC(cmd == CMD_UPL_AUXB_MIX_LRSC, addresses, volume);
break;
}
// TODO(delroth): figure this one out, it's used by almost every
// game I've tested so far.
case CMD_UNK_0A: curr_idx += 4; break;
case CMD_OUTPUT:
case CMD_OUTPUT_DPL2:
volume = m_cmdlist[curr_idx++];
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
addr2_hi = m_cmdlist[curr_idx++];
addr2_lo = m_cmdlist[curr_idx++];
OutputSamples(HILO_TO_32(addr2), HILO_TO_32(addr), volume,
cmd == CMD_OUTPUT_DPL2);
break;
case CMD_WM_OUTPUT:
{
u32 addresses[4] = {
(u32)(m_cmdlist[curr_idx + 0] << 16) | m_cmdlist[curr_idx + 1],
(u32)(m_cmdlist[curr_idx + 2] << 16) | m_cmdlist[curr_idx + 3],
(u32)(m_cmdlist[curr_idx + 4] << 16) | m_cmdlist[curr_idx + 5],
(u32)(m_cmdlist[curr_idx + 6] << 16) | m_cmdlist[curr_idx + 7],
};
curr_idx += 8;
OutputWMSamples(addresses);
break;
}
case CMD_END:
end = true;
break;
}
}
}
}
void CUCode_AXWii::Update(int cycles)
void CUCode_AXWii::SetupProcessing(u32 init_addr)
{
if (NeedsResumeMail())
// TODO: should be easily factorizable with AX
s16 init_data[60];
for (u32 i = 0; i < 60; ++i)
init_data[i] = HLEMemory_Read_U16(init_addr + 2 * i);
// List of all buffers we have to initialize
struct {
int* ptr;
u32 samples;
} buffers[] = {
{ m_samples_left, 32 },
{ m_samples_right, 32 },
{ m_samples_surround, 32 },
{ m_samples_auxA_left, 32 },
{ m_samples_auxA_right, 32 },
{ m_samples_auxA_surround, 32 },
{ m_samples_auxB_left, 32 },
{ m_samples_auxB_right, 32 },
{ m_samples_auxB_surround, 32 },
{ m_samples_auxC_left, 32 },
{ m_samples_auxC_right, 32 },
{ m_samples_auxC_surround, 32 },
{ m_samples_wm0, 6 },
{ m_samples_aux0, 6 },
{ m_samples_wm1, 6 },
{ m_samples_aux1, 6 },
{ m_samples_wm2, 6 },
{ m_samples_aux2, 6 },
{ m_samples_wm3, 6 },
{ m_samples_aux3, 6 }
};
u32 init_idx = 0;
for (u32 i = 0; i < sizeof (buffers) / sizeof (buffers[0]); ++i)
{
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);
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].ptr, 0, 3 * buffers[i].samples * sizeof (int));
else
{
for (u32 j = 0; j < 3 * buffers[i].samples; ++j)
{
buffers[i].ptr[j] = init_val;
init_val += delta;
}
}
}
}
// 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_AXWii::AXTask(u32& _uMail)
void CUCode_AXWii::AddToLR(u32 val_addr, bool neg)
{
u32 uAddress = _uMail;
//u32 Addr__AXStudio;
//u32 Addr__AXOutSBuffer;
bool bExecuteList = true;
/*
for (int i=0;i<64;i++) {
NOTICE_LOG(DSPHLE,"%x - %08x",uAddress+(i*4),HLEMemory_Read_U32(uAddress+(i*4)));
}
*/
while (bExecuteList)
int* ptr = (int*)HLEMemory_Get_Pointer(val_addr);
for (int i = 0; i < 32 * 3; ++i)
{
u16 iCommand = HLEMemory_Read_U16(uAddress);
uAddress += 2;
//NOTICE_LOG(DSPHLE,"AXWII - AXLIST Command %X",iCommand);
int val = (int)Common::swap32(*ptr++);
if (neg)
val = -val;
switch (iCommand)
{
case 0x0000:
//Addr__AXStudio = HLEMemory_Read_U32(uAddress);
uAddress += 4;
break;
m_samples_left[i] += val;
m_samples_right[i] += val;
}
}
case 0x0001:
uAddress += 4;
break;
void CUCode_AXWii::AddSubToLR(u32 val_addr)
{
int* ptr = (int*)HLEMemory_Get_Pointer(val_addr);
for (int i = 0; i < 32 * 3; ++i)
{
int val = (int)Common::swap32(*ptr++);
m_samples_left[i] += val;
}
for (int i = 0; i < 32 * 3; ++i)
{
int val = (int)Common::swap32(*ptr++);
m_samples_right[i] -= val;
}
}
case 0x0003:
uAddress += 4;
break;
AXMixControl CUCode_AXWii::ConvertMixerControl(u32 mixer_control)
{
u32 ret = 0;
case 0x0004:
// PBs are here now
m_addressPBs = HLEMemory_Read_U32(uAddress);
if (soundStream)
soundStream->GetMixer()->SetHLEReady(true);
// soundStream->Update();
uAddress += 4;
break;
if (mixer_control & 0x00000001) ret |= MIX_L;
if (mixer_control & 0x00000002) ret |= MIX_R;
if (mixer_control & 0x00000004) ret |= MIX_L_RAMP | MIX_R_RAMP;
if (mixer_control & 0x00000008) ret |= MIX_S;
if (mixer_control & 0x00000010) ret |= MIX_S_RAMP;
if (mixer_control & 0x00010000) ret |= MIX_AUXA_L;
if (mixer_control & 0x00020000) ret |= MIX_AUXA_R;
if (mixer_control & 0x00040000) ret |= MIX_AUXA_L_RAMP | MIX_AUXA_R_RAMP;
if (mixer_control & 0x00080000) ret |= MIX_AUXA_S;
if (mixer_control & 0x00100000) ret |= MIX_AUXA_S_RAMP;
if (mixer_control & 0x00200000) ret |= MIX_AUXB_L;
if (mixer_control & 0x00400000) ret |= MIX_AUXB_R;
if (mixer_control & 0x00800000) ret |= MIX_AUXB_L_RAMP | MIX_AUXB_R_RAMP;
if (mixer_control & 0x01000000) ret |= MIX_AUXB_S;
if (mixer_control & 0x02000000) ret |= MIX_AUXB_S_RAMP;
if (mixer_control & 0x04000000) ret |= MIX_AUXC_L;
if (mixer_control & 0x08000000) ret |= MIX_AUXC_R;
if (mixer_control & 0x10000000) ret |= MIX_AUXC_L_RAMP | MIX_AUXC_R_RAMP;
if (mixer_control & 0x20000000) ret |= MIX_AUXC_S;
if (mixer_control & 0x40000000) ret |= MIX_AUXC_S_RAMP;
case 0x0005:
if (!wiisportsHack)
uAddress += 10;
break;
return (AXMixControl)ret;
}
case 0x0006:
uAddress += 10;
break;
void CUCode_AXWii::GenerateVolumeRamp(u16* output, u16 vol1, u16 vol2, size_t nvals)
{
float curr = vol1;
for (size_t i = 0; i < nvals; ++i)
{
curr += (vol2 - vol1) / (float)nvals;
output[i] = curr;
}
}
case 0x0007: // AXLIST_SBUFFER
//Addr__AXOutSBuffer = HLEMemory_Read_U32(uAddress);
uAddress += 10;
break;
bool CUCode_AXWii::ExtractUpdatesFields(AXPBWii& pb, u16* num_updates, u16* updates,
u32* updates_addr)
{
u16* pb_mem = (u16*)&pb;
case 0x0008:
uAddress += 26;
break;
if (!m_old_axwii)
return false;
case 0x000a:
uAddress += wiisportsHack ? 4 : 8; // AXLIST_COMPRESSORTABLE
break;
// Copy the num_updates field.
memcpy(num_updates, pb_mem + 41, 6);
case 0x000b:
uAddress += wiisportsHack ? 2 : 10;
break;
// Get the address of the updates data
u16 addr_hi = pb_mem[44];
u16 addr_lo = pb_mem[45];
u32 addr = HILO_TO_32(addr);
u16* ptr = (u16*)HLEMemory_Get_Pointer(addr);
case 0x000c:
uAddress += wiisportsHack ? 8 : 10;
break;
*updates_addr = addr;
case 0x000d:
uAddress += 16;
break;
// Copy the updates data and change the offset to match a PB without
// updates data.
u32 updates_count = num_updates[0] + num_updates[1] + num_updates[2];
for (u32 i = 0; i < updates_count; ++i)
{
u16 update_off = Common::swap16(ptr[2 * i]);
u16 update_val = Common::swap16(ptr[2 * i + 1]);
case 0x000e:
if (wiisportsHack)
uAddress += 16;
else
bExecuteList = false;
break;
if (update_off > 45)
update_off -= 5;
case 0x000f: // only for Wii Sports uCode
bExecuteList = false;
break;
default:
INFO_LOG(DSPHLE,"DSPHLE - AXwii - AXLIST - Unknown Command: %x",iCommand);
// unknown command so stop the execution of this TaskList
bExecuteList = false;
break;
}
updates[2 * i] = update_off;
updates[2 * i + 1] = update_val;
}
m_rMailHandler.PushMail(DSP_YIELD); //its here in case there is a CMD fuckup
// Remove the updates data from the PB
memmove(pb_mem + 41, pb_mem + 46, sizeof (pb) - 2 * 46);
return true;
}
void CUCode_AXWii::ReinjectUpdatesFields(AXPBWii& pb, u16* num_updates, u32 updates_addr)
{
u16* pb_mem = (u16*)&pb;
// Make some space
memmove(pb_mem + 46, pb_mem + 41, sizeof (pb) - 2 * 46);
// Reinsert previous values
pb_mem[41] = num_updates[0];
pb_mem[42] = num_updates[1];
pb_mem[43] = num_updates[2];
pb_mem[44] = updates_addr >> 16;
pb_mem[45] = updates_addr & 0xFFFF;
}
void CUCode_AXWii::ProcessPBList(u32 pb_addr)
{
AXPBWii 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,
m_samples_auxC_left,
m_samples_auxC_right,
m_samples_auxC_surround,
m_samples_wm0,
m_samples_aux0,
m_samples_wm1,
m_samples_aux1,
m_samples_wm2,
m_samples_aux2,
m_samples_wm3,
m_samples_aux3
}};
if (!ReadPB(pb_addr, pb))
break;
u16 num_updates[3];
u16 updates[1024];
u32 updates_addr;
if (ExtractUpdatesFields(pb, num_updates, updates, &updates_addr))
{
for (int curr_ms = 0; curr_ms < 3; ++curr_ms)
{
ApplyUpdatesForMs(curr_ms, (u16*)&pb, num_updates, updates);
ProcessVoice(pb, buffers, 32,
ConvertMixerControl(HILO_TO_32(pb.mixer_control)),
m_coeffs_available ? m_coeffs : NULL);
// Forward the buffers
for (u32 i = 0; i < sizeof (buffers.ptrs) / sizeof (buffers.ptrs[0]); ++i)
buffers.ptrs[i] += 32;
}
ReinjectUpdatesFields(pb, num_updates, updates_addr);
}
else
{
ProcessVoice(pb, buffers, 96,
ConvertMixerControl(HILO_TO_32(pb.mixer_control)),
m_coeffs_available ? m_coeffs : NULL);
}
WritePB(pb_addr, pb);
pb_addr = HILO_TO_32(pb.next_pb);
}
}
void CUCode_AXWii::MixAUXSamples(int aux_id, u32 write_addr, u32 read_addr, u16 volume)
{
u16 volume_ramp[96];
GenerateVolumeRamp(volume_ramp, m_last_aux_volumes[aux_id], volume, 96);
m_last_aux_volumes[aux_id] = volume;
int* buffers[3] = { 0 };
int* main_buffers[3] = {
m_samples_left,
m_samples_right,
m_samples_surround
};
switch (aux_id)
{
case 0:
buffers[0] = m_samples_auxA_left;
buffers[1] = m_samples_auxA_right;
buffers[2] = m_samples_auxA_surround;
break;
case 1:
buffers[0] = m_samples_auxB_left;
buffers[1] = m_samples_auxB_right;
buffers[2] = m_samples_auxB_surround;
break;
case 2:
buffers[0] = m_samples_auxC_left;
buffers[1] = m_samples_auxC_right;
buffers[2] = m_samples_auxC_surround;
break;
}
// Send the content of AUX buffers to the CPU
if (write_addr)
{
int* ptr = (int*)HLEMemory_Get_Pointer(write_addr);
for (u32 i = 0; i < 3; ++i)
for (u32 j = 0; j < 3 * 32; ++j)
*ptr++ = Common::swap32(buffers[i][j]);
}
// Then read the buffers from the CPU and add to our main buffers.
int* ptr = (int*)HLEMemory_Get_Pointer(read_addr);
for (u32 i = 0; i < 3; ++i)
for (u32 j = 0; j < 3 * 32; ++j)
{
s64 sample = (s64)(s32)Common::swap32(*ptr++);
sample *= volume_ramp[j];
main_buffers[i][j] += (s32)(sample >> 15);
}
}
void CUCode_AXWii::UploadAUXMixLRSC(int aux_id, u32* addresses, u16 volume)
{
int* aux_left = aux_id ? m_samples_auxB_left : m_samples_auxA_left;
int* aux_right = aux_id ? m_samples_auxB_right : m_samples_auxA_right;
int* aux_surround = aux_id ? m_samples_auxB_surround : m_samples_auxA_surround;
int* auxc_buffer = aux_id ? m_samples_auxC_surround : m_samples_auxC_right;
int* upload_ptr = (int*)HLEMemory_Get_Pointer(addresses[0]);
for (u32 i = 0; i < 96; ++i)
*upload_ptr++ = Common::swap32(aux_left[i]);
for (u32 i = 0; i < 96; ++i)
*upload_ptr++ = Common::swap32(aux_right[i]);
for (u32 i = 0; i < 96; ++i)
*upload_ptr++ = Common::swap32(aux_surround[i]);
upload_ptr = (int*)HLEMemory_Get_Pointer(addresses[1]);
for (u32 i = 0; i < 96; ++i)
*upload_ptr++ = Common::swap32(auxc_buffer[i]);
u16 volume_ramp[96];
GenerateVolumeRamp(volume_ramp, m_last_aux_volumes[aux_id], volume, 96);
m_last_aux_volumes[aux_id] = volume;
int* mix_dest[4] = {
m_samples_left,
m_samples_right,
m_samples_surround,
m_samples_auxC_left
};
for (u32 mix_i = 0; mix_i < 4; ++mix_i)
{
int* dl_ptr = (int*)HLEMemory_Get_Pointer(addresses[2 + mix_i]);
for (u32 i = 0; i < 96; ++i)
aux_left[i] = Common::swap32(dl_ptr[i]);
for (u32 i = 0; i < 96; ++i)
{
s64 sample = (s64)(s32)aux_left[i];
sample *= volume_ramp[i];
mix_dest[mix_i][i] += (s32)(sample >> 15);
}
}
}
void CUCode_AXWii::OutputSamples(u32 lr_addr, u32 surround_addr, u16 volume,
bool upload_auxc)
{
u16 volume_ramp[96];
GenerateVolumeRamp(volume_ramp, m_last_main_volume, volume, 96);
m_last_main_volume = volume;
int upload_buffer[3 * 32] = { 0 };
for (u32 i = 0; i < 3 * 32; ++i)
upload_buffer[i] = Common::swap32(m_samples_surround[i]);
memcpy(HLEMemory_Get_Pointer(surround_addr), upload_buffer, sizeof (upload_buffer));
if (upload_auxc)
{
surround_addr += sizeof (upload_buffer);
for (u32 i = 0; i < 3 * 32; ++i)
upload_buffer[i] = Common::swap32(m_samples_auxC_left[i]);
memcpy(HLEMemory_Get_Pointer(surround_addr), upload_buffer, sizeof (upload_buffer));
}
short buffer[3 * 32 * 2];
// Clamp internal buffers to 16 bits.
for (u32 i = 0; i < 3 * 32; ++i)
{
int left = m_samples_left[i];
int right = m_samples_right[i];
// Apply global volume. Cast to s64 to avoid overflow.
left = ((s64)left * volume_ramp[i]) >> 15;
right = ((s64)right * volume_ramp[i]) >> 15;
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 < 3 * 32; ++i)
{
buffer[2 * i] = Common::swap16(m_samples_right[i]);
buffer[2 * i + 1] = Common::swap16(m_samples_left[i]);
}
memcpy(HLEMemory_Get_Pointer(lr_addr), buffer, sizeof (buffer));
// There should be a DSP_SYNC message sent here. However, it looks like not
// sending it does not cause any issue, and sending it actually causes some
// sounds to go at half speed. I have no idea why.
}
void CUCode_AXWii::OutputWMSamples(u32* addresses)
{
int* buffers[] = {
m_samples_wm0,
m_samples_wm1,
m_samples_wm2,
m_samples_wm3
};
for (u32 i = 0; i < 4; ++i)
{
int* in = buffers[i];
u16* out = (u16*)HLEMemory_Get_Pointer(addresses[i]);
for (u32 j = 0; j < 3 * 6; ++j)
{
int sample = in[j];
if (sample < -32767) sample = -32767;
if (sample > 32767) sample = 32767;
out[j] = Common::swap16((u16)sample);
}
}
}
void CUCode_AXWii::DoState(PointerWrap &p)
{
std::lock_guard<std::mutex> lk(m_csMix);
p.Do(m_addressPBs);
p.Do(wiisportsHack);
std::lock_guard<std::mutex> lk(m_processing);
DoStateShared(p);
DoAXState(p);
p.Do(m_samples_auxC_left);
p.Do(m_samples_auxC_right);
p.Do(m_samples_auxC_surround);
p.Do(m_samples_wm0);
p.Do(m_samples_wm1);
p.Do(m_samples_wm2);
p.Do(m_samples_wm3);
p.Do(m_samples_aux0);
p.Do(m_samples_aux1);
p.Do(m_samples_aux2);
p.Do(m_samples_aux3);
p.Do(m_last_main_volume);
p.Do(m_last_aux_volumes);
}

View File

@ -12,44 +12,116 @@
// 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_AXWII
#define _UCODE_AXWII
#ifndef _UCODE_AXWII_H
#define _UCODE_AXWII_H
#include "UCode_AXWii_Structs.h"
#include "UCode_AX.h"
#define NUMBER_OF_PBS 128
class CUCode_AXWii : public IUCode
class CUCode_AXWii : public CUCode_AX
{
public:
CUCode_AXWii(DSPHLE *dsp_hle, u32 _CRC);
virtual ~CUCode_AXWii();
void HandleMail(u32 _uMail);
void MixAdd(short* _pBuffer, int _iSize);
void Update(int cycles);
void DoState(PointerWrap &p);
virtual void DoState(PointerWrap &p);
protected:
// Additional AUX buffers
int m_samples_auxC_left[32 * 3];
int m_samples_auxC_right[32 * 3];
int m_samples_auxC_surround[32 * 3];
// Wiimote buffers
int m_samples_wm0[6 * 3];
int m_samples_aux0[6 * 3];
int m_samples_wm1[6 * 3];
int m_samples_aux1[6 * 3];
int m_samples_wm2[6 * 3];
int m_samples_aux2[6 * 3];
int m_samples_wm3[6 * 3];
int m_samples_aux3[6 * 3];
// Are we implementing an old version of AXWii which still has updates?
bool m_old_axwii;
// Last volume values for MAIN and AUX. Used to generate volume ramps to
// interpolate nicely between old and new volume values.
u16 m_last_main_volume;
u16 m_last_aux_volumes[3];
// If needed, extract the updates related fields from a PB. We need to
// reinject them afterwards so that the correct PB typs is written to RAM.
bool ExtractUpdatesFields(AXPBWii& pb, u16* num_updates, u16* updates,
u32* updates_addr);
void ReinjectUpdatesFields(AXPBWii& pb, u16* num_updates, u32 updates_addr);
// 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);
// Generate a volume ramp from vol1 to vol2, interpolating n volume values.
// Uses floating point arithmetic, which isn't exactly what the UCode does,
// but this gives better precision and nicer code.
void GenerateVolumeRamp(u16* output, u16 vol1, u16 vol2, size_t nvals);
virtual void HandleCommandList();
void SetupProcessing(u32 init_addr);
void AddToLR(u32 val_addr, bool neg);
void AddSubToLR(u32 val_addr);
void ProcessPBList(u32 pb_addr);
void MixAUXSamples(int aux_id, u32 write_addr, u32 read_addr, u16 volume);
void UploadAUXMixLRSC(int aux_id, u32* addresses, u16 volume);
void OutputSamples(u32 lr_addr, u32 surround_addr, u16 volume,
bool upload_auxc);
void OutputWMSamples(u32* addresses); // 4 addresses
private:
enum
enum CmdType
{
MAIL_AX_ALIST = 0xBABE0000,
CMD_SETUP = 0x00,
CMD_ADD_TO_LR = 0x01,
CMD_SUB_TO_LR = 0x02,
CMD_ADD_SUB_TO_LR = 0x03,
CMD_PROCESS = 0x04,
CMD_MIX_AUXA = 0x05,
CMD_MIX_AUXB = 0x06,
CMD_MIX_AUXC = 0x07,
CMD_UPL_AUXA_MIX_LRSC = 0x08,
CMD_UPL_AUXB_MIX_LRSC = 0x09,
CMD_UNK_0A = 0x0A,
CMD_OUTPUT = 0x0B,
CMD_OUTPUT_DPL2 = 0x0C,
CMD_WM_OUTPUT = 0x0D,
CMD_END = 0x0E,
};
// PBs
u32 m_addressPBs;
bool wiisportsHack;
int *templbuffer;
int *temprbuffer;
// ax task message handler
bool AXTask(u32& _uMail);
void SendMail(u32 _uMail);
// A lot of these are similar to the new version, but there is an offset in
// the command ids due to the PB_ADDR command (which was removed from the
// new AXWii).
enum CmdTypeOld
{
CMD_SETUP_OLD = 0x00,
CMD_ADD_TO_LR_OLD = 0x01,
CMD_SUB_TO_LR_OLD = 0x02,
CMD_ADD_SUB_TO_LR_OLD = 0x03,
CMD_PB_ADDR_OLD = 0x04,
CMD_PROCESS_OLD = 0x05,
CMD_MIX_AUXA_OLD = 0x06,
CMD_MIX_AUXB_OLD = 0x07,
CMD_MIX_AUXC_OLD = 0x08,
CMD_UPL_AUXA_MIX_LRSC_OLD = 0x09,
CMD_UPL_AUXB_MIX_LRSC_OLD = 0x0a,
CMD_UNK_0B_OLD = 0x0B,
CMD_OUTPUT_OLD = 0x0C, // no volume!
CMD_OUTPUT_DPL2_OLD = 0x0D,
CMD_WM_OUTPUT_OLD = 0x0E,
CMD_END_OLD = 0x0F
};
};
#endif // _UCODE_AXWII

View File

@ -1,92 +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 SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _UCODE_AX_ADPCM_H
#define _UCODE_AX_ADPCM_H
#include "../../DSP.h"
static inline s16 ADPCM_Step(PBADPCMInfo &adpcm, u32& samplePos, u32 newSamplePos, u16 frac)
{
while (samplePos < newSamplePos)
{
if ((samplePos & 15) == 0)
{
adpcm.pred_scale = DSP::ReadARAM((samplePos & ~15) >> 1);
samplePos += 2;
newSamplePos += 2;
}
int scale = 1 << (adpcm.pred_scale & 0xF);
int coef_idx = (adpcm.pred_scale >> 4) & 7;
s32 coef1 = adpcm.coefs[coef_idx * 2 + 0];
s32 coef2 = adpcm.coefs[coef_idx * 2 + 1];
int temp = (samplePos & 1) ?
(DSP::ReadARAM(samplePos >> 1) & 0xF) :
(DSP::ReadARAM(samplePos >> 1) >> 4);
if (temp >= 8)
temp -= 16;
// 0x400 = 0.5 in 11-bit fixed point
int val = (scale * temp) + ((0x400 + coef1 * adpcm.yn1 + coef2 * adpcm.yn2) >> 11);
if (val > 0x7FFF)
val = 0x7FFF;
else if (val < -0x7FFF)
val = -0x7FFF;
adpcm.yn2 = adpcm.yn1;
adpcm.yn1 = val;
samplePos++;
}
return adpcm.yn1;
}
// TODO: WTF is going on here?!?
// Volume control (ramping)
static inline u16 ADPCM_Vol(u16 vol, u16 delta)
{
int x = vol;
if (delta && delta < 0x5000)
x += delta * 20 * 8; // unsure what the right step is
//x += 1 * 20 * 8;
else if (delta && delta > 0x5000)
//x -= (0x10000 - delta); // this is to small, it's often 1
x -= (0x10000 - delta) * 20 * 16; // if this was 20 * 8 the sounds in Fire Emblem and Paper Mario
// did not have time to go to zero before the were closed
//x -= 1 * 20 * 16;
// make lower limits
if (x < 0) x = 0;
//if (pb.mixer_control < 1000 && x < pb.mixer_control) x = pb.mixer_control; // does this make
// any sense?
// make upper limits
//if (mixer_control > 1000 && x > mixer_control) x = mixer_control; // maybe mixer_control also
// has a volume target?
//if (x >= 0x7fff) x = 0x7fff; // this seems a little high
//if (x >= 0x4e20) x = 0x4e20; // add a definitive limit at 20 000
if (x >= 0x8000) x = 0x8000; // clamp to 32768;
return x; // update volume
}
#endif // _UCODE_AX_ADPCM_H

View File

@ -1,365 +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 SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _UCODE_AX_STRUCTS_H
#define _UCODE_AX_STRUCTS_H
struct PBMixer
{
u16 left;
u16 left_delta;
u16 right;
u16 right_delta;
u16 unknown3[8];
u16 unknown4[6];
};
struct PBMixerWii
{
// volume mixing values in .15, 0x8000 = ca. 1.0
u16 left;
u16 left_delta;
u16 right;
u16 right_delta;
u16 auxA_left;
u16 auxA_left_delta;
u16 auxA_right;
u16 auxA_right_delta;
u16 auxB_left;
u16 auxB_left_delta;
u16 auxB_right;
u16 auxB_right_delta;
// Note: the following elements usage changes a little in DPL2 mode
// TODO: implement and comment it in the mixer
u16 auxC_left;
u16 auxC_left_delta;
u16 auxC_right;
u16 auxC_right_delta;
u16 surround;
u16 surround_delta;
u16 auxA_surround;
u16 auxA_surround_delta;
u16 auxB_surround;
u16 auxB_surround_delta;
u16 auxC_surround;
u16 auxC_surround_delta;
};
struct PBMixerWM
{
u16 main0;
u16 main0_delta;
u16 aux0;
u16 aux0_delta;
u16 main1;
u16 main1_delta;
u16 aux1;
u16 aux1_delta;
u16 main2;
u16 main2_delta;
u16 aux2;
u16 aux2_delta;
u16 main3;
u16 main3_delta;
u16 aux3;
u16 aux3_delta;
};
struct PBInitialTimeDelay
{
u16 on;
u16 addrMemHigh;
u16 addrMemLow;
u16 offsetLeft;
u16 offsetRight;
u16 targetLeft;
u16 targetRight;
};
// Update data - read these each 1ms subframe and use them!
// It seems that to provide higher time precisions for MIDI events, some games
// use this thing to update the parameter blocks per 1ms sub-block (a block is 5ms).
// Using this data should fix games that are missing MIDI notes.
struct PBUpdates
{
u16 num_updates[5];
u16 data_hi; // These point to main RAM. Not sure about the structure of the data.
u16 data_lo;
};
// The DSP stores the final sample values for each voice after every frame of processing.
// The values are then accumulated for all dropped voices, added to the next frame of audio,
// and ramped down on a per-sample basis to provide a gentle "roll off."
struct PBDpop
{
s16 unknown[9];
};
struct PBDpopWii
{
s16 left;
s16 auxA_left;
s16 auxB_left;
s16 auxC_left;
s16 right;
s16 auxA_right;
s16 auxB_right;
s16 auxC_right;
s16 surround;
s16 auxA_surround;
s16 auxB_surround;
s16 auxC_surround;
};
struct PBDpopWM
{
s16 aMain0;
s16 aMain1;
s16 aMain2;
s16 aMain3;
s16 aAux0;
s16 aAux1;
s16 aAux2;
s16 aAux3;
};
struct PBVolumeEnvelope
{
u16 cur_volume; // volume at start of frame
s16 cur_volume_delta; // signed per sample delta (96 samples per frame)
};
struct PBUnknown2
{
u16 unknown_reserved[3];
};
struct PBAudioAddr
{
u16 looping;
u16 sample_format;
u16 loop_addr_hi; // Start of loop (this will point to a shared "zero" buffer if one-shot mode is active)
u16 loop_addr_lo;
u16 end_addr_hi; // End of sample (and loop), inclusive
u16 end_addr_lo;
u16 cur_addr_hi;
u16 cur_addr_lo;
};
struct PBADPCMInfo
{
s16 coefs[16];
u16 gain;
u16 pred_scale;
s16 yn1;
s16 yn2;
};
struct PBSampleRateConverter
{
// ratio = (f32)ratio * 0x10000;
// valid range is 1/512 to 4.0000
u16 ratio_hi; // integer part of sampling ratio
u16 ratio_lo; // fraction part of sampling ratio
u16 cur_addr_frac;
u16 last_samples[4];
};
struct PBSampleRateConverterWM
{
u16 currentAddressFrac;
u16 last_samples[4];
};
struct PBADPCMLoopInfo
{
u16 pred_scale;
u16 yn1;
u16 yn2;
};
struct AXPB
{
u16 next_pb_hi;
u16 next_pb_lo;
u16 this_pb_hi;
u16 this_pb_lo;
u16 src_type; // Type of sample rate converter (none, ?, linear)
u16 coef_select;
u16 mixer_control;
u16 running; // 1=RUN 0=STOP
u16 is_stream; // 1 = stream, 0 = one shot
PBMixer mixer;
PBInitialTimeDelay initial_time_delay;
PBUpdates updates;
PBDpop dpop;
PBVolumeEnvelope vol_env;
PBUnknown2 unknown3;
PBAudioAddr audio_addr;
PBADPCMInfo adpcm;
PBSampleRateConverter src;
PBADPCMLoopInfo adpcm_loop_info;
u16 unknown_maybe_padding[3];
};
struct PBLowPassFilter
{
u16 enabled;
u16 yn1;
u16 a0;
u16 b0;
};
struct PBBiquadFilter
{
u16 on; // on = 2, off = 0
u16 xn1; // History data
u16 xn2;
u16 yn1;
u16 yn2;
u16 b0; // Filter coefficients
u16 b1;
u16 b2;
u16 a1;
u16 a2;
};
union PBInfImpulseResponseWM
{
PBLowPassFilter lpf;
PBBiquadFilter biquad;
};
struct AXPBWii
{
u16 next_pb_hi;
u16 next_pb_lo;
u16 this_pb_hi;
u16 this_pb_lo;
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 running; // 1=RUN 0=STOP
u16 is_stream; // 1 = stream, 0 = one shot
PBMixerWii mixer;
PBInitialTimeDelay initial_time_delay;
PBDpopWii dpop;
PBVolumeEnvelope vol_env;
PBAudioAddr audio_addr;
PBADPCMInfo adpcm;
PBSampleRateConverter src;
PBADPCMLoopInfo adpcm_loop_info;
PBLowPassFilter lpf;
PBBiquadFilter biquad;
// WIIMOTE :D
u16 remote;
u16 remote_mixer_control;
PBMixerWM remote_mixer;
PBDpopWM remote_dpop;
PBSampleRateConverterWM remote_src;
PBInfImpulseResponseWM remote_iir;
u16 pad[12]; // align us, captain! (32B)
};
// Seems like nintendo used an early version of AXWii and forgot to remove the update functionality ;p
struct PBUpdatesWiiSports
{
u16 num_updates[3];
u16 data_hi;
u16 data_lo;
};
struct AXPBWiiSports
{
u16 next_pb_hi;
u16 next_pb_lo;
u16 this_pb_hi;
u16 this_pb_lo;
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 running; // 1=RUN 0=STOP
u16 is_stream; // 1 = stream, 0 = one shot
PBMixerWii mixer;
PBInitialTimeDelay initial_time_delay;
PBUpdatesWiiSports updates;
PBDpopWii dpop;
PBVolumeEnvelope vol_env;
PBAudioAddr audio_addr;
PBADPCMInfo adpcm;
PBSampleRateConverter src;
PBADPCMLoopInfo adpcm_loop_info;
PBLowPassFilter lpf;
PBBiquadFilter biquad;
// WIIMOTE :D
u16 remote;
u16 remote_mixer_control;
PBMixerWM remote_mixer;
PBDpopWM remote_dpop;
PBSampleRateConverterWM remote_src;
PBInfImpulseResponseWM remote_iir;
u16 pad[7]; // align us, captain! (32B)
};
// TODO: All these enums have changed a lot for wii
enum {
AUDIOFORMAT_ADPCM = 0,
AUDIOFORMAT_PCM8 = 0x19,
AUDIOFORMAT_PCM16 = 0xA,
};
enum {
SRCTYPE_LINEAR = 1,
SRCTYPE_NEAREST = 2,
MIXCONTROL_RAMPING = 8,
};
// Both may be used at once
enum {
FILTER_LOWPASS = 1,
FILTER_BIQUAD = 2,
};
#endif // _UCODE_AX_STRUCTS_H

View File

@ -1,271 +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 SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _UCODE_AXWII_VOICE_H
#define _UCODE_AXWII_VOICE_H
#include "UCodes.h"
#include "UCode_AXWii_ADPCM.h"
#include "UCode_AX.h"
#include "Mixer.h"
#include "../../AudioInterface.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;
}
//////////////////////////////////////////////////////////////////////////
// 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,
int *templbuffer, int *temprbuffer,
int _iSize)
{
if (pb.running)
{
const u32 ratio = (u32)(((pb.src.ratio_hi << 16) + pb.src.ratio_lo)
* /*ratioFactor:*/((float)AudioInterface::GetAIDSampleRate() / (float)soundStream->GetMixer()->GetSampleRate()));
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;
u32 samplePos = (pb.audio_addr.cur_addr_hi << 16) | pb.audio_addr.cur_addr_lo;
u32 frac = pb.src.cur_addr_frac;
// =======================================================================================
// 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;
}
// =======================================================================================
// 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;
}
// 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 & MIXCONTROL_RAMPING)
{
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 left = sample * leftmix >> 8;
int right = sample * rightmix >> 8;
// adpcm has to walk from oldSamplePos to samplePos here
templbuffer[s] += left;
temprbuffer[s] += right;
// 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.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)
}
#endif

View File

@ -27,13 +27,24 @@
#endif
#include "Common.h"
#include "UCode_AX_Structs.h"
#include "UCode_AXStructs.h"
#include "../../DSP.h"
// I hate OSX.
#if defined(__APPLE__)
# include <tr1/functional>
using std::tr1::function;
#else
# include <functional>
using std::function;
#endif
#ifdef AX_GC
# define PB_TYPE AXPB
# define MAX_SAMPLES_PER_FRAME 32
#else
# define PB_TYPE AXPBWii
# define MAX_SAMPLES_PER_FRAME 96
#endif
// Put all of that in an anonymous namespace to avoid stupid compilers merging
@ -65,13 +76,22 @@ union AXBuffers
int* auxC_left;
int* auxC_right;
int* auxC_surround;
int* wm_main0;
int* wm_aux0;
int* wm_main1;
int* wm_aux1;
int* wm_main2;
int* wm_aux2;
int* wm_main3;
int* wm_aux3;
#endif
};
#ifdef AX_GC
int* ptrs[9];
#else
int* ptrs[12];
int* ptrs[20];
#endif
};
@ -127,6 +147,7 @@ void DumpPB(const PB_TYPE& pb)
static u32 acc_loop_addr, acc_end_addr;
static u32* acc_cur_addr;
static PB_TYPE* acc_pb;
static bool acc_end_reached;
// Sets up the simulated accelerator.
void AcceleratorSetup(PB_TYPE* pb, u32* cur_addr)
@ -135,6 +156,7 @@ void AcceleratorSetup(PB_TYPE* pb, u32* cur_addr)
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;
acc_end_reached = false;
}
// Reads a sample from the simulated accelerator. Also handles looping and
@ -144,6 +166,49 @@ u16 AcceleratorGetSample()
{
u16 ret;
// 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 & ~1) == (acc_end_addr & ~1))
{
// loop back to loop_addr.
*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;
#ifdef AX_WII
// One of the few meaningful differences between AXGC and AXWii:
// while AXGC handles non looping voices ending by having 0000
// samples at the loop address, AXWii has the 0000 samples
// internally in DRAM and use an internal pointer to it (loop addr
// does not contain 0000 samples on AXWii!).
acc_end_reached = true;
#endif
}
}
// See above for explanations about acc_end_reached.
if (acc_end_reached)
return 0;
switch (acc_pb->audio_addr.sample_format)
{
case 0x00: // ADPCM
@ -199,104 +264,159 @@ u16 AcceleratorGetSample()
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 & ~1) == (acc_end_addr & ~1))
{
// loop back to loop_addr.
*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.
void GetInputSamples(PB_TYPE& pb, s16* samples)
// Reads samples from the input callback, resamples them to <count> samples at
// the wanted sample rate (computed from the ratio, see below).
//
// If srctype is SRCTYPE_POLYPHASE, coefficients need to be provided as well
// (or the srctype will automatically be changed to LINEAR).
//
// Returns the current position after resampling (including fractional part).
//
// The input to output ratio is set in <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
//
// <curr_pos> is a 32b integer structured in the same way as the ratio: the
// upper 16 bits are the integer part of the current position in the input
// stream, and the lower 16 bits are the decimal part.
//
// We start getting samples not from sample 0, but 0.<curr_pos_frac>. This
// avoids discontinuties in the audio stream, especially with very low ratios
// which interpolate a lot of values between two "real" samples.
u32 ResampleAudio(function<s16(u32)> input_callback, s16* output, u32 count,
s16* last_samples, u32 curr_pos, u32 ratio, int srctype,
const s16* coeffs)
{
int read_samples_count = 0;
// TODO(delroth): find out why the polyphase resampling algorithm causes
// audio glitches in Wii games with non integral ratios.
// If DSP DROM coefficients are available, support polyphase resampling.
if (0) // if (coeffs && srctype == SRCTYPE_POLYPHASE)
{
s16 temp[4];
u32 idx = 0;
temp[idx++ & 3] = last_samples[0];
temp[idx++ & 3] = last_samples[1];
temp[idx++ & 3] = last_samples[2];
temp[idx++ & 3] = last_samples[3];
for (u32 i = 0; i < count; ++i)
{
curr_pos += ratio;
while (curr_pos >= 0x10000)
{
temp[idx++ & 3] = input_callback(read_samples_count++);
curr_pos -= 0x10000;
}
u16 curr_pos_frac = ((curr_pos & 0xFFFF) >> 9) << 2;
const s16* c = &coeffs[curr_pos_frac];
s64 t0 = temp[idx++ & 3];
s64 t1 = temp[idx++ & 3];
s64 t2 = temp[idx++ & 3];
s64 t3 = temp[idx++ & 3];
s64 samp = (t0 * c[0] + t1 * c[1] + t2 * c[2] + t3 * c[3]) >> 15;
output[i] = (s16)samp;
}
last_samples[3] = temp[--idx & 3];
last_samples[2] = temp[--idx & 3];
last_samples[1] = temp[--idx & 3];
last_samples[0] = temp[--idx & 3];
}
else if (srctype == SRCTYPE_LINEAR || srctype == SRCTYPE_POLYPHASE)
{
// This is the circular buffer containing samples to use for the
// interpolation. It is initialized with the values from the PB, and it
// will be stored back to the PB at the end.
s16 temp[4];
u32 idx = 0;
temp[idx++ & 3] = last_samples[0];
temp[idx++ & 3] = last_samples[1];
temp[idx++ & 3] = last_samples[2];
temp[idx++ & 3] = last_samples[3];
for (u32 i = 0; i < count; ++i)
{
curr_pos += ratio;
// While our current position is >= 1.0, push new samples to the
// circular buffer.
while (curr_pos >= 0x10000)
{
temp[idx++ & 3] = input_callback(read_samples_count++);
curr_pos -= 0x10000;
}
// Get our current fractional position, used to know how much of
// curr0 and how much of curr1 the output sample should be.
u16 curr_frac = curr_pos & 0xFFFF;
u16 inv_curr_frac = -curr_frac;
// Interpolate! If curr_frac is 0, we can simply take the last
// sample without any multiplying.
s16 sample;
if (curr_frac)
{
s32 s0 = temp[idx++ & 3];
s32 s1 = temp[idx++ & 3];
sample = ((s0 * inv_curr_frac) + (s1 * curr_frac)) >> 16;
idx += 2;
}
else
{
sample = temp[idx++ & 3];
idx += 3;
}
output[i] = sample;
}
// Update the four last_samples values.
last_samples[3] = temp[--idx & 3];
last_samples[2] = temp[--idx & 3];
last_samples[1] = temp[--idx & 3];
last_samples[0] = temp[--idx & 3];
}
else // SRCTYPE_NEAREST
{
// No sample rate conversion here: simply read samples from the
// accelerator to the output buffer.
for (u32 i = 0; i < count; ++i)
output[i] = input_callback(i);
memcpy(last_samples, output + count - 4, 4 * sizeof (u16));
}
return curr_pos;
}
// Read <count> input samples from ARAM, decoding and converting rate
// if required.
void GetInputSamples(PB_TYPE& pb, s16* samples, u16 count, const s16* coeffs)
{
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;
// These are the two samples between which we interpolate. The initial
// values are stored in the PB, and we update them when resampling the
// input data.
s16 curr0 = pb.src.last_samples[2];
s16 curr1 = pb.src.last_samples[3];
for (u32 i = 0; i < 32; ++i)
{
// Get our current fractional position, used to know how much of
// curr0 and how much of curr1 the output sample should be.
s32 curr_frac_pos = curr_pos & 0xFFFF;
// Linear interpolation: s1 + (s2 - s1) * pos
s16 sample = curr0 + (s16)(((curr1 - curr0) * (s32)curr_frac_pos) >> 16);
samples[i] = sample;
curr_pos += ratio;
// While our current position is >= 1.0, shift to the next 2
// samples for interpolation.
while ((curr_pos >> 16) != 0)
{
curr0 = curr1;
curr1 = AcceleratorGetSample();
curr_pos -= 0x10000;
}
}
// Update the two last_samples values in the PB as well as the current
// position.
pb.src.last_samples[2] = curr0;
pb.src.last_samples[3] = curr1;
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));
}
if (coeffs)
coeffs += pb.coef_select * 0x200;
u32 curr_pos = ResampleAudio([](u32) { return AcceleratorGetSample(); },
samples, count, pb.src.last_samples,
pb.src.cur_addr_frac, HILO_TO_32(pb.src.ratio),
pb.src_type, coeffs);
pb.src.cur_addr_frac = (curr_pos & 0xFFFF);
// Update current position in the PB.
pb.audio_addr.cur_addr_hi = (u16)(cur_addr >> 16);
@ -304,7 +424,7 @@ void GetInputSamples(PB_TYPE& pb, s16* samples)
}
// Add samples to an output buffer, with optional volume ramping.
void MixAdd(int* out, const s16* input, u16* pvol, s16* dpop, bool ramp)
void MixAdd(int* out, const s16* input, u32 count, u16* pvol, s16* dpop, bool ramp)
{
u16& volume = pvol[0];
u16 volume_delta = pvol[1];
@ -315,7 +435,7 @@ void MixAdd(int* out, const s16* input, u16* pvol, s16* dpop, bool ramp)
if (!ramp)
volume_delta = 0;
for (u32 i = 0; i < 32; ++i)
for (u32 i = 0; i < count; ++i)
{
s64 sample = input[i];
sample *= volume;
@ -328,62 +448,69 @@ void MixAdd(int* out, const s16* input, u16* pvol, s16* dpop, bool ramp)
}
}
// Process 1ms of audio (32 samples) from a PB and mix it to the buffers.
void Process1ms(PB_TYPE& pb, const AXBuffers& buffers, AXMixControl mctrl)
// Execute a low pass filter on the samples using one history value. Returns
// the new history value.
s16 LowPassFilter(s16* samples, u32 count, s16 yn1, u16 a0, u16 b0)
{
for (u32 i = 0; i < count; ++i)
yn1 = samples[i] = (a0 * (s32)samples[i] + b0 * (s32)yn1) >> 15;
return yn1;
}
// Process 1ms of audio (for AX GC) or 3ms of audio (for AX Wii) from a PB and
// mix it to the output buffers.
void ProcessVoice(PB_TYPE& pb, const AXBuffers& buffers, u16 count, AXMixControl mctrl, const s16* coeffs)
{
// 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);
s16 samples[MAX_SAMPLES_PER_FRAME];
GetInputSamples(pb, samples, count, coeffs);
// Apply a global volume ramp using the volume envelope parameters.
for (u32 i = 0; i < 32; ++i)
for (u32 i = 0; i < count; ++i)
{
s64 sample = 2 * (s16)samples[i] * (s16)pb.vol_env.cur_volume;
samples[i] = (s16)(sample >> 16);
samples[i] = ((s32)samples[i] * pb.vol_env.cur_volume) >> 15;
pb.vol_env.cur_volume += pb.vol_env.cur_volume_delta;
}
// Optionally, execute a low pass filter
if (pb.lpf.enabled)
{
// TODO
}
pb.lpf.yn1 = LowPassFilter(samples, count, pb.lpf.yn1, pb.lpf.a0, pb.lpf.b0);
// Mix LRS, AUXA and AUXB depending on mixer_control
// TODO: Handle DPL2 on AUXB.
if (mctrl & MIX_L)
MixAdd(buffers.left, samples, &pb.mixer.left, &pb.dpop.left, mctrl & MIX_L_RAMP);
MixAdd(buffers.left, samples, count, &pb.mixer.left, &pb.dpop.left, mctrl & MIX_L_RAMP);
if (mctrl & MIX_R)
MixAdd(buffers.right, samples, &pb.mixer.right, &pb.dpop.right, mctrl & MIX_R_RAMP);
MixAdd(buffers.right, samples, count, &pb.mixer.right, &pb.dpop.right, mctrl & MIX_R_RAMP);
if (mctrl & MIX_S)
MixAdd(buffers.surround, samples, &pb.mixer.surround, &pb.dpop.surround, mctrl & MIX_S_RAMP);
MixAdd(buffers.surround, samples, count, &pb.mixer.surround, &pb.dpop.surround, mctrl & MIX_S_RAMP);
if (mctrl & MIX_AUXA_L)
MixAdd(buffers.auxA_left, samples, &pb.mixer.auxA_left, &pb.dpop.auxA_left, mctrl & MIX_AUXA_L_RAMP);
MixAdd(buffers.auxA_left, samples, count, &pb.mixer.auxA_left, &pb.dpop.auxA_left, mctrl & MIX_AUXA_L_RAMP);
if (mctrl & MIX_AUXA_R)
MixAdd(buffers.auxA_right, samples, &pb.mixer.auxA_right, &pb.dpop.auxA_right, mctrl & MIX_AUXA_R_RAMP);
MixAdd(buffers.auxA_right, samples, count, &pb.mixer.auxA_right, &pb.dpop.auxA_right, mctrl & MIX_AUXA_R_RAMP);
if (mctrl & MIX_AUXA_S)
MixAdd(buffers.auxA_surround, samples, &pb.mixer.auxA_surround, &pb.dpop.auxA_surround, mctrl & MIX_AUXA_S_RAMP);
MixAdd(buffers.auxA_surround, samples, count, &pb.mixer.auxA_surround, &pb.dpop.auxA_surround, mctrl & MIX_AUXA_S_RAMP);
if (mctrl & MIX_AUXB_L)
MixAdd(buffers.auxB_left, samples, &pb.mixer.auxB_left, &pb.dpop.auxB_left, mctrl & MIX_AUXB_L_RAMP);
MixAdd(buffers.auxB_left, samples, count, &pb.mixer.auxB_left, &pb.dpop.auxB_left, mctrl & MIX_AUXB_L_RAMP);
if (mctrl & MIX_AUXB_R)
MixAdd(buffers.auxB_right, samples, &pb.mixer.auxB_right, &pb.dpop.auxB_right, mctrl & MIX_AUXB_R_RAMP);
MixAdd(buffers.auxB_right, samples, count, &pb.mixer.auxB_right, &pb.dpop.auxB_right, mctrl & MIX_AUXB_R_RAMP);
if (mctrl & MIX_AUXB_S)
MixAdd(buffers.auxB_surround, samples, &pb.mixer.auxB_surround, &pb.dpop.auxB_surround, mctrl & MIX_AUXB_S_RAMP);
MixAdd(buffers.auxB_surround, samples, count, &pb.mixer.auxB_surround, &pb.dpop.auxB_surround, mctrl & MIX_AUXB_S_RAMP);
#ifdef AX_WII
if (mctrl & MIX_AUXC_L)
MixAdd(buffers.auxC_left, samples, &pb.mixer.auxC_left, &pb.dpop.auxC_left, mctrl & MIX_AUXC_L_RAMP);
MixAdd(buffers.auxC_left, samples, count, &pb.mixer.auxC_left, &pb.dpop.auxC_left, mctrl & MIX_AUXC_L_RAMP);
if (mctrl & MIX_AUXC_R)
MixAdd(buffers.auxC_right, samples, &pb.mixer.auxC_right, &pb.dpop.auxC_right, mctrl & MIX_AUXC_R_RAMP);
MixAdd(buffers.auxC_right, samples, count, &pb.mixer.auxC_right, &pb.dpop.auxC_right, mctrl & MIX_AUXC_R_RAMP);
if (mctrl & MIX_AUXC_S)
MixAdd(buffers.auxC_surround, samples, &pb.mixer.auxC_surround, &pb.dpop.auxC_surround, mctrl & MIX_AUXC_S_RAMP);
MixAdd(buffers.auxC_surround, samples, count, &pb.mixer.auxC_surround, &pb.dpop.auxC_surround, mctrl & MIX_AUXC_S_RAMP);
#endif
// Optionally, phase shift left or right channel to simulate 3D sound.
@ -391,6 +518,47 @@ void Process1ms(PB_TYPE& pb, const AXBuffers& buffers, AXMixControl mctrl)
{
// TODO
}
#ifdef AX_WII
// Wiimote mixing.
if (pb.remote)
{
// Old AXWii versions process ms per ms.
u16 wm_count = count == 96 ? 18 : 6;
// Interpolate at most 18 samples from the 96 samples we read before.
s16 wm_samples[18];
// We use ratio 0x55555 == (5 * 65536 + 21845) / 65536 == 5.3333 which
// is the nearest we can get to 96/18
u32 curr_pos = ResampleAudio([&samples](u32 i) { return samples[i]; },
wm_samples, wm_count, pb.remote_src.last_samples,
pb.remote_src.cur_addr_frac, 0x55555,
SRCTYPE_POLYPHASE, coeffs);
pb.remote_src.cur_addr_frac = curr_pos & 0xFFFF;
// Mix to main[0-3] and aux[0-3]
#define WMCHAN_MIX_ON(n) ((pb.remote_mixer_control >> (2 * n)) & 3)
#define WMCHAN_MIX_RAMP(n) ((pb.remote_mixer_control >> (2 * n)) & 2)
if (WMCHAN_MIX_ON(0))
MixAdd(buffers.wm_main0, wm_samples, wm_count, &pb.remote_mixer.main0, &pb.remote_dpop.main0, WMCHAN_MIX_RAMP(0));
if (WMCHAN_MIX_ON(1))
MixAdd(buffers.wm_aux0, wm_samples, wm_count, &pb.remote_mixer.aux0, &pb.remote_dpop.aux0, WMCHAN_MIX_RAMP(1));
if (WMCHAN_MIX_ON(2))
MixAdd(buffers.wm_main1, wm_samples, wm_count, &pb.remote_mixer.main1, &pb.remote_dpop.main1, WMCHAN_MIX_RAMP(2));
if (WMCHAN_MIX_ON(3))
MixAdd(buffers.wm_aux1, wm_samples, wm_count, &pb.remote_mixer.aux1, &pb.remote_dpop.aux1, WMCHAN_MIX_RAMP(3));
if (WMCHAN_MIX_ON(4))
MixAdd(buffers.wm_main2, wm_samples, wm_count, &pb.remote_mixer.main2, &pb.remote_dpop.main2, WMCHAN_MIX_RAMP(4));
if (WMCHAN_MIX_ON(5))
MixAdd(buffers.wm_aux2, wm_samples, wm_count, &pb.remote_mixer.aux2, &pb.remote_dpop.aux2, WMCHAN_MIX_RAMP(5));
if (WMCHAN_MIX_ON(6))
MixAdd(buffers.wm_main3, wm_samples, wm_count, &pb.remote_mixer.main3, &pb.remote_dpop.main3, WMCHAN_MIX_RAMP(6));
if (WMCHAN_MIX_ON(7))
MixAdd(buffers.wm_aux3, wm_samples, wm_count, &pb.remote_mixer.aux3, &pb.remote_dpop.aux3, WMCHAN_MIX_RAMP(7));
}
#endif
}
} // namespace

View File

@ -1,383 +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 SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "StringUtil.h"
#include "../MailHandler.h"
#include "Mixer.h"
#include "UCodes.h"
#include "UCode_AX_Structs.h"
#include "UCode_NewAXWii.h"
#define AX_WII
#include "UCode_AX_Voice.h"
CUCode_NewAXWii::CUCode_NewAXWii(DSPHLE *dsp_hle, u32 l_CRC)
: CUCode_AX(dsp_hle, l_CRC)
{
WARN_LOG(DSPHLE, "Instantiating CUCode_NewAXWii");
}
CUCode_NewAXWii::~CUCode_NewAXWii()
{
}
void CUCode_NewAXWii::HandleCommandList()
{
// Temp variables for addresses computation
u16 addr_hi, addr_lo;
u16 addr2_hi, addr2_lo;
u16 volume;
// 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 += 2; break;
case CMD_UNK_02: curr_idx += 2; break;
case CMD_UNK_03: curr_idx += 2; break;
case CMD_PROCESS:
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
ProcessPBList(HILO_TO_32(addr));
break;
case CMD_MIX_AUXA:
case CMD_MIX_AUXB:
case CMD_MIX_AUXC:
volume = m_cmdlist[curr_idx++];
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), volume);
break;
// These two go together and manipulate some AUX buffers.
case CMD_UNK_08: curr_idx += 13; break;
case CMD_UNK_09: curr_idx += 13; break;
case CMD_UNK_0A: curr_idx += 4; break;
case CMD_OUTPUT:
volume = m_cmdlist[curr_idx++];
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
addr2_hi = m_cmdlist[curr_idx++];
addr2_lo = m_cmdlist[curr_idx++];
OutputSamples(HILO_TO_32(addr2), HILO_TO_32(addr), volume);
break;
case CMD_UNK_0C: curr_idx += 5; break;
case CMD_WM_OUTPUT:
{
u32 addresses[4] = {
(u32)(m_cmdlist[curr_idx + 0] << 16) | m_cmdlist[curr_idx + 1],
(u32)(m_cmdlist[curr_idx + 2] << 16) | m_cmdlist[curr_idx + 3],
(u32)(m_cmdlist[curr_idx + 4] << 16) | m_cmdlist[curr_idx + 5],
(u32)(m_cmdlist[curr_idx + 6] << 16) | m_cmdlist[curr_idx + 7],
};
curr_idx += 8;
OutputWMSamples(addresses);
break;
}
case CMD_END:
end = true;
break;
}
}
}
void CUCode_NewAXWii::SetupProcessing(u32 init_addr)
{
// TODO: should be easily factorizable with AX
s16 init_data[60];
for (u32 i = 0; i < 60; ++i)
init_data[i] = HLEMemory_Read_U16(init_addr + 2 * i);
// List of all buffers we have to initialize
struct {
int* ptr;
u32 samples;
} buffers[] = {
{ m_samples_left, 32 },
{ m_samples_right, 32 },
{ m_samples_surround, 32 },
{ m_samples_auxA_left, 32 },
{ m_samples_auxA_right, 32 },
{ m_samples_auxA_surround, 32 },
{ m_samples_auxB_left, 32 },
{ m_samples_auxB_right, 32 },
{ m_samples_auxB_surround, 32 },
{ m_samples_auxC_left, 32 },
{ m_samples_auxC_right, 32 },
{ m_samples_auxC_surround, 32 },
{ m_samples_wm0, 6 },
{ m_samples_aux0, 6 },
{ m_samples_wm1, 6 },
{ m_samples_aux1, 6 },
{ m_samples_wm2, 6 },
{ m_samples_aux2, 6 },
{ m_samples_wm3, 6 },
{ m_samples_aux3, 6 }
};
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].ptr, 0, 3 * buffers[i].samples * sizeof (int));
else
{
for (u32 j = 0; j < 3 * buffers[i].samples; ++j)
{
buffers[i].ptr[j] = init_val;
init_val += delta;
}
}
}
}
AXMixControl CUCode_NewAXWii::ConvertMixerControl(u32 mixer_control)
{
u32 ret = 0;
if (mixer_control & 0x00000001) ret |= MIX_L;
if (mixer_control & 0x00000002) ret |= MIX_R;
if (mixer_control & 0x00000004) ret |= MIX_L_RAMP | MIX_R_RAMP;
if (mixer_control & 0x00000008) ret |= MIX_S;
if (mixer_control & 0x00000010) ret |= MIX_S_RAMP;
if (mixer_control & 0x00010000) ret |= MIX_AUXA_L;
if (mixer_control & 0x00020000) ret |= MIX_AUXA_R;
if (mixer_control & 0x00040000) ret |= MIX_AUXA_L_RAMP | MIX_AUXA_R_RAMP;
if (mixer_control & 0x00080000) ret |= MIX_AUXA_S;
if (mixer_control & 0x00100000) ret |= MIX_AUXA_S_RAMP;
if (mixer_control & 0x00200000) ret |= MIX_AUXB_L;
if (mixer_control & 0x00400000) ret |= MIX_AUXB_R;
if (mixer_control & 0x00800000) ret |= MIX_AUXB_L_RAMP | MIX_AUXB_R_RAMP;
if (mixer_control & 0x01000000) ret |= MIX_AUXB_S;
if (mixer_control & 0x02000000) ret |= MIX_AUXB_S_RAMP;
if (mixer_control & 0x04000000) ret |= MIX_AUXC_L;
if (mixer_control & 0x08000000) ret |= MIX_AUXC_R;
if (mixer_control & 0x10000000) ret |= MIX_AUXC_L_RAMP | MIX_AUXC_R_RAMP;
if (mixer_control & 0x20000000) ret |= MIX_AUXC_S;
if (mixer_control & 0x40000000) ret |= MIX_AUXC_S_RAMP;
return (AXMixControl)ret;
}
void CUCode_NewAXWii::ProcessPBList(u32 pb_addr)
{
const u32 spms = 32;
AXPBWii 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,
m_samples_auxC_left,
m_samples_auxC_right,
m_samples_auxC_surround
}};
if (!ReadPB(pb_addr, pb))
break;
for (int curr_ms = 0; curr_ms < 3; ++curr_ms)
{
Process1ms(pb, buffers, ConvertMixerControl(HILO_TO_32(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_NewAXWii::MixAUXSamples(int aux_id, u32 write_addr, u32 read_addr, u16 volume)
{
int* buffers[3] = { 0 };
int* main_buffers[3] = {
m_samples_left,
m_samples_right,
m_samples_surround
};
switch (aux_id)
{
case 0:
buffers[0] = m_samples_auxA_left;
buffers[1] = m_samples_auxA_right;
buffers[2] = m_samples_auxA_surround;
break;
case 1:
buffers[0] = m_samples_auxB_left;
buffers[1] = m_samples_auxB_right;
buffers[2] = m_samples_auxB_surround;
break;
case 2:
buffers[0] = m_samples_auxC_left;
buffers[1] = m_samples_auxC_right;
buffers[2] = m_samples_auxC_surround;
break;
}
// Send the content of AUX buffers to the CPU
if (write_addr)
{
int* ptr = (int*)HLEMemory_Get_Pointer(write_addr);
for (u32 i = 0; i < 3; ++i)
for (u32 j = 0; j < 3 * 32; ++j)
*ptr++ = Common::swap32(buffers[i][j]);
}
// Then read the buffers from the CPU and add to our main buffers.
int* ptr = (int*)HLEMemory_Get_Pointer(read_addr);
for (u32 i = 0; i < 3; ++i)
for (u32 j = 0; j < 3 * 32; ++j)
{
s64 new_val = main_buffers[i][j] + Common::swap32(*ptr++);
main_buffers[i][j] = (new_val * volume) >> 15;
}
}
void CUCode_NewAXWii::OutputSamples(u32 lr_addr, u32 surround_addr, u16 volume)
{
int surround_buffer[3 * 32] = { 0 };
for (u32 i = 0; i < 3 * 32; ++i)
surround_buffer[i] = Common::swap32(m_samples_surround[i]);
memcpy(HLEMemory_Get_Pointer(surround_addr), surround_buffer, sizeof (surround_buffer));
short buffer[3 * 32 * 2];
// Clamp internal buffers to 16 bits.
for (u32 i = 0; i < 3 * 32; ++i)
{
int left = m_samples_left[i];
int right = m_samples_right[i];
// Apply global volume. Cast to s64 to avoid overflow.
left = ((s64)left * volume) >> 15;
right = ((s64)right * volume) >> 15;
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 < 3 * 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(lr_addr), buffer, sizeof (buffer));
}
void CUCode_NewAXWii::OutputWMSamples(u32* addresses)
{
int* buffers[] = {
m_samples_wm0,
m_samples_wm1,
m_samples_wm2,
m_samples_wm3
};
for (u32 i = 0; i < 4; ++i)
{
int* in = buffers[i];
u16* out = (u16*)HLEMemory_Get_Pointer(addresses[i]);
for (u32 j = 0; j < 3 * 6; ++j)
{
int sample = in[j];
if (sample < -32767) sample = -32767;
if (sample > 32767) sample = 32767;
out[j] = Common::swap16((u16)sample);
}
}
}
void CUCode_NewAXWii::DoState(PointerWrap &p)
{
std::lock_guard<std::mutex> lk(m_processing);
DoStateShared(p);
DoAXState(p);
p.Do(m_samples_auxC_left);
p.Do(m_samples_auxC_right);
p.Do(m_samples_auxC_surround);
p.Do(m_samples_wm0);
p.Do(m_samples_wm1);
p.Do(m_samples_wm2);
p.Do(m_samples_wm3);
p.Do(m_samples_aux0);
p.Do(m_samples_aux1);
p.Do(m_samples_aux2);
p.Do(m_samples_aux3);
}

View File

@ -1,80 +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_NEWAXWII_H
#define _UCODE_NEWAXWII_H
#include "UCode_AX.h"
class CUCode_NewAXWii : public CUCode_AX
{
public:
CUCode_NewAXWii(DSPHLE *dsp_hle, u32 _CRC);
virtual ~CUCode_NewAXWii();
virtual void DoState(PointerWrap &p);
protected:
int m_samples_auxC_left[32 * 3];
int m_samples_auxC_right[32 * 3];
int m_samples_auxC_surround[32 * 3];
// Wiimote buffers
int m_samples_wm0[6 * 3];
int m_samples_aux0[6 * 3];
int m_samples_wm1[6 * 3];
int m_samples_aux1[6 * 3];
int m_samples_wm2[6 * 3];
int m_samples_aux2[6 * 3];
int m_samples_wm3[6 * 3];
int m_samples_aux3[6 * 3];
// 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);
virtual void HandleCommandList();
void SetupProcessing(u32 init_addr);
void ProcessPBList(u32 pb_addr);
void MixAUXSamples(int aux_id, u32 write_addr, u32 read_addr, u16 volume);
void OutputSamples(u32 lr_addr, u32 surround_addr, u16 volume);
void OutputWMSamples(u32* addresses); // 4 addresses
private:
enum CmdType
{
CMD_SETUP = 0x00,
CMD_UNK_01 = 0x01,
CMD_UNK_02 = 0x02,
CMD_UNK_03 = 0x03,
CMD_PROCESS = 0x04,
CMD_MIX_AUXA = 0x05,
CMD_MIX_AUXB = 0x06,
CMD_MIX_AUXC = 0x07,
CMD_UNK_08 = 0x08,
CMD_UNK_09 = 0x09,
CMD_UNK_0A = 0x0A,
CMD_OUTPUT = 0x0B,
CMD_UNK_0C = 0x0C,
CMD_WM_OUTPUT = 0x0D,
CMD_END = 0x0E
};
};
#endif // _UCODE_AXWII

View File

@ -19,7 +19,6 @@
#include "UCode_AX.h"
#include "UCode_AXWii.h"
#include "UCode_NewAXWii.h"
#include "UCode_Zelda.h"
#include "UCode_ROM.h"
#include "UCode_CARD.h"
@ -27,12 +26,6 @@
#include "UCode_GBA.h"
#include "Hash.h"
#if 0
# define AXWII CUCode_NewAXWii
#else
# define AXWII CUCode_AXWii
#endif
IUCode* UCodeFactory(u32 _CRC, DSPHLE *dsp_hle, bool bWii)
{
switch (_CRC)
@ -97,13 +90,13 @@ IUCode* UCodeFactory(u32 _CRC, DSPHLE *dsp_hle, bool bWii)
case 0x4cc52064: // Bleach: Versus Crusade
case 0xd9c4bf34: // WiiMenu
INFO_LOG(DSPHLE, "CRC %08x: Wii - AXWii chosen", _CRC);
return new AXWII(dsp_hle, _CRC);
return new CUCode_AXWii(dsp_hle, _CRC);
default:
if (bWii)
{
PanicAlert("DSPHLE: Unknown ucode (CRC = %08x) - forcing AXWii.\n\nTry LLE emulator if this is homebrew.", _CRC);
return new AXWII(dsp_hle, _CRC);
return new CUCode_AXWii(dsp_hle, _CRC);
}
else
{

View File

@ -508,7 +508,7 @@ void CConfigMain::InitializeGUITooltips()
InterfaceLang->SetToolTip(_("Change the language of the user interface.\nRequires restart."));
// Audio tooltips
DSPThread->SetToolTip(_("Run DSP LLE on a dedicated thread (not recommended)."));
DSPThread->SetToolTip(_("Run DSP HLE and LLE on a dedicated thread (not recommended: might cause audio glitches with HLE and freezes with LLE)."));
BackendSelection->SetToolTip(_("Changing this will have no effect while the emulator is running!"));
// Gamecube - Devices
@ -651,7 +651,7 @@ void CConfigMain::CreateGUIControls()
// Audio page
DSPEngine = new wxRadioBox(AudioPage, ID_DSPENGINE, _("DSP Emulator Engine"),
wxDefaultPosition, wxDefaultSize, arrayStringFor_DSPEngine, 0, wxRA_SPECIFY_ROWS);
DSPThread = new wxCheckBox(AudioPage, ID_DSPTHREAD, _("DSP LLE on Thread"));
DSPThread = new wxCheckBox(AudioPage, ID_DSPTHREAD, _("DSP on Dedicated Thread"));
DumpAudio = new wxCheckBox(AudioPage, ID_DUMP_AUDIO, _("Dump Audio"),
wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator);
DPL2Decoder = new wxCheckBox(AudioPage, ID_DPL2DECODER, _("Dolby Pro Logic II decoder"));