/*

MODULE DE GESTION DU MIDI
pour WIN95 en mode MCI   

   UBI SOUND STUDIOS   
   Olivier Amiaud      

*/

/*****************************************************************************
	Prototype de la fonction de gestion d'erreur
	void SND_fn_vDisplayError(unsigned short uwErrNum,char *message)

  Liste des erreurs
  -----------------
    
  E_uwSndMIDILoadTrack
  E_uwSndMIDIStatus
  E_uwSndMIDISet
  E_uwSndMIDIPlay
  E_uwSndMIDIStop
  E_uwSndMIDINotifyFailure
  E_uwSndMIDINotifyAborted
  E_uwSndMIDINotifySuperseded
  E_uwSndMIDICreateThread
  E_uwSndMIDIOpen
  E_uwSndMIDIClose
  E_uwSndMIDIFade
  E_uwSndMIDINbTracks
  E_uwSndMIDIAllTracksUsed

******************************************************************************/

#include <windows.h>
#include <stdio.h>

#include "SNDinc.h"

#include "sndres.h"
#include "sndwin95.h"
#include "sndmidi.h"

#if defined(_DLL_COMPILATION_MODE) 
#include  "SNDDLL.h"
#endif
#include "sndthrd.h"

//#define MIDI_MCI

#ifndef DISABLE_MIDI

// Gestion du mixer midi, volume , muet....

static MIXERCAPS   mixerCapsMidi;
static HMIXER      hMixerMidi;
static UINT        mixerIdentifierMidi;
static UINT        uiNbrMixersMidi;
static DWORD       dwNbrAudioLinesMidi;

static MIXERLINE   mixerLineMidi;
static MIXERLINECONTROLS mixerLineControlsMidi;
static MIXERCONTROL    mixerControlMidi;
static MIXERCONTROLDETAILS mixerControlDetailsMidi;
static MIXERCONTROLDETAILS_UNSIGNED valeurMidi;
static BOOL MidiControlAvailable = FALSE;

// Utilitaires de transformation d'unité de temps

#define SND_MAKE_TMSF(t,m,s,f)	((DWORD)t)|(((DWORD)m)<<8)|(((DWORD)s)<<16)|(((DWORD)f)<<24)
#define SND_TMSF_T(tmsf)		((BYTE)(tmsf))
#define SND_TMSF_M(tmsf)		((BYTE)(((WORD)(tmsf)) >> 8))
#define SND_TMSF_S(tmsf)		((BYTE)((tmsf)>>16))
#define SND_TMSF_F(tmsf)		((BYTE)((tmsf)>>24))

#define SND_TMSF_MS(tmsf)	\
((((((((tmsf & 0xff00) >> 8) * 60)	+	((tmsf & 0xff0000) >> 16))	* 75)	+	((tmsf & 0xff000000)>>24)) * 1000) / 75)

//(DWORD)	(((((((DWORD)tmsf>>8)&0xff) * 60)+(((DWORD)tmsf>>16)&0xff)) * 75)+(((DWORD)tmsf>>24)&0xff))		

#define MSG_ERR_MIDI						"Midi Device Error"
#define MSG_ERR_MIDI_FILE					"Midi File Error"

#define NB_MAX_MIDI_TRACKS				50
//#define VOL_MAX_MIDI							60

#define OFFSET_MIDI_VOICE					0x2000
#define MASK_OFFSET_MIDI_VOICE		0x0FFF
#define NO_MIDI_VOICE							0x1000
#define MIDI_UNAVAILABLE					0x0008

#define FADE_AND_STOP_MIDI			                      12

//#ifdef MIDI_MCI

//	VARIABLES GLOBALES EXTERNES
//	TYPEDEF LOCAUX 	

//typedef struct _tdstMidiTrack
//{
//	tduRefRes uMidiTrack;
//	MCIDEVICEID uiMidiDeviceId;
//	unsigned long ulLength;
//	unsigned long ulNbLoops;
//	unsigned long ulNb;
//}
//tdstMidiTrack;

typedef struct _tdstActiveMidiTrack
{
	SndBool bActive;
	SndBool bPaused;
	SndBool bLoop;
	MCIDEVICEID uiMidiDeviceId;
	unsigned long ulLength;
	unsigned long ulNbLoops;
	unsigned long ulTrackLengthInMs;
	long lVoice;
	long lIndexVoice;

	SoundParam * pstSoundParam;
	long lPrio;
	SND_td_pfn_vSoundCallback pfnSoundCallback;
	long lParSoundCallback;
}tdstActiveMidiTrack;

typedef struct stMidiDriver
{
	SndBool bMidiDriverOk;
	int iLastMidiVolume;
}tdstMidiDriver;


// -------------------------------------------
// VARIABLES CONCERNANT LA GESTION DES THREADS


UINT  SND_PLAYMIDI_MSG;
UINT  SND_STOPMIDI_MSG;
UINT  SND_LOADMIDI_MSG;
UINT  SND_UNLOADMIDI_MSG;
UINT  SND_PAUSEMIDI_MSG;
UINT  SND_RESUMEMIDI_MSG;
UINT  SND_NEXTTRACKMIDI_MSG;
UINT  SND_DESINITMIDI_MSG;
UINT  SND_RELEASEMIDI_MSG;
UINT  SND_RESTOREMIDI_MSG;
static DWORD dw_a5_Params[5];

//	VARIABLES GLOBALES LOCALES

static SndBool bMidiFadeIn = FALSE;
static UINT uiTimerMidiFadeIn;									// Gestion du fade-in
static unsigned long ulCountDownFadeInMidi;
static unsigned long ulTimerPeriodFadeInMidi;
static unsigned long ulFadeInDurationMidi; 
static long lNbIterFadeInMidi;

static SndBool bMidiFadeOut= FALSE;
static UINT uiTimerMidiFadeOut;								// Gestion du fade-out
static unsigned long ulCountDownFadeOutMidi;
static unsigned long ulTimerPeriodFadeOutMidi;
static unsigned long ulFadeOutDurationMidi; 
static long lNbIterFadeOutMidi;

static unsigned char ucFacteurVolumeMidi;				// Volume global Midi

static MCIDEVICEID NO_MIDI_DEVICE  = 127;

// Pistes Midi du jeu et piste active
static tduRefRes stMidiTracks[NB_MAX_MIDI_TRACKS];

//tdstMidiTrack stMidiTracks[NB_MAX_MIDI_TRACKS];
static tdstActiveMidiTrack stActiveMidiTrack;

static tdstMidiDriver stMidiDriver;					// Midi driver
static int iNbTracks;

static char StrMidiDirectory[256];
static char texte_erreur_Midi[256];


// -------------------------------------------
// HANDLE ET ID DE THREADS ET FENETRES


static HWND hSndMidiWnd;// handle de la fenetre de gestion des messages
static DWORD dwMidiThreadId;
static HANDLE hMidiThread=NULL;	
static DWORD dw_a2_threadParams[2];
static HANDLE hMidiThreadCallBack;
static DWORD dwMidiThreadIdCallBack;


int		SND_fn_iInitMidiThread(void);
void	SND_fn_vDesInitMidiThread(void);
SndBool SND_fn_bLoadMidiTrackThread(tdstBlockResourceMem * pstPtr,char *czFileName);
SndBool SND_fn_bUnloadMidiTrackThread(long lVoice);
long	SND_fn_lPlayMidiThread(tduRefRes res,SoundParam *par,long prio,SND_td_pfn_vSoundCallback fn_callback,long par_callback);
void	SND_fn_vStopMidiThread(long voice);
void	SND_fn_vPauseMidiThread(long voice);
void	SND_fn_vResumeMidiThread(long voice);

void SND_CALL SND_fn_vReleaseDriverMidiThread();
void SND_CALL SND_fn_vRestoreDriverMidiThread();


// -------------------------------------------
// PROTOTYPES DES FONCTIONS LOCALES

SndBool SND_fn_bLoadMidiTrack(tdstBlockResourceMem * pstPtr,char *czFileName);
SndBool SND_fn_bUnloadMidiTrack(long voice);
long SND_fn_lGetActiveTrack(void);
long SND_fn_lGetCurrentMode(void);
long SND_fn_l_GetCurrentPosition(void);
LRESULT CALLBACK SND_fn_vMidiMSG(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam);

int SND_fn_iMixerInitMidi(void);
int SND_fn_iMixerDesInitMidi(void);
DWORD SND_fn_dwSND_fn_dwGetMidiMax(void);
DWORD SND_fn_dwSND_fn_dwGetMidiMin(void);
DWORD SND_fn_dwGetMidiValue(void);
void SND_fn_vSetMidiValue(DWORD val);

#endif //DISABLE_MIDI


//-----------------------------------------------------------
//	ConvertRsvDiskToMem
//
//	But			: Convertir un BlockResourceDisk en Mem non opérationel
//	Entrées : reference au structs ressources mem et disk
//	Sorties : neant
//------------------------------------------------------------
void SND_CALL SND_fn_vConvertResDiskToMemMidi(tdstBlockResourceDisk *disk,tdstBlockResourceMem *mem,void * ptrBegin)
{
#ifndef DISABLE_MIDI
  mem->Id=disk->Id;
  mem->eType=disk->eType;
  mem->eStorage=disk->eStorage;
  mem->ucVolume=disk->ucVolume;
  mem->bIsLoaded=TRUE;
        
  //partie spécifique au type de ressource
	mem->uRes.stMidi.bStream = disk->uRes.stMidi.bStream;
	mem->uRes.stMidi.bVolable = disk->uRes.stMidi.bVolable;
	mem->uRes.stMidi.bLoop = disk->uRes.stMidi.bLoop;
	mem->uRes.stMidi.ulNbLoops = disk->uRes.stMidi.ulNbLoops;
	if (mem->uRes.stMidi.bLoop)
	{
        mem->uRes.stMidi.ulStartLoop = disk->uRes.stMidi.ulStartLoop;
        mem->uRes.stMidi.ulEndLoop = disk->uRes.stMidi.ulEndLoop;
    }
    else
	{
		mem->uRes.stMidi.ulStartLoop = mem->uRes.stMidi.ulEndLoop = 0;
	}
	if (mem->uRes.stMidi.bStream)
	{
		strcpy(mem->uRes.stMidi.uData.stStream.fichier,"TOTO.MID");
		mem->uRes.stMidi.uData.stStream.Offset = 0;
	}
	mem->uRes.stMidi.uData.stMem.uiMidiDeviceId=NO_MIDI_DEVICE;
	// seul LoadResFromdisk doit rendre la ressource opérationelle
#endif //DISABLE_MIDI
}

