page 58,132
;****************************************************************
;*                                                              *
;*             Digitized Voice Programmer's Toolkit             *
;*             ------------------------------------             *
;*                                                              *
;*                 Sound Recording Primitives                   *
;*                                                              *
;*            Copyright (c) 1991, Farpoint Software             *
;*                                                              *
;****************************************************************

.8086
.MODEL LARGE

;**********************************************************************
;*                                                                    *
;*                             Equates                                *
;*                                                                    *
;**********************************************************************

tccount		equ	72	;reload count to produce 16572 Hz
countmax_18hz	equ	910	;ratio of new timer rate to standard rate

tcaddrc		equ	43h	;timer/counter control register address
tcaddrd0	equ	40h	;timer/counter data register zero (sys clock)

tcmode0a	equ	34h	;two-byte mode for system clock timer
tcmode0b	equ	24h	;high-byte mode for system clock timer

tclatchcmd0	equ	00h	;latch command for reading timer 0

ppiaddr		equ	61h	;programmable peripheral interface address

kbdonly_mask	equ	0FDh	;mask config to allow only keyboard interrupt
intmaskaddr	equ	21h	;address of the interrupt mask register
kbdint_number	equ	9	;vector number of the keyboard hardware int

;CPU speed calibration initialization parameters

init_delayctr		equ	1024
init_cal_increment	equ	512
init_cal_passes		equ	10

;**********************************************************************
;*                                                                    *
;*                         Global variables                           *
;*                                                                    *
;**********************************************************************

.DATA

comaddr		dw	2ECh

;CPU speed calibration data

delayctr	dw	init_delayctr
cal_increment	dw	init_cal_increment
cal_passes	dw	init_cal_passes

;keyboard hardware interrupt occurrence flag

keyflag		db	0

;interrupt controller mask save byte

original_mask	db	0

comlist		dw	3FCh	;needed addresses of all COM ports
		dw	2FCh
		dw	3ECh
		dw	2ECh

;**********************************************************************
;*                                                                    *
;*                               Code                                 *
;*                                                                    *
;**********************************************************************

.CODE

	assume	ds:DGROUP

;code-segment storage for keyboard interrupt chain vector

original_kbdint_vec	dd	0

;**********************************************************************
;*          Keyboard Hardware Interrupt Intercept Routine             *
;**********************************************************************

;This routine sets the "keyflag" variable to 1 whenever a keyboard
; interrupt occurs.

kbdint	proc	far

	push	ds
	push	ax
	mov	ax,DGROUP
	mov	ds,ax
	mov	keyflag,1
	pop	ax
	pop	ds
	jmp	dword ptr cs:original_kbdint_vec

kbdint	endp

;**********************************************************************
;*            Obtain available memory figure from DOS                 *
;**********************************************************************

;This routine returns a dword indicating the number of bytes of memory
; available for allocation from DOS.

	public	GETMEMAVAIL

GETMEMAVAIL	proc	far

	push	bp
	mov	bp,sp

	mov	bx,0FFFFh
	mov	ah,48h
	int	21h
	sub	bx,16
	jc	gma_none
	sub	dx,dx
	mov	ax,bx
	mov	cx,4
gma_dshift:
	shl	ax,1
	rcl	dx,1
	loop	gma_dshift

gma_exit:
	pop	bp
	ret

gma_none:
	sub	ax,ax
	sub	dx,dx
	jmp	gma_exit

GETMEMAVAIL	endp

;**********************************************************************
;*              Calibrated software delay routine                     *
;**********************************************************************

;lots of nops

rdelay	proc	near

	db	init_delayctr dup (90h)
	ret				;starting delay value
	db	(init_delayctr-1) dup (90h)
	ret				;should never actually be executed

rdelay	endp

;**********************************************************************
;*                    Set Calibration Constant                        *
;*                                                                    *
;*       This call simply sets the calibration constant to the        *
;*       value specified in the parameter.                            *
;**********************************************************************

;Accepts the following parameters with PASCAL parameter-passing convention:

;Position   Size   Description
;--------   ----   -----------
;   1         2    The calibration constant to be set.

;There is no return value.

rsc_parm1	equ	[bp+6]	;length = 2

rsc_parmlength	equ	2

	public	RSETCAL

