From 0a6295a9b429705c1446cff2f830f840035e3c50 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Mon, 23 Mar 2020 00:28:51 +1000 Subject: [PATCH] SPU: Implement volume sweep functionality --- src/core/save_state_version.h | 2 +- src/core/spu.cpp | 210 +++++++++++++++++++--------------- src/core/spu.h | 48 +++++--- 3 files changed, 155 insertions(+), 105 deletions(-) diff --git a/src/core/save_state_version.h b/src/core/save_state_version.h index 0f8b5ff6f..f8fa7bd8a 100644 --- a/src/core/save_state_version.h +++ b/src/core/save_state_version.h @@ -2,4 +2,4 @@ #include "types.h" static constexpr u32 SAVE_STATE_MAGIC = 0x43435544; -static constexpr u32 SAVE_STATE_VERSION = 8; +static constexpr u32 SAVE_STATE_VERSION = 9; diff --git a/src/core/spu.cpp b/src/core/spu.cpp index 30773d87a..122d4ce7c 100644 --- a/src/core/spu.cpp +++ b/src/core/spu.cpp @@ -11,10 +11,7 @@ Log_SetChannel(SPU); // TODO: -// - Reverb // - Noise -// - Volume Sweep -// - Pulse Modulation SPU::SPU() = default; @@ -40,8 +37,10 @@ void SPU::Reset() m_transfer_address_reg = 0; m_irq_address = 0; m_capture_buffer_position = 0; - m_main_volume_left.bits = 0; - m_main_volume_right.bits = 0; + m_main_volume_left_reg.bits = 0; + m_main_volume_right_reg.bits = 0; + m_main_volume_left = {}; + m_main_volume_right = {}; m_cd_audio_volume_left = 0; m_cd_audio_volume_right = 0; m_key_on_register = 0; @@ -65,12 +64,7 @@ void SPU::Reset() v.current_block_samples.fill(s16(0)); v.previous_block_last_samples.fill(s16(0)); v.adpcm_last_samples.fill(s32(0)); - v.adsr_ticks_remaining = 0; - v.adsr_phase = ADSRPhase::Off; - v.adsr_target = 0; - v.adsr_rate = 0; - v.adsr_decreasing = false; - v.adsr_exponential = false; + v.SetADSRPhase(ADSRPhase::Off); v.has_samples = false; } @@ -91,8 +85,10 @@ bool SPU::DoState(StateWrapper& sw) sw.Do(&m_transfer_address_reg); sw.Do(&m_irq_address); sw.Do(&m_capture_buffer_position); - sw.Do(&m_main_volume_left.bits); - sw.Do(&m_main_volume_right.bits); + sw.Do(&m_main_volume_left_reg.bits); + sw.Do(&m_main_volume_right_reg.bits); + sw.DoPOD(&m_main_volume_left); + sw.DoPOD(&m_main_volume_right); sw.Do(&m_cd_audio_volume_left); sw.Do(&m_cd_audio_volume_right); sw.Do(&m_key_on_register); @@ -117,12 +113,11 @@ bool SPU::DoState(StateWrapper& sw) sw.Do(&v.previous_block_last_samples); sw.Do(&v.adpcm_last_samples); sw.Do(&v.last_amplitude); - sw.Do(&v.adsr_ticks_remaining); - sw.Do(&v.adsr_target); + sw.DoPOD(&v.left_volume); + sw.DoPOD(&v.right_volume); + sw.DoPOD(&v.adsr_envelope); sw.Do(&v.adsr_phase); - sw.Do(&v.adsr_rate); - sw.Do(&v.adsr_decreasing); - sw.Do(&v.adsr_exponential); + sw.Do(&v.adsr_target); sw.Do(&v.has_samples); } @@ -144,10 +139,10 @@ u16 SPU::ReadRegister(u32 offset) switch (offset) { case 0x1F801D80 - SPU_BASE: - return m_main_volume_left.bits; + return m_main_volume_left_reg.bits; case 0x1F801D82 - SPU_BASE: - return m_main_volume_right.bits; + return m_main_volume_right_reg.bits; case 0x1F801D84 - SPU_BASE: return m_reverb_registers.vLOUT; @@ -241,7 +236,8 @@ void SPU::WriteRegister(u32 offset, u16 value) { Log_DebugPrintf("SPU main volume left <- 0x%04X", ZeroExtend32(value)); m_tick_event->InvokeEarly(); - m_main_volume_left.bits = value; + m_main_volume_left_reg.bits = value; + m_main_volume_left.Reset(m_main_volume_left_reg); return; } @@ -249,7 +245,8 @@ void SPU::WriteRegister(u32 offset, u16 value) { Log_DebugPrintf("SPU main volume right <- 0x%04X", ZeroExtend32(value)); m_tick_event->InvokeEarly(); - m_main_volume_right.bits = value; + m_main_volume_right_reg.bits = value; + m_main_volume_right.Reset(m_main_volume_right_reg); return; } @@ -522,6 +519,7 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value) { Log_DebugPrintf("SPU voice %u volume left <- 0x%04X", voice_index, value); voice.regs.volume_left.bits = value; + voice.left_volume.Reset(voice.regs.volume_left); } break; @@ -529,6 +527,7 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value) { Log_DebugPrintf("SPU voice %u volume right <- 0x%04X", voice_index, value); voice.regs.volume_right.bits = value; + voice.right_volume.Reset(voice.regs.volume_right); } break; @@ -772,8 +771,10 @@ void SPU::Execute(TickCount ticks) right_sum += m_reverb_right_output; // Apply main volume before clamping. - *(output_frame++) = Clamp16(ApplyVolume(left_sum, m_main_volume_left.GetVolume())); - *(output_frame++) = Clamp16(ApplyVolume(right_sum, m_main_volume_right.GetVolume())); + *(output_frame++) = Clamp16(ApplyVolume(left_sum, m_main_volume_left.current_level)); + *(output_frame++) = Clamp16(ApplyVolume(right_sum, m_main_volume_right.current_level)); + m_main_volume_left.Tick(); + m_main_volume_right.Tick(); // Write to capture buffers. WriteToCaptureBuffer(0, cd_audio_left); @@ -855,7 +856,6 @@ void SPU::Voice::KeyOn() regs.adsr_volume = 0; has_samples = false; SetADSRPhase(ADSRPhase::Attack); - adsr_ticks_remaining = 0; } void SPU::Voice::KeyOff() @@ -864,7 +864,6 @@ void SPU::Voice::KeyOff() return; SetADSRPhase(ADSRPhase::Release); - adsr_ticks_remaining = 0; } SPU::ADSRPhase SPU::GetNextADSRPhase(ADSRPhase phase) @@ -934,6 +933,80 @@ static constexpr ADSRTableEntries ComputeADSRTableEntries() static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); +void SPU::VolumeEnvelope::Reset(u8 rate_, bool decreasing_, bool exponential_) +{ + rate = rate_; + decreasing = decreasing_; + exponential = exponential_; + + const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate]; + counter = table_entry.ticks; +} + +s16 SPU::VolumeEnvelope::Tick(s16 current_level) +{ + counter--; + if (counter > 0) + return current_level; + + const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate]; + s32 this_step = table_entry.step; + counter = table_entry.ticks; + + if (exponential) + { + if (decreasing) + { + this_step = (this_step * current_level) >> 15; + } + else + { + if (current_level >= 0x6000) + { + if (rate < 40) + { + this_step >>= 2; + } + else if (rate >= 44) + { + counter >>= 2; + } + else + { + this_step >>= 1; + counter >>= 1; + } + } + } + } + + return static_cast( + std::clamp(static_cast(current_level) + this_step, ENVELOPE_MIN_VOLUME, ENVELOPE_MAX_VOLUME)); +} + +void SPU::VolumeSweep::Reset(VolumeRegister reg) +{ + if (!reg.sweep_mode) + { + current_level = reg.fixed_volume_shr1 * 2; + envelope_active = false; + return; + } + + envelope.Reset(reg.sweep_rate, reg.sweep_direction_decrease, reg.sweep_exponential); + envelope_active = true; +} + +void SPU::VolumeSweep::Tick() +{ + if (!envelope_active) + return; + + current_level = envelope.Tick(current_level); + envelope_active = + (envelope.decreasing ? (current_level > ENVELOPE_MIN_VOLUME) : (current_level < ENVELOPE_MAX_VOLUME)); +} + void SPU::Voice::SetADSRPhase(ADSRPhase phase) { adsr_phase = phase; @@ -941,36 +1014,28 @@ void SPU::Voice::SetADSRPhase(ADSRPhase phase) { case ADSRPhase::Off: adsr_target = 0; - adsr_decreasing = false; - adsr_exponential = false; + adsr_envelope.Reset(0, false, false); return; case ADSRPhase::Attack: adsr_target = 32767; // 0 -> max - adsr_decreasing = false; - adsr_exponential = regs.adsr.attack_exponential; - adsr_rate = regs.adsr.attack_rate; + adsr_envelope.Reset(regs.adsr.attack_rate, false, regs.adsr.attack_exponential); break; case ADSRPhase::Decay: - adsr_target = (u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800; // max -> sustain level - adsr_decreasing = true; - adsr_exponential = true; - adsr_rate = regs.adsr.decay_rate_shr2 << 2; + adsr_target = static_cast(std::min((u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800, + ENVELOPE_MAX_VOLUME)); // max -> sustain level + adsr_envelope.Reset(regs.adsr.decay_rate_shr2 << 2, true, true); break; case ADSRPhase::Sustain: adsr_target = 0; - adsr_decreasing = regs.adsr.sustain_direction_decrease; - adsr_exponential = regs.adsr.sustain_exponential; - adsr_rate = regs.adsr.sustain_rate; + adsr_envelope.Reset(regs.adsr.sustain_rate, regs.adsr.sustain_direction_decrease, regs.adsr.sustain_exponential); break; case ADSRPhase::Release: adsr_target = 0; - adsr_decreasing = true; - adsr_exponential = regs.adsr.release_exponential; - adsr_rate = regs.adsr.release_rate_shr2 << 2; + adsr_envelope.Reset(regs.adsr.release_rate_shr2 << 2, true, regs.adsr.release_exponential); break; default: @@ -980,49 +1045,12 @@ void SPU::Voice::SetADSRPhase(ADSRPhase phase) void SPU::Voice::TickADSR() { - adsr_ticks_remaining--; - if (adsr_ticks_remaining > 0) - return; - - // set up for next tick - const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(adsr_decreasing)][adsr_rate]; - - s32 this_step = table_entry.step; - adsr_ticks_remaining = table_entry.ticks; - - if (adsr_exponential) - { - if (adsr_decreasing) - { - this_step = (this_step * regs.adsr_volume) >> 15; - } - else - { - if (regs.adsr_volume >= 0x6000) - { - if (adsr_rate < 40) - { - this_step >>= 2; - } - else if (adsr_rate >= 44) - { - adsr_ticks_remaining >>= 2; - } - else - { - this_step >>= 1; - adsr_ticks_remaining >>= 1; - } - } - } - } - - const s32 new_volume = s32(regs.adsr_volume) + s32(this_step); - regs.adsr_volume = static_cast(std::clamp(new_volume, ADSR_MIN_VOLUME, ADSR_MAX_VOLUME)); + regs.adsr_volume = adsr_envelope.Tick(regs.adsr_volume); if (adsr_phase != ADSRPhase::Sustain) { - const bool reached_target = adsr_decreasing ? (new_volume <= adsr_target) : (new_volume >= adsr_target); + const bool reached_target = + adsr_envelope.decreasing ? (regs.adsr_volume <= adsr_target) : (regs.adsr_volume >= adsr_target); if (reached_target) SetADSRPhase(GetNextADSRPhase(adsr_phase)); } @@ -1245,8 +1273,10 @@ std::tuple SPU::SampleVoice(u32 voice_index) } // apply per-channel volume - const s32 left = ApplyVolume(amplitude, voice.regs.volume_left.GetVolume()); - const s32 right = ApplyVolume(amplitude, voice.regs.volume_right.GetVolume()); + const s32 left = ApplyVolume(amplitude, voice.left_volume.current_level); + const s32 right = ApplyVolume(amplitude, voice.right_volume.current_level); + voice.left_volume.Tick(); + voice.right_volume.Tick(); return std::make_tuple(left, right); } @@ -1438,7 +1468,7 @@ void SPU::DrawDebugStateWindow() static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f}; const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; - ImGui::SetNextWindowSize(ImVec2(800.0f * framebuffer_scale, 600.0f * framebuffer_scale), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(800.0f * framebuffer_scale, 800.0f * framebuffer_scale), ImGuiCond_FirstUseEver); if (!ImGui::Begin("SPU State", &m_system->GetSettings().debugging.show_spu_state)) { ImGui::End(); @@ -1487,9 +1517,9 @@ void SPU::DrawDebugStateWindow() ImGui::Text("Volume: "); ImGui::SameLine(offsets[0]); - ImGui::Text("Left: %d%%", ApplyVolume(100, m_main_volume_left.GetVolume())); + ImGui::Text("Left: %d%%", ApplyVolume(100, m_main_volume_left.current_level)); ImGui::SameLine(offsets[1]); - ImGui::Text("Right: %d%%", ApplyVolume(100, m_main_volume_right.GetVolume())); + ImGui::Text("Right: %d%%", ApplyVolume(100, m_main_volume_right.current_level)); ImGui::Text("CD Audio: "); ImGui::SameLine(offsets[0]); @@ -1540,15 +1570,15 @@ void SPU::DrawDebugStateWindow() ImGui::NextColumn(); ImGui::TextColored(color, "%.2f", (float(v.regs.adpcm_sample_rate) / 4096.0f) * 44100.0f); ImGui::NextColumn(); - ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.volume_left.bits)); + ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.left_volume.current_level)); ImGui::NextColumn(); - ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.volume_right.bits)); + ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.right_volume.current_level)); ImGui::NextColumn(); ImGui::TextColored(color, "%s", adsr_phases[static_cast(v.adsr_phase)]); ImGui::NextColumn(); - ImGui::TextColored(color, "%d", ZeroExtend32(v.regs.adsr_volume)); + ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.regs.adsr_volume)); ImGui::NextColumn(); - ImGui::TextColored(color, "%d", v.adsr_ticks_remaining); + ImGui::TextColored(color, "%d", v.adsr_envelope.counter); ImGui::NextColumn(); } diff --git a/src/core/spu.h b/src/core/spu.h index 296826fc9..1677f7a94 100644 --- a/src/core/spu.h +++ b/src/core/spu.h @@ -65,8 +65,8 @@ private: static constexpr u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; static constexpr u32 SAMPLE_RATE = 44100; static constexpr u32 SYSCLK_TICKS_PER_SPU_TICK = MASTER_CLOCK / SAMPLE_RATE; // 0x300 - static constexpr s16 ADSR_MIN_VOLUME = 0; - static constexpr s16 ADSR_MAX_VOLUME = 0x7FFF; + static constexpr s16 ENVELOPE_MIN_VOLUME = 0; + static constexpr s16 ENVELOPE_MAX_VOLUME = 0x7FFF; static constexpr u32 CD_AUDIO_SAMPLE_BUFFER_SIZE = 44100 * 2; static constexpr u32 CAPTURE_BUFFER_SIZE_PER_CHANNEL = 0x400; static constexpr u32 MINIMUM_TICKS_BETWEEN_KEY_ON_OFF = 2; @@ -145,15 +145,12 @@ private: u16 bits; BitField sweep_mode; - BitField fixed_volume; // divided by 2 + BitField fixed_volume_shr1; // 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; } + BitField sweep_rate; }; // organized so we can replace this with a u16 array in the future @@ -218,6 +215,27 @@ private: u8 GetNibble(u32 index) const { return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; } }; + struct VolumeEnvelope + { + s32 counter; + u8 rate; + bool decreasing; + bool exponential; + + void Reset(u8 rate_, bool decreasing_, bool exponential_); + s16 Tick(s16 current_level); + }; + + struct VolumeSweep + { + VolumeEnvelope envelope; + bool envelope_active; + s16 current_level; + + void Reset(VolumeRegister reg); + void Tick(); + }; + enum class ADSRPhase : u8 { Off = 0, @@ -238,12 +256,12 @@ private: std::array adpcm_last_samples; s32 last_amplitude; - TickCount adsr_ticks_remaining; - s32 adsr_target; + VolumeSweep left_volume; + VolumeSweep right_volume; + + VolumeEnvelope adsr_envelope; ADSRPhase adsr_phase; - u8 adsr_rate; - bool adsr_decreasing; - bool adsr_exponential; + s16 adsr_target; bool has_samples; bool IsOn() const { return adsr_phase != ADSRPhase::Off; } @@ -366,8 +384,10 @@ private: u16 m_irq_address = 0; u16 m_capture_buffer_position = 0; - VolumeRegister m_main_volume_left = {}; - VolumeRegister m_main_volume_right = {}; + VolumeRegister m_main_volume_left_reg = {}; + VolumeRegister m_main_volume_right_reg = {}; + VolumeSweep m_main_volume_left = {}; + VolumeSweep m_main_volume_right = {}; s16 m_cd_audio_volume_left = 0; s16 m_cd_audio_volume_right = 0;