diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index fb2d0bafdf..caab0a1e6d 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -468,6 +468,7 @@ + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 482b2bd9b5..e0df3bd31f 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -754,6 +754,9 @@ HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes + + HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes + HW %28Flipper/Hollywood%29\DSP Interface + HLE\HLE\uCodes diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXStructs.h b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXStructs.h index 3217448268..e7701e4698 100644 --- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXStructs.h +++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXStructs.h @@ -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 diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h index f59693698c..f401cc630f 100644 --- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h +++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h @@ -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 diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.cpp b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.cpp index 9bc43b0f04..a7e9c561f0 100644 --- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.cpp +++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX.cpp @@ -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) diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX_Voice.h b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX_Voice.h new file mode 100644 index 0000000000..8ebbec2fd9 --- /dev/null +++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_NewAX_Voice.h @@ -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