/*---------------------------------------------------------------
FILE:   SndDrv3d.c
ROLE:   management of GUIDS, nb of voices, and .ini file for DSound3d

MODIFICATIONS: 
  - J.Vibert - 11/02/99 - bug in optimum voice number, if GUID NULL or buffers already allocated
  - J.Vibert - 08/12/98 - maximum number of voices reduced
  - J.Vibert - 07/12/98 - init cleaned+tests/StreamingBuffers
  - J.Vibert - 02/12/98 - small modif for fn_lGetOptimumVoiceNumber
  - J.Vibert - 01/12/98 - extracted from sndxdwav.c
----------------------------------------------------------------*/

#include <windows.h>
#include <mmsystem.h>
#include <dsound.h>

#include "iA3D.H"

#include "SNDinc.h"
#include "sndthrd.h"
#if defined(_DLL_COMPILATION_MODE) && defined(_DLL_MODULE) 
#include "snddll.h"
#endif
#include "sndxd.h"
#include "sndres.h"
#include "sndhardw.h"
#include "sndxdwav.h"
#include "sndini.h"
#include "sndspace.h"

#include "priv3dFl.h"

extern int WINAPI VerifyAurealA3D(void);

#define MSND_fn_vDisplayError(a,b) {\
	/*MessageBox(NULL,#a,b,MB_OK);*/\
	SND_fn_vDisplayError(a,b);\
}

//--list of available GUIDs
static int nb_GUIDSound=0;
static pstGUIDAvailable    pGUIDAvailable=NULL;
static LPGUID			   lpGuidChosen=NULL;
static SndBool				g_bA3DDisable=FALSE;
extern LPDIRECTSOUND	   g_lpDirectSoundObject;
extern long				g_lA3D;
extern SND_tdstInitStruct  InitStructSaved;
extern LPDIRECTSOUNDBUFFER lpDSPrimBuffer;
extern SndBool			   driver_SND_OK;
extern SndBool			   g_b_ChooseOutputFormat;

void GetErrorDSound(HRESULT err,char* texte,int size_texte);

void desinit_DSOUND(LPDIRECTSOUND *lplpDirectSoundObject)
{
		if (*lplpDirectSoundObject!=NULL)
				IA3d_Release(*lplpDirectSoundObject);
//				IDirectSound_Release(*lplpDirectSoundObject);
		*lplpDirectSoundObject=NULL;
}

//create DSOUND object(loops if busy driver and retry=TRUE)
//Parameters:
//	lpGuid: GUID to open (may be NULL)																				  
//	retry:	if TRUE, and Driver is busy, display MessageBox to try again; if FALSE, never display any MessageBox
//	lA3D:	return the error code of A3D init
//	bA3DDisable: indicate id DirectSoundCreate should be used than A3D
LPDIRECTSOUND create_A3DSOUND(LPGUID lpGuid,SndBool retry,long *lA3D,SndBool bA3DDisable)
{
	LPDIRECTSOUND lpDirectSoundObject;
	HRESULT res;
	int ret;
	char texte[80];
	SndBool out=FALSE;
	SndBool ok=FALSE;
	DWORD iniTime= timeGetTime();	

	//--creation +global initialisation
    while (!out)
    {
		if (timeGetTime()-iniTime>7000)
			return NULL;

		if (bA3DDisable)
			*lA3D=DirectSoundCreate(lpGuid,&lpDirectSoundObject,NULL);
		else
			*lA3D=A3dCreate(lpGuid,&lpDirectSoundObject,NULL);

		switch (*lA3D)
		{
		case A3D_OK_OLD_DLL:
		case A3D_OK:
			ok=TRUE;
            out=TRUE;
			break;
		case DS_OK:
			ok=TRUE;
            out=TRUE;
			break;
		case DSERR_ALLOCATED:
			//busy driver
			//MessageBox(NULL,"driver is busy","coucou",MB_OK);
			ret=snd_erreur_init(SND_ERREUR_INIT_SNDDRIVERBUSY);
			switch (ret)
			{
			case INIT_FAILED:
				return NULL;
				break;
			case INIT_RETRY:
				break;
			case INIT_IGNORE:
				ok=FALSE;
				out=TRUE;
				break;
			default:
				MSND_fn_vDisplayError(E_uwSndResourceMissing,"");
				ok=FALSE;
				out=TRUE;
				break;
			}
			break;
		default:
			if (!bA3DDisable)
			{//a3dcreate failled -> try with DirectSound
				bA3DDisable=TRUE;
			}
			else
			{
				//unexpected error
				GetErrorDSound(res,texte,sizeof(texte));
				MSND_fn_vDisplayError(E_uwSndSystemError,texte);
				//DisplayErrorDSound(res);
				ok=FALSE;
				out=TRUE;
			}
			break;
		}
	}

	if (!ok)
	{
		return NULL;
	}
	else
	{
		return lpDirectSoundObject;
	}
}

