	title	beep - routine to sound the speaker
;;;     beep - a routine to sound the speaker
;
;; Beep() is passed a tone period (in uS) and duration (in mS).
;    It sounds the IBM/PC speaker as specified.  If the period
;    is zero, a silent period is generated.
;
;  C Calling Sequence:  beep(tone, time);
;
;    where (unsigned) "tone" is the period of the squarewave
;    applied to the speaker in microseconds and (unsigned)
;    "time" is the duration of the tone.
;
;  Author:  Jim Wilson for Wintek Corporation.

; Macro to flush prefetch queue (required for 80286, 80386)

fpq     macro
	jmp     short $+2
	endm

; Programmable Interval Timer registers/contents defined
;
;    We assume the counters' input frequency is 1 Mhz (actually
;      it's 1.19 Mhz) and we'll divide by 1000 (actually 1024)
;      to get a millisecond clock.

PT_CN0  equ     40H     ; Counter 0 (input = 1.19Mhz, divisor = 65536)
PT_CN2  equ     42H     ; Counter 2 (speaker output period in microseconds)
PT_CWR  equ     43H     ; Control Word (Command) Register

PT_LT0  equ     00000000b ; Latch/Read Counter 0
PT_WT2  equ     10110110b ; Write Counter 2 (both bytes)

; Speaker sound control port.

SP_PPI  equ     61H     ; Bits 0, 1 of Programmable Parallel Interface gate
SP_ENA  equ   03FCH     ;   sound to speaker (SP_ENA.hi = bits, .lo = mask)

	public  _beep,_ticks,_resp,_pc,_prc,_tod
	.model  small
	.data
oldtime db      ?       ; PTM contents on previous ticks() call

	.code
_beep   proc
	push    bp
	mov     bp,sp
	mov     al,PT_WT2       ; set up for write counter2
	out     PT_CWR,al
	mov     bx,SP_ENA
	mov     ax,[bp+4]       ; ax = period
	or      ax,ax
	jne     beep1           ; bl = speaker mask
	xor     bh,bh           ; bh = silent? disable: enable

; Set up counter2 to generate square waves of the appropriate period.

beep1:  out     PT_CN2,al       ; counter2 = period
	fpq
	mov     al,ah
	out     PT_CN2,al
	in      al,SP_PPI       ; enable (or disable) sound gate
	and     al,bl
	or      al,bh
	out     SP_PPI,al

; Now wait until time expires.

	call    _ticks          ; reset timer
beep2:  call    _ticks          ; ax = delta_T
	sub     [bp+6],ax       ; time -= delta_T
	ja      beep2
	in      al,SP_PPI       ; disable sound gate
	and     ax,SP_ENA
	fpq
	out     SP_PPI,al
	pop     bp
	ret
_beep   endp

