//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//	Wod.c
//	Luiz Marques Afonso 	Agosto/95
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#include <windows.h>
#include <mmsystem.h>
#include <mem.h>
#include <dos.h>
#include <stdlib.h>
#include <toolhelp.h>

#include "mmddk.h"
#include "laycab.h"

//Periodo entre duas chamadas do timer, em milissegundos
#define  PERIODO_MS		500
//Frequencia das chamadas do timer, em segundos
#define  FREQUENCIA_S		(1000 / PERIODO_MS)
//Tamanho do buffer utilizado no ajuste do parâmetro de velocidade
#define  TAM_BUFFER_TESTE	11025
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// Typedefs
typedef struct
{
	DWORD
		dwCallback;     /* client's callback */
	DWORD
		dwInstance;     /* client's instance data */
	HANDLE
		hWave;          /* handle for stream */
	DWORD
		dwFlags;        /* allocation flags */
	DWORD
		dwByteCount;    /* byte count since last reset */
	PCMWAVEFORMAT
		pcmwf;          /* format of wave data */
}
	WAVEALLOC, NEAR * NPWAVEALLOC;
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// Prototypes
void		TerminaReproducaoBuffer( BOOL todos );
#if defined( VTDAPI )
void CALLBACK 	TimerCallbackDummy( DWORD dwUser );
#elif defined( SYSTEM_TIMER )
void CALLBACK 	TimerCallbackDummy( void );
#else
void CALLBACK 	TimerCallbackDummy( UINT uTimerID, UINT uMessage, DWORD dwUser, DWORD dw1, DWORD dw2 );
#endif
DWORD		TrataClose( DWORD dwUser );
DWORD		TrataGetDevCaps( BYTE *lpCaps, WORD wSize );
DWORD		TrataGetPos( NPWAVEALLOC pClientInfo, MMTIME *mmtime, WORD size );
DWORD		TrataOpen( DWORD dwUser, WAVEOPENDESC FAR *pOpenDesc, DWORD flags );
DWORD		TrataWrite( DWORD dwUser, WAVEHDR *pWaveHdr );
void 		WaveCallback( NPWAVEALLOC pWave, WORD msg, DWORD dw1 );
void		WaveWrite( void );
DWORD CALLBACK 	wodMessage( UINT uDeviceID, UINT uMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2 );
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// Globais
BYTE huge
	*PtrBuffer;	// Aponta para o próximo byte a ser enviado
DWORD
	TamBuffer;	// Tamanho corrente do buffer (Numero de bytes que restam)
UINT
	BytesPorVez;	// Número de bytes a serem transferidos por cada chamada da callback
WORD
	TimerID = 0;	// Identificador do timer instalado
WAVEHDR
	*PtrWaveHdr;

//[LMA 17/07/99]
static WORD
       SementeEfetiva; //semente realmente usada na temporizacao da reproducao.

#if defined( SYSTEM_TIMER )
static WORD CALLBACK
	( *KillSystemTimer )( WORD hTimer );
static WORD CALLBACK
	( *CreateSystemTimer )( WORD wRate, FARPROC fpCallback );
#endif

#if defined( DEBUG )
char
	*Msg[ 19 ] =
{
	"WODM_INIT",
	"WODM_GETNUMDEVS",
	"WODM_GETDEVCAPS",
	"WODM_OPEN",
	"WODM_CLOSE",
	"WODM_PREPARE",
	"WODM_UNPREPARE",
	"WODM_WRITE",
	"WODM_PAUSE",
	"WODM_RESTART",
	"WODM_RESET",
	"WODM_GETPOS",
	"WODM_GETPITCH",
	"WODM_SETPITCH",
	"WODM_GETVOLUME",
	"WODM_SETVOLUME",
	"WODM_GETPLAYBACKRATE",
	"WODM_SETPLAYBACKRATE",
	"WODM_BREAKLOOP"
};

