/*$Author:   DCODY  $*/
/*$Date:   01 Oct 1992 12:05:02  $*/
/*$Header:   X:/sccs/pcm/pcmioc.c_v   1.7   01 Oct 1992 12:05:02   DCODY  $*/
/*$Log:   X:/sccs/pcm/pcmioc.c_v  $
 * 
 *    Rev 1.7   01 Oct 1992 12:05:02   DCODY
 * next stage of completion for PlayThisBlock, RecordThisBlock, etc.
 * 
 *    Rev 1.6   23 Sep 1992 10:56:34   DCODY
 * more work on playthisblock, continuethisblock...
 * 
 *    Rev 1.5   26 Aug 1992 10:57:30   DCODY
 * Added Playthisblock and RecordThisBlock
 * 
 *    Rev 1.4   12 Aug 1992 17:10:30   DCODY
 * major change to eliminate the foreground buffers.
 * 
 *    Rev 1.3   24 Jul 1992 15:36:14   DCODY
 * changed _fmemcpy to _rfmemcpy
 * 
 *    Rev 1.2   17 Jul 1992 14:22:50   DCODY
 * InitMVSound() now performed within OpenPCMBuffering().
 * 
 *    Rev 1.1   23 Jun 1992 17:11:42   DCODY
 * PAS2 update
 * 
 *    Rev 1.0   15 Jun 1992 09:44:38   BCRANE
 * Initial revision.
*/
/*$Logfile:   X:/sccs/pcm/pcmioc.c_v  $*/
/*$Modtimes$*/
/*$Revision:   1.7  $*/
/*$Workfile:   pcmioc.c  $*/


	/*\
	|*|----====< PCMIOC.C >====----
	|*|
	|*| These routines maintain DMA controlled I/O of the Audio Spectrum
	|*|
	|*| Copyright (c) 1991, Media Vision, Inc. All rights reserved.
    |*|
    \*/

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

