/*/==============================================================================================
//							FICHIER D'IMPLEMENTATION DU MODULE DE TRAITEMENT DE L'"ADPCM"
//
//		Créer	  :	03 / 11 / 97
//		Fichier	  :	Adpcm.cxx
//		Auteur	  :	Nicolas Meyer
//
//		Descriptif:	Codes sources des fonctions et variables communes à toutes les targets
//					Ce module est exploité par des buffers clients( ainsi il faut tenir compte de la
//					position au sein de ce buffer )
//
//
//==============================================================================================*/

static unsigned long				gulNbVoice;								// Compteur de voie générale occupée
// ==================================== NE PAS TENIR COMPTE DE CETTE VARIABLE
static unsigned long				gulNbTheme;								// Compteur de voie thème occupée
// ==================================== NE PAS TENIR COMPTE DE CETTE VARIABLE
static lpSND_tdstADPCMVoice			gpstVoice[ NB_MAX_ADPCM_VOICE ];		// Buffer de handles de voies
static SND_tdstADPCMVoice			gstVoice[ NB_MAX_ADPCM_VOICE ];			// Buffer des voies
static SND_tdstADPCMVoice			gstTheme[ NB_MAX_ADPCM_THEME ];			// Buffer des thémes(voie future)
static SND_tdstADPCMVoice			gstVoiceFade;							// Voie uniquement utilisée pour les fade

static SndBool g_bStereoReverse=FALSE;	//TRUE when stereo reversed;FALSE when stereo is normal


/*********************************************************************************************************
// DEFINITION DE FONCTIONS

*/
// Fonctions bas niveaux(Privées)
SndBool				fn_bIsVoiceValidADPCM( lpSND_tdstADPCMVoice pstVoice, hSND_tdstADPCMVoice hstVoice );
unsigned long		fn_ulGetVoiceThemeADPCM( lpSND_tdstADPCMVoice* ppstTheme );

unsigned long		fn_ulInitialiseParamsADPCM( lpSND_tdstADPCMVoice pstVoice, hSND_tdstADPCMVoice hstVoice, tdstBlockResourceMem* pstBlock, SND_td_pfn_vSoundCallback pfnCallback, long lCallbackParam );
unsigned long		fn_ulInitialisePtrADPCM( lpSND_tdstADPCMVoice pstVoice, tdstBlockResourceMem* pstBlock, lpSND_tdstWAVEFORMATEX* ppstwfx );
unsigned long		fn_ulSetDynamicFieldsADPCM( lpSND_tdstADPCMVoice pstVoice, SampleParam* pstPosParam );
unsigned long		fn_ulSetDynamicFieldsTransitionsADPCM( lpSND_tdstADPCMVoice pstVoice, lpSND_tdstADPCMVoice pstTheme );
void				fn_vSetDataADPCM( lpSND_tdstADPCMVoice pstVoice );

/*********************************************************************************************************
// FICHIER DE CODE INCLUSION (Attention inclu sans en-tête)

*/
//#include <stdio.h>
#include "StreamApm.cxx"
#include <math.h>

/********************************************************************
	To be called whrn vol/pan change for a voice (update lVol,lPan,lCoeffRight & lCoeffLeft fileds)
*********************************************************************/
void fn_vUpdateVolPan(lpSND_tdstADPCMVoice pstVoice,long volume,long pan)
{
	pstVoice->lPan    = pan;
	pstVoice->lVolume = volume;
	volume           *= pstVoice->ulStaticVolume;
	pstVoice->lCoeffRight = volume * pan >> 7;
	pstVoice->lCoeffLeft  = volume * ( 127 - pan ) >> 7;
}

/*****************************************************
	Return TRUE when stereo has been reversed; FALSE when stereo is normal
*******************************************************/
SndBool fn_bGetReverseStereoSampleADPCM()
{
	return g_bStereoReverse;
}
 
/****************************************************
	Reverse/Unreverse stereo
	Parameter: active:TRUE to reverse stereo; FALSE to set stereo normal
*****************************************************/
void fn_vSetReverseStereoSampleADPCM( SndBool active )
{
	lpSND_tdstADPCMVoice pstVoice;
	int i;

    if( ( active && !g_bStereoReverse ) || ( !active && g_bStereoReverse ) ) {
		/*
		 * Invert or change the stereo
		 */
		for( i=0, pstVoice = gstVoice; i < NB_MAX_ADPCM_VOICE; i++, pstVoice++ )
			fn_vUpdateVolPan(pstVoice,pstVoice->lVolume,C_SOUNDPAN_RIGHT-pstVoice->lPan);
		for( i=0, pstVoice = gstTheme; i < NB_MAX_ADPCM_THEME; i++, pstVoice++ )
			fn_vUpdateVolPan(pstVoice,pstVoice->lVolume,C_SOUNDPAN_RIGHT-pstVoice->lPan);
	}
	g_bStereoReverse = active;
}


/*********************************************************************************************************
void fn_vGetRegressionADPCM( lpSND_tdstADPCMVoice pstVoice, SndReal rDuration )

Paramètres
	Entrée:	- SndReal    rDuration    Duration in second expressed in "SndReal"

	Sortie: - lpSND_tdstADPCMVoice    pstVoice    Address of the voice to modify

Valeur retournée:
			- unsigned long	   Number of sample

Description:
	Function which estimate the duration in sample unit during which the FADE OUT operate
	It also calculate the Fade step

*/
void fn_vGetRegressionADPCM( lpSND_tdstADPCMVoice pstVoice, SndReal rDuration )
{
	double dRegres;

	/*
	 * "SND_ADPCM_BUFFERFREQ" = Number of sample in a second
	 * So we calculate time in sample unit
	 */
	dRegres = M_RealToDoubleSnd( rDuration ) * SND_ADPCM_BUFFERFREQ;
	pstVoice->ulNbSampleFade = (unsigned long)floor( dRegres + 0.5 );
	/*
	 * 4294967296 = 2^32
	 * Calculate 2^32 / Time(sample)
	 */
	dRegres = 4294967296 / dRegres;
	pstVoice->ulFadeStep     = (unsigned long)floor( dRegres );
}

/*********************************************************************************************************
unsigned long fn_ulInitModuleADPCM( )

Paramètres
	Entrée:	- SND_td_pfn_vADPCMCallback pfnvFunc	Pointeur sur une fonction de désallocation

	Sortie: - NEANT

Valeur retournée:
			- unsigned long					Code d'erreur

Description:
	Fonction d'initialisation du module ADPCM:
		- Génération de la table de calcul en compression 2
		- Mise à zéro des voies

*/
unsigned long fn_ulInitModuleADPCM( )
{
	memset( &gstVoice[0], 0, NB_MAX_ADPCM_VOICE * sizeof(SND_tdstADPCMVoice) );
	memset( &gstTheme[0], 0, NB_MAX_ADPCM_THEME * sizeof(SND_tdstADPCMVoice) );
	memset( &gpstVoice[0], 0, NB_MAX_ADPCM_VOICE * sizeof(lpSND_tdstADPCMVoice) );
	gulNbVoice	=	0;
	gulNbTheme	=	0;
	return ADPCM_NOERROR;
}

