From 3912e0e8d641efd54cfb5c11ce5f8c4d8eb10f47 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 11 Oct 2019 13:24:48 +1000 Subject: [PATCH] SPU: More implementation work --- src/core/spu.cpp | 171 ++++++++++++++++++++++++++++----------- src/core/spu.h | 23 +++++- src/duckstation/main.cpp | 2 +- 3 files changed, 146 insertions(+), 50 deletions(-) diff --git a/src/core/spu.cpp b/src/core/spu.cpp index 103f56c5c..e827a0394 100644 --- a/src/core/spu.cpp +++ b/src/core/spu.cpp @@ -14,6 +14,18 @@ static s16 Clamp16(s32 value) return static_cast(std::clamp(value, -32768, 32767)); } +static constexpr float S16ToFloat(s16 value) +{ + return (value >= 0) ? (static_cast(value) / static_cast(std::numeric_limits::max())) : + (static_cast(value) / -static_cast(std::numeric_limits::min())); +} + +static constexpr s16 FloatToS16(float value) +{ + return (value >= 0.0f) ? (static_cast(value * static_cast(std::numeric_limits::max()))) : + (static_cast(value * -static_cast(std::numeric_limits::min()))); +} + SPU::SPU() = default; SPU::~SPU() = default; @@ -51,6 +63,30 @@ u16 SPU::ReadRegister(u32 offset) switch (offset) { + case 0x1F801D80 - SPU_BASE: + return m_main_volume_left.bits; + + case 0x1F801D82 - SPU_BASE: + return m_main_volume_right.bits; + + case 0x1F801D88 - SPU_BASE: + return Truncate16(m_key_on_register); + + case 0x1F801D8A - SPU_BASE: + return Truncate16(m_key_on_register >> 16); + + case 0x1F801D8C - SPU_BASE: + return Truncate16(m_key_off_register); + + case 0x1F801D8E - SPU_BASE: + return Truncate16(m_key_off_register >> 16); + + case 0x1F801D98 - SPU_BASE: + return Truncate16(m_reverb_on_register); + + case 0x1F801D9A - SPU_BASE: + return Truncate16(m_reverb_on_register >> 16); + case 0x1F801DA6 - SPU_BASE: Log_DebugPrintf("SPU transfer address register -> 0x%04X", ZeroExtend32(m_transfer_address_reg)); return m_transfer_address_reg; @@ -67,18 +103,6 @@ u16 SPU::ReadRegister(u32 offset) // Log_DebugPrintf("SPU status register -> 0x%04X", ZeroExtend32(m_SPUCNT.bits)); return m_SPUSTAT.bits; - case 0x1F801D88 - SPU_BASE: - return Truncate16(m_key_on_register); - - case 0x1F801D8A - SPU_BASE: - return Truncate16(m_key_on_register >> 16); - - case 0x1F801D8C - SPU_BASE: - return Truncate16(m_key_off_register); - - case 0x1F801D8E - SPU_BASE: - return Truncate16(m_key_off_register >> 16); - default: Log_ErrorPrintf("Unknown SPU register read: offset 0x%X (address 0x%08X)", offset, offset | SPU_BASE); return UINT16_C(0xFFFF); @@ -119,6 +143,22 @@ void SPU::WriteRegister(u32 offset, u16 value) return; } + case 0x1F801D80 - SPU_BASE: + { + Log_DebugPrintf("SPU main volume left <- 0x%04X", ZeroExtend32(value)); + m_system->Synchronize(); + m_main_volume_left.bits = value; + return; + } + + case 0x1F801D82 - SPU_BASE: + { + Log_DebugPrintf("SPU main volume right <- 0x%04X", ZeroExtend32(value)); + m_system->Synchronize(); + m_main_volume_right.bits = value; + return; + } + case 0x1F801D88 - SPU_BASE: { Log_DebugPrintf("SPU key on low <- 0x%04X", ZeroExtend32(value)); @@ -193,6 +233,22 @@ void SPU::WriteRegister(u32 offset, u16 value) bits >>= 1; } } + break; + + case 0x1F801D98 - SPU_BASE: + { + Log_DebugPrintf("SPU reverb on register <- 0x%04X", ZeroExtend32(value)); + m_system->Synchronize(); + m_reverb_on_register = (m_reverb_on_register & 0xFFFF0000) | ZeroExtend32(value); + } + break; + + case 0x1F801D9A - SPU_BASE: + { + Log_DebugPrintf("SPU reverb off register <- 0x%04X", ZeroExtend32(value)); + m_system->Synchronize(); + m_reverb_on_register = (m_reverb_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); + } break; // read-only registers @@ -212,8 +268,8 @@ void SPU::WriteRegister(u32 offset, u16 value) u16 SPU::ReadVoiceRegister(u32 offset) { - const u32 reg_index = (offset & 0x0F) / 2; - const u32 voice_index = ((offset >> 4) & 0x1F); + const u32 reg_index = (offset % 0x10) / 2; //(offset & 0x0F) / 2; + const u32 voice_index = (offset / 0x10); //((offset >> 4) & 0x1F); Assert(voice_index < 24); return m_voices[voice_index].regs.index[reg_index]; @@ -222,65 +278,69 @@ u16 SPU::ReadVoiceRegister(u32 offset) void SPU::WriteVoiceRegister(u32 offset, u16 value) { // per-voice registers - const u32 reg_index = (offset & 0x0F); - const u32 voice_index = ((offset >> 4) & 0x1F); + const u32 reg_index = (offset % 0x10); + const u32 voice_index = (offset / 0x10); Assert(voice_index < 24); + Voice& voice = m_voices[voice_index]; + if (voice.key_on) + m_system->Synchronize(); + switch (reg_index) { case 0x00: // volume left { Log_DebugPrintf("SPU voice %u volume left <- 0x%04X", voice_index, value); - m_voices[voice_index].regs.volume_left.bits = value; + voice.regs.volume_left.bits = value; } break; case 0x02: // volume right { Log_DebugPrintf("SPU voice %u volume right <- 0x%04X", voice_index, value); - m_voices[voice_index].regs.volume_right.bits = value; + voice.regs.volume_right.bits = value; } break; case 0x04: // sample rate { Log_DebugPrintf("SPU voice %u ADPCM sample rate <- 0x%04X", voice_index, value); - m_voices[voice_index].regs.adpcm_sample_rate = value; + voice.regs.adpcm_sample_rate = value; } break; case 0x06: // start address { Log_DebugPrintf("SPU voice %u ADPCM start address <- 0x%04X", voice_index, value); - m_voices[voice_index].regs.adpcm_start_address = value; + voice.regs.adpcm_start_address = value; } break; case 0x08: // adsr low { Log_WarningPrintf("SPU voice %u ADSR low <- 0x%04X", voice_index, value); - m_voices[voice_index].regs.adsr.bits_low = value; + voice.regs.adsr.bits_low = value; } break; case 0x0A: // adsr high { Log_WarningPrintf("SPU voice %u ADSR high <- 0x%04X", voice_index, value); - m_voices[voice_index].regs.adsr.bits_high = value; + voice.regs.adsr.bits_high = value; } break; case 0x0C: // adsr volume { Log_DebugPrintf("SPU voice %u ADSR volume <- 0x%04X", voice_index, value); - m_voices[voice_index].regs.adsr_volume = value; + voice.regs.adsr_volume = value; } break; case 0x0E: // repeat address { Log_DebugPrintf("SPU voice %u ADPCM repeat address <- 0x%04X", voice_index, value); - m_voices[voice_index].regs.adpcm_repeat_address = value; + voice.regs.adpcm_repeat_address = value; } break; @@ -373,7 +433,7 @@ SPU::SampleFormat SPU::Voice::SampleBlock(s32 index) const return current_block_samples[index]; } -s32 SPU::Voice::Interpolate() const +s16 SPU::Voice::Interpolate() const { static constexpr std::array gauss = {{ -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, // @@ -445,10 +505,10 @@ s32 SPU::Voice::Interpolate() const const u8 i = counter.interpolation_index; const s32 s = static_cast(ZeroExtend32(counter.sample_index.GetValue())); - s32 out = gauss[0x0FF - i] * s32(SampleBlock(s - 3)); - out += gauss[0x1FF - i] * s32(SampleBlock(s - 2)); - out += gauss[0x100 + i] * s32(SampleBlock(s - 1)); - out += gauss[0x000 + i] * s32(SampleBlock(s - 0)); + s16 out = s16(gauss[0x0FF - i] * s32(SampleBlock(s - 3)) >> 15); + out += s16(gauss[0x1FF - i] * s32(SampleBlock(s - 2)) >> 15); + out += s16(gauss[0x100 + i] * s32(SampleBlock(s - 1)) >> 15); + out += s16(gauss[0x000 + i] * s32(SampleBlock(s - 0)) >> 15); return out; } @@ -491,8 +551,8 @@ void SPU::DecodeADPCMBlock(const ADPCMBlock& block, SampleFormat out_samples[NUM static constexpr std::array filter_table_neg = {{0, 0, -52, -55, -60}}; // pre-lookup - const u8 shift = block.shift_filter.shift; - const u8 filter_index = std::min(block.shift_filter.filter, 4); + const u8 shift = block.GetShift(); + const u8 filter_index = block.GetFilter(); const s32 filter_pos = filter_table_pos[filter_index]; const s32 filter_neg = filter_table_neg[filter_index]; s32 last_samples[2] = {state[0], state[1]}; @@ -500,18 +560,16 @@ void SPU::DecodeADPCMBlock(const ADPCMBlock& block, SampleFormat out_samples[NUM // samples for (u32 i = 0; i < NUM_SAMPLES_PER_ADPCM_BLOCK; i++) { - const u8 nibble = (block.data[i / 2] >> (4 * (i % 2))) & 0x0F; - s32 sample = SignExtendN<4, s32>(static_cast(ZeroExtend32(nibble))); - sample >>= shift; - - sample += ((last_samples[0] * filter_pos) + (last_samples[1] * filter_neg) + 32) / 64; + // extend 4-bit to 16-bit, apply shift from header and mix in previous samples + const s16 sample = static_cast(ZeroExtend16(block.GetNibble(i)) << 12) >> shift; + const s32 interp_sample = s32(sample) + ((last_samples[0] * filter_pos) + (last_samples[1] * filter_neg) + 32) / 64; - out_samples[i] = - static_cast(std::clamp(sample, std::numeric_limits::min(), std::numeric_limits::max())); - - state[1] = state[0]; - state[0] = sample; + out_samples[i] = Clamp16(interp_sample); + last_samples[1] = last_samples[0]; + last_samples[0] = interp_sample; } + + std::copy_n(last_samples, countof(last_samples), state); } std::tuple SPU::SampleVoice(u32 voice_index) @@ -566,8 +624,20 @@ std::tuple SPU::SampleVoice(u32 voice_inde } // TODO: Volume - s32 sample = voice.Interpolate(); - return std::make_tuple(Clamp16(sample), Clamp16(sample)); + const s32 sample = voice.Interpolate(); + // s32 sample = voice.SampleBlock(voice.counter.sample_index); + const s16 sample16 = Clamp16(sample); + const float samplef = S16ToFloat(sample16); + + // apply volume + const float volume_left = S16ToFloat(voice.regs.volume_left.GetVolume()); + const float volume_right = S16ToFloat(voice.regs.volume_right.GetVolume()); + const float final_left = volume_left * samplef; + const float final_right = volume_right * samplef; + + return std::make_tuple(FloatToS16(final_left), FloatToS16(final_right)); + // return std::make_tuple(FloatToS16(samplef), FloatToS16(samplef)); + // return std::make_tuple(sample16, sample16); } void SPU::GenerateSample() @@ -581,9 +651,20 @@ void SPU::GenerateSample() right_sum += right; } - Log_DebugPrintf("SPU sample %d %d", left_sum, right_sum); + // Log_DebugPrintf("SPU sample %d %d", left_sum, right_sum); AudioStream::SampleType samples[2] = {Clamp16(left_sum), Clamp16(right_sum)}; - m_audio_stream->WriteSamples(samples, countof(samples)); + m_audio_stream->WriteSamples(samples, 1); + +#if 0 + static FILE* fp = nullptr; + if (!fp) + fp = std::fopen("D:\\spu.raw", "wb"); + if (fp) + { + std::fwrite(samples, sizeof(AudioStream::SampleType), 2, fp); + std::fflush(fp); + } +#endif } void SPU::DrawDebugWindow() diff --git a/src/core/spu.h b/src/core/spu.h index 8566a3d97..b98ed086f 100644 --- a/src/core/spu.h +++ b/src/core/spu.h @@ -115,13 +115,15 @@ private: u16 bits; BitField sweep_mode; - BitField fixed_volume; // divided by 2 + BitField fixed_volume; // divided by 2 BitField sweep_exponential; BitField sweep_direction_decrease; BitField sweep_phase_negative; BitField sweep_shift; BitField sweep_step; + + s16 GetVolume() { return fixed_volume * 2; } }; // organized so we can replace this with a u16 array in the future @@ -173,7 +175,16 @@ private: u8 data[NUM_SAMPLES_PER_ADPCM_BLOCK / 2]; - u8 ReadSample(u32 index) const { return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; } + // For both 4bit and 8bit ADPCM, reserved shift values 13..15 will act same as shift=9). + u8 GetShift() const + { + const u8 shift = shift_filter.shift; + return (shift > 12) ? 9 : shift; + } + + u8 GetFilter() const { return std::min(shift_filter.filter, 4); } + + u8 GetNibble(u32 index) const { return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; } }; struct Voice @@ -181,7 +192,7 @@ private: u16 current_address; VoiceRegisters regs; VoiceCounter counter; - ADPCMBlock current_block; // TODO Drop this after decoding + ADPCMBlock current_block; // TODO Drop this after decoding std::array current_block_samples; std::array previous_block_last_samples; std::array adpcm_state; @@ -194,7 +205,7 @@ private: void DecodeBlock(); SampleFormat SampleBlock(s32 index) const; - s32 Interpolate() const; + s16 Interpolate() const; }; u16 ReadVoiceRegister(u32 offset); @@ -223,9 +234,13 @@ private: u16 m_irq_address = 0; + VolumeRegister m_main_volume_left = {}; + VolumeRegister m_main_volume_right = {}; + u32 m_key_on_register = 0; u32 m_key_off_register = 0; u32 m_endx_register = 0; + u32 m_reverb_on_register = 0; TickCount m_ticks_carry = 0; diff --git a/src/duckstation/main.cpp b/src/duckstation/main.cpp index df0c8a535..ea480d476 100644 --- a/src/duckstation/main.cpp +++ b/src/duckstation/main.cpp @@ -96,7 +96,7 @@ int main(int argc, char* argv[]) #else g_pLog->SetConsoleOutputParams(true, nullptr, LOGLEVEL_DEBUG); // g_pLog->SetConsoleOutputParams(true, "GPU GPU_HW_OpenGL SPU Pad DigitalController", LOGLEVEL_DEBUG); - // g_pLog->SetConsoleOutputParams(true, "GPU GPU_HW_OpenGL SPU Pad DigitalController InterruptController", LOGLEVEL_DEBUG); + g_pLog->SetConsoleOutputParams(true, "GPU GPU_HW_OpenGL Pad DigitalController InterruptController", LOGLEVEL_DEBUG); // g_pLog->SetFilterLevel(LOGLEVEL_TRACE); g_pLog->SetFilterLevel(LOGLEVEL_DEBUG); // g_pLog->SetFilterLevel(LOGLEVEL_DEV);