Re-implementation of the AX voice mixing algorithm. Now with 100% less WTF.

This commit is contained in:
Pierre Bourdon 2012-11-15 16:30:05 +01:00
parent 531cc6aaf3
commit aa90f799b7
6 changed files with 353 additions and 91 deletions

View File

@ -468,6 +468,7 @@
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_GBA.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_InitAudioSystem.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_NewAX.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_NewAX_Voice.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_ROM.h" />
<ClInclude Include="Src\HW\DSPHLE\UCodes\UCode_Zelda.h" />
<ClInclude Include="Src\HW\DSPLLE\DSPDebugInterface.h" />

View File

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

View File

@ -35,7 +35,12 @@ struct PBMixer
u16 auxB_right;
u16 auxB_right_delta;
u16 unknown4[6];
u16 auxB_surround;
u16 auxB_surround_delta;
u16 surround;
u16 surround_delta;
u16 auxA_surround;
u16 auxA_surround_delta;
};
struct PBMixerWii
@ -212,6 +217,14 @@ struct PBADPCMLoopInfo
u16 yn2;
};
struct PBLowPassFilter
{
u16 enabled;
u16 yn1;
u16 a0;
u16 b0;
};
struct AXPB
{
u16 next_pb_hi;
@ -236,15 +249,9 @@ struct AXPB
PBADPCMInfo adpcm;
PBSampleRateConverter src;
PBADPCMLoopInfo adpcm_loop_info;
u16 unknown_maybe_padding[3];
};
PBLowPassFilter lpf;
struct PBLowPassFilter
{
u16 enabled;
u16 yn1;
u16 a0;
u16 b0;
u16 padding[25];
};
struct PBBiquadFilter
@ -360,9 +367,29 @@ enum {
};
enum {
SRCTYPE_POLYPHASE = 0,
SRCTYPE_LINEAR = 1,
SRCTYPE_NEAREST = 2,
MIXCONTROL_RAMPING = 8,
};
enum {
MIX_L = 0x0001,
MIX_R = 0x0002,
MIX_S = 0x0004,
MIX_RAMP = 0x0008,
MIX_AUXA_L = 0x0010,
MIX_AUXA_R = 0x0020,
MIX_AUXA_RAMPLR = 0x0040,
MIX_AUXA_S = 0x0080,
MIX_AUXA_RAMPS = 0x0100,
MIX_AUXB_L = 0x0200,
MIX_AUXB_R = 0x0400,
MIX_AUXB_RAMPLR = 0x0800,
MIX_AUXB_S = 0x1000,
MIX_AUXB_RAMPS = 0x2000,
MIX_AUXB_DPL2 = 0x4000
};
// Both may be used at once

View File

