/*
**	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/>.
*/

/* $Id: soundio.cpp 1.41 1994/06/20 15:01:39 joe_bostic Exp $ */
/***********************************************************************************************
 **             C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S             **
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Sound Library                                                *
 *                                                                                             *
 *                    File Name : SOUND.CPP                                                    *
 *                                                                                             *
 *                   Programmer : Joe L. Bostic                                                *
 *                                                                                             *
 *                   Start Date : July 22, 1991                                                *
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 *   File_Callback -- called to fill queue buffer for streaming sample     						  *
 *   Stream_Sample_Volume -- generic streaming sample playback init                            *
 *   Stream_Sample -- generic streaming sample playback init                                   *
 *   File_Stream_Sample -- Streams a sample directly from a file.                              *
 *   File_Stream_Preload -- Handles initial proload of a streaming samples                     *
 *   File_Stream_Sample_Volume -- Streams a sample directly from a file w/volume.              *
 *   Sound_Callback -- Audio driver callback function.                                         *
 *   Load_Sample -- Loads a digitized sample into RAM.                                         *
 *   Load_Sample_Into_Buffer -- Loads a digitized sample into a buffer.                        *
 *   Free_Sample -- Frees a previously loaded digitized sample.                                *
 *   Sound_End -- Uninitializes the sound driver.                                              *
 *   Stop_Sample -- Stops any currently playing sampled sound.                                 *
 *   Sample_Status -- Queries the current playing sample status (if any).                      *
 *   Sample_Length -- returns length of a sample in ticks                                      *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#pragma pack(4)
#include	"soundint.h"
#include <dos.h>
#include <mem.h>
#include <stdio.h>
#include <string.h>
#include	<direct.h>
#include	<stdlib.h>
#include	<wwmem.h>
#include	<keyboard.h>
#include	<file.h>
#include	<i86.h>
#include <timer.h>
#pragma pack(1)
#include "audio.h"
#pragma pack(4)


//int Mono_Printf(char const *string, ...);
#include	<mono.h>

/*
**	If this is defined, then the streaming audio buffer will be filled
**	to maximum whenever filling is to occur. If undefined, it will fill
**	the streaming buffer in smaller chunks.
*/
#define	SIMPLE_FILLING

/*
**	This define (if present) enables the simple HMI hardware initialization process.
**	The process does not do auto detection, but rather takes the value directly from
**	the setup program and uses that as the sound card number.  The only "detection" it
**	does is to recognized the presence of the card and fetch its settings.
*/
#define	SIMPLE_HMI_INIT


/*
**	This is the rate that the maintenance callback gets called.
*/
#define	MAINTENANCE_RATE			60

/*
**	Size of the temporary buffer in XMS/EMS that direct file
**	streaming of sounds will allocate.
*/
#define	STREAM_BUFFER_SIZE		(128L*1024L)

/*
** Define the number of "StreamBufferSize" blocks that are read in
** at a minimum when the streaming sample load callback routine
** is called.  We will IGNORE loads that are less that this in order
** to avoid constant seeking on the CD.
*/
#define	STREAM_CUSHION_BLOCKS	4


/*
**	This is the maximum size that a sonarc block can be.  All sonarc blocks
**	must be either a multiple of this value or a binary root of this value.
*/
#define	LARGEST_SONARC_BLOCK		2048


//////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////// structs ///////////////////////////////////////

static _SOS_CAPABILITIES	DigiCaps;
static _SOS_HARDWARE 		DigiHardware;
static WORD 					MidiHandle = -1;
static unsigned int far		DigiTimer = 0;
static unsigned int far		MaintainTimer = 0;
static unsigned int far		SystemTimer = 0;
static int						Bits_Per_Sample;
VOID 								*DigiBuffer = NULL;
static BOOL 					StartingFileStream = FALSE;
short 							StreamLowImpact = FALSE;
MemoryFlagType 				StreamBufferFlag = MEM_NORMAL;
int 								Misc;
SFX_Type 						SoundType;
Sample_Type 					SampleType;
int								ReverseChannels = FALSE;

/*=========================================================================*/
/* The following PRIVATE functions are in this file:                       */
/*=========================================================================*/

static BOOL File_Callback(WORD id, WORD *odd, VOID **buffer, LONG *size);
static int cdecl Stream_Sample_Vol(void *buffer, long size, BOOL (*callback)(WORD id, WORD *odd, VOID **buffer, LONG *size), int volume, int handle);
static int cdecl Stream_Sample(void *buffer, long size, BOOL (*callback)(WORD id, WORD *odd, VOID **buffer, LONG *size));
/*= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =*/




/***************************************************************************
 * FILE_CALLBACK -- called to fill queue buffer for streaming sample       *
 *                                                                         *
 * This callback is called whenever the queue buffer playback has begun		*
 * and another buffer is needed for queuing up.  Returns TRUE if there		*
 * is more data to read from the file.													*
 *                                                                         *
 * INPUT:	WORD id			- the sample id number                          *
 *				WORD *odd		- which sample buffer to put info in				*
 *				VOID **buffer	- the buffer pointer to load data into				*
 *				LONG *size		- the amount to load										*
 *                                                                         *
 * OUTPUT:  BOOL true if more data to load, FALSE if done loading          *
 *                                                                         *
 * HISTORY:                                                                *
 *   07/17/1995 PWG : Created.                                             *
 *=========================================================================*/