//-------------------------------------------------
// LoadResFromDiskMidi:rendre opérationel une ressource
//	Entrées:blockdisk à exploiter
//		blockmem à remplir (déjà alloué)
//--------------------------------------------------------
/*
void SND_CALL SND_fn_vLoadResFromDiskMidi(tdstBlockResourceDisk *disk,tdstBlockResourceMem *mem)
{
#ifndef DISABLE_MIDI
	SND_fn_vConvertResDiskToMemMidi(disk,mem,NULL);

    mem->bIsLoaded = TRUE;
	if (!SND_fn_bLoadMidiTrack(mem,disk->uRes.stMidi.czFileName))
	{
        mem->bIsLoaded = FALSE;
        SND_fn_vDisplayError(E_uwSndMIDILoadTrack,"From Disk");
	}
#endif //DISABLE_MIDI
}
*/


char SND_gszMidiMDXFakePartName[13]="fakepart.mid";

#if !defined(NO_ACP_SCRIPT) || defined(_DLL_COMPILATION_MODE)
/*this function must be defined in Script/Hybrid or DLL mode*/
SndBool SND_CALL SND_fn_bLoadResScriptMidi(tdstBlockResourceDisk *disk,tdstBlockResourceMem *mem)
{
#ifndef DISABLE_MIDI
	char czFileName[13];

	
	SND_fn_vConvertResDiskToMemMidi(disk,mem,NULL);

    mem->bIsLoaded = TRUE;
	
	// Case mdx fakepart: :
	// change filename to lowercase and search the ".mdx" extension:
	strncpy(czFileName,disk->uRes.stMidi.czFileName,13);
	strlwr(czFileName);
	//  it's a mdx: replace it by the fakepart.mid file
	if (strstr(czFileName,".mdx")!=NULL)
	{
		strcpy(disk->uRes.stMidi.czFileName,SND_gszMidiMDXFakePartName);
	}
	if (!SND_fn_bLoadMidiTrack(mem,disk->uRes.stMidi.czFileName))
	{
        mem->bIsLoaded = FALSE;
	    SND_fn_vDisplayError(E_uwSndMIDILoadTrack,"From Script");
		return FALSE;
	}
	else return TRUE;
#else
	return FALSE;
#endif //DISABLE_MIDI
}
#endif

SndBool SND_CALL SND_fn_bLoadResBinaryMidi(tdstBlockResourceDisk *disk,tdstBlockResourceMem *mem,char *pDataBloc)
{
#ifndef DISABLE_MIDI
	SND_fn_vConvertResDiskToMemMidi(disk,mem,NULL);

    mem->bIsLoaded = TRUE;
	if (!SND_fn_bLoadMidiTrack(mem,disk->uRes.stMidi.czFileName))
	{
        mem->bIsLoaded = FALSE;
		SND_fn_vDisplayError(E_uwSndMIDILoadTrack,"From Binary");

		return FALSE;
	}
	else
		return TRUE;
#else
	return FALSE;
#endif //DISABLE_MIDI
}


#ifndef DISABLE_MIDI

//-----------------------------------------------------------
//	SND_fn_iMidiThreadCallBack
//
//	But		: Procédure de la thread qui contient la fenetre qui traite les messages MCI
//	Entrées : void * : paramètre inutilisé
//	Sorties : void
//------------------------------------------------------------

int WINAPI SND_fn_iMidiThreadCallBack(long arglist)
{
    long * pArg = (void *)arglist;
    SND_td_pfn_vSoundCallback 	pfnCallback = (SND_td_pfn_vSoundCallback)pArg[0];
    long lParCallback = pArg[1];

	SND_fn_vEnterCriticalSectionThreadSnd();

    if (pfnCallback != NULL)
	    (*pfnCallback)(lParCallback);
 	SND_fn_vQuitCriticalSectionThreadSnd();
   
    return (0);
}



//-----------------------------------------------------------
//	SND_fn_vMidiMSG
//
//	But			: Effectuer la gestion des messages du MIDI
//	Entrées : Messages et leurs parametres
//	Sorties : Ok ou Failed
//------------------------------------------------------------

LRESULT CALLBACK SND_fn_vMidiMSG(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
	char texte[256];
 	SECURITY_ATTRIBUTES sa;

	switch(message)
	{
    case WM_CREATE :	break;
    case WM_COMMAND : break;
    case MM_MCINOTIFY :
      switch (wParam)
      {
        case MCI_NOTIFY_FAILURE:
          sprintf(texte,"MM_MCINOTIFY FAILURE wpar=%d lpar=%d",wParam,lParam);
          SND_fn_vDisplayError(E_uwSndMIDINotifyFailure,texte);
          break;
        case MCI_NOTIFY_ABORTED:
          //          sprintf(texte,"MM_MCINOTIFY ABORTED wpar=%d lpar=%d",wParam,lParam);
          //          SND_fn_vDisplayError(E_uwSndMIDINotifyAborted,texte);
          break;
        case MCI_NOTIFY_SUPERSEDED:
          sprintf(texte,"MM_MCINOTIFY SUPERSEDED wpar=%d lpar=%d",wParam,lParam);
          SND_fn_vDisplayError(E_uwSndMIDINotifyFailure,texte);
          break;
        case MCI_NOTIFY_SUCCESSFUL:
		      switch (SND_fn_lGetCurrentMode())
		      {
			      case MCI_MODE_STOP : 
				      // cas ou la piste est terminee de lire, on lance la callback
				      // si elle n'est pas nulle
					if (!stActiveMidiTrack.bPaused)
					{

						if ((stActiveMidiTrack.bLoop) && (--stActiveMidiTrack.ulNbLoops))
						{
							SND_fn_lPlayMidiThread(stMidiTracks[stActiveMidiTrack.lVoice],stActiveMidiTrack.pstSoundParam,stActiveMidiTrack.lPrio,stActiveMidiTrack.pfnSoundCallback,stActiveMidiTrack.lParSoundCallback);
						}
						else
						{
						  if (!stActiveMidiTrack.bPaused)
						  {
                            stActiveMidiTrack.lPrio = 0;
                            stActiveMidiTrack.lVoice = NO_MIDI_VOICE;
                            stActiveMidiTrack.pstSoundParam = NULL;
                            stActiveMidiTrack.bPaused = FALSE;
                            stActiveMidiTrack.bActive = FALSE;

                            sa.nLength = sizeof(SECURITY_ATTRIBUTES);
                            sa.bInheritHandle = TRUE;
                            sa.lpSecurityDescriptor = NULL;
                            dw_a2_threadParams[0] = (DWORD)stActiveMidiTrack.pfnSoundCallback;
                            dw_a2_threadParams[1] = (DWORD)stActiveMidiTrack.lParSoundCallback;
                            if (!(hMidiThreadCallBack = CreateThread(&sa,0,(LPTHREAD_START_ROUTINE)SND_fn_iMidiThreadCallBack,(LPVOID)dw_a2_threadParams,0,&dwMidiThreadIdCallBack)))
	                            SND_fn_vDisplayError(E_uwSndMIDICreateThread,"");
                              
/*                              
                              if (stActiveMidiTrack.pfnSoundCallback!= NULL)
								  (*stActiveMidiTrack.pfnSoundCallback)(stActiveMidiTrack.lParSoundCallback);
*/
                          }
						}
					}
					break;
			      case MCI_MODE_PAUSE :
			      case MCI_MODE_OPEN :  
			      case MCI_MODE_NOT_READY :	
			      case MCI_MODE_PLAY :  
			      case MCI_MODE_SEEK :	
			      case MCI_MODE_RECORD :  
				      break;
		      }
          break;
        }
        return 0;

    case WM_DESTROY:	
		if (uiTimerMidiFadeIn)
			timeKillEvent(uiTimerMidiFadeIn);
		if (uiTimerMidiFadeOut)
			timeKillEvent(uiTimerMidiFadeOut);
		PostQuitMessage(0);
		break;
    default:
		return DefWindowProc(hwnd,message,wParam,lParam);
	}
 	return 0;
}


//-----------------------------------------------------------
//	SND_fn_iMidiThreadProc
//
//	But		: Procédure de la thread qui contient la fenetre qui traite les messages MCI
//	Entrées : void * : paramètre inutilisé
//	Sorties : void
//------------------------------------------------------------

