/* 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]
*
* This library 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 Foundation; either version 2.1 of the the License, or (at your
* option) any later version.
* 
* This library is distributed in the hope that it will be useful, but WITHOUT 
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* 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 this library; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA  02111-1307  USA
* 
*/

#include "spu2.h"

static const s32 ADSR_MAX_VOL = 0x7fffffff;

static const int InvExpOffsets[] = { 0,4,6,8,9,10,11,12 };
static u32 PsxRates[160];


extern u32 core, voice;

void InitADSR()                                    // INIT ADSR
{

	/*u64 r=3;
	u64 rs=1;
	u64 rd=0;

	memset( PsxRates, 0, sizeof( PsxRates ) );

	for( uint i=32; i<160; ++i )
	{
		if( r < 0x3FFFFFFF )
		{
			r += rs;
			rd++;
			if( rd == 5 )
			{
				rd = 1;
				rs <<= 1;
			}
		}

		PsxRates[i] = (u32)min( r, 0x3FFFFFFFULL );
	}*/

	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)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 (Mode & VOLFLAG_DECREMENT)
	{
		// Decrement

		if(Mode & VOLFLAG_EXPONENTIAL)
		{
			u32 off = InvExpOffsets[(Value>>28)&7];
			Value -= PsxRates[(Increment^0x7f)-0x1b+off+32];
		}
		else
			Value -= Increment;

		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
			Value += Increment;

		if( Value < 0 )		// wrapped around the "top"?
		{
			Value = 0x7fffffff;
			Mode = 0; // disable slide
		}
	}
}
