From 7bf8f6115f4bbce9dcbb48cdfa8f1dd0955543e4 Mon Sep 17 00:00:00 2001 From: Ziemas Date: Sun, 8 Oct 2023 09:50:10 +0200 Subject: [PATCH] SPU2: Rewrite ADSR [SAVEVERSION+] --- pcsx2/SPU2/ADSR.cpp | 277 +++++++++++++++------------------------- pcsx2/SPU2/Debug.cpp | 20 +-- pcsx2/SPU2/Mixer.cpp | 13 +- pcsx2/SPU2/RegTable.cpp | 2 +- pcsx2/SPU2/defs.h | 81 ++++++++---- pcsx2/SPU2/spu2sys.cpp | 25 ++-- pcsx2/SaveState.h | 2 +- 7 files changed, 192 insertions(+), 228 deletions(-) diff --git a/pcsx2/SPU2/ADSR.cpp b/pcsx2/SPU2/ADSR.cpp index 07a68626fa..d69b29b9fb 100644 --- a/pcsx2/SPU2/ADSR.cpp +++ b/pcsx2/SPU2/ADSR.cpp @@ -21,7 +21,7 @@ #include -static constexpr s32 ADSR_MAX_VOL = 0x7fffffff; +static constexpr s32 ADSR_MAX_VOL = 0x7fff; static const int InvExpOffsets[] = {0, 4, 6, 8, 9, 10, 11, 12}; @@ -47,191 +47,120 @@ static constexpr PSXRateTable ComputePSXRates() static constexpr const PSXRateTable PsxRates = ComputePSXRates(); -bool V_ADSR::Calculate() +void V_ADSR::UpdateCache() { - pxAssume(Phase != 0); + CachedPhases[PHASE_ATTACK].Decr = false; + CachedPhases[PHASE_ATTACK].Exp = AttackMode; + CachedPhases[PHASE_ATTACK].Shift = AttackShift; + CachedPhases[PHASE_ATTACK].Step = 7 - AttackStep; + CachedPhases[PHASE_ATTACK].Target = ADSR_MAX_VOL; - if (Releasing && (Phase < 5)) - Phase = 5; + CachedPhases[PHASE_DECAY].Decr = true; + CachedPhases[PHASE_DECAY].Exp = true; + CachedPhases[PHASE_DECAY].Shift = DecayShift; + CachedPhases[PHASE_DECAY].Step = -8; + CachedPhases[PHASE_DECAY].Target = (SustainLevel + 1) << 11; - switch (Phase) - { - case 1: // attack - if (Value == ADSR_MAX_VOL) - { - // Already maxed out. Progress phase and nothing more: - Phase++; - break; - } + CachedPhases[PHASE_SUSTAIN].Decr = SustainDir; + CachedPhases[PHASE_SUSTAIN].Exp = SustainMode; + CachedPhases[PHASE_SUSTAIN].Shift = SustainShift; + CachedPhases[PHASE_SUSTAIN].Step = 7 - SustainStep; - // Case 1 below is for pseudo exponential below 75%. - // Pseudo Exp > 75% and Linear are the same. + if (CachedPhases[PHASE_SUSTAIN].Decr) + CachedPhases[PHASE_SUSTAIN].Step = ~CachedPhases[PHASE_SUSTAIN].Step; - if (AttackMode && (Value >= 0x60000000)) - Value += PsxRates[(AttackRate ^ 0x7f) - 0x18 + 32]; - else - Value += PsxRates[(AttackRate ^ 0x7f) - 0x10 + 32]; + CachedPhases[PHASE_SUSTAIN].Target = 0; - if (Value < 0) - { - // We hit the ceiling. - Phase++; - Value = ADSR_MAX_VOL; - } - break; - - case 2: // decay - { - const u32 off = InvExpOffsets[(Value >> 28) & 7]; - Value -= PsxRates[((DecayRate ^ 0x1f) * 4) - 0x18 + off + 32]; - - // calculate sustain level as a factor of the ADSR maximum volume. - - s32 suslev = ((0x80000000 / 0x10) * (SustainLevel + 1)) - 1; - - if (Value <= suslev) - { - if (Value < 0) - Value = 0; - Phase++; - } - } - break; - - case 3: // sustain - { - // 0x7f disables sustain (infinite sustain) - if (SustainRate == 0x7f) - return true; - - if (SustainMode & 2) // decreasing - { - if (SustainMode & 4) // exponential - { - const u32 off = InvExpOffsets[(Value >> 28) & 7]; - Value -= PsxRates[(SustainRate ^ 0x7f) - 0x1b + off + 32]; - } - else // linear - Value -= PsxRates[(SustainRate ^ 0x7f) - 0xf + 32]; - - if (Value <= 0) - { - Value = 0; - Phase++; - } - } - else - { // increasing - if ((SustainMode & 4) && (Value >= 0x60000000)) - Value += PsxRates[(SustainRate ^ 0x7f) - 0x18 + 32]; - else - // linear / Pseudo below 75% (they're the same) - Value += PsxRates[(SustainRate ^ 0x7f) - 0x10 + 32]; - - if (Value < 0) - { - Value = ADSR_MAX_VOL; - Phase++; - } - } - } - break; - - case 4: // sustain end - Value = (SustainMode & 2) ? 0 : ADSR_MAX_VOL; - if (Value == 0) - Phase = 6; - break; - - case 5: // release - if (ReleaseMode) // exponential - { - const u32 off = InvExpOffsets[(Value >> 28) & 7]; - Value -= PsxRates[((ReleaseRate ^ 0x1f) * 4) - 0x18 + off + 32]; - } - else - { // linear - //Value-=PsxRates[((ReleaseRate^0x1f)*4)-0xc+32]; - if (ReleaseRate != 0x1f) - Value -= (1 << (0x1f - ReleaseRate)); - } - - if (Value <= 0) - { - Value = 0; - Phase++; - } - break; - - case 6: // release end - Value = 0; - break; - - jNO_DEFAULT - } - - // returns true if the voice is active, or false if it's stopping. - return Phase != 6; + CachedPhases[PHASE_RELEASE].Decr = true; + CachedPhases[PHASE_RELEASE].Exp = ReleaseMode; + CachedPhases[PHASE_RELEASE].Shift = ReleaseShift; + CachedPhases[PHASE_RELEASE].Step = -8; + CachedPhases[PHASE_RELEASE].Target = 0; } -///////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////// -// // +bool V_ADSR::Calculate(int voiceidx) +{ + pxAssume(Phase != PHASE_STOPPED); -#define VOLFLAG_REVERSE_PHASE (1ul << 0) -#define VOLFLAG_DECREMENT (1ul << 1) -#define VOLFLAG_EXPONENTIAL (1ul << 2) -#define VOLFLAG_SLIDE_ENABLE (1ul << 3) + auto& p = CachedPhases.at(Phase); + + // maybe not correct for the "infinite" settings + u32 counter_inc = 0x8000 >> std::max(0, p.Shift - 11); + s16 level_inc = (s16)(p.Step << std::max(0, 11 - p.Shift)); + + if (p.Exp) + { + if (!p.Decr && Value > 0x6000) + { + counter_inc >>= 2; + } + + if (p.Decr) + { + level_inc = (s16)((level_inc * Value) >> 15); + } + } + + counter_inc = std::max(1, counter_inc); + Counter += counter_inc; + + if (Counter >= 0x8000) + { + Counter = 0; + Value = std::clamp(Value + level_inc, 0, INT16_MAX); + } + + // Stay in sustain until key off or silence + if (Phase == PHASE_SUSTAIN) + { + return Value != 0; + } + + // Check if target is reached to advance phase + if ((!p.Decr && Value >= p.Target) || (p.Decr && Value <= p.Target)) + { + Phase++; + } + + // All phases done, stop the voice + if (Phase > PHASE_RELEASE) + { + return false; + } + + return true; +} + +void V_ADSR::Attack() +{ + Phase = PHASE_ATTACK; + Counter = 0; + Value = 0; +} + +void V_ADSR::Release() +{ + if (Phase != PHASE_STOPPED) + { + Phase = PHASE_RELEASE; + Counter = 0; + } +} + +void V_VolumeSlide::RegSet(u16 src) +{ + Reg_VOL = src; + if (!Enable) + { + // Shift and sign extend; + Value = (s16)(src << 1); + } +} void V_VolumeSlide::Update() { - if (!(Mode & VOLFLAG_SLIDE_ENABLE)) - return; - - // Volume slides use the same basic logic as ADSR, but simplified (single-stage - // instead of multi-stage) - - if (Increment == 0x7f) - return; - - s32 value = abs(Value); - - if (Mode & VOLFLAG_DECREMENT) + if (!Enable) { - // Decrement - - if (Mode & VOLFLAG_EXPONENTIAL) - { - const u32 off = InvExpOffsets[(value >> 28) & 7]; - value -= PsxRates[(Increment ^ 0x7f) - 0x1b + off + 32]; - } - else - value -= PsxRates[(Increment ^ 0x7f) - 0xf + 32]; - - if (value < 0) - { - value = 0; - Mode = 0; // disable slide - } + return; } - else - { - // Increment - // Pseudo-exponential increments, as done by the SPU2 (really!) - // Above 75% slides slow, below 75% slides fast. It's exponential, pseudo'ly speaking. - - if ((Mode & VOLFLAG_EXPONENTIAL) && (value >= 0x60000000)) - value += PsxRates[(Increment ^ 0x7f) - 0x18 + 32]; - else - // linear / Pseudo below 75% (they're the same) - value += PsxRates[(Increment ^ 0x7f) - 0x10 + 32]; - - if (value < 0) // wrapped around the "top"? - { - value = 0x7fffffff; - Mode = 0; // disable slide - } - } - - Value = (Value < 0) ? -value : value; } diff --git a/pcsx2/SPU2/Debug.cpp b/pcsx2/SPU2/Debug.cpp index 8a1f5059dc..5755586f28 100644 --- a/pcsx2/SPU2/Debug.cpp +++ b/pcsx2/SPU2/Debug.cpp @@ -176,25 +176,29 @@ void SPU2::DoFullDump() Cores[c].Voices[v].Volume.DebugDump(dump, ""); fprintf(dump, " - ADSR Envelope: %x & %x\n" - " - Ar: %x\n" + " - Ash: %x\n" + " - Ast: %x\n" " - Am: %x\n" - " - Dr: %x\n" + " - Dsh: %x\n" " - Sl: %x\n" - " - Sr: %x\n" + " - Ssh: %x\n" + " - Sst: %x\n" " - Sm: %x\n" - " - Rr: %x\n" + " - Rsh: %x\n" " - Rm: %x\n" " - Phase: %x\n" " - Value: %x\n", Cores[c].Voices[v].ADSR.regADSR1, Cores[c].Voices[v].ADSR.regADSR2, - Cores[c].Voices[v].ADSR.AttackRate, + Cores[c].Voices[v].ADSR.AttackShift, + Cores[c].Voices[v].ADSR.AttackStep, Cores[c].Voices[v].ADSR.AttackMode, - Cores[c].Voices[v].ADSR.DecayRate, + Cores[c].Voices[v].ADSR.DecayShift, Cores[c].Voices[v].ADSR.SustainLevel, - Cores[c].Voices[v].ADSR.SustainRate, + Cores[c].Voices[v].ADSR.SustainShift, + Cores[c].Voices[v].ADSR.SustainStep, Cores[c].Voices[v].ADSR.SustainMode, - Cores[c].Voices[v].ADSR.ReleaseRate, + Cores[c].Voices[v].ADSR.ReleaseShift, Cores[c].Voices[v].ADSR.ReleaseMode, Cores[c].Voices[v].ADSR.Phase, Cores[c].Voices[v].ADSR.Value); diff --git a/pcsx2/SPU2/Mixer.cpp b/pcsx2/SPU2/Mixer.cpp index 2d9fb5bccf..20831d63da 100644 --- a/pcsx2/SPU2/Mixer.cpp +++ b/pcsx2/SPU2/Mixer.cpp @@ -287,6 +287,11 @@ static __forceinline s32 ApplyVolume(s32 data, s32 volume) return MulShr32(data << 1, volume); } +static __forceinline s32 ApplyVolume16(s32 data, s32 volume) +{ + return (volume * data) >> 15; +} + static __forceinline StereoOut32 ApplyVolume(const StereoOut32& data, const V_VolumeLR& volume) { return StereoOut32( @@ -323,13 +328,13 @@ static __forceinline void CalculateADSR(V_Core& thiscore, uint voiceidx) { V_Voice& vc(thiscore.Voices[voiceidx]); - if (vc.ADSR.Phase == 0) + if (vc.ADSR.Phase == V_ADSR::PHASE_STOPPED) { vc.ADSR.Value = 0; return; } - if (!vc.ADSR.Calculate()) + if (!vc.ADSR.Calculate(thiscore.Index | (voiceidx << 1))) { if (IsDevBuild) { @@ -465,7 +470,7 @@ static __forceinline StereoOut32 MixVoice(uint coreidx, uint voiceidx) StereoOut32 voiceOut(0, 0); s32 Value = 0; - if (vc.ADSR.Phase > 0) + if (vc.ADSR.Phase > V_ADSR::PHASE_STOPPED) { if (vc.Noise) Value = GetNoiseValues(thiscore); @@ -480,7 +485,7 @@ static __forceinline StereoOut32 MixVoice(uint coreidx, uint voiceidx) // use a full 64-bit multiply/result here. CalculateADSR(thiscore, voiceidx); - Value = ApplyVolume(Value, vc.ADSR.Value); + Value = ApplyVolume16(Value, vc.ADSR.Value); vc.OutX = Value; if (IsDevBuild) diff --git a/pcsx2/SPU2/RegTable.cpp b/pcsx2/SPU2/RegTable.cpp index 65c090b4c3..5ac9321988 100644 --- a/pcsx2/SPU2/RegTable.cpp +++ b/pcsx2/SPU2/RegTable.cpp @@ -34,7 +34,7 @@ PVCP(c, v, Pitch), \ PVCP(c, v, ADSR.regADSR1), \ PVCP(c, v, ADSR.regADSR2), \ - PVCP(c, v, ADSR.Value) + 1, \ + PVCP(c, v, ADSR.Value), \ PVCP(c, v, Volume.Left.Value) + 1, \ PVCP(c, v, Volume.Right.Value) + 1 diff --git a/pcsx2/SPU2/defs.h b/pcsx2/SPU2/defs.h index b0d351b32c..ff710d1799 100644 --- a/pcsx2/SPU2/defs.h +++ b/pcsx2/SPU2/defs.h @@ -19,6 +19,8 @@ #include "SPU2/SndOut.h" #include "SPU2/Global.h" +#include + // -------------------------------------------------------------------------------------- // SPU2 Register Table LUT // -------------------------------------------------------------------------------------- @@ -122,24 +124,49 @@ struct V_ADSR struct { - u32 SustainLevel : 4, - DecayRate : 4, - AttackRate : 7, - AttackMode : 1, // 0 for linear (+lin), 1 for pseudo exponential (+exp) - - ReleaseRate : 5, - ReleaseMode : 1, // 0 for linear (-lin), 1 for exponential (-exp) - SustainRate : 7, - SustainMode : 3; // 0 = +lin, 1 = -lin, 2 = +exp, 3 = -exp + u32 SustainLevel : 4; + u32 DecayShift : 4; + u32 AttackStep : 2; + u32 AttackShift : 5; + u32 AttackMode : 1; + u32 ReleaseShift : 5; + u32 ReleaseMode : 1; + u32 SustainStep : 2; + u32 SustainShift : 5; + u32 : 1; + u32 SustainDir : 1; + u32 SustainMode : 1; }; }; - s32 Value; // Ranges from 0 to 0x7fffffff (signed values are clamped to 0) [Reg_ENVX] - u8 Phase; // monitors current phase of ADSR envelope - bool Releasing; // Ready To Release, triggered by Voice.Stop(); + static constexpr int ADSR_PHASES = 5; + + static constexpr int PHASE_STOPPED = 0; + static constexpr int PHASE_ATTACK = 1; + static constexpr int PHASE_DECAY = 2; + static constexpr int PHASE_SUSTAIN = 3; + static constexpr int PHASE_RELEASE = 4; + + struct CachedADSR + { + bool Decr; + bool Exp; + u8 Shift; + s8 Step; + s32 Target; + }; + + std::array CachedPhases; + + u32 Counter; + s32 Value; // Ranges from 0 to 0x7fff (signed values are clamped to 0) [Reg_ENVX] + u8 Phase; // monitors current phase of ADSR envelope public: - bool Calculate(); + void UpdateCache(); + bool Calculate(int voiceidx); + void Attack(); + void Release(); }; @@ -359,34 +386,34 @@ struct V_Core V_CoreGates WetGate; V_VolumeSlideLR MasterVol; // Master Volume - V_VolumeLR ExtVol; // Volume for External Data Input - V_VolumeLR InpVol; // Volume for Sound Data Input - V_VolumeLR FxVol; // Volume for Output from Effects + V_VolumeLR ExtVol; // Volume for External Data Input + V_VolumeLR InpVol; // Volume for Sound Data Input + V_VolumeLR FxVol; // Volume for Output from Effects V_Voice Voices[NumVoices]; u32 IRQA; // Interrupt Address - u32 TSA; // DMA Transfer Start Address + u32 TSA; // DMA Transfer Start Address u32 ActiveTSA; // Active DMA TSA - Required for NFL 2k5 which overwrites it mid transfer bool IRQEnable; // Interrupt Enable - bool FxEnable; // Effect Enable - bool Mute; // Mute + bool FxEnable; // Effect Enable + bool Mute; // Mute bool AdmaInProgress; - s8 DMABits; // DMA related? - u8 NoiseClk; // Noise Clock - u32 NoiseCnt; // Noise Counter - u32 NoiseOut; // Noise Output - u16 AutoDMACtrl; // AutoDMA Status - s32 DMAICounter; // DMA Interrupt Counter - u32 LastClock; // DMA Interrupt Clock Cycle Counter + s8 DMABits; // DMA related? + u8 NoiseClk; // Noise Clock + u32 NoiseCnt; // Noise Counter + u32 NoiseOut; // Noise Output + u16 AutoDMACtrl; // AutoDMA Status + s32 DMAICounter; // DMA Interrupt Counter + u32 LastClock; // DMA Interrupt Clock Cycle Counter u32 InputDataLeft; // Input Buffer u32 InputDataTransferred; // Used for simulating MADR increase (GTA VC) u32 InputPosWrite; u32 InputDataProgress; - V_Reverb Revb; // Reverb Registers + V_Reverb Revb; // Reverb Registers s32 RevbDownBuf[2][64]; // Downsample buffer for reverb, one for each channel s32 RevbUpBuf[2][64]; // Upsample buffer for reverb, one for each channel diff --git a/pcsx2/SPU2/spu2sys.cpp b/pcsx2/SPU2/spu2sys.cpp index d6ef3540c7..47ae2f8c68 100644 --- a/pcsx2/SPU2/spu2sys.cpp +++ b/pcsx2/SPU2/spu2sys.cpp @@ -207,6 +207,7 @@ void V_Core::Init(int index) Voices[v].Volume = V_VolumeSlideLR(0, 0); // V_VolumeSlideLR::Max; Voices[v].SCurrent = 28; + Voices[v].ADSR.Counter = 0; Voices[v].ADSR.Value = 0; Voices[v].ADSR.Phase = 0; Voices[v].Pitch = 0x3FFF; @@ -236,7 +237,7 @@ void V_Voice::Start() void V_Voice::Stop() { ADSR.Value = 0; - ADSR.Phase = 0; + ADSR.Phase = V_ADSR::PHASE_STOPPED; } uint TickInterval = 768; @@ -255,9 +256,7 @@ __forceinline bool StartQueuedVoice(uint coreidx, uint voiceidx) vc.StartA = (vc.StartA + 0xFFFF8) + 0x8; } - vc.ADSR.Releasing = false; - vc.ADSR.Value = 1; - vc.ADSR.Phase = 1; + vc.ADSR.Attack(); vc.SCurrent = 28; vc.LoopMode = 0; @@ -554,16 +553,18 @@ void V_Core::WriteRegPS1(u32 mem, u16 value) case 0x8: // ADSR1 (Envelope) Voices[voice].ADSR.regADSR1 = value; + Voices[voice].ADSR.UpdateCache(); //ConLog("voice %x regADSR1 write: %x\n", voice, Voices[voice].ADSR.regADSR1); break; case 0xa: // ADSR2 (Envelope) Voices[voice].ADSR.regADSR2 = value; + Voices[voice].ADSR.UpdateCache(); //ConLog("voice %x regADSR2 write: %x\n", voice, Voices[voice].ADSR.regADSR2); break; case 0xc: // Voice 0..23 ADSR Current Volume // not commonly set by games - Voices[voice].ADSR.Value = value * 0x10001U; + Voices[voice].ADSR.Value = value; if (SPU2::MsgToConsole()) SPU2::ConLog("voice %x ADSR.Value write: %x\n", voice, Voices[voice].ADSR.Value); break; @@ -879,7 +880,7 @@ u16 V_Core::ReadRegPS1(u32 mem) value = Voices[voice].ADSR.regADSR2; break; case 0xc: // Voice 0..23 ADSR Current Volume - value = Voices[voice].ADSR.Value >> 16; // no clue + value = Voices[voice].ADSR.Value; //if (value != 0) ConLog("voice %d read ADSR.Value result = %x\n", voice, value); break; case 0xe: @@ -1035,22 +1036,20 @@ static void RegWrite_VoiceParams(u16 value) case 3: // ADSR1 (Envelope) thisvoice.ADSR.regADSR1 = value; + thisvoice.ADSR.UpdateCache(); break; case 4: // ADSR2 (Envelope) thisvoice.ADSR.regADSR2 = value; + thisvoice.ADSR.UpdateCache(); break; - // REG_VP_ENVX, REG_VP_VOLXL and REG_VP_VOLXR have been confirmed to not be allowed to be written to, so code has been commented out. + // REG_VP_ENVX, REG_VP_VOLXL and REG_VP_VOLXR are all writable, only ENVX has any effect when written to. // Colin McRae Rally 2005 triggers case 5 (ADSR), but it doesn't produce issues enabled or disabled. case 5: - // [Air] : Mysterious ADSR set code. Too bad none of my games ever use it. - // (as usual... ) - //thisvoice.ADSR.Value = (value << 16) | value; - //ConLog("* SPU2: Mysterious ADSR Volume Set to 0x%x\n", value); + thisvoice.ADSR.Value = value; break; - case 6: //thisvoice.Volume.Left.RegSet(value); break; @@ -1863,7 +1862,7 @@ void StopVoices(int core, u32 value) continue; } - Cores[core].Voices[vc].ADSR.Releasing = true; + Cores[core].Voices[vc].ADSR.Release(); if (SPU2::MsgKeyOnOff()) SPU2::ConLog("* SPU2: KeyOff: Core %d; Voice %d.\n", core, vc); } diff --git a/pcsx2/SaveState.h b/pcsx2/SaveState.h index 592dfc95f3..5f2997a13a 100644 --- a/pcsx2/SaveState.h +++ b/pcsx2/SaveState.h @@ -37,7 +37,7 @@ enum class FreezeAction // [SAVEVERSION+] // This informs the auto updater that the users savestates will be invalidated. -static const u32 g_SaveVersion = (0x9A43 << 16) | 0x0000; +static const u32 g_SaveVersion = (0x9A44 << 16) | 0x0000; // the freezing data between submodules and core