// Asura_PS3_Multistream_Voice.cpp
// Thomas Young - 17.09.08
// Copyright (C) 2008 Rebellion, All Rights Reserved.

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

#include "Core/Asura.h"

#ifdef ASURA_USE_AUDIO_SYSTEM

// Asura
#include "Core/Threads/Asura_ProducerConsumerQueue.h"
#include "Core/Memory/Asura_Mem.h"
#include "Core/Asura_Timers.h"
#include "Sound/Asura_Audio_DSP.h"

// PS3 headers
#include "Sound/Asura_PS3_Multistream_Audio.h"
#include "Sound/Asura_PS3_Multistream_Voice.h"
#include "Sound/Asura_PS3_Multistream_File_Loading.h"
#include "Sound/Asura_PS3_DSP_Params.h"

// SDK headers
#include <errno.h>
#include <cell/mstream.h>

// C O N S T A N T S /////////////////////////////////////////////////////////

static const float				fSTREAMING_MIN_SEEK_WARNING		= 0.02f;
static const float				fMS_VOICE_LPF_CUTOFF			= 1000.0f;
static const float				fMS_VOICE_LPF_MAX_GAIN			= 32.0f;
static const float				fMS_VOICE_DESTROY_TIMER			= 1.0f;
static const float				fMS_VOICE_MIN_PITCH				= 0.01f;

static const CELL_MS_DSPSLOTS	iMS_VOICE_DSP_SLOT_FILTER1		= CELL_MS_DSP_SLOT_5;
static const CELL_MS_DSPSLOTS	iMS_VOICE_DSP_SLOT_AUXSEND		= CELL_MS_DSP_SLOT_6;
static const CELL_MS_DSPSLOTS	iMS_VOICE_DSP_SLOT_FILTER2		= CELL_MS_DSP_SLOT_7;

// G L O B A L   V A R I A B L E S ///////////////////////////////////////////

static Asura_ProducerConsumerQueue	g_xStreamPrepQueue( 4, 1, ASURA_MULTITHREAD_STACK_SIZE_SMALL );

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

static inline bool Equal( float fA, float fB, float fTolerance=0.0000001f)
{
	return Asura_Maths::Abs( fA - fB ) < fTolerance;
}

//____________________ Asura_MultiStream_Voice ______________________________________________________
//___________________________________________________________________________________________________

Asura_MultiStream_Voice::Asura_MultiStream_Voice () : Asura_Audio_Voice()
{
	m_iMSStreamHandle			= 0;
	m_bStreamCreated			= false;
	m_iNumChannels				= 0;
	m_bErrorOnStream			= false;
	m_bIsLooping				= false;
	m_bPausedByPitch			= false;
	m_bPausedExternally			= false;
	m_bPlaying					= false;
	m_bReadyForMoreData			= false;
	m_iAudioSystemHandle		= ASURA_INVALID_HANDLE;
	m_fStartOffset				= 0.0f;
	m_bCanDeleteMe				= false;
	m_bDestroyPending			= false;

	m_bPrimaryFilterActive		= false;
	m_pPrimaryFilterBuffer		= 0;
	m_pPrimaryFilterParamAddr	= 0;
	m_fPrimaryFilterGain		= -1.0f;

	m_bSecondaryFilterActive	= false;
	m_pSecondaryFilterBuffer	= 0;
	m_pSecondaryFilterParamAddr	= 0;
	m_fSecondaryFilterGain		= -1.0f;

	m_bAuxSendDSPActive			= false;
	m_pAuxSendDSPBuffer			= 0;
	m_pAuxSendDSPParamAddr		= 0;

	Asura_Mem::Set( m_apChainDSPBuffers, 0, sizeof( void* ) * ASURA_DSP_MAX_EFFECTS_PER_CHAIN );

	m_bUsingMasterDSP			= true;
	m_iMultiChannelBus			= ASURA_MS_INVALID_SUBBUS;
	m_uDSPChain					= ASURA_HASH_ID_UNSET;

	m_fDeathTimer				= 0.0f;
}


bool Asura_MultiStream_Voice::CreateStream ()
{
	if ( IsStreamCreated() )
	{
		Asura_Assert(false, "Stream already created");
		return false;
	}

	m_iMSStreamHandle = cellMSStreamOpen();
	if ( m_iMSStreamHandle < 0 )
	{
		Asura_Assertf(false, ("Unable to open new stream (error %d)\n", cellMSSystemGetLastError()));
		return false;
	}

	if( cellMSCoreInit( m_iMSStreamHandle ) != 0 )
	{
		Asura_Assertf(false, ("cellMSCoreInit failed (error %d)\n", cellMSSystemGetLastError()));
		return false;
	}

	SetStreamCreated ();

	return true;
};


bool Asura_MultiStream_Voice::IsPlaying	() const
{
	return m_bPlaying;
}


void Asura_MultiStream_Voice::Update ( const bool bFirstUpdate )
{
	if( CanDeleteMe() )
	{
		//We are just waiting to be deleted
		return;
	}

	if( !IsStreamValid() )
	{
		// Is this a good way to deal with broken streams I wonder
		Destroy();
	}

	if( !IsPlaying() && !bFirstUpdate )
	{
		return;
	}

	Asura_Audio_Voice::Update	( bFirstUpdate );
	UpdateVolumes				( );
	UpdatePrimaryFilter			( );
	UpdateSecondaryFilter		( );
	UpdateAuxSend				( );
}


void Asura_MultiStream_Voice::UpdateVolumes ()
{
	const float fBaseVolume = GetVolume();

	if( m_iNumChannels == 1 )
	{
		//Set the output volumes for this channel
		const float afVolumes[8] = {

			GetSpeakerAmplitude( Asura_Audio_System::ASURA_SPEAKER_FRONT_LEFT	) * fBaseVolume,
			GetSpeakerAmplitude( Asura_Audio_System::ASURA_SPEAKER_FRONT_RIGHT	) * fBaseVolume,
			GetSpeakerAmplitude( Asura_Audio_System::ASURA_SPEAKER_FRONT_CENTRE ) * fBaseVolume,
			GetSpeakerAmplitude( Asura_Audio_System::ASURA_SPEAKER_LOW_FREQ		) * fBaseVolume,
			GetSpeakerAmplitude( Asura_Audio_System::ASURA_SPEAKER_REAR_LEFT	) * fBaseVolume,
			GetSpeakerAmplitude( Asura_Audio_System::ASURA_SPEAKER_REAR_RIGHT	) * fBaseVolume,
			GetSpeakerAmplitude( Asura_Audio_System::ASURA_SPEAKER_SIDE_LEFT	) * fBaseVolume,
			GetSpeakerAmplitude( Asura_Audio_System::ASURA_SPEAKER_SIDE_RIGHT	) * fBaseVolume		};

			#ifdef ASURA_ASSERT
			int iRet = 
			#endif
			cellMSCoreSetVolume8(m_iMSStreamHandle, CELL_MS_WET_AND_DRY, CELL_MS_CHANNEL_0, &afVolumes[0]);
			Asura_Assertf(iRet >= 0, ("cellMSCoreSetVolume8 failed, error %d", cellMSSystemGetLastError()) );

	}
	else
	{
		Asura_Assert( m_iNumChannels == 2, "Expected 2 channel sound, are we using 5.1 sounds now?" );
		Asura_Assert( m_iMultiChannelBus != ASURA_MS_INVALID_SUBBUS, "Expected multichannel sound to have dedicated subbus" ); 

		if( m_iMultiChannelBus != ASURA_MS_INVALID_SUBBUS )
		{
			//Multiple channel, pass through
			float afVolumes[8];

			for( int iChannel = 0; iChannel < m_iNumChannels; ++iChannel )
			{
				//Set the output volumes for this channel
				Asura_Mem::Set( afVolumes, 0, sizeof( float ) * 8 );
				afVolumes[iChannel] = fBaseVolume * GetSpeakerAmplitude( static_cast< Asura_Audio_System::Speakers >( iChannel ) );

				#ifdef ASURA_ASSERT
				int iRet = 
				#endif
				cellMSCoreSetVolume8(m_iMultiChannelBus | CELL_MS_BUS_FLAG, CELL_MS_WET, static_cast<CELL_MS_CHANNELS>( iChannel ), &afVolumes[0]);
				Asura_Assertf(iRet >= 0, ("cellMSCoreSetVolume8 failed, error %d", cellMSSystemGetLastError()) );

				afVolumes[iChannel] = 1.0f;
				#ifdef ASURA_ASSERT
				iRet = 
				#endif
				cellMSCoreSetVolume8(m_iMSStreamHandle, CELL_MS_DRY, static_cast<CELL_MS_CHANNELS>( iChannel ), &afVolumes[0]);
				Asura_Assertf(iRet >= 0, ("cellMSCoreSetVolume8 failed, error %d", cellMSSystemGetLastError()) );		
			}
			
			// Disable wet routing, we dont want a mono version of our signal passing through
			Asura_Mem::Set( afVolumes, 0, sizeof( float ) * 8 );
			
			#ifdef ASURA_ASSERT
			int iRet = 
			#endif
			cellMSCoreSetVolume8(m_iMSStreamHandle, CELL_MS_WET, CELL_MS_WET_STREAM, &afVolumes[0]);
			Asura_Assertf(iRet >= 0, ("cellMSCoreSetVolume8 failed, error %d", cellMSSystemGetLastError()) );		

		}
		
	}
}