#include "pcmio.h"
#include "common.h"
#include "mvsound.h"


	/*\
	|*|-----------====< T H E O R Y   O F	O P E R A T I O N >====------------
	|*|
	|*| The best DMA controlled PCM output requires a continuous stream of data
	|*| to be available in a real-time environment.
	|*|
	|*| DMA controlled PCM input, with the same real-time requirements, needs
	|*| to be able to keep storing data into memory without pausing.
	|*|
	|*| The following approach is designed to allow the DMA to be setup in
	|*| "auto-initialize" mode, thereby guarenteeing continuous play/record.
	|*|
	|*| To keep the DMA running, multiple divisions of the DMA buffer are
	|*| used to keep the data moving. Due to the fact that DOS is neither
	|*| a real-time, or re-entrant operating system, this code divides up
	|*| the buffer management tasks into a "foreground" and "background" task.
	|*|
	|*| A sample buffer count timer on the Audio Spectrum is used to interrupt
	|*| the CPU when a DMA buffer division has filled or emptied. For our
	|*| purposes here, this amount may be 1/2, 1/4, 1/8th or some smaller
	|*| division of the whole DMA buffer. Note: judgement must be used here
	|*| in selecting the DMA buffer size, and the integral division. Too small
	|*| of an integral may result in broken DMA I/O. A buffer too large never
	|*| hurts anything. (it just reduces the amount of available memory).
	|*|
	|*| 				----====< PCM OUTPUT >====----
	|*|
	|*| To perform PCM output ("play"), A linked list of buffer pointers is
	|*| used to fill the DMA buffer by the foregound task. As the DMA runs,
	|*| it will send the buffer contents to the audio card. Here is a visual:
	|*|
	|*| 						   
	|*| 					 Foreground Loads
	|*| 					   the Top Level
	|*| 						 Buffers
	|*| 						   
	|*| 					   ÚÄÂÄÂÄÂÄÂÄ¿
	|*| 	 DMA Level Buffers ³ ³ ³ ³ ³ ³
	|*| 					   ÀÄÁÄÁÄÁÄÁÄÙ
	|*| 						   
	|*| 					   ÚÄÄÄÄÄÄÄÄÄ¿
	|*| 					   ³hardware ³
	|*| 					   ÀÒÒÒÄÄÄÄÄÄÙ
	|*|
	|*| To actually start the output, the foreground task loads it's
	|*| buffers, then starts the DMA to play the buffer. The background
	|*| task only indicates when each block is played. It will shut down
	|*| the DMA if no more data is present in the buffers.
	|*|
	|*| If the foreground task can keep the linked list of buffers full,
	|*| there should be non-stop PCM output (Good!). If the foreground task
	|*| does not keep up, the background task will be forced to stop the
	|*| the DMA, thereby causing a break in the output (Bad!). Once the DMA
	|*| has stopped, the foreground task will have to restart the DMA a
	|*| second time to continue the flow of data.
	|*|
	|*| 				----====< PCM INPUT >====----
	|*|
	|*| To perform PCM input ("record"), the same linked list of buffers
	|*| are also used. This buffer is filled with sampled data from the
	|*| hardware. The background process will increment a global variable for
	|*| each buffer filled. The foreground routine must extract each buffer
	|*| and process it (copy to memory, or write it to disk). Here is a visual:
	|*|
	|*| 						   
	|*| 					 Foreground unloads
	|*| 					   the Top Level
	|*| 						 Buffers
	|*| 						   
	|*| 					   ÚÄÂÄÂÄÂÄÂÄ¿
	|*| 	 DMA Level Buffers ³ ³ ³ ³ ³ ³
	|*| 					   ÀÄÁÄÁÄÁÄÁÄÙ
	|*| 						   
	|*| 					   ÚÄÄÄÄÄÄÄÄÄ¿
	|*| 					   ³hardware ³
	|*| 					   ÀÒÒÒÄÄÄÄÄÄÙ
	|*|
	|*| To actually start the input, the foreground starts the DMA running to
	|*| begin the transfer. The background task increments the global variable
	|*| as each interrupt occurs. If all the buffers are full, the DMA transfer
	|*| is terminated. The foreground routine must poll this variable to keep
	|*| the data moving out of the DMA buffer.
	|*|
    |*| If the foreground task can keep the linked list of buffers empty,
	|*| there should be non-stop PCM input (Good!). If the foreground task
	|*| does not keep up, the background task will be forced to stop the
	|*| the DMA, thereby causing a break in the input (Bad!). Once the DMA
	|*| has stopped, the foreground task will have to restart the DMA tranfer
	|*| a second time to restart the DMA.
	|*|
	|*| 				----====< DATA VARIABLES >====----
	|*|
	|*| The following is a description of the variables shared between the
	|*| foreground and background tasks. There are three global variables,
	|*| and a linked list of buffers shared between the two tasks.
	|*|
	|*| The linked list of buffers uses a "header" to each buffer. This
	|*| header holds the information for linking to the next buffer, whether
	|*| the buffer is full or empty, and the count of bytes in the buffer.
	|*|
	|*| typedef struct _buffptr {
	|*| 	int status; 				/* 0=empty, 1=full				 * /
	|*| 	int count;					/* # of bytes in the buffer 	 * /
	|*| 	int size;					/* total size of read data		 * /
	|*| 	char huge *buffer;			/* pointer to buffer data		 * /
	|*| 	struct _buffptr *nextptr;	/* pointer to next buffer		 * /
	|*|
	|*| } BuffData,*BuffPtr;
	|*|
	|*| BuffPtr HeadOfBuffers;			/* global variable head pointer  * /
	|*| int BufferDataCount;			/* # of full DMA buffers parts	 * /
	|*| int DMARunning; 				/* DMA status (0=off,1=running)  * /
	|*| char far *StartOfDMABuffer; 	/* start of actual DMA buffer	 * /
	|*| int ProcessedBlockCount;		/* # of blocks DMA handled		 * /
	|*|
	|*| "HeadOfBuffers" points to the first buffer in the linked list.
	|*|
	|*| 	  This linked list is made up of structures containing the buffer
	|*| 	data and other information. The last entry in the list points
	|*| 	back to the first entry, thereby creating a circular linked
	|*| 	list.
	|*| 	  Each buffer has a status word: 0=empty,1=full.
	|*| 	  The count indicates the # of bytes in the buffer. This count
	|*| 	is used to communication between the foreground and background
	|*| 	process that data is available. For output, the count tells the
	|*| 	background that data is available to be loaded in the DMA buffer.
	|*| 	For input, the count tells the foreground process that there is
	|*| 	data to be written to disk.
	|*|
	|*| "BufferDataCount" is the key handshaking variable between the
	|*| 	foreground and background processes. It indicates how many DMA
	|*| 	buffers divisions contain data.
    |*|
	|*| 	For output, it holds a count of DMA divisions hold data. This
	|*| 	global variable is incremented each time a buffer is loaded by
	|*| 	the foreground task, and decremented when a buffer is emptied
	|*| 	by the background task.
	|*|
	|*| 	For input, it holds the number of buffers with data in the DMA
	|*| 	buffer. It is incremented by the background process and
	|*| 	decremented by the foreground process.
	|*|
	|*| "DMARunning" is set to true or false depending upon the state
	|*| 	of the DMA channel. It is set TRUE when the DMA is running (either
	|*| 	playing or recording), and FALSE when the DMA is turned off.
    |*|
	|*| "ProcessedBlockCount" is the running total of blocks the DMA has
	|*| 	processed from the last Start I/O call.
    |*|
	|*| 	For input, this is the total number of dma divisions filled
	|*| 	by the DMA.
    |*|
	|*| 	For output, this is the total number of blocks loaded into
	|*| 	the DMA buffer.
    |*|
    |*| "StartOfDMABuffer" points to the first byte of the DMA circular buffer.
	|*|
	|*| The following routines provide a high level interface to DMA driven
	|*| PCM output:
	|*|
	|*| int  OpenPCMBuffering  ( int, int, int, int )
	|*|
	|*| 		This routine is the first routine to be called. It sets
	|*| 		up the DMA channel, IRQ, and allocates memory for the buffers.
	|*|
	|*| int PCMState ( int, int, int, int )
	|*|
	|*| 		This routine passes in the sample rate, stereo/mono flag,
	|*| 		the compression type (0 for 8 bit, 1 for for 4 bit),
	|*| 		and the PCM data sample size (8 or 16).
    |*|
	|*| int  StartFileInput    ( FILE *f )
    |*|
	|*| 		This routine begins recording the PCM data to the disk file.
	|*| 		The routine returns immediately. The routine,
	|*| 		"ContinueFileInput" must be called to continue moving data
	|*| 		from the DMA buffer to to the disk.
    |*|
	|*| int  StartBlockInput   (  )
	|*|
	|*| 		This routine begins recording the PCM data. The routine
	|*| 		returns immediately. Subsequent call must be made to
	|*| 		"ContinueBlockInput" to receive data from the DMA buffer.
    |*|
	|*| int  StartFileOutput   ( FILE *f, long )
    |*|
	|*| 		This routine begins playing the PCM data from the disk file.
	|*| 		The routine returns immediately. The routine,
	|*| 		"ContinueFileOutput" must be called to continue moving data
	|*| 		from the disk to the DMA buffer. The long variable tells how
	|*| 		many bytes to play.
    |*|
	|*| int  StartBlockOutput  ( char far * )
	|*|
	|*| 		This routine begins playing the caller's PCM data. The
	|*| 		routine returns immediately. The routine, "ContinueBlockOutput"
	|*| 		must subsequently be called to continue data output.
	|*|
	|*| int  ContinueFileInput	( )
	|*|
	|*| 		This routine checks to see if new data has been loaded into
	|*| 		the linked list of buffers. If so, the data is written to
	|*| 		disk, and the buffer is freed up.
	|*|
	|*| int  ContinueBlockInput ( char far * )
	|*|
	|*| 		This routine checks to see if new data has been loaded into
	|*| 		the linked list of buffers. If so, the data is written to
	|*| 		the caller's buffer.
	|*|
	|*| int  ContinueFileOutput  ( )
	|*|
	|*| 		This routine checks to see if the PCM hardware is
	|*| 		still playing. This routine MUST be called frequently to
	|*| 		maintain continuous PCM output.
    |*|
	|*| int  ContinueBlockOutput (char far *)
    |*|
	|*| 		This routine checks to see if the PCM hardware is
	|*| 		still playing. The caller passes the next block to be
	|*| 		played. A non-zero return value indicates the block has
	|*| 		been queued up to be played. A zero value means the buffer
	|*| 		is currently full, please try again...
	|*|
	|*| void StopDMAIO		  ( )
	|*|
	|*| 		This routine is used to prematurely terminate PCM I/O.
	|*|
	|*| void ClosePCMBuffering ( )
	|*|
	|*| 		This routine is used to close down the whole PCM I/O system.
	|*| 		This call MUST be made before the caller's program terminates.
	|*|
	\*/

	/*\
	|*|----====< Code Generation >====----
	\*/

