page 58,132
.286c
.MODEL LARGE

;**********************************************************************
;*                                                                    *
;*                             Equates                                *
;*                                                                    *
;**********************************************************************

tccount		equ	72	;reload count to produce 16572 Hz
countmax_18hz	equ	910	;ratio of new timer rate to old rate

tcaddrc		equ	43h	;timer/counter control register address
tcaddrd		equ	40h	;timer/counter data register zero

tcmode		equ	34h	;mode control byte for timer/counter

ppiaddr		equ	61h	;programmable peripheral interface address

;**********************************************************************
;*                                                                    *
;*                             Macros                                 *
;*                                                                    *
;**********************************************************************

ljz	macro	dest
	local	skip
	jnz	skip
	jmp	dest
skip:
	endm

;**********************************************************************
;*                                                                    *
;*                         Global variables                           *
;*                                                                    *
;**********************************************************************

.DATA
		even		;don't put these on an odd boundary

blockaddr	dd	0	;beginning of memory block
blocksize	dd	0	;size of memory block
blockend	dd	0	;last address of memory block + 1
currentaddr	dd	0	;pointer to current record position in memory
datacount	dd	0	;number of data bytes already recorded
fwrtaddr	dd	0	;address of next byte to be written to disk
fwrtcount	dd	0	;number of bytes already written to disk

goflag		dw	0	;indicates whether recording is in progress
fileflag	dw	0	;indicates whether data is written to a file
filehandle	dw	0	;handle of file to receive voice data
hookedflag	dw	0	;indicates whether the interrupt is hooked
countfor_18hz	dw	0	;counter to determine when to call BIOS
comaddr		dw	0	;the address of the COM port

comlist		dw	3FEh	;needed addresses of all COM ports
		dw	2FEh
		dw	3EEh
		dw	2EEh

shiftcount	db	0	;current bit position within byte

;**********************************************************************
;*                                                                    *
;*                               Code                                 *
;*                                                                    *
;**********************************************************************

.CODE

bios_timer_routine	dd	0	;we keep this in the code segment
					; for access through the CS register
					; since the other segment registers
					; will contain unknown values when
					; this is needed

	assume	ds:DGROUP

;**********************************************************************
;*             Speed up the timer tick and set "goflag"               *
;**********************************************************************

startvoice	proc	near

	pushf
	cli
	mov	al,tcmode
	out	tcaddrc,al
	mov	ax,tccount
	out	tcaddrd,al
	mov	al,ah
	out	tcaddrd,al
	mov	shiftcount,0
	mov	countfor_18hz,countmax_18hz
	mov	goflag,1
	popf
	ret

startvoice	endp

;**********************************************************************
;*         Slow the timer tick to normal and clear "goflag"           *
;**********************************************************************

stopvoice	proc	near

	pushf
	cli
	mov	al,tcmode
	out	tcaddrc,al
	mov	al,0
	out	tcaddrd,al
	out	tcaddrd,al
	mov	goflag,0
	popf
	ret

stopvoice	endp

;**********************************************************************
;*                   Interrupt service routine                        *
;**********************************************************************

timer_tick	proc	far

	push	ds
	push	bx
	push	es
	push	ax
	push	cx
	push	dx

	mov	ax,DGROUP
	mov	ds,ax

	cmp	goflag,0
	je	chain_exit

	les	bx,currentaddr
	mov	ah,es:[bx]
	mov	dx,comaddr
	in	al,dx
	rcl	al,4
	rcl	ah,1
	mov	es:[bx],ah

	inc	shiftcount
	and	shiftcount,07h
	jnz	exit_decide

	mov	ax,es
	inc	bx
	jnz	check_wrap
	add	ax,1000h
check_wrap:
	cmp	bx,word ptr blockend
	jne	save_cur_addr
	cmp	ax,word ptr blockend+2
	jne	save_cur_addr
	mov	bx,word ptr blockaddr
	mov	ax,word ptr blockaddr+2
save_cur_addr:
	mov	word ptr currentaddr,bx
	mov	word ptr currentaddr+2,ax
	add	word ptr datacount,1
	adc	word ptr datacount+2,0

exit_decide:
	dec	countfor_18hz
	jnz	nochain_exit
	mov	countfor_18hz,countmax_18hz

chain_exit:
	pop	dx
	pop	cx
	pop	ax
	pop	es
	pop	bx
	pop	ds
	jmp	cs:bios_timer_routine

nochain_exit:
	mov	al,20h
	out	20h,al
	pop	dx
	pop	cx
	pop	ax
	pop	es
	pop	bx
	pop	ds
	iret