void Asura_MultiStream_Voice::UpdatePrimaryFilter ()
{
	if( !m_bPrimaryFilterActive )
	{
		return;
	}

	float fDistanceGain		= 0.0f;
	float fOcclusionGain	= 0.0f;

	if( IsUsingDistanceFiltering() )
	{
		fDistanceGain = GetDistanceFilterFactor();
		Asura_Assert( fDistanceGain >= 0.0f, "Distance gain is below zero" );
		fDistanceGain *= fDistanceGain;				// Try to get a perceptibly linear falloff
	}

	if( IsUsingOcclusion() )
	{
		fOcclusionGain = GetOcclusionFactor ();
		Asura_Assert( fOcclusionGain >= 0.0f, "Occlusion gain is below zero" );
	}

	float fGain = Asura_Maths::Min( fOcclusionGain + fDistanceGain, 1.0f );

	Asura_Assertf( ( fGain >= 0.0f ) && ( fGain <= 1.0f ), ( "Filter gain is out of range: %.2f (distance factor: %.2f, occlusion factor: %.2f)", fGain, fDistanceGain, fOcclusionGain ) );

	// Only adjust the filter if the change in significant
	if ( Equal( m_fPrimaryFilterGain, fGain, 0.01f ) )
	{
		return;
	}
	m_fPrimaryFilterGain = fGain;

	CellMSFXFilter xFilterInfo;

	xFilterInfo.fFrequency	= fMS_VOICE_LPF_CUTOFF;
	xFilterInfo.FilterMode	= CELL_MSFX_FILTERMODE_HIGHSHELF;
	xFilterInfo.fGain		= fGain * -fMS_VOICE_LPF_MAX_GAIN;

	#ifdef ASURA_ASSERT
	int iRet = cellMSFXFilterSet( m_pPrimaryFilterParamAddr, &xFilterInfo, -1);
	Asura_Assertf(iRet >= 0, ("cellMSFXFilterSet failed, error %d", cellMSSystemGetLastError() ) );
	#else
	cellMSFXFilterSet( m_pPrimaryFilterParamAddr, &xFilterInfo, -1);
	#endif
}


void Asura_MultiStream_Voice::UpdateSecondaryFilter ()
{
	if( !IsUsingOcclusion() )
	{
		return;
	}
	
	if( !m_bSecondaryFilterActive )
	{
		return;
	}

	const float fGain = GetObstructionFactor ();
	
	// Only adjust the filter if the change in significant
	if ( Equal( m_fSecondaryFilterGain, fGain, 0.01f ) )
	{
		return;
	}
	m_fSecondaryFilterGain = fGain;

	CellMSFXFilter xFilterInfo;

	xFilterInfo.fFrequency	= fMS_VOICE_LPF_CUTOFF;
	xFilterInfo.FilterMode	= CELL_MSFX_FILTERMODE_HIGHSHELF;
	xFilterInfo.fGain		= fGain * -fMS_VOICE_LPF_MAX_GAIN;

	#ifdef ASURA_ASSERT
	int iRet = cellMSFXFilterSet( m_pSecondaryFilterParamAddr, &xFilterInfo, -1);
	Asura_Assertf(iRet >= 0, ("cellMSFXFilterSet failed, error %d", cellMSSystemGetLastError() ) );
	#else
	cellMSFXFilterSet( m_pSecondaryFilterParamAddr, &xFilterInfo, -1);
	#endif
}


void Asura_MultiStream_Voice::UpdateAuxSend ()
{
	if( !IsUsingReverb() )
	{
		return;
	}

	if( !m_bAuxSendDSPActive )
	{
		return;
	}

	const float fSendVolume = GetReverbSendLevel();
	for( int iChan = 0; iChan < m_iNumChannels; ++iChan )
	{
		cellMSFXAuxSendSetVolumes( m_pAuxSendDSPBuffer, iChan, &fSendVolume, 1 );
	}
}


void Asura_MultiStream_Voice::StreamCallback ( int iStreamHandle, void* pUserData, int iCallbackType, void* pWriteBuffer, int iBufferSize )
{
	Asura_MultiStream_Voice* pxVoice = reinterpret_cast<Asura_MultiStream_Voice*>( pUserData );
	if( !pxVoice )
	{
		return;
	}

	switch( iCallbackType )
	{
		case CELL_MS_CALLBACK_MOREDATA:
		{
			pxVoice->SetReadyForMoreData ();
		}
		break;

		case CELL_MS_CALLBACK_FINISHSTREAM:
		{
			pxVoice->SetPlaying( false );
		}
		break;

		case CELL_MS_CALLBACK_MP3DECODEERROR:
		{
			Asura_Assert(false, "MP3 decoding error, destroying stream");
			pxVoice->Destroy();
			break;
		}

		default: break;
	}
}


void Asura_MultiStream_Voice::SetStartOffset ( const float fStartOffset )
{
	if( IsPlaying() )
	{
		Asura_Assert( false, "This voice is already playing, you cannot set the start offset" );
		return;
	}

	m_fStartOffset = fStartOffset;
}


void Asura_MultiStream_Voice::FakeDestructor ()
{
	Asura_Assert( CanDeleteMe() || !IsStreamCreated(), "You should not be deleting this voice yet" );

	if( IsStreamCreated() )
	{
		cellMSStreamClose		( m_iMSStreamHandle );
		SetStreamCreated		( false );
		
		DestroyBaseDSPEffects	( );
	}
}


void Asura_MultiStream_Voice::CreatePrimaryLPFilter ( )
{
	Asura_Assert(!m_bPrimaryFilterActive, "Primary LPF is already active");

	if( m_bErrorOnStream )
	{
		return;
	}
	
	// Decide where to create this DSP effect
	const int iStreamOrBus = ( m_iMultiChannelBus == ASURA_MS_INVALID_SUBBUS ) ? m_iMSStreamHandle : m_iMultiChannelBus | CELL_MS_BUS_FLAG;

	const int iDSPHandle = Asura_MultiStream_Audio::GetDSPHandle( ASURA_MS_DSP_EFFECT_HANDLE_LPF );

	if(iDSPHandle >= 0)
	{
		cellMSCoreSetDSP( iStreamOrBus, iMS_VOICE_DSP_SLOT_FILTER1, iDSPHandle);
		m_pPrimaryFilterParamAddr = cellMSCoreGetDSPParamAddr( iStreamOrBus, iMS_VOICE_DSP_SLOT_FILTER1 );

		Asura_Assert( m_pPrimaryFilterParamAddr, "Failed to get DSP Param addresss" );
		if(m_pPrimaryFilterParamAddr)
		{
			int iMemSize = cellMSFXFilterGetNeededMemorySize( 8 );
			m_pPrimaryFilterBuffer = Asura_MultiStream_Audio::AllocateFromDSPHeap( iMemSize, 16 );

			Asura_Assert( m_pPrimaryFilterBuffer, "Failed to allocate space for DSP effect" );
			if( m_pPrimaryFilterBuffer )
			{
				Asura_Mem::Set( m_pPrimaryFilterBuffer, 0, (u_int)iMemSize);

				CellMSFXFilter xFilterInfo;

				xFilterInfo.fFrequency		= fMS_VOICE_LPF_CUTOFF;
				xFilterInfo.FilterMode		= CELL_MSFX_FILTERMODE_HIGHSHELF;
				xFilterInfo.fGain			= 0.0f;

				int iRet = cellMSFXFilterInit( m_pPrimaryFilterParamAddr, m_pPrimaryFilterBuffer, &xFilterInfo );
				Asura_Assert(iRet >= 0, "Failed to set filter params");
				if(iRet >= 0)
				{
					cellMSCoreBypassDSP( iStreamOrBus, iMS_VOICE_DSP_SLOT_FILTER1, CELL_MS_NOTBYPASSED );
					m_bPrimaryFilterActive = true;
				}
			}
		}
	}
}