/*********************************************************************************************************
unsigned long fn_ulAddVoiceADPCM( tdstBlockResourceMem* pstBlockIn, SampleParam* pstPosParam, SND_td_pfn_vSoundCallback pfnCallback, long lCallbackParam, tdstBlockResourceMem* pstBlockTheme, long* plVoiceId, unsigned long ulTimeStart )

Paramètres
	Entrée:	- tdstBlockResourceMem*	pstBlockIn		ptr onto 1° resource for Transition(THEME) or singe resource
			- SampleParam*			pstPosParam		Pointeur sur un descripteur de la position physique
			- SND_td_pfn_vSoundCallback pfnCallback	Pointeur de "Callback"
			- long					lCallbackParam	Paramètre à fournir à la "Callback"
			- tdstBlockResourceMem*	pstBlockTheme	ptr onto 2° resource for TransitionVoice(Theme); NULL for single resource
			- unsigned long			ulTimeStart		Position du sample audible depuis la création du buffer client

	Sortie: - long*					plVoiceId		Pointeur sur un identifiant de ligne(a caster en SND_tdstADPCMVoice)

Valeur retournée:
			- unsigned long							Code d'erreur

Description:
	Fonction qui stocke les caractèristique d'une nouvelle voie, ou theme. Ces informations sont nécessaire pour
	le mixage. Un code d'erreur est retourné en fonction

*/
unsigned long fn_ulAddVoiceADPCM( tdstBlockResourceMem* pstBlockIn, SampleParam* pstPosParam, SND_td_pfn_vSoundCallback pfnCallback, long lCallbackParam, tdstBlockResourceMem* pstBlockTheme, long* plVoiceId, unsigned long ulTimeStart )
{
	unsigned long			ulOneTime;
	unsigned long			ulErr;
	lpSND_tdstWAVEFORMATEX	pstwfx;
	lpSND_tdstADPCMVoice*	ppstVoice;
	lpSND_tdstADPCMVoice	pstVoice;
	tdstBlockResourceMem*	pstBlock;
	SndBool					bDestroy;
//	SndBool               bTheme;

	if( gulNbVoice == NB_MAX_ADPCM_VOICE )
		return ADPCM_NOVOICELEFT;
	if( ! pstBlockIn )
		return ADPCM_INVALIDPARAM;
// Gestion des thèmes
	if( pstBlockTheme ) {
		if( gulNbTheme == NB_MAX_ADPCM_THEME )
			return ADPCM_NOTHEMELEFT;
//		else
//			gulNbTheme++;
	}
	if( ( pstBlockIn->eType							!=	TYPE_SAMPLE )	||
		( pstBlockIn->uRes.stSample.uwResolution	!= 16 )				||
		( ( pstBlockIn->uRes.stSample.eZip			!= SAMPLE_ADPCM ) &&
		  ( pstBlockIn->uRes.stSample.eZip			!= SAMPLE_ADPCM_TO_CONVERT ) ) )
		return ADPCM_NOTADPCMDATAS;
	if( ! pstBlockIn->uRes.stSample.bStream ) {
		pstwfx							=	(lpSND_tdstWAVEFORMATEX)( pstBlockIn->uRes.stSample.uData.stMem.pvPtrFirst );
		if( ( ulErr = fn_ulResolveVersion( pstwfx->ulCompressId ) ) != ADPCM_NOERROR )
			return ulErr;
	}

/*
 * Cherche un handle d'une voie active ou qui peut être détruite
 * Un theme ne peut prendre la place que d'une voie theme
 */
	pstVoice	=	&gstVoice[0];
	bDestroy	=	FALSE;
	while( ( ! bDestroy ) && ( pstVoice < &gstVoice[gulNbVoice] ) ) {
		if( !pstVoice->bIsPlaying ) {
			// Cas d'une voie theme
			if( pstBlockTheme && pstVoice->bTheme )
				bDestroy	=	TRUE;
			// Cas d'une voie one shot
			if( ( !pstBlockTheme ) && ( !pstVoice->bTheme ) )
				bDestroy	=	TRUE;
		}
		if( !bDestroy )
			pstVoice++;
	}
	/*
	 * Gestion d'un handle et d'une voie
	 */
	if( !bDestroy ) {
		ppstVoice	=	gpstVoice;
		while( *ppstVoice )
			ppstVoice++;
		pstVoice	=	&gstVoice[gulNbVoice];
	}
	else {
		ppstVoice	=	pstVoice->hstVoice;
		snd_assert( pstVoice->hstVoice );
	}
	*ppstVoice	=	pstVoice;
	pstBlock	=	pstBlockIn;

//Cherche une voie libre
	ulOneTime	=	0;
	do {
		memset( pstVoice, 0, sizeof(SND_tdstADPCMVoice) );
		if( pstBlockTheme )
			//it's nit a single resource (PlaySnd) but a THEME (PlayTransitionSnd)
			pstVoice->bTheme = TRUE;
		/*
		 * Store a reference of the voice
		 */
		ulErr							=	fn_ulInitialiseParamsADPCM( pstVoice, ppstVoice, pstBlock, pfnCallback, lCallbackParam );
//		memcpy( &pstVoice->pstPar, pstPosParam, sizeof(SampleParam) );
		if( ulErr != ADPCM_NOERROR ) {
			*ppstVoice		=	NULL;	// Désalloue la voie
			return ulErr;
		}
		if( !ulOneTime ) {
			/*
			 * That part open stream files
			 */
			ulErr = fn_ulInitialisePtrADPCM( pstVoice, pstBlock, & pstwfx );
			if( ulErr != ADPCM_NOERROR ) {
				*ppstVoice		=	NULL;	// Désalloue la voie
				return ulErr;
			}
			// Date de création en nombre de sample de buffer client
			pstVoice->ulBufferStart			=	ulTimeStart;
			// ATTENTION à ce niveau il ne faut surtout pas remplir le champs de thème
			fn_ulSetDynamicFieldsADPCM( pstVoice, pstPosParam );
		}
		/*
		 * Time expressed in sample played since the begining of the client buffer
		 */
		pstVoice->ulBufferStart = ulTimeStart;
		if( pstBlockTheme ) {
			/*
			 * allow to do 2 loop for allocating 2 voices (one for each part)
			 */
			ulOneTime++;
			if( ulOneTime < 2 ) {
				lpSND_tdstADPCMVoice	pstTheme;
				ulErr = fn_ulGetVoiceThemeADPCM( & pstTheme );
				if( ulErr != ADPCM_NOERROR ) {
					*ppstVoice = NULL; /* Desallocate voice */
					return ulErr;
				}
				pstVoice->pstTheme		=	pstTheme;
				pstBlock				=	pstBlockTheme;
				pstVoice				=	pstTheme;
				pstwfx					=	(lpSND_tdstWAVEFORMATEX)( pstBlockTheme->uRes.stSample.uData.stMem.pvPtrFirst );
			}
		}
	} while( pstBlockTheme && ( ulOneTime < 2 ) );
	/*
	 * Incrémente le nombre de voie
	 */
	*plVoiceId = (long)( ppstVoice );
	if( ! bDestroy )
		gulNbVoice++;
// Ne dois jamais être nul
	snd_assert( gulNbVoice );

	return ADPCM_NOERROR;
}

/*********************************************************************************************************
unsigned long fn_ulRemoveVoiceADPCM( long lVoiceId )

Paramètres
	Entrée:	- long						lVoiceId	Pointeur sur la voie à détruire

	Sortie: - NEANT

Valeur retournée:
			- unsigned long						Code d'erreur

Description:
	Stop a ADPCM voice; if this voice is a Part(THEME), stop the "pstTheme" voice too

*/
unsigned long fn_ulRemoveVoiceADPCM( long lVoiceId )
{
	lpSND_tdstADPCMVoice	pstVoice;
	static unsigned long	ulOffsetMax	=	NB_MAX_ADPCM_VOICE * sizeof(SND_tdstADPCMVoice);
	unsigned long			ulHandle;

	if( gulNbVoice == 0 )
		return ADPCM_INVALIDPARAM;
	pstVoice	=	*(lpSND_tdstADPCMVoice*)lVoiceId;
	if( ! fn_bIsVoiceValidADPCM( pstVoice, (hSND_tdstADPCMVoice)lVoiceId ) )
		return ADPCM_INVALIDVOICE;
	if( ( (unsigned long)pstVoice > (unsigned long)gstVoice + ulOffsetMax ) &&
		( (unsigned long)pstVoice < (unsigned long)gstVoice ) )
		return ADPCM_INVALIDPARAM;

	if( pstVoice->bStream )	{
		fn_ulCloseStreamADPCM( pstVoice );
		if( pstVoice->pstTheme )
			fn_ulCloseStreamADPCM( pstVoice->pstTheme );
	}
	/*
	 * Launch the callback. This is in case of a simple stop call.
	 * if the callback hasn't been done
	 */
	if( pstVoice->stCall.pfnCallBack && ( !pstVoice->bCallbackDone ) ) {
		( pstVoice->stCall.pfnCallBack )( pstVoice->stCall.lCallbackParam );
	}
	// Si la voie à supprimer n'est pas un theme
	// On libère le handle et affecte les autres handle
	*(lpSND_tdstADPCMVoice*)lVoiceId	=	NULL;
	for( ulHandle = 0; ulHandle < NB_MAX_ADPCM_VOICE; ulHandle++ ) {
		if( gpstVoice[ ulHandle ] >= pstVoice ) {
			gpstVoice[ ulHandle ]--;	// Descend d'un cran
		}
	}
	if( pstVoice == & gstVoice[ gulNbVoice - 1 ] )
		memset( pstVoice, 0, sizeof(SND_tdstADPCMVoice) );
	else {
		memmove( pstVoice, pstVoice + 1, (unsigned long)( & gstVoice[gulNbVoice - 1] ) - (unsigned long)pstVoice );
		memset( & gstVoice[gulNbVoice - 1], 0, sizeof(SND_tdstADPCMVoice) );
	}
// Ce cas ne doit jamais se produire
	snd_assert( ! ( gulNbVoice == 0 ) );
	gulNbVoice--;
//	_RPT1( _CRT_WARN, "Nb voice %d\n", gulNbVoice );
	return ADPCM_NOERROR;
}