static BOOL File_Callback(WORD id, WORD *odd, VOID **buffer, LONG *size)
{
	SampleTrackerType	*st;		// Pointer to sample playback control struct.
	VOID					*ptr;		// Pointer to working portion of file buffer.

	if (id != -1) {
		st = &LockedData.SampleTracker[id];
		ptr = st->FileBuffer;
		if (ptr) {

			/*
			**	Move the next pending block into the primary 
			**	position.  Do this only if the queue pointer is
			**	null.
			*/
			st->DontTouch = TRUE;
			if (!*buffer && st->FilePending) {
				*buffer = Add_Long_To_Pointer(ptr, (long)(*odd % LockedData.StreamBufferCount)*(long)LockedData.StreamBufferSize);
				st->FilePending--;
				*odd = *odd + 1;
				if (!st->FilePending) {
					*size = st->FilePendingSize;
				} else {
					*size = LockedData.StreamBufferSize;
				}
			}
			st->DontTouch = FALSE;
			maintenance_callback();

			/*
			**	If the file handle is still valid, then read in the next
			**	block and add it to the next pending slot available.
			*/
			if (st->FilePending <
				(StreamLowImpact ? (LockedData.StreamBufferCount>>1) : ((LockedData.StreamBufferCount-3))) && st->FileHandle != ERROR) {


				int num_empty_buffers;

#ifdef SIMPLE_FILLING
				num_empty_buffers = (LockedData.StreamBufferCount-2) - st->FilePending;
#else

				//
				// num_empty_buffers will be from 1 to StreamBufferCount
				//
				if (StreamLowImpact) {
					num_empty_buffers = MIN((LockedData.StreamBufferCount >> 1)+STREAM_CUSHION_BLOCKS, (LockedData.StreamBufferCount - 2) - st->FilePending);
				}
				else {
					num_empty_buffers = (LockedData.StreamBufferCount - 2) - st->FilePending;
				}
#endif
				


				while (num_empty_buffers && st->FileHandle != ERROR) {
					int	tofill;
					long	psize;

					tofill = (*odd + st->FilePending) % LockedData.StreamBufferCount;

					ptr = Add_Long_To_Pointer(st->FileBuffer, (long)tofill * (long)LockedData.StreamBufferSize);
					psize = Read_File(st->FileHandle, ptr, LockedData.StreamBufferSize);

					/*
					**	If less than the requested amount of data was read, this
					**	indicates that the source file is exhausted.  Flag the source
					**	file as closed so that no further reading is attempted.
					*/
					if (psize != LockedData.StreamBufferSize) {
						Close_File(st->FileHandle);
						st->FileHandle = ERROR;
					}

					/*
					**	If any real data went into the pending buffer, then flag
					**	that this buffer is valid.
					*/
					if (psize) {
						st->DontTouch = TRUE;
						st->FilePendingSize = psize;
						st->FilePending++;
						st->DontTouch = FALSE;
						maintenance_callback();
					}
					num_empty_buffers--;
				}

				/*
				**	After filling all pending buffers, check to see if the queue buffer
				**	is empty.  If so, then assign the first available pending buffer to the
				**	queue.
				*/
				st->DontTouch = TRUE;
				if (!st->QueueBuffer && st->FilePending) {
					st->QueueBuffer = Add_Long_To_Pointer(st->FileBuffer, (long)(st->Odd%LockedData.StreamBufferCount)*(long)LockedData.StreamBufferSize);
					st->FilePending--;
					st->Odd++;
					if (!st->FilePending) {
						st->QueueSize = st->FilePendingSize;
					} else {
						st->QueueSize = LockedData.StreamBufferSize;
					}
				}
				st->DontTouch = FALSE;
				maintenance_callback();
			}

			/*
			**	If there are no more buffers that the callback routine
			**	can slot into the primary position, then signal that
			**	no furthur callbacks are needed.
			*/
			if (st->FilePending) {
				return(TRUE);
			}
		}
	}
	return(FALSE);
}

/***************************************************************************
 * STREAM_SAMPLE_VOLUME -- generic streaming sample playback init          *
 *                                                                         *
 * INPUT:                                                                  *
 *                                                                         *
 * OUTPUT:                                                                 *
 *                                                                         *
 * WARNINGS:                                                               *
 *                                                                         *
 * HISTORY:                                                                *
 *   07/17/1995 PWG : Created.                                             *
 *=========================================================================*/
static int cdecl Stream_Sample_Vol(void *buffer, long size, BOOL (*callback)(WORD id, WORD *odd, VOID **buffer, LONG *size), int volume, int handle)
{
	int						playid=-1;	// Sample play ID.
	SampleTrackerType		*st;			// Working pointer to sample control structure.
	long						oldsize;		// Copy of original sound size.
	AUDHeaderType			header;

	if (buffer && size && LockedData.DigiHandle != -1) {

		/*
		**	Start the first section of the sound playing.
		*/
		Mem_Copy(buffer, &header, sizeof(header));
		oldsize = header.Size;
		header.Size = size-sizeof(header);
		Mem_Copy(&header, buffer, sizeof(header));
		playid = Play_Sample_Handle(buffer, 0xFF, volume, 0x0, handle);
		header.Size = oldsize;
		Mem_Copy(&header, buffer, sizeof(header));

		/*
		**	If the sample actually started playing, then flag this
		**	sample as a streaming type and signal for a callback
		**	to occur.
		*/
		if (playid != -1) {
			st = &LockedData.SampleTracker[playid];

			st->Callback = callback;
			st->Odd = 0;
		}
	}
	return (playid);
}

/***************************************************************************
 * STREAM_SAMPLE -- generic streaming sample playback init          			*
 *                                                                         *
 * INPUT:                                                                  *
 *                                                                         *
 * OUTPUT:                                                                 *
 *                                                                         *
 * WARNINGS:                                                               *
 *                                                                         *
 * HISTORY:                                                                *
 *   07/17/1995 PWG : Created.                                             *
 *=========================================================================*/
static int cdecl Stream_Sample(void *buffer, long size, BOOL (*callback)(WORD id, WORD *odd, VOID **buffer, LONG *size), int handle)
{
	return Stream_Sample_Vol(buffer, size, callback, 0xFF, handle);
}



/***********************************************************************************************
 * File_Stream_Sample -- Streams a sample directly from a file.                                *
 *                                                                                             *
 *    This will take the file specified and play it directly from disk.                        *
 *    It performs this by allocating a temporary buffer in XMS/EMS and                         *
 *    then keeping this buffer filled by the Sound_Callback() routine.                         *
 *                                                                                             *
 * INPUT:   filename -- The name of the file to play.                                          *
 *                                                                                             *
 * OUTPUT:  Returns the handle to the sound -- just like Play_Sample().                        *
 *                                                                                             *
 * WARNINGS:   The temporary buffer is allocated when this routine is                          *
 *             called and then freed when the sound is finished.  Keep                         *
 *             this in mind.                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/06/1994 JLB : Created.                                                                 *
 *=============================================================================================*/