#define C_ERROR_A3DUPTODATE "You should update this file from Aureal's WEB site:WWW.A3D.COM.\n \
Game performance, and system stability may be affected by using this driver." 
#define C_ERROR_A3DUPTODATE_TITLE "Your A3D.DLL is uptodate"
#define C_ERROR_NONCERTIFIED "Please upgrade your driver from Aureal's WEB site: WWW.A3D.COM.\n \
Game performance, and system stability may be affected by using this driver."
#define C_ERROR_NONCERTIFIED_TITLE "Your A3D driver is not certified from Aureal."
#define C_ERROR_UNKNOWNCERTIFIED "Your driver may be a A3D clone.\n \
Be aware that using non certified A3D driver will affect game performance and system stability. \
You can check at www.a3d.com for a list of certified A3D hardware."
#define C_ERROR_UNKNOWNCERTIFIED_TITLE "Your A3D driver failled A3D certification."

//general init of DSOUND object
int init_DSOUND(SND_tdstInitStruct *pInitStruct,LPGUID lpGuid,SndBool bA3DDisable)
{
	HRESULT res;
	BOOL out=FALSE,bOk=TRUE;
	int iAuthentifA3D;
	//driver_SND_OK=FALSE;

	//--creation and global initialization
	g_lpDirectSoundObject=create_A3DSOUND(lpGuid,TRUE,&g_lA3D,bA3DDisable);

	if (!g_lpDirectSoundObject)
		return C_INIT_FAILED;
	
	//check A3D driver
	if (g_lA3D==A3D_OK_OLD_DLL)
		MessageBox(NULL,C_ERROR_A3DUPTODATE,C_ERROR_A3DUPTODATE_TITLE,MB_OK);
	
	if (g_lA3D!=DS_OK)
	{//donot check A3D if DS3D was used
		switch (iAuthentifA3D=VerifyAurealA3D()) 
		{
		case FALSE:
			MessageBox(NULL,C_ERROR_NONCERTIFIED,C_ERROR_NONCERTIFIED_TITLE,MB_OK);
			break;
		case TRUE:
			break;
		default:
			MessageBox(NULL,C_ERROR_UNKNOWNCERTIFIED,C_ERROR_UNKNOWNCERTIFIED_TITLE,MB_OK);
			break;
		}
	}



	if (g_b_ChooseOutputFormat)
	{//prioritylevel ->possible choice of format
		if ((res=IDirectSound_SetCooperativeLevel(g_lpDirectSoundObject,pInitStruct->hMainWindow,
			//DSSCL_EXCLUSIVE))!=DS_OK)
			DSSCL_PRIORITY))!=DS_OK)
			bOk = FALSE;
	}
	else
	{//non priority-level ->choice of format not possible
		if ((res=IDirectSound_SetCooperativeLevel(g_lpDirectSoundObject,pInitStruct->hMainWindow,DSSCL_NORMAL))!=DS_OK)
			bOk = FALSE;
	}

	if (bOk) 
	{
		return C_INIT_OK;
	}
	else
	{
		desinit_DSOUND(&g_lpDirectSoundObject); //free DirectSound object
		return C_INIT_FAILED;
	}
}



//-------callback enumGuid
BOOL CALLBACK GUID_enum_callback(GUID FAR * lpGuid,LPSTR lpstrDescription,LPSTR lpstrModule,LPVOID lpContext)
{
	HRESULT res;
	char texte[80];
	DSCAPS	DSCapsInfos;
	long	lA3D=-3;

	if (nb_GUIDSound)
		pGUIDAvailable=SND_fn_pvReAllocSnd(pGUIDAvailable,sizeof(stGUIDAvailable)*(nb_GUIDSound+1));
	else
		pGUIDAvailable=SND_fn_pvMallocSnd(sizeof(stGUIDAvailable));

	if (pGUIDAvailable==NULL) return FALSE; //precaution

	if (lpGuid!=NULL)
	{
		memcpy(&pGUIDAvailable[nb_GUIDSound].Guid,lpGuid,sizeof(GUID));
		pGUIDAvailable[nb_GUIDSound].bGuidNULL=FALSE;
	}
	else
		pGUIDAvailable[nb_GUIDSound].bGuidNULL=TRUE;
	strncpy(pGUIDAvailable[nb_GUIDSound].description,lpstrDescription,sizeof(pGUIDAvailable[nb_GUIDSound].description));
	strncpy(pGUIDAvailable[nb_GUIDSound].module,lpstrModule,sizeof(pGUIDAvailable[nb_GUIDSound].module));

	//evaluation of GUID
	g_lpDirectSoundObject=create_A3DSOUND(
		pGUIDAvailable[nb_GUIDSound].bGuidNULL ? NULL : &pGUIDAvailable[nb_GUIDSound].Guid
		,FALSE
		,&lA3D
		,FALSE);

	if (g_lpDirectSoundObject)
	{		
		pGUIDAvailable[nb_GUIDSound].bA3D=(lA3D==A3D_OK) || (lA3D==A3D_OK_OLD_DLL);

		if (pGUIDAvailable[nb_GUIDSound].bA3D)
		{//check A3D certification
			pGUIDAvailable[nb_GUIDSound].bA3Dold=(lA3D==A3D_OK_OLD_DLL);
			pGUIDAvailable[nb_GUIDSound].iA3DCertified=VerifyAurealA3D();
		}

		memset(&DSCapsInfos,0,sizeof(DSCapsInfos));
		DSCapsInfos.dwSize=sizeof(DSCapsInfos);
		if ((res=IDirectSound_GetCaps(g_lpDirectSoundObject,&DSCapsInfos))!=DS_OK)
		{
			GetErrorDSound(res,texte,sizeof(texte));
			MSND_fn_vDisplayError(E_uwSndSystemError,texte);
			memset(&pGUIDAvailable[nb_GUIDSound].caps,0,sizeof(DSCapsInfos));
		}
		
		if (res==DS_OK)
		{
		#ifdef SIMU_CRITICAL_BUFF_NB //simulate allocated buffers for test
			//DSCapsInfos.dwFreeHwMixingStaticBuffers-=SIMU_BUFF_NB;
			//if ((long int)DSCapsInfos.dwFreeHwMixingStaticBuffers<0)
			//	DSCapsInfos.dwFreeHwMixingStaticBuffers=0;
			DSCapsInfos.dwMaxHw3DStaticBuffers-=SIMU_BUFF_NB;
			if ((long int)DSCapsInfos.dwMaxHw3DStaticBuffers<0)
				DSCapsInfos.dwMaxHw3DStaticBuffers=0;
			
		#endif
			//store capabilities
			memcpy(&pGUIDAvailable[nb_GUIDSound].caps,&DSCapsInfos,sizeof(DSCapsInfos));
		}		
		desinit_DSOUND(&g_lpDirectSoundObject);
	}
	else
	{//unavailable driver 
		memset(&pGUIDAvailable[nb_GUIDSound].caps,0,sizeof(DSCapsInfos));
	}

	nb_GUIDSound++;

	return TRUE;
}

