Added output support to NewAX. Now working fine with Tales of Symphonia.

This commit is contained in:
Pierre Bourdon 2012-11-14 11:20:54 +01:00
parent 18f3630af5
commit 0b275c20af
2 changed files with 83 additions and 17 deletions

View File

@ -19,6 +19,12 @@
#include "UCode_AX_Voice.h" #include "UCode_AX_Voice.h"
#include "../../DSP.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)
#define MIXBUF_MAX_SAMPLES 16000 // 500ms of stereo audio
CUCode_NewAX::CUCode_NewAX(DSPHLE* dsp_hle, u32 crc) CUCode_NewAX::CUCode_NewAX(DSPHLE* dsp_hle, u32 crc)
: IUCode(dsp_hle, crc) : IUCode(dsp_hle, crc)
, m_cmdlist_size(0) , m_cmdlist_size(0)
@ -69,7 +75,8 @@ void CUCode_NewAX::NotifyAXThread()
void CUCode_NewAX::HandleCommandList() void CUCode_NewAX::HandleCommandList()
{ {
u16 pb_addr_hi, pb_addr_lo; u16 addr_hi, addr_lo;
u32 pb_addr = 0; u32 pb_addr = 0;
u32 curr_idx = 0; u32 curr_idx = 0;
@ -84,15 +91,18 @@ void CUCode_NewAX::HandleCommandList()
// A lot of these commands are unknown, or unused in this AX HLE. // A lot of these commands are unknown, or unused in this AX HLE.
// We still need to skip their arguments using "curr_idx += N". // We still need to skip their arguments using "curr_idx += N".
case CMD_STUDIO_ADDR: curr_idx += 2; break; case CMD_STUDIO_ADDR:
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_UNK_01: curr_idx += 5; break;
case CMD_PB_ADDR: case CMD_PB_ADDR:
pb_addr_hi = m_cmdlist[curr_idx++]; addr_hi = m_cmdlist[curr_idx++];
pb_addr_lo = m_cmdlist[curr_idx++]; addr_lo = m_cmdlist[curr_idx++];
pb_addr = (pb_addr_hi << 16) | pb_addr_lo; pb_addr = HILO_TO_32(addr);
WARN_LOG(DSPHLE, "PB addr: %08x", pb_addr);
break; break;
case CMD_PROCESS: case CMD_PROCESS:
@ -109,7 +119,15 @@ void CUCode_NewAX::HandleCommandList()
case CMD_UNK_0B: break; // TODO: check other versions case CMD_UNK_0B: break; // TODO: check other versions
case CMD_UNK_0C: break; // TODO: check other versions case CMD_UNK_0C: break; // TODO: check other versions
case CMD_UNK_0D: curr_idx += 2; break; case CMD_UNK_0D: curr_idx += 2; break;
case CMD_UNK_0E: curr_idx += 4; break;
case CMD_OUTPUT:
// Skip the first address, we don't know what it's used for.
curr_idx += 2;
addr_hi = m_cmdlist[curr_idx++];
addr_lo = m_cmdlist[curr_idx++];
OutputSamples(HILO_TO_32(addr));
break;
case CMD_END: case CMD_END:
end = true; end = true;
@ -202,7 +220,7 @@ static void ApplyUpdatesForMs(AXPB& pb, int curr_ms)
for (int i = 0; i < curr_ms; ++i) for (int i = 0; i < curr_ms; ++i)
start_idx += pb.updates.num_updates[i]; start_idx += pb.updates.num_updates[i];
u32 update_addr = (pb.updates.data_hi << 16) | pb.updates.data_lo; 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 + pb.updates.num_updates[curr_ms]; ++i)
{ {
u16 update_off = HLEMemory_Read_U16(update_addr + 4 * i); u16 update_off = HLEMemory_Read_U16(update_addr + 4 * i);
@ -212,9 +230,22 @@ static void ApplyUpdatesForMs(AXPB& pb, int curr_ms)
} }
} }
void CUCode_NewAX::SetupProcessing(u32 studio_addr)
{
// Initialize to 0. Real hardware initializes using values from studio_addr
// (to have volume ramps instead of 0), but we don't emulate this yet.
(void)studio_addr;
memset(m_samples_left, 0, sizeof (m_samples_left));
memset(m_samples_right, 0, sizeof (m_samples_right));
}
void CUCode_NewAX::ProcessPBList(u32 pb_addr) void CUCode_NewAX::ProcessPBList(u32 pb_addr)
{ {
static int tmp_mix_buffer_left[5 * 32], tmp_mix_buffer_right[5 * 32]; // Samples per millisecond. In theory DSP sampling rate can be changed from
// 32KHz to 48KHz, but AX always process at 32KHz.
u32 spms = 32;
AXPB pb; AXPB pb;
@ -231,15 +262,44 @@ void CUCode_NewAX::ProcessPBList(u32 pb_addr)
if (m_CRC != 0x3389a79e) if (m_CRC != 0x3389a79e)
VoiceHacks(pb); VoiceHacks(pb);
MixAddVoice(pb, tmp_mix_buffer_left + 32 * curr_ms, MixAddVoice(pb, m_samples_left + spms * curr_ms,
tmp_mix_buffer_right + 32 * curr_ms, 32); m_samples_right + spms * curr_ms, spms);
} }
WritePB(pb_addr, pb); WritePB(pb_addr, pb);
pb_addr = (pb.next_pb_hi << 16) | pb.next_pb_lo; pb_addr = HILO_TO_32(pb.next_pb);
} }
// TODO: write the 5ms back to a buffer the audio interface can read from // Clamp to 16 bits.
// TODO: check the clamping is done in this command and not in a later
// command.
for (u32 i = 0; i < 5 * spms; ++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;
}
}
void CUCode_NewAX::OutputSamples(u32 out_addr)
{
// 32 samples per ms, 5 ms, 2 channels
short buffer[5 * 32 * 2];
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) void CUCode_NewAX::HandleMail(u32 mail)
@ -314,8 +374,8 @@ void CUCode_NewAX::CopyCmdList(u32 addr, u16 size)
void CUCode_NewAX::MixAdd(short* out_buffer, int nsamples) void CUCode_NewAX::MixAdd(short* out_buffer, int nsamples)
{ {
// nsamples * 2 for left and right audio channel // Should never be called: we do not set HLE as ready.
memset(out_buffer, 0, nsamples * 2 * sizeof (short)); // We accurately send samples to RAM instead of directly to the mixer.
} }
void CUCode_NewAX::Update(int cycles) void CUCode_NewAX::Update(int cycles)

