SPU2: Rewrite ADSR [SAVEVERSION+]

This commit is contained in:
Ziemas 2023-10-08 09:50:10 +02:00 committed by refractionpcsx2
parent 43e700036d
commit 7bf8f6115f
7 changed files with 192 additions and 228 deletions

View File

@ -21,7 +21,7 @@
#include <array>
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<u32>(1, counter_inc);
Counter += counter_inc;
if (Counter >= 0x8000)
{
Counter = 0;
Value = std::clamp<s32>(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;
}

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -19,6 +19,8 @@
#include "SPU2/SndOut.h"
#include "SPU2/Global.h"
#include <array>
// --------------------------------------------------------------------------------------
// 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<CachedADSR, ADSR_PHASES> 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

View File

@ -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);
}

View File

@ -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