/*
**	Command & Conquer Red Alert(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/****************************************************************************
*
*        C O N F I D E N T I A L -- W E S T W O O D  S T U D I O S
*
*----------------------------------------------------------------------------
*
* PROJECT
*     VQA player library. (32-Bit protected mode)
*
* FILE
*     audio.c
*
* DESCRIPTION
*     Audio playback and timing.
*
* PROGRAMMER
*     Bill Randolph
*     Denzil E. Long, Jr.
*
* DATE
*     August 4, 1995
*
*
* HISTORY:
*     Modified for Win95 Direct Sound - Steve T 1/2/96 6:35AM
*
*----------------------------------------------------------------------------
*
* PUBLIC
*     VQA_StartTimerInt - Initialize system timer interrupt.
*     VQA_StopTimerInt  - Remove system timer interrupt.
*     VQA_SetTimer      - Resets current time to given tick value.
*     VQA_GetTime       - Return current time.
*     VQA_TimerMethod   - Get timer method being used.
*     VQA_OpenAudio     - Open sound system.
*     VQA_CloseAudio    - Close sound system
*     VQA_StartAudio    - Starts audio playback
*     VQA_StopAudio     - Stop audio playback.
*     CopyAudio         - Copy data from Audio Temp buf into Audio play buf.
*
* PRIVATE
*     TimerCallback - VQA timer event. (Called by HMI)
*     AutoDetect    - Auto detect the sound card.
*     AudioCallback - Sound system callback.
*
****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <mem.h>
#include <sys\timeb.h>
#include "vqaplayp.h"
#include <vqm32\all.h>

#ifdef __WATCOMC__
#pragma pack(4);
#endif


/*---------------------------------------------------------------------------
 * AUDIO DEFINITIONS
 *-------------------------------------------------------------------------*/

#define USE_WINDOWS_TIME	1

#define HMI_SAMPLE 0x1000
#define MAKE_LONG(a,b) (((long)(a)<<16)|(long)((b)&0x0000FFFFL))

/*---------------------------------------------------------------------------
 * PROTOTYPES
 *-------------------------------------------------------------------------*/

#if( USE_WINDOWS_TIME )
	extern unsigned long Get_Game_Time( void );
#endif

#if(VQAAUDIO_ON)
#if(VQADIRECT_SOUND)
void CALLBACK TimerCallback ( UINT event_id, UINT res1 , DWORD user, DWORD  res2, DWORD  res3 );
BOOL Move_HMI_Audio_Block_To_Direct_Sound_Buffer (void);
void CALLBACK AudioCallback ( UINT , UINT , DWORD , DWORD , DWORD );

#else

long AutoDetect(_SOS_CAPABILITIES *digicaps, long bitsize, long channels);
void far TimerCallback(void);
void far __cdecl AudioCallback(WORD wDriverHandle, WORD wAction,
		WORD wSampleID);

/* Dummy functions used to mark the start/end address of the file. */
static void StartAddr(void);
static void EndAddr(void);

#endif
/*---------------------------------------------------------------------------
 * GLOBAL DATA
 *-------------------------------------------------------------------------*/

static VQAHandleP *VQAP = NULL;
static long AudioFlags = 0;
static long TimerSysCount = 0;
static long TimerIntCount = 0;
static WORD VQATimer = 0;
static long TimerMethod;
static long VQATickCount = 0;
#endif /* VQAAUDIO_ON */

static long TickOffset = 0;
char *HMIDevName = "<none>";

#if(VQAAUDIO_ON)

#if(!VQADIRECT_SOUND)

/* This is a dummy function that is used to mark the start of the module.
 * It is necessary for locking the memory the module occupies. This prevents
 * the virtual memory manager from swapping out this memory.
 */
static void StartAddr(void)
{
}


/****************************************************************************
*
* NAME
*     VQA_StartTimerInt - Initialize system timer interrupt.
*
* SYNOPSIS
*     Error = VQA_StartTimerInt(VQA, Init)
*
*     long VQA_StartTimerInt(VQAHandeP *, long);
*
* FUNCTION
*     Initialize the HMI timer system and add our own timer event. If the
*     system has already been initialized then we are given access to the
*     the timer system.
*
* INPUTS
*     VQA  - Pointer to private VQAHandle structure.
*     Init - Initialize HMI timer system flag. (TRUE = Initialize)
*
* RESULT
*     Error - 0 if successful, -1 if error.
*
****************************************************************************/

long VQA_StartTimerInt(VQAHandleP *vqap, long init)
{
	VQAAudio *audio;

	/* Dereference for quick access. */
	audio = &vqap->VQABuf->Audio;

	/* Does the caller want me to initialize the timer system? */
	if (init == 0) {

		/* If the timer system is uninitialized then it is the players
		 * responsibility to do it.
		 */
		if ((AudioFlags & VQAAUDF_TIMERINIT)==(HMI_UNINIT<<VQAAUDB_TIMERINIT)) {
			sosTIMERInitSystem(_TIMER_DOS_RATE, _SOS_DEBUG_NORMAL);

			/* Flag the timer system as open. */
			AudioFlags |= (HMI_VQAINIT << VQAAUDB_TIMERINIT);
		}

		/* Flag availability of the timer system. */
		audio->Flags |= (HMI_VQAINIT << VQAAUDB_TIMERINIT);
	} else {
		audio->Flags |= (HMI_APPINIT << VQAAUDB_TIMERINIT);
	}

	/* Increment the timer system usage count. */
	TimerSysCount++;

	/* Register the VQA_TickCount timer event. */
	if ((AudioFlags & VQAAUDF_HMITIMER) == (HMI_UNINIT<<VQAAUDB_HMITIMER)) {
		if (sosTIMERRegisterEvent(VQA_TIMETICKS,TimerCallback,&VQATimer) == 0) {

			/* Flag the timer interrupt as being registered. */
			AudioFlags |= (HMI_VQAINIT << VQAAUDB_HMITIMER);
		} else {
			return (-1);
		}
	}

	/* Flag availability of the timer interrupt. */
	audio->Flags |= (HMI_VQAINIT << VQAAUDB_HMITIMER);

	/* Increment the timer interrupt usage count. */
	TimerIntCount++;

	/* Lock the memory occupied by this module. */
	if ((audio->Flags & VQAAUDF_MODLOCKED) == 0) {
		DPMI_Lock((void *)&StartAddr, (long)&EndAddr - (long)&StartAddr);
		audio->Flags |= VQAAUDF_MODLOCKED;
	}

	return (0);
}


/****************************************************************************
*
* NAME
*     VQA_StopTimerInt - Remove system timer interrupt.
*
* SYNOPSIS
*     VQA_StopTimerInt()
*
*     void VQA_StopTimerInt(void);
*
* FUNCTION
*     Remove our timer event from the HMI timer system. Uninitialize the
*     HMI timer system if we initialized it.
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
****************************************************************************/

void VQA_StopTimerInt(VQAHandleP *vqap)
{
	VQAAudio *audio;

	/* Dereference for quick access. */
	audio = &vqap->VQABuf->Audio;

	/* Decrement the timer interrupt usage count. */
	if (TimerIntCount) {
		TimerIntCount--;
	}

	/* Remove the timer interrrupt if it is initialized and the use count is
	 * zero. Otherwise, clear the callers timer interrupt availability flag.
	 */
	if (((AudioFlags & VQAAUDF_HMITIMER) == (HMI_VQAINIT<<VQAAUDB_HMITIMER))
			&& (TimerIntCount == 0)) {

		sosTIMERRemoveEvent(VQATimer);
		AudioFlags &= ~VQAAUDF_HMITIMER;
	} else {
		audio->Flags &= ~VQAAUDF_HMITIMER;
	}

	/* Derement the timer system usage count. */
	if (TimerSysCount) {
		TimerSysCount--;
	}

	/* Close the timer system if it has been opened and the use count is zero.
	 * Otherwise, clear the callers timer system availability flag.
	 */
	if (((AudioFlags & VQAAUDF_TIMERINIT) == (HMI_VQAINIT<<VQAAUDB_TIMERINIT))
			&& (TimerSysCount == 0)) {

		sosTIMERUnInitSystem(0);
		AudioFlags &= ~VQAAUDF_TIMERINIT;
	} else {
		audio->Flags &= ~VQAAUDF_TIMERINIT;
	}

	/* Unlock the memory accupied by this module. */
	if ((audio->Flags & VQAAUDF_MODLOCKED) == 1) {
		DPMI_Unlock(&StartAddr, (long)&EndAddr - (long)&StartAddr);
		audio->Flags &= ~VQAAUDF_MODLOCKED;
	}
}


