;       SCCSID = @(#)d_beep.asm	6.1 91/02/15
;	SCCSID = @(#)d_beep.asm	1.4 90/01/22
; ****************************************************************************
; *									     *
; *			  IBM/Microsoft Confidential			     *
; *									     *
; *		    Copyright (c) IBM Corporation  1987, 1990		     *
; *		    Copyright (c) Microsoft Corp.  1987, 1990		     *
; *			      All Rights Reserved			     *
; *									     *
; ****************************************************************************
;***  D_BEEP.ASM - API stub routine DosBeep
;
;	Member of API library
;	David Silverstein
;	Copyright MicroSoft Corporation, 1986
;	29-Apr-1986
;
;	library routine for API simulator
;
;	Called from dos 2.x,3.x compatible dos 5.x applications
;
;	Special Information
;
;		DOSBEEP installs a CtrlC and critical error handler to turn
;		off the speaker when either of these events occur.
;
;	Modification History
;
;	Davidsi	   5-Aug-86	The code previously hooked the timer interrupt
;				to determine when the beep should end.  It now
;				polls the rom bios timer data areas until the
;				expiration time is reached.
;
;	06-Jan-87 (billme): Replace initial pushes and final pops
;	with calls to __api_begin and __api_end resp.  This will
;	reduce the size of FAPI and allow support for signals.
;
;	15-Jan-87 (billme): Change as many statics as possible to locals
;	to save space (both from not consuming static space and from
;	not have to use segment override - cs - for access)
;

include basemaca.inc
include dos2equ.inc
include	error2.inc
include	api.inc

	EXTRN	__api_begin_lsf:near
	EXTRN	__api_end:near

;***	DOSBEEP
;
;	simulates DOS 5.0 call from DOS 2.x,3.x environment
;
;	errorcode = unsigned far pascal
;	DosBeep(Frequency, Duration)
;
;	ENTRY:
;		Frequency - Tone frequency in range 25H to 7FFFH  (in hertz)
;		Duration - Length of sound in milliseconds.
;
;	RETURN:
;		AX = 0 if no error, else error code (see DOS 5 spec)
;
;	Pseudo Code:
;		save registers
;		use BP as pointer to stack parameter
;		calculate expiration time
;		initialize and start the 8254 timer at parameter Frequency
;		loop until the expiration time is reached
;		turn off timer and speaker
;		restore registers
;		return
;
;	WARNING:
;		may return error code from Dos 2.x, 3.x (see dos manuals)
;

	title	DOSBEEP
;
;	EXTRN	DOSBEEP:FAR
;
;	PUSH	WORD Frequency
;	PUSH	WORD Duration
;	CALL	DOSBEEP
;


; This segment is defined to access memory variables in the bios data area

BIOS	segment byte at 40h
	assume	ds:BIOS

	org	6ch
	TimerLow	dw	?	; low word of bios timer data area
	TimerHigh	dw	?	; high word of bios timer data area

BIOS	ends


_TEXT	segment byte public 'CODE'
	assume	CS:_TEXT
	public	DOSBEEP

	extrn	FapiAddSigCatch	:FAR
	extrn	FapiSubSigCatch	:FAR


Frequency	equ	[bp + 8]
Duration	equ	[bp + 6]

LOWFREQLIMIT	equ	25h		; low limit of frequency parameter
HIGHFREQLIMIT	equ	7fffh		; high limit of frequency parameter
CLOCKRATEHIGH	equ	12h		; high order word of input clock rate
CLOCKRATELOW	equ	2870h		; low order word of input clock rate
TIMER2INIT	equ	0b6h		; 8254 command to initialize timer 2
SPEAKERBITS	equ	3		; 8254 GATE 2 and speaker toggle bit
TIMERCOMPORT	equ	43h		; 8254 timer command register port
TIMERLATCHREG	equ	42h		; 8254 timer latch register port
PORTB		equ	61h		; 8255 PB port address
TICKTIME	equ	55		; timer tick interval in milliseconds
HOURS24_HIGH	equ	18h		; high word of timer after 24 hours
HOURS24_LOW	equ	0b0h		; low word of timer after 24 hours

					; local variables
ExpTimeLow	equ	[bp-2]		; low word of expiration time (dw)
ExpTimeHigh	equ	[bp-4]		; high word of expiration time (dw)
LastTimerHigh	equ	[bp-6]		; high word of timer when last read (dw)


DOSBEEP	proc	far

	mov	ax,6
	call	__api_begin_lsf	; init bp and save bp,ds,bx,cx,dx,si,di,es
				; also create local stack frame (size of ax)
				; required to save: bp,bx,cx,dx,ds

; check to see if the Frequency parameter is within range

	mov	ax, ERROR_INVALID_FREQUENCY
	mov	cx,Frequency
	cmp	cx, LOWFREQLIMIT	; low limit
	jb	dosbeep1

	cmp	cx, HIGHFREQLIMIT	; high limit
	ja	dosbeep1

	cmp	word ptr [Duration], 0		; is Duration > 0 ?
	jne	dosbeep3			; continue
	xor	ax,ax				; go exit without error
dosbeep1:
	jmp	dosbeep10

; set up control C handler. This is necessary so we
; can turn off the speaker if a control C occurs.

dosbeep3:
	mov	ax, SEG _TEXT
	mov	ds, ax
	mov	dx, offset _TEXT:ctrlchook
	mov	bx, BEEP
	call	FapiAddSigCatch

	mov	bx, SEG BIOS			; set up seg addressability for
	mov	ds, bx				; bios data area

; save and initialize timer 2

	mov	al, TIMER2INIT			; set up timer 2
	out	TIMERCOMPORT, al

	mov	dx, CLOCKRATEHIGH		; calculate divisor from input
	mov	ax, CLOCKRATELOW		; Frequency
	div	cx

	out	TIMERLATCHREG, al		; load divisor into timer 2
	mov	al, ah				; latch register
	IODELAY
	out	TIMERLATCHREG, al

	; calc expiration time

	mov	ax, [Duration]			; get duration of beep
	xor	dx, dx
	mov	bx, TICKTIME			; get ms per clock tick
	div	bx				; calc duration in clock ticks
	test	dx, dx				; any remainer?
	jz	dosbeep4			; don't round
	inc	ax				; round up 1 clock tick
	xor	dx, dx
dosbeep4:
	cli				; disable ints during clock var read
	add	ax, BIOS:[TimerLow]		; add current time
	mov	bx, BIOS:[TimerHigh]
	sti					; reenable interrupts
	adc	dx, bx
	mov	[LastTimerHigh], bx	; save for later wrap detect

	xor	bx, bx				; clear timer wrapped flag
	cmp	dx, HOURS24_HIGH		; will the timer wrap?
	jb	dosbeep5
	ja	dosbeep4b
	cmp	ax, HOURS24_LOW
	jb	dosbeep5
dosbeep4b:
	sub	ax, HOURS24_LOW			; yes, subtract timer period
	sbb	dx, HOURS24_HIGH
	mov	bx, 1				; flag that timer will wrap
dosbeep5:
	mov	[ExpTimeLow], ax		; save expiration time for later
	mov	[ExpTimeHigh], dx

; turn on speaker here

	in	al, PORTB			; enable GATE 2 for timer and
	or	al, SPEAKERBITS			; speaker toggle bit
	IODELAY
	out	PORTB, al

; loop until expiration time is reached

dosbeep6:
	; Here we call an arbitrary dos call to check ctrlC.  Although this is
	; slow, the keyboard cannot be checked for ctrlC directly. This is
	; because DOSHOLDSIGNAL would get broken.

	mov	ah, D2_GetKbdStat
	int	21h				; check for CtrlC

	cli					; disable while reading clock
	mov	ax, BIOS:[TimerLow]		; read bios clock data
	mov	dx, BIOS:[TimerHigh]
	sti					; reenable
	test	bx, bx				; is a timer wrap pending?
	jz	dosbeep7			; no
	cmp	dx, [LastTimerHigh]		; has the timer wrapped?
	mov	[LastTimerHigh], dx
	jae	dosbeep6			; no, continue
	xor	bx, bx				; timer wrapped, clear flag

dosbeep7:
	sub	ax, [ExpTimeLow]		; have we reached the exp time?
	sbb	dx, [ExpTimeHigh]
	jb	dosbeep6			; no, continue

; shut the speaker up

	call	far ptr speakerOff
	mov	bx, BEEP
	call	FapiSubSigCatch
	xor	ax, ax				; clear error return

dosbeep10:

	call	__api_end	; check sigs & restore es,di,si,dx,cx,bx,ds,bp

	ret	4	; clear stack of entry parameters (bytes)
DOSBEEP	endp


; turn the speaker off

speakerOff	proc	far

	in	al, PORTB			; get PB status bits
	mov	ah, SPEAKERBITS			; turn off GATE timer enable
	not	ah				; and speaker enable bits
	and	al, ah
	out	PORTB, al
	ret

speakerOff	endp


; This is the control C hook

ctrlchook	proc	far

	call	speakerOff
	ret

ctrlchook	endp


_TEXT	ends

	end