//------- choix du GUID pour DirectSound
void init_listGUID(void)
{
//        int context=0;
	HRESULT res;
	char texte[80];
	DWORD context=0;

	if (nb_GUIDSound)
	{
		SND_fn_vFreeSnd(pGUIDAvailable);
		pGUIDAvailable=NULL; //precaution
		nb_GUIDSound=0;
	}

	if ((res=DirectSoundEnumerate((LPDSENUMCALLBACK)&GUID_enum_callback,&context))!=DS_OK)
	{
		GetErrorDSound(res,texte,sizeof(texte));
		MSND_fn_vDisplayError(E_uwSndSystemError,texte);
	}
}

void destroy_listGUID()
{
	if (nb_GUIDSound)
	{
		SND_fn_vFreeSnd(pGUIDAvailable);
		pGUIDAvailable=NULL; //precaution
		nb_GUIDSound=0;
	}
}

int M_EVAL_A3D(i)
{
	if (i==-1)
		return -1;
	else
		return (pGUIDAvailable[i].bA3Dold ? 0 : 1)
				+ ((pGUIDAvailable[i].iA3DCertified==TRUE) ? 8 
					: ((pGUIDAvailable[i].iA3DCertified!=FALSE) ? 4 : 0))
				+ (pGUIDAvailable[i].bA3D ? 16 : 0);
}

//returns the lpGUID of the best driver + returns its name
//Param:
//	description(OUT):name of the best GUID
//	size(IN): number of bytes to be copied in description
//	pbA3DDisable(OUT):TRUE is A3D should be disabled for best result
LPGUID evaluate_bestGUID(char* description,int size,SndBool *pbA3DDisable)
{
#define DSCAPS_FLAG1 DSCAPS_CERTIFIED+DSCAPS_SECONDARY16BIT+DSCAPS_SECONDARYMONO+DSCAPS_SECONDARYSTEREO 
	int i,best;
	DWORD dwFreeHw3DAllBuffers=0;

	if (pGUIDAvailable==NULL) return NULL; //precaution
	
	best=-1;

	for (i=0;i<nb_GUIDSound;i++)
	{//search for the one with the hardware with greatest number of accelerated 3D voice hardware buffers
		if ((pGUIDAvailable[i].caps.dwSize!=0)
			&& (pGUIDAvailable[i].caps.dwFlags & DSCAPS_FLAG1)
			&& !(pGUIDAvailable[i].caps.dwFlags & DSCAPS_EMULDRIVER)
			&& (pGUIDAvailable[i].caps.dwFreeHw3DAllBuffers>=dwFreeHw3DAllBuffers))
		{//sound-card with the same number or more voices
			//best sound-card is the most A3D convenient
			if ((pGUIDAvailable[i].caps.dwFreeHw3DAllBuffers>dwFreeHw3DAllBuffers)
				|| (M_EVAL_A3D(i)>M_EVAL_A3D(best)))
			{
				best=i;
				dwFreeHw3DAllBuffers=pGUIDAvailable[best].caps.dwFreeHw3DAllBuffers;
			}
		}
		//default GUID (NULL) is the less worst
		if ((best==-1) && pGUIDAvailable[i].bGuidNULL)
			best=i;
	}

	if (best>-1)
	{
		//should we use A3D ?
		if ((pGUIDAvailable[best].bA3D) && (pGUIDAvailable[best].iA3DCertified==FALSE))
		{//NO
			MSND_fn_vDisplayError(E_uwSndSystemError,"Since your A3D driver is not certified,A3D capabilities have been disabled.\nHowever, you can re-enable A3D driver by using Sound3D-Setup application");
			*pbA3DDisable=TRUE;
		}

		strncpy(description,pGUIDAvailable[best].description,size);
		return (pGUIDAvailable[best].bGuidNULL ? NULL : &pGUIDAvailable[best].Guid);
	}

	*pbA3DDisable=FALSE;
	*description=0;
	return NULL;
}

