/*
===========================================================================

Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").

Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 BFG Edition Source Code 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "snd_local.h"

idCVar s_subFraction( "s_subFraction", "0.5", CVAR_ARCHIVE | CVAR_FLOAT, "Amount of each sound to send to the LFE channel" );

static float OpenQ4_SanitizeMixFraction( const float value, const float fallback )
{
	if( FLOAT_IS_NAN( value ) )
	{
		return fallback;
	}
	return idMath::ClampFloat( 0.0f, 1.0f, value );
}

static float OpenQ4_SanitizeMixScale( const float value )
{
	if( FLOAT_IS_NAN( value ) || value <= 0.0f )
	{
		return 0.0f;
	}
	return idMath::ClampFloat( 0.0f, 16.0f, value );
}

idVec2 idSoundVoice_Base::speakerPositions[idWaveFile::CHANNEL_INDEX_MAX];
int idSoundVoice_Base::speakerLeft[idWaveFile::CHANNEL_INDEX_MAX] = {0 };
int idSoundVoice_Base::speakerRight[idWaveFile::CHANNEL_INDEX_MAX] = {0 };
int idSoundVoice_Base::dstChannels = 0;
int idSoundVoice_Base::dstMask = 0;
int idSoundVoice_Base::dstCenter = -1;
int idSoundVoice_Base::dstLFE = -1;
int idSoundVoice_Base::dstMap[MAX_CHANNELS_PER_VOICE] = { 0 };
int idSoundVoice_Base::invMap[idWaveFile::CHANNEL_INDEX_MAX] = { 0 };
float idSoundVoice_Base::omniLevel = 1.0f;

/*
========================
idSoundVoice_Base::idSoundVoice_Base
========================
*/
idSoundVoice_Base::idSoundVoice_Base() :	
	gain( 1.0f ),
	centerChannel( 0.0f ),
	pitch( 1.0f ),
	wetLevel( 0.0f ),
	dryLevel( 1.0f ),
	innerRadius( 32.0f ),
	occlusion( 0.0f ),
	environmentMuffle( 0.0f ),
	channelMask( 0 ),
	innerSampleRangeSqr( 0.0f ),
	outerSampleRangeSqr( 0.0f )
{
	position.Zero();
	velocity.Zero();
}

