/*
//////////////////////////////////////////////////////////////
//	SNDTRANS.C												//
//////////////////////////////////////////////////////////////
//	Module d'exploitation des THEMEs PCM					//
//////////////////////////////////////////////////////////////
*/

#include "SNDinc.h"
 
#include "sndres.h"
 
#ifndef DISABLE_THEME_WAVE
 
#if defined(_DLL_COMPILATION_MODE)
#include "snddll.h"
#endif
 
#include "sndxd.h"
#include "sndthrd.h"
 
#ifndef NO_ACP_SOUND
 
typedef struct TRANSITION_BUFFER_ *pTRANSITION_BUFFER;
typedef struct TRANSITION_VOICE_ *pTRANSITION_VOICE;
 
SNDLST2_M_DynamicUseListOf(pTRANSITION_BUFFER);
SNDLST2_M_DynamicUseListOf(pTRANSITION_VOICE);
 
typedef struct TRANSITION_BUFFER_{
	void*			ptrData;
	unsigned long	ulNbSamples;
	long			lId;
	SNDLST2_M_DynamicElementDeclaration(pTRANSITION_BUFFER)
} TRANSITION_BUFFER;
 
typedef struct TRANSITION_VOICE_{
	long			lIdBufferClient;
	SndBool_field_decl(bEnded);/*TRUE quand la voie doit s'arreter*/
	SndBool_field_decl(bFading);	/*TRUE si transition avec Fade en cours*/
	long			lSampleToBytesShift;/*nb de bits par echantillon*/
	tdhSndFile		hFile;
	long			lLastPushed;			/*id du dernier data-buffer*/
	unsigned long	ulByteMax;	/*offset de dernier octet dans le fichier*/
	unsigned long	ulNextPtr;	/*offset du prochain octet à lire dans le fichier*/
	tduRefRes		uCurRes;	/*current resource*/
	tduRefRes		uNextRes;	/*next ressource*/
	tduRefRes		uAfterFadeRes;	/*resource to be played after fade*/
	unsigned long	ulCoefFade;	/*coef du fade en cours*/
	long			lCoefShift;
	SND_td_pfn_vSoundCallback	pfnCallback;
	long						lParCallback;
	SNDLST2_M_DynamicAnchorDeclaration(pTRANSITION_BUFFER) pList;
	SNDLST2_M_DynamicElementDeclaration(pTRANSITION_VOICE)
} TRANSITION_VOICE;
 
typedef struct LISTE_TRANSITION_VOICE_{
	SNDLST2_M_DynamicAnchorDeclaration(pTRANSITION_VOICE) pList;
} LISTE_TRANSITION_VOICE;
 
static LISTE_TRANSITION_VOICE ListTransition={NULL,NULL,0};
static SndBool bInitDone=FALSE;
 
#define SIZE_DATABUFFER 5120
#define NB_BUFFER_AVANCE 3
#define M_FADE_DURATION_WAVE ( 1 << 16 ) 


HANDLE hMutexSndTrans=NULL;

void SND_fn_vEnterCriticalSectionThreadSndTrans()
{
	DWORD retWait;

	if (hMutexSndTrans)
        if ((retWait=WaitForSingleObject(hMutexSndTrans,INFINITE))==WAIT_TIMEOUT) 
			/*DEADLOCK !!!!!!!!!!!!!!!!!!!!*/
            DebugBreak();
			/*DEADLOCK !!!!!!!!!!!!!!!!!!!!*/
}

void SND_fn_vQuitCriticalSectionThreadSndTrans()
{
	if (hMutexSndTrans)
		ReleaseMutex(hMutexSndTrans);
}

/*------------*/
void purge_databuffer_transition(pTRANSITION_BUFFER databuffer)
{
	SNDLST2_M_DynamicIsolate(databuffer);
	SND_fn_vFreeSnd(databuffer->ptrData);
	SND_fn_vFreeSndEx(E_ucSndBlockMain,databuffer);
}
 