//search a GUID from its description
LPGUID find_GUID(char* GUID_wanted,char* GUID_choosen,int size,SndBool *pbA3DDisable)
{
	int i;

	if (pGUIDAvailable==NULL) return NULL; //precaution

	if (GUID_wanted) //precaution
	//search the good GUID
	if (GUID_wanted[0]!=0)
	{//INI OK?
		for (i=0;i<nb_GUIDSound;i++)
		{
			if (!strcmp(pGUIDAvailable[i].description,GUID_wanted))
			{//found
				strncpy(GUID_choosen,GUID_wanted,size);
				return (pGUIDAvailable[i].bGuidNULL ? NULL : &pGUIDAvailable[i].Guid);
			}
		}
	}
	
	//not found
	return evaluate_bestGUID(GUID_choosen,size,pbA3DDisable);
}

void get_keyvalue(char *section,DWORD dwPlatformId)
{
	char texte[64];

	strcpy(section,DX3D_GUID_KEY);

	switch (dwPlatformId)
	{
	case VER_PLATFORM_WIN32_WINDOWS:
		strcat(section,"Win95");
		break;
	case VER_PLATFORM_WIN32_NT:
		strcat(section,"WinNT");
		break;
	default:
		sprintf(texte,"%d",dwPlatformId);
		strcat(section,texte);
		break;
	}
}

void SND_fn_vSetWantedGUID(char* GUID_choosen,SndBool bDisactiveA3D)
{
	char section[128];
	OSVERSIONINFO info;

	memset(&info,0,sizeof(OSVERSIONINFO));
	info.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
	GetVersionEx(&info);
	//sprintf(section,DX3D_GUID_KEY,info.dwPlatformId);
	get_keyvalue(section,info.dwPlatformId);

	if (SND_fn_bCanSetOptions())
	{
		SND_fn_vSetOptions(DX3D_SECTION,section,GUID_choosen);
		if (bDisactiveA3D)
			SND_fn_vSetOptions(DX3D_SECTION,DX3D_DISABLE_A3D,DX3D_DISABLE_A3D_YES);
		else
			SND_fn_vSetOptions(DX3D_SECTION,DX3D_DISABLE_A3D,DX3D_DISABLE_A3D_NO);
	}
	else
	{
		WritePrivateProfileString(DX3D_SECTION,section,GUID_choosen,INI_FILENAME);
		if (bDisactiveA3D)
			WritePrivateProfileString(DX3D_SECTION,DX3D_DISABLE_A3D,DX3D_DISABLE_A3D_YES,INI_FILENAME);
		else
			WritePrivateProfileString(DX3D_SECTION,DX3D_DISABLE_A3D,DX3D_DISABLE_A3D_NO,INI_FILENAME);
	}
}

//extract desired GUID from INI file
void SND_CALL SND_fn_vGetWantedGUID(char* GUID_wanted,int size,SndBool *pbA3DDisable)
{
	char section[128];
	OSVERSIONINFO info;

	*pbA3DDisable=FALSE;

	memset(&info,0,sizeof(OSVERSIONINFO));
	info.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
	GetVersionEx(&info);
	//sprintf(section,DX3D_GUID_KEY,info.dwPlatformId);
	get_keyvalue(section,info.dwPlatformId);

	//extraction du fichier INI
	if (SND_fn_bCanSetOptions())
	{
		if (!SND_fn_bGetOptions(DX3D_SECTION,section,GUID_wanted,size))
		{//section inexistante
			GUID_wanted[0]=0;
		}

		if (SND_fn_bGetOptions(DX3D_SECTION,DX3D_DISABLE_A3D,section,sizeof(section)))
			if (!strcmp(section,DX3D_DISABLE_A3D_YES))
				*pbA3DDisable=TRUE;
	}
	else
	{
		if (!GetPrivateProfileString(DX3D_SECTION,section,"NULL",GUID_wanted,size,INI_FILENAME))
		{//section inexistante
			GUID_wanted[0]=0;			
		}

		if (GetPrivateProfileString(DX3D_SECTION,DX3D_DISABLE_A3D,"NO",section,sizeof(section),INI_FILENAME))
			if (!strcmp(section,DX3D_DISABLE_A3D_YES))
				*pbA3DDisable=TRUE;
	}
}