void Asura_MultiStream_Voice::CreateSecondaryLPFilter ( )
{
	Asura_Assert(!m_bSecondaryFilterActive, "Secondary LPF is already active");

	if( m_bErrorOnStream )
	{
		return;
	}

	// Decide where to create this DSP effect
	const int iStreamOrBus = ( m_iMultiChannelBus == ASURA_MS_INVALID_SUBBUS ) ? m_iMSStreamHandle : m_iMultiChannelBus | CELL_MS_BUS_FLAG;

	const int iDSPHandle = Asura_MultiStream_Audio::GetDSPHandle( ASURA_MS_DSP_EFFECT_HANDLE_LPF );

	if(iDSPHandle >= 0)
	{
		cellMSCoreSetDSP( iStreamOrBus, iMS_VOICE_DSP_SLOT_FILTER2, iDSPHandle);
		m_pSecondaryFilterParamAddr = cellMSCoreGetDSPParamAddr( iStreamOrBus, iMS_VOICE_DSP_SLOT_FILTER2 );

		Asura_Assert( m_pSecondaryFilterParamAddr, "Failed to get DSP Param addresss" );
		if(m_pSecondaryFilterParamAddr)
		{
			int iMemSize = cellMSFXFilterGetNeededMemorySize( 8 );
			m_pSecondaryFilterBuffer = Asura_MultiStream_Audio::AllocateFromDSPHeap( iMemSize, 16 );

			Asura_Assert( m_pSecondaryFilterBuffer, "Failed to allocate space for DSP effect" );
			if( m_pSecondaryFilterBuffer )
			{
				Asura_Mem::Set( m_pSecondaryFilterBuffer, 0, (u_int)iMemSize);

				CellMSFXFilter xFilterInfo;

				xFilterInfo.fFrequency		= fMS_VOICE_LPF_CUTOFF;
				xFilterInfo.FilterMode		= CELL_MSFX_FILTERMODE_HIGHSHELF;
				xFilterInfo.fGain			= 0.0f;

				int iRet = cellMSFXFilterInit( m_pSecondaryFilterParamAddr, m_pSecondaryFilterBuffer, &xFilterInfo );
				Asura_Assert(iRet >= 0, "Failed to set filter params");
				if(iRet >= 0)
				{
					cellMSCoreBypassDSP( iStreamOrBus, iMS_VOICE_DSP_SLOT_FILTER2, CELL_MS_NOTBYPASSED );
					m_bSecondaryFilterActive = true;
				}
			}
		}
	}
}


void Asura_MultiStream_Voice::CreateAuxSend ( )
{
	Asura_Assert( !m_bAuxSendDSPActive, "aux send is active, but we are about to create it" );
	Asura_Assert( IsUsingReverb(), "why are we sending data to the reverb bus when we are not using reverb?" );
	
	if( m_bErrorOnStream )
	{
		return;
	}

	// Decide where to create this DSP effect
	const int iStreamOrBus = ( m_iMultiChannelBus == ASURA_MS_INVALID_SUBBUS ) ? m_iMSStreamHandle : m_iMultiChannelBus | CELL_MS_BUS_FLAG;

	const int iDSPHandle = Asura_MultiStream_Audio::GetDSPHandle( ASURA_MS_DSP_EFFECT_HANDLE_AUX_SEND );

	cellMSCoreSetDSP( iStreamOrBus, iMS_VOICE_DSP_SLOT_AUXSEND, iDSPHandle);
	m_pAuxSendDSPParamAddr = cellMSCoreGetDSPParamAddr( iStreamOrBus, iMS_VOICE_DSP_SLOT_AUXSEND);

	if(m_pAuxSendDSPParamAddr)
	{
		CellMSFXAuxSend xSend;

		xSend.nSendChannels		= 8;
		xSend.nSendsPerChannel	= 1;

		int iMemSize = cellMSFXAuxSendGetNeededMemorySize( &xSend );
		m_pAuxSendDSPBuffer = Asura_MultiStream_Audio::AllocateFromDSPHeap( iMemSize, 16 );

		Asura_Assert( m_pAuxSendDSPBuffer, "Failed to allocate from DSP heap" );
		if( m_pAuxSendDSPBuffer )
		{
			int iRet = cellMSFXAuxSendInit( m_pAuxSendDSPParamAddr, m_pAuxSendDSPBuffer, &xSend );
			Asura_Assert(iRet >= 0, "cellMSFXAuxSendInit failed");
			if(iRet >= 0)
			{
				//Set the send to the reverb bus
				for( int iChan = 0; iChan < m_iNumChannels; ++iChan )
				{
					iRet = cellMSFXAuxSendSet	(	m_pAuxSendDSPBuffer
												,	iChan
												,	0
												,	Asura_MultiStream_Audio::GetReverbAuxRetBuffer( m_bUsingMasterDSP )
												,	iChan
												,	0.0f );
					if(iRet < 0)
					{
						Asura_Assertf( false, ( "cellMSFXAuxSendSet failed, error : %d\n", cellMSSystemGetLastError() ) )
							return;
					}
				}

				cellMSCoreBypassDSP( iStreamOrBus, iMS_VOICE_DSP_SLOT_AUXSEND, CELL_MS_NOTBYPASSED );
				m_bAuxSendDSPActive = true;
			}
		}
	}
}

void Asura_MultiStream_Voice::CreateBaseDSPEffects ()
{
	// If we are multichannel and using stream DSP, use a new subbus
	if( !IsMono() )
	{
		m_uDSPChain = ASURA_HASH_ID_UNSET;

		m_iMultiChannelBus = Asura_MultiStream_Audio::GetNextFreeSubbus();

		if( m_iMultiChannelBus == ASURA_MS_INVALID_SUBBUS)
		{
			// If we fail to get a free subbus, dont do DSP effects on this voice
			Asura_Assert( false, "Ran out of subbusses for multichannel voices effects" );
			m_uDSPChain = ASURA_HASH_ID_UNSET;
		}
		else
		{
			// Route this subbus based on our Master DSP usage
			if( IsUsingMasterDSP() )
			{
				// Unroute from master
				cellMSCoreSetRouting( m_iMultiChannelBus | CELL_MS_BUS_FLAG, CELL_MS_MASTER_BUS, CELL_MS_WET_AND_DRY, CELL_MS_ROUTE_OFF, 0.0f, 0.0f );
				
				// Route to DSP busses (just wet)
				cellMSCoreSetRouting( m_iMultiChannelBus | CELL_MS_BUS_FLAG, Asura_MultiStream_Audio::iASURA_MS_MASTER_DSP1_BUS, CELL_MS_WET, CELL_MS_ROUTE_ON, 0.0f, 1.0f );
				cellMSCoreSetRouting( m_iMultiChannelBus | CELL_MS_BUS_FLAG, Asura_MultiStream_Audio::iASURA_MS_MASTER_DSP2_BUS, CELL_MS_WET, CELL_MS_ROUTE_ON, 0.0f, 1.0f );

				cellMSCoreSetRouting( m_iMultiChannelBus | CELL_MS_BUS_FLAG, Asura_MultiStream_Audio::iASURA_MS_MASTER_DSP1_BUS, CELL_MS_DRY, CELL_MS_ROUTE_OFF, 0.0f, 0.0f );
				cellMSCoreSetRouting( m_iMultiChannelBus | CELL_MS_BUS_FLAG, Asura_MultiStream_Audio::iASURA_MS_MASTER_DSP2_BUS, CELL_MS_DRY, CELL_MS_ROUTE_OFF, 0.0f, 0.0f );
			}
			else
			{
				// Route to master (just wet)
				cellMSCoreSetRouting( m_iMultiChannelBus | CELL_MS_BUS_FLAG, CELL_MS_MASTER_BUS, CELL_MS_WET, CELL_MS_ROUTE_ON, 0.0f, 1.0f );
				cellMSCoreSetRouting( m_iMultiChannelBus | CELL_MS_BUS_FLAG, CELL_MS_MASTER_BUS, CELL_MS_DRY, CELL_MS_ROUTE_OFF, 0.0f, 0.0f );

				// Unroute from DSP busses
				cellMSCoreSetRouting( m_iMultiChannelBus | CELL_MS_BUS_FLAG, Asura_MultiStream_Audio::iASURA_MS_MASTER_DSP1_BUS, CELL_MS_WET_AND_DRY, CELL_MS_ROUTE_OFF, 0.0f, 0.0f );
				cellMSCoreSetRouting( m_iMultiChannelBus | CELL_MS_BUS_FLAG, Asura_MultiStream_Audio::iASURA_MS_MASTER_DSP2_BUS, CELL_MS_WET_AND_DRY, CELL_MS_ROUTE_OFF, 0.0f, 0.0f );
			}
		}
	}

	// Create the 2 filters and aux send where appropriate
	if( IsUsingDistanceFiltering() || IsUsingOcclusion() )
	{
		CreatePrimaryLPFilter();

		if( IsUsingOcclusion() )
		{
			CreateSecondaryLPFilter();
		}
	}
	if( IsUsingReverb() )
	{
		CreateAuxSend();
	}
	CreateAsuraDSPChainFX();
}