timer_tick	endp

;**********************************************************************
;*                   Initialization procedure                         *
;**********************************************************************

;This routine should be called exactly one time before any of the other
; routines in this package are called. It takes no parameters, but returns
; a value indicating success or failure as follows:

;Return value        Meaning
;------------        -------
;     0              success
;     1              voice package already initialized
;     2              wrong CPU, won't run on 8088 or 8086

	public	RVOICE_INIT

RVOICE_INIT	proc	far

	push	sp
	pop	ax
	cmp	ax,sp
	je	vi_test
	mov	ax,2
	ret

vi_test:
	cmp	hookedflag,0
	je	vi_hook
	mov	ax,1
	ret

vi_hook:
	enter	0,0
	push	si
	push	di

	push	ds
	lea	dx,RVOICE_CBREAK
	mov	ax,seg RVOICE_CBREAK
	mov	ds,ax
	mov	ax,2523h
	int	21h
	pop	ds

	push	ds
	mov	ax,3508h
	int	21h
	mov	word ptr cs:bios_timer_routine,bx
	mov	word ptr cs:bios_timer_routine+2,es
	lea	dx,timer_tick
	mov	ax,seg timer_tick
	mov	ds,ax
	mov	ax,2508h
	int	21h
	pop	ds

	pop	di
	pop	si
	mov	hookedflag,1
	sub	ax,ax
	leave
	ret

RVOICE_INIT	endp

;**********************************************************************
;*                      Cleanup procedure                             *
;**********************************************************************

;This will restore the interrupt 8 vector to its original state. This
; routine MUST be called before the main program exits to DOS, unless:
; (a) the program has never called RVOICE_INIT, or (b) the program is
; becoming memory-resident.

;There are no parameters. The return values are 0 for success or 1 if
; the interrupt vector was not in fact hooked.

	public	RVOICE_CLEANUP

RVOICE_CLEANUP	proc	far

	cmp	hookedflag,1
	je	vc_unhook
	mov	ax,1
	ret

vc_unhook:
	enter	0,0
	push	si
	push	di
	call	stopvoice
	push	ds
	lds	dx,cs:bios_timer_routine
	mov	ax,2508h
	int	21h
	pop	ds
	pop	di
	pop	si
	mov	hookedflag,0
	sub	ax,ax
	leave
	ret

RVOICE_CLEANUP	endp

;**********************************************************************
;*                Vector restore for Control-Break                    *
;**********************************************************************

	public	RVOICE_CBREAK

RVOICE_CBREAK	proc	far

	pusha
	push	ds
	push	es

	mov	ax,DGROUP
	mov	ds,ax
	call	stopvoice
	call	RVOICE_CLEANUP

	pop	es
	pop	ds
	popa
	stc
	ret

RVOICE_CBREAK	endp

;**********************************************************************
;*                "File write catch-up" procedure                     *
;**********************************************************************

;This must be called frequently from the main program if file writing is
; being used and the length of the data to be recorded is longer than the
; length of the memory block. The routine checks the progress of the
; address pointer in use by the interrupt service routine and writes
; new data to the file as it becomes available.

;There are no parameters. The return values are 0 for success 1 if there
; was no new data to write, or 2 if an error occurred while writing the file.

vcat_temp1l		equ	[bp-4]	;current address from ISR
vcat_temp1h		equ	[bp-2]
vcat_temp2l		equ	[bp-8]	;<unused>
vcat_temp2h		equ	[bp-6]

;the three stopping points:

vcat_temp3l		equ	[bp-12]	;current address - fill address
vcat_temp3h		equ	[bp-10]
vcat_temp4l		equ	[bp-16]	;block end - fill address
vcat_temp4h		equ	[bp-14]
vcat_temp5l		equ	[bp-20]	;(boundary) - fill address
vcat_temp5h		equ	[bp-18]

vcat_templength		equ	20

	public	RVOICE_CATCHUP

RVOICE_CATCHUP	proc	far

	enter	vcat_templength,0

;should we even be doing this?

	cmp	fileflag,0
	ljz	vcat_exit0

;grab a stable value from currentaddr
; since it is changing all the time

	cli
	mov	ax,word ptr currentaddr
	mov	vcat_temp1l,ax
	mov	ax,word ptr currentaddr+2
	mov	vcat_temp1h,ax
	sti

;calculate the forward distance to each of the
; three possible stopping points

vcat_getgap:
	mov	ax,vcat_temp1l
	sub	ax,word ptr fwrtaddr
	mov	vcat_temp3l,ax
	pushf
	mov	ax,vcat_temp1h
	sub	ax,word ptr fwrtaddr+2
	sar	ax,12
	popf
	sbb	ax,0
	mov	vcat_temp3h,ax

vcat_getclearance:
	mov	ax,word ptr blockend
	sub	ax,word ptr fwrtaddr
	mov	vcat_temp4l,ax
	pushf
	mov	ax,word ptr blockend+2
	sub	ax,word ptr fwrtaddr+2
	sar	ax,12
	popf
	sbb	ax,0
	mov	vcat_temp4h,ax

	mov	ax,word ptr fwrtaddr
	neg	ax
	mov	vcat_temp5l,ax
	mov	word ptr vcat_temp5h,0
	jnz	vcat_cmp2
	inc	word ptr vcat_temp5h

;select the stopping point which will be
; encountered soonest

;Since we are not interested in the negative
; values, doing unsigned compares in a search
; for the smallest number will give the
; desired result.

vcat_cmp2:
	mov	ax,vcat_temp3h
	mov	cx,vcat_temp3l


vcat_cmp3:
	cmp	ax,vcat_temp4h
	jb	vcat_cmp5
	ja	vcat_cmp4
	cmp	cx,vcat_temp4l
	jb	vcat_cmp5

vcat_cmp4:
	mov	ax,vcat_temp4h
	mov	cx,vcat_temp4l


vcat_cmp5:
	cmp	ax,vcat_temp5h
	jb	vcat_cmp7
	ja	vcat_cmp6
	cmp	cx,vcat_temp5l
	jb	vcat_cmp7

vcat_cmp6:
	mov	ax,vcat_temp5h
	mov	cx,vcat_temp5l


vcat_cmp7:

;Now the smallest number is in AX:CX.
; However, AX:CX may be 65536 or 0FFFFh
; (illegal), so we must test for these
; possibilities.

	cmp	ax,1
	je	vcat_fixcx
	cmp	cx,0FFFFh
	jne	vcat_writefile

vcat_fixcx:
	mov	cx,0FE00h

vcat_writefile:
	or	cx,cx
	jz	vcat_exit1
	mov	bx,filehandle
	push	ds
	lds	dx,fwrtaddr
	mov	ah,40h
	int	21h
	pop	ds
	jc	vcat_error2
	cmp	ax,cx
	jne	vcat_error2

;update global varibles to show the current
; situation

	add	word ptr fwrtcount,cx
	adc	word ptr fwrtcount+2,0

	add	word ptr fwrtaddr,cx
	jnc	vcat_fillwrap
	add	word ptr fwrtaddr+2,1000h

vcat_fillwrap:
	mov	bx,word ptr blockend
	cmp	bx,word ptr fwrtaddr
	jne	vcat_exit0
	mov	ax,word ptr blockend+2
	cmp	ax,word ptr fwrtaddr+2
	jne	vcat_exit0
	mov	bx,word ptr blockaddr
	mov	ax,word ptr blockaddr+2
	mov	word ptr fwrtaddr,bx
	mov	word ptr fwrtaddr+2,ax

	jmp	short vcat_exit0

;return code exit points

vcat_error2:
	mov	ax,2
	jmp	short vcat_exit

vcat_exit1:
	mov	ax,1
	jmp	short vcat_exit

;normal exit point

vcat_exit0:

	;don't leave until at least one bit has been played
	; if goflag is not zero

	mov	ax,word ptr currentaddr
	cmp	vcat_temp1l,ax
	jne	vcat_exitloop
	cmp	goflag,0
	jne	vcat_exit0
vcat_exitloop:
	sub	ax,ax
vcat_exit:
	leave
	ret

RVOICE_CATCHUP	endp

;**********************************************************************
;*                   "Start recording" procedure                      *
;**********************************************************************

;Accepts the following parameters with PASCAL parameter-passing convention:

;Position   Size   Description
;--------   ----   -----------
;   1         4    A far pointer to the memory block used for voice data.
;   2         4    A dword indicating the length of the memory block.
;   3         2    A flag word. If this is 1, then data will be written to
;                   a file. If it is 0, no file operations will be performed.
;   4         2    An open file handle. This is ignored if the flag word
;                   is 0.
;   5         2    A word containing the number of the COM port to be used
;                   for input (1 thru 4).
;   6         4    A dword indicating the offset within the memory block
;                   where recording is to begin.

;Return value        Meaning
;------------        -------
;     0              success
;     1              block size is too small (blocklen < 8192 and 
;                     file write = yes)
;     2              voice recording is already in progress
;     3              a COM port number not within the range of 1 thru 4
;                     was specified
;     4              starting position is not less than block length

