;$Author:   DCODY  $
;$Date:   23 Sep 1992 10:57:04  $
;$Header:   X:/sccs/pcm/pcmioa.asv   1.4   23 Sep 1992 10:57:04   DCODY  $
;$Log:   X:/sccs/pcm/pcmioa.asv  $
;  
;     Rev 1.4   23 Sep 1992 10:57:04   DCODY
;  more work on playthisblock, continuethisblock...
;  
;     Rev 1.3   26 Aug 1992 10:58:26   DCODY
;  added Playthisblock and RecordthisBlock support routines
;  
;     Rev 1.2   12 Aug 1992 17:10:56   DCODY
;  major change to eliminate the foreground buffers and background
;  processing responsibilities.
;  
;     Rev 1.1   23 Jun 1992 17:11:34   DCODY
;  PAS2 update
;  
;     Rev 1.0   15 Jun 1992 09:44:36   BCRANE
;  Initial revision.
;$Logfile:   X:/sccs/pcm/pcmioa.asv  $
;$Modtimes$
;$Revision:   1.4  $
;$Workfile:   pcmioa.asm  $ 


	page	64,131
	Title	PCMIOA	--  Background Task for PCM I/O

;   /*\
;---|*|
;---|*|------------====< PCMIOA.ASM >====------------
;---|*|
;---|*| Copyright (c) 1991, Media Vision, Inc. All rights reserved
;---|*|
;   \*/

        .xlist
	include model.inc
	include masm.inc
        include common.inc
	include binary.inc
	.list

PCMCODEVERSION	equ	0003h		; version 00.03

;
;-----------------------================================-----------------------
;-----------------------====< Code/Data Generation >====-----------------------
;-----------------------================================-----------------------
;

BLOCKOUT	= 0		; builds block output code only
BLOCKIN 	= 0		; builds block input code only
COMMDATA	= 0		; builds both common code and data

ifdef  BUILDBO
  BLOCKOUT	= 1
endif

ifdef  BUILDBI
  BLOCKIN	= 1
endif

ifdef  BUILDCO
  COMMDATA	= 1
endif

;
;---------------------------========================---------------------------
;---------------------------====< DATA SECTION >====---------------------------
;---------------------------========================---------------------------
;
        .data
;
; Code Model Dependencies
;
if @datasize
  cptr	equ	<dword> 		; C pointer is FAR
else
  cptr	equ	<word>			; C pointer is NEAR
endif

NODIRECTION	equ	0
DMAINPUT	equ	1
DMAOUTPUT	equ	2

PCMFILEINPUT	equ	DMAINPUT
PCMFILEOUTPUT	equ	DMAOUTPUT
PCMBLOCKINPUT	equ	DMAINPUT+80h
PCMBLOCKOUTPUT	equ	DMAOUTPUT+80h

;
; Structure Definitions
;
buffptr struc

 BPstatus       dw      0               ; 0=empty, 1=full
 BPcount	dw	0		; # of bytes in the buffer
 BPsize 	dw	0		; total size of allocated buff
 BPbuffer	dd	0		; pointer to buffer data

  if @datasize
   BPnextptr	dd	0		; pointer to buffer data
  else
   BPnextptr	dw	0		; pointer to buffer data
  endif

buffptr ends
;
;--------------====< code generation dependent declarations >====------------
;

if	COMMDATA
;
; Local data declarations
;
	public	CountdownBuffers
CountdownBuffers	db	00	; countdown value
	public	NextDMAPtr
NextDMAPtr		dd	00	; our pointer to the DMA's buffer
	public	DMAUnitSize
DMAUnitSize		dw	00	; size of DMA buffer divisions

	public	__callersroutine
  if @codesize
__callersroutine        dd      00
  else
__callersroutine	dw	00
  endif

else
	extrn	__callersroutine :word
	extrn	__NextPtr	 :dword ; pointer to circular buffer list
;
; Local data declarations
;
	extrn	CountdownBuffers :byte	; countdown value
	extrn	NextDMAPtr	 :dword ; our pointer to the DMA's buffer
	extrn	DMAUnitSize	 :word	; size of DMA buffer divisions

