📄 gwplays.asm
字号:
DB "C"
DW OFFSET PLYNOT
DB "D"
DW OFFSET PLYNOT
DB "E"
DW OFFSET PLYNOT
DB "F"
DW OFFSET PLYNOT
DB "G"
DW OFFSET PLYNOT
DB "M" ;Music Meta Command
DW OFFSET B$PLYMET
; DB "Q" ;Envelope Subcommand Lead In
; DW OFFSET QCMNDS
DB "N"+128 ;PLAY NUMERIC NOTE
DW OFFSET PLYNUM
DB "O"+128 ;OCTAVE
DW OFFSET POCTAV
DB "P"+128 ;PAUSE
DW OFFSET PPAUSE
DB "T"+128 ;TEMPO
DW OFFSET PTEMPO
DB "L"+128 ;LENGTH
DW OFFSET PLYLEN
; DB "V"+128 ;Volume
; DW OFFSET PVOLUM
DB "X" ;EXECUTE STRING
; DW OFFSET B$MCLXEQ ; substring handler in MCLPRC
DW OFFSET B$PLYXEQ ; Substring handler is local
DB "<" ;Decrement Octave
DW OFFSET POCTAD
DB ">" ;Increment Octave
DW OFFSET POCTAI
DB 00 ;END OF TABLE
; This table contains the allowed subcommands for the Q command
; in the Music Macro Language.
; This table has been removed as part of PC Jr code removal
;B$PLYXEQ
;Purpose:
; Reimplemented as part of revision [5]
; This routine is dispatched to by the X command in the PLAY statement.
; It is equivalent to a macro-language subroutine call, in that it
; specifies a variable which is to be inserted in the Macro String. It:
;
; 1) Calls B$GETSTKC to check for enought stack space.
; 2) Calls B$SCNVAR to get string descriptor in the FAC
; 3) Calls B$PUTSTR to stack the current string pointer & length,
; 4) Sets B$MCLPTR & B$MCLLEN to point to new nested string,
; 5) Returns to its caller (presumably PLAY (via B$MACCMD)
cProc B$PLYXEQ,<NEAR>
cBegin
MOV CL,100 ; Get size for stack check
CALL B$GETSTKC ; Check the stack for enough room
CALL B$SCNVAR ; Get the VARPTR$ descriptor offset in FAC
CMP [b$VTYP],VT_SD ; Test if type was a string
JNE PLYERR ; Brif not, signal an error
cCALL B$PUTSTR ; Put B$MCLPTR & B$MCLLEN on local stack
MOV BX,OFFSET DGROUP:B$AC ; Get FAC for descriptor
CALL B$SETMCL ; Set new values of B$MCLPTR & B$MCLLEN
CLC ; To indicate to use new values
cEnd ; End of B$PLYXEQ
PLYERR: ; Indicate type mismatch error
JMP B$ERR_TM
; Decrement the current octave number
POCTAD: CMP B$OCTAVE,LOW 0
JZ PLYRET
DEC B$OCTAVE ; octave -1
RET
; Increment the current octave number
POCTAI: cmp B$OCTAVE,low 6
JNB PLYRET
CLC
INC B$OCTAVE ; octave +1
RET
; Set the volume level
; Volume support code has been deleted from here
; Set the note length
PLYLEN:
JNB PLGOFC ;ERROR IF NO ARG
CMP DL,LOW 65 ;ALLOW ONLY UP TO 64
JNB PLGOFC ;FC ERROR IF TOO BIG
OR DL,DL ;DON'T ALLOW ZERO
JZ PLGOFC ;FC ERROR IF ZERO
MOV B$NOTELN,DL ; store note length
RET
; Set the play tempo
PTEMPO:
CMP DL,LOW 32 ;ALLOW ONLY 32 - 255
JB PLGOFC ;FC ERROR IF TOO SMALL
mov B$BEATS,dl ; store beats per minute
CLC
RET
; Play a rest (Pause command)
PPAUSE:
JNB PLGOFC ;ERROR IF NO ARG
XOR CX,CX ;PASS FREQ OF 0
CMP DL,LOW 65 ;ALLOW ONLY 1-64
JNB PLGOFC ;FC ERROR IF TOO BIG
OR DL,DL ;SEE IF ZERO
JZ PLYRET ;RETURN IF SO - NO PAUSE
JMP PPAUS2 ;[DX]=PAUSE LENGTH
; Set the current octave number
POCTAV:
JNB PLGOFC ;ERROR IF NO ARG
CMP DL,LOW 7 ;ALLOW ONLY OCTAVES 0..6
JNB PLGOFC ;FC ERROR IF TO BIG
mov B$OCTAVE,dl
CLC
PLYRET: RET
; Play a particular note by note number
PLYNUM:
JNB PLGOFC ;ERROR IF NO ARG
MOV AL,DL ;GET NOTE NUMBER INTO [AL]
OR AL,AL ;SEE IF ZERO (PAUSE)
JZ PLYNO3 ;DO THE PAUSE
CMP AL,LOW 85 ;ALLOW ONLY 0..84
JNB PLGOFC ;FC ERROR IF TOO BIG
CBW ;CLEAR HI BYTE FOR DIVIDE
DEC AX ;MAP TO 0..83
MOV DL,LOW 12 ;DIVIDE BY 12
DIV DL
MOV DH,AL ;OCTAVE TO [DH]
MOV AL,AH ;NOTE NUMBER IS REMAINDER
INC AL ;ADD ONE
ADD AL,AL ;DOUBLE TO MAKE INDEX
JMP SHORT PLYNU3 ;PLAY NOTE [AL], OCTAVE [DH]
PLGOFC: JMP B$ERR_FC ; GIVE FUNCTION CALL ERROR
; Play a note by name
PLYNOT: SUB CL,LOW "A"-1 ;MAP TO 1..7
ADD CL,CL ;MAP TO 2..14 (THIS ASSUMES SHARP)
CALL B$FETCHR ;GET NEXT CHARACTER
JZ PLYNO2 ;END OF STRING - NO SHARP OR FLAT
CMP AL,LOW "#" ;CHECK FOR POSSIBLE SHARP
JZ PLYSHP ;SHARP IT THEN
CMP AL,LOW "+" ;"+" ALSO MEANS SHARP
JZ PLYSHP
CMP AL,LOW "-" ;"-" MEANS FLAT
JZ PLYFLT
CALL B$DECFET ;PUT CHAR BACK IN STRING.
JMP SHORT PLYNO2 ;TREAT AS UNMODIFIED NOTE.
PLYFLT: DEC CL ;DECREMENT TWICE TO FLAT IT
PLYNO2: DEC CL ;MAP BACK TO UNSHARPED
PLYSHP: MOV AL,CL ;INTO [AL] FOR XLAT
MOV BX,OFFSET NOTXLT ;POINT TO TRANSLATE TABLE
XLAT BYTE PTR CS:[BX] ; TRANSLATE INTO NOTE TABLE INDEX
OR AL,AL ;SEE IF LEGAL NOTE
JS PLGOFC ;NOTE'S OK IF NOT .GT. 127
; ENTER HERE WITH NOTE TO PLAY IN [AL]
; NOTE 0 IS PAUSE, 2,4,6,8..10,12 ARE A-G AND FRIENDS.
PLYNO3:
mov dh,B$OCTAVE ; get B$OCTAVE into [dh] for later math
PLYNU3:
PUSH AX ;Save Note
PUSH DX ; Save Octave
MOV AL,B$NOTELN
MOV B$NOTE1L,AL ; one note duration = note length
CALL B$FETCHR
JZ PLYNU4 ;Brif end of string
CALL B$VALSC2 ;See if possible number
CMP DL,LOW 65 ;If was .gt. 64
JNB PLGOFC ; then error
OR DL,DL ;Any Length?
JZ PLYNU4 ;Brif not, just do note
MOV B$NOTE1L,DL ; store duration for this note
PLYNU4:
POP DX ;Get Octave
POP AX ;Restore Note
CBW ;FILL [AH] WITH ZEROS
MOV BX,AX ;TRANSFER TO BX FOR INDEXING
OR BX,BX ;SEE IF PAUSE (NOTE # 0)
JZ PLYNO4 ;IF PAUSE, PASS [BX]=0
MOV BX,WORD PTR CS:NOTTAB-2[BX] ;FETCH FREQUENCY
MOV CL,LOW 6 ;CALCULATE 6-OCTAVE
SUB CL,DH ;FOR # OF TIMES TO SHIFT FREQ.
SHR BX,CL ;DIVIDE BY 2^(6-OCTAVE)
ADC BX,0 ;ADD IN CARRY TO ROUND UP
PLYNO4:
MOV CX,BX ;FREQUENCY INTO [CX] FOR DONOTE
MOV DL,B$NOTE1L ; get this note's length
PPAUS2:
MOV AL,B$BEATS ; GET BEATS PER UNIT TIME
MUL DL ;CALC NOTE LENGTH * B$BEATS
PUSH CX ;SAVE [CX] WHILE WE DIVIDE
MOV CX,AX ;CALC TIME CONST/(B$BEATS * NOTE LENGTH)
MOV DX,1 ;96000 (4*60*400) is
MOV AX,73400O ;SPECIAL TIME CONSTANT
DIV CX
POP CX ;RESTORE FREQUENCY
OR AX,AX ;IF DURATION IS ZERO, GET OUT.
JZ PLYNO8
PUSH CX ;Save Freq
PLYDOT:
MOV CX,AX ; Copy of duration for doted notes
PLYDOT1:
PUSH AX ; Save duration
PUSH CX ; Save the current dot duration
CALL B$FETCHR
JZ PLYDOX ; Brif EOS
CMP AL,LOW "." ; Note duration extender?
JNZ PLYDO2 ; Brif not
POP CX ; Get last dot duration
POP AX ; Get current duration
SHR CX,1 ; This dot = previous dot / 2
ADD AX,CX ; Update the new duration
JNB PLYDOT1 ; Loop if not overflow
JMP B$ERR_FC ; else complain.... (wont return)
PLYDO2:
CALL B$DECFET ; Put char back
PLYDOX:
POP AX ; Trash the dot duration
POP AX ; Duration
POP CX ; Get freq
PUSH AX ;Save Duration
PUSH CX ;Save Frequency
JCXZ PLYNO7 ;Brif Pause
CMP B$MSCALE,LOW 1
JZ PLYNO7 ;Brif Legatto
MOV CL,B$MSCALE ; using scale for shift count
MOV BX,3 ;Stacatto multiplier
CMP CL,LOW 2
JZ PLYNO6 ;Brif Stacatto
MOV BX,7 ; else Normal
PLYNO6:
MUL BX ;Duration * 7/8 or 3/4
SHR AX,CL
OR AX,AX
JNZ PLYNO7 ;If zero
INC AX ; then make 1
; Have all of the parameters for this note. Send the info to the OEM
; to queue the note.
; Because a note is sent via two separate commands (one for the first,
; sound generating, part of the note, and a second one for the inter-
; note pause) it is possible for the queue to overflow in the middle of
; the note. It isn't possible to simply wait for space to become available
; in the queue, because there is no guarantee that the queue is being
; emptied. So to handle this case the following things happen:
; If the queue overflows on either half of the note, the carry
; flag is returned set as a signal to the music string B$PARSER that
; the present command needs to be rescanned the next time around
; If the first half of the note is sent successfully, a flag is
; set (B$NOTFLG[SI]) indicating that it has been sent. When the
; second half of the note is sent successfully, then this flag is
; cleared indicating that the entire note has been sent. Before
; sending the first half, it is necessary to check the flag to see
; if this part has already been passed to the OEM on a previous
; pass through the B$PARSER.
PLYNO7:
POP CX ;Get Freq
CMP B$NOTFLG,LOW 0 ; has the first part of this note already
;been queued
JNZ PLYN7B ;If so, don't send it again
cCALL B$SNDNOT ;Send note
JNB PLYN7A ;If no queue overflowed, continue
POP AX ;If queue overflowed, get out, not even
JMP SHORT PLYNO9 ; the first part of note was queued.
PLYN7A:
mov B$NOTFLG,low 1 ; set flag to say that note has been sent
PLYN7B:
; Now send an inter-note pause for this note (if required)
POP AX ;Get back original duration
JCXZ PLYNO8 ;Brif Pause
CMP B$MSCALE,LOW 1
JZ PLYNO8 ;Brif Legatto
MOV CL,B$MSCALE ; scale factor for current mode (1/8|1/4)
SHR AX,CL ;divide note duration by scale factor
OR AX,AX ;Pause = 0?
JZ PLYNO8 ;Don't send anything if so.
cCALL B$SNDPSN ;Send the rest
JB PLYNO9 ;If queue overflowed, don't reset
; flag for this note
PLYNO8:
MOV B$NOTFLG,LOW 0 ; clear the flag to indicate that the
;complete note has been queued
PLYNO9:
RET ; else do nothing
;***
; B$SNDNOT,B$SNDPSN
; Purpose:
; Send the specified note information to the OEM routine to be queued.
; B$SNDNOT will queue the first part of a note.
; B$SNDPSN will queue the second (inter-note pause) part of a note.
; Entry:
; AX - Duration
; CX - Frequency
; Exit:
; none
; Modifies:
; SI, DI, CX preserved.
; The registers must be set up as follows for the call to B$DONOTE
; [AL] = B$DONOTE function code number (1 for note, 0 for inter-note pause)
; [AH] = Voice
; [BX] = Volume
; [CX] = FREQUENCY IN HERTZ
; [DX] = DURATION IN CLOCK TICKS (1/18.2 SECONDS)
;****
cProc B$SNDNOT,<NEAR>
cBegin
XCHG DX,AX ; B$DONOTE wants duration in DX
; MOV BX,B$VCEVOL ; Volume into bx
MOV AL,LOW QUENOT ;B$DONOTE function code in AL
; Test if this is the first time a note has been parsed in this play
; statement, and if so, queue sync marks in all voice queues.
CMP MQUEFL,LOW 0
JNE SDNT20 ;branch if cmds have already been queued
; for this PLAY statement
MOV MQUEFL,LOW 1
JMP SHORT SDNT20
cEnd <nogen> ; End of B$SNDNOT
; Queue an internote pause (rest) for the current note
cProc B$SNDPSN,<NEAR>
cBegin
XCHG DX,AX ; B$DONOTE wants duration in DX
MOV AL,LOW QUERST ;B$DONOTE function code in AL
; Send the instruction to the OEM, and test for any error codes coming
; back
SDNT20:
cCALL B$DONOTE ; PLAY THE NOTE
JNB MQD90 ;If no error occured, then go on
CMP AL,LOW 1 ;Test for overflow on this voice
JNE PLYMER ;If not queue full, then report error
MQD80:
STC ;Set error return state
MQD90:
cEnd ; End of B$SNDPSN
; Function call error occured while processing Music Meta command.
PLYMER:
JMP B$ERR_FC ; wont return
; B$PLYMET - Process Music Meta Commands.
cProc B$PLYMET,<NEAR>
cBegin
CALL B$FETCHZ ;Get Meta action or error
MOV CL,LOW 1 ;Factor for Legatto (1/1)
CMP AL,LOW "L"
JZ PLYDUR ;Brif Legatto (Full note)
INC CL ;Factor for Stecatto (3/4)
CMP AL,LOW "S"
JZ PLYDUR ;Brif Stecatto (3/4)
INC CL ;Factor for Normal (7/8)
CMP AL,LOW "N"
JZ PLYDUR ;Brif Normal (7/8)
XOR CL,CL
CMP AL,LOW "F"
JZ PLYMOD ;Brif Foreground Music
DEC CL
CMP AL,LOW "B"
JNZ PLYMER ;Brif not Background Music
PLYMOD:
MOV [B$MMODE],CL ;Store Music Mode (0=FG, 255=BG)
JMP SHORT PLYMET_RET
PLYDUR:
MOV B$MSCALE,CL ; store duration scaling factor
PLYMET_RET: ; Common exit point
cEnd ; End of B$PLYMET
; This is the executive for dispatching the Q command. It sets
; up the Macro command processor table to point to the Q subcommand
; table, and then uses the macro command processor to dispatch to
; the appropriate routine to process the subcommand.
; Envelope support code has been deleted from here
; TABLE OF INDEXES INTO NOTTAB FOR EACH NOTE
; VALUE OF 255 MEANS NOTE NOT ALLOWED.
NOTXLT LABEL BYTE
DB 9*2 ;A- (G#)
DB 10*2 ;A
DB 11*2 ;A#
DB 12*2 ;B
DB 255 ;NO C- OR B#
DB 1*2 ;C
DB 2*2 ;C#
DB 3*2 ;D
DB 4*2 ;D#
DB 5*2 ;E
DB 255 ;NO E# OR F-
DB 6*2 ;F
DB 7*2 ;F#
DB 8*2 ;G
DB 9*2 ;G#
; TABLE OF NOTE FREQUENCIES
; THESE ARE THE FREQUENCIES IN HERTZ OF THE TOP OCTAVE (6)
; DIVIDED DOWN BY POWERS OF TWO TO GET ALL OTHER OCTAVES
NOTTAB LABEL WORD
DW 4186 ;C
DW 4435 ;C#
DW 4699 ;D
DW 4978 ;D#
DW 5274 ;E
DW 5588 ;F
DW 5920 ;F#
DW 6272 ;G
DW 6645 ;G#
DW 7040 ;A
DW 7459 ;A#
DW 7902 ;B
;***
;B$QSYNC
;Purpose:
; Output a Syncronization Byte to all voices
;Input:
; none
;Output:
; A SYNC byte is put in voice [VOICEN]'s queue
;Modifies:
; none
;****
; SYNC byte is output only if multi-voice support is present.
; For single-voice music it is not necessary and hence not output.
sEnd SN_TEXT
END
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -