📄 llsnd.asm
字号:
CMP CX,37D ;check for valid frequency (37 <= CX <= 32767)
JGE FRQOK ; Brif in range
MOV DL,3 ; Error code in AL (if needed)
INC CX ; If zero, then set it to some high value
LOOP QUE_ERR2 ; Brif not zero - Error in freq value
FRQ_0:
NOT CX ; CX = -1 ; Play an inaudible frequency
FRQOK: ; Convert frequency to tics
MOV AX,34DCH ; 1.193180 MHz
MOV DX,12H
DIV CX ; [AX] = COUNT = CLOCK/FREQUENCY
CLC ; CF = 0
MOV WORD PTR [SI+3],AX ; temporarily queue the frequency
MOV ES,b$SNQueSeg ;music queue has its own buffer segment
MOV CX,QUE_BYTES ; 5 bytes to be queued
CLI ;CLI,bcos this is an indivisable operation
QUETHEM: ;[si] = address of QBLK
.erre ID_SSEQDS ; Assert SS = DS
LODSB ; [al] = byte to be queued
cCALL B$QUE ; queue the byte
LOOP QUETHEM
MOV AL,BYTE PTR QBLK ; get the note/rest (1/0)
CBW ; AH = 0
; For rest, the following additions are NOPs
ADD [BX].QUNOTE,AX ; Suitably update # of notes in queue
ADD b$NOTES,AX ; and total note count
CLC ; Clear carry to indicate no error
QUERET:
STI ;restore interrupts
cEnd ; End of B$QNOTE
QUE_ERR1:
MOV DL,1 ; error code in [al]
QUE_ERR2:
STC ; indicate error
JMP SHORT QUERET
PAGE
;***
;B$QUERY
;
;PURPOSE:
; Checks to see if any voices are active
;
;ALGORITHM:
; If the timer is turned on of off frequently, then an irritating
; 'click' sound is heard between two play statements. In order to
; avoid this 'click', when (b$NOTES | b$SNDTIM) > 2 then say that
; music is inactive. This lets Hi-Level to queue the next note
; and the speaker is not turned off in-between.
;
;ENTRY:
; None
;
;EXIT:
; [AL] = 0 if voice is not currently active
; FF if voice is active
;
;MODIFIED:
; None
;
;****
cProc B$QUERY,<NEAR>
cBegin
MOV AX,b$NOTES ; Get number of notes (may be zero)
OR AX,b$SNDTIM ; Add any music-ticks still left
CMP AX,2 ; If zero or 1 then zero else -1
CMC ; CF =1 if AX > 2
SBB AL,AL ; AL = -1 if speaker active
CLC ; No error for this call
QUERY_RET: ; Just exit...
cEnd ; End of B$QUERY
PAGE
;***
;B$TICTOC
;
;PURPOSE:
; The timer interrupt, vectors here BEFORE updating the
; time of the day. This routine helps support the
; Music routines. B$TICTOC keeps decrementing the duration
; count until it becomes zero at which point it
; either gets the next sound from the sound
; queue or else it turns off the voice by calling B$QFLUSH.
;ENTRY:
; None
;
;EXIT:
; None
;
;MODIFIED:
; None
;
;****
cProc B$TICTOC,<NEAR>
cBegin
STI ; Enable interrupts now itself - there
; is no chance of another timer intterupt
; occurring since EOI is sent only later.
PUSH AX
PUSH BX
PUSH CX ; Save temp registers AX,BX,CX
PUSH DX ; Save this also
PUSH DS
PUSH ES
MOV DS,CS:b$BASDSG ;get BASIC's data seg
MOV ES,b$SNQueSeg ;point to music buffer
XOR AX,AX ; Get ready for some zero comparisons
CMP b$SNDTIM,AX ; has SND_TIM zeroed out?
JZ NXTONE ;Brif so to get next sound
DEC b$SNDTIM ; SND_TIM - 1
JNZ CLK_TIC
NXTONE:
MOV BX,OFFSET DGROUP:b$SNDQCB ; BX = address of music queue
CMP [BX].QUENUM,AX ; Is the queue empty
JE TURN_OFF ; If so, then turn music off
cCALL B$DQUE ; Get first entry in queue
OR AL,AL ; Check if it is note or rest
JZ GETSND1 ; Brif rest : no need to check play trap
; AL = 1 for note
CMP b$PLENBL,AL ; Check if play trapping is enabled
JNE GETS1 ; Brif disabled
MOV AX,b$PLYCNT ; Get the number of notes set for trapping
CMP [BX].QUNOTE,AX ; and compare it with current # of notes
JNE GETS1 ; Brif event has not occurred
MOV b$PLAFLG,1 ; Else set the flag
MOV AL,PLAOFF ; AL = trap number for B$TrapEvent
CALL [b$pTrapEvent] ; set the global flag if events linked in
GETS1:
DEC [BX].QUNOTE ; One more note has been played
DEC b$NOTES ; update overall count also
GETSND1:
cCALL B$DQUE ; Get the duration LSB
XCHG AX,CX ; CL = LSB
cCALL B$DQUE ; Get the duration MSB
MOV CH,AL ; CH = MSB
MOV b$SNDTIM,CX
cCALL B$DQUE ; Get the frequency LSB
OUT TIMER2,AL ; & set the timer counter Lo-byte
; 8253 will wait until hi-byte is loaded.
cCALL B$DQUE ; Get the frequency MSB
OUT TIMER2,AL ; & set the timer counter Hi-byte
JMP SHORT CLK_TIC ; All done
TURN_OFF: ; Turn off the music
PUSH DS ; B$FLUSH requires ES = DS
POP ES
cCALL B$QFLUSH
CLK_TIC:
SUB CLK_TICS,CLK_DELTA ; clock tick -= CLK_DELTA
; wait for 32 interrupts to invoke ROMCLK
POP ES
POP DS
POP DX
POP CX
POP BX
JNZ CLKTIX ;don't do ROM clock INT now
POP AX
INT ROMCLK ;to ROM clock INT service routine
IRET
CLKTIX:
MOV AL,EOI ; send End-of-Interrupt
OUT INTA0,AL ; to 8259
POP AX
IRET
cEnd <nogen> ; End of B$TICTOC
PAGE
;***
;B$QSTART
;
;PURPOSE:
; This routine starts the music. It does the following
; things:
; 1. Change the interrupt vector to point at our handler
; 2. Modify timer2 to interrupt 32 times faster
; 3. Turn on the speaker and start timer2 only if timer2
; is active.
;
;ENTRY:
; None
;
;EXIT:
; None
;
;MODIFIED:
; None
;
;****
cProc B$QSTART,<NEAR>,<AX>
cBegin
MOV AH,01 ; Comes useful
; DON'T reset the counter every time: only once at program load. This is
; a modulo counter that should be retained.
; MOV CLK_TICS,CLK_DELTA ; Reset the counter
PUSHF ; Save caller's IF
CLI
IN AL,MSKREG ;get IMR into [AL]
OR AL,AH ;mask out timer interrupt
PAUSE ;make sure instruction fetch has occurred
OUT MSKREG,AL ;write mask to IMR
XCHG b$MUSIC,AH ; get b$MUSIC flag and set it to 1
OR AH,AH ; Check if music is already on
JNZ STRTMXT ; Brif so - timer is already changed
PUSH DS
PUSH CS
POP DS ;[DS] := [CS]
SETVEC CLKINT/4,B$TICTOC ;modify timer2 interrupt vector
POP DS
MOV AL,0 ;modify timer2 to interrupt
OUT TIMER0,AL ;at 32 times the
MOV AL,8H ;original
PAUSE ;make sure instruction fetch has occurred
OUT TIMER0,AL ;rate
MOV AL,SQUARE ;else set timer2 in square
PAUSE ;make sure instruction fetch has occurred
OUT TMRCMD,AL ;wave mode
PAUSE ;make sure instruction fetch has occurred
IN AL,SPEAKER ;turn on the
OR AL,SPKRON ;speaker
PAUSE ;make sure instruction fetch has occurred
OUT SPEAKER,AL
PAUSE ;make sure instruction fetch has occurred
STRTMXT:
IN AL,MSKREG ;get IMR into [AL]
AND AL,0FEH ;unmask timer interrupt
; CF = 0 to indicate no error
PAUSE ;make sure instruction fetch has occurred
OUT MSKREG,AL ;write mask to IMR
POPFF ; Restore caller's IF
cEnd ; End of B$QSTART
;***
;B$QFLUSH
;
;PURPOSE:
; This routine stops music, initializes the music block, resets the timer
; count, restores the original timer ISR and clears the flag b$SNDTIM
;
;ENTRY:
; None
;
;EXIT:
; None
;
;MODIFIED:
; None
;
;****
;
cProc B$QFLUSH,<NEAR>,<AX>
cBegin
PUSHF ; Save caller's IF
CLI
IN AL,MSKREG ; get IMR into [AL]
OR AL,01H ; mask out timer interrupt
PAUSE ; make sure instruction fetch has occurred
OUT MSKREG,AL ; write mask to IMR
cCALL B$SNDOFF
JMP STRTMXT ; Share code
cEnd <nogen> ; End of B$QFLUSH
; B$SNDOFF moved here from LLQUE.ASM to increase /O modularity
; revision [6] applies to the entire routine:
;***
;B$SNDOFF - Turn off Sound and Clean Up
;OEM-interface routine
;
;Purpose:
; This routine stops music and flushes the music queue(s).
; It also performs all needed functions to disable sound.
; B$SNDOFF is called before the RUN command is executed
; and at program termination.
;
; The sound queue is allocated and deallocated by the
; runtime, so this routine need not worry about it. The
; queue will exist when this routine is called.
;
;Entry:
; None
;
;Exit:
; None
;
;Uses:
; Per Convention
;
;Preserves:
; AX, BX, CX, DX
;
;Exceptions:
; None.
;***************************************************************************
DB "<LEO>" ; This is used via a separate tool to
; actually modify the .EXE file to stub
; out B$SNDOFF. This is necessary for
; profiling, as B$SNDOFF modifies the
; timer hook, as does the profiler.
; (chosen string pure vanity on Leo's part)
cProc B$SNDOFF, <PUBLIC,NEAR>,<AX,BX>
cBegin
XOR AX,AX ;zero out [AX]
MOV b$NOTES,AX ; zero out b$NOTES
MOV b$SNDTIM,AX ; SND_TIM = 0
MOV b$MUSIC,AL ;music is currently OFF
MOV BX,OFFSET DGROUP:b$SNDQCB ; Get music block offset
cCALL B$INIQUE ; init music queue at zero offset
IN AL,SPEAKER
AND AL,NOT SPKRON ;turn off speaker
PAUSE ;ensure instruction fetch has occurred
OUT SPEAKER,AL
XOR BX,BX ; CF = 0 to indicate no error
PUSH DS
MOV DS,BX ; [DS] = interrupt vectors segment
PUSHF ; Save flags
CLI ;make sure interrupts are off
SVINT DS:CLKINT,DS:CLKVEC
POPFF ; Restore flags
POP DS
XCHG AX,BX ; AX = 0
OUT TIMER0,AL ;restore timer2 count
PAUSE ;ensure instruction fetch has occurred
OUT TIMER0,AL
cEnd ; End of B$SNDOFF
sEnd EV_TEXT
END
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -