// Asura_XAPO_DSP_Flanger.cpp
// Thomas Young - 27.04.09
// Copyright (C) 2009 Rebellion, All Rights Reserved.

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_USE_AUDIO_SYSTEM

#include "Sound/XAPO_DSP/Asura_XAPO_DSP_Flanger.h"

#include "Sound/Asura_Audio_DSP.h"
#include "Sound/Asura_XAudio2.h"

// F U N C T I O N   D E F I N I T I O N S ///////////////////////////////////

Asura_XAPO_DSP_Flanger::Asura_XAPO_DSP_Flanger( ) : Asura_XAPOBase< Asura_XAPO_DSP_Flanger, Asura_DSP_Params_Flanger::Params >( )
{
	m_pfSignal = 0;
	m_uExpectedChans = 0;
	m_uExpectedFrames = 0;
	m_uSampleOffset = 0;
	m_bReverseDir = false;
}


Asura_XAPO_DSP_Flanger::~Asura_XAPO_DSP_Flanger()
{
	if( m_pfSignal )
	{
		Asura_DeallocateAlign( m_pfSignal );
	}
}


void Asura_XAPO_DSP_Flanger::DoProcess ( float* pfInputBuffer, u_int uFrames, u_int uChannels, const Asura_DSP_Params_Flanger::Params& xParams )
{
	if( !m_pfSignal )
	{
		// We need to create a temp buffer, so we make some assumptions about the buffer format based on this first call
		m_uExpectedChans = uChannels;
		m_uExpectedFrames = uFrames;

		// Allocate a buffer to store the previous signal
		const u_int uAlocSize = ( uChannels * uFrames * 3 ) * sizeof( float );

		m_pfSignal = reinterpret_cast<float*>( Asura_AllocateAlign( uAlocSize, ASURA_ALIGN_128_BIT ) );

		// Zero it since we will be using it immediately
		Asura_Mem::Set( m_pfSignal, 0, uAlocSize );
	}

	if( ( m_uExpectedChans != uChannels ) || ( m_uExpectedFrames != uFrames ) )
	{
		return;
	}

	// Update our signal buffer, it consists of the signal from... | 2 calls ago | Last call | This call |
	// Move the last signals further down the buffer

	Asura_Mem::Move( m_pfSignal, &m_pfSignal[ uChannels * uFrames ], ( uChannels * uFrames * 2 ) * sizeof( float ) );

	// Copy the current signal into the end of the temp buffer

	Asura_Mem::Copy( &m_pfSignal[ uChannels * uFrames * 2 ], pfInputBuffer, ( uChannels * uFrames ) * sizeof( float ) );

	// Flange

	// Algorithm:	Loop over all samples mulutplying them by an offset version on the signal.
	//				Said offset changes over time by 1 sample per x samples

	const float fSampleContrib1 = ( xParams.m_fWetDry * 0.5f );				// Previous sample contribution
	const float fSampleContrib2 = ( 1.0f - xParams.m_fWetDry * 0.5f );		// Current sample contribution

	// Loop over every sample
	for( int iResultPos = 0; iResultPos < static_cast<int>( uFrames ); ++iResultPos )
	{
		const int iSourcePos1 = ( iResultPos - static_cast<int>( m_uSampleOffset ) );
		const int iSourcePos2 = ( iResultPos );

		const int iStartOfCurrentSignal = static_cast<int>( uFrames * uChannels * 2 );

		const int iSourceFramePos1 = iStartOfCurrentSignal + iSourcePos1 * static_cast<int>( uChannels );
		const int iSourceFramePos2 = iStartOfCurrentSignal + iSourcePos2 * static_cast<int>( uChannels );

		const int iResultFramePos = iResultPos * static_cast<int>( uChannels );

		// for all channels in this frame
		for( int iChan = 0; iChan < static_cast<int>( uChannels ); ++iChan )
		{
			const int iSourceSamplePos1 = iSourceFramePos1 + iChan;
			const int iSourceSamplePos2 = iSourceFramePos2 + iChan;
				
			const int iResultSamplePos = iResultFramePos + iChan;
					
			// Take a weighted average of the two signals, weighted by the wet/dry amount

			pfInputBuffer[ iResultSamplePos ]	= (	  ( m_pfSignal[ iSourceSamplePos1 ] * fSampleContrib1 ) 
													+ ( m_pfSignal[ iSourceSamplePos2 ] * fSampleContrib2 ) ) * xParams.m_fGain;
		}
	}

	// Shift our sample offset (triangular wave LFO)
	// I actually did a better LFO implementation in the chorus DSP but I cant really be bothered to port it to here, its less important
	// for the flanger anyway because we are working with a smaller delay time
	
	u_int uMaxOffset = static_cast<u_int>( ( static_cast<float>( Asura_XAudio2::GetOutputSampleRate() ) / static_cast<float>( uFrames ) ) * xParams.m_fRange );
	uMaxOffset = Asura_Maths::Min( uFrames * 2 - 1, uMaxOffset );

	if( !m_bReverseDir )
	{
		m_uSampleOffset = m_uSampleOffset + 1;
		
		if( m_uSampleOffset == uMaxOffset )
		{
			m_bReverseDir = true;
		}
	}
	else
	{
		m_uSampleOffset = m_uSampleOffset - 1;

		if( m_uSampleOffset == 0 )
		{
			m_bReverseDir = false;
		}
	}
	
}


#endif

//eof