//automatically choose GUID
LPGUID auto_chooseGUID(SndBool *pbA3DDisable)
{
	char GUID_wanted[128],GUID_choosen[128];
	LPGUID lpGuid;

	SND_fn_vGetWantedGUID(GUID_wanted,sizeof(GUID_wanted),pbA3DDisable);

	//search for good GUID
	lpGuid=find_GUID(GUID_wanted,GUID_choosen,sizeof(GUID_choosen),pbA3DDisable);
	if ((GUID_wanted[0]!=0) && !strcmp(GUID_choosen,GUID_wanted))
		return lpGuid;

	//update INI file (INI file is invalide->update it
	SND_fn_vSetWantedGUID(GUID_choosen,*pbA3DDisable);

	return lpGuid; 
}

int init_DSoundChosen(SND_tdstInitStruct *pInitStructSaved)
{	
	lpGuidChosen=auto_chooseGUID(&g_bA3DDisable);
	
	if (init_DSOUND(pInitStructSaved,lpGuidChosen,g_bA3DDisable)==C_INIT_FAILED)
	{
		destroy_listGUID();
		return C_INIT_FAILED;
	}
	return C_INIT_OK;
}

//export liste of GUIDs; returns number of GUIDs
int SND_CALL SND_fn_iGetGUIDList(pstGUIDAvailable lpGuidDescrption,int size)
{
	int size1=nb_GUIDSound*sizeof(stGUIDAvailable);

	if ((pGUIDAvailable==NULL)||(size<=0)||(lpGuidDescrption==NULL)) return 0;
	if (size<=size1)
	{		
		memcpy(lpGuidDescrption,pGUIDAvailable,size);
	}
	else
	{
		memcpy(lpGuidDescrption,pGUIDAvailable,size1);
	}
	
	return nb_GUIDSound;
}

//obtain the current GUID
void SND_CALL  SND_fn_vGetCurrentGUID(char* description,int size,SndBool *pbA3DDisable)
{
	int i;

	strncpy(description,"None",size);

	for (i=0;i<nb_GUIDSound;i++)
	{
		if ((pGUIDAvailable[i].bGuidNULL) && (lpGuidChosen==NULL)
			|| (!pGUIDAvailable[i].bGuidNULL && (lpGuidChosen==&pGUIDAvailable[i].Guid)))
		{//found
			strncpy(description,pGUIDAvailable[i].description,size);
			return;
		}
	}
	*pbA3DDisable=g_bA3DDisable;
}

//save the desired (checked) GUID without modifying the current GUID
//Param:
//	wanted (IN): description of GUID wished to be used
//	selected (OUT): description of GUID effectively used (may differ of wanted when wanted is invalid)
//	sizs (IN): maximum bytes number to be copied in selected
//	bA3DDisable: TRUE is A3D should be disabled
void SND_CALL SND_fn_vChangeWantedGUID(char* wanted,char* selected,int size,SndBool _bA3DDisable)
{
	LPGUID lpGuid;
	SndBool bA3DDisable=_bA3DDisable;

	lpGuid=find_GUID(wanted,selected,size,&bA3DDisable);

	SND_fn_vSetWantedGUID(selected,bA3DDisable);
}

//save the current (checked) GUID without modifying the INI file
void SND_CALL SND_fn_vChangeCurrentGUID(char* wanted,char* selected,int size,SndBool _bA3DDisable)
{
	LPGUID lpGuid;
	SndBool bA3DDisable=_bA3DDisable;

	lpGuid=find_GUID(wanted,selected,size,&bA3DDisable);

	desinit_DSOUND(&g_lpDirectSoundObject);

	if (init_DSOUND(&InitStructSaved,lpGuid,bA3DDisable)==C_INIT_FAILED)
	{

	};
}