/****************************************************************************
*
* NAME
*     TimerCallback - VQA timer event. (Called by HMI)
*
* SYNOPSIS
*     TimerCallback()
*
*     void TimerCallback(void);
*
* FUNCTION
*     Our custom timer event. This is the timer event that we register with
*     HMI for system timing.
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
****************************************************************************/

void far TimerCallback(void)
{
	VQATickCount++;
}


/****************************************************************************
*
* NAME
*     VQA_OpenAudio - Open sound system.
*
* SYNOPSIS
*     Error = VQA_OpenAudio(VQAHandleP)
*
*     long VQA_OpenAudio(VQAHandleP *);
*
* FUNCTION
*     Initialize the sound system by setting the DigiHandle to and HMI
*     driver handle. The timer must first be initialized before calling
*     this function.
*
* INPUTS
*     VQAHandleP - Pointer to private VQAHandle.
*
* RESULT
*     Error - 0 if successful, -1 if error.
*
****************************************************************************/

long VQA_OpenAudio(VQAHandleP *vqap)
{
	VQAData       *vqabuf;
	VQAAudio      *audio;
	VQAConfig     *config;
	VQAHeader     *header;
	unsigned char *driver_path;
	WORD          port;
	long          rc;

	/* Dereference data memebers for quicker access. */
	config = &vqap->Config;
	header = &vqap->Header;
	vqabuf = vqap->VQABuf;
	audio = &vqabuf->Audio;

	/* Fail if no audio buffer or DigiCard is 0 (no sound) */
	if ((audio->Buffer == NULL) || (config->DigiCard == 0)) {
		return (-1);
	}

	/* Reset the buffer position to the beginning. */
	audio->CurBlock = 0;

	/* Initialize the HMI driver structure. */
	audio->sSOSInitDriver.wAllocateBuffer = _TRUE;
	audio->sSOSInitDriver.wParam = 19;
	audio->sSOSInitDriver.wTimerID = _SOS_NORMAL_TIMER;

	/*-------------------------------------------------------------------------
	 * Compute the playback rate:
	 *
	 * - If Config->AudioRate is -1, use HMI_DATA_RATE scaled to the specified
	 *   frame rate (so the audio plays faster if we're playing faster)
	 * - otherwise, use the specified rate
	 *-----------------------------------------------------------------------*/
	if (config->AudioRate != -1) {
		audio->sSOSInitDriver.wSampleRate = config->AudioRate;
	}
	else if (config->FrameRate != header->FPS) {
		audio->sSOSInitDriver.wSampleRate = ((audio->SampleRate*config->FrameRate)
				/ (unsigned long)header->FPS);

		config->AudioRate = audio->sSOSInitDriver.wSampleRate;
	} else {
		audio->sSOSInitDriver.wSampleRate = audio->SampleRate;
		config->AudioRate = audio->SampleRate;
	}

	/* If the application has already initialized HMI then set the
	 * necessary variables. Otherwise we must setup HMI ourself.
	 */
	if ((config->OptionFlags & VQAOPTF_HMIINIT)
			|| ((AudioFlags & VQAAUDF_DIGIINIT) != 0)) {

		/* The application MUST provide the card type! */
		if (config->DigiCard == -1) {
			return (-1);
		}

		/* Init the detection system */
		driver_path = (unsigned char *)".\\";

		if ((rc = sosDIGIDetectInit(driver_path)) != 0) {
			return (rc);
		}

		/* Get the capabilities of the card being used. */
		rc = sosDIGIDetectGetCaps(config->DigiCard, &audio->DigiCaps);
		sosDIGIDetectUnInit();

		if (rc != 0) {
			return (rc);
		}

		audio->DigiHandle = config->DigiHandle;
		audio->Flags |= (HMI_APPINIT << VQAAUDB_TIMERINIT);
		audio->Flags |= (HMI_APPINIT << VQAAUDB_DIGIINIT);

		if ((AudioFlags & (VQAAUDF_TIMERINIT|VQAAUDF_DIGIINIT)) == 0) {
			HMIDevName = "App-Specific";
			AudioFlags |= (HMI_APPINIT << VQAAUDB_TIMERINIT);
			AudioFlags |= (HMI_APPINIT << VQAAUDB_DIGIINIT);
		}
	} else {

		/* Init the detection system */
		driver_path = (unsigned char *)".\\";

		if ((rc = sosDIGIDetectInit(driver_path)) != 0) {
			return (rc);
		}

		/*-----------------------------------------------------------------------
		 * Initialize DigiHardware with port, IRQ, and DMA, and make sure
		 * Config.DigiCard contains the HMI ID we want to use:
		 *
		 * - If Config.DigiCard is -1, auto-detect the hardware; then, do a
		 *   FindHardware so the GetSettings will work
		 * - If Config.DigiCard is filled in, but port, IRQ or DMA is -1, use
		 *   FindHardware & GetSettings to get the settings
		 * - If all are filled in, just use them as they are
		 *---------------------------------------------------------------------*/

		/* Auto-Detect */
		if (config->DigiCard == -1) {

			/* Version 1 VQA's have only 8 bit mono audio streams. */
			if (header->Version == VQAHD_VER1) {
				config->DigiCard = AutoDetect(&audio->DigiCaps, 8, 1);
			} else {
				config->DigiCard = AutoDetect(&audio->DigiCaps, audio->BitsPerSample,
						audio->Channels);

				/* Resort to 8bit mono */
				if (config->DigiCard == -1) {
					config->DigiCard = AutoDetect(&audio->DigiCaps, 8, 1);
				}
			}

			if (config->DigiCard == -1) {
				sosDIGIDetectUnInit();
				return (-1);
			}
		}

		/* Do a FindHardware & GetSettings */
		if (config->DigiPort == -1) {
		  if (sosDIGIDetectFindHardware(config->DigiCard, &audio->DigiCaps,
					&port)) {

				sosDIGIDetectUnInit();
				return (-1);
			}

			if (sosDIGIDetectGetSettings(&audio->DigiHardware)) {
				sosDIGIDetectUnInit();
				return (-1);
			}

			config->DigiPort = audio->DigiHardware.wPort;
			config->DigiIRQ = audio->DigiHardware.wIRQ;
			config->DigiDMA = audio->DigiHardware.wDMA;
			HMIDevName = (char *)audio->DigiCaps.szDeviceName;
		} else {
			audio->DigiHardware.wPort = config->DigiPort;
			audio->DigiHardware.wIRQ = config->DigiIRQ;
			audio->DigiHardware.wDMA = config->DigiDMA;
			HMIDevName = "App-Specific";
		}

		sosDIGIDetectUnInit();

		/* Initialize the DIGI system & driver */
		sosDIGIInitSystem(driver_path, _SOS_DEBUG_NORMAL);
		audio->sSOSInitDriver.wBufferSize = config->HMIBufSize;
		audio->sSOSInitDriver.lpBuffer = NULL;
		audio->sSOSInitDriver.lpFillHandler = NULL;
		audio->sSOSInitDriver.lpDriverMemory = NULL;
		audio->sSOSInitDriver.lpTimerMemory = NULL;
		audio->DigiHandle = -1;

		if ((rc = sosDIGIInitDriver(config->DigiCard, &audio->DigiHardware,
				&audio->sSOSInitDriver, &audio->DigiHandle)) != 0) {

			return (rc);
		}

		/*-----------------------------------------------------------------------
		 * Register the timer event
		 *---------------------------------------------------------------------*/

		/* If the timer hasn't been init'd, do it now */
		if ((AudioFlags & VQAAUDF_TIMERINIT) == (HMI_UNINIT<<VQAAUDB_TIMERINIT)) {
			sosTIMERInitSystem(_TIMER_DOS_RATE ,_SOS_DEBUG_NORMAL);
			audio->Flags |= (HMI_VQAINIT << VQAAUDB_TIMERINIT);
 			AudioFlags |= (HMI_VQAINIT << VQAAUDB_TIMERINIT);
		}

		/* Register the event */
		rc = sosTIMERRegisterEvent(VQA_TIMETICKS,
				audio->sSOSInitDriver.lpFillHandler, &audio->DigiTimer);

		if (rc) {
			sosDIGIUnInitDriver(audio->DigiHandle, _TRUE, _TRUE);
			sosDIGIUnInitSystem();
			return (rc);
		}

		config->DigiHandle = audio->DigiHandle;
		audio->Flags |= (HMI_VQAINIT << VQAAUDB_DIGIINIT);
		AudioFlags |= (HMI_VQAINIT << VQAAUDB_DIGIINIT);
	}

	if ((audio->Flags & VQAAUDF_MODLOCKED) == 0) {
		DPMI_Lock(&StartAddr, (long)&EndAddr - (long)&StartAddr);
		audio->Flags |= VQAAUDF_MODLOCKED;
	}

	return (0);
}