int File_Stream_Sample(char const *filename, BOOL real_time_start)
{
	return File_Stream_Sample_Vol(filename, 0xFF, real_time_start);
}

/***************************************************************************
 * FILE_STREAM_PRELOAD -- Handles initial proload of streaming samples     *
 *                                                                         *
 * This function is called before a sample which streams from disk is      *
 * started.  It can be called to either fill the buffer in small chunks		*
 * from the call back routine or to fill the entire buffer at once.  This	*
 * is wholely dependant on whether the Loading bit is set within the 		*
 * sample tracker.																			*
 *                                                                         *
 * INPUT:		LockedData.SampleTracker * to the header which tracks this samples     *
 *										 processing.											*
 *                                                                         *
 * OUTPUT:                                                                 *
 *                                                                         *
 * WARNINGS:                                                               *
 *                                                                         *
 * HISTORY:                                                                *
 *   06/05/1995 PWG : Created.                                             *
 *=========================================================================*/

void File_Stream_Preload(int handle)
{
	SampleTrackerType	*st	= &LockedData.SampleTracker[handle];
	int	fh						= st->FileHandle;
	int 	maxnum				= (LockedData.StreamBufferCount >> 1) + STREAM_CUSHION_BLOCKS;
	void 	*buffer				= st->FileBuffer;
	int	num;

	/*
	** Figure just how much we need to load.  If we are doing the load in progress
	** then we will only load two blocks.
	*/
	if (st->Loading) {
		num = st->FilePending + 2;
		num = MIN(num, maxnum);
	} else {
		num = maxnum;
	}

	//printf("Before buffer load!\n");
	/*
	** Loop through the blocks and load up the number we need.
	*/
	for (int index = st->FilePending; index < num; index++) {
		long s = Read_File(fh, Add_Long_To_Pointer(buffer, (long)index * (long)LockedData.StreamBufferSize), LockedData.StreamBufferSize);
		//printf("Reading block of size %d Stream Buffer = %d\n");
		if (s) {
			st->FilePendingSize = s;
			st->FilePending++;
		}
		if (s < LockedData.StreamBufferSize) break;
	}

	/*
	** If the last block was incomplete (ie. it didn't completely fill the buffer) or
	** we have now filled up as much of the Streaming Buffer as we need to, then now is
	** the time to kick off the sample.
	*/
	if (st->FilePendingSize < LockedData.StreamBufferSize || index == maxnum) {
		//printf("Before Stream Sample Volume!\n");
		/*
		** Actually start the sample playing, and don't worry about the file callback
		** it won't be called for a while.
		*/
		{
			int old						= LockedData.SoundVolume;
			int size						= (st->FilePending == 1) ? st->FilePendingSize : LockedData.StreamBufferSize;
			LockedData.SoundVolume	= LockedData.ScoreVolume;

			StartingFileStream 		= TRUE;
			Stream_Sample_Vol(buffer, size, File_Callback, st->Volume, handle);
			StartingFileStream 		= FALSE;
			LockedData.SoundVolume 	= old;
		}
		//printf("After Stream Sample Volume!\n");
		/*
		** The Sample is finished loading (if it was loading in small pieces) so record that
		** so that it will now use the active logic in the file call back.
		*/
		st->Loading					= FALSE;

		/*
		** Decrement the file pending because the first block is already playing thanks
		** to the play sample call above.
		*/
		st->FilePending--;

		/*
		** If File pending is now a zero, then we only preloaded one block and there
		** is nothing more to play.  So clear the sample tracing structure of the
		** information it no longer needs.
		*/
		if (!st->FilePending) {
			st->Odd					= 0;
			st->QueueBuffer		= 0;
			st->QueueSize			= 0;
			st->FilePendingSize	= 0;
			st->Callback 			= NULL;
			Close_File(fh);
		} else {
			/*
			**	The QueueBuffer counts as an already played block so remove it from the total.
			** Note: We didn't remove it before because there might not have been one.
			*/
			st->FilePending--;

			/*
			** When we start loading we need to start past the first two blocks.  Why this
			** is called Odd, I haven't got the slightest.
			*/
			st->Odd 					= 2;

			/*
			** If the file pending size is less than the stream buffer, then the last block
			** we loaded was the last block period.  So close the file and reset the handle.
			*/
			if (st->FilePendingSize != LockedData.StreamBufferSize) {
				Close_File(fh);
				st->FileHandle = ERROR;
			}

			/*
			** The Queue buffer needs to point at the next block to be processed.  The size
			** of the queue is dependant on how many more blocks there are.
			*/
			st->QueueBuffer = Add_Long_To_Pointer(buffer, LockedData.StreamBufferSize);
			if (!st->FilePending) {
				st->QueueSize = st->FilePendingSize;
			} else {
				st->QueueSize = LockedData.StreamBufferSize;
			}
		}
	}
}
/***********************************************************************************************
 * File_Stream_Sample_Vol -- Streams a sample directly from a file.                            *
 *                                                                                             *
 *    This will take the file specified and play it directly from disk.                        *
 *    It performs this by allocating a temporary buffer in XMS/EMS and                         *
 *    then keeping this buffer filled by the Sound_Callback() routine.                         *
 *                                                                                             *
 * INPUT:   filename -- The name of the file to play.                                          *
 *                                                                                             *
 * OUTPUT:  Returns the handle to the sound -- just like Play_Sample().                        *
 *                                                                                             *
 * WARNINGS:   The temporary buffer is allocated when this routine is                          *
 *             called and then freed when the sound is finished.  Keep                         *
 *             this in mind.                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/06/1994 JLB : Created.                                                                 *
 *=============================================================================================*/