int __stdcall SND_fn_iMidiThreadProc(HANDLE arglist)
{
	MSG msg;
	WNDCLASS wcMidiWndClass;
	DWORD *	pdwParams;
	
	PeekMessage(&msg,NULL,0,0,PM_NOREMOVE);


	if (SND_fn_iInitMidiThread() == C_INIT_FAILED)
	{
		stMidiDriver.bMidiDriverOk = FALSE;
		ExitThread(C_INIT_FAILED);
	}

	//definit la window-classe
	//definit la window-classe
	wcMidiWndClass.hInstance = SND_hGlobalInst;
	wcMidiWndClass.lpszClassName = "SoundAcpMidi";
	wcMidiWndClass.lpfnWndProc = SND_fn_vMidiMSG;//fonction associée à la fonction
	wcMidiWndClass.style = 0;
	wcMidiWndClass.hIcon = 0;
	wcMidiWndClass.hCursor = 0;
	wcMidiWndClass.lpszMenuName = "\0";
	wcMidiWndClass.cbClsExtra = 0;
	wcMidiWndClass.cbWndExtra = 0;
	wcMidiWndClass.hbrBackground = 0;

	//enregistrement de la classe CdWnd
	if (!RegisterClass(&wcMidiWndClass))
		SND_fn_vDisplayError(E_uwSndCDRegisterWndClass,"");
	
	//création de la fenetre midi
	hSndMidiWnd = CreateWindow(wcMidiWndClass.lpszClassName,"MidiWindow",WS_OVERLAPPEDWINDOW,0,0,0,0,HWND_DESKTOP,NULL,SND_hGlobalInst,NULL);

	// synchro d'initialisation du thread et de la pompe a messages
	SetEvent(arglist);

	while (GetMessage(&msg,NULL,0,0))
	{
		if (msg.message == SND_PLAYMIDI_MSG) 
		{
			pdwParams = (DWORD *)msg.wParam;
			pdwParams[0] = SND_fn_lPlayMidiThread(*(tduRefRes *)pdwParams[0],(SoundParam *)pdwParams[1],(long)pdwParams[2] ,(SND_td_pfn_vSoundCallback)pdwParams[3] ,(long)pdwParams[4] );
			// on debloque le thread appelant
			SetEvent((HANDLE)msg.lParam);
		}
		else
		if (msg.message == SND_STOPMIDI_MSG) 
		{
			pdwParams = (DWORD *)msg.wParam;
			SND_fn_vStopMidiThread((long)pdwParams[0]);
			SetEvent((HANDLE)msg.lParam);
		}
		else
		if (msg.message == SND_LOADMIDI_MSG)
		{
			pdwParams = (DWORD *)msg.wParam;
			pdwParams[0] = SND_fn_bLoadMidiTrackThread((tdstBlockResourceMem *)pdwParams[0],(char *)pdwParams[1]);
			SetEvent((HANDLE)msg.lParam);
		}
		else
		if (msg.message == SND_UNLOADMIDI_MSG)
		{
			pdwParams = (DWORD *)msg.wParam;
			pdwParams[0] = SND_fn_bUnloadMidiTrackThread((long)pdwParams[0]);
			SetEvent((HANDLE)msg.lParam);
		}
		else
		if (msg.message == SND_PAUSEMIDI_MSG)
		{
			pdwParams = (DWORD *)msg.wParam;
			SND_fn_vPauseMidiThread((long)pdwParams[0]);
			SetEvent((HANDLE)msg.lParam);
		}
		else
		if (msg.message == SND_RESUMEMIDI_MSG)
		{
			pdwParams = (DWORD *)msg.wParam;
			SND_fn_vResumeMidiThread((long)pdwParams[0]);
			SetEvent((HANDLE)msg.lParam);
		}
		else
		if (msg.message == SND_DESINITMIDI_MSG)
		{
			SND_fn_vDesInitMidiThread();
            SetEvent((HANDLE)msg.lParam);
			ExitThread(0);
		}
		else
		if (msg.message == SND_RELEASEMIDI_MSG)
		{
			SND_fn_vReleaseDriverMidiThread();
			SetEvent((HANDLE)msg.lParam);
		}
		else
		if (msg.message == SND_RESTOREMIDI_MSG)
		{
			SND_fn_vRestoreDriverMidiThread();
			SetEvent((HANDLE)msg.lParam);
		}
		else
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		//WaitMessage();
	}
	return 0;
}


//-----------------------------------------------------------
//	SND_fn_lTimerIn()
//
//	But			: Callback de gestion du temps pour les fade-in.
//	Entrées : neant
//	Sorties : neant
//------------------------------------------------------------
void CALLBACK SND_fn_lMidiTimerIn(UINT IDEvent,UINT msg,DWORD par,DWORD dw1,DWORD dw2)
{
	if (!stActiveMidiTrack.bPaused && stActiveMidiTrack.bActive)
		if (bMidiFadeIn)
		{
			if (lNbIterFadeInMidi--)
				SND_fn_vSetMidiValue(ucFacteurVolumeMidi++);
			else
			{
				bMidiFadeIn = FALSE;
				ulCountDownFadeInMidi = 0;
			}
	}
}	
	
//-----------------------------------------------------------
//	SND_fn_lTimerOut()
//
//	But			: Callback de gestion du temps pour les fade-in.
//	Entrées : neant
//	Sorties : neant
//------------------------------------------------------------
void CALLBACK SND_fn_lMidiTimerOut(UINT IDEvent,UINT msg,DWORD par,DWORD dw1,DWORD dw2)
{
	unsigned long ulRealTime = 0;
	if (!stActiveMidiTrack.bPaused && stActiveMidiTrack.bActive)
		if (bMidiFadeOut)
		{
			ulCountDownFadeOutMidi += ulTimerPeriodFadeOutMidi;
			ulRealTime = stActiveMidiTrack.ulTrackLengthInMs - lNbIterFadeOutMidi * ulTimerPeriodFadeOutMidi;
			if (ulCountDownFadeOutMidi > ulRealTime)
			{
				SND_fn_vSetMidiValue(ucFacteurVolumeMidi);
				if (ucFacteurVolumeMidi-- == 0)
				{
					bMidiFadeOut = FALSE;
					ulCountDownFadeOutMidi = 0;
					if (par == FADE_AND_STOP_MIDI)
					{
						SND_fn_vStopMidiThread(0);
						PostMessage(hSndMidiWnd,MM_MCINOTIFY,MCI_NOTIFY_SUCCESSFUL,0);
					}
				}
			}
		}
}

#endif //DISABLE_MIDI

//-----------------------------------------------------------
//	SND_fn_iInitMidi
//
//	But		: Realiser l'init bas niveau du driver midi
//	Entrées : neant
//	Sorties : C_INIT_OK ou C_INIT_FAILED
//------------------------------------------------------------
int SND_CALL SND_fn_iInitMidi(SND_tdstInitStruct *pInitStruct)
{
#ifndef DISABLE_MIDI
	DWORD dwExitCode = 0;
	HANDLE hEventSynchro;
	SECURITY_ATTRIBUTES sa;
	HANDLE hHandlesForWait[2];

#if defined(_DLL_COMPILATION_MODE)
    SND_DllInitEntryPoints_StaticFunctions((HMODULE)pInitStruct->hProcessInstance );
#endif

	stMidiDriver.bMidiDriverOk = MIDI_UNAVAILABLE;
	uiTimerMidiFadeIn = uiTimerMidiFadeOut = 0;

//	Init toutes pistes CD

    //init de hwndSnd et hinst ,et SND_hResourceHandle:
    hwndMainSnd=pInitStruct->hMainWindow;
	SND_hGlobalInst=pInitStruct->hProcessInstance;
	if (SND_g_hResourceHandle==NULL)	
		SND_g_hResourceHandle=SND_hGlobalInst;


	// on init les points d'entree fonctions externes DLL

#ifdef _DLL_COMPILATION_MODE
	SND_DllInitEntryPoints_StaticFunctions((HMODULE)SND_hGlobalInst);
#endif

	// on enregistre les differents types de messages recus par le thread
	SND_PLAYMIDI_MSG = RegisterWindowMessage("PlayMIDI");
	SND_STOPMIDI_MSG = RegisterWindowMessage("StopMIDI");
	SND_LOADMIDI_MSG = RegisterWindowMessage("LoadMIDI");
	SND_UNLOADMIDI_MSG = RegisterWindowMessage("UnloadMIDI");
	SND_PAUSEMIDI_MSG = RegisterWindowMessage("PauseMIDI");
	SND_RESUMEMIDI_MSG = RegisterWindowMessage("ResumeMIDI");
	SND_NEXTTRACKMIDI_MSG = RegisterWindowMessage("NextTrackMIDI");
	SND_DESINITMIDI_MSG = RegisterWindowMessage("DesinitMIDI");

	SND_RELEASEMIDI_MSG = RegisterWindowMessage("ReleaseMIDI");
	SND_RESTOREMIDI_MSG = RegisterWindowMessage("RestoreMIDI");


	// on cree le thread de gestion CD l'ID du thread est dwCDThreadId
    hMidiThread = NULL;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = TRUE;
	sa.lpSecurityDescriptor = NULL;

	hEventSynchro = CreateEvent(NULL,FALSE,FALSE,NULL);
	while (TRUE)
	{
		if (!(hMidiThread = CreateThread(&sa,0,(LPTHREAD_START_ROUTINE)SND_fn_iMidiThreadProc,hEventSynchro,0,&dwMidiThreadId)))
		{
			SND_fn_vDisplayError(E_uwSndMIDICreateThread,"");
			return C_INIT_FAILED;
		}
		else
		{
			hHandlesForWait[0] = hEventSynchro;
			hHandlesForWait[1] = hMidiThread;

			// on attend la synchronisation d'init., l'attente se termine quand un des deux events arrive
			WaitForMultipleObjects(2,hHandlesForWait,FALSE,INFINITE);
			GetExitCodeThread(hMidiThread,&dwExitCode);
			if (dwExitCode == STILL_ACTIVE)
			{
				stMidiDriver.bMidiDriverOk = TRUE;
				CloseHandle(hEventSynchro);
				return C_INIT_OK;
			}
			else
			{
				if (dwExitCode == C_INIT_FAILED)
				{
					// on joue sans Midi
					CloseHandle(hEventSynchro);
					return C_INIT_FAILED;
				}
			}
		}
	}


	return C_INIT_OK;
#else
    return C_INIT_FAILED;
#endif //DISABLE_MIDI
}

#ifndef DISABLE_MIDI

