/*=============================================================================
	CAVolumeCurve.cpp

	$Log: CAVolumeCurve.cpp,v $
	Revision 1.6  2004/05/21 00:11:30  jcm10
	add support for the driver to say that a level control shouldn't use the volume curve
	
	Revision 1.5  2003/07/26 00:19:17  jcm10
	use the right max to clamp the input in ConvertRawToScalar()
	
	Revision 1.4  2002/12/17 20:01:54  jcm10
	round when converting to raw to make the UI better
	
	Revision 1.3  2002/11/08 04:04:10  jcm10
	stop using interpolation in the conversions so that everything clamps onto actual steps in the ranges
	
	Revision 1.2  2002/11/07 21:14:14  jcm10
	fix some naming in CAVolumeCurve to better match the usage in the HAL API
	
	Revision 1.1  2002/10/31 01:04:43  jcm10
	moved here from AudioHardware/Utility
	
	Revision 1.3  2002/06/26 21:37:09  jcm10
	take out the old code
	
	Revision 1.2  2002/06/25 23:33:39  jcm10
	make the curve for percent conversions a bit more shallow
	
	Revision 1.1  2002/05/21 00:55:49  jcm10
	new sample rate properties and new volume control behavior
	
	Revision 0.0  Sun May 19 2002 19:26:33 US/Pacific  moorf
	Created
		
	$NoKeywords: $
=============================================================================*/

//=============================================================================
//	Includes
//=============================================================================

#include "CAVolumeCurve.h"
#include "CADebugMacros.h"
#include <math.h>

//=============================================================================
//	CAVolumeCurve
//=============================================================================

CAVolumeCurve::CAVolumeCurve()
:
	mCurveMap(),
	mIsApplyingCurve(true)
{
}

CAVolumeCurve::~CAVolumeCurve()
{
}

SInt32	CAVolumeCurve::GetMinimumRaw() const
{
	SInt32 theAnswer = 0;
	
	if(!mCurveMap.empty())
	{
		CurveMap::const_iterator theIterator = mCurveMap.begin();
		theAnswer = theIterator->first.mMinimum;
	}
	
	return theAnswer;
}

SInt32	CAVolumeCurve::GetMaximumRaw() const
{
	SInt32 theAnswer = 0;
	
	if(!mCurveMap.empty())
	{
		CurveMap::const_iterator theIterator = mCurveMap.begin();
		std::advance(theIterator, mCurveMap.size() - 1);
		theAnswer = theIterator->first.mMaximum;
	}
	
	return theAnswer;
}

Float64	CAVolumeCurve::GetMinimumDB() const
{
	Float64 theAnswer = 0;
	
	if(!mCurveMap.empty())
	{
		CurveMap::const_iterator theIterator = mCurveMap.begin();
		theAnswer = theIterator->second.mMinimum;
	}
	
	return theAnswer;
}

Float64	CAVolumeCurve::GetMaximumDB() const
{
	Float64 theAnswer = 0;
	
	if(!mCurveMap.empty())
	{
		CurveMap::const_iterator theIterator = mCurveMap.begin();
		std::advance(theIterator, mCurveMap.size() - 1);
		theAnswer = theIterator->second.mMaximum;
	}
	
	return theAnswer;
}

void	CAVolumeCurve::AddRange(SInt32 inMinRaw, SInt32 inMaxRaw, Float64 inMinDB, Float64 inMaxDB)
{
	CARawPoint theRaw(inMinRaw, inMaxRaw);
	CADBPoint theDB(inMinDB, inMaxDB);
	
	bool isOverlapped = false;
	bool isDone = false;
	CurveMap::iterator theIterator = mCurveMap.begin();
	while((theIterator != mCurveMap.end()) && !isOverlapped && !isDone)
	{
		isOverlapped = CARawPoint::Overlap(theRaw, theIterator->first);
		isDone = theRaw >= theIterator->first;
		
		if(!isOverlapped && !isDone)
		{
			std::advance(theIterator, 1);
		}
	}
	
	if(!isOverlapped)
	{
		mCurveMap.insert(CurveMap::value_type(theRaw, theDB));
	}
	else
	{
		DebugMessage("CAVolumeCurve::AddRange: new point overlaps");
	}
}