View File

@ -61,7 +61,7 @@ private:
CMD_UNK_0B = 0x0B, CMD_UNK_0B = 0x0B,
CMD_UNK_0C = 0x0C, CMD_UNK_0C = 0x0C,
CMD_UNK_0D = 0x0D, CMD_UNK_0D = 0x0D,
CMD_UNK_0E = 0x0E, CMD_OUTPUT = 0x0E,
CMD_END = 0x0F, CMD_END = 0x0F,
CMD_UNK_10 = 0x10, CMD_UNK_10 = 0x10,
CMD_UNK_11 = 0x11, CMD_UNK_11 = 0x11,
@ -69,6 +69,10 @@ private:
CMD_UNK_13 = 0x13, 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];
// Volatile because it's set by HandleMail and accessed in // Volatile because it's set by HandleMail and accessed in
// HandleCommandList, which are running in two different threads. // HandleCommandList, which are running in two different threads.
volatile u16 m_cmdlist[512]; volatile u16 m_cmdlist[512];
@ -90,7 +94,9 @@ private:
void AXThread(); void AXThread();
void HandleCommandList(); void HandleCommandList();
void SetupProcessing(u32 studio_addr);
void ProcessPBList(u32 pb_addr); void ProcessPBList(u32 pb_addr);
void OutputSamples(u32 out_addr);
}; };
#endif // !_UCODE_NEWAX_H #endif // !_UCODE_NEWAX_H