/*
========================
idSoundVoice_Base::InitSurround
========================
*/
void idSoundVoice_Base::InitSurround( int outputChannels, int channelMask )
{

	speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_LEFT			].Set( 0.70710678118654752440084436210485f,  0.70710678118654752440084436210485f );	// 45 degrees
	speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT			].Set( 0.70710678118654752440084436210485f, -0.70710678118654752440084436210485f );	// 315 degrees
	speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_CENTER			].Set( 0.0f,								  0.0f );									// 0 degrees
	speakerPositions[idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY		].Set( 0.0f,								  0.0f );									// -
	speakerPositions[idWaveFile::CHANNEL_INDEX_BACK_LEFT			].Set( -0.70710678118654752440084436210485f,  0.70710678118654752440084436210485f );	// 135 degrees
	speakerPositions[idWaveFile::CHANNEL_INDEX_BACK_RIGHT			].Set( -0.70710678118654752440084436210485f, -0.70710678118654752440084436210485f );	// 225 degrees
	speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER	].Set( 0.92387953251128675612818318939679f,  0.3826834323650897717284599840304f );		// 22.5 degrees
	speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER	].Set( 0.92387953251128675612818318939679f, -0.3826834323650897717284599840304f );		// 337.5 degrees
	speakerPositions[idWaveFile::CHANNEL_INDEX_BACK_CENTER			].Set( -1.0f,								  0.0f );									// 180 degrees
	speakerPositions[idWaveFile::CHANNEL_INDEX_SIDE_LEFT			].Set( 0.0f,								  1.0f );									// 90 degrees
	speakerPositions[idWaveFile::CHANNEL_INDEX_SIDE_RIGHT			].Set( 0.0f,								 -1.0f );									// 270 degrees

	speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT;
	speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_LEFT] = idWaveFile::CHANNEL_INDEX_SIDE_LEFT;
	speakerLeft[idWaveFile::CHANNEL_INDEX_SIDE_LEFT] = idWaveFile::CHANNEL_INDEX_BACK_LEFT;
	speakerLeft[idWaveFile::CHANNEL_INDEX_BACK_LEFT] = idWaveFile::CHANNEL_INDEX_BACK_CENTER;
	speakerLeft[idWaveFile::CHANNEL_INDEX_BACK_CENTER] = idWaveFile::CHANNEL_INDEX_BACK_RIGHT;
	speakerLeft[idWaveFile::CHANNEL_INDEX_BACK_RIGHT] = idWaveFile::CHANNEL_INDEX_SIDE_RIGHT;
	speakerLeft[idWaveFile::CHANNEL_INDEX_SIDE_RIGHT] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT;
	speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER;
	speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER;

	speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_CENTER;
	speakerLeft[idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY] = idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY;

	speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT;
	speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT] = idWaveFile::CHANNEL_INDEX_SIDE_RIGHT;
	speakerRight[idWaveFile::CHANNEL_INDEX_SIDE_RIGHT] = idWaveFile::CHANNEL_INDEX_BACK_RIGHT;
	speakerRight[idWaveFile::CHANNEL_INDEX_BACK_RIGHT] = idWaveFile::CHANNEL_INDEX_BACK_CENTER;
	speakerRight[idWaveFile::CHANNEL_INDEX_BACK_CENTER] = idWaveFile::CHANNEL_INDEX_BACK_LEFT;
	speakerRight[idWaveFile::CHANNEL_INDEX_BACK_LEFT] = idWaveFile::CHANNEL_INDEX_SIDE_LEFT;
	speakerRight[idWaveFile::CHANNEL_INDEX_SIDE_LEFT] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT;
	speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_LEFT] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER;
	speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER;

	speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_CENTER;
	speakerRight[idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY] = idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY;

	dstChannels = idMath::ClampInt( 0, MAX_CHANNELS_PER_VOICE, outputChannels );
	dstMask = channelMask;

	// dstMap maps a destination channel to a speaker
	// invMap maps a speaker to a destination channel
	dstLFE = -1;
	dstCenter = -1;
	memset( dstMap, 0, sizeof( dstMap ) );
	memset( invMap, 0, sizeof( invMap ) );
	for( int i = 0, c = 0; i < idWaveFile::CHANNEL_INDEX_MAX; i++ )
	{
		if( ( dstMask & (1ULL << (i)) ) != 0 && c < dstChannels )
		{
			if( i == idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY )
			{
				dstLFE = c;
			}
			if( i == idWaveFile::CHANNEL_INDEX_FRONT_CENTER )
			{
				dstCenter = c;
			}
			dstMap[c] = i;
			invMap[i] = c++;
		}
		else
		{
			// Remove this speaker from the chain
			int right = speakerRight[i];
			int left = speakerLeft[i];
			speakerRight[left] = right;
			speakerLeft[right] = left;
		}
	}
	assert( ( dstLFE == -1 ) || ( ( dstMask & idWaveFile::CHANNEL_MASK_LOW_FREQUENCY ) != 0 ) );
	assert( ( dstCenter == -1 ) || ( ( dstMask & idWaveFile::CHANNEL_MASK_FRONT_CENTER ) != 0 ) );

	float omniChannels = ( float )dstChannels;
	if( dstMask & idWaveFile::CHANNEL_MASK_LOW_FREQUENCY )
	{
		omniChannels -= 1.0f;
	}
	if( dstMask & idWaveFile::CHANNEL_MASK_FRONT_CENTER )
	{
		omniChannels -= 1.0f;
	}
	if( omniChannels > 0.0f )
	{
		omniLevel = 1.0f / omniChannels;
	}
	else
	{
		// This happens in mono mode
		omniLevel = 1.0f;
	}
}