void push_databuffer_transition(pTRANSITION_VOICE transition)
{
	pTRANSITION_BUFFER	databuffer;
	unsigned long		nb_lus,nb_to_read,nb_left,nb_wished;
	SND_tdstStackBuffer stackbuffer;
	char				filename[256];
	SndBool				bFadeToDo=FALSE;
	int					i;
	long				lTmp,lTmp1,lTmp2;
 
	if (!transition->bEnded)
	{
		databuffer=SND_fn_pvMallocSndEx(E_ucSndBlockMain,sizeof(TRANSITION_BUFFER));
		SNDLST2_M_DynamicInitElement(databuffer);
		databuffer->ulNbSamples=SIZE_DATABUFFER>>transition->lSampleToBytesShift;
		databuffer->ptrData=SND_fn_pvMallocSnd(SIZE_DATABUFFER);
        
		nb_to_read=SIZE_DATABUFFER;
		nb_left=nb_to_read;
 
		while (nb_left>0)
		{/*tant que le buffer n'est pas plein*/
			if ((transition->hFile==OPENFILESND_FAILED) && (transition->uNextRes.pstPtr==SND_C_RES_FANTOME_PTR))
			{/*lack of data (no data to read)*/
				memset(databuffer->ptrData,0,nb_left);
				nb_left=0;
			}
			else
			{
				if (transition->hFile==OPENFILESND_FAILED)
				{/*theme has been re-started*/
					transition->uCurRes.pstPtr=transition->uNextRes.pstPtr;
					transition->uNextRes.pstPtr=SND_C_RES_FANTOME_PTR;
					transition->ulByteMax=transition->uCurRes.pstPtr->uRes.stSample.uData.stStream.ulOffsetLast;
					transition->ulNextPtr=transition->uCurRes.pstPtr->uRes.stSample.uData.stStream.ulOffsetFirst;
 
					(*transition->pfnCallback)(transition->lParCallback);

					SND_fn_vResolveFileName(transition->uCurRes.pstPtr->uRes.stSample.uData.stStream.szFileName,filename);
					transition->hFile=SND_fn_hOpenFileReadSnd(filename);
					SND_fn_dwSeekFileSnd(transition->hFile,transition->ulNextPtr,SEEKFILESND_BEGIN);
				}

				nb_wished=min(nb_to_read,transition->ulByteMax-transition->ulNextPtr);

				if (transition->bFading)
				{
					nb_wished=min(nb_wished,transition->ulCoefFade);
					bFadeToDo=TRUE;
				}
 
				if ((nb_lus=SND_fn_dwReadFileSnd(transition->hFile
					,nb_wished
					,databuffer->ptrData)) != nb_to_read)
				{/*on est arrive à la fin du fichier ou du Fade*/
					SND_fn_vCloseFileSnd(transition->hFile);
					transition->hFile=OPENFILESND_FAILED;

					if ((transition->bFading) && (nb_lus==transition->ulCoefFade))
					{/*fade is finished*/
						transition->uNextRes.pstPtr=transition->uAfterFadeRes.pstPtr;
						transition->uAfterFadeRes.pstPtr=NULL;
					}

					if (transition->uNextRes.pstPtr!=SND_C_RES_FANTOME_PTR)
					{
						transition->uCurRes.pstPtr=transition->uNextRes.pstPtr;
						transition->uNextRes.pstPtr=SND_C_RES_FANTOME_PTR;
						transition->ulByteMax=transition->uCurRes.pstPtr->uRes.stSample.uData.stStream.ulOffsetLast;
						transition->ulNextPtr=transition->uCurRes.pstPtr->uRes.stSample.uData.stStream.ulOffsetFirst;
 
						SND_fn_vResolveFileName(transition->uCurRes.pstPtr->uRes.stSample.uData.stStream.szFileName,filename);
						transition->hFile=SND_fn_hOpenFileReadSnd(filename);
						SND_fn_dwSeekFileSnd(transition->hFile,transition->ulNextPtr,SEEKFILESND_BEGIN);
					}/*if (transition->uNextRes)*/
					else
					{
						//databuffer->ulNbSamples=(nb_to_read-nb_left)>>transition->lSampleToBytesShift;
						//transition->lLastPushed=databuffer->lId;

						/*no more data to read*/
						memset((char*)databuffer->ptrData+nb_lus,0,nb_left-nb_lus);
						nb_left=0;
					}
 
					/*user's callback*/
					if (!(transition->bFading) || (nb_lus==transition->ulCoefFade))
					{
						(*transition->pfnCallback)(transition->lParCallback);
						transition->bFading=FALSE;
					}
				}/*if ((nb_lus=SND_fn_dwReadFileSnd*/
				else
				{/*on n'est pas encore à la fin du fichier*/
					transition->ulNextPtr+=nb_to_read;
				}
 
				if (bFadeToDo)
				{/*fade*/
					for (i=0;i<(long)(databuffer->ulNbSamples<<transition->lSampleToBytesShift)/4;i++)
					{
						lTmp=*((long*)databuffer->ptrData+i);
						lTmp1=(lTmp<<16)>>16;
						lTmp2=lTmp>>16;
						lTmp1*=transition->ulCoefFade;
						lTmp2*=transition->ulCoefFade;
						lTmp1>>=transition->lCoefShift;
						lTmp2>>=transition->lCoefShift;
						lTmp=((lTmp2<<16)&0xFFFF0000) | (lTmp1 & 0xFFFF);
						*((long*)databuffer->ptrData+i)=lTmp;
 
						if (transition->uCurRes.pstPtr->uRes.stSample.uwNbChannels==2)
							transition->ulCoefFade-=2;
						else
							transition->ulCoefFade--;
						
						if (!transition->ulCoefFade)
							break;
					}
 
				}
 
				nb_left=nb_to_read-nb_lus;
			}
		}/*while*/
 
					
		SNDLST2_M_DynamicAddTail(&transition->pList,databuffer);
 
		stackbuffer.ulNbSamples=databuffer->ulNbSamples;
		stackbuffer.ptrData=databuffer->ptrData;
		databuffer->lId=SND_fn_lPushBufferExSxd(transition->lIdBufferClient,&stackbuffer);
	}
}
 