void	CAVolumeCurve::ResetRange()
{
	mCurveMap.clear();
}

bool	CAVolumeCurve::CheckForContinuity() const
{
	bool theAnswer = true;
	
	CurveMap::const_iterator theIterator = mCurveMap.begin();
	if(theIterator != mCurveMap.end())
	{
		SInt32 theRaw = theIterator->first.mMinimum;
		Float64 theDB = theIterator->second.mMinimum;
		do
		{
			SInt32 theRawMin = theIterator->first.mMinimum;
			SInt32 theRawMax = theIterator->first.mMaximum;
			SInt32 theRawRange = theRawMax - theRawMin;
			
			Float64 theDBMin = theIterator->second.mMinimum;
			Float64 theDBMax = theIterator->second.mMaximum;
			Float64 theDBRange = theDBMax - theDBMin;

			theAnswer = theRaw == theRawMin;
			theAnswer = theDB == theDBMin;
			
			theRaw += theRawRange;
			theDB += theDBRange;
			
			std::advance(theIterator, 1);
		}
		while((theIterator != mCurveMap.end()) && theAnswer);
	}
	
	return theAnswer;
}

SInt32	CAVolumeCurve::ConvertDBToRaw(Float64 inDB) const
{
	//	clamp the value to the dB range
	Float64 theOverallDBMin = GetMinimumDB();
	Float64 theOverallDBMax = GetMaximumDB();
	
	if(inDB < theOverallDBMin) inDB = theOverallDBMin;
	if(inDB > theOverallDBMax) inDB = theOverallDBMax;
	
	//	get the first entry in the curve map;
	CurveMap::const_iterator theIterator = mCurveMap.begin();
	
	//	initialize the answer to the minimum raw of the first item in the curve map
	SInt32 theAnswer = theIterator->first.mMinimum;
	
	//	iterate through the curve map until we run out of dB
	bool isDone = false;
	while(!isDone && (theIterator != mCurveMap.end()))
	{
		SInt32 theRawMin = theIterator->first.mMinimum;
		SInt32 theRawMax = theIterator->first.mMaximum;
		SInt32 theRawRange = theRawMax - theRawMin;
		
		Float64 theDBMin = theIterator->second.mMinimum;
		Float64 theDBMax = theIterator->second.mMaximum;
		Float64 theDBRange = theDBMax - theDBMin;
		
		Float64 theDBPerRaw = theDBRange / static_cast<Float64>(theRawRange);
		
		//	figure out how many steps we are into this entry in the curve map
		if(inDB > theDBMax)
		{
			//	we're past the end of this one, so add in the whole range for this entry
			theAnswer += theRawRange;
		}
		else
		{
			//	it's somewhere within the current entry
			//	figure out how many steps it is
			Float64 theNumberRawSteps = inDB - theDBMin;
			theNumberRawSteps /= theDBPerRaw;
			
			//	only move in whole steps
			theNumberRawSteps = round(theNumberRawSteps);
			
			//	add this many steps to the answer
			theAnswer += static_cast<SInt32>(theNumberRawSteps);
			
			//	mark that we are done
			isDone = true;
		}
		
		//	go to the next entry in the curve map
		std::advance(theIterator, 1);
	}
	
	return theAnswer;
}