#define BLOCKOUT	0		/* builds block output code only			*/
#define BLOCKIN 	0		/* builds block input code only 			*/
#define FILEOUT 	0		/* builds file output code only 			*/
#define FILEIN		0		/* builds file input code only				*/
#define COMMDATA	0		/* builds both common code and data 		*/

#ifdef	BUILDBO
#undef  BLOCKOUT
#define BLOCKOUT	1
#endif

#ifdef	BUILDBI
#undef  BLOCKIN
#define BLOCKIN 	1
#endif

#ifdef	BUILDFO
#undef  FILEOUT
#define FILEOUT 	1
#endif

#ifdef	BUILDFI
#undef  FILEIN
#define FILEIN		1
#endif

#ifdef	BUILDCO
#undef	COMMDATA
#define COMMDATA	1
#endif

    /*\
	|*|----====< common data for CODE and DATA generation >====----
	\*/

		/* buffer linked list header structures 							*/

		typedef struct _buffptr {
			int status; 					/* 0=empty, 1=full				*/
			int count;						/* # of bytes in the buffer 	*/
			int size;						/* total size of allocated buff */
			char huge *buffer;				/* pointer to buffer data		*/
			struct _buffptr *nextptr;		/* pointer to next buffer hdr	*/

		} BuffData,*BuffPtr;

#define NODIRECTION 	0					/* defines for DirectionFlag	*/
#define DMAINPUT		1
#define DMAOUTPUT		2


    /*\
	|*|----====< Global Data >====----
	\*/

#define QUEUESIZE	32						/* 32 entries					*/
#define QUEUEMASK	0x1F					/* mask to circulate the count	*/

#if COMMDATA

		int MaxBuffCount = 0;				/* # of DMA buffer divisions	*/
		int BufferSize = 0; 				/* size of each buffer division */

	/* shared global variables between the two tasks (in pcmioa.asm)		*/

		BuffPtr HeadOfBuffers = 0;			/* global variable head pointer */
		int BufferDataCount   = 0;			/* # of full buffers (0=done)	*/
		int DMARunning		  = 0;			/* DMA status (0=off,1=running) */
		char huge *DMABuffPtr = 0;			/* 128k+1 DMA buffer pointer	*/
		char far  *StartOfDMABuffer = 0;	/* start of DMA buffer pointer	*/
		int ProcessedBlockCount = 0;		/* # of I/O blocks processed	*/
		unsigned long _file_data_length = 0;/* size of data output			*/
		char __pcmdatasize	  = 8;			/* default to 8 bit pcm 		*/

        FILE *__fptr = 0;                   /* file pointer for disk I/O    */
		char *__LocalBuff = 0;				/* local target buffer			*/
		BuffPtr __NextPtr = 0;				/* next buffer pointer			*/
		int __DirectionFlag = 0;			/* current I/O direction		*/
		char far * __singleblockpointer;	/* single shot users buffer 	*/

	/* local data for this body of code, but needs to be public 			*/

		int VoiceActivatedSavedCount = 0;	/* # of I/O blocks saved		*/

		int  __queuein	  = 0;
		int  __queueincnt = 0;
		int  __queueout   = 0;
		long __queuedata  = 0;

		char far *__queuebuff[QUEUESIZE];	// number of queued blocks
		long __queuelen[QUEUESIZE]; 		// queued block lengths
		void (far * __queuecb[QUEUESIZE])();// queue of callback routines

		void (far *__synccallback)() = 0;	// callback to user code

#else

        extern int MaxBuffCount;            /* # of DMA buffer divisions    */
		extern int BufferSize;				/* size of each buffer division */

	/* shared global variables between the two tasks (in pcmioa.asm)		*/

		extern BuffPtr HeadOfBuffers;		/* global variable head pointer */
		extern int BufferDataCount; 		/* # of full buffers (0=done)	*/
		extern int DMARunning;				/* DMA status (0=off,1=running) */
		extern char huge *DMABuffPtr;		/* 128k+1 DMA buffer pointer	*/
		extern char far *StartOfDMABuffer;	/* start of DMA buffer pointer	*/
		extern int ProcessedBlockCount; 	/* # of I/O blocks processed	*/
		extern unsigned long _file_data_length; /* size of data output		*/
		extern char __pcmdatasize;			/* default to 8 bit pcm 		*/

		extern FILE *__fptr;				/* file pointer for disk I/O	*/
		extern char *__LocalBuff;			/* local target buffer			*/
		extern BuffPtr __NextPtr;			/* next buffer pointer			*/
		extern int __DirectionFlag; 		/* current I/O direction		*/
		extern char far* __singleblockpointer;/* single shot users buffer  */

		extern int VoiceActivatedSavedCount;/* # of I/O blocks saved		*/

		extern int	__queuein;
		extern int	__queueincnt;
		extern int	__queueout;
		extern long __queuedata;

		extern char far *__queuebuff[]; 	// number of queued blocks
		extern long __queuelen[];			// queued block lengths
		extern void (far * __queuecb[])();	// queue of callback routines

		extern void (far *__synccallback)();// callback to user code

#endif

	/* additional prototypes												*/

		void far  * _rfmemcpy (void far *, void far *, unsigned int);
		void huge * _rfhmemcpy(void huge *,void huge *,unsigned int);

#if BLOCKOUT
		static int _loadtheblock  ( char far * );
#endif

#if FILEOUT
		static int _loadthebuffer ( FILE * );
#endif



	/*\
    |*|-----------------====================================-----------------
	|*|-----------------====< Start of Executable Code >====-----------------
	|*|-----------------====================================-----------------
	\*/