void Asura_MultiStream_Voice::CreateAsuraDSPChainFX ( )
{
	if( m_uDSPChain == ASURA_HASH_ID_UNSET )
	{
		// No DSP chain on this voice
		return;
	}

	Asura_Audio_DSPChain* pxChain = Asura_Audio_System::GetDSPChainByHash( m_uDSPChain );

	if( !pxChain )
	{
		Asura_Assertf( false, ( "Failed to find DSP chain with hash %u", m_uDSPChain ) );
		return;
	}

	const int iStreamOrBus = ( m_iMultiChannelBus == ASURA_MS_INVALID_SUBBUS ) ? m_iMSStreamHandle : m_iMultiChannelBus | CELL_MS_BUS_FLAG;

	for( u_int uSlot = 0; uSlot < pxChain->m_uNumEffects; ++uSlot )
	{
		// The independent and multistream DSP handles are interchangable
		const int iDSPHandle = Asura_MultiStream_Audio::GetDSPHandle( static_cast< ASURA_MS_DSP_EFFECT_HANDLE >( pxChain->m_ppxEffects[ uSlot ]->GetType() ) );

		if(iDSPHandle >= 0)
		{
			cellMSCoreSetDSP( iStreamOrBus, static_cast<CELL_MS_DSPSLOTS>( CELL_MS_DSP_SLOT_0 + uSlot ), iDSPHandle);
			void* pParams = cellMSCoreGetDSPParamAddr( iStreamOrBus, CELL_MS_DSP_SLOT_0 );

			Asura_Assert( pParams, "Failed to get DSP Param addresss" );
			if(pParams)
			{
				// Apply the parameters, i.e. map the Asura params to PS3 params

				switch( pxChain->m_ppxEffects[ uSlot ]->GetType() )
				{
					case ASURA_DSP_ADD_NOISE:
					{	 
						Asura_DSP_Params_AddNoise*		pxAsuraParams	= reinterpret_cast< Asura_DSP_Params_AddNoise* >( pxChain->m_ppxEffects[ uSlot ] );
						Asura_PS3_DSP_Params::AddNoise* pxPS3Params		= reinterpret_cast< Asura_PS3_DSP_Params::AddNoise*>( pParams );

						pxPS3Params->m_fNoiseAmount = pxAsuraParams->m_xParams.m_fNoiseAmount;

						const int iRequiredMemory = Asura_PS3_DSP_Params::AddNoise::uSCRATCH_MEMORY_REQ;

						pxPS3Params->m_pScratchMem		= Asura_MultiStream_Audio::AllocateFromDSPHeap ( iRequiredMemory, 16 );
						pxPS3Params->m_uScratchMemSize	= static_cast<u_int>( iRequiredMemory );

						m_apChainDSPBuffers[ uSlot ] = pxPS3Params->m_pScratchMem;		// Keep a record so we can deallocate it later
					}
					break;

					case ASURA_DSP_SIMPLE_FILTER:
					{
						Asura_DSP_Params_SimpleFilter*		pxAsuraParams	= reinterpret_cast< Asura_DSP_Params_SimpleFilter* >( pxChain->m_ppxEffects[ uSlot ] );
						Asura_PS3_DSP_Params::SimpleFilter* pxPS3Params		= reinterpret_cast< Asura_PS3_DSP_Params::SimpleFilter*>( pParams );

						// the number of coefficients must be what we are expecting (it would be better if the value was in one place only, but it isnt really practical)
						ASURA_STATIC_ASSERT( Asura_DSP_Params_SimpleFilter::uFILTER_NUM_COEFFICIENTS == Asura_PS3_DSP_Params::SimpleFilter::uNUM_COEFFICIENTS );

						Asura_Mem::Copy( pxPS3Params->m_afCoefficients, pxAsuraParams->m_xParams.m_afCoefficients, sizeof( float ) * Asura_DSP_Params_SimpleFilter::uFILTER_NUM_COEFFICIENTS );
						pxPS3Params->m_bValid = pxAsuraParams->m_xParams.m_bValid;
						pxPS3Params->m_fGain = pxAsuraParams->m_xParams.m_fGain;

						const int iRequiredMemory = Asura_PS3_DSP_Params::SimpleFilter::uSCRATCH_MEMORY_REQ;

						pxPS3Params->m_pScratchMem		= Asura_MultiStream_Audio::AllocateFromDSPHeap ( iRequiredMemory, 16 );
						pxPS3Params->m_uScratchMemSize	= static_cast<u_int>( iRequiredMemory );

						Asura_Mem::Set( pxPS3Params->m_pScratchMem, 0, iRequiredMemory );

						m_apChainDSPBuffers[ uSlot ] = pxPS3Params->m_pScratchMem;
					}
					break;

					case ASURA_DSP_NUM_TYPES:
					default:
					{
						Asura_Assertf( false, ( "Unknown DSP effect in slot %u of DSP chain '%s'", uSlot, pxChain->m_xName.GetString() ) );
						continue;
					}
				}
			}
		}

		cellMSCoreBypassDSP( iStreamOrBus, CELL_MS_DSP_SLOT_0, CELL_MS_NOTBYPASSED );
	}
}


void Asura_MultiStream_Voice::DestroyBaseDSPEffects ()
{
	// Primary filter

	if( m_bPrimaryFilterActive )
	{
		Asura_MultiStream_Audio::DeallocateFromDSPHeap( m_pPrimaryFilterBuffer );
		
		m_pPrimaryFilterBuffer		= 0;
		m_pPrimaryFilterParamAddr	= 0;
		m_bPrimaryFilterActive		= false;
	}
	
	// Secondary Filter

	if( m_bSecondaryFilterActive )
	{
		Asura_MultiStream_Audio::DeallocateFromDSPHeap( m_pSecondaryFilterBuffer );

		m_pSecondaryFilterBuffer	= 0;
		m_pSecondaryFilterParamAddr	= 0;
		m_bSecondaryFilterActive	= false;
	}

	// Aux Send

	if( m_bAuxSendDSPActive )
	{
		Asura_MultiStream_Audio::DeallocateFromDSPHeap( m_pAuxSendDSPBuffer );

		m_pAuxSendDSPBuffer		= 0;
		m_pAuxSendDSPParamAddr	= 0;
		m_bAuxSendDSPActive		= false;
	}

	// Asura voice chain

	for( u_int uSlot = 0; uSlot < ASURA_DSP_MAX_EFFECTS_PER_CHAIN; ++uSlot )
	{
		Asura_MultiStream_Audio::DeallocateFromDSPHeap( m_apChainDSPBuffers[ uSlot ] );
		m_apChainDSPBuffers[ uSlot ] = 0;
	}

	// 'Release' the multichannel subbus if we have one

	if( m_iMultiChannelBus != ASURA_MS_INVALID_SUBBUS )
	{
		Asura_MultiStream_Audio::ReleaseSubbus( m_iMultiChannelBus );
	}
}


void Asura_MultiStream_Voice::SetupStreamRouting ( )
{
	// See the enumeration in Asura_PS3_Multistream_Audio.h for a brief rundown of each subbus

	// Unroute from the master
	cellMSStreamSetRoutingDry( m_iMSStreamHandle, CELL_MS_MASTER_BUS, 0.0f);
	cellMSStreamSetRoutingWet( m_iMSStreamHandle, CELL_MS_MASTER_BUS, 0.0f);

	// Route to the dead bus, all streams get routed here
	cellMSStreamSetRoutingDry( m_iMSStreamHandle, Asura_MultiStream_Audio::iASURA_MS_DEAD_BUS, 1.0f);
	cellMSStreamSetRoutingWet( m_iMSStreamHandle, Asura_MultiStream_Audio::iASURA_MS_DEAD_BUS, 1.0f);

	if( m_iMultiChannelBus != ASURA_MS_INVALID_SUBBUS )
	{
		// We have more than 1 channel, so route to out multichannel bus
		cellMSStreamSetRoutingDry( m_iMSStreamHandle, m_iMultiChannelBus | CELL_MS_BUS_FLAG, 1.0f);
	}
	else
	{
		// If we are mono
		if( IsUsingMasterDSP() )
		{
			// Route to master DSP busses (both of them)
			cellMSStreamSetRoutingWet( m_iMSStreamHandle, Asura_MultiStream_Audio::iASURA_MS_MASTER_DSP1_BUS, 1.0f);
			cellMSStreamSetRoutingWet( m_iMSStreamHandle, Asura_MultiStream_Audio::iASURA_MS_MASTER_DSP2_BUS, 1.0f);
		}
		else
		{
			// Route to the master voice
			cellMSStreamSetRoutingWet( m_iMSStreamHandle, CELL_MS_MASTER_BUS, 1.0f);
		}
	}
}