/*********************************************************************************************************
void fn_vStopThemeADPCM( long lVoiceId )

Paramètres
	Entrée:	- long					lVoiceId		Identifiant de la voie

	Sortie: - NEANT

Valeur retournée:
			- NEANT

Description:
	Procédure qui stop un thème

*/
void fn_vStopThemeADPCM( long lVoiceId )
{
	lpSND_tdstADPCMVoice	pstVoice;

	if(lVoiceId) {
		pstVoice	=	*(lpSND_tdstADPCMVoice*)lVoiceId;
		if( ! fn_bIsVoiceValidADPCM( pstVoice, (hSND_tdstADPCMVoice)lVoiceId ) ) {
			return;
		}
		if( pstVoice->pstTheme ) {
// Libère la voie thème
			pstVoice->pstTheme->pchBeginDatas	=	NULL;
		}
		fn_ulRemoveVoiceADPCM( lVoiceId );
	}
}

/*********************************************************************************************************
SndBool fn_bEndReachADPCM( lpSND_tdstADPCMVoice pstVoice )

Paramètres
	Entrée:	- lpSND_tdstADPCMVoice	pstVoice	Voie à désallouer
			- 
	Sortie: - NEANT

Valeur retournée:
			- SndBool		Booléen

Description:
	Fonction qui gère les voies en cas de désallocation

*/
SndBool fn_bEndReachADPCM( lpSND_tdstADPCMVoice pstVoice, unsigned long ulTime )
{
	lpSND_tdstWAVEFORMATEX pstwfx;
	unsigned long ulErr;
	SndBool	      bEndReach;

	bEndReach	=	TRUE;
	if( pstVoice->bLoop && ( !pstVoice->bTheme) ) {
		/*
		 * Case of looping sample
		 * We return to the sample position
		 */
		if( pstVoice->unChannels == 1 )
			pstVoice->pchCurrentDatas = pstVoice->pchBeginDatas + (unsigned long)( pstVoice->ulSampleStart >> 1 );
		else
			pstVoice->pchCurrentDatas = pstVoice->pchBeginDatas + pstVoice->ulSampleStart;
		pstVoice->ulSamplePos = pstVoice->ulSampleStart;
		memcpy( & pstVoice->uEnv.stStateStereo, & pstVoice->uLoop.stStateStereo, sizeof(tduState) );
		bEndReach = FALSE;
	}
	else {
		if( !pstVoice->bTheme ) {
			/*
			 * Case of ONE SHOT sample
			 * It switch in finishing state. Data filled inside client buffer are
			 * Stereo 16 bits. In that case one frame is 4 byte long
			 */
			pstVoice->bIsFinishing = TRUE;
			pstVoice->ulBufferEnd  = ulTime;
			pstVoice->ulBufferEnd  = pstVoice->ulBufferStart + ( pstVoice->ulNbSample >> 1 );
			/*
			 * In case we play a non streaming resource there
			 * are no delay between the effective audible end
			 * and the end load reach
			 */
			if( !pstVoice->bStream )
				pstVoice->bIsPlaying   = FALSE;
			snd_assert( pstVoice->pchEndDatas + 1 >= pstVoice->pchCurrentDatas );
		}
		else {
			/*
			 * Case of THEMEs
			 * We chain up parts
			 */
			if( pstVoice->pstTheme ) {
				lpSND_tdstADPCMVoice	pstTheme;

				pstTheme	=	pstVoice->pstTheme;
				if( pstVoice->bFade ) {
					/*
					 * Parameters should be copied in case "pstTheme != &gstVoiceFade"
					 */
#if 0
					if( pstTheme != &gstVoiceFade ) {
						/*
						 * If we reached the FADE IN part, we must
						 * force the next Theme voice with "gstVoiceFade"
						 */
						if( pstVoice->bFadeIn ) {
							pstTheme->pchBeginDatas	=	NULL;
							snd_assert( pstTheme->pstTheme == &gstVoiceFade );
						}
						pstTheme->bFade          = pstVoice->bFade;
						pstTheme->bFadeIn        = pstVoice->bFadeIn;
						pstTheme->ulNbSampleFade = pstVoice->ulNbSampleFade;
						pstTheme->ulFadeStep     = pstVoice->ulFadeStep;
					}
#else
					if( pstVoice->bFadeIn ) {
						/*
						 * In case we begin FADE IN we use the special voice
						 * dedicated to end of FADE
						 */
						if( pstTheme != &gstVoiceFade ) {
							if( pstTheme->bStream )
								fn_ulCloseStreamADPCM( pstTheme );
							pstTheme->pchBeginDatas	=	NULL;
							snd_assert( pstTheme->pstTheme == &gstVoiceFade );
							pstTheme  = &gstVoiceFade;
						}
						bEndReach = TRUE;
					}
					pstTheme->bFade          = pstVoice->bFade;
					pstTheme->bFadeIn        = pstVoice->bFadeIn;
					pstTheme->ulNbSampleFade = pstVoice->ulNbSampleFade;
					pstTheme->ulFadeStep     = pstVoice->ulFadeStep;
#endif
				}
				if( pstVoice->bStream )
					fn_ulCloseStreamADPCM( pstVoice );
				/*
				 * Open the stream in that case only
				 */
				pstwfx = (lpSND_tdstWAVEFORMATEX)( pstTheme->uRefRes.pstPtr->uRes.stSample.uData.stMem.pvPtrFirst );
				ulErr  = fn_ulInitialisePtrADPCM( pstTheme, pstTheme->uRefRes.pstPtr, & pstwfx );
				fn_ulSetDynamicFieldsTransitionsADPCM( pstVoice, pstTheme );
				if( ulErr == ADPCM_NOERROR ) {
					bEndReach = FALSE;
					fn_vSetDataADPCM( pstTheme ); // Initialise les bons paramètres
					memcpy( pstVoice, pstTheme, sizeof(SND_tdstADPCMVoice) );
					if( pstTheme )
						pstTheme->pchBeginDatas	=	NULL;
				}
			}
		}
/*
 * Appel de la callback
 * Gestion particulière pour les themes, en cas de FADE
 */
		if( pstVoice->stCall.pfnCallBack ) {
			/*
			 * The callback call must be done only if there are no FADE, or if we
			 * end the FADE OUT
			 */
			if( ( !pstVoice->bFade ) || ( pstVoice->bFade && pstVoice->bFadeIn ) ) {
				pstVoice->bCallbackDone	=	TRUE;
				( pstVoice->stCall.pfnCallBack )( pstVoice->stCall.lCallbackParam );
			}
		}
	}
	return bEndReach;
}

#if 0
void fn_vRoutineMix( long* plMixBegin, long* plMixEnd, long* plDataBegin, lpSND_tdstADPCMVoice pstVoice )
{
	long* plMixCurrent;

	if( pstVoice->unChannels == 2 ) {
		unsigned long	ulOffset;
		for( plMixCurrent = plMixBegin, ulOffset = 0; plMixCurrent < plMixEnd; plMixCurrent++, ulOffset += pstVoice->ulPitch ) {
			*plMixCurrent++ += (long)( *( plDataBegin + (unsigned long)( ulOffset >> 9 ) * 2 ) );
			*plMixCurrent   += (long)( *( plDataBegin + (unsigned long)( ulOffset >> 9 ) * 2 + 1 ) );
		}
	}
	else {

		unsigned long	ulOffset;
		for( plMixCurrent = plMixBegin, ulOffset = 0; plMixCurrent < plMixEnd; plMixCurrent++, ulOffset += pstVoice->ulPitch ) {
			*plMixCurrent++ += (long)( *( plDataBegin + (unsigned long)( ulOffset >> 9 ) ) );
			*plMixCurrent   += (long)( *( plDataBegin + (unsigned long)( ulOffset >> 9 ) ) );
		}
	}
}
#endif