min_blocksize		equ	8192

vs_parm1		equ	[bp+20]	;length = 4
vs_parm2		equ	[bp+16]	;length = 4
vs_parm3		equ	[bp+14]	;length = 2
vs_parm4		equ	[bp+12]	;length = 2
vs_parm5		equ	[bp+10]	;length = 2
vs_parm6		equ	[bp+6]	;length = 4

vs_parmlength	equ	18

	public	RVOICE_START

RVOICE_START	proc	far

	cmp	goflag,0
	je	vs_begin
	mov	ax,2
	ret	vs_parmlength

vs_begin:
	enter	0,0
	mov	word ptr datacount,0
	mov	word ptr datacount+2,0
	mov	word ptr fwrtcount,0
	mov	word ptr fwrtcount+2,0

	les	bx,dword ptr vs_parm1
	mov	word ptr blockaddr,bx
	mov	word ptr blockaddr+2,es
	les	bx,dword ptr vs_parm6
	mov	ax,es
	add	bx,word ptr blockaddr
	adc	ax,0
	shl	ax,12
	add	ax,word ptr blockaddr+2
	mov	word ptr currentaddr,bx
	mov	word ptr currentaddr+2,ax
	mov	word ptr fwrtaddr,bx
	mov	word ptr fwrtaddr+2,ax
	les	bx,dword ptr vs_parm2
	mov	word ptr blocksize,bx
	mov	word ptr blocksize+2,es
	mov	ax,es
	add	bx,word ptr blockaddr
	adc	ax,0
	shl	ax,12
	add	ax,word ptr blockaddr+2
	mov	word ptr blockend,bx
	mov	word ptr blockend+2,ax
	cmp	ax,word ptr currentaddr+2
	jb	vs_error4
	ja	vs_gethandle
	cmp	bx,word ptr currentaddr
	jbe	vs_error4

vs_gethandle:
	mov	ax,vs_parm4
	mov	filehandle,ax

	mov	bx,vs_parm5
	cmp	bx,1
	jb	vs_error3
	cmp	bx,4
	ja	vs_error3
	dec	bx
	shl	bx,1
	lea	ax,comlist
	add	bx,ax
	mov	ax,[bx]
	mov	comaddr,ax

	mov	ax,vs_parm3
	mov	fileflag,ax

	or	ax,ax
	jz	vs_exit0

	mov	ax,word ptr blocksize
	sub	ax,min_blocksize
	mov	ax,word ptr blocksize+2
	sbb	ax,0
	jnc	vs_exit0
	mov	ax,1
	jmp	short vs_exit

vs_error3:
	mov	ax,3
	jmp	short vs_exit

vs_error4:
	mov	ax,4
	jmp	short vs_exit

vs_exit0:
	call	startvoice
	sub	ax,ax
vs_exit:
	leave
	ret	vs_parmlength

RVOICE_START	endp

;**********************************************************************
;*                   "Stop recording" procedure                       *
;**********************************************************************

;Call this to stop the voice recording operation.

;This routine accepts no parameters and has no return information.

	public	RVOICE_STOP

RVOICE_STOP	proc	far

	call	stopvoice
	sub	ax,ax
	ret

RVOICE_STOP	endp

;**********************************************************************
;*                     "Get status" procedure                         *
;**********************************************************************

;This will report the status of voice operations.

;The first parameter is a far pointer to a dword which will receive the
; number of bytes which have already been recorded.
;The second parameter is a far pointer to a dword which will receive a
; value indicating the offset within the memory block of the next byte
; to be filled by the recording operation.

;There is no return value.

vst_parm1	equ	[bp+10]	;length = 4
vst_parm2	equ	[bp+6]	;length = 4

vst_parmlength	equ	8

	public	RVOICE_STATUS

RVOICE_STATUS	proc	far

	enter	0,0

	les	bx,vst_parm1
	cli
	mov	ax,word ptr datacount
	mov	dx,word ptr datacount+2
	mov	es:[bx],ax
	mov	es:[bx+2],dx
	les	bx,vst_parm2
	mov	ax,word ptr currentaddr
	mov	dx,word ptr currentaddr+2
	sti
	shr	dx,12	;we can cheat here because we
			; know it's incremented by 1000h units
	mov	cx,word ptr blockaddr+2
	shr	cx,12
	sub	ax,word ptr blockaddr
	sbb	dx,cx
	mov	es:[bx],ax
	mov	es:[bx+2],dx

	sub	ax,ax
	leave
	ret	vst_parmlength

RVOICE_STATUS	endp

	end