#endif	//DEBUG
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#if 0
int
	AjustaVelocidade( void )
{
	DWORD
		tamBuffer = TAM_BUFFER_TESTE,
		numBytes,
		tInicio,
		tFim;
	WORD
		timerID,
		semente;
	int
		velocidade;
	BYTE
		huge *ptrBuffer,
		huge *pt;
	HCURSOR
		hOldCursor;
	unsigned
		dummy1 = 1;
	register unsigned
		dummy2;
	TIMERINFO
		timerInfo;

/*
	//Cria um timer dummy somente para que a velocidade de reproducao seja a mesma
#if defined( VTDAPI )
	timerID = SetTimerVTDAPI( 2 * PERIODO_MS, 10 , (FARPROC) TimerCallbackDummy, NULL );
#elif defined( SYSTEM_TIMER )
	timerID = CreateSystemTimer( 2 * PERIODO_MS, (FARPROC) TimerCallbackDummy );
#else
	timerID = timeSetEvent( 2 * PERIODO_MS, 10, TimerCallbackDummy, NULL, TIME_PERIODIC );
#endif
*/
	//Aloca tudo zerado para nao fazer barulho durante os testes
	pt = ptrBuffer = calloc( TAM_BUFFER_TESTE, 1 );

	if( !timerID || !ptrBuffer )
		return 0;

	semente = SEMENTE_MAX - 10 * VELOCIDADE_DEFAULT;

	//Muda o cursor para ampulheta
	hOldCursor = SetCursor( LoadCursor( NULL, IDC_WAIT ) );

	TamBuffer = tamBuffer;
	PtrBuffer = ptrBuffer;
	Semente   = semente;

	//tInicio = GetTickCount();
	timerInfo.dwSize = sizeof( TIMERINFO );
	TimerCount( &timerInfo );
	tInicio = timerInfo.dwmsSinceStart;
/*
	//Simula a reproducao de um pequeno trecho
	for( numBytes = tamBuffer; numBytes && dummy1 ; numBytes--, dummy2-- )
	{
		outportb( PortaParalela, *pt++ );

		asm	mov 	cx, semente;
	CONTA_TEMPO:
		asm	loop	CONTA_TEMPO;
	}
*/
	//LMAXXX
	//Simula a reproducao de um pequeno trecho
	for( numBytes = TamBuffer; numBytes && dummy1 ; numBytes--, dummy2-- )
	{
		outportb( PortaParalela, *PtrBuffer++ );

		asm	mov 	cx, Semente;
	CONTA_TEMPO:
		asm	loop	CONTA_TEMPO;
	}

	//tFim = GetTickCount();
	TimerCount( &timerInfo );
	tFim = timerInfo.dwmsSinceStart;

	TamBuffer = 0;
	PtrBuffer = 0;
	Semente   = 0;

	// sem. ideal = (1000 ms * semente default) / tempo obtido
	semente = (WORD) ( (1000L * semente) / (tFim - tInicio) );
	velocidade = (SEMENTE_MAX - semente) / 10;

	//Restaura o cursor
	SetCursor( hOldCursor );

	free( ptrBuffer );

#if defined( VTDAPI )
	KillTimerVTDAPI( &timerID );
#elif defined( SYSTEM_TIMER )
	KillSystemTimer( timerID );
#else
	timeKillEvent( timerID );
#endif

	return( velocidade );
}
#else

//[LMA 17/07/99] - Nova alteracao da funcao: passou a pegar apenas a semente na
//configuracao, sem mascarar pela velocidade.
#if 0
/*
int
	AjustaVelocidade( void )
{
	DWORD
		tamBuffer = TAM_BUFFER_TESTE,
		numBytes,
		tInicio,
		dtProcessamento,
		dtObtido;
	WORD
		semente;
	int
		velocidade;
	BYTE
		huge *ptrBuffer,
		huge *pt;
	unsigned
		dummy1 = 1;
	register unsigned
		dummy2;
	TIMERINFO
		timerInfo;

	timerInfo.dwSize = sizeof( TIMERINFO );

	//Pega os valores default para a velocidade e a semente
	velocidade = VELOCIDADE_DEFAULT;
	semente = SEMENTE_MAX - 10 * velocidade;

	pt = ptrBuffer = (BYTE huge *) calloc( TAM_BUFFER_TESTE, 1 );

	if( !ptrBuffer )
		return 0;

	TimerCount( (TIMERINFO FAR *) &timerInfo );
	tInicio = timerInfo.dwmsSinceStart;

	// Mede o tempo gasto no envio do numero de bytes equivalentes a 1000 ms
	for( numBytes = tamBuffer; numBytes && dummy1 ; numBytes-- , dummy2-- )
		outportb( PortaParalela, *pt++ );

	TimerCount( (TIMERINFO FAR *) &timerInfo );
	dtProcessamento = timerInfo.dwmsSinceStart - tInicio;

	// Mede novamente, dessa vez com a contagem da semente default
	pt = ptrBuffer;
	TimerCount( (TIMERINFO FAR *) &timerInfo );
	tInicio = timerInfo.dwmsSinceStart;

	for( numBytes = tamBuffer; numBytes && dummy1 ; numBytes--, dummy2-- )
	{
		outportb( PortaParalela, *pt++ );

		asm	mov 	cx, semente;
	CONTA_TEMPO:
		asm	loop	CONTA_TEMPO;
	}

	TimerCount( (TIMERINFO FAR *) &timerInfo );
	dtObtido = timerInfo.dwmsSinceStart - tInicio;

	// sem. procurada = ( (tempo ideal - tempo de processamento) * semente default)
	//		    ------------------------------------------------------------
	//		 		tempo obtido com a semente default
	semente = (WORD) ( ((1000L - dtProcessamento) * semente) / dtObtido );

	//Metodo arbitrado para calcular a velocidade a partir da semente
	velocidade = (SEMENTE_MAX - semente) / 10;

	free( ptrBuffer );

	return( velocidade );
}

*/
#else

