pcsx2/plugins/spu2-x/src/ADSR.cpp

240 lines
5.5 KiB
C++

/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2
* Developed and maintained by the Pcsx2 Development Team.
*
* Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz]
*
* SPU2-X is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SPU2-X is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SPU2-X. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Global.h"
static const s32 ADSR_MAX_VOL = 0x7fffffff;
static const int InvExpOffsets[] = { 0,4,6,8,9,10,11,12 };
static u32 PsxRates[160];
void InitADSR() // INIT ADSR
{
for (int i=0; i<(32+128); i++)
{
int shift=(i-32)>>2;
s64 rate=(i&3)+4;
if (shift<0)
rate >>= -shift;
else
rate <<= shift;
PsxRates[i] = (int)std::min( rate, 0x3fffffffLL );
}
}
#define VOL(x) (((s32)x)) //24.8 volume
// Returns the linear slide value for AR and SR inputs.
// (currently not used, it's buggy)
static int GetLinearSrAr( uint SrAr )
{
// The Sr/Ar settings work in quarter steps, which means
// the bottom 2 bits go on the left side of the shift, and
// the right side of the shift gets divided by 4:
const uint newSr = 0x7f - SrAr;
return ((1|(newSr&3)) << (newSr>>2));
}
bool V_ADSR::Calculate()
{
jASSUME( Phase != 0 );
if(Releasing && (Phase < 5))
Phase = 5;
switch (Phase)
{
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
{
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
{
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
{
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;
}
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// //
#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::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)
{
// Decrement
if(Mode & VOLFLAG_EXPONENTIAL)
{
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
}
}
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;
}