//////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Sound_LowPassFilter.cpp
//
// Copyright (c) 2007 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

/******************************************************************************/
/* Included Files                                                             */
/******************************************************************************/

#include "Core/Asura.h"

#ifndef ASURA_USE_AUDIO_SYSTEM

#include "Sound/Asura_Xbox360_Sound_LowPassFilter.h"

#include "Core/Asura_Maths.h"
#include "Debugging/Asura_Assert.h"

#include <xaudio.h>

/******************************************************************************/
/* Constants                                                                  */
/******************************************************************************/

const float fFREQUENCY_MIN = 0.0f;
const float fFREQUENCY_MAX = XAUDIOSAMPLERATE_NATIVE / 2.0f;

const float fGAIN_MIN = -42.0f;
const float fGAIN_MAX = 42.0f;

/******************************************************************************/
/* Asura_Xbox360_Sound_LowPassFilter Class Functions                          */
/******************************************************************************/

Asura_Xbox360_Sound_LowPassFilter::Asura_Xbox360_Sound_LowPassFilter( void* pContext )
	: m_pContext( pContext )
	, m_uRefCount(0)
	, m_fFrequency( 5000.0f ) // OpenAL on PC fixed at 5 KHz, so let's use the same
	, m_fGain( 0.0f )
	, m_fk( 0.0f )
	, m_fv0( 0.0f )
	, m_fh02( 0.0f )
	, m_fa( 0.0f )
	, m_fDelay( 0.0f )
	, m_bActive( false )
{
}

STDMETHODIMP_( ULONG ) Asura_Xbox360_Sound_LowPassFilter::AddRef( void )
{
	return ++m_uRefCount;
}

STDMETHODIMP_( ULONG ) Asura_Xbox360_Sound_LowPassFilter::Release( void )
{
	--m_uRefCount;

	if( m_uRefCount == 0 )
	{
		delete this;
	}

	return m_uRefCount;
}

STDMETHODIMP Asura_Xbox360_Sound_LowPassFilter::GetContext( LPVOID* ppContext )
{
	Asura_Assert( ppContext, "NULL parameter passed to " __FUNCTION__ );
	*ppContext = m_pContext;

	return S_OK;
}

STDMETHODIMP Asura_Xbox360_Sound_LowPassFilter::GetInfo( XAUDIOFXINFO* pxInfo )
{
	Asura_Assert( pxInfo, "NULL parameter passed to " __FUNCTION__ );
	pxInfo->DataFlow = XAUDIODATAFLOW_INPLACE;
	pxInfo->TrailFrames = 0;

	return S_OK;
}

STDMETHODIMP Asura_Xbox360_Sound_LowPassFilter::GetParam( XAUDIOFXPARAMID ucParamID,
														  XAUDIOFXPARAMTYPE ucParamType,
														  XAUDIOFXPARAM* pxParam )
{
	Asura_Assert( pxParam, "NULL parameter passed to " __FUNCTION__ );
	XAUDIOFXPARAM xParam = {0};
	HRESULT xResult = S_OK;

	XAudioLock( XAUDIOLOCKTYPE_LOCK | XAUDIOLOCKTYPE_SPINLOCK );

	switch( ucParamID )
	{
		case PID_FREQUENCY:
		{
			Asura_Assert( ucParamType == XAUDIOFXPARAMTYPE_NUMERIC,
				"Incorrect parameter type for PID_FREQUENCY." );

			xParam.Value = m_fFrequency;
			break;
		}

		case PID_GAIN:
		{
			Asura_Assert( ucParamType == XAUDIOFXPARAMTYPE_NUMERIC,
				"Incorrect parameter type for PID_GAIN." );

			xParam.Value = m_fGain;
			break;
		}

		default:
		{
			Asura_Assertf( false, ("Invalid parameter ID (%u).", ucParamID) );

			xResult = E_FAIL;
			break;
		}
	}

	XAudioLock( XAUDIOLOCKTYPE_UNLOCK | XAUDIOLOCKTYPE_SPINLOCK );

	*pxParam = xParam;
	return xResult;
}