//-----------------------------------------------------------
//	SND_fn_iInitMidi
//
//	But			: Realiser l'init bas niveau du driver midi
//	Entrées : neant
//	Sorties : C_INIT_OK ou C_INIT_FAILED
//------------------------------------------------------------
int SND_fn_iInitMidiThread()
{
	int i;

    for (i=0;i < NB_MAX_MIDI_TRACKS;i++)
		stMidiTracks[i].pstPtr = NULL;
	
	stActiveMidiTrack.bLoop = FALSE;
	stActiveMidiTrack.bActive = FALSE;
	stActiveMidiTrack.bPaused = FALSE;
	stActiveMidiTrack.ulNbLoops = 0;
	stActiveMidiTrack.ulLength = 0;
	stActiveMidiTrack.lVoice = NO_MIDI_VOICE;
	stActiveMidiTrack.pstSoundParam = NULL;
	stActiveMidiTrack.lPrio = 0;
	stActiveMidiTrack.pfnSoundCallback= NULL;
	stActiveMidiTrack.lParSoundCallback= 0;
	
	stMidiDriver.bMidiDriverOk = TRUE;
	iNbTracks = 0;
 
	if (!SND_fn_iMixerInitMidi())
	{
		// On sauve l'ancienne valeur du volume CD
		stMidiDriver.iLastMidiVolume = SND_fn_dwGetMidiValue();
		ucFacteurVolumeMidi = stMidiDriver.iLastMidiVolume;
		SND_fn_vSetMidiValue(ucFacteurVolumeMidi);
	}
	return C_INIT_OK;
}
#endif //DISABLE_MIDI

//-----------------------------------------------------------
//	SND_fn_iTestInitMidi
//
//	But			: Verifier l'etat d'init du driver midi
//	Entrées : neant
//	Sorties : True ou False suivant l'etat
//------------------------------------------------------------
SndBool SND_CALL SND_fn_bTestInitMidi(void)
{
#ifndef DISABLE_MIDI
	return (stMidiDriver.bMidiDriverOk);
#else
    return FALSE;
#endif //DISABLE_MIDI
}



//-----------------------------------------------------------
//	SND_fn_iDesInitMidi
//
//	But			: Realiser la desnit bas niveau du driver midi
//	Entrées : neant
//	Sorties : neant
//------------------------------------------------------------
void SND_CALL SND_fn_vDesInitMidi(void)
{
#ifndef DISABLE_MIDI
    HANDLE hEventSynchro;

    if (hMidiThread)
	{
		hEventSynchro = CreateEvent(NULL,FALSE,FALSE,NULL);

        if (!PostThreadMessage(dwMidiThreadId,(UINT)SND_DESINITMIDI_MSG,(WPARAM)0,(LPARAM)hEventSynchro))
   			SND_fn_vDisplayError(E_uwSndMIDIClose,"PostThreadMessage Desinit");

        WaitForSingleObject(hEventSynchro,INFINITE);
		CloseHandle(hEventSynchro);

		WaitForSingleObject(hMidiThread,INFINITE);
		CloseHandle(hMidiThread);
	}
#endif //DISABLE_MIDI
}

#ifndef DISABLE_MIDI
//-----------------------------------------------------------
//	SND_fn_iDesInitMidiThread
//
//	But			: Realiser la desnit bas niveau du driver midi
//	Entrées : neant
//	Sorties : neant
//------------------------------------------------------------
void SND_fn_vDesInitMidiThread(void)
{
	int i;
	for (i=0;i<iNbTracks;i++)
		SND_fn_bUnloadMidiTrackThread(i);

    stActiveMidiTrack.bLoop = FALSE;
	stActiveMidiTrack.bActive = FALSE;
	stActiveMidiTrack.bPaused = FALSE;
	stActiveMidiTrack.ulNbLoops = 0;
	stActiveMidiTrack.ulLength = 0;
	stActiveMidiTrack.lVoice = NO_MIDI_VOICE;
	stActiveMidiTrack.pstSoundParam = NULL;
	stActiveMidiTrack.lPrio = 0;
	stActiveMidiTrack.pfnSoundCallback= NULL;
	stActiveMidiTrack.lParSoundCallback= 0;

	stMidiDriver.bMidiDriverOk = FALSE;

	// On restaure l'ancienne valeur du volume CD
	SND_fn_vSetMidiValue(stMidiDriver.iLastMidiVolume);
	SND_fn_iMixerDesInitMidi();
	DestroyWindow(hSndMidiWnd);
}
#endif //DISABLE_MIDI

//-----------------------------------------------------------
//	SND_fn_vRemoveCallbackMidi
//
//	But			: Remettre a zero la callback de la ressource 
//						joue sur la voie voice
//	Entrées : voie jouee
//	Sorties : neant
//------------------------------------------------------------
void SND_CALL SND_fn_vRemoveCallbackMidi(long voice)
{
#ifndef DISABLE_MIDI
	stActiveMidiTrack.pfnSoundCallback= NULL;
	stActiveMidiTrack.lParSoundCallback= 0;
#endif //DISABLE_MIDI
}

//-----------------------------------------------------------
//	SND_fn_bSetParamMidi
//
//	But			: Faire varier en temps reel les parametres
//						de la ressource midi
//	Entrées : voie jouee, parametres sonores
//	Sorties : TRUE si maj possible FALSE sinon
//------------------------------------------------------------
SndBool SND_CALL SND_fn_bSetParamMidi(long voice,SoundParam *par)
{
#ifndef DISABLE_MIDI
	return (stActiveMidiTrack.bActive);
#else
    return FALSE;
#endif //DISABLE_MIDI
}

//-----------------------------------------------------------
//	SND_fn_bTestIsPlayingMidi
//
//	But			: Une ressource midi est-elle active
//	Entrées : voie jouee
//	Sorties : Activite midi si voie bonne, FALSE sinon
//------------------------------------------------------------
SndBool SND_CALL SND_fn_bTestIsPlayingMidi(long voice)
{
#ifndef DISABLE_MIDI
	return (stActiveMidiTrack.bActive);
#else
    return FALSE;
#endif //DISABLE_MIDI
}

#ifndef DISABLE_MIDI
//-----------------------------------------------------------
//	SND_fn_lGetMidiTrack
//
//	But			: Renvoie la voie bas niveau sur laquelle
//						est chargee une ressource
//	Entrées : Id de la ressource
//	Sorties : voie bas niveau si resource chargee, NO_MIDI_VOICE sinon
//------------------------------------------------------------
long SND_fn_lGetMidiTrack(MCIDEVICEID Id)
{
	int i = 0;
	do
	{
		if (stMidiTracks[i].pstPtr == NULL)
			i++;
		else
			if (stMidiTracks[i].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId != Id)
				i++;
			else
				break;
	}
	while (i < NB_MAX_MIDI_TRACKS);

	if (i == NB_MAX_MIDI_TRACKS)
		return (NO_MIDI_VOICE);
	else
		return (i);
}
//-----------------------------------------------------------
//	SND_fn_lGetActiveTrack
//
//	But			: Renvoie la voie active
//	Entrées : neant
//	Sorties : voie bas niveau active, NO_MIDI_VOICE sinon
//------------------------------------------------------------
long SND_fn_lGetActiveTrack()
{
	if (stActiveMidiTrack.bActive)
		return (stActiveMidiTrack.lVoice);
	else
		return (NO_MIDI_VOICE);
}
//-----------------------------------------------------------
//	SND_fn_lGetCurrentMode
//
//	But			: Renvoie la callback midi
//	Entrées : voie bas niveau
//	Sorties : Mode du device correspondant a la voie
//------------------------------------------------------------
long SND_fn_lGetCurrentMode(void)
{
  MCI_STATUS_PARMS status;
  MCIERROR mciReturn;
	// Mode actuel de la piste
	status.dwItem = MCI_STATUS_MODE;  
	if (stActiveMidiTrack.uiMidiDeviceId != NO_MIDI_DEVICE)
	{
		if (mciReturn=mciSendCommand(stActiveMidiTrack.uiMidiDeviceId,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)(LPVOID)&status))
		{
			mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
			SND_fn_vDisplayError(E_uwSndMIDIStatus,texte_erreur_Midi);
			return (0);
		}
	}
	else return (0);
	return(status.dwReturn);
}

//-----------------------------------------------------------
//	SND_fn_lGetCurrentPosition
//
//	But			: Renvoie la position courante de lecture
//						de la voie active
//	Entrées : neant
//	Sorties : position en millisecondes
//------------------------------------------------------------
long SND_fn_lGetCurrentPosition(void)
{
  MCI_STATUS_PARMS status;
  MCIERROR mciReturn;
  
	// Mode actuel de la piste
	status.dwItem = MCI_STATUS_MODE;  
    if (mciReturn=mciSendCommand(stActiveMidiTrack.uiMidiDeviceId,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)(LPVOID)&status))
    {
      mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
      SND_fn_vDisplayError(E_uwSndMIDIStatus,texte_erreur_Midi);
	    return (0);
    }
	if ((status.dwReturn == MCI_MODE_PLAY)||(status.dwReturn == MCI_MODE_PAUSE))
	{
		// Si la piste est en mode play ou en pause
		status.dwItem = MCI_STATUS_POSITION;  
		if (mciReturn=mciSendCommand(stActiveMidiTrack.uiMidiDeviceId,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)(LPVOID)&status))
		{
			mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
			SND_fn_vDisplayError(E_uwSndMIDIStatus,texte_erreur_Midi);
			return (0);
		}

		return (status.dwReturn);
	}
	else return (0);
}


//-----------------------------------------------------------
//	SND_ulGetMidiTrackLengthInMs
//
//	But			: Calcule la duree exacte de la piste en millisecondes
//	Entrées : La voie active
//	Sorties : La position en tmsf 
//------------------------
unsigned long SND_fn_ulGetMidiTrackLengthInMs(unsigned long ulVoice)
{
	unsigned long ulRealLength;
	ulRealLength = stMidiTracks[stActiveMidiTrack.lVoice].pstPtr->uRes.stMidi.uData.stMem.ulLength;
	return (ulRealLength);
}