int File_Stream_Sample_Vol(char const *filename, int volume, BOOL real_time_start)
{
	static VOID	*buffer = NULL;
	SampleTrackerType	*st;
	int	fh;
	int	handle = -1;
	long	size;
	int	index;

	if (LockedData.DigiHandle != -1 && filename && Find_File(filename)) {
		//printf("Before initialize memory!\n");
		/*
		**	Make sure all sample tracker structures point to the same
		**	upper memory buffer.  This allocation only occurs if at
		**	least one sample gets "streamed".
		*/
		if (!buffer) {
			buffer = Alloc(LockedData.StreamBufferSize * LockedData.StreamBufferCount, (MemoryFlagType)(StreamBufferFlag | MEM_TEMP | MEM_LOCK));
			for (index = 0; index < LockedData.MaxSamples; index++) {
				LockedData.SampleTracker[index].FileBuffer = buffer;
			}
		}

		/*
		** If after trying to allocate the buffer we still fail then
		** we can stream this sample.
		*/
		if (!buffer) return(-1);
		
		//printf("Before Open File!\n");
		/*
		** Lets see if we can sucessfully open up the file.  If we can't, 
		** then there is no point in going any farther.
		*/
		if ((fh = Open_File(filename, READ)) == -1) {
			return (-1);
		}

		//printf("Before Get Free Handle!\n");
		/*
		** Reserve a handle so that we can fill in the sample tracker
		** with the needed information.  If we dont get valid handle then
		** we might as well give up.
		*/
		if ((unsigned)(handle = Get_Free_Sample_Handle(0xFF)) >= LockedData.MaxSamples) {
			return(-1);
		}

		/*
		** Now lets get a pointer to the proper sample handler and start
		** our manipulations.
		*/ 
		st							= &LockedData.SampleTracker[handle];
		st->IsScore				= TRUE;
		st->FilePending		= 0;
		st->FilePendingSize	= 0;
		st->Loading				= real_time_start;
		st->Volume				= volume;
		st->FileHandle			= fh;
		/*
		** Now that we have setup our initial data properly, let load up
		** the beginning of the sample we intend to stream.
		*/
		//printf("Before Preload!\n");
		File_Stream_Preload(handle);
	}
	return (handle);
}

/***********************************************************************************************
 * Sound_Callback -- Audio driver callback function.                                           *
 *                                                                                             *
 *    Maintains the audio buffers.  This routine must be called at least                       *
 *    11 times per second or else audio glitches will occur.                                   *
 *                                                                                             *
 * INPUT:   none                                                                               *
 *                                                                                             *
 * OUTPUT:  none                                                                               *
 *                                                                                             *
 * WARNINGS:   If this routine is not called often enough then audio                           *
 *             glitches will occur.                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/06/1994 JLB : Created.                                                                 *
 *=============================================================================================*/
VOID cdecl __saveregs __loadds Sound_Callback(VOID)
{
	int					index;
	SampleTrackerType	*st;

	if (LockedData.DigiHandle != -1) {
		st = &LockedData.SampleTracker[0];
		for (index = 0; index < LockedData.MaxSamples; index++) {
			if (st->Loading) {
				File_Stream_Preload(index);
			} else {
				/*
				**	General service routine to handle moving small blocks from the
				**	source into the low RAM staging buffers.
				*/

				if (st->Active) {
			

					/*
					**	Special check to see if the sample is a fading one AND
					**	it has faded to silence, then stop it here.
					*/
					if (st->Reducer && !st->Volume) {
						Stop_Sample(index);
					} else {

						/*
						**	Fill the queuebuffer if it is currently empty
						**	and there is a callback function defined to fill it.
						**
						** PWG/CDY & CO: We should be down by at least two blocks
						**					  before we bother with this
						*/
						if ((!st->QueueBuffer || 
							(st->FileHandle != ERROR && st->FilePending < LockedData.StreamBufferCount-3)) && 
							st->Callback) {

							if (!st->Callback(index, &st->Odd, &st->QueueBuffer, &st->QueueSize)) {
								st->Callback = NULL;
							}
						}
					}

				} else {

					/*
					**	This catches the case where a streaming sample gets
					**	aborted prematurely because of failure to call the
					**	callback function frequently enough.  In this case, the
					**	sample will be flagged as inactive, but the file handle
					**	will not have been closed.
					*/
					if (st->FileHandle != ERROR) {
						Close_File(st->FileHandle);
						st->FileHandle = ERROR;
					}
				}
			}

			/*
			**	Advance to the next sample control structure.
			*/
			st++;
		}
	}
}


/***********************************************************************************************
 * Load_Sample -- Loads a digitized sample into RAM.                                           *
 *                                                                                             *
 *    This routine loads a digitized sample into RAM.                                          *
 *                                                                                             *
 * INPUT:   filename -- Name of the sound file to load.                                        *
 *                                                                                             *
 * OUTPUT:  Returns with a pointer to the loaded sound file.  This pointer                     *
 *          is passed to Play_Sample when playback is desired.                                 *
 *                                                                                             *
 * WARNINGS:   If there is insufficient memory to load the sample, then                        *
 *             NULL will be returned.                                                          *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/17/1992 JLB : Created.                                                                 *
 *   01/06/1994 JLB : HMI version.                                                             *
 *=============================================================================================*/
VOID *Load_Sample(char const *filename)
{
	void *buffer = NULL;
	long	size;
	int	fh;

	if (LockedData.DigiHandle == -1 || !filename || !Find_File(filename)) {
		return (NULL);
	}

	fh = Open_File(filename, READ);
	if (fh != ERROR) {
		size = File_Size(fh)+sizeof(AUDHeaderType);
		buffer = Alloc(size, MEM_NORMAL);

		if (buffer) {
			Sample_Read(fh, buffer, size);
		}
		Close_File(fh);
		Misc = size;
	}
	return(buffer);
}


/***********************************************************************************************
 * Load_Sample_Into_Buffer -- Loads a digitized sample into a buffer.                          *
 *                                                                                             *
 *    This routine is used to load a digitized sample into a buffer                            *
 *    provided by the programmer.  This buffer can be in XMS or EMS.                           *
 *                                                                                             *
 * INPUT:   filename -- The filename to load.                                                  *
 *                                                                                             *
 *          buffer   -- Pointer to the buffer to load into.                                    *
 *                                                                                             *
 *          size     -- The size of the buffer to load into.                                   *
 *                                                                                             *
 * OUTPUT:  Returns the number of bytes actually used in the buffer.                           *
 *                                                                                             *
 * WARNINGS:   This routine will not overflow the buffer provided.  This                       *
 *             means that the buffer must be big enough to hold the data                       *
 *             or else the sound will be cut short.                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/06/1994 JLB : Created.                                                                 *
 *=============================================================================================*/