RSETCAL		proc	far

	push	bp
	mov	bp,sp

;in case this routine has been called before, erase the "ret" instruction
; that was placed in the delay routine

	lea	bx,rdelay
	add	bx,delayctr
	mov	byte ptr cs:[bx],90h	;this is a "nop" instruction

;get the new delay parameter

	mov	ax,rsc_parm1
	mov	delayctr,ax

;insert a "ret" instruction at the new location in the delay routine

	lea	bx,rdelay
	add	bx,delayctr
	mov	byte ptr cs:[bx],0C3h	;this is a "ret" instruction

	pop	bp
	ret	rsc_parmlength

RSETCAL		endp

;**********************************************************************
;*                      Calibration procedure                         *
;*                                                                    *
;*      This routine must be called once before any calls are made    *
;*      to the recording routine. It measures the CPU execution       *
;*      speed and saves compensation values.                          *
;**********************************************************************

;There are no entry parameters.

;The return value is a double word, defined as follows:

;Return value low word        Meaning
;---------------------        -------
;     0                       success
;     1                       this CPU is too slow to accomplish
;                              normal-speed playback
;     2                       operation not possible under Windows
;                              in "Enhanced" mode

;The return value high word is the actual speed calibration constant
; for this computer

cal_temp1		equ	[bp-2]	;length = 2
cal_temp2		equ	[bp-4]	;length = 2

cal_locallength		equ	4

	public	RCALIBRATE

RCALIBRATE	proc	far

	push	bp
	mov	bp,sp
	sub	sp,cal_locallength
	push	si

;test for the "Enhanced Mode Windows" environment

	mov	ax,1600h
	int	2Fh
	cmp	al,00h
	je	cal_no_enh_win
	cmp	al,80h
	je	cal_no_enh_win
	mov	delayctr,1
	mov	ax,2
	jmp	cal_exit
cal_no_enh_win:

;in case this routine has been called before, erase the "ret" instruction
; that was placed in the delay routine

	lea	bx,rdelay
	add	bx,delayctr
	mov	byte ptr cs:[bx],90h	;this is a "nop" instruction

;initialize parameters

	mov	delayctr,init_delayctr
	mov	cal_increment,init_cal_increment
	mov	cal_passes,init_cal_passes

;insert a "ret" instruction into the starting location in the delay routine

	lea	bx,rdelay
	add	bx,delayctr
	mov	byte ptr cs:[bx],0C3h	;this is a "ret" instruction

;no interrupts until we are through calibrating

	cli

;write the standard setup to timer zero

	mov	al,tcmode0a
	out	tcaddrc,al
	jmp	short $+2
	jmp	short $+2
	mov	al,0
	out	tcaddrd0,al
	jmp	short $+2
	jmp	short $+2
	out	tcaddrd0,al
	jmp	short $+2
	jmp	short $+2

;set timer zero to high-byte mode

	mov	al,tcmode0b
	out	tcaddrc,al
	jmp	short $+2
	jmp	short $+2
	mov	al,0
	out	tcaddrd0,al
	jmp	short $+2
	jmp	short $+2

;use a "dummy" port address

	mov	dx,comaddr

;initialize loop count

cal_zerocnt:
	sub	cx,cx
	mov	word ptr cal_temp1,0
	mov	word ptr cal_temp2,0
	mov	keyflag,0

;synchronize to timer tick

	mov	ah,0
	mov	al,tclatchcmd0
	out	tcaddrc,al
	jmp	short $+2
	jmp	short $+2
	in	al,tcaddrd0
	jmp	short $+2
	jmp	short $+2
	mov	si,ax
cal_sync:
	mov	al,tclatchcmd0
	out	tcaddrc,al
	jmp	short $+2
	jmp	short $+2
	in	al,tcaddrd0
	jmp	short $+2
	cmp	ax,si
	mov	si,ax
	jbe	cal_sync

;calibration loop

cal_dummyloop:
	mov	al,01h
	out	dx,al
	jmp	short $+2
	jmp	short $+2
	add	dx,2
	in	al,dx
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	mov	ah,al
	sub	dx,2
	mov	al,00h
	out	dx,al
	jmp	short $+2
	jmp	short $+2
	add	dx,2
	in	al,dx
	and	al,0F0h
	or	ah,al
	mov	al,02h
	sub	dx,2
	out	dx,al
	jmp	short $+2
	jmp	short $+2
	jmp	short $+2
	jmp	short $+2
	jmp	short $+2
	jmp	short $+2
	mov	al,00h
	out	dx,al
	inc	cx
	jnz	cal_countbytes