void SND_CALL TransitionCallback(long user_id,long poped_id)
{
	pTRANSITION_VOICE	transition;
	pTRANSITION_BUFFER	databuffer;
	int					i;
	SndBool				found=FALSE;
 
	SND_fn_vEnterCriticalSectionThreadSndTrans();
 
	transition=(pTRANSITION_VOICE)user_id;
	databuffer=(pTRANSITION_BUFFER)poped_id;
	SNDLST2_M_DynamicForEachElementOf(&transition->pList,databuffer,i)
	{
		if (databuffer->lId==poped_id)
		{
			purge_databuffer_transition(databuffer);
			found=TRUE;
			break;
		}
	}
 
	snd_assert(found);
 
	push_databuffer_transition(transition);
 
	SND_fn_vQuitCriticalSectionThreadSndTrans();
}
 
void purge_transition(pTRANSITION_VOICE transition)
{
	pTRANSITION_BUFFER	pDataBuffer,pDataBufferNext;
	int i;
 
	snd_assert(transition->bEnded);
 
	SND_fn_vDeleteBufferSxd(transition->lIdBufferClient);
	transition->lIdBufferClient=C_PLAY_FAILED;
 
	if (transition->hFile!=OPENFILESND_FAILED)
		SND_fn_vCloseFileSnd(transition->hFile);
	transition->hFile=OPENFILESND_FAILED;
 
	SNDLST2_M_DynamicForEachMovingElementOf(&transition->pList,pDataBuffer,pDataBufferNext,i)
	{
		purge_databuffer_transition(pDataBuffer);
	}
 
	SNDLST2_M_DynamicIsolate(transition);
	SND_fn_vFreeSndEx(E_ucSndBlockMain,transition);
}
 
 
/*---themes*/
/*--------------------------------------------------------------------------------
PlayTransition:lance une melodie dynamique (suite continue de samples s'enchainant
Entrees:FirstRes=1° ressource à jouee immediatement
    NextRes=ressource devant s'enchainer à la fin de la 1° sauf contre-ordre par SetNextTrtansition
    callback=fonction a appeler chaque fois qu'une tranistion à lieu (c.a.d. qu'en on vodrait
        savoir quel sera le prochain element à jouee apres celui qui vient d'etre lance)
Sortie:ID de la voie (pour les fonctions suivantes)
-------------------------------------------------------------------------------------*/
long SND_fn_lPlayTransitionSxdWave(tduRefRes FirstRes,tduRefRes NextRes,SND_td_pfn_vSoundCallback transition_callback,long param_callback,SoundParam* par)
{
	pTRANSITION_VOICE	transition;
	SND_tdstFormat		format;
	SND_tdstCallback	callback;
	char				filename[256];
	int					i;
 
	snd_assert(FirstRes.pstPtr->uRes.stSample.bStream);
	snd_assert(NextRes.pstPtr->uRes.stSample.bStream);
	if (!FirstRes.pstPtr->uRes.stSample.bStream || !NextRes.pstPtr->uRes.stSample.bStream)
		return C_PLAY_FAILED;
 
	SND_fn_vEnterCriticalSectionThreadSndTrans();
 
	transition=SND_fn_pvMallocSndEx(E_ucSndBlockMain,sizeof(TRANSITION_VOICE));
	SNDLST2_M_DynamicInitElement(transition);
	SNDLST2_M_DynamicAddTail(&ListTransition.pList,transition);
 
	SNDLST2_M_DynamicInitAnchor(&transition->pList);
	/*
	 * Format of the client buffer
	 */
	format.eZip                       = SAMPLE_PCM;
	format.uFormat.stPCM.ulNbSamples  = 0;
	format.uFormat.stPCM.uwResolution = FirstRes.pstPtr->uRes.stSample.uwResolution;
	format.uFormat.stPCM.uwNbChannels = FirstRes.pstPtr->uRes.stSample.uwNbChannels;
	format.uFormat.stPCM.ulFreq       = FirstRes.pstPtr->uRes.stSample.ulFreq;
	/*
	 * Callback parameters
	 */
	callback.eType                   = BUFFER_STACK;
	callback.uCallback.CallbackStack = TransitionCallback;
	callback.uInfo.lNbSampleToPush   = 0;
	/*
	 * Open and affect the theme voice
	 */
	transition->pfnCallback         = transition_callback;
	transition->lParCallback        = param_callback;
 	transition->lLastPushed         = C_PLAY_FAILED;
	transition->lIdBufferClient     = SND_fn_lCreateNewBufferExSxd( &format, &callback, par, (long)transition );

    SND_fn_vResolveFileName( FirstRes.pstPtr->uRes.stSample.uData.stStream.szFileName, filename );
	transition->uNextRes.pstPtr     = NextRes.pstPtr;
	transition->uCurRes.pstPtr      = FirstRes.pstPtr;
    transition->ulByteMax           = transition->uCurRes.pstPtr->uRes.stSample.uData.stStream.ulOffsetLast;
    transition->ulNextPtr           = transition->uCurRes.pstPtr->uRes.stSample.uData.stStream.ulOffsetFirst;
	transition->bFading             = FALSE;
	transition->hFile               = SND_fn_hOpenFileReadSnd( filename );
	transition->bEnded              = FALSE;
	transition->lSampleToBytesShift = format.uFormat.stPCM.uwResolution/8 + format.uFormat.stPCM.uwNbChannels - 2;
	SND_fn_dwSeekFileSnd( transition->hFile, transition->ulNextPtr, SEEKFILESND_BEGIN );
 
	/*lancement des 1° push*/
	for (i=0;i<NB_BUFFER_AVANCE;i++)
	{
		push_databuffer_transition(transition);
	}
 
	SND_fn_vQuitCriticalSectionThreadSndTrans();
 
	return (long)transition;
}
 