endif
    externPTR	HeadOfBuffers		; pointer to buffer linked list
	extrn	BufferDataCount  :word	; # of full buffers (0=done)
	extrn	DMARunning	 :word	; DMA status (0=off,1=running)
	extrn	StartOfDMABuffer :dword ; always a far * to start of DMA buffer
	extrn __pcmdatasize	 :byte	; default to 8 bit pcm
	extrn	MaxBuffCount	 :word	; count of DMA blocks
	extrn	ProcessedBlockCount:word; # of full buffers (0=done)
	extrn	__synccallback	 :dword ; callback to application at int

;
;---------------------------========================---------------------------
;---------------------------====< CODE SECTION >====---------------------------
;---------------------------========================---------------------------
;
        .code

    externADDR	InitPCM 		; initialize the PCM low level code
    externADDR	StopPCM 		; Kill the PCM hardware
    externADDR	PCMPlay 		; start the PCM output
    externADDR	PCMRecord		; start the PCM input
    externADDR	UserFunc		; sets up the call-back routine

if (BLOCKOUT or BLOCKIN)
    externADDR	_resetdmaptrs		; reset the criticals
endif

if COMMDATA
;
;   /*\
;---|*|
;---|*|------------====< BackgroundInit( int, int ) >====------------
;---|*|
;---|*| Initialize our internal variables, etc...
;---|*|
;---|*| Entry Conditions:
;---|*|     wParm1 - Size of DMA buffer
;---|*|     wParm2 - # of DMA buffer divisions
;---|*|
;---|*| Exit Conditions:
;---|*|     No return value
;---|*|
;---|*| Functionality:
;---|*|
;---|*|     Clear interrupt mechanisms.
;---|*|     Disable DMA channel.
;---|*|     Set DMARunning to 0;
;---|*|
;   \*/

        public  BackgroundInit
BackgroundInit	proc
	push	bp
	mov	bp,sp
	push	es
;
; get the # of buffers in the linked list
;
	mov	ax,wParm1
	mov	[DMAUnitSize],ax	; max size of each DMA division
;
; flush any prior low-level setup
;
	call	InitPCM
	mov	DMARunning,0		; signal it dead
;
; all done, return home
;
	pop	es
	pop	bp
	ret

BackgroundInit	endp
endif   ; COMMDATA

if BLOCKOUT
;   /*\
;---|*|
;---|*|--------------====< FlushBuffer >====-----------------
;---|*|
;---|*| Flush the buffer by filling it with silence (value of 0x80)
;---|*|
;---|*| Entry Conditions:
;---|*|       dParm1 is the far pointer to the block
;---|*|       wParm2 is an unsigned integer which is the block length
;---|*|
;---|*| Exit Conditions:
;---|*|       DX:AX hold the ending pointer
;---|*|
;---|*| Prototype:
;---|*|
;---|*| char far * FlushBuffer (char far *, int)
;---|*|
;---|*|
;   \*/

        public  FlushBuffer
FlushBuffer	proc
	push	bp
	mov	bp,sp

	push	es
	push	di

	pushf
	cld

	les	di,dParm1		; grab 2 words off the stack
	mov	cx,wParm3		; wParm3 is used to skip the 2nd word

	cmp	[__pcmdatasize],9	; 8 bit sets the carry
	sbb	al,al			; al = ff if 8 bit PCM
	and	al,80h			; al = 80 if 8 bit, 00 if 16 bit

	jcxz	@F
	rep	stosb
    @@:

	mov	dx,es			; return the ending ptr
	mov	ax,di

        popf

	pop	di
	pop	es
	pop	bp
	ret

FlushBuffer	endp
endif	; BLOCKOUT


if BLOCKIN
;   /*\
;---|*|
;---|*|------------====< StartTheDMAInput() >====------------
;---|*|
;---|*| Calculate the H/W timer value (internal routine)
;---|*|
;---|*| Entry Conditions:
;---|*|       pointer to a routine
;---|*|
;---|*| Exit Conditions:
;---|*|       AX =  0 indicates DMA running
;---|*|       AX = -1 indicates a failure
;---|*|
;---|*| Functionality:
;---|*|
;---|*|     Reset input pointer to the DMA buffer.
;---|*|     Load next buffer of data into the DMA buffer.
;---|*|     Enable DMA & interrupts.
;---|*|     Set DMARunning to 1;
;---|*|
;   \*/

	public StartTheDMAInput
StartTheDMAInput        proc
	push	bp
	mov	bp,sp

        push    es
;
; Reset the DMA's internal buffer pointer & counter
;
	call	_resetdmaptrs		; reset the criticals