long Load_Sample_Into_Buffer(char const *filename, void *buffer, long size)
{
	int	fh;

	/*
	**	Verify legality of parameters.
	*/
	if (!buffer || !size || LockedData.DigiHandle == -1 || !filename || !Find_File(filename)) {
		return (NULL);
	}

	fh = Open_File(filename, READ);
	if (fh != ERROR) {
		size = Sample_Read(fh, buffer, size);
		Close_File(fh);
	} else {
		return(0);
	}
	return(size);
}


/***********************************************************************************************
 * Sample_Read -- Reads sample data from an openned file.                                      *
 *                                                                                             *
 *    This routine reads a sample file.  It is presumed that the file is                       *
 *    already positioned at the start of the sample.  From this, it can                        *
 *    determine if it is a VOC or raw data and proceed accordingly.                            *
 *                                                                                             *
 * INPUT:   fh       -- File handle of already openned sample file.                            *
 *                                                                                             *
 *          buffer   -- Pointer to the buffer to load data into.                               *
 *                                                                                             *
 *          size     -- The size of the buffer.                                                *
 *                                                                                             *
 * OUTPUT:  Returns the number of bytes actually used in the buffer.                           *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/06/1994 JLB : Created.                                                                 *
 *=============================================================================================*/
long Sample_Read(int fh, void *buffer, long size)
{

	AUDHeaderType	RawHeader;
	VOID	*outbuffer;					// Pointer to start of raw data.
	long	actual_bytes_read;		// Actual bytes read in, including header

/*
	Conversion formula for TCrate and Hz rate.

	TC = 256 - 1m/rate
	rate = 1m / (256-TC)
*/

	if (!buffer || fh == ERROR || size <= sizeof(RawHeader)) return(NULL);
	size -= sizeof(RawHeader);
	outbuffer = Add_Long_To_Pointer(buffer, sizeof(RawHeader));

				actual_bytes_read = 
					Read_File(fh, &RawHeader, sizeof(RawHeader));

				actual_bytes_read +=
					Read_File(fh, outbuffer, MIN(size, RawHeader.Size));


	Mem_Copy(&RawHeader, buffer, sizeof(RawHeader));
	return(actual_bytes_read);
}


/***********************************************************************************************
 * Free_Sample -- Frees a previously loaded digitized sample.                                  *
 *                                                                                             *
 *    Use this routine to free the memory allocated by a previous call to                      *
 *    Load_Sample.                                                                             *
 *                                                                                             *
 * INPUT:   sample   -- Pointer to the sample to be freed.                                     *
 *                                                                                             *
 * OUTPUT:  none                                                                               *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/17/1992 JLB : Created.                                                                 *
 *=============================================================================================*/
VOID Free_Sample(VOID const *sample)
{
	if (sample) Free((void *)sample);
}

BOOL Sound_Init(int sfx, int score, int sample, RateType rate, int bits_per_sample, int max_samples, int reverse_channels)
{
	return Audio_Init(sample, -1, -1, -1, rate, bits_per_sample, max_samples, reverse_channels);
}