/*********************************************************************************************************
unsigned long fn_ulMixerStereoADPCM( ApmBool bStream, unsigned long ulSizeByte, short* pnDataOut, unsigned long ulTime )

Paramètres
	Entrée:	- ApmBool          bStream       Indicates the type of voice to mix
	        - unsigned long    ulSizeByte    Size in byte to refresh
			- short*           pnDataOut     Pointeur sur le buffer à remplir
			- unsigned long    ulTime        Number of byte filled in the client buffer since begining

	Sortie: - NEANT

Valeur retournée:
			- unsigned long						Code d'erreur

Description:
	Procédure qui génère le mixage de toutes les voies ADPCM
	Mixage sur des samples PCM aprés décompression
	Les données calculées sont des samples 16 bits stéréo, de ce fait "ulNbBytes" ce doit d'être
	un multiple de 4. Les paramètres pris en compte sont le volume, panoramique, et pitch.

*/
unsigned long fn_ulMixerStereoADPCM( ApmBool bStream, unsigned long ulSizeByte, short* pnDataOut, unsigned long ulTime )
{
	lpSND_tdstADPCMVoice	pstVoice;
	unsigned long			ulNbGenere;
	unsigned long			ulIndiceVoice;
	unsigned long			ulEstimate;
	unsigned long			ulEstimateTemp;
	unsigned long			ulEstimatePitch;
	unsigned long			ulEstimateBoard;
	unsigned long			ulEstimateRest;
	unsigned long			ulNbVoiceActive;
	unsigned long			ulTemp;

	static long				plMixBegin[ UNDER_NB_BUFFER << 1 ];	// Données 32 bits (4 octets) en stéréo
	long*					plMixEnd;
	long*					plMixCurrent;

	static long				plDataBegin[ UNDER_NB_BUFFER_PITCH << 1 ];
	long*					plDataCurrent;
	long*					plDataEnd	=	plDataBegin + (unsigned long)( UNDER_NB_BUFFER << 1 );
	SndBool					bEndReach;
	short*					pshDatas;


	if( ! gulNbVoice )
		return ADPCM_NOVOICELEFT;
	ulNbVoiceActive		=	0;
	ulNbGenere			=	ulSizeByte;
	pshDatas			=	pnDataOut;
	/*
	 * Boucle principale de calcul des échantillons
	 */
	while( ulNbGenere > 0 ) {
		/*
		 * Estimate the size of the needed buffer
		 */
		if( ulNbGenere < UNDER_NB_BUFFER )
			ulEstimate	=	ulNbGenere;
		else
			ulEstimate		=	UNDER_NB_BUFFER;

		memset( plMixBegin, 0, ulEstimate << 3 );
		plMixEnd	=	plMixBegin + (unsigned long)( ulEstimate << 1 );
		/*
		 * Get the first voice, and compute remaining data
		 */
		pstVoice    = &gstVoice[0];
		ulNbGenere -= ulEstimate;
		for( ulIndiceVoice = 0; ulIndiceVoice < gulNbVoice; ulIndiceVoice++ ) {
			if( ( ! pstVoice->bPause ) && !pstVoice->bIsFinishing/*pstVoice->bIsPlaying*/ && ( pstVoice->bStream == bStream ) ) {
				ulNbVoiceActive++;
				/*
				 * 'ulEstimate = Number of samples to generate
				 */
				ulEstimateTemp = ulEstimate;
				plDataCurrent  = plDataBegin;
				ulTemp         = ulEstimateTemp * pstVoice->ulPitch;
				if( ulTemp & 0x01ff )
					ulEstimatePitch	=	1 + ( ulTemp >> 9 );
				else
					ulEstimatePitch	=	ulTemp >> 9;
				do {
					/*
					 * Case we are at the end of the sample
					 */
					if( pstVoice->ulSamplePos + ulEstimatePitch > pstVoice->ulNbSample ) {
						ulEstimateBoard	=	pstVoice->ulNbSample - pstVoice->ulSamplePos;
						bEndReach		=	TRUE;
					}
					else {
						ulEstimateBoard	=	ulEstimatePitch;
						bEndReach		=	FALSE;
					}
					/*
					 * THEME and FADEs manage
					 */
					if( pstVoice->bFade ) {
						unsigned long	ulSample;

						if( pstVoice->bFadeIn )
							pstVoice->bFade	=	FALSE;
						else {
							ulSample	=	( ulEstimateBoard << 9 ) / pstVoice->ulPitch;
							if( pstVoice->ulNbSampleFade > ulSample )
								pstVoice->ulNbSampleFade	-=	ulSample;
							else {
								pstVoice->ulNbSampleFade	=	0;
								pstVoice->bFadeIn			=	TRUE;
								/*
								 * We detect apparently a end part
								 */
								bEndReach					=	TRUE;
							}
						}
					}
					/*
					 * STREAMING manage
					 */
					if( pstVoice->bStream )
						fn_ulLoadSampleADPCM( ulEstimateBoard, pstVoice );
					pstVoice->ulSamplePos	+=	ulEstimateBoard;
					if( ( pstVoice->ulSamplePos > pstVoice->ulNbSample ) && ( pstVoice->bLoop ) )
						pstVoice->ulSamplePos	-=	pstVoice->ulNbSample;
					/*
					 * Decompression routine call
					 */
					if( pstVoice->unChannels == 1 )
						AdpcmDecoderMono( ulEstimateBoard, & pstVoice->pchCurrentDatas, & plDataCurrent, & pstVoice->uEnv.stStateMono );
					else
						AdpcmDecoderStereo( ulEstimateBoard, & pstVoice->pchCurrentDatas, & plDataCurrent, & pstVoice->uEnv.stStateStereo );
					/*
					 * Retrieve exactly the transition which should be done
					 */
					if( bEndReach )
						bEndReach = fn_bEndReachADPCM( pstVoice, ulTime );
					ulEstimatePitch	-=	ulEstimateBoard;
				}
				while( ( ulEstimatePitch > 0 ) && !pstVoice->bIsFinishing/*pstVoice->bIsPlaying*/ && ( ! bEndReach ) );
				ulEstimateRest	=	( ulEstimatePitch << 9 ) / pstVoice->ulPitch;
				plMixCurrent	=	plMixBegin + (unsigned long)( ( ulEstimateTemp - ulEstimateRest ) << 1 );
				if( plMixCurrent > plMixEnd )
					plMixCurrent	=	plMixEnd;
#if 1
				AdpcmMix_( plMixBegin, plMixCurrent, plDataBegin, pstVoice );
#else
				fn_vRoutineMix( plMixBegin, plMixCurrent, plDataBegin, pstVoice );
#endif
			}
			pstVoice++;
		}
		if( ulNbVoiceActive == 0 )
			return ADPCM_NOVOICEACTIVE;
		AdpcmStoreClampDatas( plMixBegin, plMixEnd, & pshDatas );
	}
	return ADPCM_NOERROR;
}

/*********************************************************************************************************
unsigned long fn_ulResolveVersion( unsigned long ulVersionCode )

Paramètres
	Entrée:	- unsigned long		ulVersionCode	Code de la version

	Sortie: - NEANT

Valeur retournée:
			- unsigned long						Code d'erreur

Description:
	Fonction qui détermine si la version courante du fichier peut-être décompressée. retourne 0, code erreur
	sinon
*/
unsigned long fn_ulResolveVersion( unsigned long ulVersionCode )
{
	char	chVersion[ 4 ];

	GETCOMPRESSID( ulVersionCode, chVersion );

// Recalle tout en minuscule
	if( ( chVersion[ 0 ] >= 'A' ) && ( chVersion[ 0 ] <= 'Z' ) )
		chVersion[ 0 ]	+=	'a' - 'A';
	if( ( chVersion[ 1 ] >= 'A' ) && ( chVersion[ 1 ] <= 'Z' ) )
		chVersion[ 1 ]	+=	'a' - 'A';

	if( ( chVersion[ 0 ] == 'v' ) && ( chVersion[ 1 ] == 's' ) ) {
			
		if( ( ulVersionCode	= SETCOMPRESSID( chVersion[ 0 ], chVersion[ 1 ], chVersion[ 2 ], chVersion[ 3 ] ) ) ==
			ADPCM_CODAGE_VER )
			return ADPCM_NOERROR;
		else
			return ( ulVersionCode << 16 ) | ADPCM_BADFILE;
	}
	else
		return SND_BAD_VERSION | ADPCM_BADFILE;
}

/*********************************************************************************************************
unsigned long fn_ulSetParams( long lVoiceId, SampleParam* pstPosParam )

Paramètres
	Entrée:	- long				lVoiceId		Identifiant de la voie
			- SampleParam*		stParam			Structure contenant les paramètres à modifier

	Sortie: - NEANT

Valeur retournée:
			- unsigned long						Code d'erreur

Description:
	Fonction qui modifie le pitch d'une voie.

*/
unsigned long fn_ulSetParams( long lVoiceId, SampleParam* pstPosParam )
{
	lpSND_tdstADPCMVoice	pstVoice;
	unsigned long			ulOneTime;

	if( lVoiceId ) {
		pstVoice					=	*(lpSND_tdstADPCMVoice*)lVoiceId;
		if( ! fn_bIsVoiceValidADPCM( pstVoice, (hSND_tdstADPCMVoice)lVoiceId ) )
			return ADPCM_INVALIDVOICE;
		if( pstVoice->bIsPlaying ) {
			ulOneTime					=	0;
			do {
				fn_ulSetDynamicFieldsADPCM( pstVoice, pstPosParam );
				ulOneTime++;
				if( pstVoice->pstTheme ) {
					if( ulOneTime < 2 )
						pstVoice		=	pstVoice->pstTheme;
				}
			} while( pstVoice && ( ulOneTime < 2 ) );
			return ADPCM_NOERROR;
		}
		fn_ulRemoveVoiceADPCM( lVoiceId );
		return ADPCM_VOICEDELETE;
	}
	return ADPCM_NONALLOCATEVOICE;
}