STDMETHODIMP Asura_Xbox360_Sound_LowPassFilter::SetParam( XAUDIOFXPARAMID ucParamID,
														  XAUDIOFXPARAMTYPE ucParamType,
														  const XAUDIOFXPARAM* pxParam )
{
 	Asura_Assert( pxParam, "NULL parameter passed to " __FUNCTION__ );
	HRESULT xResult = S_OK;

	XAudioLock( XAUDIOLOCKTYPE_LOCK | XAUDIOLOCKTYPE_SPINLOCK );

	switch( ucParamID )
	{
		case PID_FREQUENCY:
		{
			Asura_Assert( ucParamType == XAUDIOFXPARAMTYPE_NUMERIC,
				"Incorrect parameter type for PID_FREQUENCY." );

			const float fFreq = pxParam->Value;
			if( fFREQUENCY_MIN <= fFreq && fFreq <= fFREQUENCY_MAX )
			{
				 m_fFrequency = fFreq;
			}
			else
			{
				Asura_Assertf( false, ("Invalid frequency value (%f).", fFreq ));
				xResult = E_FAIL;
			}

			break;
		}

		case PID_GAIN:
		{
			Asura_Assert( ucParamType == XAUDIOFXPARAMTYPE_NUMERIC,
				"Incorrect parameter type for PID_GAIN." );

			const float fGain = pxParam->Value;
			if( fGAIN_MIN <= fGain && fGain <= fGAIN_MAX )
			{
				 m_fGain = fGain;
			}
			else
			{
				Asura_Assertf( false, ("Invalid gain value (%f).", fGain ));
				xResult = E_FAIL;
			}
			break;
		}

		default:
		{
			Asura_Assertf( false, ("Invalid parameter ID (%u).", ucParamID) );

			xResult = E_FAIL;
			break;
		}
	}

	// Recompute stuff
	{
		m_bActive = (m_fGain != 0.0f);

		m_fk = Asura_Maths::Tangent( Asura_Maths::Pi * m_fFrequency / XAUDIOSAMPLERATE_NATIVE );
		m_fv0 = Asura_Maths::Power( 10.0f, m_fGain / 20.0f );
		m_fh02 = (m_fv0 - 1.0f) / 2.0f;

		if( m_fGain > 0.0f )
		{
			m_fa = (m_fk - 1.0f) / (m_fk + 1.0f);
		}
		else
		{
			m_fa = (m_fv0 * m_fk - 1.0f) / (m_fv0 * m_fk + 1.0f);
		}
	}

	XAudioLock( XAUDIOLOCKTYPE_UNLOCK | XAUDIOLOCKTYPE_SPINLOCK );

	return xResult;
}

STDMETHODIMP Asura_Xbox360_Sound_LowPassFilter::Process( IXAudioFrameBuffer* pxInputBuffer,
														 IXAudioFrameBuffer* pxOutputBuffer )
{
	// This is an in-place effect, there should be no separate output buffer
	Asura_Assert( pxInputBuffer && !pxOutputBuffer, "Invalid parameters passed to " __FUNCTION__ );

	if( !m_bActive ) return S_OK;

	XAUDIOFRAMEBUFDATA xFrameBufferData;
	pxInputBuffer->GetProcessingData( &xFrameBufferData );

	Asura_Assert( xFrameBufferData.Format.SampleType == XAUDIOSAMPLETYPE_NATIVE,
		"Invalid sample type in audio frame buffer." );

	float* pfSample = xFrameBufferData.pSampleBuffer;

	for( u_int u = 0; u < xFrameBufferData.Format.ChannelCount; ++u )
	{
		for( u_int v = 0; v < XAUDIOFRAMESIZE_NATIVE; ++v )
		{
			const float fxhn = *pfSample - m_fa * m_fDelay;
			*pfSample += m_fh02 * (*pfSample - fxhn * m_fa - m_fDelay);
			m_fDelay = fxhn;

			++pfSample;
		}
	}

	return S_OK;
}

/******************************************************************************/
/* Asura_Xbox360_Sound_LowPassFilter Static Class Functions                   */
/******************************************************************************/

HRESULT Asura_Xbox360_Sound_LowPassFilter::QuerySize( LPCXAUDIOFXINIT pxInit, LPDWORD puEffectSize )
{
	Asura_Assert( pxInit && puEffectSize, "NULL parameter passed to " __FUNCTION__ );

	*puEffectSize = sizeof( Asura_Xbox360_Sound_LowPassFilter );
	return S_OK;
}

// Need to disable Asura's macros which alter 'new', so XAUDIO_BATCHALLOC_NEW works
#include "Core/Asura_MemoryManagement_DebugOff.h"

HRESULT Asura_Xbox360_Sound_LowPassFilter::Create( LPCXAUDIOFXINIT pxInit,
												   IXAudioBatchAllocator* pxAllocator,
												   IXAudioEffect** ppxEffect )
{
	*ppxEffect =
		XAUDIO_BATCHALLOC_NEW( Asura_Xbox360_Sound_LowPassFilter( pxInit->pContext ), pxAllocator );

	return *ppxEffect ? S_OK : E_OUTOFMEMORY;
}

#include "Core/Asura_MemoryManagement_DebugOn.h"

#endif // #ifndef ASURA_USE_AUDIO_SYSTEM

//eof