@ -230,7 +230,7 @@ inline void MixAddVoice(ParamBlockType &pb, const AXBuffers& buffers,
int vol = pb.vol_env.cur_volume >> 9;
sample = sample * vol >> 8;
if (pb.mixer_control & MIXCONTROL_RAMPING)
if (pb.mixer_control & MIX_RAMP)
{
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

View File

@ -16,13 +16,9 @@
// http://code.google.com/p/dolphin-emu/
#include "UCode_NewAX.h"
#include "UCode_AX_Voice.h"
#include "UCode_NewAX_Voice.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)
@ -173,74 +169,6 @@ void CUCode_NewAX::HandleCommandList()
}
}
// From old UCode_AX.cpp.
static void VoiceHacks(AXPB &pb)
{
// get necessary values
const u32 sampleEnd = (pb.audio_addr.end_addr_hi << 16) | pb.audio_addr.end_addr_lo;
const u32 loopPos = (pb.audio_addr.loop_addr_hi << 16) | pb.audio_addr.loop_addr_lo;
// const u32 updaddr = (u32)(pb.updates.data_hi << 16) | pb.updates.data_lo;
// const u16 updpar = HLEMemory_Read_U16(updaddr);
// const u16 upddata = HLEMemory_Read_U16(updaddr + 2);
// =======================================================================================
/* Fix problems introduced with the SSBM fix. Sometimes when a music stream ended sampleEnd
would end up outside of bounds while the block was still playing resulting in noise
a strange noise. This should take care of that.
*/
if ((sampleEnd > (0x017fffff * 2) || loopPos > (0x017fffff * 2))) // ARAM bounds in nibbles
{
pb.running = 0;
// also reset all values if it makes any difference
pb.audio_addr.cur_addr_hi = 0; pb.audio_addr.cur_addr_lo = 0;
pb.audio_addr.end_addr_hi = 0; pb.audio_addr.end_addr_lo = 0;
pb.audio_addr.loop_addr_hi = 0; pb.audio_addr.loop_addr_lo = 0;
pb.src.cur_addr_frac = 0; pb.src.ratio_hi = 0; pb.src.ratio_lo = 0;
pb.adpcm.pred_scale = 0; pb.adpcm.yn1 = 0; pb.adpcm.yn2 = 0;
pb.audio_addr.looping = 0;
pb.adpcm_loop_info.pred_scale = 0;
pb.adpcm_loop_info.yn1 = 0; pb.adpcm_loop_info.yn2 = 0;
}
/*
// the fact that no settings are reset (except running) after a SSBM type music stream or another
looping block (for example in Battle Stadium DON) has ended could cause loud garbled sound to be
played from one or more blocks. Perhaps it was in conjunction with the old sequenced music fix below,
I'm not sure. This was an attempt to prevent that anyway by resetting all. But I'm not sure if this
is needed anymore. Please try to play SSBM without it and see if it works anyway.
*/
if (
// detect blocks that have recently been running that we should reset
pb.running == 0 && pb.audio_addr.looping == 1
//pb.running == 0 && pb.adpcm_loop_info.pred_scale
// this prevents us from ruining sequenced music blocks, may not be needed
/*
&& !(pb.updates.num_updates[0] || pb.updates.num_updates[1] || pb.updates.num_updates[2]
|| pb.updates.num_updates[3] || pb.updates.num_updates[4])
*/
//&& !(updpar || upddata)
&& pb.mixer_control == 0 // only use this in SSBM
)
{
// reset the detection values
pb.audio_addr.looping = 0;
pb.adpcm_loop_info.pred_scale = 0;
pb.adpcm_loop_info.yn1 = 0; pb.adpcm_loop_info.yn2 = 0;
//pb.audio_addr.cur_addr_hi = 0; pb.audio_addr.cur_addr_lo = 0;
//pb.audio_addr.end_addr_hi = 0; pb.audio_addr.end_addr_lo = 0;
//pb.audio_addr.loop_addr_hi = 0; pb.audio_addr.loop_addr_lo = 0;
//pb.src.cur_addr_frac = 0; PBs[i].src.ratio_hi = 0; PBs[i].src.ratio_lo = 0;
//pb.adpcm.pred_scale = 0; pb.adpcm.yn1 = 0; pb.adpcm.yn2 = 0;
}
}
static void ApplyUpdatesForMs(AXPB& pb, int curr_ms)
{
u32 start_idx = 0;
@ -292,7 +220,7 @@ void CUCode_NewAX::SetupProcessing(u32 init_addr)
for (u32 j = 0; j < 32 * 5; ++j)
{
buffers[i][j] = init_val;
init_val -= delta;
init_val += delta;
}
}
}
@ -311,10 +239,13 @@ void CUCode_NewAX::ProcessPBList(u32 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_right,
m_samples_auxB_surround
}};
if (!ReadPB(pb_addr, pb))
@ -324,11 +255,7 @@ void CUCode_NewAX::ProcessPBList(u32 pb_addr)
{
ApplyUpdatesForMs(pb, curr_ms);
// TODO: is that still needed?
if (m_CRC != 0x3389a79e)
VoiceHacks(pb);
MixAddVoice(pb, buffers, spms, false);
Process1ms(pb, buffers);
// Forward the buffers
for (u32 i = 0; i < sizeof (buffers.ptrs) / sizeof (buffers.ptrs[0]); ++i)

View File

@ -0,0 +1,304 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official Git repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _UCODE_NEWAX_VOICE_H
#define _UCODE_NEWAX_VOICE_H
#include "Common.h"
#include "UCode_AXStructs.h"
#include "../../DSP.h"
// Useful macro to convert xxx_hi + xxx_lo to xxx for 32 bits.
#define HILO_TO_32(name) \
((name##_hi << 16) | name##_lo)
// Used to pass a large amount of buffers to the mixing function.
union AXBuffers
{
struct
{
int* left;
int* right;
int* surround;
int* auxA_left;
int* auxA_right;
int* auxA_surround;
int* auxB_left;
int* auxB_right;
int* auxB_surround;
};
int* ptrs[9];
};
// Read a PB from MRAM/ARAM
inline bool ReadPB(u32 addr, AXPB& pb)
{
u16* dst = (u16*)&pb;
const u16* src = (const u16*)Memory::GetPointer(addr);
if (!src)
return false;
for (u32 i = 0; i < sizeof (pb) / sizeof (u16); ++i)
dst[i] = Common::swap16(src[i]);
return true;
}
// Write a PB back to MRAM/ARAM
inline bool WritePB(u32 addr, const AXPB& pb)
{
const u16* src = (const u16*)&pb;
u16* dst = (u16*)Memory::GetPointer(addr);
if (!dst)
return false;
for (u32 i = 0; i < sizeof (pb) / sizeof (u16); ++i)
dst[i] = Common::swap16(src[i]);
return true;
}
// Simulated accelerator state.
static u32 acc_loop_addr, acc_end_addr;
static u32* acc_cur_addr;
static AXPB* acc_pb;
// Sets up the simulated accelerator.
inline void AcceleratorSetup(AXPB* pb, u32* cur_addr)
{
acc_pb = pb;
acc_loop_addr = HILO_TO_32(pb->audio_addr.loop_addr);
acc_end_addr = HILO_TO_32(pb->audio_addr.end_addr);
acc_cur_addr = cur_addr;
}
// Reads a sample from the simulated accelerator.
inline u16 AcceleratorGetSample()
{
u16 ret;
switch (acc_pb->audio_addr.sample_format)
{
case 0x00: // ADPCM
{
if ((*acc_cur_addr & 15) == 0)
{
acc_pb->adpcm.pred_scale = DSP::ReadARAM((*acc_cur_addr & ~15) >> 1);
*acc_cur_addr += 2;
}
int scale = 1 << (acc_pb->adpcm.pred_scale & 0xF);
int coef_idx = (acc_pb->adpcm.pred_scale >> 4) & 0x7;
s32 coef1 = acc_pb->adpcm.coefs[coef_idx * 2 + 0];
s32 coef2 = acc_pb->adpcm.coefs[coef_idx * 2 + 1];
int temp = (*acc_cur_addr & 1) ?
(DSP::ReadARAM(*acc_cur_addr >> 1) & 0xF) :
(DSP::ReadARAM(*acc_cur_addr >> 1) >> 4);
if (temp >= 8)
temp -= 16;
int val = (scale * temp) + ((0x400 + coef1 * acc_pb->adpcm.yn1 + coef2 * acc_pb->adpcm.yn2) >> 11);
if (val > 0x7FFF) val = 0x7FFF;
else if (val < -0x7FFF) val = -0x7FFF;
acc_pb->adpcm.yn2 = acc_pb->adpcm.yn1;
acc_pb->adpcm.yn1 = val;
*acc_cur_addr += 1;
ret = val;
break;
}
case 0x0A: // 16-bit PCM audio
ret = (DSP::ReadARAM(*acc_cur_addr * 2) << 8) | DSP::ReadARAM(*acc_cur_addr * 2 + 1);
acc_pb->adpcm.yn2 = acc_pb->adpcm.yn1;
acc_pb->adpcm.yn1 = ret;
*acc_cur_addr += 1;
break;
case 0x19: // 8-bit PCM audio
ret = DSP::ReadARAM(*acc_cur_addr) << 8;
acc_pb->adpcm.yn2 = acc_pb->adpcm.yn1;
acc_pb->adpcm.yn1 = ret;
*acc_cur_addr += 1;
break;
default:
ERROR_LOG(DSPHLE, "Unknown sample format: %d", acc_pb->audio_addr.sample_format);
return 0;
}
if (*acc_cur_addr >= acc_end_addr)
{
if ((*acc_cur_addr & ~0x1F) == (acc_end_addr & ~0x1F))
*acc_cur_addr = acc_loop_addr;
// Simulate an ACC overflow interrupt.
if (acc_pb->audio_addr.looping)
{
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
{
acc_pb->running = 0;
}
}
return ret;
}
// Read 32 input samples from ARAM, decoding and converting rate if required.
inline void GetInputSamples(AXPB& pb, s16* samples)
{
u32 cur_addr = HILO_TO_32(pb.audio_addr.cur_addr);
AcceleratorSetup(&pb, &cur_addr);
// TODO: support polyphase interpolation if coefficients are available.
if (pb.src_type == SRCTYPE_POLYPHASE || pb.src_type == SRCTYPE_LINEAR)
{
u32 ratio = HILO_TO_32(pb.src.ratio);
u32 curr_pos = pb.src.cur_addr_frac;
u32 real_samples_needed = (32 * ratio + curr_pos) >> 16;
s16 real_samples[130]; // Max supported ratio is 4
real_samples[0] = pb.src.last_samples[2];
real_samples[1] = pb.src.last_samples[3];
for (u32 i = 0; i < real_samples_needed; ++i)
real_samples[i + 2] = AcceleratorGetSample();
for (u32 i = 0; i < 32; ++i)
{
u32 curr_int_pos = (curr_pos >> 16);
s32 curr_frac_pos = curr_pos & 0xFFFF;
s16 samp1 = real_samples[curr_int_pos];
s16 samp2 = real_samples[curr_int_pos + 1];
s16 sample = samp1 + (s16)(((samp2 - samp1) * (s32)curr_frac_pos) >> 16);
samples[i] = sample;
curr_pos += ratio;
}
if (real_samples_needed >= 2)
memcpy(pb.src.last_samples, &real_samples[real_samples_needed + 2 - 4], 4 * sizeof (u16));
else
{
memmove(pb.src.last_samples, &pb.src.last_samples[real_samples_needed], (4 - real_samples_needed) * sizeof (u16));
memcpy(&pb.src.last_samples[4 - real_samples_needed], &real_samples[2], real_samples_needed * sizeof (u16));
}
pb.src.cur_addr_frac = curr_pos & 0xFFFF;
}
else // SRCTYPE_NEAREST
{
for (u32 i = 0; i < 32; ++i)
samples[i] = AcceleratorGetSample();
memcpy(pb.src.last_samples, samples + 28, 4 * sizeof (u16));
}
pb.audio_addr.cur_addr_hi = (u16)(cur_addr >> 16);
pb.audio_addr.cur_addr_lo = (u16)(cur_addr & 0xFFFF);
}
// Mix samples to an output buffer, with optional volume ramping.
inline void MixAdd(int* out, const s16* input, u16* pvol, bool ramp)
{
u16& volume = pvol[0];
u16 volume_delta = pvol[1];
if (!ramp)
volume_delta = 0;
for (u32 i = 0; i < 32; ++i)
{
s64 sample = 2 * (s16)input[i] * (s16)volume;
out[i] += (s32)(sample >> 16);
volume += volume_delta;
}
}
// Process 1ms of audio from a PB and mix it to the buffers.
inline void Process1ms(AXPB& pb, const AXBuffers& buffers)
{
// If the voice is not running, nothing to do.
if (!pb.running)
return;
// Read input samples, performing sample rate conversion if needed.
s16 samples[32];
GetInputSamples(pb, samples);
// Apply a global volume ramp using the volume envelope parameters.
for (u32 i = 0; i < 32; ++i)
{
s64 sample = 2 * (s16)samples[i] * (s16)pb.vol_env.cur_volume;
samples[i] = (s16)(sample >> 16);
pb.vol_env.cur_volume += pb.vol_env.cur_volume_delta;
}
// Optionally, execute a low pass filter
if (pb.lpf.enabled)
{
// TODO
}
// Mix LRS, AUXA and AUXB depending on mixer_control
// TODO: Handle DPL2 on AUXB.
// HACK: at the moment we don't mix surround into left and right, so always
// mix left and right in order to have sound even if a game uses surround
// only.
//if (pb.mixer_control & MIX_L)
MixAdd(buffers.left, samples, &pb.mixer.left, pb.mixer_control & MIX_RAMP);
//if (pb.mixer_control & MIX_R)
MixAdd(buffers.right, samples, &pb.mixer.right, pb.mixer_control & MIX_RAMP);
if (pb.mixer_control & MIX_S)
MixAdd(buffers.surround, samples, &pb.mixer.surround, pb.mixer_control & MIX_RAMP);
if (pb.mixer_control & MIX_AUXA_L)
MixAdd(buffers.auxA_left, samples, &pb.mixer.auxA_left, pb.mixer_control & MIX_AUXA_RAMPLR);
if (pb.mixer_control & MIX_AUXA_R)
MixAdd(buffers.auxA_right, samples, &pb.mixer.auxA_right, pb.mixer_control & MIX_AUXA_RAMPLR);
if (pb.mixer_control & MIX_AUXA_S)
MixAdd(buffers.auxA_surround, samples, &pb.mixer.auxA_surround, pb.mixer_control & MIX_AUXA_RAMPS);
if (pb.mixer_control & MIX_AUXB_L)
MixAdd(buffers.auxB_left, samples, &pb.mixer.auxB_left, pb.mixer_control & MIX_AUXB_RAMPLR);
if (pb.mixer_control & MIX_AUXB_R)
MixAdd(buffers.auxB_right, samples, &pb.mixer.auxB_right, pb.mixer_control & MIX_AUXB_RAMPLR);
if (pb.mixer_control & MIX_AUXB_S)
MixAdd(buffers.auxB_surround, samples, &pb.mixer.auxB_surround, pb.mixer_control & MIX_AUXB_RAMPS);
// Optionally, phase shift left or right channel to simulate 3D sound.
if (pb.initial_time_delay.on)
{
// TODO
}
}
#endif // !_UCODE_NEWAX_VOICE_H