Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPU: Rewrite ADSR/Volume slides #10084

Merged
merged 7 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
295 changes: 120 additions & 175 deletions pcsx2/SPU2/ADSR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,219 +19,164 @@

#include "common/Assertions.h"

#include <array>
static constexpr s32 ADSR_MAX_VOL = 0x7fff;

static constexpr s32 ADSR_MAX_VOL = 0x7fffffff;
void V_ADSR::UpdateCache()
{
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;

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;

CachedPhases[PHASE_SUSTAIN].Decr = SustainDir;
CachedPhases[PHASE_SUSTAIN].Exp = SustainMode;
CachedPhases[PHASE_SUSTAIN].Shift = SustainShift;
CachedPhases[PHASE_SUSTAIN].Step = 7 - SustainStep;

if (CachedPhases[PHASE_SUSTAIN].Decr)
CachedPhases[PHASE_SUSTAIN].Step = ~CachedPhases[PHASE_SUSTAIN].Step;

CachedPhases[PHASE_SUSTAIN].Target = 0;

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

static const int InvExpOffsets[] = {0, 4, 6, 8, 9, 10, 11, 12};
auto& p = CachedPhases.at(Phase);

using PSXRateTable = std::array<u32, 160>;
// maybe not correct for the "infinite" settings
u32 counter_inc = 0x8000 >> std::max(0, p.Shift - 11);
s32 level_inc = p.Step << std::max(0, 11 - p.Shift);

static constexpr PSXRateTable ComputePSXRates()
{
PSXRateTable rates = {};
for (int i = 0; i < (32 + 128); i++)
if (p.Exp)
{
const int shift = (i - 32) >> 2;
s64 rate = (i & 3) + 4;
if (shift < 0)
rate >>= -shift;
else
rate <<= shift;
if (!p.Decr && Value > 0x6000)
{
counter_inc >>= 2;
}

// Maximum rate is 0x4000.
rates[i] = (int)std::min(rate, (s64)0x40000000LL);
if (p.Decr)
{
level_inc = (s16)((level_inc * Value) >> 15);
}
}
return rates;
}

static constexpr const PSXRateTable PsxRates = ComputePSXRates();
counter_inc = std::max<u32>(1, counter_inc);
Counter += counter_inc;

bool V_ADSR::Calculate()
{
pxAssume(Phase != 0);
if (Counter >= 0x8000)
{
Counter = 0;
Value = std::clamp<s32>(Value + level_inc, 0, INT16_MAX);
}

if (Releasing && (Phase < 5))
Phase = 5;
// Stay in sustain until key off or silence
if (Phase == PHASE_SUSTAIN)
{
return Value != 0;
}

switch (Phase)
// Check if target is reached to advance phase
if ((!p.Decr && Value >= p.Target) || (p.Decr && Value <= p.Target))
{
case 1: // attack
if (Value == ADSR_MAX_VOL)
{
// Already maxed out. Progress phase and nothing more:
Phase++;
break;
}

// Case 1 below is for pseudo exponential below 75%.
// Pseudo Exp > 75% and Linear are the same.

if (AttackMode && (Value >= 0x60000000))
Value += PsxRates[(AttackRate ^ 0x7f) - 0x18 + 32];
else
Value += PsxRates[(AttackRate ^ 0x7f) - 0x10 + 32];

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];
Phase++;
}

// calculate sustain level as a factor of the ADSR maximum volume.
// All phases done, stop the voice
if (Phase > PHASE_RELEASE)
{
return false;
}

s32 suslev = ((0x80000000 / 0x10) * (SustainLevel + 1)) - 1;
return true;
}

if (Value <= suslev)
{
if (Value < 0)
Value = 0;
Phase++;
}
}
break;
void V_ADSR::Attack()
{
Phase = PHASE_ATTACK;
Counter = 0;
Value = 0;
}

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
void V_ADSR::Release()
{
if (Phase != PHASE_STOPPED)
{
Phase = PHASE_RELEASE;
Counter = 0;
}

// returns true if the voice is active, or false if it's stopping.
return Phase != 6;
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// //

#define VOLFLAG_REVERSE_PHASE (1ul << 0)
#define VOLFLAG_DECREMENT (1ul << 1)
#define VOLFLAG_EXPONENTIAL (1ul << 2)
#define VOLFLAG_SLIDE_ENABLE (1ul << 3)
void V_VolumeSlide::RegSet(u16 src)
{
Reg_VOL = src;
if (!Enable)
{
Value = SignExtend16(src << 1);
}
}

void V_VolumeSlide::Update()
{
if (!(Mode & VOLFLAG_SLIDE_ENABLE))
if (!Enable)
return;

// Volume slides use the same basic logic as ADSR, but simplified (single-stage
// instead of multi-stage)
s32 step_size = 7 - Step;

if (Increment == 0x7f)
return;
if (Decr)
{
step_size = ~step_size;
}

s32 value = abs(Value);
u32 counter_inc = 0x8000 >> std::max(0, Shift - 11);
s32 level_inc = step_size << std::max(0, 11 - Shift);

if (Mode & VOLFLAG_DECREMENT)
if (Exp)
{
// Decrement

if (Mode & VOLFLAG_EXPONENTIAL)
if (!Decr && Value > 0x6000)
{
const u32 off = InvExpOffsets[(value >> 28) & 7];
value -= PsxRates[(Increment ^ 0x7f) - 0x1b + off + 32];
counter_inc >>= 2;
}
else
value -= PsxRates[(Increment ^ 0x7f) - 0xf + 32];

if (value < 0)
if (Decr)
{
value = 0;
Mode = 0; // disable slide
level_inc = (s16)((level_inc * Value) >> 15);
}
}
else

counter_inc = std::max<u32>(1, counter_inc);
Counter += counter_inc;

// If negative phase "increase" to -0x8000 or "decrease" towards 0
level_inc = Phase ? -level_inc : level_inc;

if (Counter >= 0x8000)
{
// Increment
// Pseudo-exponential increments, as done by the SPU2 (really!)
// Above 75% slides slow, below 75% slides fast. It's exponential, pseudo'ly speaking.
Counter = 0;

if ((Mode & VOLFLAG_EXPONENTIAL) && (value >= 0x60000000))
value += PsxRates[(Increment ^ 0x7f) - 0x18 + 32];
if (!Decr)
{
Value = std::clamp<s32>(Value + level_inc, INT16_MIN, INT16_MAX);
}
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
s32 low = Phase ? INT16_MIN : 0;
s32 high = Phase ? 0 : INT16_MAX;
Value = std::clamp<s32>(Value + level_inc, low, high);
}
}

Value = (Value < 0) ? -value : value;
}
26 changes: 14 additions & 12 deletions pcsx2/SPU2/Debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,8 @@ void SPU2::ConLog(const char* fmt, ...)
void V_VolumeSlide::DebugDump(FILE* dump, const char* title, const char* nameLR)
{
fprintf(dump, "%s Volume for %s Channel:\t%x\n"
" - Value: %x\n"
" - Mode: %x\n"
" - Increment: %x\n",
title, nameLR, Reg_VOL, Value, Mode, Increment);
" - Value: %x\n",
title, nameLR, Reg_VOL, Value);
}

void V_VolumeSlideLR::DebugDump(FILE* dump, const char* title)
Expand Down Expand Up @@ -176,25 +174,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);
Expand Down
Loading
Loading