static BOOL g_bD3DOptimum=FALSE;
BOOL CALLBACK GUID_enumCallbk_test(GUID FAR * lpGuid,LPSTR lpstrDescription,LPSTR lpstrModule,LPVOID lpContext)
{
	HRESULT res;
	char texte[80];
	DSCAPS	DSCapsInfos;
	stGUIDAvailable availGUI;
	LPDIRECTSOUND lpTestDirectSoundObject;
	long	lA3D;

	if (g_bD3DOptimum==TRUE) return TRUE;

	if (lpGuid!=NULL)
	{
		memcpy(&availGUI.Guid,lpGuid,sizeof(GUID));
		availGUI.bGuidNULL=FALSE;
	}
	else
		availGUI.bGuidNULL=TRUE;

	strncpy(availGUI.description,lpstrDescription,sizeof(availGUI.description));
	strncpy(availGUI.module,lpstrModule,sizeof(availGUI.module));

	//evaluation of GUID
	lpTestDirectSoundObject=create_A3DSOUND(
		availGUI.bGuidNULL ? NULL : &availGUI.Guid
		,FALSE
		,&lA3D
		,FALSE);

	if (lpTestDirectSoundObject)
	{	
		memset(&DSCapsInfos,0,sizeof(DSCapsInfos));
		DSCapsInfos.dwSize=sizeof(DSCapsInfos);
		if ((res=IDirectSound_GetCaps(lpTestDirectSoundObject,&DSCapsInfos))!=DS_OK)
		{
			GetErrorDSound(res,texte,sizeof(texte));
			MSND_fn_vDisplayError(E_uwSndSystemError,texte);
			memset(&availGUI.caps,0,sizeof(DSCapsInfos));
		}
		
		if (res==DS_OK)
		{		
			//store capabilities
			memcpy(&availGUI.caps,&DSCapsInfos,sizeof(DSCapsInfos));
		}		
		desinit_DSOUND(&lpTestDirectSoundObject);
	}
	else
	{//unavailable driver 
		memset(&availGUI.caps,0,sizeof(DSCapsInfos));
	}

	if ((availGUI.caps.dwSize!=0)
		&& (availGUI.caps.dwFlags & DSCAPS_FLAG1)
			&& !(availGUI.caps.dwFlags & DSCAPS_EMULDRIVER)
					//we'll face difficulties if the number of buffers is too low
			//&& (availGUI.caps.dwFreeHw3DAllBuffers>1)			    
			&& 
			
			((availGUI.caps.dwMaxHw3DStaticBuffers>2)
			||(availGUI.caps.dwMaxHw3DStreamingBuffers>2))

			)
		{//one of the GUIDs may be fine		    			
			g_bD3DOptimum=TRUE;
		}

	return TRUE;
}

/************************************************************************
*	ROLE: Evaluate D3D DLL
*	Returns TRUE if at least one GUID has 3D hardware
************************************************************************/
SndBool SND_CALL SND_fn_bIsDirect3DOptimum()
{
	char texte[80];
	DWORD context=0;
	HRESULT res;

	g_bD3DOptimum=FALSE;
	if ((res=DirectSoundEnumerate((LPDSENUMCALLBACK)&GUID_enumCallbk_test,&context))!=DS_OK)
	{
			GetErrorDSound(res,texte,sizeof(texte));
			MSND_fn_vDisplayError(E_uwSndSystemError,texte);
			return FALSE;
	}
	return g_bD3DOptimum;
	
}

///////////////////////////////////////////////////////
// NAME: fn_lGetOptimumVoiceNumber
//
// ROLE: returns an estimated optimal value for SNDVIRT's number of voices
// DATE: 25/03/98
// AUTHOR: Joel Vibert
///////////////////////////////////////////////////////
long fn_lGetOptimumVoiceNumber()
{
	int i;
	DWORD max,max1;
	BOOL bFound=FALSE;
	HRESULT result;
	DSCAPS  dscaps;

	if (pGUIDAvailable==NULL) return 0;

	for (i=0;i<nb_GUIDSound;i++)
	{
		if ((pGUIDAvailable[i].bGuidNULL) && (lpGuidChosen==NULL)
			|| (!pGUIDAvailable[i].bGuidNULL && (lpGuidChosen==&pGUIDAvailable[i].Guid)))
		{//found
			bFound=TRUE;
			break;
		}
	}//for

    //no available piece of information=>default value

	if (!bFound) max1=8; //default value
	else 
	{
		if (pGUIDAvailable[i].caps.dwMaxHw3DStaticBuffers>0)
			max1=pGUIDAvailable[i].caps.dwMaxHw3DStaticBuffers;
		else if (pGUIDAvailable[i].caps.dwMaxHw3DStreamingBuffers>0)
			max1=pGUIDAvailable[i].caps.dwMaxHw3DStreamingBuffers;
			else
				return 8; //default value (emulated)

		if (max1<=0)  //2D card=>emulation
		{
			return 8; //with 8 virtual voices
		}
	}

	max=max1;

	if (lpDSPrimBuffer!=NULL)
	{
		dscaps.dwSize=sizeof(DSCAPS);
		result=IDirectSound_GetCaps(g_lpDirectSoundObject,&dscaps);
		if (result!=DS_OK)
		{
		} else //result OK
		{
			if (dscaps.dwFreeHw3DStaticBuffers>0)
			{
#ifdef SIMU_CRITICAL_BUFF_NB
				dscaps.dwFreeHw3DStaticBuffers-=SIMU_BUFF_NB;
				if ((long int)dscaps.dwFreeHw3DStaticBuffers<0) dscaps.dwFreeHw3DStaticBuffers=0;
#endif
				max=dscaps.dwFreeHw3DStaticBuffers;
			} else
			if (dscaps.dwFreeHw3DStreamingBuffers>0)
			{
				max=dscaps.dwFreeHw3DStreamingBuffers;
			} 
			else return 8;  //default value (emulated)
		} //result OK

	}//primary buffer!=NULL

//NB: the format of the primary buffer may change the maximum number of available buffers
// (ex: MonsterSound at 44 kHz=>5  buffers
//                   at 22 kHz=>10 buffers)
	
	//returns the maximum number of virtual voices
	//with a safety margin that grows as a function of this number
	//(to avoid too many computations when table is full+pb with GetCaps on MonsterSound)
	//if (max<= 1) return 0; //precaution

	//// with a margin of 1 buffer for alloc , some sounds may be emulated
	//// with a margin of 2 buffers for alloc, no emulation, but sometimes lack of buffer
	//// if the margin here is unsufficient

	//changed 10th Feb 1999 in case this function is called
	//although buffers have already been created (few buffers available)
	// [important if number of buffers is critical]

	if (max1>=5)
	{
		//value based on max number of buffers
		if (max1>8)
		{
			max=max1*4/5; //little safety margin
		}
		else if (max1>5) 
		{
			max=5;
		}
		else 
		{
			max=max1;
		}
	}
	else if ((max<=max1)&&(max1>0)) 
	{
		return max1; //card with very few buffers (possible?)
	}
	else 
	{
		//getcaps obviously returned bad values
		if ((max<=0)||(max>8))
			max=8;
	}
	
	
/*
	if (max==1)  return 1; //possible?
	if (max<=5)  return max-1;
	max=max*4/5;
*/
	

	if (max>24) return 24;
	return max;

	//if (max<=11) return max-2; //optimal for MonsterSound (can always find available buffers in F1 game)	
	//if (max<=21) return max-3;
	//if (max<=29) return max-5;
	//return 24;  //32=maximum number of playing buffers on MaxiSound
	            // (much lower than maximum number of buffers: 247)
	            // it seems that it can only be determined because a play failed
	            // (it's too late for us because the number of voices cannot be adjusted
				//  dynamically yet)
}