//-----------------------------------------------------------
//	SND_fn_ulInitFadeMidi
//
//	But			: Initialise les variables de fading
//	Entrées : 
//	Sorties : 
//------------------------
void SND_fn_ulInitFadeMidi(	unsigned long ulFadeInDurationMidi,unsigned long ulFadeOutDurationMidi)
{
	lNbIterFadeInMidi =	ucFacteurVolumeMidi;
	lNbIterFadeOutMidi = ucFacteurVolumeMidi;

	if (ulFadeInDurationMidi+ulFadeOutDurationMidi < stActiveMidiTrack.ulTrackLengthInMs)
	{
		bMidiFadeIn = (SndBool)ulFadeInDurationMidi;
		bMidiFadeOut = (SndBool)ulFadeOutDurationMidi;

		if (bMidiFadeIn)
		{
			ulTimerPeriodFadeInMidi = ulFadeInDurationMidi / lNbIterFadeInMidi;
			ulCountDownFadeInMidi = 0;
			ucFacteurVolumeMidi = 0;
			// init du timer cross-fade
			uiTimerMidiFadeIn = timeSetEvent(ulTimerPeriodFadeInMidi,10,(LPTIMECALLBACK)SND_fn_lMidiTimerIn,(DWORD)0,TIME_PERIODIC);
		}
		if (bMidiFadeOut)
		{
			ulTimerPeriodFadeOutMidi = ulFadeOutDurationMidi / lNbIterFadeInMidi;
			ulCountDownFadeOutMidi = 0;
			if (!bMidiFadeIn)
			{
				ucFacteurVolumeMidi = ucFacteurVolumeMidi;
			}
			// init du timer cross-fade
			uiTimerMidiFadeOut = timeSetEvent(ulTimerPeriodFadeOutMidi,10,(LPTIMECALLBACK)SND_fn_lMidiTimerOut,(DWORD)0,TIME_PERIODIC);
		}
		SND_fn_vSetMidiValue(ucFacteurVolumeMidi);
	}
	else
	{
		SND_fn_vSetMidiValue(ucFacteurVolumeMidi);
		SND_fn_vDisplayError(E_uwSndMIDIFade,"Bad Fading Parameters");
	}
}
#endif //DISABLE_MIDI

//-----------------------------------------------------------
//	SND_fn_lPlayMidi
//
//	But			: Joue la ressource midi res
//	Entrées : La ressource, parametres son, la priorite et 
//						la callback
//	Sorties : voie allouee pour jouee la ressource
//------------------------------------------------------------
long SND_CALL SND_fn_lPlayMidi(tduRefRes res,SoundParam *par,long prio,SND_td_pfn_vSoundCallback fn_callback,long par_callback)
{
#ifndef DISABLE_MIDI
	HANDLE hEventSynchro;

	// Case mdx part: fail:
	char czFileName[13];
	// change filename to lowercase and search the ".mdx" extension:
	strncpy(czFileName,res.pstPtr->uRes.stMidi.uData.stStream.fichier,13);
	strlwr(czFileName);
	if (strstr(czFileName,".mdx")!=NULL) return C_PLAY_FAILED;
	


	if (stMidiDriver.bMidiDriverOk)
	{
		hEventSynchro = CreateEvent(NULL,FALSE,FALSE,NULL);
		dw_a5_Params[0] = (DWORD)&res;
		dw_a5_Params[1] = (DWORD)par;
		dw_a5_Params[2] = (DWORD)prio;
		dw_a5_Params[3] = (DWORD)fn_callback;
		dw_a5_Params[4] = (DWORD)par_callback;

		if (!PostThreadMessage(dwMidiThreadId,(UINT)SND_PLAYMIDI_MSG,(WPARAM)dw_a5_Params,(LPARAM)hEventSynchro))
   			SND_fn_vDisplayError(E_uwSndMIDIPlay,"PostThreadMessage");
	
		WaitForSingleObject(hEventSynchro,INFINITE);
		CloseHandle(hEventSynchro);
	
		// dans ce cas, on retourne le numero de voie dans dw_a5_Params[0]
		return (dw_a5_Params[0]);
	}
	else
#endif //DISABLE_MIDI
		return (C_PLAY_FAILED);
}

#ifndef DISABLE_MIDI
//-----------------------------------------------------------
//	SND_fn_lPlayMidiThread
//
//	But			: Joue la ressource midi res
//	Entrées : La ressource, parametres son, la priorite et 
//						la callback
//	Sorties : voie allouee pour jouee la ressource
//------------------------------------------------------------
long SND_fn_lPlayMidiThread(tduRefRes res,SoundParam *par,long prio,SND_td_pfn_vSoundCallback fn_callback,long par_callback)
{
	MCIERROR mciReturn;
	MCI_PLAY_PARMS play;
	long lVoice = 0,lIndexVoice;

	play.dwCallback = (DWORD)hSndMidiWnd;
	play.dwFrom = 0;
	if ((lVoice = SND_fn_lGetMidiTrack(res.pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId)) != NO_MIDI_VOICE)
	{
debut:
		lIndexVoice = SND_fn_lGetMidiTrack(res.pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId);
		if (stActiveMidiTrack.lVoice == NO_MIDI_VOICE)
		{
			stActiveMidiTrack.bLoop = res.pstPtr->uRes.stMidi.bLoop;
			stActiveMidiTrack.lPrio = prio;
			stActiveMidiTrack.lVoice = lVoice;
			stActiveMidiTrack.lIndexVoice = lIndexVoice;
			stActiveMidiTrack.ulNbLoops = stMidiTracks[lIndexVoice].pstPtr->uRes.stMidi.uData.stMem.ulNbLoops;
			stActiveMidiTrack.ulTrackLengthInMs = SND_fn_ulGetMidiTrackLengthInMs(lIndexVoice);
		}
		else
		{
			// on a un ressource bouclante qui en est a son deuxieme tout minimum
			if ((stActiveMidiTrack.bActive && stActiveMidiTrack.bLoop)&&
				(stActiveMidiTrack.ulNbLoops != stMidiTracks[stActiveMidiTrack.lIndexVoice].pstPtr->uRes.stMidi.uData.stMem.ulNbLoops))
				lVoice = stActiveMidiTrack.lVoice;
			// on a une ressource pas bouclante mais il y a eu un play sans stop de la ressource precedente
			else
			{
				// on arrete spontanement une voix, on doit reallouer la meme voie pour le haut niveau
				lVoice = stActiveMidiTrack.lVoice;
				SND_fn_vStopMidiThread(stActiveMidiTrack.lVoice);
				goto debut;
			}

		}
		if (stMidiTracks[lIndexVoice].pstPtr != NULL)
			stActiveMidiTrack.uiMidiDeviceId = stMidiTracks[lIndexVoice].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId;
		else
		{
			SND_fn_vDisplayError(E_uwSndMIDILoadTrack,"no unload before loading");
			return (NO_MIDI_VOICE);
		}

		if (mciReturn=mciSendCommand(stActiveMidiTrack.uiMidiDeviceId,MCI_PLAY,MCI_NOTIFY|MCI_FROM,(DWORD)(LPVOID)&play))
		{
			mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
			SND_fn_vDisplayError(E_uwSndMIDIPlay,texte_erreur_Midi);
			return (NO_MIDI_VOICE);
		}
		stActiveMidiTrack.bActive = TRUE; 
		stActiveMidiTrack.bPaused = FALSE;
		stActiveMidiTrack.pstSoundParam = par;
		stActiveMidiTrack.pfnSoundCallback=fn_callback;
		stActiveMidiTrack.lParSoundCallback=par_callback;
		return (lVoice | OFFSET_MIDI_VOICE);
	}
	return C_PLAY_FAILED;
}

//-----------------------------------------------------------
//	SND_fn_lPlayMidiWithFade
//
//	But			: Joue la ressource Midi res avec un fade
//	Entrées : La ressource, parametres son, la priorite et 
//						la callback
//	Sorties : voie allouee pour jouee la ressource
//------------------------------------------------------------
long SND_CALL SND_fn_lPlayMidiWithFade(tduRefRes res,SoundParam *par,long prio,SND_td_pfn_vSoundCallback fn_callback,long par_callback,unsigned long ulFadeIn,unsigned long ulFadeOut)
{
	long result;
	result = SND_fn_lPlayMidi(res,par,prio,fn_callback,par_callback);
	SND_fn_ulInitFadeMidi(ulFadeIn,ulFadeOut);
	return (result);
}
#endif //DISABLE_MIDI

//-----------------------------------------------------------
//	SND_fn_vStopMidi
//
//	But		: Stopper une voie
//	Entrées : la voie a stopper
//	Sorties : neant
//------------------------------------------------------------
void SND_CALL SND_fn_vStopMidi(long voice)
{
#ifndef DISABLE_MIDI
	HANDLE hEventSynchro;
	dw_a5_Params[0] = (DWORD)voice;
	
	if (stMidiDriver.bMidiDriverOk)
	{
		hEventSynchro = CreateEvent(NULL,FALSE,FALSE,NULL);
		if (!PostThreadMessage(dwMidiThreadId,(UINT)SND_STOPMIDI_MSG,(WPARAM)dw_a5_Params,(LPARAM)hEventSynchro))
   			SND_fn_vDisplayError(E_uwSndMIDIStop,"PostThreadMessage");

		WaitForSingleObject(hEventSynchro,INFINITE);
		CloseHandle(hEventSynchro);
	}
#endif //DISABLE_MIDI
}

