;;--------------------------------------------------------------------------
; NEWBEEP.ASM  --  redirect DOS's standard BEL to the SoundBlaster FM chips
; ver 1.0  (Tuesday March 2, 1992)
;--------------------------------------------------------------------------
; Copyright (C) 1992, Heath I. Hunnicutt
; This document may be freely distributed if no fee is assigned and the
; document including this notice is unaltered.
;--------------------------------------------------------------------------
; By: heathh@tybalt.cco.caltech.edu -or- @cco.caltech.edu  (preferred acct)
;     aka hihunn@through.ugcs.caltech.edu  (NOT preferred)
;--------------------------------------------------------------------------
; Requires: SoundBlaster sound card or compatible,
;           Turbo Assembler from Borland
;--------------------------------------------------------------------------
; Assumes: I/O address of card is based at 0220h.
;--------------------------------------------------------------------------
; Acknowledgements: Ralf Brown -- Interrupt List 29a
;                   Jeffrey S. Lee -- Programming DOCs for SB FM
;--------------------------------------------------------------------------
; Warnings/Bugs: Never checks to see if card is installed.
;--------------------------------------------------------------------------
; Legal: SoundBlaster is a trademark of Creative Labs, Inc.
;        Turbo Assembler is a trademark of Borland International.
;--------------------------------------------------------------------------

                IDEAL   ;Depends on TASM's truly ideal mode
                MODEL small

SB_Port EQU 0220h       ;Alternate SB address is 0240h
                        ;AdLib Boards apparently use 03??
Delay EQU 20000         ;Length of beep

                CODESEG ;No DATASEG for TSRs.
Begin:

;Concept: Hook the BIOS video interrupt and watch for BEL characters
;         being output.

OldVideo DD 0   ;Store address of previous Video BIOS routine


PROC NewVideo   ;This routine will soon accept Video BIOS calls
        pushf                           ;Preserve flags through cmp
        cmp ax,0E07h                    ;Is function displaying BEL character?
          je Handle_This                ; y: Jump to our BEL code
        popf                            ; n: Restore original flags...
        jmp [DWORD PTR cs:OldVideo]     ;    ...Pass function on to BIOS
Handle_This:
        popf                            ;Discard flags that were saved
        call FM_Beep                    ;Doin' da Beep...
        iret                            ;See ya.
ENDP NewVideo

l       DW 0    ;loop counter
Regs    DB 20h,40h,60h,80h,0A0h,23h,43h,63h,83h,0B0h,0E0h,0FFh
Content DB 01h,10h,0F0h,77h,98h,01h,00h,0F0h,7Bh,31h,00h
;Above values will be sent to IO address.
;Example: Register 20h will be loaded with 01h, 0B0h with 31h
;From Jeffrey S. Lee's programming documentation.

PROC FM_Beep
;------------------------------
;Produces a tone through the SB
;Preserves all registers
;------------------------------
   push ax bx cx dx                     ;Preserve these registers
        call Reset_FM                   ;Set the card up

        mov  [cs:l],0                   ;Initialize counter
Looper:
        mov  bx,(OFFSET Regs)
        add  bx,[cs:l]                  ;bx= &regs[l]
        cmp  [BYTE PTR cs:bx],0FFh      ;At end of list?
          je Done                       ; y: Stop programming card
        mov  dx,SB_Port                 ; n: Set up for port write
        mov  al,[cs:bx]                 ;al= *bx = regs[l]
        out  dx,al                      ;Write port
;After feeding a value to the SB port, we must pause while it 'swallows'
        mov  cx,6                       ;Set up for port delay
  @@D_1:
        in   al,dx                      ;Just wastin' time accurately
         loop @@D_1                     ;Repeat until well mixed

        mov  bx,(OFFSET Content)        
        add  bx,[cs:l]                  ;bx= &content[l]
        mov  dx,SB_Port+1               ;Set up for port write
        mov  al,[cs:bx]                 ;al= *bx= content[l]
        out  dx,al                      ;Write port
        mov  cx,35                      ;Waste even more time...
  @@D_2:
        in   al,dx                      ;...but do it accurately
         loop @@D_2                     ;Even though it's dumb HW design

        inc  [cs:l]                     ;Step to the next reg/content pair.
         jmp Looper                     ;Repeat
Done:
        mov  cx,Delay                   ;Another Delay, so the sound can be
        mov  dx,SB_Port                 ;heard
  @@TD_1:
        in   al,dx                      ;Independent of machine speed
        loop @@TD_1
        call Reset_FM                   ;Now, shut it back up
    pop dx cx bx ax                     ;Restore those precious registers
ret

PROC Reset_FM
;--------------------------------------------------------------------
;Writes zeros to all FM registers, resetting chips (turns off sound.)
;Dirties ax,bx,cx,dx
;--------------------------------------------------------------------
        mov  bl,0                       ;bl holds current register number
Next_Reg:
        mov  dx,SB_Port                 ;Set up for port write
        mov  al,bl                      ;To reg #bl
        out  dx,al                      ;Write Port
        mov  cx,6                       ;SB is swallowing....
  @@D_1:
        in   al,dx
         loop @@D_1                     ;....
        mov  dx,SB_Port+1               ;Now we write a value
        mov  al,0                       ; (namely, 0)
        out  dx,al                      ;to the register
        mov  cx,35                      ;Yawn....
  @@D_2:
        in   al,dx
         loop @@D_2                     ;....
        inc  bl                         ;Next register
        cmp  bl,0FFh                    ;Done?
         jne Next_Reg                   ; n: Continue
        ret                             ; y: Return
ENDP Reset_FM
ENDP FM_Beep


Start:
        mov  ax,cs                      ;Set up segments
        mov  es,ax
        mov  ds,ax
        mov  ax,3510h                   ;Get interrupt vector for Video
        int  21h
        mov  [WORD PTR OldVideo],bx     ;Store it at OldVideo
        mov  ax,es
        mov  [WORD PTR OldVideo+2],ax

        mov  dx,OFFSET NewVideo         ;ds:dx -> NewVideo
        mov  ax,2510h                   ;Set new interrupt vector
        int  21h
        mov  dx,((Start-Begin)+100h+15)/16      ;Calculate memory size..
        mov  ax,3100h                   ;Termiante Stay Resident
        int  21h
ENDS
END Start                               ;Execution begins at Start: label
