Category : Music and Digitized Voice
Archive   : PAS-SDK1.ZIP
Filename : PCMIOC.C

 
Output of file : PCMIOC.C contained in archive : PAS-SDK1.ZIP
/*$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
#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 *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
/* 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
\*/



  3 Responses to “Category : Music and Digitized Voice
Archive   : PAS-SDK1.ZIP
Filename : PCMIOC.C

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/