void Asura_MultiStream_Voice::SetVoiceDSPChain ( Asura_Hash_ID uDSPChain )
{
	if( IsPlaying() )
	{
		Asura_Assert( false, "Cannot set voice DSP chain after we have started playing" );
		return;
	}

	m_uDSPChain = uDSPChain;
}


void Asura_MultiStream_Voice::Destroy()
{
	// Disable all of the DSP effects for this stream
	const int iStreamOrBus = ( m_iMultiChannelBus == ASURA_MS_INVALID_SUBBUS ) ? m_iMSStreamHandle : m_iMultiChannelBus | CELL_MS_BUS_FLAG;

	if( m_bPrimaryFilterActive	) cellMSCoreSetDSP( iStreamOrBus, iMS_VOICE_DSP_SLOT_FILTER1, CELL_MS_DSPOFF );
	if( m_bAuxSendDSPActive		) cellMSCoreSetDSP( iStreamOrBus, iMS_VOICE_DSP_SLOT_AUXSEND, CELL_MS_DSPOFF );
	if( m_bSecondaryFilterActive) cellMSCoreSetDSP( iStreamOrBus, iMS_VOICE_DSP_SLOT_FILTER2, CELL_MS_DSPOFF );

	if( !IsPlaying() )
	{
		if( m_iMultiChannelBus != ASURA_MS_INVALID_SUBBUS )
		{
			// Delay to let the bus finish processing (we dont get any notification for this)
			m_fDeathTimer = fMS_VOICE_DESTROY_TIMER;
			SetDestroyPending();
		}
		else
		{
			SetCanDeleteMe();
		}
	}
	else if( IsStreamCreated() )
	{
		//Wait for stream to be 'finished' before deleting
		cellMSCoreStop		( m_iMSStreamHandle, CELL_MS_STOP );

		if( m_iMultiChannelBus != ASURA_MS_INVALID_SUBBUS )
		{
			// Delay to let the bus finish processing (we dont get any notification for this)
			m_fDeathTimer = fMS_VOICE_DESTROY_TIMER;
		}

		// Mark as ready to destroy, will be flagged to delete when the stream finishes
		SetDestroyPending	();
	}
}


void Asura_MultiStream_Voice::SetPausedByPitch( const bool bPaused )
{
	if( m_bPausedByPitch == bPaused )
	{
		return;
	}

	const bool bWasPaused = m_bPausedExternally || m_bPausedByPitch;

	m_bPausedByPitch = bPaused;

	const bool bCurrentlyPaused = m_bPausedExternally || m_bPausedByPitch;

	if( IsStreamCreated() )
	{
		if( !bWasPaused && bCurrentlyPaused && IsPlaying() )
		{
			cellMSCoreSetPause( m_iMSStreamHandle, CELL_MS_PAUSED );
		}
		else if( bWasPaused && !bCurrentlyPaused )
		{
			cellMSCoreSetPause( m_iMSStreamHandle, CELL_MS_NOTPAUSED );
		}
	}
}


void Asura_MultiStream_Voice::SetPausedExternally( const bool bPaused )
{
	if( m_bPausedExternally == bPaused )
	{
		return;
	}

	const bool bWasPaused = m_bPausedExternally || m_bPausedByPitch;

	m_bPausedExternally = bPaused;

	const bool bCurrentlyPaused = m_bPausedExternally || m_bPausedByPitch;

	if( IsStreamCreated() )
	{
		if( !bWasPaused && bCurrentlyPaused && IsPlaying() )
		{
			cellMSCoreSetPause( m_iMSStreamHandle, CELL_MS_PAUSED );
		}
		else if( bWasPaused && !bCurrentlyPaused )
		{
			cellMSCoreSetPause( m_iMSStreamHandle, CELL_MS_NOTPAUSED );
		}
	}
}


bool Asura_MultiStream_Voice::SetSubmixOutputs ( const Asura_Handle* auSubmixVoices, const u_int uNumVoices )
{
	return false;

	// TODO: Clear all the DSP effects and routing
	// route ourselves to the submixes specified
}


void Asura_MultiStream_Voice::ClearSubmixOutputs ( )
{
	// TODO: unroute ourselves from all submixes
	// create DSP effects and re-route ourselves to the appropriate places
}


//________________ Asura_MultiStream_Buffer_Voice ___________________________________________________________________
//___________________________________________________________________________________________________________________



Asura_MultiStream_Buffer_Voice::Asura_MultiStream_Buffer_Voice () : Asura_MultiStream_Voice ()
{
	for( int iWav = 0; iWav < ASURA_PS3_MULTISTREAM_VOICE_MAX_QUEUED_WAVS; ++iWav )
	{
		m_xWaveQueue[ iWav ] = ASURA_HASH_ID_UNSET;
	}
	m_xCurrentWav = ASURA_HASH_ID_UNSET;
}


void Asura_MultiStream_Buffer_Voice::EnqueueWave ( const Asura_Hash_ID uWave )
{
	if( !Asura_MultiStream_Audio::FindBuffer( uWave ) )
	{
		Asura_Assertf( false, ("Failed to find buffer for wave %u", uWave ) );
		return;
	}

	int iQueuePos = 0;
	while( m_xWaveQueue[iQueuePos] != ASURA_HASH_ID_UNSET )
	{
		++ iQueuePos;

		if( iQueuePos == ASURA_PS3_MULTISTREAM_VOICE_MAX_QUEUED_WAVS )
		{
			Asura_Assertf( false, ( "Cannot queue more than %d wavs", ASURA_PS3_MULTISTREAM_VOICE_MAX_QUEUED_WAVS ) );
			return;
		}
	}

	m_xWaveQueue[iQueuePos] = uWave;
}


void Asura_MultiStream_Buffer_Voice::Start	()
{
	if ( !IsStreamCreated() )
	{
		Asura_Assert( false, "stream not created");
		return;
	}

	if( IsPlaying() )
	{
		Asura_Assert( false, "voice already playing");
		return;
	}

	m_xCurrentWav = PopNextWaveToPlay();

	Asura_MultiStream_Buffer* pxBuffer = Asura_MultiStream_Audio::FindBuffer( m_xCurrentWav );

	if( !pxBuffer )
	{
		SetErrorOnStream();
		Asura_Assertf( pxBuffer, ( "Failed to find audio buffer with hash %u", m_xCurrentWav ) );
		return;
	}

	#if defined ASURA_AUDIO_STORE_FILENAMES
	m_szFilename = pxBuffer->GetFilename();
	#endif

	CellMSInfo xWaveInfo;
	xWaveInfo.SubBusGroup		= CELL_MS_MASTER_BUS;
	xWaveInfo.flags				= CELL_MS_STREAM_NOFLAGS;
	xWaveInfo.initialOffset		= 0;								// Initial delay (in samples) before playback starts
	xWaveInfo.inputType			= pxBuffer->GetSampleFormat		();
	xWaveInfo.Pitch				= pxBuffer->GetSampleRate		();
	xWaveInfo.numChannels		= pxBuffer->GetNumChannels		();

	xWaveInfo.FirstBuffer		= pxBuffer->GetSampleData		();
	xWaveInfo.FirstBufferSize	= pxBuffer->GetSampleDataSize	();

	xWaveInfo.SecondBuffer		= 0;
	xWaveInfo.SecondBufferSize	= 0;

	// Bind audio information to the current stream
	int iResult = cellMSStreamSetInfo( m_iMSStreamHandle, &xWaveInfo );
	if ( iResult != CELL_OK )
	{
		SetErrorOnStream();
		Asura_Assertf( false, ( "cellMSStreamSetInfo() error: %d\n", cellMSSystemGetLastError() ) );
		return;
	}

	m_iNumChannels = xWaveInfo.numChannels;

	Asura_Assert( m_fStartOffset == 0.0f, "Didnt expect to have non-zero start offsets for non streaming sounds, need to implement this" );

	if( pxBuffer->IsLooping() )
	{
		SetLooping ();

		cellMSStreamSetFirstReadSkip	(m_iMSStreamHandle, pxBuffer->GetSampleData(), pxBuffer->GetSampleDataSize(), 0, 0);
		cellMSStreamSetSecondReadSkip	(m_iMSStreamHandle, pxBuffer->GetSampleData(), pxBuffer->GetSampleDataSize(), 0, 0);
	}

	cellMSStreamSetCallbackData( m_iMSStreamHandle, this );
	cellMSStreamSetCallbackFunc( m_iMSStreamHandle, &Asura_MultiStream_Voice::StreamCallback );

	CreateBaseDSPEffects();	//Do this before calling update

	Update( true );

	// Set this stream playing
	if( !m_bPausedByPitch && !m_bPausedExternally )
	{
		cellMSStreamPlay(m_iMSStreamHandle);
	}
	else
	{
		cellMSStreamPlay(m_iMSStreamHandle);
		cellMSCoreSetPause(m_iMSStreamHandle, CELL_MS_PAUSED);
	}

	SetupStreamRouting ();

	SetCanDeleteMe	( false );
	SetPlaying		( true );
}