#ifndef DISABLE_MIDI
//-----------------------------------------------------------
//	SND_fn_vStopMidiThread
//
//	But		: Stopper une voie
//	Entrées : la voie a stopper
//	Sorties : neant
//------------------------------------------------------------
void SND_fn_vStopMidiThread(long voice)
{
	MCIERROR mciReturn;
	MCI_GENERIC_PARMS stop;
   	SECURITY_ATTRIBUTES sa;

	stop.dwCallback = (DWORD)hSndMidiWnd;
	if (mciReturn=mciSendCommand(stActiveMidiTrack.uiMidiDeviceId,MCI_STOP,MCI_NOTIFY,(DWORD)&stop))
	{
		mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
		SND_fn_vDisplayError(E_uwSndMIDIStop,"Stopping Midi Error");
	}
	stActiveMidiTrack.lPrio = 0;
	stActiveMidiTrack.lVoice = NO_MIDI_VOICE;
	stActiveMidiTrack.uiMidiDeviceId = NO_MIDI_DEVICE;
	stActiveMidiTrack.bPaused = FALSE;
	stActiveMidiTrack.bActive = FALSE;
	
	if (stActiveMidiTrack.pfnSoundCallback)
	{
		sa.nLength = sizeof(SECURITY_ATTRIBUTES);
		sa.bInheritHandle = TRUE;
		sa.lpSecurityDescriptor = NULL;
		dw_a2_threadParams[0] = (DWORD)stActiveMidiTrack.pfnSoundCallback;
		dw_a2_threadParams[1] = (DWORD)stActiveMidiTrack.lParSoundCallback;
		if (!(hMidiThreadCallBack = CreateThread(&sa,0,(LPTHREAD_START_ROUTINE)SND_fn_iMidiThreadCallBack,(LPVOID)dw_a2_threadParams,0,&dwMidiThreadIdCallBack)))
			SND_fn_vDisplayError(E_uwSndCDCreateThread,"");
	}
	stActiveMidiTrack.pfnSoundCallback = NULL;
	stActiveMidiTrack.lParSoundCallback= 0;
	stActiveMidiTrack.pstSoundParam = NULL;
}
#endif //DISABLE_MIDI

//-----------------------------------------------------------
//	SND_fn_vPauseMidi
//
//	But			: Pauser une voie
//	Entrées : la voie a pauser
//	Sorties : true,false
//------------------------------------------------------------
void SND_CALL SND_fn_vPauseMidi(long voice)
{
#ifndef DISABLE_MIDI
	HANDLE hEventSynchro;
	dw_a5_Params[0] = (DWORD)voice;
	
	if (stMidiDriver.bMidiDriverOk)
	{
		hEventSynchro = CreateEvent(NULL,FALSE,FALSE,NULL);
		if (!PostThreadMessage(dwMidiThreadId,(UINT)SND_PAUSEMIDI_MSG,(WPARAM)dw_a5_Params,(LPARAM)hEventSynchro))
   			SND_fn_vDisplayError(E_uwSndMIDIStop,"PostThreadMessage Pause");

		WaitForSingleObject(hEventSynchro,INFINITE);
		CloseHandle(hEventSynchro);
	}
#endif //DISABLE_MIDI
}

#ifndef DISABLE_MIDI
//-----------------------------------------------------------
//	SND_fn_vPauseMidiThread
//
//	But			: Pauser une voie
//	Entrées : la voie a pauser
//	Sorties : true,false
//------------------------------------------------------------
void SND_fn_vPauseMidiThread(long voice)
{
  MCIERROR mciReturn;
  MCI_GENERIC_PARMS pause;

  pause.dwCallback = (DWORD)hSndMidiWnd;

  if (mciReturn=mciSendCommand(stActiveMidiTrack.uiMidiDeviceId,MCI_STOP,MCI_NOTIFY,(DWORD)&pause))
  {
    mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
    SND_fn_vDisplayError(E_uwSndMIDIStop,texte_erreur_Midi);
    return;
  }

  stActiveMidiTrack.bPaused = TRUE;
}
#endif //DISABLE_MIDI


//-----------------------------------------------------------
//	SND_fn_vResumeMidi
//
//	But			: Resumer une voie
//	Entrées : la voie a resumer
//	Sorties : True,false
//------------------------------------------------------------
void SND_CALL SND_fn_vResumeMidi(long voice)
{
#ifndef DISABLE_MIDI
	HANDLE hEventSynchro;

	dw_a5_Params[0] = (DWORD)voice;
	
	if (stMidiDriver.bMidiDriverOk)
	{
		hEventSynchro = CreateEvent(NULL,FALSE,FALSE,NULL);
		if (!PostThreadMessage(dwMidiThreadId,(UINT)SND_RESUMEMIDI_MSG,(WPARAM)dw_a5_Params,(LPARAM)hEventSynchro))
   			SND_fn_vDisplayError(E_uwSndMIDIPlay,"PostThreadMessage Resume");
		WaitForSingleObject(hEventSynchro,INFINITE);
		CloseHandle(hEventSynchro);
	}
#endif //DISABLE_MIDI
}


#ifndef DISABLE_MIDI
//-----------------------------------------------------------
//	SND_fn_vResumeMidi
//
//	But			: Resumer une voie
//	Entrées : la voie a resumer
//	Sorties : True,false
//------------------------------------------------------------
void SND_fn_vResumeMidiThread(long voice)
{
	MCIERROR mciReturn;
	MCI_GENERIC_PARMS resume;
	MCI_PLAY_PARMS play;

	resume.dwCallback = (DWORD)hSndMidiWnd;
	play.dwCallback = (DWORD)hSndMidiWnd;


	if (stActiveMidiTrack.bPaused)
		if (mciReturn=mciSendCommand(stActiveMidiTrack.uiMidiDeviceId,MCI_PLAY,MCI_NOTIFY,(DWORD)&play))
		{
			mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
			SND_fn_vDisplayError(E_uwSndMIDIPlay,texte_erreur_Midi);
            return;
		}
	stActiveMidiTrack.bPaused = FALSE;
}

//-----------------------------------------------------------
//	SND_fn_bLoadMidiTrack
//
//	But			: Charge une piste midi, a partir du block ressource
//	Entrées : Le block ressource et le nom du fichier midi
//	Sorties : true,false
//------------------------------------------------------------
SndBool SND_fn_bLoadMidiTrack(tdstBlockResourceMem * pstPtr,char *czFileName)
{
	HANDLE hEventSynchro;

	if (stMidiDriver.bMidiDriverOk)
	{
		hEventSynchro = CreateEvent(NULL,FALSE,FALSE,NULL);
		dw_a5_Params[0] = (DWORD)pstPtr;
		dw_a5_Params[1] = (DWORD)czFileName;

		if (!PostThreadMessage(dwMidiThreadId,(UINT)SND_LOADMIDI_MSG,(WPARAM)dw_a5_Params,(LPARAM)hEventSynchro))
   			SND_fn_vDisplayError(E_uwSndMIDILoadTrack,"PostThreadMessage");
	
		WaitForSingleObject(hEventSynchro,INFINITE);
		CloseHandle(hEventSynchro);
	
		// dans ce cas, on retourne le numero de voie dans dw_a5_Params[0]
		return (dw_a5_Params[0]);
	}
	else
		return FALSE;
}

//-----------------------------------------------------------
//	SND_fn_bLoadMidiTrackThread
//
//	But			: Charge une piste midi, a partir du block ressource
//	Entrées : Le block ressource et le nom du fichier midi
//	Sorties : true,false
//------------------------------------------------------------
SndBool SND_fn_bLoadMidiTrackThread(tdstBlockResourceMem * pstPtr,char *czFileName)
{
	MCI_OPEN_PARMS open;
	MCI_STATUS_PARMS status;
	MCI_SET_PARMS set;
	MCIERROR mciReturn; 
	int iNumTrack = 0;

	// on cherche une track libre dans le tableau de stMidiTracks
	while (stMidiTracks[iNumTrack].pstPtr != NULL)
	{
		if (iNumTrack > NB_MAX_MIDI_TRACKS)
		{
			// on en trouve pas
			SND_fn_vDisplayError(E_uwSndMIDIAllTracksUsed,"");
			return FALSE;
		}
		iNumTrack++;
	}
	// on en a trouve une
	// on reference le bloc midi a la liste des pistes midi

	stMidiTracks[iNumTrack].pstPtr  = pstPtr;
	open.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_SEQUENCER;
	open.dwCallback = (DWORD)hSndMidiWnd;
    SND_fn_vResolveFileName(czFileName,StrMidiDirectory);
 	//SND_fn_vGetDataDirectory(StrMidiDirectory,sizeof(StrMidiDirectory));
	//strncat((LPSTR)StrMidiDirectory,(LPCSTR)czFileName,sizeof(StrMidiDirectory)-strlen(StrMidiDirectory));
	open.lpstrElementName = (LPSTR)StrMidiDirectory;

	if (mciReturn=mciSendCommand(	stMidiTracks[iNumTrack].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId,
									MCI_OPEN,
									MCI_WAIT|MCI_OPEN_TYPE|MCI_OPEN_TYPE_ID|MCI_OPEN_ELEMENT,
									(DWORD)(LPVOID)&open))
	{
		mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
		SND_fn_vDisplayError(E_uwSndMIDIOpen,texte_erreur_Midi);
		return FALSE;
 	}

	stMidiTracks[iNumTrack].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId = open.wDeviceID;
	status.dwItem = MCI_SEQ_STATUS_PORT;

	if (mciReturn=mciSendCommand(stMidiTracks[iNumTrack].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)(LPVOID)&status))
	{
		mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
		stMidiDriver.bMidiDriverOk = FALSE;
		SND_fn_vDisplayError(E_uwSndMIDIStatus,texte_erreur_Midi);
		mciSendCommand(stMidiTracks[iNumTrack].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId,MCI_CLOSE,0,0);
		return FALSE;
	}
	
	// Par défaut, l'unité de temps MIDI est la milliseconde
	set.dwTimeFormat = MCI_FORMAT_MILLISECONDS;
  
	if (mciReturn=mciSendCommand(	stMidiTracks[iNumTrack].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId,
									MCI_SET,
									MCI_SET_TIME_FORMAT,
									(DWORD)(LPVOID)&set))
	{
		mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
		SND_fn_vDisplayError(E_uwSndMIDISet,texte_erreur_Midi);
		return FALSE;
	}

	// Par défaut, l'unité de temps MIDI est la milliseconde
	status.dwItem = MCI_STATUS_LENGTH;
  
	// On calcule la longueur de la piste en millisecondes
	if (mciReturn=mciSendCommand(	stMidiTracks[iNumTrack].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId,
									MCI_STATUS,
									MCI_STATUS_ITEM,
									(DWORD)(LPVOID)&status))
	{
		mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
		SND_fn_vDisplayError(E_uwSndMIDIStatus,texte_erreur_Midi);
		return FALSE;
	}
	stMidiTracks[iNumTrack].pstPtr->uRes.stMidi.uData.stMem.ulLength = status.dwReturn;
	stMidiTracks[iNumTrack].pstPtr->uRes.stMidi.uData.stMem.ulNbLoops = pstPtr->uRes.stMidi.ulNbLoops;
	iNbTracks = iNumTrack+1;
	return TRUE;
}

