/*$Author: DCODY $*/ /*$Date: 06 Jan 1993 15:26:34 $*/ /*$Header: X:/sccs/pcm/pcmioc.c_v 1.11 06 Jan 1993 15:26:34 DCODY $*/ /*$Log: X:/sccs/pcm/pcmioc.c_v $ * * Rev 1.11 06 Jan 1993 15:26:34 DCODY * corrected two bugs: ContinueThisBlockOutput had a bug in an IF statement * where it evaluated the two expressions incorrectly. The second bug was * in QueryPCMStream, which returned an incorrect value. * Also, some debugging info was added, but commented out. * * Rev 1.10 08 Dec 1992 17:13:54 DCODY * added a new routine: QueryPCMStream to return the number of blocks * buffered. * Also added, but commented out some debugging code. * * Rev 1.9 20 Oct 1992 10:07:38 DCODY * lots of cosmetic changes. all variables are now initialized so they * have memory allocated at compile time. * * Rev 1.8 06 Oct 1992 15:59:50 DCODY * major changes so code is free (freer) from the C libraries. Replaced * malloc and fread/fwrite. * * 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.11 $*/ /*$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 #include #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 far *nextptr; /* pointer to next buffer hdr */ } BuffData, far *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 unsigned int MaxBuffCount = 0; /* # of DMA buffer divisions */ unsigned 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 */ long __fptrpos = 0; /* file pointer position */ BuffPtr __NextPtr = 0; /* next buffer pointer */ int __DirectionFlag = 0; /* current I/O direction */ char far * __singleblockpointer = 0;/* 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 { 0 }; long __queuelen[QUEUESIZE] = // queued block lengths { 0 }; void (far * __queuecb[QUEUESIZE])()=// queue of callback routines { 0 }; void (far *__synccallback)() = 0; // callback to user code #else extern unsigned int MaxBuffCount; /* # of DMA buffer divisions */ extern unsigned 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 long __fptrpos; /* file pointer position */ 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 extern int __debugdw (); #endif /* additional prototypes */ void far * _rfmemcpy ( void far *, void far *, unsigned int ); void huge * _rfhmemcpy ( void huge *,void huge *,unsigned int ); void far * _memmalloc ( long ); void _memmfree ( void far * ); int _dofread ( char far *, int, int ); int _dofwrite ( char far *, int, int ); #if BLOCKOUT static int _loadtheblock ( char far * ); #endif #if FILEOUT static int _loadthebuffer (); #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) { /* validate the level of noise before writing it to disk */ if (MakeHalfHistoGram(__NextPtr->buffer,BufferSize,noise) || (VoiceActivatedSavedCount && goflag) ) { /* if not all data is written, return in error */ if (_dofwrite (__NextPtr->buffer,BufferSize,fileno(__fptr)) != BufferSize) { StopDMAIO(); return (0); } VoiceActivatedSavedCount++; } else ProcessedBlockCount--; /* move to the next buffer */ __NextPtr->count = __NextPtr->status = 0; __NextPtr = __NextPtr->nextptr; BufferDataCount--; } /* if at the end of this block, reposition the stream pointer */ if (!DMARunning) fseek ( __fptr, __fptrpos, SEEK_SET ); return (DMARunning); } #endif #if COMMDATA ; /*\ ;---|*|----====< ClosePCMBuffering >====---- ;---|*| ;---|*| Removes the PCM system & deallocates the buffer memory. There is ;---|*| no return value. ;---|*| ; \*/ void ClosePCMBuffering() { BuffPtr p,op; //__debugdw (0x01); /* 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 */ _memfree (op); /* free up the old header */ } while ( (p != HeadOfBuffers) && p ); } /* free up the DMA buffer */ if (DMABuffPtr) _memfree (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() { unsigned int n, // working integer loop, // loop flag to keep loading blocks bcount; // increments the BufferDataCount unsigned int result = 0; // holds the final count static unsigned 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); } #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 not all data is written, return in error */ if (_dofwrite (__NextPtr->buffer,BufferSize,fileno(__fptr)) != BufferSize) { StopDMAIO(); return (0); } /* move to the next buffer */ __NextPtr->status = 0; __NextPtr = __NextPtr->nextptr; BufferDataCount--; } /* if at the end of this block, reposition the stream pointer */ if (!DMARunning) fseek ( __fptr, __fptrpos, SEEK_SET ); 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() { unsigned int n, // working integer TargetSize, // size of the target dma buffer loop, // loop flag to keep loading blocks bcount; // increments the BufferDataCount unsigned 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) { //__debugdw (0x20); 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)) { //__debugdw (0x21); return(0); } // if the DMA has been turned off, re-sync the buffers if (DMARunning == 0) { //__debugdw (0x29); _resetbuffers(); } // if the current remaining length is null, prime for the next block if (_file_data_length == 0) { //__debugdw (0x2A,__queueout); _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) { //__debugdw (0x22); // 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); //__debugdw (0x23,n); // 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) { //__debugdw (0x24); // 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) { //__debugdw (0x25); FlushBuffer (s,TargetSize-n); loop = FALSE; } else { //__debugdw (0x26); _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, //__debugdw (0x27); goto nextblock; // then go load it... } } } if (DMARunning == 0) { //__debugdw (0x28); 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()) { if (DMARunning == 0) { /* yuck! a DMA break! */ _resetbuffers(); if (StartTheDMAOutput(0)) return(0); } } } /* if at the end of this block, reposition the stream pointer */ if (!DMARunning) fseek ( __fptr, __fptrpos, SEEK_SET ); 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; //__debugdw (0x00); /* setup the globa variables & a local buffer */ MaxBuffCount = divisions; BufferSize = LONG(dmasize/divisions) * 1024L; /* Setup the lowlevel routines */ InitMVSound(); /* flush any background task setup */ BackgroundInit( BufferSize, MaxBuffCount ); /* Allocate twice the size for background DMA buffer */ l = LONG(dmasize) * 1024 * 2; if ((DMABuffPtr = (char huge *)_memmalloc(l)) == 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;nnextptr = 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) */ { //__debugdw (0x02); /* just pass them on... */ __pcmdatasize = sz; /* pcm data size */ return (PCMInfo(sr,sm,cp,sz) ? PCMIOERR_SAMPLERATE : 0 ); } #endif #if COMMDATA ; /*\ ;---|*|----====< QueryPCMStream >====---- ;---|*| ;---|*| Returns the number of blocks playing in the DMA buffer and ;---|*| the number of blocks queued up. ;---|*| ;---|*| Exit Conditions: ;---|*| 0 - x is the number of blocks waiting to be played ;---|*| -1 DMA is stopped, no more data ;---|*| ; \*/ int QueryPCMStream() { //__debugdw (0x15,__queueincnt+BufferDataCount); // done if no DMA activity if (!DMARunning) return(-1); // return the count of active blocks return (__queueincnt+BufferDataCount); } #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() { //__debugdw (0x03); /* 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); //// __fptrpos = 0; //// } ////} /* 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; // 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; __fptrpos = ftell(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++; //__debugdw (0x10,__queueincnt); //__debugdw (0x14,BufferDataCount); } // if the blocks are not playing, the let'er rip... if ((DMARunning == 0) && __queueincnt ) { //__debugdw (0x11); // 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) { //__debugdw (0x12,__queueincnt); 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++; //__debugdw (0x12,__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; // 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; __fptrpos = ftell(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()) 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() { 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 (__fptr) || (!_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((n = _dofread (__NextPtr->buffer, ((int)(l & 0xffff)),fileno(__fptr) )) == 0) return(0); s = __NextPtr->buffer+n; // point to the end of the block // 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) != 0) { /* 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; } int __debugdw (int idx,int val) { #if 0 _asm { push di push es sub di,di mov es,di les di,es:[0x194] mov ax,es or ax,ax jz dedw05 cmp di,16768 jae dedw05 cld mov ax,idx stosb mov ax,val stosw sub ax,ax mov es,ax mov es:[0x194],di } dedw05: _asm { pop es pop di } #endif } #endif ; /*\ ;---|*| end of PCMIOC.C ; \*/