#if FILEIN
	/*\
	|*|----====< ASpecialContinueFileInput >====----
	|*|
	|*| This is a special adaptation of the standard, "ContinueDMAInput"
	|*| routine. It will check the noise level in each block before writting
	|*| it out to disk. This way, no data is written until a noise level
	|*| is reached.
	|*|
	\*/
int ASpecialContinueFileInput(noise,goflag)
	int noise;	/* offset from silence									*/
	int goflag; /* record all after first block 						*/
{
int temp;

	/* if BufferDataCount is non-zero, we must process the DMA data 	*/

		while (BufferDataCount) {

			/* data is available, move it out to memory 				*/

				_rfmemcpy (__LocalBuff,__NextPtr->buffer,BufferSize);

			/* validate the level of noise before writing  it to disk	*/

				if (MakeHalfHistoGram(__LocalBuff,BufferSize,noise) ||
					(VoiceActivatedSavedCount && goflag) ) {

					/* if not all data is written, return in error		*/

#if LARGEDATA
					if (fwrite (__NextPtr->buffer,1,BufferSize,__fptr) != BufferSize) {
						StopDMAIO();
						return (0);
					}
#else
					if (fwrite (__LocalBuff,1,BufferSize,__fptr) != BufferSize) {
						StopDMAIO();
						return (0);
					}
#endif
                    VoiceActivatedSavedCount++;
                }
				else
					ProcessedBlockCount--;

			/* move to the next buffer									*/

				__NextPtr->count = __NextPtr->status = 0;
				__NextPtr = __NextPtr->nextptr;
				BufferDataCount--;
		}
		return (DMARunning);
}
#endif


#if COMMDATA
	/*\
	|*|----====< ClosePCMBuffering >====----
	|*|
	|*| Removes the PCM system & deallocates the buffer memory. There is
	|*| no return value.
	|*|
	\*/
void ClosePCMBuffering()
{
BuffPtr p,op;

	/* we will kill the DMA low level processing						*/

		StopDMAIO();
		_unloadirqvector();

	/* Free up the linked list of buffers								*/

		if ((p = HeadOfBuffers) != 0) {

			do {
				op	= p;				/* save the old ptr 			*/
				p	= p->nextptr;		/* point to the next buffer 	*/
				free  (op); 			/* free up the old header		*/

			} while ( (p != HeadOfBuffers) && p );
	   }

	/* free up the DMA buffer											*/

		if (DMABuffPtr)
			hfree (DMABuffPtr);

	/* null it all out...												*/

		DMABuffPtr		 = 0;
		HeadOfBuffers	 = 0;
		StartOfDMABuffer = 0;
		BufferDataCount  = BufferSize = DMARunning = 0;

}
#endif


#if BLOCKIN
    /*\
	|*|----====< ContinueBlockInput >====----
	|*|
	|*| This routine checks to see if another buffer can be stored in memory.
	|*| if so, it will load copy the DMA buffer to the caller's local buffer,
	|*| A return value of 0 indicates the caller's buffer is empty.
	|*|
	\*/
int ContinueBlockInput(buff)
	char far *buff;
{

	/* if BufferDataCount is non-zero, we must move the data to memory	*/

		if (BufferDataCount) {

			/* data is available, just move it out						*/

				_rfmemcpy (buff,__NextPtr->buffer,BufferSize);

			/* move to the next buffer									*/

				__NextPtr->count = __NextPtr->status = 0;
				__NextPtr = __NextPtr->nextptr;
				BufferDataCount--;

			/* returns the fact that the data has been loaded			*/

				return(1);
        }
        return (0);
}


    /*\
	|*|----====< ContinueThisBlockInput >====----
	|*|
	|*| This routine extracts a DMA buffer into one or
	|*| more target user buffers.
	|*|
	|*| Returns:
	|*|    Nonzero for running & processing, else 0 for dead.
    |*|
	\*/
int  ContinueThisBlockInput()
{
int n,          // working integer
	loop,		// loop flag to keep loading blocks
	bcount; 	// increments the BufferDataCount
int result = 0; // holds the final count

static int TargetSize;	 // remaining size of the target dma buffer
static char far *dmaptr; // pointer to this DMA block

	// if the DMA is dead, give it a jump start. Bad thing, it flushes all...

		if (DMARunning == 0) {

			// blow off anything that is saved locally

				dmaptr = 0;
				TargetSize = 0;

			// reset and restart the low level stuff...

				_resetbuffers();
				StartTheDMAInput(ContinueThisBlockInput);

            // we have no more data, just return now

				return(DMARunning);
		}

    // if the current remaining length is null, prime for the next block

		if (_file_data_length == 0) {

			// bomb out if no data buffers queued up

            if (__queueincnt == 0)
				return(1);

			// get the next block from the queue

			_file_data_length	 = __queuelen [__queueout];
			__singleblockpointer = __queuebuff[__queueout];
		}

	// loop here to stuff as many blocks as possible into the DMA buffer

    nextblock:

	// move up to one buffer division worth of data

		if (!TargetSize) {

            dmaptr = __NextPtr->buffer;
			TargetSize = BufferSize;
		}

		loop = TRUE;
		bcount = 1;

	// move as many blocks as possible into the DMA buffer

        while (loop) {

            // Get the block length, up to the division size

				if (_file_data_length <= TargetSize) {

					n = _file_data_length;	// full target size
					_file_data_length = 0;

                }
				else						// partial target size

					_file_data_length -= (n = TargetSize);

			// copy the data to the buffer, and advance the buffer that far

				if (n) {

					// move the recorded data into the buffer

					__singleblockpointer
						= _rfmemcpy(__singleblockpointer,dmaptr,n);
					dmaptr += n;				// move the dma pointer
					result += n;				// more for the return value

                    __queuedata -= (n &0xffff); // less queued up
					BufferDataCount -= bcount;	// increment buffer count once
					bcount = 0;

                }

			// if the length is zero, this buffer is done, let the caller know

				if (!_file_data_length) {

					// let the app. know we are done with this buffer

						if (__queuecb[__queueout])
							(*__queuecb[__queueout])(__queuebuff[__queueout],__queuelen[__queueout]);

						__queueincnt--;
						__queueout = ++__queueout & QUEUEMASK;

					// Now, try to get the next available block out of the list

					if (__queuein != __queueout) {

						_file_data_length	 = __queuelen[__queueout];
						__singleblockpointer = __queuebuff[__queueout];
                    }
					else
						loop = FALSE;
				}

			// we are now done with this much of the buffer, stop if zero

				if (!(TargetSize -= n))
                    loop = FALSE;

        }

	// advance the list to the next DMA buffer and count one more...

		__NextPtr = __NextPtr->nextptr;

	// if we can do more, then DO IT!!!

		if (BufferDataCount > 0) {				// if there is data in the DMA...
			if (__queueincnt)					// if we have buffers...
					goto nextblock; 			// then go load it...
		}

    // return the number of bytes loaded

		return(result);

}