/*
SndBool SND_fn_bUnloadMidiRes(tduRefRes RR)
{
	long voice = SND_fn_lGetMidiTrack(RR.pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId);
	if (SND_fn_bUnloadMidiTrack(voice))
		return TRUE;
	else
		return FALSE;
}
*/

//-----------------------------------------------------------
//	SND_fn_bUnloadMidiTrack
//
//	But		: Decharge une piste midi, a partir de la voie
//	Entrées : La voie a decharger
//	Sorties : true,false
//------------------------------------------------------------
SndBool SND_fn_bUnloadMidiTrack(long lVoice)
{
	HANDLE hEventSynchro;

	if (stMidiDriver.bMidiDriverOk)
	{
		hEventSynchro = CreateEvent(NULL,FALSE,FALSE,NULL);
		dw_a5_Params[0] = (DWORD)lVoice;

		if (!PostThreadMessage(dwMidiThreadId,(UINT)SND_UNLOADMIDI_MSG,(WPARAM)dw_a5_Params,(LPARAM)hEventSynchro))
   			SND_fn_vDisplayError(E_uwSndMIDIUnloadTrack,"PostThreadMessage");
	
		WaitForSingleObject(hEventSynchro,INFINITE);
		CloseHandle(hEventSynchro);
	
		// dans ce cas, on retourne le numero de voie dans dw_a5_Params[0]
		return (dw_a5_Params[0]);
	}
	else
		return FALSE;
}

//-----------------------------------------------------------
//	SND_fn_bUnloadMidiTrack
//
//	But			: Decharge une piste midi, a partir de la voie
//	Entrées : La voie a decharger
//	Sorties : true,false
//------------------------------------------------------------
SndBool SND_fn_bUnloadMidiTrackThread(long lVoice)
{
	MCIERROR mciReturn;

	if ((lVoice < NB_MAX_MIDI_TRACKS)&&
		 (stMidiTracks[lVoice].pstPtr != NULL))
	{
		if ((stMidiTracks[lVoice].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId != NO_MIDI_DEVICE)&&
            (stMidiTracks[lVoice].pstPtr->bIsLoaded))
		{
			if (mciReturn=mciSendCommand(stMidiTracks[lVoice].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId,MCI_CLOSE,0,0))
			{
				mciGetErrorString(mciReturn,texte_erreur_Midi,sizeof(texte_erreur_Midi));
				SND_fn_vDisplayError(E_uwSndMIDIClose,texte_erreur_Midi);
				return FALSE;
			}
			stMidiTracks[lVoice].pstPtr->uRes.stMidi.uData.stMem.uiMidiDeviceId= NO_MIDI_DEVICE;
			stMidiTracks[lVoice].pstPtr = NULL;
			iNbTracks--;
			return TRUE;
		}
		else
			return FALSE;
	}
	else
		return FALSE;
}

#endif //DISABLE_MIDI


void SND_CALL SND_fn_vUnLoadResMidi(tdstBlockResourceMem *mem)
{
#ifndef DISABLE_MIDI
	long voice = SND_fn_lGetMidiTrack(mem->uRes.stMidi.uData.stMem.uiMidiDeviceId);
	SND_fn_bUnloadMidiTrack(voice);
	mem->eType = TYPE_INVALID;
#endif //DISABLE_MIDI
}


//-----------------------------------------------------------
//	SND_fn_bIsResLoadedMidi
//
//	Goal		: Tell if the binary data associated with a midi resource are loaded.
//	Input : The block Resource to test
//	Output : true,false
//------------------------------------------------------------

SndBool SND_CALL SND_fn_bIsResLoadedMidi(tdstBlockResourceMem* _pBRMem)
{
#ifndef DISABLE_MIDI
  if (_pBRMem->uRes.stMidi.bStream)
    return TRUE;// Streaming sample is always "loaded"
  else
    return (_pBRMem->uRes.stMidi.uData.stMem.uiMidiDeviceId!=NO_MIDI_DEVICE);
#else
  return FALSE;
#endif //DISABLE_MIDI
}

/*
void SND_CALL SND_fn_vSetResUnloadedMidi(tdstBlockResourceMem *pResMem)
{
#ifndef DISABLE_MIDI
	pResMem->uRes.stMidi.bStream=FALSE;
	pResMem->uRes.stMidi.uData.stMem.uiMidiDeviceId=NO_MIDI_DEVICE;
#endif //DISABLE_MIDI
}
*/



#ifndef DISABLE_MIDI

//#endif	//MIDI_MCI

int SND_fn_iMixerInitMidi(void)
{
	uiNbrMixersMidi = mixerGetNumDevs(); //OK
	if (uiNbrMixersMidi == 0)
	{
		SND_fn_vDisplayError(E_uwSndMixerNoMixer,"");
		return 1;
	}

	if(mixerGetID((HMIXEROBJ)0,&mixerIdentifierMidi,MIXER_OBJECTF_MIXER) !=MMSYSERR_NOERROR)
	{
		SND_fn_vDisplayError(E_uwSndMixerNoId,"");
		return 1;
	}


	if(mixerOpen(&hMixerMidi,mixerIdentifierMidi,0,0,MIXER_OBJECTF_MIXER/* MIXER_OBJECTF_HWAVEOUT*/) != MMSYSERR_NOERROR)
	{
		SND_fn_vDisplayError(E_uwSndMixerOpen,"");
		return 1;
	}

	if(mixerGetDevCaps((UINT)hMixerMidi,&mixerCapsMidi,sizeof(MIXERCAPS)) != MMSYSERR_NOERROR)
	{
		SND_fn_vDisplayError(E_uwSndMixerCapacities,"");
		return 1;
	}

	dwNbrAudioLinesMidi = mixerCapsMidi.cDestinations;

//==================
// MIXERLINE MIDI
//==================

	mixerLineMidi.cbStruct = sizeof(MIXERLINE);
	mixerLineMidi.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;
	if (mixerGetLineInfo((HMIXEROBJ)hMixerMidi,&mixerLineMidi,MIXER_GETLINEINFOF_COMPONENTTYPE|MIXER_OBJECTF_HMIXER) != MMSYSERR_NOERROR)
    {
	    //return MIXER_ERR_GETLINEINFOSAMPLE;
        MidiControlAvailable = FALSE;
    }
    else
    {
	    MidiControlAvailable = TRUE;
    }

//======================
// MIXERLINECONTROL MIDI
//======================
	mixerLineControlsMidi.cbStruct = sizeof(MIXERLINECONTROLS);
	mixerLineControlsMidi.dwLineID = mixerLineMidi.dwLineID;
	mixerLineControlsMidi.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
	mixerLineControlsMidi.cControls = 1;
	mixerLineControlsMidi.cbmxctrl  = sizeof(MIXERCONTROL);
	mixerLineControlsMidi.pamxctrl =(MIXERCONTROL *) &mixerControlMidi;
	if(mixerGetLineControls((HMIXEROBJ)hMixerMidi,&mixerLineControlsMidi,MIXER_GETLINECONTROLSF_ONEBYTYPE|MIXER_OBJECTF_HMIXER) != MMSYSERR_NOERROR)
		MidiControlAvailable = FALSE;
	else
		MidiControlAvailable = TRUE;


//=============================
// MIXERLINECONTROLDETAILS MIDI
//=============================
	mixerControlDetailsMidi.cbStruct = sizeof(MIXERCONTROLDETAILS); 
	mixerControlDetailsMidi.dwControlID = mixerControlMidi.dwControlID;
	mixerControlDetailsMidi.cChannels = 1/*mixerLineMidi.cChannels 1*/;
	mixerControlDetailsMidi.cMultipleItems = mixerControlMidi.cMultipleItems;
	mixerControlDetailsMidi.cbDetails =sizeof(MIXERCONTROLDETAILS_UNSIGNED);
	mixerControlDetailsMidi.paDetails= &valeurMidi; 

	return 0;
}


DWORD SND_fn_dwGetMidiMax(void)
{
  return mixerControlMidi.Bounds.dwMaximum;
}
DWORD SND_fn_dwGetMidiMin(void)
{
  return mixerControlMidi.Bounds.dwMinimum; 
}

//=======================================
// void SND_fn_vSetMidiValue(DWORD val)
//=======================================
// Positionne le volume du Midi en %
// val entre 0 et 100
//=======================================
void SND_fn_vSetMidiValue(DWORD val)
{
	DWORD inc;

	if (MidiControlAvailable)
	{
		//snd_assert(val>=0);
		snd_assert(val<=100);

		inc = (SND_fn_dwGetMidiMax()-SND_fn_dwGetMidiMin())/100;
		val = (val* inc) + SND_fn_dwGetMidiMin();

		snd_assert(val>=SND_fn_dwGetMidiMin());
		snd_assert(val<=SND_fn_dwGetMidiMax());

		valeurMidi.dwValue = val;
 
		if(mixerSetControlDetails((HMIXEROBJ)hMixerMidi,&mixerControlDetailsMidi,MIXER_SETCONTROLDETAILSF_VALUE|MIXER_OBJECTF_HMIXER) != MMSYSERR_NOERROR)
		{
			//return 1;
		}
	}
}

 
//=================================
// SND_fn_dwGetMidiValue
//=================================
// Retourne la valeur du mixer Midi 
// en %
//=================================
DWORD SND_fn_dwGetMidiValue(void)
{
	DWORD inc;
	DWORD result;

	if (MidiControlAvailable)
	{
		inc = (SND_fn_dwGetMidiMax()-SND_fn_dwGetMidiMin())/100;

		if(mixerGetControlDetails((HMIXEROBJ)hMixerMidi,&mixerControlDetailsMidi,MIXER_GETCONTROLDETAILSF_VALUE|MIXER_OBJECTF_HMIXER) != MMSYSERR_NOERROR)
		{
			return 1;
		}

		result = (valeurMidi.dwValue - SND_fn_dwGetMidiMin())/inc;

		//snd_assert(result>=0);
		snd_assert(result<=100);
		return result;
	}
	else
	return 0;
}