/****************************************************************************
*
* NAME
*     VQA_CloseAudio - Close sound system
*
* SYNOPSIS
*     VQA_CloseAudio()
*
*     void VQA_CloseAudio(void);
*
* FUNCTION
*     Removes VQA's involvement in the audio system.
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
****************************************************************************/

void VQA_CloseAudio(VQAHandleP *vqap)
{
	VQAAudio *audio;

	/* Dereference for quick access. */
	audio = &vqap->VQABuf->Audio;

	/* Remove the Digi event */
	if ((AudioFlags & VQAAUDF_DIGIINIT) == (HMI_VQAINIT<<VQAAUDB_DIGIINIT)) {
		sosTIMERRemoveEvent(audio->DigiTimer);
	}

	/* Un-init timer if necessary */
	if ((AudioFlags & VQAAUDF_TIMERINIT) == (HMI_VQAINIT<<VQAAUDB_TIMERINIT)) {
		sosTIMERUnInitSystem(0);
	}

	audio->Flags &= ~VQAAUDF_TIMERINIT;
	AudioFlags &= ~VQAAUDF_TIMERINIT;

	/* Remove the driver */
	if ((audio->Flags & VQAAUDF_DIGIINIT) == (HMI_VQAINIT<<VQAAUDB_DIGIINIT)) {
		sosDIGIUnInitDriver(audio->DigiHandle, _TRUE, _TRUE);
		sosDIGIUnInitSystem();
	}

	audio->Flags &= ~VQAAUDF_DIGIINIT;
	AudioFlags &= ~VQAAUDF_DIGIINIT;
	AudioFlags &= ~VQAAUDF_ISPLAYING;

	/* Unlock the memory accupied by this module. */
	if ((audio->Flags & VQAAUDF_MODLOCKED) == 1) {
		DPMI_Unlock(&StartAddr, (long)&EndAddr - (long)&StartAddr);
		audio->Flags &= ~VQAAUDF_MODLOCKED;
	}
}


/****************************************************************************
*
* NAME
*     AutoDetect - Auto detect the sound card.
*
* SYNOPSIS
*     CardID = AutoDetect(DigiCaps, BitSize, Channels)
*
*     long AutoDetect(_SOS_CAPABILITIES *, long, long);
*
* FUNCTION
*     Autodetects the type of sound card present in the system.
*
* INPUTS
*     DigiCaps - Pointer to HMI digital card capabilities structure.
*     BitSize  - Bits per sample size.
*     Channels - Number of desired channels.
*
* RESULT
*     CardID - HMI ID of the sound card found, -1 if none.
*
****************************************************************************/

long AutoDetect(_SOS_CAPABILITIES *digicaps, long bitsize, long channels)
{
	long device_id = -1;
	WORD port;
	long i;
	long rc;

	/* Search for an 8-bit mono device */
	if (sosDIGIDetectFindFirst(digicaps, &port)) {
		return (-1);
	}

	channels--;

	i = 0;
	while (i < 6) {
		i++;

		if ((digicaps->wBitsPerSample == bitsize)
				&& (digicaps->wChannels == channels)) {

			break;
		}

		if (sosDIGIDetectFindNext(digicaps, &port)) {
			return (-1);
		}
	}

	/* Exit if failed to find the required device */
	if ((digicaps->wBitsPerSample != bitsize)
			|| (digicaps->wChannels != channels)) {

		return (-1);
	}

	/* Stash the ID */
	device_id = digicaps->wDeviceID;

	/* Now that we have handled the initial pass, verify that if we found an
	 * _ADLIB_GOLD_8_MONO that it is not a Logitech Sound Wave man in disguise.
	 */
	if ((WORD)digicaps->wDeviceID == _ADLIB_GOLD_8_MONO) {
		rc = sosDIGIDetectFindNext(digicaps, &port);

		while ((i < 6) && (rc == 0)) {
			i++;

			if ((digicaps->wBitsPerSample == 8) && (digicaps->wChannels == 0)) {
				break;
			}

			if ((rc = sosDIGIDetectFindNext(digicaps, &port)) != 0) {
				break;
			}
		}

		/* If we don't have an error use the secondary device ID, after all,
		 * anything's better than an Adlib Gold.  If we do have an error or there
		 * is nothing we can use then the device ID is already set to the adlib
		 * gold so just let it rip.
		 */
		if ((rc == 0) && ((WORD)digicaps->wDeviceID == _SBPRO_8_MONO)) {
			return (digicaps->wDeviceID);
		}
	}

	return (device_id);
}


/****************************************************************************
*
* NAME
*     VQA_StartAudio - Starts audio playback
*
* SYNOPSIS
*     Error = VQA_StartAudio(VQA)
*
*     long VQA_StartAudio(VQAHandleP *);
*
* FUNCTION
*     Start the audio playback for the movie.
*
* INPUTS
*     VQA - Pointer to private VQA handle.
*
* RESULT
*     Error - 0 if successful, or -1 error code.
*
****************************************************************************/

long VQA_StartAudio(VQAHandleP *vqap)
{
	VQAConfig *config;
	VQAAudio  *audio;

	/* Save buffers for the callback routine */
	VQAP = vqap;

	/* Dereference commonly used data members for quicker access. */
	config = &vqap->Config;
	audio = &vqap->VQABuf->Audio;

	/* Return if already playing */
	if (AudioFlags & VQAAUDF_ISPLAYING) {
		return (-1);
	}

 	/* Set my driver handle */
	if (config->DigiHandle != -1) {
		audio->DigiHandle = config->DigiHandle;
	}

	if (audio->DigiHandle == (WORD)-1) {
		return (-1);
	}

	/*-------------------------------------------------------------------------
	 * Initialize the sample structure.
	 *-----------------------------------------------------------------------*/
	memset(&audio->sSOSSampleData, 0, sizeof(_SOS_START_SAMPLE));
  audio->sSOSSampleData.lpSamplePtr = (unsigned char *)audio->Buffer;
 	audio->sSOSSampleData.dwSampleSize = config->HMIBufSize;
	audio->sSOSSampleData.wVolume = (config->Volume << 7);
	audio->sSOSSampleData.wSampleID = HMI_SAMPLE;
	audio->sSOSSampleData.lpCallback = AudioCallback;

	/* Set the channel flags for the type of data we have. */
	if (audio->Channels == 2) {
		audio->sSOSSampleData.wChannel = _INTERLEAVED;
	} else {
		audio->sSOSSampleData.wChannel = _CENTER_CHANNEL;
	}

	/* If the card is unable to handle stereo data the we must notify the
	 * sound system to convert the stereo data to mono data during playback.
	 */
	if ((audio->Channels - 1) > audio->DigiCaps.wChannels) {
		audio->sSOSSampleData.wSampleFlags |= _STEREOTOMONO;
	}

	/* If the card is unable to handle the sample size of the audio data
	 * then we must notify the sound system to convert the audio data to
	 * the proper format.
	 */
	if (audio->BitsPerSample != audio->DigiCaps.wBitsPerSample) {
		if (audio->BitsPerSample > audio->DigiCaps.wBitsPerSample) {
			audio->sSOSSampleData.wSampleFlags |= _TRANSLATE16TO8;
		} else {
			audio->sSOSSampleData.wSampleFlags |= _TRANSLATE8TO16;
		}
	}

	/* Adjust the pitch if the driver is setup to playback at a different
	 * rate than what the sample was recorded at.
	 */
	if (audio->sSOSInitDriver.wSampleRate != audio->SampleRate) {
		ldiv_t result;

		result = ldiv(audio->SampleRate, audio->sSOSInitDriver.wSampleRate);
		audio->sSOSSampleData.dwSamplePitchAdd = (long)MAKE_LONG((short)result.quot,
				(short)(((long)result.rem * 0x10000L) / audio->sSOSInitDriver.wSampleRate));
		audio->sSOSSampleData.wSampleFlags |= _PITCH_SHIFT;
	}

	/* Start playback */
 	audio->SampleHandle = sosDIGIStartSample(audio->DigiHandle,
			&audio->sSOSSampleData);

	audio->Flags |= VQAAUDF_ISPLAYING;
	AudioFlags |= VQAAUDF_ISPLAYING;

	return (0);
}


/****************************************************************************
*
* NAME
*     VQA_StopAudio - Stop audio playback.
*
* SYNOPSIS
*     VQA_StopAudio(VQA)
*
*     void VQA_StopAudio(VQAHandleP *);
*
* FUNCTION
*     Halts the currently playing audio stream.
*
* INPUTS
*     VQA - Pointer to private VQAHandle.
*
* RESULT
*     NONE
*
****************************************************************************/