;
; Validate some critical data
;
	mov	ax,-1			; setup for bad return

	cmp	DMAUnitSize,0		; we must have a valid size of units
	jz	stdmaout_bad

	les	bx,[NextDMAPtr] 	; we must have a valid DMA buffer
	mov	cx,es
	or	bx,cx
        jz      stdmaout_bad
;
; setup the callers callback
;
      if @codesize
	mov	ax,wParm1+0
	mov	dx,wParm1+2
	mov	wptr [__callersroutine+0],ax
	mov	wptr [__callersroutine+2],dx
      else
        mov     ax,wParm1+0
	mov	[__callersroutine],ax
      endif
;
; setup our call-back routine
;
	lea	ax,InputBackgroundTask
	push	cs
	push	ax
	call	UserFunc
	add	sp,4
;
; The DMA is loaded, let'er rip!!!
;
	call	PCMRecord
	mov	DMARunning,1

	sub	ax,ax
;
stdmaout_bad:
	pop	es
	pop	bp
	ret

StartTheDMAInput	endp
endif	; BLOCKIN


if BLOCKOUT
;   /*\
;---|*|
;---|*|------------====< StartTheDMAOutput() >====------------
;---|*|
;---|*| Calculate the H/W timer value (internal routine)
;---|*|
;---|*| Entry Conditions:
;---|*|       None
;---|*|
;---|*| Exit Conditions:
;---|*|       AX =  0 indicates DMA running
;---|*|       AX = -1 indicates a failure
;---|*|
;---|*| Functionality:
;---|*|
;---|*|     Reset input pointer to the DMA buffer.
;---|*|     Load next buffer of data into the DMA buffer.
;---|*|     Enable DMA & interrupts.
;---|*|     Set DMARunning to 1;
;---|*|
;   \*/

	public	StartTheDMAOutput
StartTheDMAOutput       proc
        push    bp
        mov     bp,sp
	push	es
;
; Reset the DMA's internal buffer pointer & counter
;
        call    _resetdmaptrs           ; reset the criticals
;
; Validate some critical data
;
	mov	ax,-1			; setup for bad return

	cmp	DMAUnitSize,0		; we must have a valid size of units
	jz	stdmaout_bad

	les	bx,[NextDMAPtr] 	; we must have a valid DMA buffer
	mov	cx,es
	or	bx,cx
        jz      stdmaout_bad
;
; setup the callers callback
;
      if @codesize
	mov	ax,wParm1+0
	mov	dx,wParm1+2
	mov	wptr [__callersroutine+0],ax
	mov	wptr [__callersroutine+2],dx
      else
        mov     ax,wParm1+0
	mov	[__callersroutine],ax
      endif
;
; setup our call-back routine
;
	lea	ax,OutputBackgroundTask
	push	cs
	push	ax
	call	UserFunc
	add	sp,4
;
; The DMA is loaded, let'er rip!!!
;
	call	PCMPlay
	mov	DMARunning,1

	sub	ax,ax
;
stdmaout_bad:
	pop	es
        pop     bp
	ret

StartTheDMAOutput	endp
endif ;; BLOCKOUT

;
;--------------------------=============================-----------------------
;--------------------------====< Internal Routines >====-----------------------
;--------------------------=============================-----------------------
;

if BLOCKIN
;   /*\
;---|*|
;---|*|------------====< InputBackgroundTask() >====------------
;---|*|
;---|*| This is the interrupt code for processing DMA-buffer-has-data interrupts
;---|*|
;---|*| Entry Conditions:
;---|*|     None
;---|*|
;---|*| Exit Conditions:
;---|*|     None
;---|*|
;---|*| Interrupt handling:
;---|*|
;---|*|     if Next Buffer has data,
;---|*| 	Kill-the-DMA;
;---|*| 	set DMARunning to 0;
;---|*| 	exit interrupt.
;---|*|     else
;---|*|        Load DMA into Next buffer.
;---|*|
;---|*|     Increment BufferDataCount.
;---|*|     Increment ProcessBlockCount.
;---|*|
;   \*/

InputBackgroundTask     proc    far
	push	ax			; save the entire state
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	ds
	push	es
;
; initialize the segment registers to point to our local static segment
;
	mov	ax,@data		; load our data segment
	mov	ds,ax			; DS too in case of failure
;
; Save the next DMA pointer, increment our internal buffer-loaded count
;
        inc     ProcessedBlockCount     ; total block count the DMA has seen
        inc     BufferDataCount         ; tell the world, we have new data
	mov	ax,BufferDataCount
	cmp	ax,MaxBuffCount
	jb	inbkgrtsk_exit