BOOL Audio_Init(int sample, int address, int inter, int dma, RateType rate, int bits_per_sample, int max_samples, int reverse_channels)
{
	int					error;	// Function error return code.
	unsigned int 		port;
	int					index;
	_SOS_INIT_DRIVER	init;		// Driver init structure.

	Init_Locked_Data();
	memset(&LockedData.SampleTracker[0], 0, sizeof(LockedData.SampleTracker));
	LockedData.Rate = rate;

	Bits_Per_Sample = bits_per_sample;

	sosTIMERInitSystem(_TIMER_DOS_RATE, _SOS_DEBUG_NORMAL);

	if (TimerSystemOn) {
		sosTIMERRegisterEvent(60, &Timer_Interrupt_Func, &SystemTimer);
//		TickCount.Start();
	} else
		sosTIMERRegisterEvent(60, &HMI_TimerCallback, &SystemTimer);

	/*
	**	Special code to handle the HMI problem with running under Windows.
	*/
	if (/*Operating_System() != OS_DOS ||*/ sample == SAMPLE_NONE) {
		return(FALSE);
	}

	while (sample) {

#ifdef SIMPLE_HMI_INIT

		if (address == -1 || inter == -1 || dma == -1) {
			error = sosDIGIDetectInit((LPSTR)NULL);
			if (error) {
				printf("Cannot initialize detection system (%d).\n", error);
				Get_Key();
				break;
			}

			error = sosDIGIDetectFindHardware(sample, &DigiCaps, &port);
			if (error) {
				printf("Cannot find sound card specified.\n");
				Get_Key();
				break;
			}

			/*
			** Handle the override for Address, Interrupt, and DMA settings.
			*/
			error = sosDIGIDetectGetSettings(&DigiHardware);
			sosDIGIDetectUnInit();
			if (error) {
				printf("Cannot get sound card settings.\n");
				Get_Key();
				break;
			}
		} else {
		 	DigiHardware.wPort = address;
			DigiHardware.wIRQ = inter;
			DigiHardware.wDMA = dma;
		}
#endif

		/*
		**	Initialize the digi-system and driver.
		*/
		sosDIGIInitSystem((LPSTR)NULL, _SOS_DEBUG_NORMAL);
		init.wBufferSize		= 1024*8;				// Specify the DMA buf size
		init.lpBuffer			= (LPSTR)NULL;
		init.wAllocateBuffer = TRUE;
		init.wSampleRate		= rate;					// Sample playback rate.
		init.wParam				= 19;
		init.dwParam			= 0;
		init.lpFillHandler	= NULL;
		init.lpDriverMemory	= (LPSTR)NULL;
		init.lpTimerMemory	= (LPSTR)NULL;
		init.wTimerID			= _SOS_NORMAL_TIMER;
 		
		error = 0;
		error = sosDIGIInitDriver( (int)sample, &DigiHardware, &init, &LockedData.DigiHandle);
		if (error) {
			printf("Unable to initialize the sound driver.\n");
			Get_Key();
			break;
		}

		error = 0;


		if (sample >= _GUS_8_MONO && sample <= _GUS_16_ST) {
			error = sosTIMERRegisterEvent(120, init.lpFillHandler, &DigiTimer);
		} else {
			error = sosTIMERRegisterEvent(60, init.lpFillHandler, &DigiTimer);
		}
		if (error) {
			printf("Unable to regiser the fill handler.\n");
			Get_Key();
			sosDIGIUnInitDriver(LockedData.DigiHandle, TRUE, TRUE);
			LockedData.DigiHandle = -1;
			break;
		}

		/*
		**	Allocate a decompression buffer equal to the size of a SONARC frame
		**	block.
		*/
		LockedData.UncompBuffer = Alloc(LARGEST_SONARC_BLOCK + 50, (MemoryFlagType)(MEM_NORMAL|MEM_CLEAR|MEM_LOCK));
		LockedData.MaxSamples	= max_samples;

		/*
		**	Allocate private staging buffers for double buffering sound into HMI
		**	driver.
		*/
		DigiBuffer = Alloc(2048+(((LONG)LockedData.MaxSamples*2) * (LONG)(SFX_MINI_STAGE_BUFFER_SIZE+SONARC_MARGIN)), (MemoryFlagType)(MEM_NORMAL|MEM_CLEAR|MEM_LOCK));
		for (index = 0; index < LockedData.MaxSamples; index++) {
			LockedData.SampleTracker[index].Buffer[0] = Add_Long_To_Pointer(DigiBuffer, ((LONG)index*2) * (LONG)(SFX_MINI_STAGE_BUFFER_SIZE+SONARC_MARGIN));
			LockedData.SampleTracker[index].Buffer[1] = Add_Long_To_Pointer(DigiBuffer, ((LONG)index*2+1) * (LONG)(SFX_MINI_STAGE_BUFFER_SIZE+SONARC_MARGIN));
			LockedData.SampleTracker[index].FileHandle = ERROR;
			LockedData.SampleTracker[index].QueueBuffer = NULL;
		}
		error = sosTIMERRegisterEvent(MAINTENANCE_RATE, (void (__far *)(void))maintenance_callback, &MaintainTimer);
		if (error) {
			printf("Unable to initialize the maintenance callback.\n");
			Get_Key();
			sosTIMERRemoveEvent(DigiTimer);
			sosDIGIUnInitDriver(LockedData.DigiHandle, TRUE, TRUE);
			LockedData.DigiHandle = -1;
			break;
		}
		SoundType = (SFX_Type)sample;
		SampleType = (Sample_Type)sample;
		ReverseChannels = reverse_channels;

		break;
	}

	return(TRUE);
}


/***********************************************************************************************
 * Sound_End -- Uninitializes the sound driver.                                                *
 *                                                                                             *
 *    This routine will uninitialize the sound driver (if any was                              *
 *    installed).  This routine must be called at program termination                          *
 *    time.                                                                                    *
 *                                                                                             *
 * INPUT:   none                                                                               *
 *                                                                                             *
 * OUTPUT:  none                                                                               *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   07/23/1991 JLB : Created.                                                                 *
 *=============================================================================================*/
VOID Sound_End(VOID)
{

	if (LockedData.DigiHandle != -1) {
		sosTIMERRemoveEvent(DigiTimer);
		sosTIMERRemoveEvent(MaintainTimer);
		sosDIGIUnInitDriver(LockedData.DigiHandle, TRUE, TRUE);
		sosDIGIUnInitSystem();
		LockedData.DigiHandle = -1;
	}
	if (DigiBuffer) {
		Free(DigiBuffer);
		DigiBuffer = 0;
	}
	if (LockedData.UncompBuffer) {
		Free(LockedData.UncompBuffer);
		LockedData.UncompBuffer = 0;
	}
	sosTIMERRemoveEvent(SystemTimer);
	sosTIMERUnInitSystem(0);
	Unlock_Locked_Data();
}




/***********************************************************************************************
 * Stop_Sample -- Stops any currently playing sampled sound.                                   *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   06/02/1992 JLB : Created.                                                                 *
 *=============================================================================================*/
VOID Stop_Sample(int handle)
{
	if (LockedData.DigiHandle != -1 && (unsigned)handle < LockedData.MaxSamples) {


		if (LockedData.SampleTracker[handle].Active || LockedData.SampleTracker[handle].Loading) {

			
			LockedData.SampleTracker[handle].Active = FALSE;
			if (!LockedData.SampleTracker[handle].IsScore) {
				DPMI_Unlock(LockedData.SampleTracker[handle].Original, LockedData.SampleTracker[handle].OriginalSize);
				LockedData.SampleTracker[handle].Original = NULL;
			}
			LockedData.SampleTracker[handle].Priority = 0;

			/*
			**	Stop the sample if it is playing.
			*/
			if (!LockedData.SampleTracker[handle].Loading) {
				sosDIGIStopSample(LockedData.DigiHandle, LockedData.SampleTracker[handle].Handle);
			}
			LockedData.SampleTracker[handle].Loading = FALSE;

			/*
			**	If this is a streaming sample, then close the source file.
			*/
			if (LockedData.SampleTracker[handle].FileHandle != ERROR) {
				Close_File(LockedData.SampleTracker[handle].FileHandle);
				LockedData.SampleTracker[handle].FileHandle = ERROR;
			}

			LockedData.SampleTracker[handle].QueueBuffer = NULL;
		}
	}
}


/***********************************************************************************************
 * Sample_Status -- Queries the current playing sample status (if any).                        *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   06/02/1992 JLB : Created.                                                                 *
 *=============================================================================================*/