void VQA_StopAudio(VQAHandleP *vqap)
{
	VQAAudio *audio;

	/* Dereference for quick access. */
	audio = &vqap->VQABuf->Audio;

	/* Just return if not playing */
	if (AudioFlags & VQAAUDF_ISPLAYING) {
		sosDIGIStopSample(audio->DigiHandle, audio->SampleHandle);
		audio->Flags &= ~VQAAUDF_ISPLAYING;
		AudioFlags &= ~VQAAUDF_ISPLAYING;
	}

	VQAP = NULL;
}


/****************************************************************************
*
* NAME
*     CopyAudio - Copy data from Audio Temp buffer into Audio play buffer.
*
* SYNOPSIS
*     Error = CopyAudio(VQA)
*
*     long CopyAudio(VQAHandleP *);
*
* FUNCTION
*     This routine just copies the data in the TempBuf into the correct
*     spots in the audio play buffer.  If there is no room available in the
*     audio play buffer, the routine returns VQAERR_SLEEPING, which will put
*     the whole Loader to "sleep" while it waits for a free buffer.
*
*     If there's no data in the TempBuf to copy, the routine just returns 0.
*
* INPUTS
*     VQA - Pointer to private VQAHandle structure.
*
* RESULT
*     Error - 0 if successful or VQAERR_??? error code.
*
****************************************************************************/