#if 0

	//////////////// Original ContinueThisBlockInpu ///////////////


	/* if there is no more data, just exit									*/

		if (!_file_data_length)
			return(0);

	/* move as much as possible...											*/


		if (_file_data_length <= BufferSize) {
			n = _file_data_length;
			_file_data_length = 0;
		}
		else {
			_file_data_length -= (n = (BufferSize & 0xffff));
		}

		__singleblockpointer =
			_rfmemcpy(__singleblockpointer,__NextPtr->buffer,n);

		BufferDataCount--;



        __NextPtr         = __NextPtr->nextptr; /* advance the list     */

		return(n);
#endif


#endif


#if FILEIN
	/*\
	|*|----====< ContinueFileInput >====----
	|*|
	|*| This routine checks to see if another buffer can be written to disk.
	|*| if so, it will load copy the buffer to a local buffer, then write it
	|*| out to disk. A return value of 0 indicates recording has stopped,
	|*| which could mean that the disk file is full, so the DMA had to be
	|*| stopped prematurely.
	|*|
	\*/
int ContinueFileInput()
{

	/* if BufferDataCount is non-zero, we must write out the data		*/

		while (BufferDataCount) {

			/* data is available, move it out to disk					*/

#if LARGEDATA

			/* if not all data is written, return in error				*/

				if (fwrite (__NextPtr->buffer,1,BufferSize,__fptr) != BufferSize) {
					StopDMAIO();
					return (0);
				}
#else
			/* if not all data is written, return in error				*/

				_rfmemcpy  (__LocalBuff,__NextPtr->buffer,BufferSize);
				if (fwrite (__LocalBuff,1,BufferSize,__fptr) != BufferSize) {
					StopDMAIO();
					return (0);
				}
#endif
            /* move to the next buffer                                  */

				__NextPtr->status = 0;
				__NextPtr = __NextPtr->nextptr;
				BufferDataCount--;
		}
		return (DMARunning);
}
#endif


#if BLOCKOUT
	/*\
	|*|----====< ContinueBlockOutput >====----
	|*|
	|*| This routine checks to see if another DMA buffer can be loaded.
	|*| If so, it will load the user's block data into an empty buffer.
	|*| A return value of 1 indicates the buffer has been loaded, else
	|*| it must be sent in again until it is loaded.
	|*|
	\*/
int ContinueBlockOutput(buff)
	char far *buff;
{

	/* if the internal count is not max-ed out, try to load the next buffer */

		if (BufferDataCount < MaxBuffCount ) {

			_loadtheblock (buff);

			if (DMARunning == 0) {		/* yuck! a DMA break!			*/
				_resetbuffers();
				StartTheDMAOutput(0);
			}

			return (1); 				/* return running				*/
		}
		else
			return(0);
}


    /*\
	|*|----====< ContinueThisBlockOutput >====----
	|*|
	|*| This routine checks to see if another DMA buffer can be loaded.
	|*| If so, it will load the user's block data into an empty buffer.
	|*| A return value of ~0 indicates the buffer has been loaded.
	|*|
	|*| The foreground routine will call this when DMARunning == 0. The
	|*| background routine will call this at every interrupt to keep the
	|*| buffers loaded
    |*|
	\*/
int ContinueThisBlockOutput()
{

int n,          // working integer
	TargetSize, // size of the target dma buffer
	loop,		// loop flag to keep loading blocks
	bcount; 	// increments the BufferDataCount
int result = 0; // holds the final count
char far *s;

	// If no more data to load in the buffer, flush the next DMA & return

		if (__queueincnt == 0) {
			FlushBuffer (__NextPtr->buffer,BufferSize);
			__NextPtr = __NextPtr->nextptr;
			return(0);
		}

	// if there is little data, but a lot in the DMA, just return

		if ((__queuedata < BufferSize) && BufferDataCount > 2)
			return(0);

	// if the DMA has been turned off, re-sync the buffers

		if (DMARunning == 0)
			_resetbuffers();

	// if the current remaining length is null, prime for the next block

		if (_file_data_length == 0) {
			_file_data_length	 = __queuelen [__queueout];
			__singleblockpointer = __queuebuff[__queueout];
		}

	// loop here to stuff as many blocks as possible into the DMA buffer

    nextblock:

	// move up to one buffer division worth of data

		TargetSize = BufferSize;
		s = __NextPtr->buffer;
		loop = TRUE;
		bcount = 1;

	// move as many blocks as possible into the DMA buffer

        while (loop) {

            // Get the block length, up to the division size

				if (_file_data_length <= TargetSize) {

					n = _file_data_length;	// full target size
					_file_data_length = 0;

                }
				else						// partial target size

					_file_data_length -= (n = TargetSize);

			// copy the data to the buffer, and advance the buffer that far

				if (n) {

                    s = _rfmemcpy(s, __singleblockpointer, n );

					result += n;				// more for the return value
					__singleblockpointer += n;	// advance the pointer
					__queuedata -= (n &0xffff); // less queued up

                    BufferDataCount += bcount;  // increment buffer count once
					bcount = 0;

                }
				else
					s = __NextPtr->buffer;		// no data, but do point here

			// if the length is zero, this buffer is done, let the caller know

				if (!_file_data_length) {

					// if this old block was valid, send a DONE msg.

					if(__queueincnt) {

                        // let the app. know we are done with this buffer

						if (__queuecb[__queueout])
							(*__queuecb[__queueout])(__queuebuff[__queueout],FALSE);

						__queueincnt--;
						__queueout = ++__queueout & QUEUEMASK;
					}

					// Now, try to get the next available block out of the list

					if (__queuein == __queueout) {

                        FlushBuffer (s,TargetSize-n);
						loop = FALSE;

                    }
					else {

						_file_data_length	 = __queuelen[__queueout];
						__singleblockpointer = __queuebuff[__queueout];

					}
				}

			// we are now done with this much of the buffer, stop if zero

				if (!(TargetSize -= n))
                    loop = FALSE;
        }

	// advance the list to the next DMA buffer and count one more...

		__NextPtr = __NextPtr->nextptr;

	// if we can do more, then DO IT!!!

		if (BufferDataCount < MaxBuffCount) {	// if there is room in the DMA
			if (__queueincnt) { 				// if we have pcm data
				if (__queuedata >= BufferSize)	// and its GE a buffer division,
					goto nextblock; 			// then go load it...
			}
		}

		if (DMARunning == 0)
            StartTheDMAOutput(ContinueThisBlockOutput);

    // return the number of bytes loaded

		return(result);

}
#endif