cal_countbytes:
	inc	word ptr cal_temp2
	jnz	cal_dummydelay
	inc	word ptr cal_temp1
cal_dummydelay:
	call	rdelay
	inc	dx
	in	al,dx
	dec	dx
	test	al,00h		;dummy test for end of recording
	jnz	cal_checktime
	cmp	keyflag,0
	jne	cal_checktime
cal_checktime:
	mov	al,tclatchcmd0
	out	tcaddrc,al
	jmp	short $+2
	jmp	short $+2
	in	al,tcaddrd0
	sub	ah,ah
	cmp	ax,si
	mov	si,ax
	jbe	cal_dummyloop

;kill the old "ret" instruction in the delay routine

	lea	bx,rdelay
	add	bx,delayctr
	mov	byte ptr cs:[bx],90h	;this is a "nop" instruction

;adjust delay counter

	mov	ax,cal_increment
	shr	cal_increment,1
	cmp	cx,countmax_18hz
	je	cal_setret
	jb	cal_makefaster
	add	delayctr,ax
	jmp	cal_setret
cal_makefaster:
	sub	delayctr,ax
cal_setret:				;here we insert a new return
	lea	bx,rdelay
	add	bx,delayctr
	mov	byte ptr cs:[bx],0C3h	;this is a "ret" instruction
	dec	cal_passes
	cmp	cal_passes,0
	je	cal_restore
	jmp	cal_zerocnt

;repair loss of system timer ticks and restore interrupts

cal_restore:
	mov	bx,40h
	mov	es,bx
	mov	bx,6Ch
	add	word ptr es:[bx],init_cal_passes+1
	adc	word ptr es:[bx+2],0
	cmp	word ptr es:[bx+2],18h
	jb	cal_tc_end
	ja	cal_tc_midnite
	cmp	word ptr es:[bx],0B0h
	jb	cal_tc_end
cal_tc_midnite:
	mov	byte ptr es:[bx+4],1
	sub	word ptr es:[bx],0B0h
	sbb	word ptr es:[bx+2],18h
cal_tc_end:
	sti

;determine if CPU is too slow

cal_checkcpu:
	cmp	delayctr,1
	je	cal_cpuslow
	sub	ax,ax
	jmp	cal_exit
cal_cpuslow:
	mov	ax,1
cal_exit:
	mov	dx,delayctr
	pop	si
	add	sp,cal_locallength
	pop	bp
	ret

RCALIBRATE	endp

;**********************************************************************
;*                         Record 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 word containing the number of the COM port to be used
;                   for input (1 thru 4).

;The return value is a dword indicating the number of bytes recorded.
;A return value of -1L indicates that the COM port was invalid.

;A full buffer or a break interrupt from the UART stops the recording.

rv_parm1		equ	[bp+12]	;length = 4
rv_parm2hi		equ	[bp+10]	;length = 2
rv_parm2lo		equ	[bp+8]	;length = 2
rv_parm3		equ	[bp+6]	;length = 2

rv_parmlength		equ	10

rv_temp1		equ	[bp-2]	;length = 2
rv_temp2		equ	[bp-4]	;length = 2

rv_locallength		equ	4

	public	RECORDVOICE

RECORDVOICE	proc	far

	push	bp
	mov	bp,sp
	sub	sp,rv_locallength
	push	si

;set up com port

	mov	dx,rv_parm3
	cmp	dx,1
	jae	rv_comhirange
	jmp	rv_badcom
rv_comhirange:
	cmp	dx,4
	jbe	rv_lookupcomaddress
	jmp	rv_badcom
rv_lookupcomaddress:
	dec	dx
	shl	dx,1
	lea	bx,comlist
	add	bx,dx
	mov	dx,[bx]
	mov	comaddr,dx

	sub	dx,4
	in	al,dx
	jmp	short $+2
	jmp	short $+2
	in	al,dx
	jmp	short $+2
	jmp	short $+2
	in	al,dx
	jmp	short $+2
	jmp	short $+2
	add	dx,2
	in	al,dx
	jmp	short $+2
	jmp	short $+2
	add	dx,3
	in	al,dx
	jmp	short $+2
	jmp	short $+2
	inc	dx
	in	al,dx
	sub	dx,2