/*********************************************************************************************************
unsigned long fn_ulInitialisePtrADPCM( lpSND_tdstADPCMVoice pstVoice, tdstBlockResourceMem* pstBlock, lpSND_tdstWAVEFORMATEX* ppstwfx )

Paramètres
	Entrée:	- lpSND_tdstADPCMVoice		pstVoice		Pointeur sur la voie active
			- tdstBlockResourceMem*		pstBlock		Pointeur sur un bloc ressource mémoire
			- lpSND_tdstWAVEFORMATEX*	ppstwfx			Pointeur de pointeur sur la structure d'en-tête d'un fichier "APM"

	Sortie: - lpSND_tdstWAVEFORMATEX*	ppstwfx			Pointeur de pointeur sur la structure d'en-tête d'un fichier "APM"

Valeur retournée:
			- unsigned long						Code d'erreur

Description:
	Fonction qui initialise des  paramètres liés aux datas

*/
unsigned long fn_ulInitialisePtrADPCM( lpSND_tdstADPCMVoice pstVoice, tdstBlockResourceMem* pstBlock, lpSND_tdstWAVEFORMATEX* ppstwfx )
{
	lpSND_tdstWAVEFORMATEX	pstwfx;
	unsigned long			ulNbSample;

// Une ressource doit absolument avoir été associée à la voie 
	snd_assert(pstVoice->uRefRes.pstPtr);

// Pointeur sur les datas
	if( ! pstVoice->bStream ) {
		pstwfx							=	*ppstwfx;
		pstVoice->pchBeginDatas			=	(char*)( pstwfx + 1 );
		pstVoice->pchCurrentDatas		=	(char*)( pstwfx + 1 );
	}
	else {
		unsigned long ulCode;

		ulCode = fn_ulOpenStreamADPCM( pstBlock->uRes.stSample.uData.stStream.szFileName, pstVoice, ppstwfx );
		pstwfx							=	*ppstwfx;
		// Ouverture du fichier en streaming
		if( ulCode != ADPCM_NOERROR )
			return ulCode;
	}

	if( pstBlock->uRes.stSample.uwNbChannels != pstwfx->unChannels ) {
		snd_assert( 0 );
		return ADPCM_CONFLICT;
	}
	pstVoice->unChannels			=	pstwfx->unChannels;
	ulNbSample						=	pstwfx->ulNbSample;
	if( pstwfx->ulSampleLoop != SND_ADPCM_NOLOOP ) {
		pstVoice->bLoop				=	TRUE;
		ulNbSample					=	pstwfx->ulSampleLoop + pstwfx->ulLengthLoop;
	}
	else
		pstVoice->bLoop				=	FALSE;
	pstVoice->ulNbSample			=	ulNbSample;
	pstVoice->unSamplesPerSec		=	(unsigned short)( pstwfx->ulSamplesPerSec );

	memcpy( & pstVoice->uLoop.stStateStereo, & pstwfx->uLoop.stStateStereo, sizeof(tduState) );
	if( pstVoice->unChannels == 1 ) 
	{
		pstVoice->pchEndDatas			=	pstVoice->pchBeginDatas + (unsigned long)( ulNbSample >> 1 );
		// Ce sont des tailles à convertir en sample
		if( pstVoice->bLoop )
			pstVoice->ulSampleStart		=	pstwfx->ulSampleLoop;
		else
			pstVoice->ulSampleStart		=	0;
		pstVoice->ulSampleLength		=	pstwfx->ulLengthLoop;
		memcpy( & pstVoice->uInitEnv.stStateMono, & pstwfx->uInitial.stStateMono, sizeof(tduState) );

		pstVoice->uEnv.stStateMono.lIndex	=	pstwfx->uInitial.stStateMono.lIndex;
	}
	else 
	{
		pstVoice->pchEndDatas				=	pstVoice->pchBeginDatas + (unsigned long)( ulNbSample );
		// Ce sont des tailles à convertir en sample
		pstVoice->ulSampleStart				=	pstwfx->ulSampleLoop;
		pstVoice->ulSampleLength			=	pstwfx->ulLengthLoop;
		memcpy( & pstVoice->uInitEnv.stStateStereo, & pstwfx->uInitial.stStateStereo, sizeof(tduState) );
	}

	fn_vSetDataADPCM( pstVoice );// Initialise les paramètres de décompression ADPCM
	*ppstwfx	=	pstwfx;
	return ADPCM_NOERROR;
}

/*********************************************************************************************************
unsigned long fn_ulSetDynamicFieldsADPCM( lpSND_tdstADPCMVoice pstVoice, SampleParam* pstPosParam )

Paramètres
	Entrée:	- lpSND_tdstADPCMVoice	pstVoice		Pointeur sur la voie active
			- SampleParam*			stParam			Structure contenant les paramètres à modifier

	Sortie: - NEANT

Valeur retournée:
			- unsigned long						Code d'erreur

Description:
	Fonction qui remplie les champs correspondant aux volumes, panoramique et pitch d'une voie

*/
unsigned long fn_ulSetDynamicFieldsADPCM( lpSND_tdstADPCMVoice pstVoice, SampleParam* pstPosParam )
{
	long  lPan;
	float fBufferCoef;

	if( !pstVoice->bStream || ( pstVoice->bStream && ( pstVoice->phFile != OPENFILESND_FAILED ) ) )
		snd_assert( pstVoice->unSamplesPerSec );
	if( pstVoice->unSamplesPerSec != SND_ADPCM_BUFFERFREQ ) {
		fBufferCoef		=	(float)pstVoice->unSamplesPerSec / (float)SND_ADPCM_BUFFERFREQ;
		// Dans le cas d'un coéfficient nul
		if( !fBufferCoef )
			fBufferCoef		=	1;
	}
	else
		fBufferCoef		=	1;

	if( pstVoice->bPitchable )
		pstVoice->ulPitch	=	(unsigned long)( fBufferCoef * (unsigned long)SET16_16TO23_9( pstPosParam->Freq ) );
	else
		pstVoice->ulPitch	=	(unsigned long)( fBufferCoef * SND_PITCH_DEFAULT_9 );
	/*
	 * Clamp the pitch value
	 */
	if( pstVoice->ulPitch > SND_PITCH_MAX_9 )
		pstVoice->ulPitch	=	SND_PITCH_MAX_9;
	else 
	{
		if( pstVoice->ulPitch < SND_PITCH_MIN_9 )
			pstVoice->ulPitch	=	SND_PITCH_MIN_9;
	}
	if( pstVoice->unChannels == 2 ) {
		/*
		 * In case of stereo panoramic has no meaning
		 */
		pstVoice->bPanable		=	FALSE;
		if( pstVoice->ulPitch > SND_PITCH_MEDIUM_9 ) {
			pstVoice->ulPitch	=	SND_PITCH_MEDIUM_9;
		}
	}

	if( pstVoice->bVolable )
		pstVoice->lVolume	=	(long)pstPosParam->ucVol;
	else
		pstVoice->lVolume	=	127;
	/*
	 * Affect and calculate results
	 */
	if( pstVoice->bPanable )
		lPan = (long)pstPosParam->ucPan;
	else
		lPan = C_SOUNDPAN_MEDIAN;
	if( g_bStereoReverse && ( pstVoice->unChannels == 1 ) )
		lPan = C_SOUNDPAN_RIGHT - lPan;
	fn_vUpdateVolPan( pstVoice, pstVoice->lVolume, lPan );

	return ADPCM_NOERROR;
}

/*********************************************************************************************************
unsigned long fn_ulPauseVoiceADPCM( long lVoiceId )

Paramètres
	Entrée:	- long				lVoiceId	Identifiant de la voie

	Sortie: - NEANT

Valeur retournée:
			- unsigned long					Code d'erreur

Description:
	Fonction qui met une voie ADPCM en pause

*/
unsigned long fn_ulPauseVoiceADPCM( long lVoiceId )
{
	lpSND_tdstADPCMVoice	pstVoice;

	if( lVoiceId ) {
		pstVoice			=	*(lpSND_tdstADPCMVoice*)lVoiceId;
		if( ! fn_bIsVoiceValidADPCM( pstVoice, (hSND_tdstADPCMVoice)lVoiceId ) )
			return ADPCM_INVALIDVOICE;
//		snd_assert( !pstVoice->bPause );
		pstVoice->bPause	=	TRUE;
		return ADPCM_NOERROR;
	}
	return ADPCM_NONALLOCATEVOICE;
}