#if FILEOUT
	/*\
	|*|----====< ContinueFileOutput >====----
	|*|
	|*| This routine checks to see if another buffer can be loaded. If so, it
	|*| will load the data into an empty buffer. All empty buffers will be
	|*| loaded. A return value of 0 indicates playing has finished.
	|*|
	\*/
int ContinueFileOutput()
{

	/* if BufferDataCount is not max-ed out, try to load the next buffer*/

		if (BufferDataCount < MaxBuffCount ) {

			if (_loadthebuffer (__fptr)) {

				if (DMARunning == 0) {	 /* yuck! a DMA break!			*/
					_resetbuffers();
					if (StartTheDMAOutput(0))
						return(0);
				}
			}
		}
		return (DMARunning);			/* return the DMA state 		*/
}
#endif


#if COMMDATA
    /*\
	|*|----====< OpenPCMBuffering >====----
	|*|
	|*|  This routine is the first-call routine. It initializes the buffers
	|*|  needed for the PCM play/record system. A return value of non-zero
	|*|  indicates a failure to initialize the system.
	|*|
	|*|  Entry Conditions:
	|*|
	|*| 		dma -- New DMA #. (1-3, or -1 for no changes)
	|*| 		irq -- New IRQ #. (3,5,6,7, or -1 for no changes)
	|*|
	|*|  Exit Conditions:
	|*|
	|*| 		non-zero return indicates an error
	|*|
	\*/
int OpenPCMBuffering(dma,irq,dmasize,divisions)
   int dma; 		/* DMA channel # (-1 for no changes)	*/
   int irq; 		/* IRQ channel # (-1 for no changes)	*/
   int dmasize; 	/* requested DMA size (4/8/16/32/64)	*/
   int divisions;	/* # of divisions in the DMA buffer 	*/
{
BuffPtr op,p;
long l;
int n;
char far *db;

	/* setup the globa variables & a local buffer						*/

		MaxBuffCount = divisions;
		BufferSize	 = LONG(dmasize) * 1024L / LONG(MaxBuffCount);

	/* Setup the lowlevel routines										*/

		InitMVSound();

    /* flush any background task setup                                  */

		BackgroundInit( BufferSize, MaxBuffCount );

		if ((__LocalBuff=(char*)malloc(BufferSize)) == 0)
			 return (PCMIOERR_OPENFILE);

	/* Allocate twice the size for background DMA buffer				*/

		l = LONG(dmasize) * 1024 * 2;

		if ((DMABuffPtr = (char huge *) halloc (l,1)) == 0)
			return(PCMIOERR_NOMEM);

		if ((db=StartOfDMABuffer=FindDMABuffer(DMABuffPtr,dmasize)) == 0)
			return (PCMIOERR_OPENPCM);

	/* if the low level code doesn't like it, bomb out                  */

		if (!DMABuffer ( StartOfDMABuffer, dmasize, MaxBuffCount ))
			return(PCMIOERR_OPENPCM);

	/* Attempt to allocate each foreground buffer						*/

		op = 0;
		for (n=0;n<divisions;n++) {

			/* allocate the linked list header for each buffer			*/

				if ((p = (BuffPtr) malloc (sizeof(BuffData))) == 0)
					return(PCMIOERR_NOMEM);

			/* reset the pointer in case of other failures during init	*/

				p->nextptr = 0;

			/* if first block, save as the head of the list 			*/

				if (!HeadOfBuffers)
					HeadOfBuffers = p;

			/* if we have already allocated a block, setup the fwd ptr	*/

				if (op)
					op->nextptr = p;

				p->buffer = db;
				p->size   = BufferSize;
				db		  += BufferSize;

			/* save as the old pointer for linking purposes 			*/

				op = p;
		}

	/* link the last buffer back to the first							*/

		p->nextptr = HeadOfBuffers;

	/* Possibly select new DMA & IRQ channels							*/

		if (dma != -1)
			if (SelectDMA(dma))
				return(PCMIOERR_BADDMA);

		if (irq != -1)
			if (SelectIRQ(irq))
				return(PCMIOERR_BADIRQ);

	/* well, it looks good so far, flush any variables					*/

		BufferDataCount   = ProcessedBlockCount =
		_file_data_length = __queuedata 		=
		VoiceActivatedSavedCount = __queueincnt =
		__queuein  = __queueout = 0;

	/* and return good! 												*/

		return (0);
}
#endif


#if COMMDATA
    /*\
	|*|----====< PCMState >====----
	|*|
	|*| This routine passes in the sample rate, stereo/mono flag, and any
	|*| other miscellaneous data (to be determined later...)
	|*|
	|*| Exit Conditions:
	|*|    Non-zero means the sample rate was in error.
	|*|    Zero means the sample rate was okay error.
	|*|
	\*/
int PCMState(sr,sm,cp,sz)
	long sr;	/* sample rate	*/
	int  sm;	/* stereo/mono	*/
	int  cp;	/* compression	*/
	int  sz;	/* size(8/16)	*/
{

	/* just pass them on... 											*/

		__pcmdatasize = sz; 					/* pcm data size		*/
		return (!PCMInfo (sr,sm,cp,sz));

}
#endif


#if COMMDATA
	/*\
	|*|----====< StopDMAIO >====----
	|*|
	|*| This routine forceably kills the PCM I/O. All buffers will be
	|*| reset, the current position of the input file is not altered. There
	|*| is no return value.
	|*|
	\*/