WORD
	AjustaSemente( void )
{
	DWORD
		tamBuffer = TAM_BUFFER_TESTE,
		numBytes,
		tInicio,
		dtProcessamento,
		dtObtido;
	WORD
		semente, semEfetiva;
	BYTE
		huge *ptrBuffer,
		huge *pt;
	unsigned
		dummy1 = 1;
	register unsigned
		dummy2;
	TIMERINFO
		timerInfo;

	timerInfo.dwSize = sizeof( TIMERINFO );

	semente = SEMENTE_DEFAULT;
    semEfetiva = semente * 10;

	pt = ptrBuffer = (BYTE huge *) calloc( TAM_BUFFER_TESTE, 1 );

	if( !ptrBuffer )
		return 0;

	TimerCount( (TIMERINFO FAR *) &timerInfo );
	tInicio = timerInfo.dwmsSinceStart;

	// Mede o tempo gasto no envio do numero de bytes equivalentes a 1000 ms
	for( numBytes = tamBuffer; numBytes && dummy1 ; numBytes-- , dummy2-- )
		outportb( PortaParalela, *pt++ );

	TimerCount( (TIMERINFO FAR *) &timerInfo );
	dtProcessamento = timerInfo.dwmsSinceStart - tInicio;

	// Mede novamente, dessa vez com a contagem da semente default
	pt = ptrBuffer;
	TimerCount( (TIMERINFO FAR *) &timerInfo );
	tInicio = timerInfo.dwmsSinceStart;

	for( numBytes = tamBuffer; numBytes && dummy1 ; numBytes--, dummy2-- )
	{
		outportb( PortaParalela, *pt++ );

		asm	mov 	cx, semEfetiva;
	CONTA_TEMPO:
		asm	loop	CONTA_TEMPO;
	}

	TimerCount( (TIMERINFO FAR *) &timerInfo );
	dtObtido = timerInfo.dwmsSinceStart - tInicio;

	// sem. procurada = ( ( tempo ideal - tempo de processamento) * semente default)
	//		    ------------------------------------------------------------
	//                                       tempo sem. default
	semEfetiva = (WORD) ( ((1000L - dtProcessamento) * semEfetiva) / dtObtido );

    semente = semEfetiva / 10;


	free( ptrBuffer );

	return( semente );
}



#endif //[LMA 17/07/99]