/*********************************************************************************************************
unsigned long fn_ulResumeVoiceADPCM( long lVoiceId )

Paramètres
	Entrée:	- long				lVoiceId		Identifiant de la voie

	Sortie: - NEANT

Valeur retournée:
			- NEANT

Description:
	Fonction qui réactive une voie "ADPCM" mise en pause

*/
unsigned long fn_ulResumeVoiceADPCM( long lVoiceId )
{
	lpSND_tdstADPCMVoice	pstVoice;

	if( lVoiceId ) {
		pstVoice			=	*(lpSND_tdstADPCMVoice*)lVoiceId;
		if( ! fn_bIsVoiceValidADPCM( pstVoice, (hSND_tdstADPCMVoice)lVoiceId ) )
			return ADPCM_INVALIDVOICE;
//		snd_assert( pstVoice->bPause );
		pstVoice->bPause	=	FALSE;
		return ADPCM_NOERROR;
	}
	return ADPCM_NONALLOCATEVOICE;
}

/*********************************************************************************************************
SndBool fn_bVoiceIsPlayingADPCM( long lVoiceId )

Paramètres
	Entrée:	- long				lVoiceId		Identifiant de la voie

	Sortie: - NEANT

Valeur retournée:
			- SndBool							Booléen

Description:
	Fonction qui indique qu'une voie est toujours active

*/
SndBool fn_bVoiceIsPlayingADPCM( long lVoiceId )
{
	if( lVoiceId ) {
		if( ( *(lpSND_tdstADPCMVoice*)lVoiceId )->bIsPlaying )
			return TRUE;
		// Détruit la voie
		fn_ulRemoveVoiceADPCM( lVoiceId );
	}
	return FALSE;
}

/*********************************************************************************************************
unsigned long fn_ulRemoveVoiceCallbackADPCM( long lVoiceId )

Paramètres
	Entrée:	- long				lVoiceId		Identifiant de la voie

	Sortie: - NEANT

Valeur retournée:
			- unsigned long

Description:
	Fonction qui supprime la callback d'une voie

*/
unsigned long fn_ulRemoveVoiceCallbackADPCM( long lVoiceId )
{
	lpSND_tdstADPCMVoice	pstVoice;
	if( lVoiceId ) {
		pstVoice	=	*(lpSND_tdstADPCMVoice*)lVoiceId;
		if( ! fn_bIsVoiceValidADPCM( pstVoice, (hSND_tdstADPCMVoice)lVoiceId ) )
			return ADPCM_INVALIDVOICE;
		pstVoice->stCall.pfnCallBack	=	NULL;
		pstVoice->stCall.lCallbackParam	=	0;
		return ADPCM_NOERROR;
	}
	return ADPCM_NONALLOCATEVOICE;
}

/*********************************************************************************************************
unsigned long fn_ulInitialiseParamsADPCM( lpSND_tdstADPCMVoice pstVoice, hSND_tdstADPCMVoice hVoice, tdstBlockResourceMem* pstBlock, SND_td_pfn_vSoundCallback pfnCallback, long lCallbackParam )

Paramètres
	Entrée:	- lpSND_tdstADPCMVoice		pstVoice	Adress of an voice
			- tdstBlockResourceMem*		pstBlock	Adress of a block which discribe the resource
			- hSND_tdstADPCMVoice       hVoice      Handle of the voice

	Sortie: - NEANT

Valeur retournée:
			- unsigned long							Error code

Description:
	Function which initialize current parameters

*/
unsigned long fn_ulInitialiseParamsADPCM( lpSND_tdstADPCMVoice pstVoice, hSND_tdstADPCMVoice hVoice, tdstBlockResourceMem* pstBlock, SND_td_pfn_vSoundCallback pfnCallback, long lCallbackParam )
{
	/*
	 * Callback management
	 */
	pstVoice->stCall.pfnCallBack    = pfnCallback;
	pstVoice->stCall.lCallbackParam = lCallbackParam;
	/*
	 * Flags
	 */
	pstVoice->bCallbackDone         = FALSE;
	pstVoice->bPause                = FALSE;
	pstVoice->bStream               = pstBlock->uRes.stSample.bStream;
	pstVoice->bLoop                 = pstBlock->uRes.stSample.bLoop;
	pstVoice->bPitchable            = pstBlock->uRes.stSample.bPitchable;
	pstVoice->bPanable              = pstBlock->uRes.stSample.bPanable;
	pstVoice->bVolable              = pstBlock->uRes.stSample.bVolable;
	pstVoice->bIsPlaying            = TRUE;
	pstVoice->bIsFinishing          = FALSE;
	pstVoice->hstVoice              = hVoice;
	pstVoice->phFile                = OPENFILESND_FAILED;
	/*
	 * Static parameters
	 */
	pstVoice->unChannels            = pstBlock->uRes.stSample.uwNbChannels;
	pstVoice->unSamplesPerSec		= (*hVoice)->unSamplesPerSec;
	pstVoice->uRefRes.pstPtr        = pstBlock;
	pstVoice->ulStaticVolume        = (unsigned long)pstBlock->ucVolume;
//	snd_assert( ( pstVoice->ulStaticVolume != 0 ) );
	return ADPCM_NOERROR;
}

/*********************************************************************************************************
unsigned long fn_ulGetVoiceThemeADPCM( 	lpSND_tdstADPCMVoice* ppstTheme )

Paramètres
	Entrée:	- NEANT

	Sortie: - lpSND_tdstADPCMVoice* ppstTheme		Pointeur sur l'adresse d'une voie thème

Valeur retournée:
			- unsigned long						Code d'erreur

Description:
	Fonction qui recherche une voie thème libre.

*/
unsigned long fn_ulGetVoiceThemeADPCM( 	lpSND_tdstADPCMVoice* ppstTheme )
{
	lpSND_tdstADPCMVoice	pstTheme;

	if( gulNbTheme < NB_MAX_ADPCM_THEME ) {
		SndBool	bFindThemeFree;

		bFindThemeFree	=	FALSE;
		pstTheme		=	gstTheme;
		pstTheme->bTheme=	TRUE;
// Le pointeur sur le début des données fait égalemnet office de flag. Si ce pointeur est nul, alors la voie est
// libre
		while( ( !bFindThemeFree ) && pstTheme < &gstTheme[NB_MAX_ADPCM_THEME] ) {
			if( !pstTheme->pchBeginDatas )
				bFindThemeFree	=	TRUE;
			else
				pstTheme++;
		}
		if( ! bFindThemeFree )
			return ADPCM_NOTHEMELEFT;
	}
	else
		return ADPCM_NOTHEMELEFT;
	*ppstTheme	=	pstTheme;
	return ADPCM_NOERROR;
}
/*********************************************************************************************************
unsigned long fn_ulSetDynamicFieldsTransitionsADPCM( lpSND_tdstADPCMVoice pstVoice, lpSND_tdstADPCMVoice pstTheme )

Paramètres
	Entrée:	- lpSND_tdstADPCMVoice	pstVoice		Pointeur sur la voie
			- lpSND_tdstADPCMVoice	pstTheme		Pointeur sur le theme

	Sortie: - NEANT

Valeur retournée:
			- unsigned long

Description:
	Fonction qui remplie des paramètres dynamiques, au moment des transitions de part avec ou
	sans Fade.

*/
unsigned long fn_ulSetDynamicFieldsTransitionsADPCM( lpSND_tdstADPCMVoice pstVoice, lpSND_tdstADPCMVoice pstTheme )
{
	unsigned long			ulPitch;
	float					fBufferCoef;

	if( pstVoice && pstTheme ) {
//		memcpy( &pstTheme->pstPar, &pstVoice->pstPar, sizeof(SampleParam) );
		pstTheme->ulBufferStart			=	pstVoice->ulBufferStart;
		pstTheme->lVolume				=	pstVoice->lVolume;
		pstTheme->lPan					=	pstVoice->lPan;
		pstTheme->hstVoice				=	pstVoice->hstVoice;
		pstTheme->bTheme				=	TRUE;
		if( pstVoice->unSamplesPerSec == pstTheme->unSamplesPerSec )
			/*
			 * Case we've got the same value of PITCH
			 */
			pstTheme->ulPitch			=	pstVoice->ulPitch;
		else {
			/*
			 * Case we keep in mind the ratio
			 */
			snd_assert( pstTheme->unSamplesPerSec );
			fBufferCoef					=	(float)pstTheme->unSamplesPerSec / (float)SND_ADPCM_BUFFERFREQ;
			ulPitch						=	pstVoice->ulPitch * SND_ADPCM_BUFFERFREQ / pstVoice->unSamplesPerSec;
			pstTheme->ulPitch			=	(unsigned long)( fBufferCoef * (float)ulPitch );
		}
		/*
		 * Si nous sommes en FADE il faut reprendre le volume précédent
		 */
		if( pstVoice->bFade ) {
			pstTheme->lCoeffRight		=	pstVoice->lCoeffRight;
			pstTheme->lCoeffLeft		=	pstVoice->lCoeffLeft;
		}
		else {
			fn_vUpdateVolPan( pstTheme, pstTheme->lVolume, pstTheme->lPan );
		}
		return ADPCM_NOERROR;
	}
	else
		return ADPCM_INVALIDPARAM;
}