void Asura_MultiStream_Buffer_Voice::Update ( const bool bFirstUpdate )
{
	if( CanDeleteMe() )
	{
		//We are just waiting to be deleted
		return;
	}

	if( IsDestroyPending() )
	{
		if( m_fDeathTimer > 0.0f )
		{
			m_fDeathTimer = Asura_Maths::Max( m_fDeathTimer - Asura_Timers::GetActualFrameTime(), 0.0f );
		}

		if( IsPlaying() )
		{
			//Waiting for stream to be 'finished'
			return;
		}
		else
		{
			if( m_fDeathTimer == 0.0f )
			{
				//Ready to be deleted now
				SetCanDeleteMe();
			}
			return;
		}
	}

	if( !IsStreamValid() )
	{
		return;
	}

	Asura_MultiStream_Voice::Update( bFirstUpdate );

	// Update the pitch
	const float fPitch = GetPitch();

	if( fPitch < fMS_VOICE_MIN_PITCH )
	{
		if( !IsPausedByPitch() )
		{
			SetPausedByPitch();
		}
	}
	else
	{
		if( IsPausedByPitch() )
		{
			SetPausedByPitch( false );
		}

		// Set the pitch
		if( m_xCurrentWav != ASURA_HASH_ID_UNSET )
		{
			Asura_MultiStream_Buffer* pxBuffer = Asura_MultiStream_Audio::FindBuffer( m_xCurrentWav );
			Asura_Assertf( pxBuffer, ( "Failed to find audio buffer with hash %u", m_xCurrentWav ) );
			if( pxBuffer )
			{
				cellMSStreamSetPitch( m_iMSStreamHandle, static_cast<int>( static_cast<float>( pxBuffer->GetSampleRate() ) * fPitch ) );
			}
		}
		
	}
	

	// Check if we are ready for more data
	if( IsReadyForMoreData() )
	{
		SetReadyForMoreData( false );

		//If looping the second buffer already contains the correct data
		if( !IsLooping() )
		{
			m_xCurrentWav = PopNextWaveToPlay();
			
			Asura_MultiStream_Buffer* pxBuffer = Asura_MultiStream_Audio::FindBuffer( m_xCurrentWav );
			
			if( !pxBuffer )
			{
				cellMSStreamSetSecondRead ( m_iMSStreamHandle, 0, 0 );

				//Nothing else to play, are we done?
				if( IsReleased() )
				{
					Destroy();
				}
			}
			else
			{
				//Queue the next wav
				cellMSStreamSetSecondReadSkip( m_iMSStreamHandle, pxBuffer->GetSampleData(), pxBuffer->GetSampleDataSize(), 0, 0 );
			}
		}
	}
}


Asura_Hash_ID Asura_MultiStream_Buffer_Voice::PopNextWaveToPlay	()
{
	Asura_Hash_ID xNextWave = m_xWaveQueue[0];
	if( xNextWave != ASURA_HASH_ID_UNSET )
	{
		memmove( &m_xWaveQueue[0], &m_xWaveQueue[1], sizeof( Asura_Hash_ID ) * ASURA_PS3_MULTISTREAM_VOICE_MAX_QUEUED_WAVS );	//no Asura_Mem::Move yet :(
		m_xWaveQueue[ ASURA_PS3_MULTISTREAM_VOICE_MAX_QUEUED_WAVS - 1 ] = ASURA_HASH_ID_UNSET; 
	}
	return xNextWave;
}


//________________ Asura_MultiStream_MP3_Streaming_Voice ___________________________________________________________________
//__________________________________________________________________________________________________________________________



Asura_MultiStream_MP3_Streaming_Voice::Asura_MultiStream_MP3_Streaming_Voice ()	: Asura_MultiStream_Voice ()
{
	m_pxStreamer				= 0;
	m_eSeekState				= SEEK_STATE_NONE;
	m_fTimeToSeek				= 0.0f;
	m_fSeekPosition				= 0.0f;
	m_fCurrentBufferFinishTime	= 0.0f;
	m_fPlayTime					= 0.0f;
	m_bPlayAfterPrepared		= false;
	m_bPreparing				= false;
}


void Asura_MultiStream_MP3_Streaming_Voice::Start ()
{
	if ( !IsStreamCreated() )
	{
		Asura_Assert( false, "stream not created");
		return;
	}

	if( IsPlaying() )
	{
		Asura_Assert( false, "voice already playing");
		return;
	}

	if( !m_pxStreamer )
	{
		Asura_Assert( false, "Cannot start voice, nothing to play" );
		return;
	}

	if( m_pxStreamer->GetState() == Asura_MultiStream_MP3_Stream::STREAM_STATE_ERROR )
	{
		Asura_Assert( false, "Stream has an error, cannot play" );
		return;
	}

	if( m_bPreparing )
	{
		m_bPlayAfterPrepared = true;
	}
	else
	{
		Start_Internal();
	}

	// Report as playing (even if we are prepping)
	SetPlaying ( true );
}


void Asura_MultiStream_MP3_Streaming_Voice::Start_Internal ()
{
	Asura_Assert( !m_bPreparing, "Should not be preparing here" );

	if( !m_pxStreamer )
	{
		Asura_Assert( false, "Cannot start voice, nothing to play" );
		return;
	}

	if( m_pxStreamer->GetState() == Asura_MultiStream_MP3_Stream::STREAM_STATE_ERROR )
	{
		Asura_Assert( false, "Stream has an error, cannot play" );
		return;
	}

	cellMSStreamSetCallbackData( m_iMSStreamHandle, this );
	cellMSStreamSetCallbackFunc( m_iMSStreamHandle, &Asura_MultiStream_Voice::StreamCallback );

	CreateBaseDSPEffects();	//Do this before calling update

	Update( true );

	// Set this stream playing
	if( !m_bPausedByPitch && !m_bPausedExternally )
	{
		cellMSStreamPlay(m_iMSStreamHandle);
	}
	else
	{
		cellMSStreamPlay(m_iMSStreamHandle);
		cellMSCoreSetPause(m_iMSStreamHandle, CELL_MS_PAUSED);
	}

	Asura_OutputToDebugger(("Playback of Streamed Sound begins (handle=%d)\n", m_iMSStreamHandle));

	SetupStreamRouting ();
}