void StopDMAIO()
{

	/* if this code has not already been setup, exit now				*/

		if (!HeadOfBuffers)
			return;

	/* stop the hardware... 											*/

		StopPCM( );

		__queuein	= __queueincnt = __queueout = DMARunning = 0;
		__queuedata = _file_data_length = 0;

	/* flush any prior background task setup							*/

		if (__DirectionFlag == DMAOUTPUT) {
			if (__fptr)
				rewind	(__fptr);
		}

	/* reset the linked list of buffers 								*/

		_resetbuffers();

    /* setup our internal direction flag                                */

		__DirectionFlag = NODIRECTION;
}
#endif


#if BLOCKIN
	/*\
	|*|----====< StartBlockInput >====----
	|*|
	|*| This routine resets the buffer pointers, then starts up
	|*| the DMA PCM input. Nothing else needs to be done. A return
	|*| value of 0 indicates the DMA failed to startup; No input
	|*| is occuring.
	|*|
	\*/
int StartBlockInput()
{

	/* setup our internal direction flag								*/

		__DirectionFlag = DMAINPUT;

    /* Reset the # of blocks seen during I/O processing                 */

		ProcessedBlockCount = 0;

	/* Flush all buffers												*/

		_resetbuffers();

	/* if the hardware level code isn't gonna work, then return a failure. */

		return (!StartTheDMAInput(0));
}


    /*\
	|*|----====< RecordThisBlock >====----
	|*|
	|*| This routine offers the caller a simplified recording of one
	|*| variable length block of data. The call just needs to call
	|*| OpenPCMBuffering, then RecordThisBlock. The caller just has
	|*| to poll DMARunning to see if the block has completed. Calling
	|*| StopDMAIO will stop the process, if need be.
	|*|
	\*/
int RecordThisBlock(p,len,cb)
	char far *p;
	unsigned long len;
	void (far *cb)();
{
int n;

        ///////////////////// under construction! /////////////////////////

    // if the pointer is valid, then queue it up

		if (p) {

			// return if already full

			if (__queuein == QUEUESIZE)
				return(2);

			// extract the 1st entry from the queue

			__queuebuff[__queuein] = p;
			__queuedata += (__queuelen [__queuein] = len);
			__queuecb  [__queuein] = cb;
			__queuein  = ++__queuein & QUEUEMASK;
			__queueincnt++;

        }

	// if the blocks are not recording , the let'er rip...

		if ((DMARunning == 0) && __queueincnt ) {

			// setup the direction flag

				__DirectionFlag = DMAINPUT;

			/* reset the DMA block pointers 							*/

				_resetbuffers();

			/* return good or bad if the PCM engine is running			*/

				return (ContinueThisBlockInput() ? 1 : 0 );

		}

	// assume the block is now recording

		return(0);

}
#endif


#if FILEIN
	/*\
	|*|----====< StartFileInput >====----
	|*|
	|*| This routine resets the buffer pointers, then starts up the DMA PCM
	|*| input. Nothing else needs to be done.
	|*|
	\*/
int StartFileInput(f)
   FILE *f;
{

	/* save our local file handle										*/

		__fptr	  = f;

	/* setup our internal direction flag								*/

		__DirectionFlag = DMAINPUT;

	/* Reset the # of blocks seen during I/O processing 				*/

		ProcessedBlockCount = 0;

	/* Flush all buffers and other stuff..								*/

		_resetbuffers();
		VoiceActivatedSavedCount = 0;

	/* start the DMA engine 											*/

		return (!StartTheDMAInput(0));
}
#endif


#if BLOCKOUT
	/*\
	|*|----====< StartBlockOutput >====----
	|*|
	|*| This routine allocates and loads the necessary buffers with data from
	|*| the PCM disk file. Upon return, if there is data available, the
	|*| background task will be called to start the DMA. The file handle will
	|*| be saved in a global variable to be access from other foreground
	|*| routines. a non-zero return value indicates PCM output is playing.
	|*|
	\*/
int StartBlockOutput(buff)
	char far *buff;
{

	/* setup our internal direction flag								*/

		__DirectionFlag = DMAOUTPUT;

	/* Reset the # of blocks seen during I/O processing 				*/

		ProcessedBlockCount = 0;

    /* load the DMA buffers                                             */

        _resetbuffers();
		_loadtheblock (buff);

	/* return good or bad if the engine is started						*/

		return (!StartTheDMAOutput(0));
}


    /*\
	|*|----====< PlayThisBlock >====----
	|*|
	|*| This routine offers the caller a simplified playback of one
	|*| variable length block of data. The call just needs to call
	|*| OpenPCMBuffering, then PlayThisBlock. The caller just has
	|*| to poll DMARunning to see if the block has completed. Calling
	|*| StopDMAIO will stop the process, if need be.
	|*|
	|*| Also see QueueThisBlock.
    |*|
	|*| Entry:
	|*|    p   is the far pointer to a block of data (64k max size). If
	|*| 	   the pointer is null, the block will not be queued.
	|*|    len is the length of the block in bytes (one based count).
	|*|    cb  is the callback routine to call when the block is empty.
    |*|
	|*| Returns:
	|*|    0 - block is queued and playing
	|*|    1 - Block failed to start
	|*|    2 - queue is full, try later
    |*|
	\*/
int PlayThisBlock(p,len,cb)
	char far *p;
	unsigned long len;
	void (far *cb)();
{
int n;

	// if the pointer is valid, then queue it up

		if (p) {

			// return if already full

			if (__queuein == QUEUESIZE)
				return(2);

            // extract the 1st entry from the queue

			__queuebuff[__queuein] = p;
			__queuedata += (__queuelen [__queuein] = len);
			__queuecb  [__queuein] = cb;
			__queuein  = ++__queuein & QUEUEMASK;
			__queueincnt++;

        }

    // if the blocks are not playing, the let'er rip...

		if ((DMARunning == 0) && __queueincnt ) {

			// setup to transfer this block

				__DirectionFlag = DMAOUTPUT;

			// start the process by loading the DMA buffers

				return (ContinueThisBlockOutput() ? 0 : 1 );

        }

	// Indicate this has been queued up

		return(0);
}


    /*\
	|*|----====< QueueThisBlock >====----
	|*|
	|*| This routine will queue up one block, but not start the PCM
	|*| transfers. Once X number of blocks are queued up, then the caller
	|*| can call PlayThisBlock/RecordThisBlock to start the process.
	|*|
	|*| Also see PlayThisBlock/RecordThisBlock.
    |*|
	|*| Entry:
	|*|    p   is the far pointer to a block of data (64k max size).
	|*| 	   All pointers are assumed to be valid.
	|*|    len is the length of the block in bytes (one based count).
	|*|    cb  is the callback routine to call when the block is empty.
    |*|
	|*| Returns:
	|*|    0 - block is queued and playing
	|*|    2 - queue is full, try later
    |*|
	\*/