/*********************************************************************************************************
unsigned long fn_ulSetNextThemeADPCM( long lVoiceId, tdstBlockResourceMem* pstBlockTheme )

Paramètres
	Entrée:	- long					lVoiceId		Identifiant de la voie
			- tdstBlockResourceMem*	pstBlockTheme	Pointeur sur un descripteur de ressource

	Sortie: - NEANT

Valeur retournée:
			- unsigned long

Description:
	Fonction qui charge le prochain theme à jouer

*/
unsigned long fn_ulSetNextThemeADPCM( long lVoiceId, tdstBlockResourceMem* pstBlockTheme )
{
	lpSND_tdstADPCMVoice	pstVoice;
	lpSND_tdstADPCMVoice	pstTheme;
	lpSND_tdstWAVEFORMATEX	pstwfx;
	unsigned long			ulCode;

	if( lVoiceId ) {
		pstVoice	=	*(lpSND_tdstADPCMVoice*)lVoiceId;
		if( !fn_bIsVoiceValidADPCM( pstVoice, (hSND_tdstADPCMVoice)lVoiceId ) )
			return ADPCM_INVALIDVOICE;
		if( pstVoice->pstTheme )
			pstTheme	=	pstVoice->pstTheme;
		else {
			ulCode	=	fn_ulGetVoiceThemeADPCM( &pstTheme );
			if( ulCode != ADPCM_NOERROR )
				return ulCode;
			pstVoice->pstTheme	=	pstTheme;
		}
		pstwfx							=	(lpSND_tdstWAVEFORMATEX)( pstBlockTheme->uRes.stSample.uData.stMem.pvPtrFirst );

		memset( pstTheme, 0, sizeof(SND_tdstADPCMVoice) );
		fn_ulInitialiseParamsADPCM( pstTheme, pstVoice->hstVoice, pstBlockTheme, pstVoice->stCall.pfnCallBack, pstVoice->stCall.lCallbackParam );
		return ADPCM_NOERROR;
	}
	return ADPCM_NONALLOCATEVOICE;
}

/*********************************************************************************************************
SndBool fn_bFadeThemeADPCM( long lVoiceId, tdstBlockResourceMem* pstBlockTheme, SndReal rDuration )

Paramètres
	Entrée:	- long					lVoiceId		Identifiant de la voie
			- tdstBlockResourceMem*	pstBlockTheme	Prochaine ressource à jouer
	        - SndReal               rDuration       Set the fade duration

	Sortie: - NEANT

Valeur retournée:
			- SndBool								Indique si la transition est faisable

Description:
	Fonction qui effectue une transition inopinée entre deux blocs qui ne s'enchainent pas
	La durée de la transition doit se faire en dur et durer une seconde

*/
SndBool fn_bFadeThemeADPCM( long lVoiceId, tdstBlockResourceMem* pstBlockTheme, SndReal rDuration )
{
	lpSND_tdstADPCMVoice	pstVoice;

	if( lVoiceId ) {
		unsigned long			ulNbSample;
		pstVoice					=	*(lpSND_tdstADPCMVoice*)lVoiceId;
		if( ! fn_bIsVoiceValidADPCM( pstVoice, (hSND_tdstADPCMVoice)lVoiceId ) ) {
			snd_assert( 0 );
			return ADPCM_INVALIDVOICE;
		}
		ulNbSample					=	(unsigned long)( pstVoice->pchEndDatas - pstVoice->pchCurrentDatas ) << 1;
		// Il faut enchainer le prochain theme avec un "fade"
		pstVoice->bFade				=	TRUE;
		fn_vGetRegressionADPCM( pstVoice, rDuration );
		pstVoice->bFadeIn			=	FALSE;	// "Fade" descendant ou "fade out"
		// Paramétrage d'une voie dédiée aux transitions avec "Fade"
		if( pstVoice->pstTheme )
			pstVoice->pstTheme->pstTheme = &gstVoiceFade;
		else {
			snd_assert( 0 );
			pstVoice->pstTheme = &gstVoiceFade;
		}
		memset( & gstVoiceFade, 0, sizeof(SND_tdstADPCMVoice) );
		gstVoiceFade.bTheme			=	TRUE;
		fn_ulInitialiseParamsADPCM( &gstVoiceFade, pstVoice->hstVoice, pstBlockTheme, pstVoice->stCall.pfnCallBack, pstVoice->stCall.lCallbackParam );
		return TRUE;
	}
	return FALSE;
}

/*********************************************************************************************************
unsigned long fn_ulGetLengthVoiceADPCM( long lVoiceId, unsigned long* pulLength )

Paramètres
	Entrée:	- long				lVoiceId	Identifiant de la voie

	Sortie: - unsigned long*	pulLength	Pointeur sur le résultat

Valeur retournée:
			- unsigned long					Code d'erreur

Description:
	Fonction qui indique la durée de la voie en sample

*/
unsigned long fn_ulGetLengthVoiceADPCM( long lVoiceId, unsigned long* pulLength )
{
    lpSND_tdstADPCMVoice	pstVoice;

	if( ! pulLength )
		return ADPCM_INVALIDPARAM;
	if( lVoiceId ) {
		pstVoice	=	*(lpSND_tdstADPCMVoice*)lVoiceId;
		if( ! fn_bIsVoiceValidADPCM( pstVoice, (hSND_tdstADPCMVoice)lVoiceId ) )
			return ADPCM_INVALIDVOICE;
		if( ! pstVoice->bLoop )
			*pulLength	=	pstVoice->ulNbSample;
		else
			*pulLength	=	pstVoice->ulSampleStart + pstVoice->ulSampleLength;
		return ADPCM_NOERROR;
	}
	return ADPCM_NONALLOCATEVOICE;
}

/*********************************************************************************************************
unsigned long fn_ulGetPosVoiceADPCM( long lVoiceId, double* pdPos, unsigned long ulBytePos )

Paramètres
	Entrée:	- long       lVoiceId    Identifiant de la voie
	        - double*    pdPos       Current position inside the buffer expressed in seconds

	Sortie: - double*    pdPos       Modified result, depending on the position

Valeur retournée:
			- unsigned long			La position du sample audible

Description:
	Fonction qui retourne un code d'erreur. La position en seconde au sein du sample

*/
unsigned long fn_ulGetPosVoiceADPCM( long lVoiceId, double* pdPos, unsigned long ulBytePos )
{
	unsigned long        ulAnswer;

	ulAnswer = ADPCM_NONALLOCATEVOICE;
	if( !pdPos )
		ulAnswer = ADPCM_INVALIDPARAM;
	else {
		if( lVoiceId ) {
			lpSND_tdstADPCMVoice pstVoice;

			pstVoice = *(lpSND_tdstADPCMVoice*)lVoiceId;
			if( fn_bIsVoiceValidADPCM( pstVoice, (hSND_tdstADPCMVoice)lVoiceId ) ) {
				unsigned long ulLength;

				ulLength = pstVoice->ulSamplePos;
				/*
				 * Looping sample
				 */
				if( pstVoice->bLoop ) {
					if( ulLength > pstVoice->ulSampleStart + pstVoice->ulSampleLength )
						ulLength = ( ( ulLength - pstVoice->ulSampleStart ) % pstVoice->ulSampleLength ) + pstVoice->ulSampleStart;
				}
				/*
				 * Non looping sample
				 */
				else {
					if( ulLength > pstVoice->ulNbSample )
						ulLength = pstVoice->ulNbSample;
				}
				if( !pstVoice->bStream ) {
					*pdPos = (double)ulLength / (double)pstVoice->unSamplesPerSec - 8.0 / 735.0;
				}
				else {
					if( pstVoice->bIsPlaying ) {
						/*
						 * In an empirique way we knew the asynchronous
						 * client buffer have a 0.8 second delay. Experimentaly we employ
						 * a 0.90 second delay
						 */
						if( ulLength > (unsigned long)( ( 9 * pstVoice->unSamplesPerSec ) / 10 ) )
							*pdPos = (double)ulLength / (double)pstVoice->unSamplesPerSec - 0.90;
						else
							*pdPos = 0.0;
					}
					if( pstVoice->bIsFinishing ) {
						double        dTemp;
						const double  d = (double)M_SIZE_ASSYNCHRONE / 2;
						dTemp  = (double)( ulBytePos - pstVoice->ulBufferEnd );
						dTemp /= d;
						*pdPos += dTemp;
						if( dTemp >= 0.9 )
							pstVoice->bIsPlaying = FALSE;
					}
				}
				if( ( ulLength == pstVoice->ulNbSample ) && !pstVoice->bIsPlaying )
					ulAnswer = ADPCM_ENDREACH;
				else
					ulAnswer = ADPCM_NOERROR;
			}
		}
	}
	return ulAnswer;
}