#endif
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//O parâmetro indica se é para pegar o próximo buffer, ou se é para terminar de vez com todos
void
	TerminaReproducaoBuffer( BOOL todos )
{
	WAVEHDR
		*pHdrAux;

	if( !PtrWaveHdr )
		return;

	// Garante que se só tem um buffer, não vai tentar pegar o próximo
	if( ! PtrWaveHdr->lpNext )
		todos = TRUE;

	if( todos )
	{
//LMAXXX
/*
		// Desliga o timer instalado
		//[LMA 07/12/95] - Testa se está em modo assíncrono
		if( !Sincrono && TimerID )
		{
#if defined( VTDAPI )
			KillTimerVTDAPI( &TimerID );
#elif defined( SYSTEM_TIMER )
			KillSystemTimer( TimerID );
#else
			timeKillEvent( TimerID );
#endif
			TimerID = 0;
		}
*/

		// Zera as variaveis envolvidas
		PtrBuffer 	= NULL;
//		Semente         = 0;
//		BytesPorVez	= 0;
		TamBuffer	= 0L;

		pHdrAux = PtrWaveHdr;
		while( pHdrAux )
		{
			// seta os flags
			pHdrAux->dwFlags |= WHDR_DONE;
			pHdrAux->dwFlags &= ~WHDR_INQUEUE;

			// Notifica o cliente
			WaveCallback( (NPWAVEALLOC) pHdrAux->reserved, WOM_DONE, (DWORD) pHdrAux );

			pHdrAux = pHdrAux->lpNext;
		}

		// Zera o byte count
//		(((NPWAVEALLOC) (PtrWaveHdr->reserved))->dwByteCount) = 0L;

		PtrWaveHdr = NULL;
	}
	else
	{
		// seta os flags
		PtrWaveHdr->dwFlags |= WHDR_DONE;
		PtrWaveHdr->dwFlags &= ~WHDR_INQUEUE;

		// Notifica o cliente
		WaveCallback( (NPWAVEALLOC) PtrWaveHdr->reserved, WOM_DONE, (DWORD) PtrWaveHdr );

		PtrWaveHdr = PtrWaveHdr->lpNext;
		PtrBuffer  = (BYTE huge *) PtrWaveHdr->lpData;
		TamBuffer  = PtrWaveHdr->dwBufferLength;
	}
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#pragma argsused
#if defined( VTDAPI )
void CALLBACK _export
	TimerCallbackDummy( DWORD dwUser )
#elif defined( SYSTEM_TIMER )
void CALLBACK _export
	TimerCallbackDummy( void )
#else
void CALLBACK _export
	TimerCallbackDummy( UINT uTimerID, UINT uMessage, DWORD dwUser, DWORD dw1, DWORD dw2 )
#endif
{
	DWORD
		numBytes;
	WORD
		dummy1 = 1,
		dummy2 = 0;

	if( !TamBuffer )
		return;

	if( !HabilitaIntr )
	{
		asm 	mov 	cx, 3;
	TENTA_CLI:
		asm	cli;
		asm	loop 	TENTA_CLI;
	}

	//[LMA 18/12/95] - Salva a TamBuffer para somar no byte count no final
	for( numBytes = TamBuffer; dummy1 && numBytes ; numBytes--, dummy2++ )
	{
		outportb( PortaParalela, *PtrBuffer++ );

		asm	mov 	cx, SementeEfetiva;
	CONTA_TEMPO:
		asm	loop	CONTA_TEMPO;
	}

	//Incrementa o numero de bytes enviados
	((NPWAVEALLOC) PtrWaveHdr->reserved)->dwByteCount += TamBuffer;

	if( !HabilitaIntr )
	{
		asm	mov 	cx, 3;
	TENTA_STI:
		asm	sti;
		asm	loop 	TENTA_STI;
	}

#if defined( VTDAPI )
	KillTimerVTDAPI( &TimerID );
#elif defined( SYSTEM_TIMER )
	KillSystemTimer( TimerID );
#else
	timeKillEvent( TimerID );
#endif

	TerminaReproducaoBuffer( FALSE );

	TimerID = 0;
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#pragma argsused
#if defined( VTDAPI )
void CALLBACK _export
	TimerCallback( DWORD dwUser )
#elif defined( SYSTEM_TIMER )
void CALLBACK _export
	TimerCallback( void )
#else
void CALLBACK _export
	TimerCallback( UINT uTimerID, UINT uMessage, DWORD dwUser, DWORD dw1, DWORD dw2 )
#endif
{
	register unsigned
		numBytes = BytesPorVez;

	//Se entrou com buffer vazio, entao nao tem nada a fazer
	if( ! TamBuffer )
		return;

	for( ; TamBuffer && numBytes ; TamBuffer--, numBytes-- )
	{
		outportb( PortaParalela, *PtrBuffer++ );

		asm	mov 	cx, SementeEfetiva;
	CONTA_TEMPO:
		asm	loop	CONTA_TEMPO;
	}

	//Incrementa o numero de bytes enviados
	((NPWAVEALLOC) PtrWaveHdr->reserved)->dwByteCount += ( BytesPorVez - numBytes );

	if( !TamBuffer )
		TerminaReproducaoBuffer( FALSE );
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DWORD
	TrataClose( DWORD dwUser )
{
	NPWAVEALLOC
		pClientInfo;

	// Recupera as informações fornecidas ao cliente
	pClientInfo = (NPWAVEALLOC) LOWORD( dwUser );

	// Notifica o CLOSE
	WaveCallback( pClientInfo, WOM_CLOSE, 0L );

	// Libera a estrutura alocada
	LocalFree( (LOCALHANDLE) pClientInfo );


//LMAXXX
/*
#if !defined( VTDAPI ) && !defined( SYSTEM_TIMER )
		timeEndPeriod( PERIODO_MS );
#endif
*/
	if( !Sincrono )
	{
#if defined( VTDAPI )
		KillTimerVTDAPI( &TimerID );
#elif defined( SYSTEM_TIMER )
		KillSystemTimer( TimerID );
#else
		timeKillEvent( TimerID );
		timeEndPeriod( PERIODO_MS );
#endif
		TimerID = 0;

	}

	return( MMSYSERR_NOERROR );
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DWORD
	TrataGetDevCaps( BYTE *lpCaps, WORD wSize )
{
	WAVEOUTCAPS
		wc;

	wc.wMid 	  = 0; // MM_MICROSOFT;
	wc.wPid 	  = MM_SNDBLST_WAVEOUT;
	wc.vDriverVersion = VERSAO_DRIVER;
	wc.dwFormats 	  = WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08;
	wc.wChannels 	  = 1;
	wc.dwSupport  	  = 0;
	LoadString( hInstance, IDS_LAYCABWAVEOUT, wc.szPname, MAXPNAMELEN );

	memcpy( lpCaps, &wc, min( wSize, sizeof(wc) ) );

	return( MMSYSERR_NOERROR );
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DWORD
	TrataGetPos( NPWAVEALLOC pClientInfo, MMTIME *mmtime, WORD size )
{
	if( size < sizeof( MMTIME ) )
		return MMSYSERR_ERROR;

	if( mmtime->wType == TIME_BYTES )
		mmtime->u.cb = pClientInfo->dwByteCount;
	else
	{
		// Neste caso, samples=byte count, pois so'tem 8 bits mono
		mmtime->wType = TIME_SAMPLES;
		mmtime->u.sample = pClientInfo->dwByteCount;
	}

	return MMSYSERR_NOERROR;
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DWORD
	TrataOpen( DWORD dwUser, WAVEOPENDESC FAR *pOpenDesc, DWORD flags )
{
	const WAVEFORMAT FAR
		*lpFmt;
	NPWAVEALLOC
		pClientInfo;

	lpFmt = pOpenDesc->lpFormat;

	//Verifica se o formato é aceito pelo driver
	if( (lpFmt->wFormatTag != WAVE_FORMAT_PCM) ||
	    (lpFmt->nChannels != 1) ||
	    (lpFmt->nSamplesPerSec < 11025L) ||
	    (lpFmt->nSamplesPerSec > 44100L) ||
	    (lpFmt->nAvgBytesPerSec != lpFmt->nSamplesPerSec) ||
	    (lpFmt->nBlockAlign < 1) ||
	    (((LPPCMWAVEFORMAT)lpFmt)->wBitsPerSample != 8))
	{
#if defined( DEBUG )
		RegistraNoLog( "Open: WAVERR_BADFORMAT" );
#endif
		return( WAVERR_BADFORMAT );
	}

	//Vê se era apenas teste de formato do Wave
	if( flags & WAVE_FORMAT_QUERY )
		return( MMSYSERR_NOERROR );

	// Aloca estrutura com informações sobre o cliente
	pClientInfo = (NPWAVEALLOC) LocalAlloc( LPTR, sizeof(WAVEALLOC) );
	if( pClientInfo == NULL )
	{
#if defined( DEBUG )
		RegistraNoLog( "Open: MMSYSERR_NOMEM" );
#endif
		return( MMSYSERR_NOMEM );
	}

	pClientInfo->dwCallback  = pOpenDesc->dwCallback;
	pClientInfo->dwInstance  = pOpenDesc->dwInstance;
	pClientInfo->hWave       = pOpenDesc->hWave;
	pClientInfo->dwFlags     = flags;
	pClientInfo->dwByteCount = 0L;
	pClientInfo->pcmwf       = *((LPPCMWAVEFORMAT)lpFmt);

	// retorna ponteiro para estrutura para o cliente
	*( (DWORD FAR *) dwUser ) = MAKELONG( pClientInfo, 0 );

	// Inicializa a semente do contador de tempo
    //[LMA 17/07/99] - Pega a semente direto do arquivo ou da configuracao
	//Semente = SEMENTE_MAX - 10 * Velocidade;
    SementeEfetiva = Semente * 10;

	// Identifica a frequencia da amostragem
	switch( lpFmt->nSamplesPerSec )
	{
		case 11025L:
			BytesPorVez = 11025 / FREQUENCIA_S;
			break;
		case 22050L:
			BytesPorVez = 22050 / FREQUENCIA_S;
			SementeEfetiva /= 2;	// Corrige a semente do contador de tempo
			break;
		case 44100L:
			BytesPorVez = (unsigned) ( 44100L / FREQUENCIA_S );
			SementeEfetiva /= 4;
			break;
		default:
#if defined( DEBUG )
			RegistraNoLog( "Open: WAVERR_BADFORMAT 2" );
#endif
			return( WAVERR_BADFORMAT );
	}

	//[LMA 07/12/95] - Testa se está em modo assíncrono
	if( !Sincrono )
	{
#if defined( VTDAPI )
		if( ! PegaEntryPointVTDAPI() )
#elif defined( SYSTEM_TIMER )
		{
			HMODULE
				hSystem;

			hSystem = GetModuleHandle( "SYSTEM" );
			CreateSystemTimer = ( WORD CALLBACK (*) (WORD,FARPROC) ) GetProcAddress( hSystem, "CREATESYSTEMTIMER" );
			KillSystemTimer = ( WORD CALLBACK (*) (WORD) ) GetProcAddress( hSystem, "KILLSYSTEMTIMER" );
		}
		if( !CreateSystemTimer || !KillSystemTimer )
#else
		//[LMA 09/12/95]
		//if( timeBeginPeriod( PERIODO_MS - 100 ) == TIMERR_NOCANDO )
		if( timeBeginPeriod( PERIODO_MS ) == TIMERR_NOCANDO )
#endif
			return( MMSYSERR_ERROR );

//LMAXXX
#if defined( VTDAPI )
		TimerID = SetTimerVTDAPI( PERIODO_MS, 1 , (FARPROC) TimerCallback, (DWORD) 0L );
#elif defined( SYSTEM_TIMER )
		TimerID = CreateSystemTimer( PERIODO_MS, (FARPROC) TimerCallback );
#else
		TimerID = timeSetEvent( PERIODO_MS, 1, TimerCallback, (DWORD) 0L, TIME_PERIODIC );
#endif
		if( !TimerID )
		{
#if defined( DEBUG )
			RegistraNoLog( "Write: TimerID == 0" );
#endif
			return( MMSYSERR_ERROR );
		}

	}

	// chama DriverCallback()
	WaveCallback( pClientInfo, WOM_OPEN, 0L );

	return( MMSYSERR_NOERROR );
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DWORD
	TrataWrite( DWORD dwUser, WAVEHDR *pWaveHdr )
{
	NPWAVEALLOC
		pClientInfo;
	WAVEHDR
		*pHdrAux;

	if( pWaveHdr == NULL )
	{
#if defined( DEBUG )
		RegistraNoLog( "Write: pWaveHdr == NULL" );
#endif
		return( MMSYSERR_ERROR );
	}

	pWaveHdr->dwFlags &= (WHDR_INQUEUE|WHDR_DONE|WHDR_PREPARED|WHDR_BEGINLOOP|WHDR_ENDLOOP);

	// Verifica se buffer foi preparado
	if( ! (pWaveHdr->dwFlags & WHDR_PREPARED) )
	{
#if defined( DEBUG )
		RegistraNoLog( "Write: WAVERR_UNPREPARED" );
#endif
		return( WAVERR_UNPREPARED );
	}

	// Verifica se ainda esta na fila
	if ( pWaveHdr->dwFlags & WHDR_INQUEUE )
	{
#if defined( DEBUG )
		RegistraNoLog( "Write: WAVERR_STILLPLAYING" );
#endif
		return ( WAVERR_STILLPLAYING );
	}

	// Armazena o ponteiro para a estrutura WAVEALLOC no wavehdr
	pClientInfo 		= (NPWAVEALLOC) LOWORD( dwUser );
	pWaveHdr->reserved 	= (DWORD)(LPSTR)pClientInfo;
	pWaveHdr->lpNext   	= NULL;

	// Coloca na fila e desliga o DONE
	pWaveHdr->dwFlags |= WHDR_INQUEUE;
	pWaveHdr->dwFlags &= ~WHDR_DONE;

	// Se a fila está vazia, insere o novo buffer no início
	// Senão, percorre a fila para encontrar o final
	if( !PtrWaveHdr )
	{
#if defined( DEBUG )
		char
			str[51];

		RegistraNoLog( "Write: PtrWaveHdr == NULL" );
#endif
		PtrWaveHdr = pWaveHdr;

		// Prepara o buffer e contador
		PtrBuffer = (BYTE huge *) pWaveHdr->lpData;
		TamBuffer = pWaveHdr->dwBufferLength;

#if defined( DEBUG )
		wsprintf( str, "TamBuffer = %ld", TamBuffer );
		RegistraNoLog( str );
#endif

		if( Sincrono )
			WaveWrite();
		else    // Modo Assíncrono
		{
//LMAXXX
/*
#if defined( VTDAPI )
			TimerID = SetTimerVTDAPI( PERIODO_MS, 1 , (FARPROC) TimerCallback, (DWORD) pWaveHdr );
#elif defined( SYSTEM_TIMER )
			TimerID = CreateSystemTimer( PERIODO_MS, (FARPROC) TimerCallback );
#else
			TimerID = timeSetEvent( PERIODO_MS, 1, TimerCallback, (DWORD) pWaveHdr, TIME_PERIODIC );
#endif
			if( !TimerID )
			{
#if defined( DEBUG )
				RegistraNoLog( "Write: TimerID == 0" );
#endif
				return( MMSYSERR_ERROR );
			}
*/
		}
	}
	else
	{
#if defined( DEBUG )
		RegistraNoLog( "Write: PtrWaveHdr != NULL" );
#endif

		pHdrAux = PtrWaveHdr;
		while( pHdrAux->lpNext )
			pHdrAux = pHdrAux->lpNext;

		pHdrAux->lpNext = pWaveHdr;
	}

	return( MMSYSERR_NOERROR );
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/* @doc INTERNAL
 *
 * @api void | waveCallback | This calls DriverCallback for a WAVEHDR.
 *
 * @parm PWAVEALLOC | pWave | Pointer to wave device.
 *
 * @parm WORD | msg | The message.
 *
 * @parm DWORD | dw1 | message DWORD (dw2 is always set to 0).
 *
 * @rdesc There is no return value.
 */
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//Codigo extraido do DDK (Device Development Kit) da Microsoft
void
	WaveCallback( NPWAVEALLOC pWave, WORD msg, DWORD dw1 )
{

	/* Invoke the callback function, if it exists.  dwFlags contains
	* wave driver specific flags in the LOWORD and generic driver
	* flags in the HIWORD.
	*
	* DON'T switch stacks in DriverCallback - we already did at the
	* beginning of our ISR (using StackEnter).  No need to burn another
	* stack, as we should have plenty of room for the callback.  Also,
	* we may not have been called from an ISR.  In that case, we know
	* that we are on an app's stack, and this should be ok.
	*/

	if( pWave->dwCallback )
		DriverCallback( pWave->dwCallback,       /* user's callback DWORD */
				HIWORD(pWave->dwFlags) | DCB_NOSWITCH,  /* flags */
				pWave->hWave,            /* handle to the wave device */
				msg,                     /* the message */
				pWave->dwInstance,       /* user's instance data */
				dw1,                     /* first DWORD */
				0L );                    /* second DWORD */
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#if 1	//LMAXXXX Versao sem callback
void
	WaveWrite( void )
{
	DWORD
		numBytes;
	unsigned
		dummy1 = 1;
	register unsigned
		dummy2;

	if( !HabilitaIntr )
	{
		asm 	mov 	cx, 3;
	TENTA_CLI:
		asm	cli;
		asm	loop 	TENTA_CLI;
	}

#if defined( DEBUG )
	{
		char
			straux[ 51 ];

		wsprintf( straux, "TamBuffer = %lu\n", TamBuffer );
		RegistraNoLog( straux );
		wsprintf( straux, "Semente = %u\n", Semente );
		RegistraNoLog( straux );
		wsprintf( straux, "PtrWaveHdr = %X:%X\n", FP_SEG(PtrWaveHdr), FP_OFF(PtrWaveHdr) );
		RegistraNoLog( straux );
	}
#endif

	//[LMA 18/12/95] - Salva a TamBuffer para somar no byte count no final
	for( numBytes = TamBuffer; numBytes && dummy1 ; numBytes--, dummy2-- )
	{
		outportb( PortaParalela, *PtrBuffer++ );

		asm	mov 	cx, SementeEfetiva;
	CONTA_TEMPO:
		asm	loop	CONTA_TEMPO;
	}

	//Incrementa o numero de bytes enviados
	((NPWAVEALLOC) PtrWaveHdr->reserved)->dwByteCount += TamBuffer;

	if( !HabilitaIntr )
	{
		asm	mov 	cx, 3;
	TENTA_STI:
		asm	sti;
		asm	loop 	TENTA_STI;
	}
/*
	WORD
		timerID;
#if defined( VTDAPI )
	KillTimerVTDAPI( &timerID );
#elif defined( SYSTEM_TIMER )
	KillSystemTimer( tmerID );
#else
	timeKillEvent( timerID );
#endif
*/
	TerminaReproducaoBuffer( FALSE );
}

#else	//LMAXXXX Versao com callback

void
	WaveWrite( void )
{

	//Cria um timer dummy somente para que a velocidade de reproducao seja semelhante a
	//do modo assincrono
#if defined( VTDAPI )
	TimerID = SetTimerVTDAPI( PERIODO_MS, 1 , (FARPROC) TimerCallbackDummy, NULL );
#elif defined( SYSTEM_TIMER )
	TimerID = CreateSystemTimer( PERIODO_MS, (FARPROC) TimerCallbackDummy );
#else
	TimerID = timeSetEvent( PERIODO_MS, 1, TimerCallbackDummy, NULL, TIME_ONESHOT );
#endif
	if( !TimerID )
		return;
/*
	DWORD
		numBytes;

	if( !HabilitaIntr )
	{
		asm 	mov 	cx, 3;
	TENTA_CLI:
		asm	cli;
		asm	loop 	TENTA_CLI;
	}

	//[LMA 18/12/95] - Salva a TamBuffer para somar no byte count no final
	for( numBytes = TamBuffer; numBytes ; numBytes-- )
	{
		outportb( PortaParalela, *PtrBuffer++ );

		asm	mov 	cx, Semente;
	CONTA_TEMPO:
		asm	loop	CONTA_TEMPO;
	}

	//Incrementa o numero de bytes enviados
	((NPWAVEALLOC) PtrWaveHdr->reserved)->dwByteCount += TamBuffer;

	if( !HabilitaIntr )
	{
		asm	mov 	cx, 3;
	TENTA_STI:
		asm	sti;
		asm	loop 	TENTA_STI;
	}

#if defined( VTDAPI )
	KillTimerVTDAPI( &timerID );
#elif defined( SYSTEM_TIMER )
	KillSystemTimer( tmerID );
#else
	timeKillEvent( timerID );
#endif

	TerminaReproducaoBuffer( FALSE );
*/
}
#endif

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#pragma argsused
DWORD CALLBACK _export _loadds
	wodMessage( UINT uDeviceID, UINT uMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2 )
{
	DWORD
		ret;
#if 0
	WORD
		oldPsp;

	//Por se tratar de uma callback, tenho de setar o PSP corrente, para poder utilizar o
	//handle correto do arquivo de log.

	//pega o psp
	_AX = 0x5100;
	asm int 0x21;
	oldPsp = _BX;

	//seta o psp
	_AX = 0x5000;
	_BX = Psp;
	asm int 0x21;

	RegistraNoLog( (uMsg == WODM_INIT)  ?  Msg[ 0 ]  :  Msg[ uMsg - 2 ] );

	//seta o psp velho
	_AX = 0x5000;
	_BX = oldPsp;
	asm int 0x21;
#endif
#if defined( DEBUG )
	RegistraNoLog( (uMsg == WODM_INIT)  ?  Msg[ 0 ]  :  Msg[ uMsg - 2 ] );
#endif

	switch( uMsg )
	{
		case WODM_OPEN:
			ret = TrataOpen( dwUser, (WAVEOPENDESC FAR *) dwParam1, dwParam2 );
			break;

		case WODM_CLOSE:
			ret = TrataClose( dwUser );
			break;

		case WODM_GETDEVCAPS:
			ret = TrataGetDevCaps( (BYTE FAR *)dwParam1, (WORD) dwParam2 );
			break;

		case WODM_GETNUMDEVS:
			ret = 1L;
			break;

		case WODM_WRITE:
			ret = TrataWrite( dwUser, (WAVEHDR FAR *)dwParam1 );
			break;

		case WODM_RESET:
			if( PtrWaveHdr )
				(((NPWAVEALLOC) (PtrWaveHdr->reserved))->dwByteCount) = 0L;

			TerminaReproducaoBuffer( TRUE );
			ret = MMSYSERR_NOERROR;
			break;

		case WODM_GETPOS:
			ret = TrataGetPos( (NPWAVEALLOC) LOWORD(dwUser),
					   (MMTIME *) dwParam1,
					   (WORD) dwParam2 );
#if defined( DEBUG )
			{
				char
					str[ 51 ],
					fmt[] = "GetPos - %s %lu",
					strbytes[] = "Bytes:",
					strsamples[] = "Samples:",
					*pt;
				DWORD
					valor;
				MMTIME
					*mmtime = ((MMTIME *)dwParam1);

				if( mmtime->wType == TIME_BYTES )
				{
					valor = mmtime->u.cb;
					pt = strbytes;
				}
				else
				{
					valor = mmtime->u.sample;
					pt = strsamples;
				}

				wsprintf( str, fmt, pt, valor );
				RegistraNoLog( str );
			}
#endif

#if 0
				//pega o psp
				_AX = 0x5100;
				asm int 0x21;
				oldPsp = _BX;

				//seta o psp
				_AX = 0x5000;
				_BX = Psp;
				asm int 0x21;

				RegistraNoLog( str );
				//seta o psp velho
				_AX = 0x5000;
				_BX = oldPsp;
				asm int 0x21;
			}

#endif
			break;

		default:
			ret = MMSYSERR_NOTSUPPORTED;
	}

	return( ret );
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