SndBool SND_fn_bSetParamTransitionSxdWave(long voice,SoundParam* par)
{
	pTRANSITION_VOICE	transition;
	SndBool				bRet;
 
	SND_fn_vEnterCriticalSectionThreadSndTrans();
 
	if (voice==C_PLAY_FAILED)
		bRet=FALSE;
	else
	{
		transition=(pTRANSITION_VOICE)voice;
		if (transition->bEnded) 
		{
			purge_transition(transition);
			bRet=FALSE;
		}
		else
		{
			SND_fn_vSetParamBufferSxd(transition->lIdBufferClient,par);
			bRet=TRUE;
		}
	}
 
	
	SND_fn_vQuitCriticalSectionThreadSndTrans();
 
	return bRet;
}
 
/*--------------------------------------------------------------------------------
SetNextTransition:Modifie la ressource devant s’enchainer à la fin de la ressource en cours de restitution ; l’effet de cette fonction ne se fera donc sentir qu’à la fin de la ressource courante. De plus, si plusieurs appels ont lieu durant la restitution de la meme ressource, le dernier annule les effets des precedents.
Entrees:voice=voie concernee par la remise à jour
    new_res=nouvelle ressource à lancer par defaut à la fin de celle en cours
Retour:TRUE si OK, FALSE si voie morte (plus d'autre SetNextTransition necessaire)
-----------------------------------------------------------------------------------*/
SndBool SND_fn_bSetNextTransitionSxdWave(long voice,tduRefRes new_res)
{
	pTRANSITION_VOICE	transition;
 
	snd_assert(new_res.pstPtr->uRes.stSample.bStream);
	if (!new_res.pstPtr->uRes.stSample.bStream)
		return FALSE;
 
	SND_fn_vEnterCriticalSectionThreadSndTrans();
 
	transition=(pTRANSITION_VOICE)voice;
 
	transition->uNextRes.pstPtr=new_res.pstPtr;
    
	SND_fn_vQuitCriticalSectionThreadSndTrans();
 
	return TRUE;
}
 