;;;     ticks - return elapsed time since last call
;
;; Ticks() returns a count of milliseconds since it was last called.
;
;  NOTE:  The code assumes the PC's timer is driven by a 1.024Mhz
;    source (actually it's 1.19Mhz) so the clock runs about 16% too
;    fast.  Since the PC's timer underflows 36.2 times per second,
;    ticks() must be called at least every 27 m.s. to detect this
;    underflow.  The 8254 counter is set to "mode 3" so that its
;    16-bit counter decrements by two on every input clock.
;
;  C Calling Sequence:  elapsed_time = ticks();
;
;   where (unsigned) "elapsed_time" is the number of milliseconds
;     since the last call to ticks().
;
;  Author:  Jim Wilson for Wintek Corporation.

_ticks  proc
	mov     al,PT_LT0       ; latch counter 0
	out     PT_CWR,al
	fpq
	in      al,PT_CN0       ; fetch/discard l.s. byte
	fpq
	in      al,PT_CN0       ; fetch/retain m.s. byte
	shr     al,1            ; al = raw milliseconds (0-31)
	shr     al,1
	shr     al,1
	mov     ah,oldtime      ; ah = time of last call
	mov     oldtime,al      ; oldtime = time of this call
	xchg    al,ah
	sub     al,ah           ; al = delta_T.lo
	jae     tick1           ; if timer's underflowed
	add     al,32           ;   correct returned value.
tick1:  xor     ah,ah           ; ah = delta_t.hi
	ret
_ticks  endp

;;;     resp - return keyboard character (non-blocking)
;
;;  Resp() returns a character typed at the keyboard or '\0' if none.
;     Codes a-z are converted to A-Z.
;
;   NOTE:  An earlier version of this program used the BIOS keyboard
;     function AH = 01, "check keyboard status", to check if there
;     was a character available.  Unfortunately, some BIOS's take this
;     opportunity to suspend the program (to do some housekeeping?)
;     long enough so that ticks() overflows.  This version examines
;     the BIOS-defined keyboard circular buffer pointers to see if
;     they are the same (indicating there is no character to be had).
;     If there is a character, BIOS keyboard interrupt with AH = 00 is
;     used to fetch it.
;
;   C Calling Sequence: c = resp();
;
;      where (char) "c" is the ASCII key value converted to
;        lower case or NUL if no character is present.

_resp   proc
	mov     ax,40h          ; Segment for BIOS keyboard circular buffer
	mov     es,ax           ; Into extra segment register
	mov     ax,es:[1Ah]     ; AX = input pointer
	sub     ax,es:[1Ch]     ; If same as output pointer,
	je      res3            ;   return immediately
	xor     ah,ah           ; AH = "Keyboard read"
	int     16h             ; Call BIOS to fetch the key
	xor     ah,ah           ; Zero-pad to 16 bits
res2:   cmp     al,'a'          ; ax = toupper(ax)
	jb      res3
	cmp     al,'z'
	ja      res3
	sub     al,'a'-'A'
res3:   ret
_resp   endp

;;;     prc - put character/attribute at specific screen location
;
;;  prc() is passed an integer character/attribute and a screen row
;;    and column for its display.  (Row/column 0/0 is at the top/left
;;    of the screen).  Pcxy() moves the "cursor" to the location, writes
;;    the character there, and then restores the old cursor location.
;     These contortions are the easiest way to make the code display-
;     adapter- and mode-independent.
;
;   NOTE:  The ASCII character code is in the LS byte; the attribute
;     is in the MS byte of the (short) integer "char_attr" parameter.
;
;   Calling Sequence:    void prc(int char_attr, int row, int column);

_prc    proc
	push    bp              ; Save caller's frame
	mov     bp,sp           ; Point at parameter block
	mov     ah,03h          ; AH = "Read cursor Position"
	xor     bh,bh           ; BH = page number = 0
	int     10h             ; DH,DL = current cursor row, column
	push    dx              ; Save old cursor position
	mov     dh,[bp+6]       ; DH = row for character
	mov     dl,[bp+8]       ; DL = column for character
	mov     ah,02h          ; AH = "Set (new) Cursor Position"
	int     10h             ; Call BIOS to move cursor
	pop     dx              ; DX = old cursor position
	mov     ax,[bp+4]       ; AX = ASCII character/attribute
	xor     cx,cx           ; CX = nr. chars to display (minus one)
	jmp     short pc6       ; Shared code with pc(), below.
_prc    endp

;;;     pc - put character (to screen)
;
;;  Pc() is passed a character/attribute to display at the current cursor
;;     position.  It displays the character and advances the cursor.
;
;   Pc() watches for two special characters:
;
;     '\f':  Select active page 0, clear the screen, and put the
;             the cursor in column 0 on the first line.
;     '\n':  If the cursor is on the last line, scroll the bottom
;             few lines (the code window).  Then put the cursor on
;             in column 0 on the next line.
;     '\v':  Move the cursor to the first column on display's last
;             line.
;
;  In these cases, the attribute (the parameter MS byte) is used
;  to fill the empty line(s) created by the respective operation.
;
;  Calling Sequence:    void pc(int char_attr);

CODE_HT equ     5               ; Height of code window (in lines)

_pc     proc
	push    bp              ; Save caller's frame
	mov     bp,sp           ; Point at parameter block
	mov     ah,03h          ; AH = "Read cursor Position"
	xor     bh,bh           ; BH = page number = 0
	mov     bl,24           ; BL = code window last row
	int     10h             ; DH,DL = current cursor row, column
	mov     ax,[bp+4]       ; AX = ASCII character/attribute
	xor     cx,cx           ; CH,CL = screen upper-left row, column
	cmp     al,0Bh          ; If character is '\v',
	je      pc3             ;   just move cursor
	cmp     al,0Ah          ; if character not '\n',
	jne     pc1             ;   go see if character is '\f'.

;  Have a newline.  If we are on the bottom line, we must scroll
;    the screen.  Otherwise we can simply advance the cursor.

	mov     al,1            ; AL = scroll(?) line displacement
	mov     ch,25-CODE_HT   ; CH = code window first row
	cmp     dh,bl           ; If we are on last line,
	je      pc2             ;   we need to scroll the display
	inc     dh              ; DH = next row
	jmp     pc4             ; Go to move-cursor common code

pc1:    cmp     al,0Ch          ; if character not '\f',
	jne     pc5             ;   go to code that displays it.

;  Have a formfeed.  Select page 0, clear screen (all 25 lines), and
;  move cursor to upper-left corner.

	mov     ah,05h          ; AH = "Select Active Display Page"
	xor     al,al           ; AL = page number = 0
	int     10h             ; Call BIOS to select page 0
	xor     bl,bl           ; BL = screen first row
pc2:    mov     ah,6            ; AH = "Scroll Active Page Up"
	mov     dx,24*256+79    ; DH,DL = screen last row, column
	mov     bh,[bp+5]       ; Attribute to use on filled lines
	int     10h             ; Call BIOS to scroll window/clear screen
pc3:    mov     dh,bl           ; DH = first or last row
pc4:    xor     bh,bh           ; BH = page number = 0
	xor     dl,dl           ; DL = 1st column on display
	jmp     short pc7       ; Move cursor to requested position

;  Have "normal", displayable character/attribute.

pc5:    inc     dl              ; DL = next column
pc6:    inc     cx              ; CX = 1 = number characters to display
	mov     bl,ah           ; BL = attribute for display
	mov     ah,9            ; AH = "Display Char/Attr (here)"
	int     10h             ; Call BIOS to display character
pc7:    mov     ah,02h          ; AH = "Set Cursor Position"
	int     10h             ; Call BIOS to reposition cursor
	pop     bp              ; Restore caller's frame pointer
	ret
_pc     endp

;;;    tod - return TOD (in 18.2 Hz ticks)
;;
;;   Tod() simply returns the l.s. 16 bits of the timer counter.
;      This counter (which increments about 18 times each second)
;      makes a good seed for the random number generator.

_tod    proc
	mov     ax,40h
	mov     es,ax           ; ES = Segment of BIOS data area
	mov     ax,es:[6Ch]     ; AX = BIOS TOD clock
	ret
_tod    endp

	end