void Asura_MultiStream_MP3_Streaming_Voice::Update ( const bool bFirstUpdate )
{
	if( CanDeleteMe() )
	{
		//We are just waiting to be deleted
		return;
	}

	if( IsDestroyPending() )
	{
		if( IsPlaying() )
		{
			//Waiting for stream to be 'finished'
			return;
		}
		else
		{
			if( m_fDeathTimer > 0.0f )
			{
				m_fDeathTimer = Asura_Maths::Max( m_fDeathTimer - Asura_Timers::GetActualFrameTime(), 0.0f );
			}

			if( m_fDeathTimer == 0.0f )
			{
				//Ready to be deleted now
				SetCanDeleteMe();
			}
			return;
		}
	}

	if( !IsStreamValid() )
	{
		return;
	}

	if( IsPreparing() )
	{
		// Do nothing until we are prepared
		return;
	}
	else if( m_bPlayAfterPrepared )
	{
		// we have finished prepping, play
		m_bPlayAfterPrepared = false;
		Start_Internal ();				// note: this calls this function
		return;
	}

	Asura_MultiStream_Voice::Update( bFirstUpdate );


	// Update the pitch
	const float fPitch = GetPitch();

	if( fPitch < fMS_VOICE_MIN_PITCH )
	{
		if( !IsPausedByPitch() )
		{
			SetPausedByPitch();
		}
	}
	else
	{
		if( IsPausedByPitch() )
		{
			SetPausedByPitch( false );
		}

		// Set the pitch
		cellMSStreamSetPitch( m_iMSStreamHandle, static_cast<int>( static_cast<float>( m_pxStreamer->GetSampleRate() ) * fPitch ) );
	}

	Asura_MultiStream_MP3_Stream::StreamState eState = m_pxStreamer->GetState();

	//Check if our streamer has finished a load
	if(	m_pxStreamer )
	{
		if( IsPlaying() && ( eState == Asura_MultiStream_MP3_Stream::STREAM_STATE_LOADED ) )
		{
			char*	pxBuffer				= 0;
			u_int	uBufferSize				= 0;
			bool	bBufferDiscontinuous	= false;
			float	fBufferStartTime		= 0.0f;

			if( m_pxStreamer->GetBuffer( pxBuffer, uBufferSize, bBufferDiscontinuous, fBufferStartTime ) )
			{
				if( bBufferDiscontinuous )
				{
					cellMSStreamSetSecondReadSkip(m_iMSStreamHandle, pxBuffer, uBufferSize, 0, 0);
				}
				else
				{
					cellMSStreamSetSecondRead( m_iMSStreamHandle, pxBuffer, uBufferSize );
				}

				float fBytesPerSecond = static_cast<float>( ( m_pxStreamer->GetBitRate() * 1000 ) / 8 );
				m_fCurrentBufferFinishTime = fBufferStartTime + ( static_cast<float>( uBufferSize ) / fBytesPerSecond );
			}
		}
		else if( eState == Asura_MultiStream_MP3_Stream::STREAM_STATE_ERROR )
		{
			//um... I guess we should just shit-can this stream
			Destroy();
		}
	}

	if( IsReadyForMoreData() )
	{
		SetReadyForMoreData( false );

		if( eState == Asura_MultiStream_MP3_Stream::STREAM_STATE_COMPLETED )
		{
			cellMSStreamSetSecondRead( m_iMSStreamHandle, 0, 0 );
		}
		else if( m_pxStreamer )
		{
			//MS is using the next buffer
			m_pxStreamer->IncrementProcessingBuffer();

			switch( m_eSeekState )
			{
				case SEEK_STATE_NONE:
				case SEEK_STATE_REPORT_SEEKING:
				{
					// Just read more data
					m_pxStreamer->Read ();
				}
				break;

				case SEEK_STATE_PENDING:
				{
					// Check if we need to seek at some point during the next buffer
					const float fBufferDuration = m_pxStreamer->GetDefaultBufferSize() / static_cast<float>( ( m_pxStreamer->GetBitRate() * 1000 ) / 8 );
					
					if( m_fCurrentBufferFinishTime + fBufferDuration > m_fTimeToSeek )
					{
						if( ( m_fTimeToSeek - m_fCurrentBufferFinishTime ) < fSTREAMING_MIN_SEEK_WARNING )
						{
							// If we dont have enough warning, just ignore and read as normal
							Asura_Assert( false, "Not enough warning given to seek, aborting" );
							m_pxStreamer->Read ();
							m_eSeekState = SEEK_STATE_NONE;
						}
						else
						{
							// Set our next buffer size and read
							m_pxStreamer->SetNextReadSize( m_fTimeToSeek - m_fCurrentBufferFinishTime );
							m_pxStreamer->CleanNextRead();
							m_pxStreamer->Read();

							m_eSeekState = SEEK_STATE_NEXT_BUFFER;
						}
					}
					else
					{
						//Dont need to seek yet, just read
						m_pxStreamer->Read ();
					}
				}
				break;

				case SEEK_STATE_NEXT_BUFFER:
				{
					// Time to seek
					m_pxStreamer->SeekNextRead( m_fSeekPosition );
					m_pxStreamer->Read();

					m_eSeekState = SEEK_STATE_REPORT_SEEKING;
				}
				break;
			}
		}
	}

	//Update our play time
	if( IsPlaying() )
	{
		int iSampleOffset, iPacketSize, iSeekAddr;
		int iRet = cellMSStreamGetMP3FramePos( m_iMSStreamHandle, &iSampleOffset, &iPacketSize, &iSeekAddr );
		m_fPlayTime = m_pxStreamer->GetReadPosition( iSeekAddr );

		//const float fBytesPerSecond = static_cast<float>( ( m_pxStreamer->GetSoundInfo()->GetBitRate() * 1000 ) / 8 );
		//m_fPlayTime += ( static_cast<float>( iSampleOffset ) / fBytesPerSecond );

		//Asura_OutputToDebugger(("Playback time is %.8f - Seek: %d, offset %d, size %d\n", m_fPlayTime, iSeekAddr, iSampleOffset, iPacketSize));

		//TODO: This is really crude and needs to be changed. I am not entirely sure what to do though, since MS doesn't really let you know what buffer is currently being processed
		if( ( m_eSeekState == SEEK_STATE_REPORT_SEEKING ) && ( Asura_Maths::Abs( m_fPlayTime - m_fSeekPosition ) < 0.5f ) )
		{
			m_eSeekState = SEEK_STATE_NONE;
		}
	}

	if( ( eState == Asura_MultiStream_MP3_Stream::STREAM_STATE_COMPLETED ) && !IsPlaying() && IsReleased() )
	{
		Destroy();
	}
}


void Asura_MultiStream_MP3_Streaming_Voice::SeekAtPlaybackTime ( const float fSeekPosition, const float fPlaybackTime )
{
	if( SeekIsPending() )
	{
		Asura_Assert( false, "Seek is already pending" );
		return;
	}
	
	m_eSeekState		= SEEK_STATE_PENDING;
	m_fTimeToSeek		= fPlaybackTime;
	m_fSeekPosition		= fSeekPosition;
}


bool Asura_MultiStream_MP3_Streaming_Voice::SeekIsPending ( )
{
	return (m_eSeekState != SEEK_STATE_NONE);
}


void Asura_MultiStream_MP3_Streaming_Voice::Prepare ( const Asura_Hash_ID xWavHash )
{
	if ( !IsStreamCreated() )
	{
		Asura_Assert( false, "stream not created");
		return;
	}

	if( IsPlaying() )
	{
		Asura_Assert( false, "voice already playing");
		return;
	}

	//Create a 'streamer' object
	if( m_pxStreamer )
	{
		Asura_Assert( false, "Preparing a streaming sound which is already streaming" );
		delete m_pxStreamer;
	}

	m_pxStreamer = new Asura_MultiStream_MP3_Stream( xWavHash );

	if( m_pxStreamer )
	{
		if( m_pxStreamer->GetState() == Asura_MultiStream_MP3_Stream::STREAM_STATE_ERROR )
		{
			Asura_Assert( false, "Aborting streaming sound preperation" );
			return;
		}

		if( m_fStartOffset != 0.0f )
		{
			m_pxStreamer->SeekNextRead( m_fStartOffset );
		}
		#if defined ASURA_AUDIO_STORE_FILENAMES
		m_szFilename = m_pxStreamer->GetFilename();
		#endif

		m_bPreparing = true;

		// Do the preparation step, this is threaded out
		g_xStreamPrepQueue.TryToAddToQueue( ThreadFunc_Prepare, this, 0 );
	}
}


void Asura_MultiStream_MP3_Streaming_Voice::FakeDestructor ()
{
	Asura_MultiStream_Voice::FakeDestructor();

	delete m_pxStreamer;
	m_pxStreamer = 0;
}


float Asura_MultiStream_MP3_Streaming_Voice::GetPlayPosition () const
{
	//This is set during Update() to avoid threading issues
	return m_fPlayTime;
}


void Asura_MultiStream_MP3_Streaming_Voice::ThreadFunc_Prepare ( void* pData, u_int uDataSize )
{
	char*	pxBufferData	[2];
	u_int	uBufferSize		[2];
	bool	bBufferDiscon	[2];
	float	fBufferStartTime[2];

	Asura_MultiStream_MP3_Streaming_Voice* pxThis = static_cast<Asura_MultiStream_MP3_Streaming_Voice*>( pData );
	Asura_Assert( pxThis, "Expected a valid data pointer here" );

	//Prime the stream
	pxThis->m_pxStreamer->Prepare();

	// Read a buffer's worth of data
	pxThis->m_pxStreamer->Read( false );
	if( !pxThis->m_pxStreamer->GetBuffer( pxBufferData[0], uBufferSize[0], bBufferDiscon[0], fBufferStartTime[0] ) )
	{
		Asura_Assert( false, "Failed to get buffer" );
		return;
	}

	// Assuming we have not had an error, or completed the stream already, fill another buffer
	if( pxThis->m_pxStreamer->GetState() == Asura_MultiStream_MP3_Stream::STREAM_STATE_IDLE )
	{
		pxThis->m_pxStreamer->Read( false );
		if( !pxThis->m_pxStreamer->GetBuffer( pxBufferData[1], uBufferSize[1], bBufferDiscon[1], fBufferStartTime[1] ) )
		{
			Asura_Assert( false, "Failed to get buffer" );
			return;
		}
	}
	else if( pxThis->m_pxStreamer->GetState() == Asura_MultiStream_MP3_Stream::STREAM_STATE_COMPLETED )
	{
		// The file was so small it fitted into one buffer
		pxBufferData[1]			= 0;
		uBufferSize[1]			= 0;
		bBufferDiscon[1]		= false;
		fBufferStartTime[1]		= 0.0f;
	}


	CellMSInfo xWaveInfo;
	xWaveInfo.SubBusGroup		= CELL_MS_MASTER_BUS;
	xWaveInfo.flags				= CELL_MS_STREAM_NOFLAGS;
	xWaveInfo.initialOffset		= 0;												// Initial delay (in samples) before playback starts
	xWaveInfo.inputType			= pxThis->m_pxStreamer->GetSampleFormat();
	xWaveInfo.Pitch				= pxThis->m_pxStreamer->GetSampleRate();
	xWaveInfo.numChannels		= pxThis->m_pxStreamer->GetNumChannels();

	xWaveInfo.FirstBuffer		= pxBufferData	[0];
	xWaveInfo.FirstBufferSize	= uBufferSize	[0];

	xWaveInfo.SecondBuffer		= pxBufferData	[1];
	xWaveInfo.SecondBufferSize	= uBufferSize	[1];

	// Bind audio information to the current stream
	int iResult = cellMSStreamSetInfo( pxThis->m_iMSStreamHandle, &xWaveInfo );
	if ( iResult != CELL_OK )
	{
		pxThis->SetErrorOnStream();
		Asura_Assertf( false, ( "cellMSStreamSetInfo() error: %d\n", cellMSSystemGetLastError() ) );
		return;
	}

	pxThis->m_iNumChannels = xWaveInfo.numChannels;

	if( bBufferDiscon[0] )
	{
		cellMSStreamSetFirstReadSkip( pxThis->m_iMSStreamHandle, pxBufferData[0], uBufferSize[0], 0, 0 );
	}
	if( bBufferDiscon[1] )
	{
		cellMSStreamSetSecondReadSkip( pxThis->m_iMSStreamHandle, pxBufferData[1], uBufferSize[1], 0, 0 );
	}
	float fBytesPerSecond = static_cast<float>( ( pxThis->m_pxStreamer->GetBitRate() * 1000 ) / 8 );

	pxThis->m_fCurrentBufferFinishTime = fBufferStartTime[1] + ( static_cast<float>( uBufferSize[1] ) / fBytesPerSecond );

	pxThis->m_bPreparing = false;
}