long CopyAudio(VQAHandleP *vqap)
{
	VQAAudio  *audio;
	VQAConfig *config;
	long      startblock;
	long      endblock;
	long      len1,len2;
	long      i;

	/* Dereference commonly used data members for quicker access. */
	audio = &vqap->VQABuf->Audio;
	config = &vqap->Config;

	/* If audio is disabled, or if we're playing from a VOC file, or if
	 * there's no Audio Buffer, or if there's no data to copy, just return 0
	 */
	#if(VQAVOC_ON && VQAAUDIO_ON)
	if (((config->OptionFlags & VQAOPTF_AUDIO) == 0) || (vqap->vocfh != -1)
			|| (audio->Buffer == NULL) || (audio->TempBufLen == 0)) {
	#else  /* VQAVOC_ON */
	if (((config->OptionFlags & VQAOPTF_AUDIO) == 0) || (audio->Buffer == NULL)
			|| (audio->TempBufLen == 0)) {
	#endif /* VQAVOC_ON */

		return (0);
	}

	/* Compute start & end blocks to copy into */
	startblock = (audio->AudBufPos / config->HMIBufSize);
	endblock = (audio->AudBufPos + audio->TempBufLen) / config->HMIBufSize;

	if (endblock >= audio->NumAudBlocks) {
		endblock -= audio->NumAudBlocks;
	}

	/* If 'endblock' hasn't played yet, return VQAERR_SLEEPING */
	if (audio->IsLoaded[endblock] == 1) {
		return (VQAERR_SLEEPING);
	}

	/* Copy the data:
	 *
	 *  - If 'startblock' < 'endblock', copy the entire buffer
	 *  - Otherwise, fill to the end of the buffer with part of the data, then
	 *    copy the rest to the beginning of the buffer
	 */
	if (startblock <= endblock) {

		/* Copy data */
		memcpy((audio->Buffer + audio->AudBufPos), audio->TempBuf,
				audio->TempBufLen);

		/* Adjust current load position */
		audio->AudBufPos += audio->TempBufLen;

		/* Mark buffer as empty */
		audio->TempBufLen = 0;

		/* Set all blocks to loaded */
		for (i = startblock; i < endblock; i++) {
			audio->IsLoaded[i] = 1;
		}

		return (0);
	} else {

		/* Compute length of each piece */
		len1 = config->AudioBufSize - audio->AudBufPos;
		len2 = audio->TempBufLen - len1;

		/* Copy 1st piece into end of Audio Buffer */
		memcpy((audio->Buffer + audio->AudBufPos), audio->TempBuf, len1);

		/* Copy 2nd piece into start of Audio Buffer */
		memcpy(audio->Buffer, audio->TempBuf + len1, len2);

		/* Adjust load position */
		audio->AudBufPos = len2;

		/* Mark buffer as empty */
		audio->TempBufLen = 0;

		/* Set blocks to loaded */
		for (i = startblock; i < audio->NumAudBlocks; i++) {
			audio->IsLoaded[i] = 1;
		}

		for (i = 0; i < endblock; i++) {
			audio->IsLoaded[i] = 1;
		}

		return (0);
	}
}


/****************************************************************************
*
* NAME
*     AudioCallback - Sound system callback.
*
* SYNOPSIS
*     AudioCallback(DriverHandle, Action, SampleID)
*
*     void AudioCallback(WORD, WORD, WORD);
*
* FUNCTION
*     Our custom audio callback routine that services HMI.
*
* INPUTS
*     DriverHandle - HMI driver handle.
*     Action       - Action taken.
*     SampleID     - ID of sample.
*
* RESULT
*     NONE
*
****************************************************************************/

void far __cdecl AudioCallback(WORD wDriverHandle,WORD wAction,WORD wSampleID)
{
	VQAAudio  *audio;
	VQAConfig *config;

	/* Dereference commonly used data members for quicker access. */
	audio = &VQAP->VQABuf->Audio;
	config = &VQAP->Config;

	/* Suppress compiler warnings */
	wDriverHandle = wDriverHandle;
	wSampleID = wSampleID;

  /* See if we're called because the buffer is empty */
	if (wAction == _SAMPLE_PROCESSED) {

		/* Compute the 'NextBlock' index */
		audio->NextBlock = audio->CurBlock + 1;

		if (audio->NextBlock >= audio->NumAudBlocks) {
			audio->NextBlock = 0;
		}

		/* See if the next block has data in it; if so, update the audio
		 * buffer play position & the 'CurBlock' value.
		 * If not, don't change anything and replay this block.
		 */
		if (audio->IsLoaded[audio->NextBlock] == 1) {

			/* Update this block's status to loadable (0) */
			audio->IsLoaded[audio->CurBlock] = 0;

			/* Update position within audio buffer */
			audio->PlayPosition += config->HMIBufSize;
			audio->CurBlock++;

			if (audio->PlayPosition >= config->AudioBufSize) {
				audio->PlayPosition = 0;
				audio->CurBlock = 0;
			}
		} else {
			audio->NumSkipped++;
		}

		/* Start the new buffer playing */
	  audio->sSOSSampleData.lpSamplePtr = (unsigned char *)(audio->Buffer)
				+ audio->PlayPosition;

   	sosDIGIContinueSample(audio->DigiHandle, audio->SampleHandle,
				&audio->sSOSSampleData);

		audio->SamplesPlayed += config->HMIBufSize;
	}
}

/* Dummy function used to mark the beginning address of the file. */
static void EndAddr(void)
{
}



#else	//!VQADIRECT_SOUND

/**************************************************************************
*
* Start of Direct Sound code
*
*
* The direct sound implementation works by taking what would have been the
* HMI sound buffer and feeding the contents to a direct sound secondary
* buffer.
*
* Steve T. -  12/15/95
*
*
*
*
***************************************************************************/

BOOL	SuspendAudioCallback = FALSE;

/****************************************************************************
*
* NAME
*     VQA_StartTimerInt - Initialize system timer interrupt.
*
* SYNOPSIS
*     Error = VQA_StartTimerInt(VQA, Init)
*
*     long VQA_StartTimerInt(VQAHandeP *, long);
*
* FUNCTION
*     Initialize the HMI timer system and add our own timer event. If the
*     system has already been initialized then we are given access to the
*     the timer system.
*
* INPUTS
*     VQA  - Pointer to private VQAHandle structure.
*     Init - Initialize HMI timer system flag. (TRUE = Initialize)
*
* RESULT
*     Error - 0 if successful, -1 if error.
*
****************************************************************************/

long VQA_StartTimerInt(VQAHandleP *vqap, long init)
{
	VQAAudio *audio;

	/* Dereference for quick access. */
	audio = &vqap->VQABuf->Audio;

	/* Register the VQA_TickCount timer event. */
	if ((AudioFlags & VQAAUDF_HMITIMER) == (HMI_UNINIT<<VQAAUDB_HMITIMER)) {
		VQATimer = timeSetEvent ( 1000/VQA_TIMETICKS , 1 , TimerCallback , 0 , TIME_PERIODIC);

		if (VQATimer){

			/* Flag the timer interrupt as being registered. */
			AudioFlags |= (HMI_VQAINIT << VQAAUDB_HMITIMER);
		} else {
			return (-1);
		}
	}

	/* Flag availability of the timer interrupt. */
	audio->Flags |= (HMI_VQAINIT << VQAAUDB_HMITIMER);

	/* Increment the timer interrupt usage count. */
	TimerIntCount++;

	return (0);
}


/****************************************************************************
*
* NAME
*     VQA_StopTimerInt - Remove system timer interrupt.
*
* SYNOPSIS
*     VQA_StopTimerInt()
*
*     void VQA_StopTimerInt(void);
*
* FUNCTION
*     Remove our timer event from the HMI timer system. Uninitialize the
*     HMI timer system if we initialized it.
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
****************************************************************************/

void VQA_StopTimerInt(VQAHandleP *vqap)
{
	VQAAudio *audio;

	/* Dereference for quick access. */
	audio = &vqap->VQABuf->Audio;

	/* Decrement the timer interrupt usage count. */
	if (TimerIntCount) {
		TimerIntCount--;
	}

	/* Remove the timer interrrupt if it is initialized and the use count is
	 * zero. Otherwise, clear the callers timer interrupt availability flag.
	 */
	if (((AudioFlags & VQAAUDF_HMITIMER) == (HMI_VQAINIT<<VQAAUDB_HMITIMER))
			&& (TimerIntCount == 0)) {

		timeKillEvent(VQATimer);
		AudioFlags &= ~VQAAUDF_HMITIMER;
	} else {
		AudioFlags &= ~VQAAUDF_HMITIMER;
	}

}


/****************************************************************************
*
* NAME
*     TimerCallback - VQA timer event. (Called by Windoze)
*
* SYNOPSIS
*     TimerCallback()
*
*     void TimerCallback(void);
*
* FUNCTION
*     Our custom timer event. This is the timer event that we register with
*     Windows for system timing.
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
****************************************************************************/

void CALLBACK TimerCallback (UINT , UINT , DWORD , DWORD , DWORD)
{
	VQATickCount++;
}


/****************************************************************************
*
* NAME
*     VQA_OpenAudio - Open sound system.
*
* SYNOPSIS
*     Error = VQA_OpenAudio(VQAHandleP)
*
*     long VQA_OpenAudio(VQAHandleP *);
*
* FUNCTION
*     Initialise the sound system. Create a direct sound object and the
*     direct sound primary sound buffer if they dont already exist.
*
* INPUTS
*     VQAHandleP - Pointer to private VQAHandle.
*
* RESULT
*     Error - 0 if successful, -1 if error.
*
****************************************************************************/

long VQA_OpenAudio(VQAHandleP *vqap, HWND window)
{
	VQAData       *vqabuf;
	VQAAudio      *audio;
	VQAConfig     *config;
	VQAHeader     *header;
	unsigned char *driver_path;
	WORD          port;
	long          rc;

	/* Dereference data memebers for quicker access. */
	config = &vqap->Config;
	header = &vqap->Header;
	vqabuf = vqap->VQABuf;
	audio = &vqabuf->Audio;

	/* Reset the buffer position to the beginning. */
	audio->CurBlock = 0;


	/*
	**	create the direct sound object if it doesnt already exist
	*/
	if (!config->SoundObject){

		if ( DirectSoundCreate (NULL,&config->SoundObject,NULL) !=DS_OK ) {
			return (-1);
		}else{
			audio->CreatedSoundObject = TRUE;
			/*
			**	Give ourselves exclusive access to the sound card
			*/
			if ( config->SoundObject->SetCooperativeLevel( window, DSSCL_EXCLUSIVE ) != DS_OK){
				config->SoundObject->Release();
				return (-1);
			}
		}


		if (!config->PrimaryBufferPtr){
			/*
			** Define the format of the primary sound buffer
			*/
			memset (&audio->BufferDesc , 0 , sizeof(DSBUFFERDESC));
			audio->BufferDesc.dwSize=sizeof(DSBUFFERDESC);
			audio->BufferDesc.dwFlags=DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;

			memset (&audio->DsBuffFormat , 0 , sizeof(WAVEFORMATEX));
			audio->DsBuffFormat.wFormatTag	= WAVE_FORMAT_PCM;
			audio->DsBuffFormat.nChannels		= (unsigned short) 2;


			/*-------------------------------------------------------------------------
	 		* Compute the playback rate:
	 		*
	 		* - If Config->AudioRate is -1, use HMI_DATA_RATE scaled to the specified
	 		*   frame rate (so the audio plays faster if we're playing faster)
	 		* - otherwise, use the specified rate
	 		*-----------------------------------------------------------------------*/
			if (config->AudioRate != -1) {
				audio->DsBuffFormat.nSamplesPerSec = config->AudioRate;
			}
			else if (config->FrameRate != header->FPS) {
				audio->DsBuffFormat.nSamplesPerSec = ((audio->SampleRate*config->FrameRate)
						/ (unsigned long)header->FPS);

				config->AudioRate = audio->DsBuffFormat.nSamplesPerSec;
			} else {
				audio->DsBuffFormat.nSamplesPerSec = audio->SampleRate;
				config->AudioRate = audio->SampleRate;
			}

			audio->DsBuffFormat.wBitsPerSample	= (short) 16;
			audio->DsBuffFormat.nBlockAlign	= (unsigned short)( (audio->DsBuffFormat.wBitsPerSample/8) * audio->DsBuffFormat.nChannels);
			audio->DsBuffFormat.nAvgBytesPerSec= audio->DsBuffFormat.nSamplesPerSec * audio->DsBuffFormat.nBlockAlign;
			audio->DsBuffFormat.cbSize = 0;

			/*
			**  Create the direct sound primary sound buffer object
			*/
			if ( config->SoundObject->CreateSoundBuffer (&audio->BufferDesc ,
																		&config->PrimaryBufferPtr ,
																		NULL ) !=DS_OK ){
				if (audio->CreatedSoundObject){
					config->SoundObject->Release();
					audio->CreatedSoundObject = FALSE;
				}
				return (-1);
			}

			audio->CreatedSoundBuffer = TRUE;
		}
	}

	audio->Flags |= (HMI_VQAINIT << VQAAUDB_DIGIINIT);
	AudioFlags |= (HMI_VQAINIT << VQAAUDB_DIGIINIT);

	return (0);
}


/****************************************************************************
*
* NAME
*     VQA_CloseAudio - Close sound system
*
* SYNOPSIS
*     VQA_CloseAudio()
*
*     void VQA_CloseAudio(void);
*
* FUNCTION
*     Removes VQA's involvement in the audio system.
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
****************************************************************************/

void VQA_CloseAudio(VQAHandleP *vqap)
{
	VQAAudio		*audio;
	VQAConfig	*config;

	/* Dereference for quick access. */
	audio	 = &vqap->VQABuf->Audio;
	config = &vqap->Config;

	/*
	** If the audio is still playing then stop it
	*/
	VQA_StopAudio(vqap);

	/* Remove the windows callback event */
	//if ((AudioFlags & VQAAUDF_DIGIINIT) == (HMI_VQAINIT<<VQAAUDB_DIGIINIT)) {
	//	timeKillEvent(audio->TimerHandle);
	//}

	audio->Flags &= ~VQAAUDF_TIMERINIT;
	AudioFlags &= ~VQAAUDF_TIMERINIT;

	/*
	**  Remove the direct sound primary buffer if we created it
	*/
	if (audio->CreatedSoundBuffer){
		config->PrimaryBufferPtr->Stop();
		config->PrimaryBufferPtr->Release();
		config->PrimaryBufferPtr = NULL;
		audio->CreatedSoundBuffer = FALSE;
	}

	/*
	** If we created the sound object then remove that as well
	*/
	if (audio->CreatedSoundObject){
		config->SoundObject->Release();
		config->SoundObject = NULL;
	}

	audio->Flags &= ~VQAAUDF_DIGIINIT;
	AudioFlags &= ~VQAAUDF_DIGIINIT;
	AudioFlags &= ~VQAAUDF_ISPLAYING;

}


/****************************************************************************
*
* NAME
*     VQA_StartAudio - Starts audio playback
*
* SYNOPSIS
*     Error = VQA_StartAudio(VQA)
*
*     long VQA_StartAudio(VQAHandleP *);
*
* FUNCTION
*     Start the audio playback for the movie.
*
* INPUTS
*     VQA - Pointer to private VQA handle.
*
* RESULT
*     Error - 0 if successful, or -1 error code.
*
****************************************************************************/

long VQA_StartAudio(VQAHandleP *vqap)
{
	VQAConfig *config;
	VQAAudio  *audio;

	/* Save buffers for the callback routine */
	VQAP = vqap;

	/* Dereference commonly used data members for quicker access. */
	config = &vqap->Config;
	audio = &vqap->VQABuf->Audio;

	/* Return if already playing */
	if (AudioFlags & VQAAUDF_ISPLAYING) {
		return (-1);
	}

	/*
	** If we already have a direct sound secondary buffer then get rid of it
	*/
	if (audio->SecondaryBufferPtr != NULL){
		audio->SecondaryBufferPtr->Stop();
		audio->SecondaryBufferPtr->Release();
		audio->SecondaryBufferPtr = NULL;
	}
	/*
	** Make it big enough for 4 blocks of HMI data
	*/
	audio->SecondaryBufferSize = config->HMIBufSize*4;

	/*
	** Define the format for the secondary sound buffer
	*/
	memset (&audio->BufferDesc , 0 , sizeof(DSBUFFERDESC));
	audio->BufferDesc.dwSize				= sizeof(DSBUFFERDESC);
	audio->BufferDesc.dwFlags				= DSBCAPS_CTRLVOLUME;
	audio->BufferDesc.dwBufferBytes		= audio->SecondaryBufferSize;
	audio->BufferDesc.lpwfxFormat 		= (LPWAVEFORMATEX) &audio->DsBuffFormat;
	memset (&audio->DsBuffFormat , 0 , sizeof(WAVEFORMATEX));
	audio->DsBuffFormat.wFormatTag		= WAVE_FORMAT_PCM;
	audio->DsBuffFormat.nSamplesPerSec	= audio->SampleRate;
	audio->DsBuffFormat.nChannels			= audio->Channels;
	audio->DsBuffFormat.wBitsPerSample	= audio->BitsPerSample;
	audio->DsBuffFormat.nBlockAlign		= (short) ((audio->DsBuffFormat.wBitsPerSample/8) * audio->DsBuffFormat.nChannels);
	audio->DsBuffFormat.nAvgBytesPerSec	= audio->DsBuffFormat.nSamplesPerSec * audio->DsBuffFormat.nBlockAlign;

	/*
	** Create the secondary sound buffer object
	*/
	config->SoundObject->CreateSoundBuffer (&audio->BufferDesc , &audio->SecondaryBufferPtr , NULL);

	/*
	** Set the format of the primary buffer to the same as the secondary buffer
	*/
	config->PrimaryBufferPtr->Stop();
	config->PrimaryBufferPtr->SetFormat (&audio->DsBuffFormat);

	if (config->PrimaryBufferPtr->Play(0, 0, DSBPLAY_LOOPING) != DS_OK){
		return (-1);
	}

	/* Start playback */
	audio->EndLastAudioChunk = 0;
	audio->ChunksMovedToAudioBuffer = 0;
	Move_HMI_Audio_Block_To_Direct_Sound_Buffer();
	Move_HMI_Audio_Block_To_Direct_Sound_Buffer();
	audio->SecondaryBufferPtr->SetCurrentPosition (0);
	if (audio->SecondaryBufferPtr->Play(0, 0, DSBPLAY_LOOPING) != DS_OK){
		return (-1);
	}

	/*
	** Set the volume
	*/
	long volume = config->Volume << 7;
	audio->SecondaryBufferPtr->SetVolume(- ( ( (32768 - volume)*1000) >>15 ) );

	// Set orf 60hz timer
	audio->TimerHandle = timeSetEvent ( 1000/60 , 1 , AudioCallback , 0 , TIME_PERIODIC);

	audio->Flags |= VQAAUDF_ISPLAYING;
	AudioFlags |= VQAAUDF_ISPLAYING;

	return (0);
}


/****************************************************************************
*
* NAME
*     VQA_StopAudio - Stop audio playback.
*
* SYNOPSIS
*     VQA_StopAudio(VQA)
*
*     void VQA_StopAudio(VQAHandleP *);
*
* FUNCTION
*     Halts the currently playing audio stream.
*
* INPUTS
*     VQA - Pointer to private VQAHandle.
*
* RESULT
*     NONE
*
****************************************************************************/

void VQA_StopAudio(VQAHandleP *vqap)
{
	VQAAudio *audio;
	VQAConfig *config;

	/* Dereference commonly used data members for quicker access. */
	config = &vqap->Config;
	audio = &vqap->VQABuf->Audio;

	/* Just return if not playing */
	if (AudioFlags & VQAAUDF_ISPLAYING) {

		/*
		** Stop the primary buffer so the audio doesnt glitch
		*/
		config->PrimaryBufferPtr->Stop();

		/*
		** Remove the windows timer event
		*/
		timeKillEvent(audio->TimerHandle);
		audio->TimerHandle = NULL;

		/*
		** Kill the secondary sound buffer
		*/
		if (audio->SecondaryBufferPtr){
			audio->SecondaryBufferPtr->Stop();
			audio->SecondaryBufferPtr->Release();
			audio->SecondaryBufferPtr = NULL;
		}
		audio->Flags &= ~VQAAUDF_ISPLAYING;
		AudioFlags &= ~VQAAUDF_ISPLAYING;
	}

	VQAP = NULL;
}


/****************************************************************************
*
* NAME
*     CopyAudio - Copy data from Audio Temp buffer into Audio play buffer.
*
* SYNOPSIS
*     Error = CopyAudio(VQA)
*
*     long CopyAudio(VQAHandleP *);
*
* FUNCTION
*     This routine just copies the data in the TempBuf into the correct
*     spots in the audio play buffer.  If there is no room available in the
*     audio play buffer, the routine returns VQAERR_SLEEPING, which will put
*     the whole Loader to "sleep" while it waits for a free buffer.
*
*     If there's no data in the TempBuf to copy, the routine just returns 0.
*
* INPUTS
*     VQA - Pointer to private VQAHandle structure.
*
* RESULT
*     Error - 0 if successful or VQAERR_??? error code.
*
****************************************************************************/

long CopyAudio(VQAHandleP *vqap)
{
	VQAAudio  *audio;
	VQAConfig *config;
	long      startblock;
	long      endblock;
	long      len1,len2;
	long      i;

	/* Dereference commonly used data members for quicker access. */
	audio = &vqap->VQABuf->Audio;
	config = &vqap->Config;

	/* If audio is disabled, or if we're playing from a VOC file, or if
	 * there's no Audio Buffer, or if there's no data to copy, just return 0
	 */
	#if(VQAVOC_ON && VQAAUDIO_ON)
	if (((config->OptionFlags & VQAOPTF_AUDIO) == 0) || (vqap->vocfh != -1)
			|| (audio->Buffer == NULL) || (audio->TempBufLen == 0)) {
	#else  /* VQAVOC_ON */
	if (((config->OptionFlags & VQAOPTF_AUDIO) == 0) || (audio->Buffer == NULL)
			|| (audio->TempBufLen == 0)) {
	#endif /* VQAVOC_ON */

		return (0);
	}

	/* Compute start & end blocks to copy into */
	startblock = (audio->AudBufPos / config->HMIBufSize);
	endblock = (audio->AudBufPos + audio->TempBufLen) / config->HMIBufSize;

	if (endblock >= audio->NumAudBlocks) {
		endblock -= audio->NumAudBlocks;
	}

	/* If 'endblock' hasn't played yet, return VQAERR_SLEEPING */
	if (audio->IsLoaded[endblock] == 1) {
		return (VQAERR_SLEEPING);
	}

	/* Copy the data:
	 *
	 *  - If 'startblock' < 'endblock', copy the entire buffer
	 *  - Otherwise, fill to the end of the buffer with part of the data, then
	 *    copy the rest to the beginning of the buffer
	 */
	if (startblock <= endblock) {

		/* Copy data */
		memcpy((audio->Buffer + audio->AudBufPos), audio->TempBuf,
				audio->TempBufLen);

		/* Adjust current load position */
		audio->AudBufPos += audio->TempBufLen;

		/* Mark buffer as empty */
		audio->TempBufLen = 0;

		/* Set all blocks to loaded */
		for (i = startblock; i < endblock; i++) {
			audio->IsLoaded[i] = 1;
		}

		return (0);
	} else {

		/* Compute length of each piece */
		len1 = config->AudioBufSize - audio->AudBufPos;
		len2 = audio->TempBufLen - len1;

		/* Copy 1st piece into end of Audio Buffer */
		memcpy((audio->Buffer + audio->AudBufPos), audio->TempBuf, len1);

		/* Copy 2nd piece into start of Audio Buffer */
		memcpy(audio->Buffer, audio->TempBuf + len1, len2);

		/* Adjust load position */
		audio->AudBufPos = len2;

		/* Mark buffer as empty */
		audio->TempBufLen = 0;

		/* Set blocks to loaded */
		for (i = startblock; i < audio->NumAudBlocks; i++) {
			audio->IsLoaded[i] = 1;
		}

		for (i = 0; i < endblock; i++) {
			audio->IsLoaded[i] = 1;
		}

		return (0);
	}
}


BOOL	VQAAudioPaused = FALSE;

void VQA_PauseAudio(void)
{
	VQAAudio  	*audio;
	if (VQAP && VQAP->VQABuf){

		audio = &VQAP->VQABuf->Audio;

		if (audio->SecondaryBufferPtr){

			if (AudioFlags & VQAAUDF_ISPLAYING && !VQAAudioPaused) {

				audio->SecondaryBufferPtr->Stop();
				VQAAudioPaused = TRUE;
			}
		}
	}
}


void VQA_ResumeAudio(void)
{
	VQAAudio  	*audio;
	if (VQAP && VQAP->VQABuf){

		audio = &VQAP->VQABuf->Audio;

		if (audio->SecondaryBufferPtr){

			if (AudioFlags & VQAAUDF_ISPLAYING && VQAAudioPaused) {

				audio->SecondaryBufferPtr->SetCurrentPosition (0);
				audio->LastChunkPosition = 0;
				audio->EndLastAudioChunk = 0;
				Move_HMI_Audio_Block_To_Direct_Sound_Buffer();
				audio->SecondaryBufferPtr->Play(0,0,DSBPLAY_LOOPING);
				VQAAudioPaused = FALSE;
			}
		}
	}
}






/****************************************************************************
*
* NAME
*     AudioCallback - Sound system callback.
*
* SYNOPSIS
*     AudioCallback(DriverHandle, Action, SampleID)
*
*     void AudioCallback(WORD, WORD, WORD);
*
* FUNCTION
*     Our custom audio callback routine that services HMI.
*
* INPUTS
*     DriverHandle - HMI driver handle.
*     Action       - Action taken.
*     SampleID     - ID of sample.
*
* RESULT
*     NONE
*
****************************************************************************/

void CALLBACK AudioCallback ( UINT, UINT, DWORD, DWORD, DWORD )

{
	VQAAudio  	*audio;
	VQAConfig 	*config;
	DWORD			play_cursor;		//Position that direct sound is reading from
	DWORD			write_cursor;		//Position in buffer that we can write to
	HRESULT		return_code;
	BOOL			buffer_stopped = FALSE;
	DWORD			status;


	if (SuspendAudioCallback || VQAAudioPaused) return;

	/* Dereference commonly used data members for quicker access. */
	audio = &VQAP->VQABuf->Audio;
	config = &VQAP->Config;

	if (!audio->SecondaryBufferPtr) return;

	/*
	** See if we are nearing the end of the meaningful data in the direct sound buffer
	*/
	return_code = audio->SecondaryBufferPtr->GetCurrentPosition (&play_cursor , &write_cursor);

	BOOL write_more = FALSE;

	if (return_code == DSERR_BUFFERLOST || config->PrimaryBufferPtr->GetStatus(&status) == DSERR_BUFFERLOST){
		config->PrimaryBufferPtr->Restore();
		audio->SecondaryBufferPtr->Restore();
		audio->SecondaryBufferPtr->Stop();
		buffer_stopped = TRUE;
		audio->SecondaryBufferPtr->SetCurrentPosition (0);
		audio->LastChunkPosition = 0;
		audio->EndLastAudioChunk = 0;
		write_more = TRUE;
	}


	if (play_cursor < audio->EndLastAudioChunk){
		if (audio->EndLastAudioChunk - play_cursor <= audio->SecondaryBufferSize/4){
			write_more = TRUE;
		}
	}else{
		if (( play_cursor > audio->SecondaryBufferSize*3/4) && audio->EndLastAudioChunk==0 ){
			write_more = TRUE;
		}
	}

	/*
	** See if we need to fill the buffer
	*/
	if (write_more){
		if (Move_HMI_Audio_Block_To_Direct_Sound_Buffer()){

			/*
			** Start the buffer playing again if we had to stop it
			*/
			if (buffer_stopped){
				audio->SecondaryBufferPtr->Play(0,0,DSBPLAY_LOOPING);
			}
		}
	}
}









/***********************************************************************************************
 * Move_HMI_Audio_Block_To_Direct_Sound_Buffer -- moves an audio block which would have been   *
 *                                                played by HMI into a direct sound            *
 *                                                secondary buffer                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   BOOL was block moved                                                              *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *    12/21/95 9:51AM ST : Created                                                              *
 *=============================================================================================*/
extern int VQAMovieDone;
BOOL Move_HMI_Audio_Block_To_Direct_Sound_Buffer (void)
{
	VQAAudio  	*audio;
	VQAConfig 	*config;

	LPVOID		play_buffer_ptr;	//Beginning of locked area of buffer
	LPVOID		dummy_buffer_ptr;	//Length of locked area in buffer
	DWORD			lock_length1;		//Beginning of second locked area in buffer
	DWORD			lock_length2;		//Length of second locked area in buffer
	unsigned		next_fill_pos;
	HRESULT		return_code;
	DWORD			status;


	/* Dereference commonly used data members for quicker access. */
	audio = &VQAP->VQABuf->Audio;
	config = &VQAP->Config;


	/*************************************************************************
	**
	** Copy the data from the HMI play position into the direct sound buffer
	**
	*/
	next_fill_pos = audio->EndLastAudioChunk;

	/*
	** Lock the buffer to get a pointer to it
	*/
	return_code= audio->SecondaryBufferPtr->Lock ((DWORD)next_fill_pos,
																	(DWORD)config->HMIBufSize,
																	&play_buffer_ptr,
																	&lock_length1,
																	&dummy_buffer_ptr,
																	&lock_length2,
																	0 );

	if (return_code!=DS_OK) return(FALSE);
	if (config->PrimaryBufferPtr->GetStatus(&status) == DSERR_BUFFERLOST) return(FALSE);

	/*
	** Copy the HMI audio buffer to the direct sound buffer
	*/
	memcpy ((char*)play_buffer_ptr , (char*)((unsigned)audio->Buffer + (unsigned)audio->PlayPosition) , config->HMIBufSize);

	/*
	** Unlock the direct sound buffer
	*/
	audio->SecondaryBufferPtr->Unlock(play_buffer_ptr,
												lock_length1,
												dummy_buffer_ptr,
												lock_length2);
	/*
	** Update our audio data pointers
	*/
	audio->LastChunkPosition = next_fill_pos;
	audio->EndLastAudioChunk = next_fill_pos + config->HMIBufSize;
	if (audio->EndLastAudioChunk >= audio->SecondaryBufferSize){
		audio->EndLastAudioChunk = 0;
	}

	/* Compute the 'NextBlock' index */
	audio->NextBlock = audio->CurBlock + 1;

	if (audio->NextBlock >= audio->NumAudBlocks) {
		audio->NextBlock = 0;
	}

	/* See if the next block has data in it; if so, update the audio
		* buffer play position & the 'CurBlock' value.
		* If not, don't change anything and replay this block.
		*/
	if (audio->IsLoaded[audio->NextBlock] == 1) {

		/* Update this block's status to loadable (0) */
		audio->IsLoaded[audio->CurBlock] = 0;

		/* Update position within audio buffer */
		audio->PlayPosition += config->HMIBufSize;
		audio->CurBlock++;

		if (audio->PlayPosition >= config->AudioBufSize) {
			audio->PlayPosition = 0;
			audio->CurBlock = 0;
		}
		audio->ChunksMovedToAudioBuffer++;
		return (TRUE);
	} else {
		if (VQAMovieDone){
			audio->ChunksMovedToAudioBuffer++;
		}
		audio->NumSkipped++;
		/*
		** Enable frame skipping to prevent this happening again
		*/
		config->DrawFlags &= ~VQACFGF_NOSKIP;
		return (FALSE);
	}
}





#endif	//!VQADIRECT_SOUND


#endif /* VQAAUDIO_ON */












/****************************************************************************
*
* NAME
*     VQA_SetTimer - Resets current time to given tick value.
*
* SYNOPSIS
*     VQA_SetTimer(Time, Method)
*
*     void VQA_SetTimer(long, long);
*
* FUNCTION
*     Sets 'TickOffset' to a value that will make the current time look like
*     the time passed in. This function allows the player to be "paused",
*     by recording the time of the pause, and then setting the timer to
*     that time. The timer method used by the player is also set. The method
*     selected is not neccesarily the method that will be used because some
*     timer methods work with only certain playback conditions. (EX: The
*     audio DMA timer method cannot be used if there is not any audio
*     playing.)
*
* INPUTS
*     Time   - Value to set current time to.
*     Method - Timer method to use.
*
* RESULT
*     NONE
*
****************************************************************************/

void VQA_SetTimer(VQAHandleP *vqap, long time, long method)
{
	unsigned long curtime;

	#if(VQAAUDIO_ON)
	VQAAudio      *audio;

	/* Dereference for quick access. */
	audio = &vqap->VQABuf->Audio;

	/* If the client does not have a preferencee then pick a method
	 * based on the state of the player.
	 */
	if (method == VQA_TMETHOD_DEFAULT) {

		/* If we are playing audio, use the audio DMA position. */
		if (AudioFlags & VQAAUDF_ISPLAYING) {
			method = VQA_TMETHOD_AUDIO;
		}

		/* Otherwise use the HMI timer if it is initialized. */
		else if (AudioFlags & VQAAUDF_HMITIMER) {
			method = VQA_TMETHOD_INT;
		}

		/* If all else fails resort the the "jerky" DOS time. */
		else {
			method = VQA_TMETHOD_DOS;
		}
	} else {

		/* We cannot use the DMA position if there isn't any audio playing. */
		if (!(AudioFlags & VQAAUDF_ISPLAYING) && (method == VQA_TMETHOD_AUDIO)) {
			method = VQA_TMETHOD_INT;
		}

		/* We cannot use the timer if it has not been initialized. */
		if (!(AudioFlags & VQAAUDF_HMITIMER) && (method == VQA_TMETHOD_INT)) {
			method = VQA_TMETHOD_DOS;
		}
	}

	TimerMethod = method;
	#else
	method = method;
	#endif

	TickOffset = 0L;
	curtime = VQA_GetTime(vqap);
	TickOffset = (time - curtime);
}


/****************************************************************************
*
* NAME
*     VQA_GetTime - Return current time.
*
* SYNOPSIS
*     Time = VQA_GetTime()
*
*     unsigned long VQA_GetTime(void);
*
* FUNCTION
*     This routine returns timer ticks computed one of 3 ways:
*
*     1) If audio is playing, the timer is based on the DMA buffer position:
*        Compute the number of audio samples that have actually been played.
*        The following internal HMI variables are used:
*
*          _lpSOSDMAFillCount[drv_handle]: current DMA buffer position
*          _lpSOSSampleList[drv_handle][samp_handle]:
*          sampleTotalBytes: total bytes sent by HMI to the DMA buffer
*          sampleLastFill: HMI's last fill position in DMA buffer
*
*        So, the number of samples actually played is:
*
*          sampleTotalBytes - <DMA_diff>
*          where <DMA_diff> is how far ahead sampleLastFill is in front of
*          _lpSOSDMAFillCount: (sampleLastFill - _lpSOSDMAFillCount)
*
*        These values are indices into a circular DMA buffer, so:
*
*          if (sampleLastFill >= _lpSOSDMAFillCount)
*            <DMA_diff> = sampleLastFill - _lpSOSDMAFillCount
*          else
*            <DMA_diff> = (DMA_BUF_SIZE - lpSOSDMAFillCount) + sampleLastFill
*
*        Note that, if using the stereo driver with mono data, you must
*        divide LastFill & FillCount by 2, but not TotalBytes. If using the
*        stereo driver with stereo data, you must divide all 3 variables
*        by 2.
*
*     2) If no audio is playing, but the timer interrupt is running,
*        VQATickCount is used as the timer
*
*     3) If no audio is playing & no timer interrupt is going, the DOS 18.2
*        system timer is used.
*
*     Regardless of the method, TickOffset is used as an offset from the
*     computed time.
*
* INPUTS
*     NONE
*
* RESULT
*     Time - Time in VQA_TIMETICKS
*
****************************************************************************/
unsigned long VQA_GetTime(VQAHandleP *vqap)
{
	#if(VQAAUDIO_ON)
	VQAAudio      *audio;
	VQAConfig     *config;
	unsigned long fillcount;
	unsigned long lastfill;
	unsigned long dma_diff;
	unsigned long totalbytes;
	unsigned long samples;
	DWORD				play_cursor;		//Position that direct sound is reading from
	DWORD				write_cursor;		//Position in buffer that we can write to
	unsigned			temp;
	#endif

	// MEG 09.25.95 - changed from long to unsigned long
	unsigned long ticks;

	#if(VQAAUDIO_ON)
	switch (TimerMethod) {

		/* If Audio is playing then timing is based on the audio DMA buffer
		 * position.
		 */
		case VQA_TMETHOD_AUDIO:

			/* Dereference commonly used data members for quicker access. */
			audio = &vqap->VQABuf->Audio;
			config = &vqap->Config;
			//vqabuf = ((VQAHandleP *)vqa)->VQABuf;

			#if (VQADIRECT_SOUND)

				totalbytes = (audio->ChunksMovedToAudioBuffer) * config->HMIBufSize;

			if (audio->SecondaryBufferPtr &&
				audio->SecondaryBufferPtr->GetCurrentPosition (&play_cursor , &write_cursor) == DS_OK){
				totalbytes = (audio->ChunksMovedToAudioBuffer) * config->HMIBufSize;

				if (audio->LastChunkPosition){
					totalbytes += play_cursor - audio->LastChunkPosition;
				}else {
					if (play_cursor > audio->SecondaryBufferSize*3/4){
						totalbytes -= audio->SecondaryBufferSize - play_cursor;
					} else {
						totalbytes += play_cursor - audio->LastChunkPosition;
					}
				}


			}else{
				totalbytes = (audio->ChunksMovedToAudioBuffer-1) * config->HMIBufSize;
			}


			samples = totalbytes/audio->DsBuffFormat.nChannels;
			samples = samples/(audio->DsBuffFormat.wBitsPerSample >> 3);

			#else		//VQADIRECT_SOUND

			/* Compute our current position in the audio track by getting the
			 * bytes processed by HMI. Then adjust the bytes processed by the
			 * position of the DMA fill handler, this should tell us exactly
			 * where we are in the audio track.
			 */
			fillcount = (unsigned long)(*_lpSOSDMAFillCount[audio->DigiHandle]);
			lastfill = _lpSOSSampleList[audio->DigiHandle][audio->SampleHandle]->sampleLastFill;
			totalbytes = _lpSOSSampleList[audio->DigiHandle][audio->SampleHandle]->sampleTotalBytes;

			if (totalbytes == 0) {
				dma_diff = 0;
			} else {
				if (lastfill > fillcount) {
					dma_diff = lastfill - fillcount;
				} else {
					dma_diff = (config->HMIBufSize - fillcount) + lastfill;
				}

				if (dma_diff > totalbytes) {
					dma_diff = totalbytes;
				}
			}

			/* Calculate the number of samples by taking the total number of
			 * bytes processed and divide it by the number of channels and
			 * bits per sample.
			 */
			samples = totalbytes - dma_diff;
			samples -= (audio->NumSkipped * config->HMIBufSize);
			samples /= (audio->Channels * (audio->BitsPerSample >> 3));

			#endif	//VQADIRECT_SOUND

			/* The elapsed ticks is calculated by the number of samples
			 * processed times the tick resolution per second divided by the
			 * sample rate.
			 */
			ticks = (long)((samples * VQA_TIMETICKS) / audio->SampleRate);
			ticks += TickOffset;
			break;

		/* No audio playing, but timer interrupt is going; use VQATickCount */
		case VQA_TMETHOD_INT:
			ticks = (VQATickCount + TickOffset);
			break;

		/* No interrupts are going at all; use DOS's time */
		default:
		case VQA_TMETHOD_DOS:
			{
			struct timeb mytime;

			ftime(&mytime);
 			ticks = (unsigned long)mytime.time*1000L+(unsigned long)mytime.millitm;
			ticks = ((ticks * VQA_TIMETICKS) / 1000L);
			ticks += TickOffset;
			}
			break;
	}
	#else
	{
	// MEG 09.23.95 - Use Windows timer.
#if( ! USE_WINDOWS_TIME )

	struct timeb mytime;

	ftime(&mytime);
	ticks = (unsigned long)mytime.time*1000L+(unsigned long)mytime.millitm;

#else

	ticks = Get_Game_Time();

#endif

//	ticks = ((ticks * VQA_TIMETICKS) / 1000L);

	// MEG 09.25.95 - multiply by 3, divide by 50 instead of multiplying by
	// 60 and dividing by 1000.  The reason for this is that multiplying by
	// 60 causes an overflow pretty quickly if the value from Get_Game_Time
	// is high.
	ticks = ((ticks * 3L) / 50L);

	ticks += TickOffset;
	}
	#endif

	return (ticks);
}


/****************************************************************************
*
* NAME
*     VQA_TimerMethod - Get timer method being used.
*
* SYNOPSIS
*     Method = VQA_TimerMethod()
*
*     long VQA_TimerMethod(void);
*
* FUNCTION
*     Returns the ID of the current timer method being used.
*
* INPUTS
*     NONE
*
* RESULT
*     Method - Method used for the timer.
*
****************************************************************************/

long VQA_TimerMethod(void)
{
	#if(VQAAUDIO_ON)
	return (TimerMethod);
	#else
	return (VQA_TMETHOD_DOS);
	#endif
}

#ifdef __WATCOMC__
#pragma pack(1);
#endif