;
; Yuck! We just ran out of buffer space, we must kill the DMA
;
        call    StopPCM                 ; kill the interrupts...
	mov	DMARunning,0
;
inbkgrtsk_exit:
  if @codesize
	cmp	wptr [__callersroutine+2],0  ; if there is no vector, just exit
	jz	@F
	call	dptr [__callersroutine]      ; call the user routine
      @@:
  else
	cmp	wptr [__callersroutine],0    ; if there is no vector, just exit
	jz	@F
	call	wptr [__callersroutine]      ; call the user routine
      @@:
  endif
;
; Call the application level it it wants a slice of time...
;
        cmp     wptr [__synccallback+2],0    ; if no App. vector, just exit
	jz	@F
	call	dptr [__synccallback]	     ; if no App. vector, just exit
    @@:
;
; finally, all done.
;
        pop     es
	pop	ds
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	retf

InputBackgroundTask    endp
endif	; BLOCKIN


if BLOCKOUT
;   /*\
;---|*|
;---|*|------------====< OutputBackgroundTask() >====------------
;---|*|
;---|*| This is the interrupt code for processing DMA buffer
;---|*| empty interrupts for the file output routines.
;---|*|
;---|*| Entry Conditions:
;---|*|     None
;---|*|
;---|*| Exit Conditions:
;---|*|     None
;---|*|
;---|*| Interrupt handling:
;---|*|
;---|*|     If BufferDataCount	   is zero,
;---|*| 	Kill-the-DMA;
;---|*| 	set DMARunning to 0;
;---|*|
;---|*|     Attempt to load as many blocks as possible into the DMA buffer.
;---|*|
;---|*|     If data is loaded and DMARunning = 0,
;---|*| 	restart the DMA;
;---|*| 	set DMARunning to -1;
;---|*|
;---|*|     Exit interrupt.
;---|*|
;   \*/

OutputBackgroundTask    proc    far
	push	ax			; save the entire state
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	ds
	push	es
;
; initialize the data segment to point to our local static segment
;
        mov     ax,@data                ; load our data segment
	mov	ds,ax
;
; We just finished a block. If done (0), stop the DMA, else go load another
;
	inc	ProcessedBlockCount	; total block count the DMA has seen
        sub     BufferDataCount,1       ; decrement the internal buffered data
	adc	BufferDataCount,0	; count down to zero
	jnz	bkgrtsk_05		; more data, go load it...
;
; Yuck! We just ran out of data, we must silence the DMA
;
        call    StopPCM                 ; kill the interrupts...
	mov	DMARunning,0
;
bkgrtsk_05:
  if @codesize
	cmp	wptr [__callersroutine+2],0  ; if there is no vector, just exit
	jz	@F
	call	dptr [__callersroutine]      ; call the user routine
      @@:
  else
	cmp	wptr [__callersroutine],0    ; if there is no vector, just exit
	jz	@F
        call    wptr [__callersroutine]      ; call the user routine
      @@:
  endif
;
; Call the application level it it wants a slice of time...
;
        cmp     wptr [__synccallback+2],0    ; if no App. vector, just exit
	jz	@F
	call	dptr [__synccallback]	     ; if no App. vector, just exit
    @@:
;
; finally, all done.
;
        pop     es
	pop	ds
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	retf

OutputBackgroundTask	endp
endif	; BLOCKOUT

if COMMDATA
;
;   /*\
;---|*|
;---|*|------------====< _resetdmaptrs >====----------------
;---|*|
;---|*| Reset the DMA starting point
;---|*|
;---|*| Entry Conditions:
;---|*|     DS points to the default data segment
;---|*|
;---|*| Exit Conditions:
;---|*|     None
;---|*|
;   \*/

_resetdmaptrs	proc
	push	es
;
; load the number of buffer divisions. this tells us when to wrap the DMA buff
;
	mov	al,byte ptr [MaxBuffCount]
	mov	[CountdownBuffers],al
;
; reset the DMA buffer offset
;
	les	ax,[StartOfDMABuffer]
	mov	wptr [NextDMAPtr+0],ax
	mov	wptr [NextDMAPtr+2],es

        pop     es
	ret

_resetdmaptrs   endp

endif	; COMMDATA

;   /*\
;---|*| end of PCMIOA.ASM
;   \*/

        end