BOOL Sample_Status(int handle)
{
	/*
	** If its an invalid handle or we do not have a sound driver then
	** the sample in question is not playing.
	*/
	if (LockedData.DigiHandle == -1 || (unsigned)handle >= LockedData.MaxSamples) return(FALSE);
	/*
	** If the sample is loading, then for all intents and purposes the
	** sample is playing.
	*/
	if (LockedData.SampleTracker[handle].Loading) return(TRUE);
	/* 
	** If the sample is not active, then it is not playing
	*/
	if (!LockedData.SampleTracker[handle].Active) return(FALSE);
	/*
	** If we made it this far, then the Sample is still playing if sos says
	** that it is.
	*/
	return (!sosDIGISampleDone(LockedData.DigiHandle, LockedData.SampleTracker[handle].Handle));
}


BOOL Is_Sample_Playing(void const * sample)
{
	int index;

	if (!sample) return FALSE;
	for (index = 0; index < LockedData.MaxSamples; index++) {
		if (LockedData.SampleTracker[index].Original == sample && Sample_Status(index)) {
			return (TRUE);
		}
	}
	return (FALSE);
}


VOID Stop_Sample_Playing(void const * sample)
{
	int index;

	if (sample) {
		for (index = 0; index < LockedData.MaxSamples; index++) {
			if (LockedData.SampleTracker[index].Original == sample) {
				Stop_Sample(index);
				break;
			}
		}
	}
}
int Get_Free_Sample_Handle(int priority)
{
	int	id;

	/*
	**	Find a free SFX holding buffer slot.
	*/
	for (id = LockedData.MaxSamples - 1; id >= 0; id--) {
		if (!LockedData.SampleTracker[id].Active && !LockedData.SampleTracker[id].Loading) {
			if (!StartingFileStream && LockedData.SampleTracker[id].IsScore) {
				StartingFileStream = TRUE;	// Ensures only one channel is kept free for scores.
				continue;
			}
			break;
		}
	}

	if (id < 0) {
		for (id = 0; id < LockedData.MaxSamples; id++) {
			if (LockedData.SampleTracker[id].Priority <= priority) break;
		}
		if (id == LockedData.MaxSamples) {
			return(-1);		// Cannot play!
		}
		Stop_Sample(id);							// This sample gets clobbered.
	}
	if (id == -1) {
		return -1;
	}
	if (LockedData.SampleTracker[id].FileHandle != ERROR) {
		Close_File(LockedData.SampleTracker[id].FileHandle);
		LockedData.SampleTracker[id].FileHandle = ERROR;
	}
	if (LockedData.SampleTracker[id].Original && !LockedData.SampleTracker[id].IsScore) {
		DPMI_Unlock(LockedData.SampleTracker[id].Original, LockedData.SampleTracker[id].OriginalSize);
		LockedData.SampleTracker[id].Original = NULL;
	}
	LockedData.SampleTracker[id].IsScore = FALSE;
	return(id);
}

int Play_Sample(void const *sample, int priority, int volume, signed short panloc)
{
	return(Play_Sample_Handle(sample, priority, volume, panloc, Get_Free_Sample_Handle(priority)));
}
/***********************************************************************************************
 * Play_Sample_Vol -- Plays a digitized sample.                                                *
 *                                                                                             *
 *    Use this routine to play a previously loaded digitized sample.                           *
 *                                                                                             *
 * INPUT:   sample   -- Sample pointer as returned from Load_Sample.                           *
 *                                                                                             *
 *          volume   -- The volume to play (0..255 with 255=loudest).                          *
 *                                                                                             *
 * OUTPUT:  none                                                                               *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/17/1992 JLB : Created.                                                                 *
 *   05/24/1992 JLB : Volume support -- Soundblaster Pro                                       *
 *   04/22/1994 JLB : Multiple sample playback rates.                                          *
 *=============================================================================================*/