;mask all interrupts except the keyboard

	cli

	in	al,intmaskaddr
	mov	original_mask,al
	mov	al,kbdonly_mask
	out	intmaskaddr,al

;install the keyboard interrupt intercept routine

	sub	ax,ax
	mov	es,ax
	mov	bx,kbdint_number*4
	mov	ax,es:[bx]
	mov	word ptr cs:original_kbdint_vec,ax
	mov	ax,es:[bx+2]
	mov	word ptr cs:original_kbdint_vec+2,ax
	lea	ax,kbdint
	mov	es:[bx],ax
	mov	ax,cs
	mov	es:[bx+2],ax

	sti

;set up initial conditions

	mov	word ptr rv_temp1,0
	mov	word ptr rv_temp2,0
	mov	keyflag,0
	les	bx,dword ptr rv_parm1
	mov	cx,rv_parm2lo
	mov	si,rv_parm2hi
	cmp	cx,0
	je	rv_recordloop
	inc	si

;recording loop

rv_recordloop:
	mov	al,01h
	out	dx,al
	jmp	short $+2
	jmp	short $+2
	add	dx,2
	in	al,dx
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	mov	ah,al
	sub	dx,2
	mov	al,00h
	out	dx,al
	jmp	short $+2
	jmp	short $+2
	add	dx,2
	in	al,dx
	and	al,0F0h
	or	ah,al
	mov	al,02h
	sub	dx,2
	out	dx,al
	jmp	short $+2
	jmp	short $+2
	jmp	short $+2
	jmp	short $+2
	jmp	short $+2
	jmp	short $+2
	mov	al,00h
	out	dx,al
	mov	es:[bx],ah
	inc	bx
	jnz	rv_countbytes
	mov	ax,es
	add	ax,1000h
	mov	es,ax
rv_countbytes:
	inc	word ptr rv_temp2
	jnz	rv_delay
	inc	word ptr rv_temp1
rv_delay:
	call	rdelay
	inc	dx
	in	al,dx
	dec	dx
	test	al,10h		;test for end of recording
	jnz	rv_stop
	cmp	keyflag,0
	jne	rv_stop
	mov	al,tclatchcmd0
	out	tcaddrc,al
	jmp	short $+2
	jmp	short $+2
	in	al,tcaddrd0
	sub	ah,ah
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	loop	rv_recordloop
	dec	si
	jnz	rv_recordloop
rv_stop:

;remove the keyboard interrupt intercept routine

	cli

	sub	ax,ax
	mov	es,ax
	mov	bx,kbdint_number*4
	mov	ax,word ptr cs:original_kbdint_vec
	mov	es:[bx],ax
	mov	ax,word ptr cs:original_kbdint_vec+2
	mov	es:[bx+2],ax

;restore the original interrupt mask

	mov	al,original_mask
	out	intmaskaddr,al

	sti

;compensate for lost timer ticks

	mov	dx,rv_parm2hi
	mov	ax,rv_parm2lo
	mov	cx,countmax_18hz
	div	cx
	mov	bx,40h
	mov	es,bx
	mov	bx,6Ch
	cli
	add	es:[bx],ax
	adc	word ptr es:[bx+2],0
	cmp	word ptr es:[bx+2],18h
	jb	rv_tc_end
	ja	rv_tc_midnite
	cmp	word ptr es:[bx],0B0h
	jb	rv_tc_end
rv_tc_midnite:
	mov	byte ptr es:[bx+4],1
	sub	word ptr es:[bx],0B0h
	sbb	word ptr es:[bx+2],18h
rv_tc_end:
	sti

;set return value

	mov	dx,rv_temp1
	mov	ax,rv_temp2

;exit

rv_exit:
	pop	si
	add	sp,rv_locallength
	pop	bp
	ret	rv_parmlength

;set return value for COM port out of range

rv_badcom:
	mov	dx,0FFFFh
	mov	ax,0FFFFh
	jmp	rv_exit
	
RECORDVOICE	endp

	end