/*
========================
idSoundVoice_Base::CalculateSurround
========================
*/
void idSoundVoice_Base::CalculateSurround( int srcChannels, float pLevelMatrix[ MAX_CHANNELS_PER_VOICE * MAX_CHANNELS_PER_VOICE ], float scale )
{
	if( pLevelMatrix == NULL )
	{
		return;
	}

	memset( pLevelMatrix, 0, sizeof( float ) * MAX_CHANNELS_PER_VOICE * MAX_CHANNELS_PER_VOICE );
	scale = OpenQ4_SanitizeMixScale( scale );
	if( srcChannels <= 0 || srcChannels > MAX_CHANNELS_PER_VOICE || dstChannels <= 0 || dstChannels > MAX_CHANNELS_PER_VOICE )
	{
		return;
	}

	// Hack for mono
	if( dstChannels == 1 )
	{
		if( srcChannels == 1 )
		{
			pLevelMatrix[ 0 ] = scale;
		}
		else if( srcChannels == 2 )
		{
			pLevelMatrix[ 0 ] = scale * 0.7071f;
			pLevelMatrix[ 1 ] = scale * 0.7071f;
		}
		return;
	}

#define MATINDEX( src, dst ) ( srcChannels * dst + src )

	float subFraction = OpenQ4_SanitizeMixFraction( s_subFraction.GetFloat(), 0.0f );

	if( srcChannels == 1 )
	{
		idVec2 p2 = position.ToVec2();

		float centerFraction = OpenQ4_SanitizeMixFraction( centerChannel, 0.0f );

		float sqrLength = p2.LengthSqr();
		if( FLOAT_IS_NAN( sqrLength ) || sqrLength <= 0.01f )
		{
			// If we are on top of the listener, simply route all channels to each speaker equally
			for( int i = 0; i < dstChannels; i++ )
			{
				pLevelMatrix[MATINDEX( 0, i )] = omniLevel;
			}
		}
		else
		{
			float invLength = idMath::InvSqrt( sqrLength );
			float distance = ( invLength * sqrLength );
			p2 *= invLength;

			float spatialize = 1.0f;
			if( innerRadius > 0.0f && distance < innerRadius )
			{
				spatialize = distance / innerRadius;
			}
			spatialize = OpenQ4_SanitizeMixFraction( spatialize, 1.0f );
			float omni = omniLevel * ( 1.0f - spatialize );

			if( dstCenter != -1 )
			{
				centerFraction *= Max( 0.0f, p2.x );
				spatialize *= ( 1.0f - centerFraction );
				omni *= ( 1.0f - centerFraction );
			}

			float channelDots[MAX_CHANNELS_PER_VOICE] = { 0 };
			for( int i = 0; i < dstChannels; i++ )
			{
				// Calculate the contribution to each destination channel
				channelDots[i] = speakerPositions[dstMap[i]] * p2;
			}
			// Find the speaker nearest to the sound
			int channelA = 0;
			for( int i = 1; i < dstChannels; i++ )
			{
				if( channelDots[i] > channelDots[channelA] )
				{
					channelA = i;
				}
			}
			int speakerA = dstMap[channelA];

			// Find the 2nd nearest speaker
			int speakerB;
			float speakerACross = ( speakerPositions[speakerA].x * p2.y ) - ( speakerPositions[speakerA].y * p2.x );
			if( speakerACross > 0.0f )
			{
				speakerB = speakerLeft[speakerA];
			}
			else
			{
				speakerB = speakerRight[speakerA];
			}
			int channelB = invMap[speakerB];

			// Divide the amplitude between the 2 closest speakers
			float distA = ( speakerPositions[speakerA] - p2 ).Length();
			float distB = ( speakerPositions[speakerB] - p2 ).Length();
			float volumes[MAX_CHANNELS_PER_VOICE] = { 0 };
			const float distSum = distA + distB;
			if( FLOAT_IS_NAN( distSum ) || distSum <= 0.0f )
			{
				volumes[channelA] = 1.0f;
			}
			else
			{
				float distCinv = 1.0f / distSum;
				volumes[channelA] = ( distB * distCinv );
				volumes[channelB] = ( distA * distCinv );
			}
			for( int i = 0; i < dstChannels; i++ )
			{
				pLevelMatrix[MATINDEX( 0, i )] = ( volumes[i] * spatialize ) + omni;
			}
		}
		if( dstLFE != -1 )
		{
			pLevelMatrix[MATINDEX( 0, dstLFE )] = subFraction;
		}
		if( dstCenter != -1 )
		{
			pLevelMatrix[MATINDEX( 0, dstCenter )] = centerFraction;
		}
	}
	else if( srcChannels == 2 )
	{
		pLevelMatrix[ MATINDEX( 0, 0 ) ] = 1.0f;
		pLevelMatrix[ MATINDEX( 1, 1 ) ] = 1.0f;
		if( dstLFE != -1 )
		{
			pLevelMatrix[ MATINDEX( 0, dstLFE ) ] = subFraction * 0.5f;
			pLevelMatrix[ MATINDEX( 1, dstLFE ) ] = subFraction * 0.5f;
		}
	}
	else
	{
		idLib::Warning( "We don't support %d channel sound files", srcChannels );
		return;
	}
	for( int i = 0; i < srcChannels * dstChannels; i++ )
	{
		pLevelMatrix[ i ] *= scale;
	}
}