int Play_Sample_Handle(void const *sample, int priority, int volume, signed short panloc, int id)
{
	AUDHeaderType			RawHeader;
	_SOS_START_SAMPLE 	start;
	SampleTrackerType		*st=NULL;	// Working pointer to sample tracker structure.

	if (!sample || LockedData.DigiHandle == -1) {
		return(-1);
	}

	if (id == -1) {
		return -1;
	}

	/*
	**	Fetch the control bytes from the start of the sample data.
	*/
	Mem_Copy((void *)sample, (void *)&RawHeader, sizeof(RawHeader));

	/*
	**	Prepare the sample tracker structure for processing of this
	**	sample.  Fill the structure with data that can be determined
	**	before the sample is started.
	*/
	st = &LockedData.SampleTracker[id];
	st->Compression	= (SCompressType) ((unsigned char)RawHeader.Compression);
	st->Original		= sample;
	st->OriginalSize	= RawHeader.Size + sizeof(RawHeader);
	if (!st->IsScore) {
		DPMI_Lock(st->Original, st->OriginalSize);
	}
	st->Priority		= priority;
	st->DontTouch		= FALSE;
	st->Odd				= 0;
	st->Reducer			= 0;
	st->Restart			= FALSE;
	st->QueueBuffer	= NULL;
	st->QueueSize		= NULL;
	st->TrailerLen 	= 0;
	st->Remainder		= RawHeader.Size;
	st->Source			= Add_Long_To_Pointer((void *)sample, sizeof(RawHeader));
	st->Service			= FALSE;

	/*
 	** If the code in question using HMI based compression then we need
	** to set up for uncompressing it.
	*/
	if (st->Compression == SCOMP_SOS) {
		st->sosinfo.wChannels	 = (RawHeader.Flags & AUD_FLAG_STEREO) ? 2  : 1;
		st->sosinfo.wBitSize		 = (RawHeader.Flags & AUD_FLAG_16BIT)  ? 16 : 8;
		st->sosinfo.dwCompSize	 = RawHeader.Size;
   	st->sosinfo.dwUnCompSize = RawHeader.Size * ( st->sosinfo.wBitSize / 4 );
	   sosCODECInitStream(&st->sosinfo);
	}

	/*
	**	Fill in one or both staging buffers if possible.
	*/
	_disable();
//	Disable_Timer_Interrupt();
	if (SFX_MINI_STAGE_BUFFER_SIZE == 
					Sample_Copy(st,
									&st->Source, 
									&st->Remainder, 
									&st->QueueBuffer, 
									&st->QueueSize, 
									st->Buffer[0], 
									SFX_MINI_STAGE_BUFFER_SIZE, 
									st->Compression, 
									&st->Trailer[0], 
									&st->TrailerLen)) {

		st->DataLength = Sample_Copy(st,
										&st->Source, 
										&st->Remainder, 
										&st->QueueBuffer, 
										&st->QueueSize, 
										st->Buffer[1], 
										SFX_MINI_STAGE_BUFFER_SIZE, 
										st->Compression, 
										&st->Trailer[0], 
										&st->TrailerLen);
		st->Index = 1;
	} else {
		st->Index = 0;
		st->DataLength = 0;
	}
	_enable();
//	Enable_Timer_Interrupt();

	/*
	**	Fill in the HMI start sample structure.
	*/
	memset(&start, 0, sizeof(start));

	start.lpSamplePtr = (LPSTR)st->Buffer[0];
	if (st->Index == 1) {
		start.dwSampleSize = SFX_MINI_STAGE_BUFFER_SIZE-1;
	} else {
		start.dwSampleSize = st->DataLength-1;
	}
	start.lpCallback	= (void cdecl (far *)(unsigned int, unsigned int, unsigned int))&DigiCallback;
	start.wLoopCount	= 0;
	start.wSampleID	= id;

	/*
	**	Adjust pitch shifting as necessary so that lower playback
	**	samples can be supported.
	*/
	if (RawHeader.Rate != LockedData.Rate) {
		ldiv_t	result;

		result						= ldiv((long)RawHeader.Rate, LockedData.Rate);
		start.dwSamplePitchAdd	= (long)MAKE_LONG((short)result.quot, (short)(((long)result.rem * 0x10000L) / LockedData.Rate));
		start.wSampleFlags 	  |= _PITCH_SHIFT;
	}

	/*
	**	Sample translation flag.
	*/

	if (RawHeader.Flags & AUD_FLAG_16BIT) {
		if (Bits_Per_Sample == 8) {
			start.wSampleFlags 	  |= _TRANSLATE16TO8;
		}
	} else {
		if (Bits_Per_Sample == 16) {
			start.wSampleFlags 	  |= _TRANSLATE8TO16;
		}
	}

	/*
	**	Sample stereo flag.
	*/
	if (RawHeader.Flags & AUD_FLAG_STEREO) {
		start.wChannel 			= _INTERLEAVED;
		start.wSampleFlags 	  |= _STEREOTOMONO;
	} else {
		start.wChannel 			= _CENTER_CHANNEL;
	}

	/*
	**	Sample volume control flags.  Always give it volume control because
	**	if not, then future volume control is either ignored or stops the
	**	sample.
	*/
	st->Volume 						= volume << 7;
	start.wVolume					= (st->Volume >> 8) * LockedData.SoundVolume;
	start.wSampleFlags  		  |= _VOLUME;
	
	/*
	** If we have defined a panning location for the sound driver than
	** take care of it here.  Panning will only work with a stereo driver
	** and only if the sample is played _CENTER_CHANNEL.
	*/
	if ((panloc != 0x0) && (!(RawHeader.Flags & AUD_FLAG_STEREO))) {
		start.wChannel 			= _CENTER_CHANNEL;
		panloc 						= ((ReverseChannels) ? ((-panloc) + 0x8000) : ((panloc) + 0x8000));
		start.wSampleFlags 	  |= _PANNING;
		start.wSamplePanLocation= panloc;
	}

	st->Stereo 	= start.wChannel;
	st->Pitch 	= start.dwSamplePitchAdd;
	st->Flags 	= start.wSampleFlags;

	/*
	**	Start the sample playing now.
	*/
	_disable();		// NEW
//	Disable_Timer_Interrupt();
	st->Handle = sosDIGIStartSample(LockedData.DigiHandle, &start);
	if (st->Handle == -1) {
		id = -1;
	} else {

		/*
		**	Fill in the sample tracker structure with those values that are
		**	determined AFTER the sample starts.
		*/
		st->Active = TRUE;
	}
	_enable();			// NEW
//	Enable_Timer_Interrupt();

	return(id);
}


int Set_Sound_Vol(int volume)
{
	int old;
	old = LockedData.SoundVolume;
	LockedData.SoundVolume = volume & 0xFF;
	return(old);
}


int Set_Score_Vol(int volume)
{
	int old;
	old = LockedData.ScoreVolume;
	LockedData.ScoreVolume = volume & 0xFF;
	return(old);
}


VOID Fade_Sample(int handle, int ticks)
{
	if (Sample_Status(handle)) {
		if (!ticks || LockedData.SampleTracker[handle].Loading) {
			Stop_Sample(handle);
		} else {
			SampleTrackerType * st;

			st = &LockedData.SampleTracker[handle];
			st->Reducer = (st->Volume / ticks)+1;
		}
	}
}
int Get_Digi_Handle(void)
{
	return(LockedData.DigiHandle);
}

/***************************************************************************
 * SAMPLE_LENGTH -- returns length of a sample in ticks                    *
 *                                                                         *
 * INPUT:		void const *sample - pointer to the sample to get length    *
 *											   of.												*
 *                                                                         *
 * OUTPUT:		long   				 - length of the sample in ticks (60/sec) *
 *                                                                         *
 * HISTORY:                                                                *
 *   07/05/1995 PWG : Created.                                             *
 *=========================================================================*/
long Sample_Length(void const *sample)
{
	AUDHeaderType			RawHeader;

	if (!sample) return(0);

	Mem_Copy((void *)sample, (void *)&RawHeader, sizeof(RawHeader));

	long time = RawHeader.UncompSize;

	/*
	** If the sample is a 16 bit sample, then it will take only half
	** as long to play.
	*/
	if (RawHeader.Flags & AUD_FLAG_16BIT) {
		time >>= 1;
	}

	/*
	** If the sample is a stereo sample, then it will take only half
	** as long to play.
	*/
	if (RawHeader.Flags & AUD_FLAG_STEREO) {
		time >>= 1;
	}

	if (RawHeader.Rate/60) {
		time /= (RawHeader.Rate/60);
	}
	return(time);
}
