home *** CD-ROM | disk | FTP | other *** search
/ Shareware Super Platinum 8 / Shareware Super Platinum 8.iso / mac / PROGTOOL / PASSDK30.ZIP;1 / DISK1.ZIP / PAS / SUBS / PCM / PCMIOC.C < prev    next >
Encoding:
Text File  |  1993-02-24  |  35.4 KB  |  1,281 lines

  1. /*$Author:   DCODY  $*/
  2. /*$Date:   24 Feb 1993 16:17:14  $*/
  3. /*$Header:   X:/sccs/pcm/pcmioc.c_v   1.15   24 Feb 1993 16:17:14   DCODY  $*/
  4. /*$Log:   X:/sccs/pcm/pcmioc.c_v  $
  5.  * 
  6.  *    Rev 1.15   24 Feb 1993 16:17:14   DCODY
  7.  * added code to return all queued buffers in StopDMAIO.
  8.  * 
  9.  *    Rev 1.14   09 Feb 1993 08:31:58   DCODY
  10.  * changed SyncCallBack prototype to void SyncCallBack(void(far*)());
  11.  * 
  12.  *    Rev 1.13   09 Feb 1993 08:28:10   DCODY
  13.  * separated out the old file and block I/O processes to another file. This
  14.  * file now contains the new process of PlayThisBlock, RecordThisBlock, and
  15.  * related routines. This forms the new core of block I/O PCM routines.
  16.  * 
  17.  *    Rev 1.12   03 Feb 1993 12:09:48   DCODY
  18.  * just more debug statements added, no real changes.
  19.  * 
  20.  *    Rev 1.11   06 Jan 1993 15:26:34   DCODY
  21.  * corrected two bugs: ContinueThisBlockOutput had a bug in an IF statement
  22.  * where it evaluated the two expressions incorrectly. The second bug was
  23.  * in QueryPCMStream, which returned an incorrect value. 
  24.  * Also, some debugging info was added, but commented out.
  25.  * 
  26.  *    Rev 1.10   08 Dec 1992 17:13:54   DCODY
  27.  * added a new routine: QueryPCMStream to return the number of blocks
  28.  * buffered.
  29.  * Also added, but commented out some debugging code.
  30.  * 
  31.  *    Rev 1.9   20 Oct 1992 10:07:38   DCODY
  32.  * lots of cosmetic changes. all variables are now initialized so they
  33.  * have memory allocated at compile time.
  34.  * 
  35.  *    Rev 1.8   06 Oct 1992 15:59:50   DCODY
  36.  * major changes so code is free (freer) from the C libraries. Replaced
  37.  * malloc and fread/fwrite.
  38.  * 
  39.  *    Rev 1.7   01 Oct 1992 12:05:02   DCODY
  40.  * next stage of completion for PlayThisBlock, RecordThisBlock, etc.
  41.  * 
  42.  *    Rev 1.6   23 Sep 1992 10:56:34   DCODY
  43.  * more work on playthisblock, continuethisblock...
  44.  * 
  45.  *    Rev 1.5   26 Aug 1992 10:57:30   DCODY
  46.  * Added Playthisblock and RecordThisBlock
  47.  * 
  48.  *    Rev 1.4   12 Aug 1992 17:10:30   DCODY
  49.  * major change to eliminate the foreground buffers.
  50.  * 
  51.  *    Rev 1.3   24 Jul 1992 15:36:14   DCODY
  52.  * changed _fmemcpy to _rfmemcpy
  53.  * 
  54.  *    Rev 1.2   17 Jul 1992 14:22:50   DCODY
  55.  * InitMVSound() now performed within OpenPCMBuffering().
  56.  * 
  57.  *    Rev 1.1   23 Jun 1992 17:11:42   DCODY
  58.  * PAS2 update
  59.  * 
  60.  *    Rev 1.0   15 Jun 1992 09:44:38   BCRANE
  61.  * Initial revision.
  62. */
  63. /*$Logfile:   X:/sccs/pcm/pcmioc.c_v  $*/
  64. /*$Modtimes$*/
  65. /*$Revision:   1.15  $*/
  66. /*$Workfile:   pcmioc.c  $*/
  67.  
  68.  
  69. ;    /*\
  70. ;---|*|----====< PCMIOC.C >====----
  71. ;---|*|
  72. ;---|*| These routines maintain DMA controlled I/O of the Audio Spectrum
  73. ;---|*|
  74. ;---|*| Copyright (c) 1991, Media Vision, Inc. All rights reserved.
  75. ;---|*|
  76. ;    \*/
  77.  
  78. #include <stdio.h>
  79. #include <stdlib.h>
  80.  
  81. #include "pcmio.h"
  82. #include "common.h"
  83. #include "mvsound.h"
  84.  
  85. ;    /*\
  86. ;---|*|-----------====< T H E O R Y   O F    O P E R A T I O N >====------------
  87. ;---|*|
  88. ;---|*| The best DMA controlled PCM output requires a continuous stream of data
  89. ;---|*| to be available in a real-time environment.
  90. ;---|*|
  91. ;---|*| DMA controlled PCM input, with the same real-time requirements, needs
  92. ;---|*| to be able to keep storing data into memory without pausing.
  93. ;---|*|
  94. ;---|*| The following approach is designed to allow the DMA to be setup in
  95. ;---|*| "auto-initialize" mode, thereby guarenteeing continuous play/record.
  96. ;---|*|
  97. ;---|*| To keep the DMA running, multiple divisions of the DMA buffer are
  98. ;---|*| used to keep the data moving. Due to the fact that DOS is neither
  99. ;---|*| a real-time, or re-entrant operating system, this code divides up
  100. ;---|*| the buffer management tasks into a "foreground" and "background" task.
  101. ;---|*|
  102. ;---|*| A sample buffer count timer on the Audio Spectrum is used to interrupt
  103. ;---|*| the CPU when a DMA buffer division has filled or emptied. For our
  104. ;---|*| purposes here, this amount may be 1/2, 1/4, 1/8th or some smaller
  105. ;---|*| division of the whole DMA buffer. Note: judgement must be used here
  106. ;---|*| in selecting the DMA buffer size, and the integral division. Too small
  107. ;---|*| of an integral may result in broken DMA I/O. A buffer too large never
  108. ;---|*| hurts anything. (it just reduces the amount of available memory).
  109. ;---|*|
  110. ;---|*|          ----====< I m p l e m e n t a t i o n >====----
  111. ;---|*|
  112. ;---|*| The approach used in this implementation is to provide a queue for
  113. ;---|*| receiving the user's PCM blocks. These blocks are emptied into the
  114. ;---|*| DMA buffer, or filled from the DMA buffer, at appropriate times. The
  115. ;---|*| user is notified on a block by block basis as each block is handled
  116. ;---|*| via a callback mechanism. Internally within these high level routines,
  117. ;---|*| the queue of blocks is used to fill/empty a circular DMA buffer so the
  118. ;---|*| DMA will continue to run. The user's blocks can be any arbitrary size.
  119. ;---|*| to make handling data easier at the application level.
  120. ;---|*|
  121. ;---|*|                 ----====< PCM OUTPUT >====----
  122. ;---|*|
  123. ;---|*| To actually start the output, the foreground passes in blocks first to
  124. ;---|*| QueueThisBlock with the last block passed to PlayThisBlock. The routine,
  125. ;---|*| PlayThisBlock queues the user's data, then starts up the PCM engine, if
  126. ;---|*| not already running. The background task continues to move the users
  127. ;---|*| data into the DMA buffer at each interrupt. If no more data is present
  128. ;---|*| in the queue, the DMA is shut down.
  129. ;---|*|
  130. ;---|*|                    Caller sends blocks
  131. ;---|*|                       to be played
  132. ;---|*|                            
  133. ;---|*|                    Caller's buffers are
  134. ;---|*|                      loaded into DMA
  135. ;---|*|                          Buffers
  136. ;---|*|                            
  137. ;---|*|                        ⁄ƒ¬ƒ¬ƒ¬ƒ¬ƒø
  138. ;---|*|      DMA Level Buffers ≥ ≥ ≥ ≥ ≥ ≥
  139. ;---|*|                        ¿ƒ¡ƒ¡ƒ¡ƒ¡ƒŸ
  140. ;---|*|                            
  141. ;---|*|                        ⁄ƒƒƒƒƒƒƒƒƒø
  142. ;---|*|                        ≥hardware ≥
  143. ;---|*|                        ¿“““ƒƒƒƒƒƒŸ
  144. ;---|*|
  145. ;---|*| If the user code can keep the queue full, there should be non-stop PCM
  146. ;---|*| output (Good!). If not, the background task will be forced to stop the
  147. ;---|*| the DMA, thereby causing a break in the output (Bad!). Once the DMA has
  148. ;---|*| stopped, the user code will have to restart the DMA a second time to
  149. ;---|*| continue the flow of data.
  150. ;---|*|
  151. ;---|*|                 ----====< PCM INPUT >====----
  152. ;---|*|
  153. ;---|*| To perform PCM input ("record"), the same queue now receives the
  154. ;---|*| empty user's block to be filled at interrupt time. As each block is
  155. ;---|*| filled the user is notified via the user supplied call back routine.
  156. ;---|*|
  157. ;---|*|                   Caller Notified when
  158. ;---|*|                      buffers' filled
  159. ;---|*|                            
  160. ;---|*|                   Caller's buffers are
  161. ;---|*|                      filled from DMA
  162. ;---|*|                          Buffers
  163. ;---|*|                            
  164. ;---|*|                        ⁄ƒ¬ƒ¬ƒ¬ƒ¬ƒø
  165. ;---|*|      DMA Level Buffers ≥ ≥ ≥ ≥ ≥ ≥
  166. ;---|*|                        ¿ƒ¡ƒ¡ƒ¡ƒ¡ƒŸ
  167. ;---|*|                            
  168. ;---|*|                        ⁄ƒƒƒƒƒƒƒƒƒø
  169. ;---|*|                        ≥hardware ≥
  170. ;---|*|                        ¿“““ƒƒƒƒƒƒŸ
  171. ;---|*|
  172. ;---|*| To actually start the input, the foreground calls RecordThisBlock with
  173. ;---|*| an empty block. The routine will queue the block, then start the PCM
  174. ;---|*| engine. The caller must provide additional blocks to keep the process
  175. ;---|*| running. If the queue is empty at interrupt time, the DMA transfer
  176. ;---|*| is terminated. If the caller can keep the blocks queued up, there
  177. ;---|*| should be non-stop PCM input (Good!). If not, the background task will
  178. ;---|*| be forced to stop the the DMA, thereby causing a break in the input
  179. ;---|*| (Bad!). Once the DMA has stopped, the foreground task will have to
  180. ;---|*| restart the DMA tranfer a second time to restart the DMA.
  181. ;---|*|
  182. ;---|*|                 ----====< DATA VARIABLES >====----
  183. ;---|*|
  184. ;---|*| The following is a description of the variables shared between the
  185. ;---|*| foreground and background tasks of the PCMIO routines.
  186. ;---|*|
  187. ;---|*| int BufferDataCount;            /* # of full DMA buffers parts     * /
  188. ;---|*| int DMARunning;                 /* DMA status (0=off,1=running)  * /
  189. ;---|*|
  190. ;---|*| "BufferDataCount" is the key handshaking variable between the
  191. ;---|*|     foreground and background processes. It indicates how many DMA
  192. ;---|*|     buffers divisions contain data.
  193. ;---|*|
  194. ;---|*|     For output, it holds a count of DMA divisions hold data. This
  195. ;---|*|     global variable is incremented each time a buffer is loaded by
  196. ;---|*|     the foreground task, and decremented when a buffer is emptied
  197. ;---|*|     by the background task.
  198. ;---|*|
  199. ;---|*|     For input, it holds the number of buffers with data in the DMA
  200. ;---|*|     buffer. It is incremented by the background process and
  201. ;---|*|     decremented by the foreground process.
  202. ;---|*|
  203. ;---|*|     For applications, calling QueryPCMStream returns this count,
  204. ;---|*|     plus the count of queued blocks.
  205. ;---|*|
  206. ;---|*| "DMARunning" is set to true or false depending upon the state
  207. ;---|*|     of the DMA channel. It is set TRUE when the DMA is running (either
  208. ;---|*|     playing or recording), and FALSE when the DMA is turned off.
  209. ;---|*|
  210. ;---|*| The following routines provide a high level interface to DMA driven
  211. ;---|*| PCM output:
  212. ;---|*|
  213. ;---|*| int  OpenPCMBuffering  ( int, int, int, int )
  214. ;---|*|
  215. ;---|*|         This routine is the first routine to be called. It sets
  216. ;---|*|         up the DMA channel, IRQ, and allocates memory for the buffers.
  217. ;---|*|
  218. ;---|*| int  PCMState ( int, int, int, int )
  219. ;---|*|
  220. ;---|*|         This routine passes in the sample rate, stereo/mono flag,
  221. ;---|*|         the compression type (0 for 8 bit, 1 for for 4 bit),
  222. ;---|*|         and the PCM data sample size (8 or 16).
  223. ;---|*|
  224. ;---|*| int  PlayThisBlock    ( char far *, unsigned long, void (far*)() )
  225. ;---|*|
  226. ;---|*|         This routine plays the user's block of data. Internally,
  227. ;---|*|         the routine queues the block into a circular list of buffers,
  228. ;---|*|         then checks to see if the PCM engine is running. If not, then
  229. ;---|*|         the next user block is loaded into the DMA and the PCM engine
  230. ;---|*|         is started. Once the block has been loaded into the DMA buffer,
  231. ;---|*|         the empty buffer is handed back via the user's callback
  232. ;---|*|         routine. The return value indicates success or failure. The
  233. ;---|*|         callback routine passed in, is used to notify the user code
  234. ;---|*|         when the block is emptied/filled.
  235. ;---|*|
  236. ;---|*| int  QueryPCMStream ( )
  237. ;---|*|
  238. ;---|*|         This routine returns the number of blocks of prepared data.
  239. ;---|*|         For playback, the number returned is the count of blocks
  240. ;---|*|         queued PLUS blocks loaded into the DMA buffer, but not yet
  241. ;---|*|         played. For recording, this is the number of unfilled blocks
  242. ;---|*|         queued in the circular list of buffers.
  243. ;---|*|
  244. ;---|*| int  QueueThisBlock ( char far *, unsigned long, void (far*)() )
  245. ;---|*|
  246. ;---|*|         This routine only queues up the user's block in the internal
  247. ;---|*|         circular block list. This routine is used for both play and
  248. ;---|*|         record, typically to queue up a few blocks before calling the
  249. ;---|*|         appropriate routine (PlayThisBlock or RecordThisBlock) to
  250. ;---|*|         begin the process. The return value indicates success or
  251. ;---|*|         failure. The callback routine passed in, is used to notify
  252. ;---|*|         the user code when the block is emptied/filled.
  253. ;---|*|
  254. ;---|*| int  RecordThisBlock  ( char far *, unsigned long, void (far*)() )
  255. ;---|*|
  256. ;---|*|         This routine records PCM data into the user's block of data.
  257. ;---|*|         Internally, the routine queues the block into a circular list
  258. ;---|*|         of buffers, then checks to see if the PCM engine is running.
  259. ;---|*|         If not, then the engine is started. Once data has been loaded,    PCM engine
  260. ;---|*|         the user's block is passed back via the call back routine
  261. ;---|*|         specified in this call. The return value indicates success
  262. ;---|*|         or failure.
  263. ;---|*|
  264. ;---|*| void SyncCallBack ( void (far*)() )
  265. ;---|*|
  266. ;---|*|         This function provides the caller with syncronization
  267. ;---|*|         callbacks for things, such as timing video frames with
  268. ;---|*|         audio frames. The callbacks occure on each DMA interrupt.
  269. ;---|*|
  270. ;---|*| void StopDMAIO          ( )
  271. ;---|*|
  272. ;---|*|         This routine is used to prematurely terminate PCM I/O.
  273. ;---|*|
  274. ;---|*| void ClosePCMBuffering ( )
  275. ;---|*|
  276. ;---|*|         This routine is used to close down the whole PCM I/O system.
  277. ;---|*|         This call MUST be made before the caller's program terminates.
  278. ;---|*|
  279. ;    \*/
  280.  
  281.  
  282. ;    /*\
  283. ;---|*|----====< Global Data >====----
  284. ;    \*/
  285.  
  286. #define QUEUESIZE    32                        /* 32 entries                    */
  287. #define QUEUEMASK    0x1F                    /* mask to circulate the count    */
  288.  
  289. #define NODIRECTION     0                    /* defines for DirectionFlag    */
  290. #define DMAINPUT        1
  291. #define DMAOUTPUT        2
  292.  
  293.     /* shared global variables within the high level code                    */
  294.  
  295.         unsigned int MaxBuffCount = 0;        /* # of DMA buffer divisions    */
  296.         unsigned int BufferSize = 0;        /* size of each buffer division */
  297.  
  298.     /* buffer linked list header structures                                 */
  299.  
  300.         typedef struct _buffptr {
  301.             int status;                     /* 0=empty, 1=full                */
  302.             int count;                        /* # of bytes in the buffer     */
  303.             int size;                        /* total size of allocated buff */
  304.             char huge *buffer;                /* pointer to buffer data        */
  305.             struct _buffptr far *nextptr;    /* pointer to next buffer hdr    */
  306.  
  307.         } BuffData, far *BuffPtr;
  308.  
  309.         BuffPtr HeadOfBuffers = 0;            /* global variable head pointer */
  310.  
  311.         int BufferDataCount   = 0;          /* # of full buffers (0=done)   */
  312.         int DMARunning          = 0;            /* DMA status (0=off,1=running) */
  313.         char huge *DMABuffPtr = 0;            /* 128k+1 DMA buffer pointer    */
  314.         char far  *StartOfDMABuffer = 0;    /* start of DMA buffer pointer    */
  315.         int ProcessedBlockCount = 0;        /* # of I/O blocks processed    */
  316.         unsigned long _file_data_length = 0;/* size of data output            */
  317.         char __pcmdatasize      = 8;            /* default to 8 bit pcm         */
  318.  
  319.         FILE *__fptr = 0;                   /* file pointer for disk I/O    */
  320.         long __fptrpos = 0;                 /* file pointer position        */
  321.         BuffPtr __NextPtr = 0;                /* next buffer pointer            */
  322.         int __DirectionFlag = 0;            /* current I/O direction        */
  323.         char far * __singleblockpointer = 0;/* single shot users buffer     */
  324.  
  325.     /* local data for this body of code, but needs to be public             */
  326.  
  327.         int VoiceActivatedSavedCount = 0;    /* # of I/O blocks saved        */
  328.  
  329.         int  __queuein      = 0;
  330.         int  __queueincnt = 0;
  331.         int  __queueout   = 0;
  332.         long __queuedata  = 0;
  333.  
  334.         char far *__queuebuff[QUEUESIZE] =    // number of queued blocks
  335.             { 0 };
  336.         long __queuelen[QUEUESIZE] =        // queued block lengths
  337.             { 0 };
  338.         void (far * __queuecb[QUEUESIZE])()=// queue of callback routines
  339.             { 0 };
  340.         void (far *__synccallback)() = 0;    // callback to user code
  341.  
  342.     /* additional prototypes                                                */
  343.  
  344.         void far  * _rfmemcpy          ( void far *, void far *, unsigned int );
  345.         void huge * _rfhmemcpy          ( void huge *,void huge *,unsigned int );
  346.         void far  * _memmalloc          ( long );
  347.         void        _memmfree          ( void far * );
  348.         int         _dofread          ( char far *, int, int );
  349.         int         _dofwrite          ( char far *, int, int );
  350.  
  351.  
  352. ;    /*\
  353. ;---|*|-----------------====================================-----------------
  354. ;---|*|-----------------====< Start of Executable Code >====-----------------
  355. ;---|*|-----------------====================================-----------------
  356. ;    \*/
  357.  
  358. ;    /*\
  359. ;---|*|----====< ClosePCMBuffering >====----
  360. ;---|*|
  361. ;---|*| Removes the PCM system & deallocates the buffer memory. There is
  362. ;---|*| no return value.
  363. ;---|*|
  364. ;    \*/
  365. void ClosePCMBuffering()
  366. {
  367. BuffPtr p,op;
  368.  
  369. //__debugdw (0x01);
  370.  
  371.     /* we will kill the DMA low level processing                        */
  372.  
  373.         StopDMAIO();
  374.         _unloadirqvector();
  375.  
  376.     /* Free up the linked list of buffers                                */
  377.  
  378.         if ((p = HeadOfBuffers) != 0) {
  379.  
  380.             do {
  381.                 op    = p;                /* save the old ptr             */
  382.                 p    = p->nextptr;        /* point to the next buffer     */
  383.                 _memfree (op);            /* free up the old header        */
  384.  
  385.             } while ( (p != HeadOfBuffers) && p );
  386.        }
  387.  
  388.     /* free up the DMA buffer                                            */
  389.  
  390.         if (DMABuffPtr)
  391.             _memfree (DMABuffPtr);
  392.  
  393.     /* null it all out...                                                */
  394.  
  395.         DMABuffPtr         = 0;
  396.         HeadOfBuffers     = 0;
  397.         StartOfDMABuffer = 0;
  398.         BufferDataCount  = BufferSize = DMARunning = 0;
  399.  
  400. }
  401.  
  402.  
  403. ;    /*\
  404. ;---|*|----====< ContinueThisBlockInput >====----
  405. ;---|*|
  406. ;---|*| This routine extracts a DMA buffer into one or
  407. ;---|*| more target user buffers.
  408. ;---|*|
  409. ;---|*| Returns:
  410. ;---|*|    Nonzero for running & processing, else 0 for dead.
  411. ;---|*|
  412. ;    \*/
  413. int  ContinueThisBlockInput()
  414. {
  415. unsigned int n, // working integer
  416.     loop,        // loop flag to keep loading blocks
  417.     bcount;     // increments the BufferDataCount
  418. unsigned int result = 0; // holds the final count
  419.  
  420. static unsigned int TargetSize;   // remaining size of the target dma buffer
  421. static char far *dmaptr; // pointer to this DMA block
  422.  
  423.     // if the DMA is dead, give it a jump start. Bad thing, it flushes all...
  424.  
  425.         if (DMARunning == 0) {
  426.  
  427.             // blow off anything that is saved locally
  428.  
  429.                 dmaptr = 0;
  430.                 TargetSize = 0;
  431.  
  432.             // reset and restart the low level stuff...
  433.  
  434.                 _resetbuffers();
  435.                 StartTheDMAInput(ContinueThisBlockInput);
  436.  
  437.             // we have no more data, just return now
  438.  
  439.                 return(DMARunning);
  440.         }
  441.  
  442.     // if the current remaining length is null, prime for the next block
  443.  
  444.         if (_file_data_length == 0) {
  445.  
  446.             // bomb out if no data buffers queued up
  447.  
  448.             if (__queueincnt == 0)
  449.                 return(1);
  450.  
  451.             // get the next block from the queue
  452.  
  453.             _file_data_length     = __queuelen [__queueout];
  454.             __singleblockpointer = __queuebuff[__queueout];
  455.         }
  456.  
  457.     // loop here to stuff as many blocks as possible into the DMA buffer
  458.  
  459.     nextblock:
  460.  
  461.     // move up to one buffer division worth of data
  462.  
  463.         if (!TargetSize) {
  464.  
  465.             dmaptr = __NextPtr->buffer;
  466.             TargetSize = BufferSize;
  467.         }
  468.  
  469.         loop = TRUE;
  470.         bcount = 1;
  471.  
  472.     // move as many blocks as possible into the DMA buffer
  473.  
  474.         while (loop) {
  475.  
  476.             // Get the block length, up to the division size
  477.  
  478.                 if (_file_data_length <= TargetSize) {
  479.  
  480.                     n = _file_data_length;    // full target size
  481.                     _file_data_length = 0;
  482.  
  483.                 }
  484.                 else                        // partial target size
  485.  
  486.                     _file_data_length -= (n = TargetSize);
  487.  
  488.             // copy the data to the buffer, and advance the buffer that far
  489.  
  490.                 if (n) {
  491.  
  492.                     // move the recorded data into the buffer
  493.  
  494.                     __singleblockpointer
  495.                         = _rfmemcpy(__singleblockpointer,dmaptr,n);
  496.                     dmaptr += n;                // move the dma pointer
  497.                     result += n;                // more for the return value
  498.  
  499.                     __queuedata -= (n &0xffff); // less queued up
  500.                     BufferDataCount -= bcount;    // decrement buffer count once
  501.                     bcount = 0;
  502.  
  503.                 }
  504.  
  505.             // if the length is zero, this buffer is done, let the caller know
  506.  
  507.                 if (!_file_data_length) {
  508.  
  509.                     // let the app. know we are done with this buffer
  510.  
  511.                         if (__queuecb[__queueout])
  512.                             (*__queuecb[__queueout])(__queuebuff[__queueout],__queuelen[__queueout]);
  513.  
  514.                         __queueincnt--;
  515.                         __queueout = ++__queueout & QUEUEMASK;
  516.  
  517.                     // Now, try to get the next available block out of the list
  518.  
  519.                     if (__queuein != __queueout) {
  520.  
  521.                         _file_data_length     = __queuelen[__queueout];
  522.                         __singleblockpointer = __queuebuff[__queueout];
  523.                     }
  524.                     else
  525.                         loop = FALSE;
  526.                 }
  527.  
  528.             // we are now done with this much of the buffer, stop if zero
  529.  
  530.                 if (!(TargetSize -= n))
  531.                     loop = FALSE;
  532.  
  533.         }
  534.  
  535.     // advance the list to the next DMA buffer and count one more...
  536.  
  537.         __NextPtr = __NextPtr->nextptr;
  538.  
  539.     // if we can do more, then DO IT!!!
  540.  
  541.         if (BufferDataCount > 0) {                // if there is data in the DMA...
  542.             if (__queueincnt)                    // if we have buffers...
  543.                     goto nextblock;             // then go load it...
  544.         }
  545.  
  546.     // return the number of bytes loaded
  547.  
  548.         return(result);
  549.  
  550. }
  551.  
  552.  
  553. ;    /*\
  554. ;---|*|----====< ContinueThisBlockOutput >====----
  555. ;---|*|
  556. ;---|*| This routine checks to see if another DMA buffer can be loaded.
  557. ;---|*| If so, it will load the user's block data into an empty buffer.
  558. ;---|*| A return value of ~0 indicates the buffer has been loaded.
  559. ;---|*|
  560. ;---|*| The foreground routine will call this when DMARunning == 0. The
  561. ;---|*| background routine will call this at every interrupt to keep the
  562. ;---|*| buffers loaded
  563. ;---|*|
  564. ;    \*/
  565. int ContinueThisBlockOutput()
  566. {
  567. unsigned int n,          // working integer
  568.     TargetSize, // size of the target dma buffer
  569.     loop,        // loop flag to keep loading blocks
  570.     bcount;     // increments the BufferDataCount
  571. unsigned int result = 0; // holds the final count
  572. char far *s;
  573.  
  574. //__debugdw (0x2C);
  575.  
  576.     // If no more data to load in the buffer, flush the next DMA & return
  577.  
  578.         if (__queueincnt == 0) {
  579. //__debugdw (0x20);
  580.             FlushBuffer (__NextPtr->buffer,BufferSize);
  581.             __NextPtr = __NextPtr->nextptr;
  582.             return(0);
  583.         }
  584.  
  585.     // if there is little data, but a lot in the DMA, just return
  586.  
  587.         if ((__queuedata < BufferSize) && (BufferDataCount > 2)) {
  588. //__debugdw (0x21);
  589.             return(0);
  590.         }
  591.  
  592.     // if the DMA has been turned off, re-sync the buffers
  593.  
  594.         if (DMARunning == 0) {
  595. //__debugdw (0x29);
  596.             _resetbuffers();
  597.         }
  598.  
  599.     // if the current remaining length is null, prime for the next block
  600.  
  601.         if (_file_data_length == 0) {
  602. //__debugdw (0x2A,__queueout);
  603.             _file_data_length     = __queuelen [__queueout];
  604.             __singleblockpointer = __queuebuff[__queueout];
  605.         }
  606.  
  607.     // loop here to stuff as many blocks as possible into the DMA buffer
  608.  
  609.     nextblock:
  610.  
  611.     // move up to one buffer division worth of data
  612.  
  613.         TargetSize = BufferSize;
  614.         s = __NextPtr->buffer;
  615.         loop = TRUE;
  616.         bcount = 1;
  617.  
  618.     // move as many blocks as possible into the DMA buffer
  619.  
  620.         while (loop) {
  621.  
  622. //__debugdw (0x22);
  623.  
  624.             // Get the block length, up to the division size
  625.  
  626.                 if (_file_data_length <= TargetSize) {
  627.  
  628.                     n = _file_data_length;    // full target size
  629.                     _file_data_length = 0;
  630.  
  631.                 }
  632.                 else                        // partial target size
  633.  
  634.                     _file_data_length -= (n = TargetSize);
  635.  
  636. //__debugdw (0x23,n);
  637.  
  638.             // copy the data to the buffer, and advance the buffer that far
  639.  
  640.                 if (n) {
  641.  
  642.                     s = _rfmemcpy(s, __singleblockpointer, n );
  643.  
  644.                     result += n;                // more for the return value
  645.                     __singleblockpointer += n;    // advance the pointer
  646.                     __queuedata -= (n &0xffff); // less queued up
  647.  
  648.                     BufferDataCount += bcount;  // increment buffer count once
  649.                     bcount = 0;
  650.  
  651.                 }
  652.                 else
  653.                     s = __NextPtr->buffer;        // no data, but do point here
  654.  
  655.             // if the length is zero, this buffer is done, let the caller know
  656.  
  657.                 if (!_file_data_length) {
  658.  
  659. //__debugdw (0x24);
  660.  
  661.                     // if this old block was valid, send a DONE msg.
  662.  
  663.                     if (__queueincnt) {
  664.  
  665.                         // let the app. know we are done with this buffer
  666.  
  667.                         if (__queuecb[__queueout])
  668.                             (*__queuecb[__queueout])(__queuebuff[__queueout],FALSE);
  669.  
  670.                         __queueincnt--;
  671.                         __queueout = ++__queueout & QUEUEMASK;
  672.                     }
  673.  
  674.                     // Now, try to get the next available block out of the list
  675.  
  676.                     if (__queuein == __queueout) {
  677.  
  678. //__debugdw (0x25);
  679.  
  680.                         FlushBuffer (s,TargetSize-n);
  681.                         loop = FALSE;
  682.  
  683.                     }
  684.                     else {
  685.  
  686. //__debugdw (0x26);
  687.  
  688.                         _file_data_length     = __queuelen[__queueout];
  689.                         __singleblockpointer = __queuebuff[__queueout];
  690.  
  691.                     }
  692.                 }
  693.  
  694.             // we are now done with this much of the buffer, stop if zero
  695.  
  696.                 if (!(TargetSize -= n))
  697.                     loop = FALSE;
  698.         }
  699.  
  700.     // advance the list to the next DMA buffer and count one more...
  701.  
  702.         __NextPtr = __NextPtr->nextptr;
  703.  
  704.     // if we can do more, then DO IT!!!
  705.  
  706.         if (BufferDataCount < MaxBuffCount) {    // if there is room in the DMA
  707.             if (__queueincnt) {                 // if we have pcm data
  708.                 if (__queuedata >= BufferSize){ // and its GE a buffer division,
  709.  
  710. //__debugdw (0x27);
  711.                     goto nextblock;             // then go load it...
  712.                 }
  713.             }
  714.         }
  715.  
  716.         if (DMARunning == 0) {
  717.  
  718. //__debugdw (0x28);
  719.  
  720.             StartTheDMAOutput(ContinueThisBlockOutput);
  721.         }
  722.  
  723.     // return the number of bytes loaded
  724.  
  725. //__debugdw (0x2D);
  726.  
  727.         return(result);
  728.  
  729. }
  730.  
  731.  
  732. ;   /*\
  733. ;---|*|----====< OpenPCMBuffering >====----
  734. ;---|*|
  735. ;---|*|  This routine is the first-call routine. It initializes the buffers
  736. ;---|*|  needed for the PCM play/record system. A return value of non-zero
  737. ;---|*|  indicates a failure to initialize the system.
  738. ;---|*|
  739. ;---|*|  Entry Conditions:
  740. ;---|*|
  741. ;---|*|         dma -- New DMA #. (1-3, or -1 for no changes)
  742. ;---|*|         irq -- New IRQ #. (3,5,6,7, or -1 for no changes)
  743. ;---|*|
  744. ;---|*|  Exit Conditions:
  745. ;---|*|
  746. ;---|*|         non-zero return indicates an error
  747. ;---|*|
  748. ;    \*/
  749. int OpenPCMBuffering(dma,irq,dmasize,divisions)
  750.    int dma;         /* DMA channel # (-1 for no changes)    */
  751.    int irq;         /* IRQ channel # (-1 for no changes)    */
  752.    int dmasize;     /* requested DMA size (4/8/16/32/64)    */
  753.    int divisions;    /* # of divisions in the DMA buffer     */
  754. {
  755. BuffPtr op,p;
  756. long l;
  757. int n;
  758. char far *db;
  759.  
  760. //__debugdw (0x00);
  761.  
  762.     /* setup the globa variables & a local buffer                        */
  763.  
  764.         MaxBuffCount = divisions;
  765.         BufferSize     = LONG(dmasize/divisions) * 1024L;
  766.  
  767.     /* Setup the lowlevel routines                                        */
  768.  
  769.         InitMVSound();
  770.  
  771.     /* flush any background task setup                                  */
  772.  
  773.         BackgroundInit( BufferSize, MaxBuffCount );
  774.  
  775.     /* Allocate twice the size for background DMA buffer                */
  776.  
  777.         l = LONG(dmasize) * 1024 * 2;
  778.  
  779.         if ((DMABuffPtr = (char huge *)_memmalloc(l)) == 0)
  780.             return(PCMIOERR_NOMEM);
  781.  
  782.         if ((db=StartOfDMABuffer=FindDMABuffer(DMABuffPtr,dmasize)) == 0)
  783.             return (PCMIOERR_OPENPCM);
  784.  
  785.     /* if the low level code doesn't like it, bomb out                  */
  786.  
  787.         if (!DMABuffer ( StartOfDMABuffer, dmasize, MaxBuffCount ))
  788.             return(PCMIOERR_OPENPCM);
  789.  
  790.     /* Attempt to allocate each foreground buffer                        */
  791.  
  792.         op = 0;
  793.         for (n=0;n<divisions;n++) {
  794.  
  795.             /* allocate the linked list header for each buffer            */
  796.  
  797.                 if ((p = (BuffPtr)_memmalloc (sizeof(BuffData))) == 0)
  798.                     return(PCMIOERR_NOMEM);
  799.  
  800.             /* reset the pointer in case of other failures during init    */
  801.  
  802.                 p->nextptr = 0;
  803.  
  804.             /* if first block, save as the head of the list             */
  805.  
  806.                 if (!HeadOfBuffers)
  807.                     HeadOfBuffers = p;
  808.  
  809.             /* if we have already allocated a block, setup the fwd ptr    */
  810.  
  811.                 if (op)
  812.                     op->nextptr = p;
  813.  
  814.                 p->buffer = db;
  815.                 p->size   = BufferSize;
  816.                 db          += BufferSize;
  817.  
  818.             /* save as the old pointer for linking purposes             */
  819.  
  820.                 op = p;
  821.         }
  822.  
  823.     /* link the last buffer back to the first                            */
  824.  
  825.         p->nextptr = HeadOfBuffers;
  826.  
  827.     /* Possibly select new DMA & IRQ channels                            */
  828.  
  829.         if (dma != -1)
  830.             if (SelectDMA(dma))
  831.                 return(PCMIOERR_BADDMA);
  832.  
  833.         if (irq != -1)
  834.             if (SelectIRQ(irq))
  835.                 return(PCMIOERR_BADIRQ);
  836.  
  837.     /* well, it looks good so far, flush any variables                    */
  838.  
  839.         BufferDataCount   = ProcessedBlockCount =
  840.         _file_data_length = __queuedata         =
  841.         VoiceActivatedSavedCount = __queueincnt =
  842.         __queuein  = __queueout = 0;
  843.  
  844.     /* and return good!                                                 */
  845.  
  846.         return (0);
  847. }
  848.  
  849.  
  850. ;    /*\
  851. ;---|*|----====< PCMState >====----
  852. ;---|*|
  853. ;---|*| This routine passes in the sample rate, stereo/mono flag, and any
  854. ;---|*| other miscellaneous data (to be determined later...)
  855. ;---|*|
  856. ;---|*| Exit Conditions:
  857. ;---|*|    Non-zero means the sample rate was in error.
  858. ;---|*|    Zero means the sample rate was okay error.
  859. ;---|*|
  860. ;    \*/
  861. int PCMState(sr,sm,cp,sz)
  862.     long sr;    /* sample rate    */
  863.     int  sm;    /* stereo/mono    */
  864.     int  cp;    /* compression    */
  865.     int  sz;    /* size(8/16)    */
  866. {
  867.  
  868. //__debugdw (0x02);
  869.  
  870.     /* just pass them on...                                             */
  871.  
  872.         __pcmdatasize = sz;                     /* pcm data size        */
  873.  
  874.         return (PCMInfo(sr,sm,cp,sz) ? PCMIOERR_SAMPLERATE : 0 );
  875.  
  876. }
  877.  
  878.  
  879. ;   /*\
  880. ;---|*|----====< QueryPCMStream >====----
  881. ;---|*|
  882. ;---|*| Returns the number of blocks playing in the DMA buffer and
  883. ;---|*| the number of blocks queued up.
  884. ;---|*|
  885. ;---|*| Exit Conditions:
  886. ;---|*|    0 - x is the number of blocks waiting to be played
  887. ;---|*|    -1 DMA is stopped, no more data
  888. ;---|*|
  889. ;    \*/
  890. int QueryPCMStream()
  891. {
  892.  
  893. //__debugdw (0x15,__queueincnt+BufferDataCount);
  894.  
  895.     // done if no DMA activity
  896.  
  897.         if (!DMARunning)
  898.             return(-1);
  899.  
  900.     // return the count of active blocks
  901.  
  902.         if (__DirectionFlag == DMAINPUT)
  903.             return (__queueincnt);
  904.  
  905.         else
  906.             return (__queueincnt+BufferDataCount);
  907.  
  908. }
  909.  
  910.  
  911. ;    /*\
  912. ;---|*|----====< StopDMAIO >====----
  913. ;---|*|
  914. ;---|*| This routine forceably kills the PCM I/O. All buffers will be
  915. ;---|*| reset, the current position of the input file is not altered. There
  916. ;---|*| is no return value.
  917. ;---|*|
  918. ;    \*/
  919. void StopDMAIO()
  920. {
  921.  
  922. //__debugdw (0x03);
  923.  
  924.     /* if this code has not already been setup, exit now                */
  925.  
  926.         if (!HeadOfBuffers)
  927.             return;
  928.  
  929.     /* stop the hardware...                                             */
  930.  
  931.         StopPCM( );
  932.  
  933.     /* if there are queued up blocks, send them back to the caller        */
  934.  
  935.         if (__queueincnt) {
  936.  
  937.             while (__queueincnt) {
  938.  
  939.                     if (__queuecb[__queueout])
  940.                         (*__queuecb[__queueout]) (__queuebuff[__queueout],0);
  941.  
  942.                     __queueout = ++__queueout & QUEUEMASK;
  943.                     __queueincnt--;
  944.             }
  945.  
  946.         }
  947.  
  948.     /* flush all the counters, etc.                                     */
  949.  
  950.         __queuein    = __queueincnt = __queueout = DMARunning = 0;
  951.         __queuedata = _file_data_length = 0;
  952.  
  953.     /* flush any prior background task setup                            */
  954.  
  955.     ////if (__DirectionFlag == DMAOUTPUT) {
  956.     ////    if (__fptr) {
  957.     ////        rewind    (__fptr);
  958.     ////        __fptrpos = 0;
  959.     ////    }
  960.     ////}
  961.  
  962.     /* reset the linked list of buffers                                 */
  963.  
  964.         _resetbuffers();
  965.  
  966.     /* setup our internal direction flag                                */
  967.  
  968.         __DirectionFlag = NODIRECTION;
  969.  
  970. }
  971.  
  972.  
  973. ;    /*\
  974. ;---|*|----====< RecordThisBlock >====----
  975. ;---|*|
  976. ;---|*| This routine offers the caller a simplified recording of one
  977. ;---|*| variable length block of data. The call just needs to call
  978. ;---|*| OpenPCMBuffering, then RecordThisBlock. The caller just has
  979. ;---|*| to poll DMARunning to see if the block has completed. Calling
  980. ;---|*| StopDMAIO will stop the process, if need be.
  981. ;---|*|
  982. ;    \*/
  983. int RecordThisBlock(p,len,cb)
  984.     char far *p;
  985.     unsigned long len;
  986.     void (far *cb)();
  987. {
  988. int n;
  989.  
  990.     // if the pointer is valid, then queue it up
  991.  
  992.         if (p) {
  993.  
  994.             // return if already full
  995.  
  996.             if (__queuein == QUEUESIZE)
  997.                 return(2);
  998.  
  999.             // extract the 1st entry from the queue
  1000.  
  1001.             __queuebuff[__queuein] = p;
  1002.             __queuedata += (__queuelen [__queuein] = len);
  1003.             __queuecb  [__queuein] = cb;
  1004.             __queuein  = ++__queuein & QUEUEMASK;
  1005.             __queueincnt++;
  1006.  
  1007.         }
  1008.  
  1009.     // if the blocks are not recording , the let'er rip...
  1010.  
  1011.         if ((DMARunning == 0) && __queueincnt ) {
  1012.  
  1013.             // setup the direction flag
  1014.  
  1015.                 __DirectionFlag = DMAINPUT;
  1016.  
  1017.             /* reset the DMA block pointers                             */
  1018.  
  1019.                 _resetbuffers();
  1020.  
  1021.             /* return good or bad if the PCM engine is running            */
  1022.  
  1023.                 return (ContinueThisBlockInput() ? 1 : 0 );
  1024.  
  1025.         }
  1026.  
  1027.     // assume the block is now recording
  1028.  
  1029.         return(0);
  1030.  
  1031. }
  1032.  
  1033.  
  1034. ;    /*\
  1035. ;---|*|----====< PlayThisBlock >====----
  1036. ;---|*|
  1037. ;---|*| This routine offers the caller a simplified playback of one
  1038. ;---|*| variable length block of data. The call just needs to call
  1039. ;---|*| OpenPCMBuffering, then PlayThisBlock. The caller just has
  1040. ;---|*| to poll DMARunning to see if the block has completed. Calling
  1041. ;---|*| StopDMAIO will stop the process, if need be.
  1042. ;---|*|
  1043. ;---|*| Also see QueueThisBlock.
  1044. ;---|*|
  1045. ;---|*| Entry:
  1046. ;---|*|    p   is the far pointer to a block of data (64k max size). If
  1047. ;---|*|        the pointer is null, the block will not be queued.
  1048. ;---|*|    len is the length of the block in bytes (one based count).
  1049. ;---|*|    cb  is the callback routine to call when the block is empty.
  1050. ;---|*|
  1051. ;---|*| Returns:
  1052. ;---|*|    0 - block is queued and playing
  1053. ;---|*|    1 - Block failed to start
  1054. ;---|*|    2 - queue is full, try later
  1055. ;---|*|
  1056. ;    \*/
  1057. int PlayThisBlock(p,len,cb)
  1058.     char far *p;
  1059.     unsigned long len;
  1060.     void (far *cb)();
  1061. {
  1062. int n;
  1063.  
  1064.     // if the pointer is valid, then queue it up
  1065.  
  1066.         if (p) {
  1067.  
  1068.             // return if already full
  1069.  
  1070.             if (__queuein == QUEUESIZE)
  1071.                 return(2);
  1072.  
  1073.             // extract the 1st entry from the queue
  1074.  
  1075.             __queuebuff[__queuein] = p;
  1076.             __queuedata += (__queuelen [__queuein] = len);
  1077.             __queuecb  [__queuein] = cb;
  1078.             __queuein  = ++__queuein & QUEUEMASK;
  1079.             __queueincnt++;
  1080.  
  1081. //            if (!len)
  1082. //              __debugdw (0x32,__queueincnt);
  1083.  
  1084. //__debugdw (0x10,__queueincnt);
  1085. //__debugdw (0x14,BufferDataCount);
  1086.  
  1087.         }
  1088.  
  1089.     // if the blocks are not playing, the let'er rip...
  1090.  
  1091.         if ((DMARunning == 0) && __queueincnt ) {
  1092.  
  1093. //__debugdw (0x11);
  1094.  
  1095.             // setup to transfer this block
  1096.  
  1097.                 __DirectionFlag = DMAOUTPUT;
  1098.  
  1099.             // start the process by loading the DMA buffers
  1100.  
  1101.                 return (ContinueThisBlockOutput() ? 0 : 1 );
  1102.  
  1103.         }
  1104.  
  1105.     // Indicate this has been queued up
  1106.  
  1107.         return(0);
  1108. }
  1109.  
  1110.  
  1111. ;    /*\
  1112. ;---|*|----====< QueueThisBlock >====----
  1113. ;---|*|
  1114. ;---|*| This routine will queue up one block, but not start the PCM
  1115. ;---|*| transfers. Once X number of blocks are queued up, then the caller
  1116. ;---|*| can call PlayThisBlock/RecordThisBlock to start the process.
  1117. ;---|*|
  1118. ;---|*| Also see PlayThisBlock/RecordThisBlock.
  1119. ;---|*|
  1120. ;---|*| Entry:
  1121. ;---|*|    p   is the far pointer to a block of data (64k max size).
  1122. ;---|*|        All pointers are assumed to be valid.
  1123. ;---|*|    len is the length of the block in bytes (one based count).
  1124. ;---|*|    cb  is the callback routine to call when the block is empty.
  1125. ;---|*|
  1126. ;---|*| Returns:
  1127. ;---|*|    0 - block is queued and playing
  1128. ;---|*|    2 - queue is full, try later
  1129. ;---|*|
  1130. ;    \*/
  1131. int QueueThisBlock(p,len,cb)
  1132.     char far *p;
  1133.     unsigned long len;
  1134.     void (far *cb)();
  1135. {
  1136. int n;
  1137.  
  1138.     // return if already full
  1139.  
  1140.         if (__queuein == QUEUESIZE) {
  1141.  
  1142. //__debugdw (0x12,__queueincnt);
  1143.  
  1144.             return(2);
  1145.  
  1146.         }
  1147.  
  1148.     // if idle, setup our internal direction flag, and the desired length
  1149.  
  1150.         __queuebuff[__queuein] = p;
  1151.         __queuedata += (__queuelen [__queuein] = len);
  1152.         __queuecb  [__queuein] = cb;
  1153.         __queuein  = ++__queuein & QUEUEMASK;
  1154.         __queueincnt++;
  1155.  
  1156. //        if ((len & (unsigned long)p) == 0)
  1157. //            __debugdw (0x32,__queueincnt);
  1158.  
  1159. //__debugdw (0x12,__queueincnt);
  1160.  
  1161.     // return all queued up
  1162.  
  1163.         return(0);
  1164.  
  1165. }
  1166.  
  1167.  
  1168. ;    /*\
  1169. ;---|*|----====< SyncCallBack >====----
  1170. ;---|*|
  1171. ;---|*| This routine will setup a callback to the caller's routine
  1172. ;---|*| at the end of every DMA block interrupt
  1173. ;---|*|
  1174. ;---|*| Returns:
  1175. ;---|*|    nothing.
  1176. ;---|*|
  1177. ;    \*/
  1178. void SyncCallBack(cb)
  1179.     void (far *cb)();
  1180. {
  1181. int n;
  1182.  
  1183.     // just save for a later call...
  1184.  
  1185.         __synccallback = cb;
  1186.  
  1187. }
  1188.  
  1189. ;    /*\
  1190. ;---|*|----====< _resetbuffers >====----
  1191. ;---|*|
  1192. ;---|*| This routine flushes the contents of the top level buffers
  1193. ;---|*|
  1194. ;    \*/
  1195. _resetbuffers()
  1196. {
  1197.  
  1198. //__debugdw (0x2B);
  1199.  
  1200.     /* flush the count and status of every linked list block            */
  1201.  
  1202.         if ((__NextPtr = HeadOfBuffers) != 0) {
  1203.  
  1204.             /* if there are buffers, reset them all                     */
  1205.  
  1206.             do {
  1207.  
  1208.                 /* get the next buffer full & exit if done                */
  1209.  
  1210.                     __NextPtr->count = __NextPtr->status = 0;
  1211.  
  1212.                 /* break when we reach the top                            */
  1213.  
  1214.                     if ((__NextPtr = __NextPtr->nextptr) == 0)
  1215.                             break;
  1216.  
  1217.             } while (__NextPtr != HeadOfBuffers);
  1218.  
  1219.         }
  1220.  
  1221.     /* reset the global hand shake count.                                */
  1222.  
  1223.         BufferDataCount = 0;
  1224. }
  1225.  
  1226.  
  1227. ;   /*\
  1228. ;---|*|----====< __debugdw >====----
  1229. ;---|*|
  1230. ;---|*| Debuggin code commonly used for MV use. You can use it if you want...
  1231. ;---|*|
  1232. ;    \*/
  1233.  
  1234. int __debugdw (int idx,int val)
  1235. {
  1236. #if 0
  1237.  
  1238.     _asm {
  1239.  
  1240.         pushf
  1241.         cli
  1242.  
  1243.         push    di
  1244.         push    es
  1245.  
  1246.         sub     di,di
  1247.         mov     es,di
  1248.         les     di,es:[0x194]
  1249.  
  1250.         mov     ax,es
  1251.         or        ax,ax
  1252.         jz        dedw05
  1253.  
  1254.         cmp     di,16768
  1255.         jae     dedw05
  1256.  
  1257.         cld
  1258.         mov     ax,idx
  1259.         stosb
  1260.         mov     ax,val
  1261.         stosw
  1262.  
  1263.         sub     ax,ax
  1264.         mov     es,ax
  1265.         mov     es:[0x194],di
  1266.  
  1267.     } dedw05: _asm {
  1268.  
  1269.         pop     es
  1270.         pop     di
  1271.  
  1272.         popf
  1273.     }
  1274. #endif
  1275. }
  1276.  
  1277. ;   /*\
  1278. ;---|*| end of PCMIOC.C
  1279. ;    \*/
  1280.  
  1281.