'===========================================================================
' Subject: OUTPUT SOUND FROM PC SPEAKER       Date: 01-28-97 (19:59)       
'  Author: Brian Mclaughlin                   Code: ASM                    
'  Origin: comp.lang.basic.misc             Packet: ASMCODE.ABC
'===========================================================================
; SPEAKER.ASM  - assemble using MASM 5.1 or compatible assembler
;              - for use with QB4.x only
;              - written by Brian McLaughlin. Released to public domain.
;
; This SUB works a lot like BASIC's SOUND statement, but it won't pull 
; BASIC's floating point math library into your EXE file, saving you 9K.
;
; You pass it two integers; the first is the frequency you want
; (in hertz) -- this number must be 40 or greater. The second integer
; is how many timer ticks (1/18th seconds each) you would like the speaker
; to sound. The speaker is initialized only at the first CALL.
; If the duration requested is a negative number or zero, no sound is made.
;
; It is especially good for making beeps less annoying than BEEP makes.
;
; Declare it:   DECLARE SUB Speaker (Freq%, Duration%)
;
; Example:
;          FOR Freq% = 50 TO 1000 STEP 50
;            Speaker Freq%, 1
;          NEXT Freq%

.MODEL MEDIUM, BASIC

.CODE
        PortReady   DB 0        ; put this data in code segment
        ClockFreq   DD 1193180  ; put this data in code segment

Speaker PROC USES ES, Freq:WORD, Ticks:WORD
        Mov BX, Freq    
        Mov CX, [BX]            ; CX = requested frequency in hertz
        Cmp CX, 40              ; trap any frequency below 40
        Jl  Exit                ; Jl because CX is a signed integer
        Mov AL, PortReady       ; let's see if this is the first call
        Cmp AL, 0               ; if it is, PortReady will be zero
        Jz  SetPort
PortSet:
        Mov BX, Offset ClockFreq ; to get the period you must divide
        Mov AX, CS:[BX]          ; Freq by the constant 1193180, so ...
        Mov DX, CS:[BX+2]       ; now DX:AX = 1193180
        Div CX                  ; this puts the period in AX
        Out 42h, AL             ; send it as two consecutive bytes
        Mov AL, AH
        Out 42h, AL
        In  AL, 61h             ; read timer port B
        Mov CL, AL              ; save it
        Or  AL, 3               ; toggle speaker bits on
        Out 61h, AL             ; and send it back
        Xor AX, AX
        Mov ES, AX
        Mov BX, Ticks           
        Mov AX, [BX]            ; AX = requested number of ticks
        Cmp AX, 1               ; see if Ticks requested were < 1
        Jl  SkipLoop            ; remember AX is a signed integer
        Mov DX, ES:[046Ch]      ; peek at low word of timer tick count
        Add AX, DX
WaitLoop:
        Mov DX, ES:[046Ch]      ; peek at low word of timer tick count again
        Cmp AX, DX
        Ja  WaitLoop            ; as long as AX > DX, keep looping
SkipLoop:
        Mov AL, CL              ; restore old port B setting (speaker off)
        Out 61h, AL
Exit:
        Ret
SetPort:
        Mov AL, 182
        Mov PortReady, AL
        Out 43h, AL
        Jmp SHORT PortSet
Speaker ENDP
        END