Float64	CAVolumeCurve::ConvertRawToDB(SInt32 inRaw) const
{
	Float64 theAnswer = 0;
	
	//	clamp the raw value
	SInt32 theOverallRawMin = GetMinimumRaw();
	SInt32 theOverallRawMax = GetMaximumRaw();
	
	if(inRaw < theOverallRawMin) inRaw = theOverallRawMin;
	if(inRaw > theOverallRawMax) inRaw = theOverallRawMax;
	
	//	figure out how many raw steps need to be taken from the first one
	SInt32 theNumberRawSteps = inRaw - theOverallRawMin;

	//	get the first item in the curve map
	CurveMap::const_iterator theIterator = mCurveMap.begin();
	
	//	initialize the answer to the minimum dB of the first item in the curve map
	theAnswer = theIterator->second.mMinimum;
	
	//	iterate through the curve map until we run out of steps
	while((theNumberRawSteps > 0) && (theIterator != mCurveMap.end()))
	{
		//	compute some values
		SInt32 theRawMin = theIterator->first.mMinimum;
		SInt32 theRawMax = theIterator->first.mMaximum;
		SInt32 theRawRange = theRawMax - theRawMin;
		
		Float64 theDBMin = theIterator->second.mMinimum;
		Float64 theDBMax = theIterator->second.mMaximum;
		Float64 theDBRange = theDBMax - theDBMin;
		
		Float64 theDBPerRaw = theDBRange / static_cast<Float64>(theRawRange);
		
		//	there might be more steps than the current map entry accounts for
		SInt32 theRawStepsToAdd = std::min(theRawRange, theNumberRawSteps);
		
		//	add this many steps worth of db to the answer;
		theAnswer += theRawStepsToAdd * theDBPerRaw;
		
		//	figure out how many steps are left
		theNumberRawSteps -= theRawStepsToAdd;
		
		//	go to the next map entry
		std::advance(theIterator, 1);
	}
	
	return theAnswer;
}

Float64	CAVolumeCurve::ConvertRawToScalar(SInt32 inRaw) const
{
	//	get some important values
	Float64	theDBMin = GetMinimumDB();
	Float64	theDBMax = GetMaximumDB();
	Float64	theDBRange = theDBMax - theDBMin;
	SInt32	theRawMin = GetMinimumRaw();
	SInt32	theRawMax = GetMaximumRaw();
	SInt32	theRawRange = theRawMax - theRawMin;
	
	//	range the raw value
	if(inRaw < theRawMin) inRaw = theRawMin;
	if(inRaw > theRawMax) inRaw = theRawMax;

	//	calculate the distance in the range inRaw is
	Float64 theAnswer = static_cast<Float64>(inRaw - theRawMin) / static_cast<Float64>(theRawRange);

	//	only apply a curve to the scalar values if the dB range is greater than 30
	if(mIsApplyingCurve && (theDBRange > 30.0))
	{
		theAnswer = pow(theAnswer, 2.0 / 1.0);
	}

	return theAnswer;
}

Float64	CAVolumeCurve::ConvertDBToScalar(Float64 inDB) const
{
	SInt32 theRawValue = ConvertDBToRaw(inDB);
	Float64 theAnswer = ConvertRawToScalar(theRawValue);
	return theAnswer;
}

SInt32	CAVolumeCurve::ConvertScalarToRaw(Float64 inScalar) const
{
	//	range the scalar value
	inScalar = std::min(1.0, std::max(0.0, inScalar));
	
	//	get some important values
	Float64	theDBMin = GetMinimumDB();
	Float64	theDBMax = GetMaximumDB();
	Float64	theDBRange = theDBMax - theDBMin;
	SInt32	theRawMin = GetMinimumRaw();
	SInt32	theRawMax = GetMaximumRaw();
	SInt32	theRawRange = theRawMax - theRawMin;
	
	//	have to undo the curve if the dB range is greater than 30
	if(mIsApplyingCurve && (theDBRange > 30.0))
	{
		inScalar = pow(inScalar, 1.0 / 2.0);
	}
	
	//	now we can figure out how many raw steps this is
	Float64 theNumberRawSteps = inScalar * static_cast<Float64>(theRawRange);
	theNumberRawSteps = round(theNumberRawSteps);
	
	//	the answer is the minimum raw value plus the number of raw steps
	SInt32 theAnswer = theRawMin + static_cast<SInt32>(theNumberRawSteps);
	
	return theAnswer;
}

Float64	CAVolumeCurve::ConvertScalarToDB(Float64 inScalar) const
{
	SInt32 theRawValue = ConvertScalarToRaw(inScalar);
	Float64 theAnswer = ConvertRawToDB(theRawValue);
	return theAnswer;
}