/*********************************************************************************************************
SndBool fn_bIsVoiceValidADPCM( lpSND_tdstADPCMVoice pstVoice, hSND_tdstADPCMVoice hstVoice )

Paramètres
	Entrée:	- lpSND_tdstADPCMVoice	pstVoice	Identifiant de la voie
			- hSND_tdstADPCMVoice	hstVoice	Handle de la voie

	Sortie: - NEANT

Valeur retournée:
			- SndBool						Un booléen qui indique si la voie n'est pas aberrante

Description:
	Fonction de test de validité

*/
SndBool fn_bIsVoiceValidADPCM( lpSND_tdstADPCMVoice pstVoice, hSND_tdstADPCMVoice hstVoice )
{
	SndBool	bResult;
	bResult	=	FALSE;

	if (pstVoice == NULL)
		return FALSE;

	if( ( gstVoice <= pstVoice ) && ( & gstVoice[ NB_MAX_ADPCM_VOICE ] >= pstVoice ) ) {
		if( ( ( (int)pstVoice - (int)gstVoice ) % sizeof( SND_tdstADPCMVoice ) ) == 0 )
			bResult	=	TRUE;
	}
	if( hstVoice && bResult ) {
		if( hstVoice != pstVoice->hstVoice )
			bResult	=	FALSE;
	}
	if( bResult == FALSE ) {
#ifdef DEBUG_OUTPUT_ADPCM
		_RPT0( _CRT_WARN, "Invalide Voice\n" );
#endif 
		snd_assert( 0 ); // Ne pas abuser!
	}
	return bResult;
}

/*********************************************************************************************************
void fn_vSetDataADPCM( lpSND_tdstADPCMVoice pstVoice )

Paramètres
	Entrée:	- lpSND_tdstADPCMVoice	pstVoice	Identifiant de la voie

	Sortie: - NEANT

Valeur retournée:
			- NEANT

Description:
	Procédure d'affectation de données au sein d'une voie

*/
void fn_vSetDataADPCM( lpSND_tdstADPCMVoice pstVoice )
{
	if( pstVoice->unChannels == 1 )
		memcpy( & pstVoice->uEnv.stStateMono, & pstVoice->uInitEnv.stStateMono, sizeof(tduState) );
	else
		memcpy( & pstVoice->uEnv.stStateStereo, & pstVoice->uInitEnv.stStateStereo, sizeof(tduState) );
}

/*********************************************************************************************************
unsigned long fn_ulFillSizeAndLoopADPCM( tduResDisk* puRes, void* pvPtr )

Paramètres
	Entrée:	- tduResDisk*			puRes
			- void*					pvPtr

	Sortie: - NEANT

Valeur retournée:
			- NEANT

Description:
	Procédure qui remplie un "tduResDisk" à partir d'une en tête "APM"

*/
unsigned long fn_ulFillSizeAndLoopADPCM( tdstBlockResourceDisk* pstDisk, void* pvPtr )
{
	unsigned long			ulErr;
	lpSND_tdstWAVEFORMATEX	pstwfx;
	tdstTypeSampleDisk*		pstSample;

	if( ( pstDisk == NULL ) || ( pvPtr == NULL ) )
		return ADPCM_INVALIDPARAM;
	pstwfx	=	(lpSND_tdstWAVEFORMATEX)pvPtr;
	if( ( ulErr = fn_ulResolveVersion( pstwfx->ulCompressId ) ) != ADPCM_NOERROR )
		return ulErr;
	pstSample	=	& pstDisk->uRes.stSample;
	pstSample->uwNbChannels	=	pstwfx->unChannels;
	if( pstwfx->unChannels == 1 )
		pstDisk->ulDataSize		=	pstwfx->ulNbSample << 1;
	else
		pstDisk->ulDataSize		=	pstwfx->ulNbSample << 2;
	if( pstwfx->ulSampleLoop != SND_ADPCM_NOLOOP ) {
		pstSample->bLoop		=	TRUE;
		pstSample->ulStartLoop	=	( pstwfx->ulSampleLoop * pstwfx->unChannels ) << 1;
		pstSample->ulLoopLength	=	( pstwfx->ulLengthLoop * pstwfx->unChannels ) << 1;
	}
	else {
		pstSample->bLoop		=	FALSE;
		pstSample->ulStartLoop	=	0;
	}

	return ADPCM_NOERROR;
}

/*********************************************************************************************************
unsigned long fn_ulConvertDataADPCM( void* pDataIn, void* pDataOut )

Paramètres
	Entrée:	- void*					pDataIn

	Sortie: - void*					pDataOut

Valeur retournée:
			- Code d'erreur

Description:
	Fonction qui covertie en mémoire un sample APM en sample PCM

*/
//#define M_SAVE_CONV_APM
unsigned long fn_ulConvertDataADPCM( void* pDataIn, void* pDataOut )
{
	unsigned long			ulErr;
	lpSND_tdstWAVEFORMATEX	pstwfx;
	long*					plBegin;
	long*					plCurrent;
	long*					plEnd;
	char*					pchDataAPM;
	tduState				uState;
#ifdef M_SAVE_CONV_APM
	void*  pvFileDta;
	HANDLE hBinFile;
	DWORD  dwWrite;

	pvFileDta = pDataOut;
#endif

	ulErr	=	ADPCM_NOERROR;
	pstwfx	=	(lpSND_tdstWAVEFORMATEX)pDataIn;
	if( ( ulErr = fn_ulResolveVersion( pstwfx->ulCompressId ) ) == ADPCM_NOERROR ) {
		memcpy( & uState, & pstwfx->uInitial, sizeof(tduState) );
		if( pstwfx->unChannels == 1 ) {
			plBegin		=	(long*)SND_fn_pvMallocSnd( pstwfx->ulNbSample << 2 );
			plCurrent	=	plBegin;
			if( plBegin != NULL ) {
				plEnd		=	plBegin + (unsigned long)( pstwfx->ulNbSample );
				pchDataAPM	=	(char*)( pstwfx + 1 );
				AdpcmDecoderMono( pstwfx->ulNbSample, & pchDataAPM, & plCurrent, & uState.stStateMono );
			}
			else
				ulErr	=	ADPCM_ALLOCMEMFAIL;
		}
		else {
			plBegin		=	(long*)SND_fn_pvMallocSnd( pstwfx->ulNbSample << 3 );
			plCurrent	=	plBegin;
			if( plBegin != NULL ) {
				plEnd		=	plBegin + (unsigned long)( pstwfx->ulNbSample << 1 );
				pchDataAPM	=	(char*)( pstwfx + 1 );
				AdpcmDecoderStereo( pstwfx->ulNbSample, & pchDataAPM, & plCurrent, & uState.stStateStereo );
			}
			else
				ulErr	=	ADPCM_ALLOCMEMFAIL;
		}
		if( ulErr != ADPCM_ALLOCMEMFAIL ) {
			AdpcmStoreClampDatas( plBegin, plEnd, (short**)( & pDataOut ) );
			SND_fn_vFreeSnd( plBegin );
		}

#ifdef M_SAVE_CONV_APM
		hBinFile = CreateFile( "c:\\convapm.raw", GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL );
		SetFilePointer( hBinFile, 0, NULL, FILE_END );
		WriteFile( hBinFile, pvFileDta, (char*)pDataOut - (char*)pvFileDta, &dwWrite, NULL );
		CloseHandle( hBinFile );
#endif

	}
	return ulErr;
}