#include "VolCtrl.h"
#include <cmath>

static double lin2log_vol(double v)
{
	return v>0 ? 20.0*log10(v) : -100.0;
}

static double log2lin_vol(double v)
{
	return v<=-100.0 ? 0 : pow(10.0,v/20.0);
}

static double lin2log_pan(double p)
{
	if (p==0) return 0;
	else return lin2log_vol(1.0-fabs(p)) * (p>0 ? -1 : 1);
}

static double log2lin_pan(double p)
{
	if (p==0) return 0;
	else return (1.0-log2lin_vol(-fabs(p))) * (p>0 ? 1 :-1);
}

void DsVolCtrl::MapVol(double Vol,double Pan,double &OutNewVol,double &OutNewPan)
{
	DestVolHack=Vol;
	DestPanHack=Pan;
	
	double NewVol = 0.0;
	double NewPan = 0.0;

	switch(VolMode)
	{
	case 0:
		NewVol=lin2log_vol(Vol);
		NewPan=lin2log_pan(Pan);

		NewVol=Vol>0 ? 20*log10(Vol) : -100.0;//in negative db

		if (Pan==0) NewPan=0;
		else
		{
			double d= 1.0 - fabs(Pan);
			d = d>0 ? 20*log10(d) : -1000.0;
			if (Pan>0) d=-d;
			NewPan=d;
		}
		break;
	case 1:
		{
			double left,right;
			NewVol=left=right=(double)LogVolMin * (Vol-1.0);

			left+=lin2log_vol(sqrt(0.5-0.5*Pan));
			right+=lin2log_vol(sqrt(0.5+0.5*Pan));
			
			//NewVol=left>right ? left : right;
			NewPan=right-left;
		}
		break;
	case 2:
		{
			double left,right;
			NewVol=left=right=100.0 * (pow(Vol,0.25)-1.0);
		
			left+=lin2log_vol(sqrt(0.5-0.5*Pan));
			right+=lin2log_vol(sqrt(0.5+0.5*Pan));
			
			//NewVol=left>right ? left : right;
			NewPan=right-left;
		}
		break;
	}

	if (NewVol<-100.0) NewVol=-100.0;
	else if (NewVol>0) NewVol=0;
	if (NewPan<-100.0) NewPan=-100.0;
	else if (NewPan>100.0) NewPan=100.0;

	OutNewVol=NewVol;
	OutNewPan=NewPan;
}
	
DsVolCtrl::DsVolCtrl(int _VolMode,double _LogVolMin,bool _LogFades)
{
	IsFading=0;
	LogFades=_LogFades;
	VolMode=_VolMode;
	LogVolMin=_LogVolMin;
	FadeSrcTime=FadeDstTime=-1;
	CurTime=0;
	CurVol=1;
	LastVol=0;
	CurPan=0;
	LastPan=0;
	DestPanHack = 0;
	DestVolHack = 0;
	FadeDstPan = 0;
	FadeDstVol = 0;
	FadeSrcPan = 0;
	FadeSrcVol = 0;

}

void DsVolCtrl::SetFade(__int64 duration,double destvol,double destpan)
{
	FadeSrcTime=CurTime;
	FadeDstTime=CurTime+duration;
	FadeSrcVol=CurVol;
	FadeSrcPan=CurPan;
	IsFading=1;
	MapVol(destvol,destpan,FadeDstVol,FadeDstPan);
}

void DsVolCtrl::SetTime(__int64 time)
{
	CurTime=time;
}

void DsVolCtrl::SetVolume(double vol)
{
	if (Fading()) SetFade(FadeDstTime-CurTime,vol,DestPanHack);
	else MapVol(vol,DestPanHack,CurVol,CurPan);
}

void DsVolCtrl::SetPan(double pan)
{
	if (Fading()) SetFade(FadeDstTime-CurTime,DestVolHack,pan);
	else MapVol(DestVolHack,pan,CurVol,CurPan);
}

void DsVolCtrl::Apply(IDirectSoundBuffer * pDSB)
{
	if (Fading())
	{
		if (LogFades)
		{
			CurVol= FadeSrcVol + (FadeDstVol-FadeSrcVol) * (double)(CurTime-FadeSrcTime) / (double)(FadeDstTime-FadeSrcTime);
			CurPan= FadeSrcPan + (FadeDstPan-FadeSrcPan) * (double)(CurTime-FadeSrcTime) / (double)(FadeDstTime-FadeSrcTime);
		}
		else
		{
			double SrcVol=log2lin_vol(FadeSrcVol);
			double DstVol=log2lin_vol(FadeDstVol);
			double SrcPan=log2lin_pan(FadeSrcPan);
			double DstPan=log2lin_pan(FadeDstPan);

			CurVol=lin2log_vol( SrcVol + (DstVol-SrcVol) * (double)(CurTime-FadeSrcTime) / (double)(FadeDstTime-FadeSrcTime) );
			CurPan=lin2log_pan( SrcPan + (DstPan-SrcPan) * (double)(CurTime-FadeSrcTime) / (double)(FadeDstTime-FadeSrcTime) );
		}
	}
	else if (FadeDstTime>=0)
	{
		CurVol=FadeDstVol;
		CurPan=FadeDstPan;
		FadeDstTime=-1;
		IsFading=0;
	}
	
	if (CurVol!=LastVol)
	{
		LastVol=CurVol;
		pDSB->SetVolume((long)(CurVol*100.0));
	}

	if (CurPan!=LastPan)
	{
		LastPan=CurPan;
		pDSB->SetPan((long)(CurPan*100.0));
	}

}

bool DsVolCtrl::Fading()
{
	return IsFading && CurTime<FadeDstTime;
}

__int64 DsVolCtrl::RelFade(__int64 max,double destvol)
{
	return (__int64)(fabs(destvol-DestVolHack)*(double)max);
}

double DsVolCtrl::Stat_GetVolLeft()
{
	return CurPan<0 ? CurVol : CurVol - CurPan;
}

double DsVolCtrl::Stat_GetVolRight()
{
	return CurPan>0 ? CurVol : CurVol + CurPan;
}