SndBool SND_fn_bDoTransitionWithFadeSxdWave2( long voice, tduRefRes new_res, SndReal rDuration )
{
	pTRANSITION_VOICE	transition;
	int cmpt;
 
	snd_assert( new_res.pstPtr->uRes.stSample.bStream );
	if( !new_res.pstPtr->uRes.stSample.bStream )
		return FALSE;
 
	SND_fn_vEnterCriticalSectionThreadSndTrans();
 
	transition=(pTRANSITION_VOICE)voice;
 
	transition->uAfterFadeRes.pstPtr=new_res.pstPtr;
 
	if (!transition->bFading)
	{
		transition->bFading=TRUE;
#if 0
		transition->ulCoefFade=transition->uCurRes.pstPtr->uRes.stSample.ulFreq;/*1 sec de fade*/
#endif
		transition->ulCoefFade = M_RealToIntSnd( M_MulIntRealSnd( rDuration, transition->uCurRes.pstPtr->uRes.stSample.ulFreq ) );
		cmpt=0;
		while (transition->ulCoefFade>0)
		{
			cmpt++;transition->ulCoefFade>>=1;
		}
		transition->ulCoefFade=1<<cmpt;/*2^x/Freq sec de fade <-> transition->lCoefFade=coef de mixage*/
		transition->lCoefShift=cmpt;
	}
 
 
	SND_fn_vQuitCriticalSectionThreadSndTrans();
 
	return FALSE;
}
 
SndBool SND_fn_bDoTransitionWithFadeSxdWave( long voice, tduRefRes new_res )
{
	return SND_fn_bDoTransitionWithFadeSxdWave2( voice, new_res, M_FADE_DURATION_WAVE );
}

 
/*--------------------------------------------------------------------------------
StopTransition:Stop une melodie/theme en cours
Entrees:voice=voie devant etre stoppee
-----------------------------------------------------------------------------------*/
void SND_fn_vStopTransitionSxdWave(long voice)
{
	pTRANSITION_VOICE	transition;
 
	SND_fn_vEnterCriticalSectionThreadSndTrans();
 
	if (voice!=C_PLAY_FAILED)
	{
		transition=(pTRANSITION_VOICE)voice;
		transition->bEnded=TRUE; 
 
		purge_transition(transition);
	}
 
	SND_fn_vQuitCriticalSectionThreadSndTrans();
}
 
void SND_fn_vPauseTransitionSxdWave(long voice)
{
	pTRANSITION_VOICE	transition;
 
	SND_fn_vEnterCriticalSectionThreadSndTrans();
 
	transition=(pTRANSITION_VOICE)voice;
 
	SND_fn_vPauseBufferSxd(transition->lIdBufferClient);
 
	SND_fn_vQuitCriticalSectionThreadSndTrans();
}
 
void SND_fn_vResumeTransitionSxdWave(long voice)
{
	pTRANSITION_VOICE	transition;
 
	SND_fn_vEnterCriticalSectionThreadSndTrans();
 
	transition=(pTRANSITION_VOICE)voice;
 
	SND_fn_vResumeBufferSxd(transition->lIdBufferClient);
 
	SND_fn_vQuitCriticalSectionThreadSndTrans();
}

int SND_fn_iInitTransitionSxdWave(SND_tdstInitStruct *pInitStruct)
{
	hMutexSndTrans=CreateMutex(NULL,FALSE,NULL);

	if (hMutexSndTrans)
		return C_INIT_OK;
	else
		return C_INIT_FAILED;
}
 
#endif /*NO_ACP_SOUND*/
 
#endif /* DISABLE_THEME_WAVE*/