//parameterizing of Listener Configuration
void SND_CALL SND_fn_vChangeWantedSpeakerConfigurationSxdWave(DWORD config,DWORD geometry)
{
	char texte[128];

	texte[0]=0;

	switch (config)
	{
	case DSSPEAKER_HEADPHONE:
		sprintf(texte,"HeadPhone");
		break;
	case DSSPEAKER_MONO:
		sprintf(texte,"Monoaural");
		break;
	case DSSPEAKER_QUAD:
		sprintf(texte,"Quadraphonic");
		break;
	case DSSPEAKER_SURROUND:
		sprintf(texte,"Surround");
		break;
	case DSSPEAKER_STEREO:
		switch (geometry)
		{
		case DSSPEAKER_GEOMETRY_WIDE:
			sprintf(texte,"Wide Stereo");
			break;
		case DSSPEAKER_GEOMETRY_NARROW:
			sprintf(texte,"Narrow Stereo");
			break;
		case DSSPEAKER_GEOMETRY_MIN:
			sprintf(texte,"Min Stereo");
			break;
		case DSSPEAKER_GEOMETRY_MAX:
			sprintf(texte,"Max Stereo");
			break;
		default:
			sprintf(texte,"Stereo");
			break;
		}
		break;
	}

	if (texte[0])
	{
		if (SND_fn_bCanSetOptions())
			SND_fn_vSetOptions(DX3D_SECTION,DX3D_SPEAKERS_KEY,texte);
		else
			WritePrivateProfileString(DX3D_SECTION,DX3D_SPEAKERS_KEY,texte,INI_FILENAME);
	}
}

SndBool SND_CALL SND_fn_bChangeCurrentSpeakerConfigurationSxdWave(DWORD config,DWORD geometry)
{
	DWORD param;

	if (g_lpDirectSoundObject)
	{
		if (config==DSSPEAKER_STEREO)
			param=DSSPEAKER_COMBINED(config,geometry);
		else
			param=config;

		if (IDirectSound_SetSpeakerConfig(g_lpDirectSoundObject,param)==DS_OK)
			return TRUE;
		else
		
			return FALSE;
	}

	return FALSE;
}

SndBool SND_CALL SND_fn_bGetWantedSpeakerConfigurationSxdWave(DWORD* config,DWORD* geometry)
{
	char texte[128];

	texte[0]=0;

	if (SND_fn_bCanSetOptions())
	{
		if (!SND_fn_bGetOptions(DX3D_SECTION,DX3D_SPEAKERS_KEY,texte,sizeof(texte)))
			texte[0]=0;
	}	
	else
	{
		if (!GetPrivateProfileString(DX3D_SECTION,DX3D_SPEAKERS_KEY,"",texte,sizeof(texte),INI_FILENAME))
			texte[0]=0;
	}

	if (!strcmp(texte,"HeadPhone"))
	{
		*config=DSSPEAKER_HEADPHONE;
		*geometry=0;
	}
	else
	if (!strcmp(texte,"Monoaural"))
	{
		*config=DSSPEAKER_MONO;
		*geometry=0;
	}
	else
	if (!strcmp(texte,"Quadraphonic"))
	{
		*config=DSSPEAKER_QUAD;
		*geometry=0;
	}
	else
	if (!strcmp(texte,"Quadraphonic"))
	{
		*config=DSSPEAKER_QUAD;
		*geometry=0;
	}
	else
	if (!strcmp(texte,"Surround"))
	{
		*config=DSSPEAKER_SURROUND;
		*geometry=0;
	}
	else
	if (!strcmp(texte,"Stereo"))
	{
		*config=DSSPEAKER_STEREO;
		*geometry=0;
	}
	else
	if (!strcmp(texte,"Wide Stereo"))
	{
		*config=DSSPEAKER_STEREO;
		*geometry=DSSPEAKER_GEOMETRY_WIDE;
	}
	else
	if (!strcmp(texte,"Narrow Stereo"))
	{
		*config=DSSPEAKER_STEREO;
		*geometry=DSSPEAKER_GEOMETRY_NARROW;
	}
	else
	if (!strcmp(texte,"Min Stereo"))
	{
		*config=DSSPEAKER_STEREO;
		*geometry=DSSPEAKER_GEOMETRY_MIN;
	}
	else
	if (!strcmp(texte,"Max Stereo"))
	{
		*config=DSSPEAKER_STEREO;
		*geometry=DSSPEAKER_GEOMETRY_MAX;
	}
	else
		return FALSE;

	return TRUE;
}

