gwplays.asm
资源名称:DOS系统的源代码.rar [点击查看]
上传用户:xiaoan1112
上传日期:2013-04-11
资源大小:19621k
文件大小:26k
源码类别:
操作系统开发
开发平台:
Visual C++
- TITLE GWPLAYS - GW BASIC 2.0 Multi Voice Play
- ;***
- ; GWPLAYS - GW BASIC 2.0 Multi Voice Play
- ;
- ; Copyright <C> 1986 - 1988, Microsoft Corporation
- ;
- ;Purpose:
- ; Multi Voice Play statement processor
- ;
- ; BASIC Syntax mapping to included runtime entry points:
- ;
- ; - PLAY Statement:
- ;
- ; PLAY string
- ; |
- ; B$SPLY
- ;
- ; NOTE: the more advanced syntax of 'PLAY string, string, ...' (i.e.
- ; multi-voiced PLAY) will be accepted by the compiler. For all but the
- ; last parameter (which is passed to B$SPLY), defaulted parameters will
- ; generate a call to B$PL1, and specified parameters get passed to B$PL0
- ; both of these, in turn, just generate an Advanced Feature error.
- ; NOTE 2: the above comment doesn't apply to bascom 30. The compiler
- ; will not generate calls to B$PL0 and B$PL1. They will be flagged as
- ; syntax errors. B$PL0 and B$PL1 will not be present for BASCOM 30.
- ; The code to support multivoice play will be left for future reference,
- ; but it will need to be changed to the new style interface (stack based)
- ; if a multi voice version is ever produced.
- ;
- ;******************************************************************************
- INCLUDE switch.inc ; switch file [new]
- INCLUDE rmacros.inc ; Runtime Macro Defintions
- USESEG _DATA
- USESEG _BSS
- USESEG SN_TEXT
- INCLUDE seg.inc
- INCLUDE ibmunv.inc
- INCLUDE rtps.inc
- INCLUDE idmac.inc
- INCLUDE string.inc
- .RADIX 10
- ; Two new macros Alloc & DeclareB/DeclareW are defined here.
- ; The main purpose of Declare macro is to allocate given number of
- ; bytes (static variable) and initialize it to the specified value.
- ; Additionally, it declares an EQUate for the specified variable
- ; for automatic indexing if FS_MVOICE is TRUE. The index register
- ; assumed is SI. Actually, the variable defined has a trailing underscore
- ; and the indexed EQUate is identical to the name supplied to this macro.
- ; Alloc is just a helper macro.
- Alloc MACRO v,nm,init,siz
- static&v <nm&_>,<init>,<siz>
- nm EQU nm&_
- ENDM
- DeclareB MACRO nm,init,siz
- Alloc B,<nm>,<init>,<siz>
- ENDM
- DeclareW MACRO nm,init,siz
- Alloc W,<nm>,<init>,<siz>
- ENDM
- sBegin _DATA
- externW B$AC
- sEnd
- sBegin _BSS
- externW B$MCLTAB ;defined in GWDATA.ASM
- externW B$MCLLEN ;defined in GWDATA.ASM
- externW B$MCLPTR ;defined in GWDATA.ASM
- externB B$MMODE ;defined in GWINI.ASM
- externB B$BEATS ;defined in GWINI.ASM
- externB B$NOTE1L ;defined in GWINI.ASM
- externB B$NOTELN ;defined in GWINI.ASM
- externB B$NOTFLG ;defined in GWINI.ASM
- externB B$MSCALE ;defined in GWINI.ASM
- externB B$OCTAVE ;defined in GWINI.ASM
- externB b$VTYP
- externW b$curlevel ;current program level
- DeclareB MQUEFL,?,1
- DeclareB PLYPRS,?,1
- DeclareW STRDSC,?,NUM_VOICES
- DeclareW VSTACK,?,<NUM_VOICES*NUM_VSTACK>
- DeclareW VSTBAS,?,NUM_VOICES
- DeclareW VSTOFF,?,NUM_VOICES
- sEnd _BSS
- assumes CS,SN_TEXT
- sBegin SN_TEXT
- ; externNP B$MCLXEQ ;execute substring routine
- externNP B$DONOTE ;oem routine
- externNP B$STRTSD
- externNP B$SNDWAT
- externNP B$ERR_FC
- externNP B$ERR_OM
- externNP B$ERR_TM ; Type mismatch error
- externNP B$SETMCL
- externNP B$MACCMD
- externNP B$FETCHR
- externNP B$DECFET
- externNP B$FETCHZ
- externNP B$VALSC2
- externNP B$BREAK_CHK
- externNP B$GETSTKC ; Check stack
- externNP B$SCNVAR ; get descriptor from VARPTR$
- externNP B$STDALCALLTMP ; **** liberate temporaries ****
- ;PLAY Statement
- ;Syntax:
- ; PLAY ON
- ; PLAY OFF
- ; PLAY STOP
- ; PLAY [A$] [, [B$] [, [C$]]] - multi-voice play
- ;PLAY is made up of a forground task and a background task. A queue exists
- ;for each voice through which the forground task passes the background task
- ;Commands and Syncronization information. One SYNC byte is placed in each
- ;voice queue at the start of each PLAY statement. All commands which are
- ;produced by the PLAY statement follow the SYNC byte in the queue.
- ;When the background task encounters the SYNC byte, it disables that voice
- ;until it has received a SYNC byte for each voice and PLYBGC .GT. 0. This
- ;mechanism insures that all voices for a play statement will begin at
- ;the same time, even if one of the voices in the previous play statement
- ;was shorter than the rest.
- ;A String-Pointer stack is associated with each voice to allow each voice
- ;string to include (nest) other voice strings via the X macro-language
- ;command.
- ;PLAY Algorithm:
- ; Initialize a str-ptr stack for every voice.
- ; Parse n strings (saving ptrs to string temps in STRDSC(i)).
- ; For each voice,
- ; Put SYNC byte at start of each voice queue
- ; Release Temp string desc and push str ptr&len to voice's str-ptr stack.
- ; For each voice,
- ;1) Fill each queue as far as possible (till end-of-string or end-of-queue)
- ; if 1st pass
- ; B$STRTSD (start clock int routine, all voices)
- ; if no voices active and all strings are not empty, B$STRTSD(all voices)
- ; (This is a safety valve to insure BASIC will never hang during PLAY)
- ; If all strings have not been completely consumed,
- ; goto step 1
- ; If Music Forground, wait till all voice activity halts
- PLAYS:
- ; Entry point for the PLAY statement. Test to see which of the
- ; allowed forms has been specified.
- ;***
- ; B$PL0, B$PL1, B$SPLY - Play <sexp>... statement
- ;
- ; Purpose:
- ; Runtime Entry Points.
- ; B$PL0 and B$PL1 are alternate PLAY Statement preamble entry points
- ; B$SPLY is called with the last parm, and it actually executes
- ; the statement.
- ; B$PL0 called if a parm is specified
- ; B$PL1 called if a parm is defaulted
- ; B$SPLY called for last parm (may be the only parm as well)
- ;
- ; Parse the strings from the program statement, and set up the pointers
- ; in the string stacks.
- ;
- ; Input:
- ; sdPlay == string descriptor of a single argument (B$PL0 & B$SPLY only -
- ; no input is given for B$PL1)
- ; Output:
- ; NONE
- ; Modifies:
- ; NONE
- ;****
- cProc B$SPLY,<PUBLIC,FAR>,<SI>
- parmSD sdPlay
- cBegin
- cCALL B$CHKINI
- GetpSD BX,sdPlay ;BX = psd for play string
- MOV STRDSC,BX ; save str desc for B$PARSER.
- cCALL B$PARSE0 ; Go process everything
- MOV AX,b$curlevel ;deallocate all temps >= current level
- cCALL B$STDALCALLTMP
- cEnd
- ;***
- ;B$CHKINI
- ;
- ;PURPOSE:
- ; Initialize runtime variables related to music if starting play statement
- ;
- ;ENTRY:
- ;
- ;EXIT:
- ;
- ;MODIFIES:
- ; AX,CX,SI
- ;
- ;****
- cProc B$CHKINI,<NEAR>
- cBegin
- ;exit -- bx is preserved
- XOR AX,AX
- MOV [B$MCLTAB],OFFSET PLYTAB ;B$MCLTAB points to play command table
- ; for B$MACCMD
- MOV [MQUEFL],AL ; indicates no music cmds have been queued
- ; Initialize the string stacks for use while parsing the play strings.
- ; These stacks are used to support the X music command which functions
- ; like a subroutine call.
- MOV CX,OFFSET DGROUP:VSTACK ; *cx = string stack for voice 1
- inistl:
- MOV VSTOFF,AX ; reset offset for voice si
- MOV VSTBAS,CX ; set stack base ptr for voice si
- MOV STRDSC,AX ; reset string desr ptr for voice si
- chkret:
- cEnd ; End of B$CHKINI
- ;
- ;***
- ;B$PARSE0
- ;
- ;PURPOSE:
- ; Pre-PARSER for play string
- ;
- ;ENTRY:
- ;
- ;EXIT:
- ;
- ;USES:
- ; per convention
- ;
- ;****
- cProc B$PARSE0,<NEAR>
- cBegin ; **** HERE AFTER LAST PARM ****
- ; Release string temps in reverse order (because its a stack)
- MOV [PLYPRS],NUM_VOICES ;NUM_VOICES strings have been parsed
- SETSTL:
- MOV BX,STRDSC ; *bx = temp str desc for voice(si)
- OR BX,BX
- JE EMPTYS ;branch if no string was parsed
- CALL B$SETMCL ;set B$MCLPTR, B$MCLLEN to str @bx
- cCALL B$PUTSTR ;save B$MCLPTR, B$MCLLEN on voicen's stack
- DEC [PLYPRS] ;one less string has been parsed
- emptys:
- JMP SHORT B$PARSER
- cEnd <nogen> ; End of B$PARSE0
- ;***
- ;B$PARSER fills the background-music queues as follows:
- ; Repeat
- ; For Each voice
- ; while (input string is not empty) and
- ; (room exists in voice's queue for 1 command)
- ; Call B$MACCMD to process next command in string.
- ; (This causes some routine in PLYTAB to be called which may try to
- ; queue information for the background task into that voice's queue.
- ; If 1st pass, DI, bump PLYBGC, cCALL B$STRTSD(7) to initiate background task
- ; Until all voice strings are empty
- ; If music-forground, wait until PLYMSK=0
- ;****
- cProc B$PARSER,<NEAR>
- cBegin
- PARSER_BEGIN:
- ; Get music macro commands from the string for this voice until the
- ; queue for this voice fills or we hit the end of the string.
- PRSL10:
- cCALL B$GETSTR ;Get B$MCLPTR, B$MCLLEN for [voicen]
- CMP [B$MCLLEN],0 ; Check if end of string
- JNE PRSCONT ; Brif more in string, process it
- CMP VSTOFF,0 ; Test if string was nested by X command
- JNE PRSL10 ; Brif so, jump to return to next string up
- JMP SHORT PRSLPX ; Else, the top level string s done
- PRSCONT: ; Continue with parsing
- PUSH [B$MCLPTR] ;SAve the current string pointers
- PUSH [B$MCLLEN] ; in case the queue overflows
- CALL B$MACCMD ;Parse one command from this string
- JB PRSL20 ;Quit this loop if the queue overflowed
- POP AX ;Clear old pointers from stack
- POP AX
- cCALL B$PUTSTR ;save new B$MCLPTR, B$MCLLEN for [voicen]
- JMP SHORT PRSL10 ;continue trying to fill this queue
- ; Had a queue overflow on the last call to B$MACCMD. Restore the original
- ; string pointers so that the command which caused the overflow can be
- ; reprocessed the next time.
- PRSL20:
- POP [B$MCLLEN] ;RESTORE the old pointers
- POP [B$MCLPTR]
- cCALL B$PUTSTR ;And put them back on the local stack
- PRSLPX:
- ; Test if the user wants to break
- CALL B$BREAK_CHK ;CTRL-BREAK?
- ; Have made a pass through the strings. If this is pass 1 and there
- ; is data in any of the queues, then start the music playing.
- TEST PLYPRS,LOW 128D
- JNZ NOTPS1 ;Brif not Pass 1
- OR PLYPRS,LOW 128D ;Set parsed once flag
- CMP MQUEFL,LOW 0
- JE NOTPS1 ;branch if no cmds have been queued for
- ; this PLAY statement (no need to start
- ; background task
- cCALL B$STRTSD ;Start sounds
- NOTPS1:
- ; Test if we are all done. This occurs when the count of voice strings
- ; completely parsed (contained in PLYPRS) equals the number of voices.
- CMP [PLYPRS],LOW 200O+NUM_VOICES
- JE PRSDON ;Brif all NUM_VOICES strings parsed
- MOV AL,LOW TSTVOC ;Ask the OEM if any voices are active
- cCALL B$DONOTE
- OR AL,AL ;See what they had to say about it
- JNE PARSER_BEGIN ; If there are active voices, then the
- ;queue's are emptying, so go try to
- ;parse the rest of the strings.
- ; If we get here, then it means that we are not done parsing the PLAY
- ; strings, the queues are full, but none of the voices are active.
- ; This should never occur, but IF it did, BASIC would be hung indefinitely.
- ; This simple safeguard ensures this will never happen.
- MOV AL,LOW STRSND ;Function code to start music
- cCALL B$DONOTE ; Tell OEM to start playing the music
- JMP PARSER_BEGIN ; and go parse the rest of the strings
- ; We are all done parsing the input strings. If the music mode is
- ; Background, then we are done. If mode is Foreground, we need to
- ; wait around until the sound stops.
- PRSDON:
- cCALL B$SNDWAT ;wait if Music Foreground
- cEnd ; End of B$PARSER
- ;***
- ;B$PUTSTR
- ;Purpose:
- ; Save B$MCLPTR, B$MCLLEN on Voice Stack for voice [VOICEN]
- ;Input:
- ; [VOICEN] = voice id (0..2)
- ; [B$MCLLEN]=number of bytes left in current string for this voice
- ; [B$MCLPTR]=pointer to next byte in current string for this voice
- ;Output:
- ; If stack overflows, an Out of Memory error is issued
- ;Modifies:
- ; AX
- ;****
- cProc B$PUTSTR,<NEAR>,<BX,SI,DI>
- cBegin
- ; Get the stack pointer for this string and check for stack overflow
- MOV DI,VSTBAS ; di points to stack base for voice si
- MOV BX,VSTOFF ; bx = offset for current top of stack
- CMP BX,NUM_VSTACK ;Check if the stack is full
- JB STKOK ;branch if still room on stack
- JMP B$ERR_OM ;NO room, so signal out of memory error
- ; Save the current data on the stack
- STKOK:
- MOV AX,[B$MCLPTR]
- MOV [BX+DI],AX ;save string pointer on stack
- MOV AX,[B$MCLLEN]
- MOV [BX+DI]+2,AX ;save string length on stack
- ; Update the stack pointer to account for new stack size
- ADD BX,4
- MOV VSTOFF,BX ; save offset to top of stack
- cEnd ; End of B$PUTSTR
- ;***
- ;B$GETSTR
- ;Purpose:
- ; Set B$MCLPTR, B$MCLLEN for Voice [VOICEN]
- ;Input:
- ; [VOICEN] = voice id (0..2)
- ;Output:
- ; [B$MCLLEN]=number of bytes left in current string for this voice
- ; [B$MCLPTR]=pointer to next byte in current string for this voice
- ; If this voice's string has been completely consumed then
- ; PLYPRS is incremented and B$MCLLEN=0
- ;Modifies:
- ; AX
- ;****
- cProc B$GETSTR,<NEAR>,<BX,SI,DI>
- cBegin
- ; Get the stack pointer for this voice, and test if stack is empty
- MOV BX,VSTOFF ; bx = offset for current top of stack
- MOV [B$MCLLEN],BX ;length=0 if no more strings stacked
- OR BX,BX
- JE GETSTX ;brif no entries exist on voice's stack
- MOV DI,VSTBAS ; DI points to stack base for voice SI
- ; Get the next set of entries from the stack.
- GETSTL:
- SUB BX,4 ;Adjust stack pointer to next entry
- MOV AX,[BX+DI] ;get string pointer from stack
- MOV [B$MCLPTR],AX
- MOV AX,[BX+DI]+2 ;get string length from stack
- MOV [B$MCLLEN],AX
- OR AX,AX
- JNE GETSTX ;exit if this string is not empty
- OR BX,BX
- JNE GETSTL ;brif more entries on voice's stack
- INC [PLYPRS] ;Bump number of strings consumed
- ; Update the stack pointer, restore the registers, and get out
- GETSTX:
- MOV VSTOFF,BX ; save offset to top of stack
- cEnd ; End of B$GETSTR
- ;--------------------------------------------------------------------------
- ; Music Macro Language command table
- ; This table contains all of the command characters allowed in the music
- ; language strings, and the entry points of the routines to process them
- PLYTAB LABEL BYTE
- DB "A" ;The notes A-G
- DW OFFSET PLYNOT
- DB "B"
- DW OFFSET PLYNOT
- 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