//________________ Asura_MultiStream_Custom_Streaming_Voice ________________________________________________________________
//__________________________________________________________________________________________________________________________


Asura_MultiStream_Custom_Streaming_Voice::Asura_MultiStream_Custom_Streaming_Voice () : Asura_MultiStream_Voice ()
{
	m_iSampleFormat			= 0;
	m_iSampleRate			= 0;
	m_iNumChannels			= 0;
	m_bDestroyPending		= false;
	m_pxStream				= 0;
}

void Asura_MultiStream_Custom_Streaming_Voice::Prepare ( Asura_PS3_Audio_MoreDataFunc pfnMoreDataFunc, const int iSampleFormat, const int iSampleRate, const int iNumChannels, void* pMoreDataCallbackArg )
{
	Asura_Assert( pfnMoreDataFunc, "You must provide a 'more data' callback" );

	m_pxStream = new Asura_MultiStream_Custom_Stream( pfnMoreDataFunc, pMoreDataCallbackArg );
	Asura_Assert( m_pxStream, "Failed to create stream object" )

	m_iSampleFormat			= iSampleFormat;
	m_iSampleRate			= iSampleRate;
	m_iNumChannels			= iNumChannels;
}

void Asura_MultiStream_Custom_Streaming_Voice::Start	()
{
	if ( !IsStreamCreated() )
	{
		Asura_Assert( false, "stream not created");
		return;
	}

	if( IsPlaying() )
	{
		Asura_Assert( false, "voice already playing");
		return;
	}

	if( !m_pxStream )
	{
		Asura_Assert( false, "No stream object");
		return;
	}

	CellMSInfo xWaveInfo;
	xWaveInfo.SubBusGroup		= CELL_MS_MASTER_BUS;
	xWaveInfo.flags				= CELL_MS_STREAM_NOFLAGS;
	xWaveInfo.initialOffset		= 0;												// Initial delay (in samples) before playback starts
	xWaveInfo.inputType			= m_iSampleFormat;
	xWaveInfo.Pitch				= m_iSampleRate;
	xWaveInfo.numChannels		= m_iNumChannels;

	// Fill one buffer
	char*	pcData		= 0;
	u_int	uDataSize	= 0;

	m_pxStream->Read( false );
	m_pxStream->GetBuffer( pcData, uDataSize );

	xWaveInfo.FirstBuffer		= pcData;
	xWaveInfo.FirstBufferSize	= uDataSize;

	if( m_pxStream->GetState() != Asura_MultiStream_Custom_Stream::STREAM_STATE_COMPLETED )
	{
		// Fill another buffer
		m_pxStream->Read( false );
		m_pxStream->GetBuffer( pcData, uDataSize );

		xWaveInfo.SecondBuffer		= pcData;
		xWaveInfo.SecondBufferSize	= uDataSize;
	}
	else
	{
		xWaveInfo.SecondBuffer		= 0;
		xWaveInfo.SecondBufferSize	= 0;
	}


	// Bind audio information to the current stream
	int iResult = cellMSStreamSetInfo( m_iMSStreamHandle, &xWaveInfo );
	if ( iResult != CELL_OK )
	{
		SetErrorOnStream();
		Asura_Assertf( false, ( "cellMSStreamSetInfo() error: %d\n", cellMSSystemGetLastError() ) );
		return;
	}

	m_iNumChannels = xWaveInfo.numChannels;

	cellMSStreamSetCallbackData( m_iMSStreamHandle, this );
	cellMSStreamSetCallbackFunc( m_iMSStreamHandle, &Asura_MultiStream_Voice::StreamCallback );

	CreateBaseDSPEffects ();	//Do this before calling update

	Update( true );

	// Set this stream playing
	if( !m_bPausedByPitch && !m_bPausedExternally )
	{
		cellMSStreamPlay(m_iMSStreamHandle);
	}
	else
	{
		cellMSStreamPlay(m_iMSStreamHandle);
		cellMSCoreSetPause(m_iMSStreamHandle, CELL_MS_PAUSED);
	}

	SetupStreamRouting ();

	SetCanDeleteMe	( false );
	SetPlaying		( true );
}


void Asura_MultiStream_Custom_Streaming_Voice::Update ( const bool bFirstUpdate )
{
	if( CanDeleteMe() )
	{
		//We are just waiting to be deleted
		return;
	}

	if( IsDestroyPending() )
	{
		if( IsPlaying() )
		{
			//Waiting for stream to be 'finished'
			return;
		}
		else
		{
			//Ready to be deleted now
			SetCanDeleteMe();
			return;
		}
	}

	Asura_MultiStream_Voice::Update( bFirstUpdate );

	// Update the pitch
	const float fPitch = GetPitch();

	if( fPitch < fMS_VOICE_MIN_PITCH )
	{
		if( !IsPausedByPitch() )
		{
			SetPausedByPitch();
		}
	}
	else
	{
		if( IsPausedByPitch() )
		{
			SetPausedByPitch( false );
		}
		// Set the pitch
		cellMSStreamSetPitch( m_iMSStreamHandle, static_cast<int>( static_cast<float>( m_iSampleRate ) * fPitch ) );
	}

	Asura_MultiStream_Custom_Stream::StreamState eState = m_pxStream->GetState();

	//Check if our streamer has finished a load
	if(	m_pxStream )
	{
		if( eState == Asura_MultiStream_Custom_Stream::STREAM_STATE_LOADED )
		{
			char*	pxBuffer				= 0;
			u_int	uBufferSize				= 0;
			
			if( m_pxStream->GetBuffer( pxBuffer, uBufferSize ) )
			{
				cellMSStreamSetSecondRead( m_iMSStreamHandle, pxBuffer, uBufferSize );
			}
		}
	}

	if( IsReadyForMoreData() )
	{
		SetReadyForMoreData( false );

		if( eState == Asura_MultiStream_Custom_Stream::STREAM_STATE_COMPLETED )
		{
			cellMSStreamSetSecondRead( m_iMSStreamHandle, 0, 0 );
		}
		else if( m_pxStream )
		{
			m_pxStream->Read ();
		}
	}

	if( ( eState == Asura_MultiStream_Custom_Stream::STREAM_STATE_COMPLETED ) && !IsPlaying() && IsReleased() )
	{
		Destroy();
	}
}

//____________________ Asura_MultiStream_Submix_Voice ______________________________________________________
//__________________________________________________________________________________________________________

Asura_MultiStream_Submix_Voice::Asura_MultiStream_Submix_Voice ( ) : Asura_MultiStream_Voice ()
{

}

void Asura_MultiStream_Submix_Voice::Asura_MultiStream_Submix_Voice::Prepare ( )
{
	// TODO: Set up the routing, Grab a subbus etc...
}

void Asura_MultiStream_Submix_Voice::Update ( const bool bFirstUpdate )
{
	// TODO: Update as normal
}


#endif //ASURA_USE_AUDIO_SYSTEM

//eof