SndBool SND_CALL SND_fn_bGetCurrentSpeakerConfigurationSxdWave(DWORD* config,DWORD* geometry)
{
	DWORD param;

	if (g_lpDirectSoundObject)
	{
		IDirectSound_GetSpeakerConfig(g_lpDirectSoundObject,&param);
		
		*config=DSSPEAKER_CONFIG(param);
		if (*config==DSSPEAKER_STEREO)
			*geometry=DSSPEAKER_GEOMETRY(param);
		else
			*geometry=0;

		return TRUE;
	}
	else
	
		return FALSE;
}

SndBool SND_fn_bCheckVersionResourceSxdWave(tdstBlockResourceDisk* disk)
{
	return TRUE;
}

///////////////////////////////////////////////////////
// NAME: SND_fn_vSetNbVoiceWishedSxdWave
//
// ROLE: modify value for number of voices stored in INI file
// AUTHOR: Stéphane Ronse
// MODIFICATIONS:
//    25/03/98/ Joël Vibert / default value: estimated optimal value instead of constant
//							  + value checked
///////////////////////////////////////////////////////
void SND_fn_vSetNbVoiceWishedSxdWave(long nb)
{
	char texte[256];

	sprintf(texte,"%d",nb);

	if (SND_fn_bCanSetOptions())
		SND_fn_vSetOptions(DX3D_SECTION,DX3D_NBVOICES_KEY,texte);
	else
		WritePrivateProfileString(DX3D_SECTION,DX3D_NBVOICES_KEY,texte,INI_FILENAME);

	//do not modify effective number of voice ! risk of bug; please, call 2 functions to modify both effecti and wished numbers of voices
	//SND_fn_vSetNbVoiceWishedSxd(SAMPLE_ADPCM,nb);
}

///////////////////////////////////////////////////////
// NAME: SND_fn_lGetNbVoiceWishedSxdWave
//
// ROLE: returns value for SNDVIRT's number of voices
//       (checked value found in .ini or estimated optimal value)
// AUTHOR: Stéphane Ronse
// MODIFICATIONS:
//    25/03/98/ Joël Vibert / default value: estimated optimal value instead of constant
//							  + value checked
///////////////////////////////////////////////////////
long SND_fn_lGetNbVoiceWishedSxdWave()
{
	char texte[256];
	char defaultNbText[10];
	long nb,nbOptim;

	if (SND_fn_bCanSetOptions())
	{
		if (SND_fn_bGetOptions(DX3D_SECTION,DX3D_NBVOICES_KEY,texte,sizeof(texte)))
			sscanf(texte,"%d",&nb);
		else
		{
			nb=fn_lGetOptimumVoiceNumber();
			if (nb!=0)
				SND_fn_vSetNbVoiceWishedSxdWave(nb);
		}
	}
	else
	{
		nbOptim=fn_lGetOptimumVoiceNumber();
		sprintf(defaultNbText,"%d",nbOptim);

		if (GetPrivateProfileString(DX3D_SECTION,DX3D_NBVOICES_KEY,defaultNbText,texte,sizeof(texte),INI_FILENAME))
			sscanf(texte,"%d",&nb);
		else
		{
			nb=nbOptim;
			if (nb!=0)
				SND_fn_vSetNbVoiceWishedSxdWave(nb);
		}
	}

//precautions (in case the values in the .ini file are absurd)
	if (nb<0) nb=0;
	if (nb>NB_VOICES) nb=NB_VOICES;

	return nb;
}

///////////////////////////////////////////////////////
// NAME: SND_fn_lGetNbVoiceOptimumSxdWave
//
// ROLE: returns best value for SNDVIRT's number of voices
//       (always estimated optimal value)
// AUTHOR: Stéphane Ronse
// MODIFICATIONS:
///////////////////////////////////////////////////////
long SND_fn_lGetNbVoiceOptimumSxdWave()
{
	return fn_lGetOptimumVoiceNumber();
}