int QueueThisBlock(p,len,cb)
	char far *p;
	unsigned long len;
	void (far *cb)();
{
int n;

	// return if already full

		if (__queuein == QUEUESIZE)
			return(2);

	// if idle, setup our internal direction flag, and the desired length

		__queuebuff[__queuein] = p;
		__queuedata += (__queuelen [__queuein] = len);
		__queuecb  [__queuein] = cb;
		__queuein  = ++__queuein & QUEUEMASK;
		__queueincnt++;

	// return all queued up

		return(0);

}


    /*\
	|*|----====< SyncCallBack >====----
	|*|
	|*| This routine will setup a callback to the caller's routine
	|*| at the end of every DMA block interrupt
	|*|
	|*| Returns:
    |*|
	\*/
int SyncCallBack(cb)
	void (far *cb)();
{
int n;

        ///////////////////// under construction! /////////////////////////

    // just save for a later call...

		__synccallback = cb;

}

#endif


#if FILEOUT
	/*\
	|*|----====< StartFileOutput >====----
	|*|
	|*| This routine allocates and loads the necessary buffers with data from
	|*| the PCM disk file. Upon return, if there is data available, the
	|*| background task will be called to start the DMA. The file handle will
	|*| be saved in a global variable to be access from other foreground
	|*| routines. a non-zero return value indicates PCM output is playing.
	|*|
	\*/
int StartFileOutput(f,len)
	FILE *f;
	long len;
{

	/* save our local file handle										*/

		__fptr = f;

	/* setup our internal direction flag, and the desired length		*/

		__DirectionFlag   = DMAOUTPUT;
		_file_data_length = len;

	/* Reset the # of blocks seen during I/O processing 				*/

		ProcessedBlockCount = 0;

    /* if any data is loaded into the buffer, start the DMA & return    */

		_resetbuffers();

		do {

			/* get the next buffer full & exit if done					*/

			if (!_loadthebuffer (f))
				break;

		} while (__NextPtr != HeadOfBuffers);

	/* return good or bad if the engine is running						*/

		return (!StartTheDMAOutput(0));

}
#endif


#if BLOCKOUT
	/*\
	|*|----====< _loadtheblock >====----
	|*|
	|*| This routine loads the block into the DMA buffer.
	|*|
	\*/
static int _loadtheblock(buff)
	char far *buff;
{

	/* load the block of data into the DMA buffer						*/

		_rfmemcpy(__NextPtr->buffer, buff, BufferSize );

	/* now that the data is secure, fill out the rest of the header to	*/
	/* allow the background process to "see" this new data              */

		__NextPtr->status = 1;
		__NextPtr->count  = BufferSize;
		__NextPtr		  = __NextPtr->nextptr; /* advance the list 	*/
		BufferDataCount++;

	/* we have data, return the size									*/

	   return (BufferSize);
}
#endif


#if FILEOUT
	/*\
	|*|----====< _loadthebuffer >====----
	|*|
	|*| This routine loads the disk contents into an available buffer.
	|*| A return value of 0 indicates no more data has been loaded.
	|*|
	\*/
static int _loadthebuffer(f)
	FILE *f;
{
char huge *s;
register int n;
long l;

	/* reset the header data											*/

		__NextPtr->count = __NextPtr->status = 0;

	/* exit if there is no data to be read								*/

		if (feof (f) || (!_file_data_length))
			return (0);

	/* adjust the max count we want to process from the file			*/

		if (_file_data_length <= BufferSize) {
			l = _file_data_length;
			_file_data_length = 0;
		}
		else {
			_file_data_length -= (l = (BufferSize & 0xffff));
		}

	/* read the data from the file										*/

#if LARGEDATA
		if((n = fread (__NextPtr->buffer, 1,((unsigned int)(l & 0xffff)), f)) == 0)
			return(0);

		s = __NextPtr->buffer+n; // point to the end of the block
#else
        if((n = fread (__LocalBuff, 1,((unsigned int)(l & 0xffff)), f)) == 0)
			return(0);

	/* move the data to the linked list. Yuck! double handling of data	*/
	/* because fread won't take a far pointer in small model!           */

		s = _rfmemcpy(__NextPtr->buffer, __LocalBuff, n ); // s points to eob
#endif

	// flush to the end if not a full buffer worth of data

		if (n < BufferSize)
			FlushBuffer (s,BufferSize-n);

	/* now that the data is secure, fill out the rest of the header to	*/
	/* allow the background process to "see" this new data              */

		__NextPtr->status = 1;
		__NextPtr->count  = BufferSize;
		__NextPtr		  = __NextPtr->nextptr; /* advance the list 	*/
		BufferDataCount++;

	/* we have data, return the size									*/

	   return (n);
}
#endif


#if COMMDATA
	/*\
	|*|----====< _resetbuffers >====----
	|*|
	|*| This routine flushes the contents of the top level buffers
	|*|
	\*/
_resetbuffers()
{

	/* flush the count and status of every linked list block			*/

		if ((__NextPtr = HeadOfBuffers)) {

			/* if there are buffers, reset them all 					*/

			do {

				/* get the next buffer full & exit if done				*/

					__NextPtr->count = __NextPtr->status = 0;

				/* break when we reach the top							*/

					if ((__NextPtr = __NextPtr->nextptr) == 0)
							break;

			} while (__NextPtr != HeadOfBuffers);

		}

	/* reset the global hand shake count.								*/

		BufferDataCount = 0;
}
#endif


	/*\
	|*| end of PCMIOC.C
	\*/