BOOL SND_fn_bIsMidiControlAvailable(void)
{
  return MidiControlAvailable;
}


int SND_fn_iMixerDesInitMidi(void)
{         
  if(mixerClose(hMixerMidi) != MMSYSERR_NOERROR)
  {
		SND_fn_vDisplayError(E_uwSndMixerDesinit,"");
		return 1;
  }
  return 0;
}

#endif //DISABLE_MIDI

#ifndef NO_ACP_LDBIN
SndBool SND_CALL SND_fn_bCanFreeDataMidi(void)
{
	return FALSE;
}
#endif


/*--------------------------------------------------
relache-reprise a chaud des drives
---------------------------------------------------------*/
#define PAUSED_BY_RESTORE_RELEASE	2

void SND_CALL SND_fn_vReleaseDriverMidiThread()
{
#ifndef DISABLE_MIDI
	if (! stActiveMidiTrack.bPaused)
	{
		SND_fn_vPauseMidiThread(stActiveMidiTrack.lVoice);
		stActiveMidiTrack.bPaused = (BOOL)PAUSED_BY_RESTORE_RELEASE;
	}

#endif //DISABLE_MIDI
}

void SND_CALL SND_fn_vReleaseDriverMidi()
{
#ifndef DISABLE_MIDI
	HANDLE hEventSynchro;
	
	if (stMidiDriver.bMidiDriverOk && stActiveMidiTrack.bActive) 
	{
		hEventSynchro = CreateEvent(NULL,FALSE,FALSE,NULL);
		if (!PostThreadMessage(dwMidiThreadId,(UINT)SND_RELEASEMIDI_MSG,(WPARAM)dw_a5_Params,(LPARAM)hEventSynchro))
   			SND_fn_vDisplayError(E_uwSndMIDIStop,"PostThreadMessage Release");

		WaitForSingleObject(hEventSynchro,INFINITE);
		CloseHandle(hEventSynchro);
	}
#endif //DISABLE_MIDI
}

void SND_CALL SND_fn_vRestoreDriverMidiThread()
{
#ifndef DISABLE_MIDI
	if (stActiveMidiTrack.bPaused == (BOOL)PAUSED_BY_RESTORE_RELEASE)
		SND_fn_vResumeMidiThread(stActiveMidiTrack.lVoice);
#endif //DISABLE_MIDI
}

void SND_CALL SND_fn_vRestoreDriverMidi()
{
#ifndef DISABLE_MIDI
	HANDLE hEventSynchro;
	
	if (stMidiDriver.bMidiDriverOk && stActiveMidiTrack.bActive)
	{
		hEventSynchro = CreateEvent(NULL,FALSE,FALSE,NULL);
		if (!PostThreadMessage(dwMidiThreadId,(UINT)SND_RESTOREMIDI_MSG,(WPARAM)dw_a5_Params,(LPARAM)hEventSynchro))
   			SND_fn_vDisplayError(E_uwSndMIDIStop,"PostThreadMessage Restore");

		WaitForSingleObject(hEventSynchro,INFINITE);
		CloseHandle(hEventSynchro);
	}
#endif //DISABLE_MIDI
}

//---------------------------------------------------------
//	GetPos: retourne la position "théorique" de la ressource
//			exprimé en seconde depuis le début
//			différent du temps absolu (car pitch, dérive..)
//	Entrée: voie concernée
//	Sortie: temps en S (SndReal)
//----------------------------------------------------------
SndReal SND_CALL SND_fn_rGetPosMidi(long voie)
{
	return SND_C_POS_UNKNOWN;
}

//---------------------------------------------------------
//	GetLength: retourne la durée "théorique" de la ressource
//			exprimé en seconde (constant pour une ressource donnée)
//	Entrée: voie concernée
//	Sortie: durée en S (SndReal)
//----------------------------------------------------------
SndReal SND_CALL SND_fn_rGetLengthMidi(long voie)
{
	return SND_C_POS_UNKNOWN;
}

//-------------------------------------------------
// Fonction de synchro moteur
//	ne sert à rien pour Midi
//-------------------------------------------------
void SND_CALL SND_fn_vSynchroMidi()
{
}

//------------------------------------------------------
// Fonctions pour thems NON IMPLEMENTéES
//------------------------------------------------------

static tduRefRes s_CurrentMidiThemeRes={0};
static long s_lCurrentMidiThemeVoice;
static tduRefRes s_NextMidiThemeRes={0};
static SND_td_pfn_vSoundCallback s_pfnThemeTransitionCB;
static long s_lThemeTransitionCBParam;
static SndBool s_bChangingTheme=FALSE;
void SND_CALL fn_MidiThemeTransition(long par)
{
	// A midi theme part just ended (either normally, or it was ended). 
	// s_bChangingTheme is true when a transition is forced.	
	if (s_bChangingTheme==TRUE)
	{
		s_bChangingTheme=FALSE;
		s_lCurrentMidiThemeVoice=
			SND_fn_lPlayMidi(s_CurrentMidiThemeRes,(SoundParam *)par,0,fn_MidiThemeTransition,par);
		if (s_NextMidiThemeRes.pstPtr==NULL)
			s_pfnThemeTransitionCB(s_lThemeTransitionCBParam);
		return;
	}
	s_CurrentMidiThemeRes=s_NextMidiThemeRes;
	if (s_CurrentMidiThemeRes.pstPtr!=NULL)
	{
		s_lCurrentMidiThemeVoice=
			SND_fn_lPlayMidi(s_CurrentMidiThemeRes,(SoundParam *)par,0,fn_MidiThemeTransition,par);
		s_pfnThemeTransitionCB(s_lThemeTransitionCBParam);
	}
}

long SND_CALL SND_fn_lPlayTransitionMidi(tduRefRes FirstRes,tduRefRes NextRes,SND_td_pfn_vSoundCallback transition_callback,long param_callback,SoundParam* par)
{
	if (s_CurrentMidiThemeRes.pstPtr!=NULL) s_bChangingTheme=TRUE;
	else s_bChangingTheme=FALSE;

	s_CurrentMidiThemeRes=FirstRes;
	s_NextMidiThemeRes=NextRes;
	s_pfnThemeTransitionCB=transition_callback;
	s_lThemeTransitionCBParam=param_callback;

	// If a theme is already playing, stopping the current part will trigger the midi CB,
	//		and start the next part.
	// Otherwise, we must launch the first part...
	if (s_bChangingTheme==TRUE)
		SND_fn_vStopMidi(s_lCurrentMidiThemeVoice); // stop current midi part	
	else
	{
		s_lCurrentMidiThemeVoice=
			SND_fn_lPlayMidi(s_CurrentMidiThemeRes,par,0,fn_MidiThemeTransition,(long)par);
	}

	return s_lCurrentMidiThemeVoice;
}


SndBool SND_CALL SND_fn_bSetParamTransitionMidi(long voice,SoundParam* par)
{
	return FALSE;
}

SndBool	SND_CALL SND_fn_bSetNextTransitionMidi(long voice,tduRefRes new_res)
{
	s_NextMidiThemeRes=new_res;
	return TRUE;
}

void SND_CALL SND_fn_vStopTransitionMidi(long voice)
{
	s_NextMidiThemeRes.pstPtr=NULL; 
	if (s_CurrentMidiThemeRes.pstPtr!=NULL)
		SND_fn_vStopMidi(s_lCurrentMidiThemeVoice); // stop current midi part	
}

void SND_CALL SND_fn_vResumeTransitionMidi(long voice)
{
	if (s_CurrentMidiThemeRes.pstPtr!=NULL) SND_fn_vResumeMidi(s_lCurrentMidiThemeVoice);
}

void SND_CALL SND_fn_vPauseTransitionMidi(long voice)
{
		if (s_CurrentMidiThemeRes.pstPtr!=NULL) SND_fn_vPauseMidi(s_lCurrentMidiThemeVoice);
}

SndBool	SND_CALL SND_fn_bDoTransitionWithFadeMidi(long voice,tduRefRes new_res)
{
		s_bChangingTheme=TRUE;
		s_CurrentMidiThemeRes=new_res;
		s_NextMidiThemeRes.pstPtr=NULL; 
		SND_fn_vStopMidi(s_lCurrentMidiThemeVoice); // stop current midi part	
		return TRUE;
}

SndBool	SND_CALL SND_fn_bDoTransitionWithFadeMidi2( long voice, tduRefRes new_res, SndReal rDuration )
{
	s_bChangingTheme=TRUE;
	s_CurrentMidiThemeRes=new_res;
	s_NextMidiThemeRes.pstPtr=NULL; 
	SND_fn_vStopMidi(s_lCurrentMidiThemeVoice); // stop current midi part	
	return TRUE;
}

SndBool SND_CALL SND_fn_bCheckVersionResourceMidi(tdstBlockResourceDisk* disk)
{
	return TRUE;
}

SndBool SND_CALL SND_fn_bSetResourceStaticVolumeMidi(tdstBlockResourceMem* pstRes,unsigned char ucVolume)
{
	pstRes->ucVolume=ucVolume;

	return TRUE;
}

