home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Sound Sensations!
/
sound_sensations.iso
/
miscprog
/
play
/
play.asm
next >
Wrap
Assembly Source File
|
1987-04-01
|
22KB
|
466 lines
; Play.asm
; Emulates Basic's Play commands
; Syntax PLAY [string[/K] | filespec/F[/K]]
; /K denotes no keyboard polling
;
CODE SEGMENT ;*************************
ASSUME CS:CODE,DS:CODE ;* *
ORG 100H ;* REMEMBER TO EXE2BIN *
;* *
START: JMP BEGINNING ;*************************
COPYRIGHT DB 'Copyright l987 Ziff-Davis Publishing Co.',1AH
PROGRAMMER DB 'Michael J. Mefford'
TIMER_TIC DD ?
OCTAVE DB 4
LEN DW 64
TEMPO DW 136
MUSIC DB 1
COUNT DW 0
NOTE:
; C C# D D# E F
DW 16744,17736,18792,19912,21096,22352
; F# G G# A A# B
DW 23680,25088,26576,28160,29712,31616
FILE DB 'PLAY.DAT',0
PARA_FLAG DB 0
FILE_FLAG DB 0
POLL_KEY DB 0
;----------------------------------------------------------;
; Speed the clock up to four times normal speed. ;
; Capitalize command line and check for switch characters. ;
;----------------------------------------------------------;
BEGINNING: MOV BX,16384 ;Divisor for 72.8/sec IRQ 0.
CALL SET_CLOCK
MOV AX,3508H ;Get INT 8 (timer tic).
INT 21H
MOV WORD PTR TIMER_TIC,BX ;Save old vector.
MOV WORD PTR TIMER_TIC[2],ES
PUSH DS
POP ES ;Restore ES.
MOV DX,OFFSET INT_8 ;Point INT 8 to our routine.
MOV AX,2508H
INT 21H
CMP BYTE PTR DS:[80H],0 ;Are there any parameters?
JZ PARAMETERS ;If no, open PLAY.DAT.
CAPITALIZE: MOV SI,81H ;Point to parameters.
NEXT_CAP: LODSB ;Get a byte.
CMP AL,13 ;Is it carriage return?
JZ PARAMETERS ;If yes, done here.
CMP AL,'/' ;Is it switch character?
JZ SWITCHES ;If yes, check which one.
CMP AL,'0' ;Is it a possible command?
JB NEXT_CAP ;If no, get next byte.
MOV PARA_FLAG,1 ;Else flag that parameter exists.
CMP AL,'a' ;Is it lower case?
JB NEXT_CAP ;If no, get next byte.
AND BYTE PTR [SI-1],5FH ;Else, capitalize.
JMP SHORT NEXT_CAP ;Next byte.
SWITCHES: CMP AL,'/' ;Is it switch character?
JNZ GET_SWITCH ;If no, get next byte.
MOV BYTE PTR [SI-1],0 ;Else create ASCIIZ for DOS.
GET_SWITCH: LODSB ;Get a byte.
CMP AL,13 ;Is it carriage return?
JZ PARAMETERS ;If yes, done here.
AND AL,5FH ;Else, capitalize.
CMP AL,'K' ;Is it /K switch?
JNZ CK_FILE ;If no, check /F.
MOV POLL_KEY,1 ;Else, flag as no keyboard poll.
CK_FILE: CMP AL,'F' ;Is it /F?
JNZ SWITCHES ;If no, get next byte.
MOV FILE_FLAG,1 ;Else, flag as filespec.
JMP SHORT SWITCHES ;Get next byte.
;-----------------------------------------------------------------------;
; Exit is here so it can be reached by as many short jumps as possible. ;
; Before terminating, reset clock to 18.2/sec and restore INT 8 vector. ;
;-----------------------------------------------------------------------;
EXIT: MOV BX,0 ;Divisor of 65536 or zero.
CALL SET_CLOCK
MOV DX,WORD PTR TIMER_TIC ;Restore old vector
MOV AX,WORD PTR TIMER_TIC[2]
MOV DS,AX
MOV AX,2508H ; of INT 8.
INT 21H
INT 20H ;Terminate.
;-----------------------------------------------------------------;
; Check to see if parameters exist and if yes check if filespec. ;
; If yes, read file into buffer and process by stripping Wordstar ;
; high bit, capitalizing, stripping comments and stripping space ;
; characters and below. Finish by tacking on a carriage return. ;
;-----------------------------------------------------------------;
PARAMETERS: CMP PARA_FLAG,1 ;Are there parameter?
JZ FILE_NAME ;If yes, check if filespec.
MOV DX,OFFSET FILE ;Else, point to PLAY.DAT
JMP SHORT OPEN_FILE ; and open file.
FILE_NAME: CMP FILE_FLAG,1 ;Is it a filespec?
JNZ READ_COMMAND ;If no, read the command line.
MOV DX,82H ;Else, point to filespec.
OPEN_FILE: MOV AX,3D00H ;Open file for reading.
INT 21H
JC EXIT ;If not found, exit.
MOV BX,AX ;Else, filehandle in BX.
MOV DX,OFFSET BUFFER ;Read file into buffer.
MOV CX,0F000H
MOV AH,3FH
INT 21H
MOV CX,AX ;File length in CX.
MOV SI,OFFSET BUFFER ;Initialized SI and DI
MOV DI,OFFSET BUFFER ; to head of buffer.
FORMAT: LODSB ;Get a byte.
AND AL,7FH ;Strip Wordstar high bit.
CMP AL,':' ;Is it comment character?
JZ STRIP ;If yes, strip comment.
CMP AL,'a' ;Is it lower case?
JB CK_CONTROL ;If no, check space and below.
AND AL,5FH ;Else, capitalize.
CK_CONTROL: CMP AL,32 ;Is it space or below?
JBE NEXT_FORMAT ;If yes, don't store.
STOSB ;Else store the character.
NEXT_FORMAT: LOOP FORMAT ;Get next byte.
END_FORMAT: MOV BYTE PTR [DI],13 ;Tack on carriage return as EOF.
MOV SI,OFFSET BUFFER ;Point to commands.
JMP SHORT NEXT_COMMAND ;Get the commands.
NEXT_STRIP: LODSB ;Get a byte.
AND AL,7FH ;Strip Wordstar high bit.
CMP AL,10 ;Is it linefeed?
JZ NEXT_FORMAT ;If yes get next command.
STRIP: LOOP NEXT_STRIP ;Else, skip and get next byte.
JMP SHORT END_FORMAT
;--------------------------------;
; This is the command processor. ;
;--------------------------------;
READ_COMMAND: MOV SI,82H ;Initialize pointer to commands.
NEXT_COMMAND: CMP POLL_KEY,1 ;Should we poll keyboard?
JZ GET_COMMAND ;If no, skip.
MOV AH,1 ;Else, check for keystroke
INT 16H ; via BIOS.
JNZ EXIT ;If keystroke, exit.
GET_COMMAND: LODSB ;Else, get a byte.
CMP AL,13 ;Is it carriage return?
JA CONTINUE ;If no, continue.
JMP EXIT ;Else, we are done.
CONTINUE: CMP AL,32 ;Is it space, comma or semicolon?
JZ NEXT_COMMAND ;If yes, skip.
CMP AL,','
JZ NEXT_COMMAND
CMP AL,';'
JZ NEXT_COMMAND
CMP AL,'O' ;Is it "O" ; Octave?
JNZ CK_L ;If no, check "L".
CALL CK_NUMBER ;Else, get number.
CMP AL,7 ;Is it in range?
JA NEXT_COMMAND ;If no, skip.
MOV OCTAVE,AL ;Else store.
JMP SHORT NEXT_COMMAND
CK_L: CMP AL,'L' ;Is it "L" ; Length?
JNZ CK_TEMPO ;If no, check tempo.
CALL CK_NUMBER ;Else, get number.
CMP AL,0 ;Is it between 1 and 64?
JZ NEXT_COMMAND ;If no, skip.
CMP AL,64
JA NEXT_COMMAND
XOR DX,DX ;Else, zero in high half.
MOV AX,256 ;Dividend of 256.
DIV BX ;Divide by length
MOV LEN,AX ; and store.
JMP SHORT NEXT_COMMAND
CK_TEMPO: CMP AL,'T' ;Is it "T" ; tempo?
JNZ CK_N ;If no, check number.
CALL CK_NUMBER ;Else, get number.
CMP AL,32 ;Is it between 32 and 255?
JB NEXT_COMMAND ;If no, skip.
MOV AX,255
SUB AL,BL ;Else, complement.
INC AX ;Make non zero
MOV TEMPO,AX ; and store.
JMP SHORT NEXT_COMMAND
CK_N: CMP AL,'N' ;Is it "N" ; number?
JNZ CK_NOTE ;If no, check note.
CALL CK_NUMBER ;Else, get number.
CMP AL,84 ;Is it between 0 and 84?
JA NEXT_COMMAND ;If no, skip.
CMP AL,0
JZ GOT_N
DEC AL ;Adjust number.
GOT_N: XOR AH,AH ;Zero in high half.
MOV CL,12 ;Divide by 12.
DIV CL
MOV CX,8 ;Get octave.
SUB CL,AL
MOV BL,AH ;Remainder in BX pointer.
XOR BH,BH
SHL BX,1 ;Adjusts as word pointer.
MOV BX,[OFFSET NOTE+BX] ;Retrieve note.
CALL PLAY_NUMBER ;Play the note.
JMP NEXT_COMMAND
CK_NOTE: CMP AL,'A' ;Is it letter note ; A-G?
JB COMMAND_END
CMP AL,'G'
JA CK_P ;If no, check pause.
CMP AL,'C'
JB ABOVE_C
SUB AL,7 ;First subtract 7 for C and above.
ABOVE_C: SUB AL,60 ;Finish pointer adjustment.
XOR AH,AH ;Zero in high half.
MOV DI,AX ;Move into pointer.
MOV CL,2 ;Convert to word pointer
SHL DI,CL ; by multiplying by 2.
CMP DI,8 ;Adjust if necessary.
JBE GOT_NOTE
SUB DI,2
GOT_NOTE: CALL BLACK_KEY ;Check for sharps or flats.
CALL PLAY ;Play the note.
JMP NEXT_COMMAND
CK_P: CMP AL,'P' ;Is it "P" ; pause?
JNZ CK_K ;If no, check key poll.
CALL CK_NUMBER ;Else, get number.
CMP AL,0 ;Is it between 1 and 64?
JZ COMMAND_END ;If no, skip.
CMP AL,64
JA COMMAND_END
XOR DX,DX ;Else, zero in high half.
MOV AX,256 ;Dividend of 256.
DIV BX ;Divide by length
MOV BP,AX ;Pause in BP.
CALL PAUSE_DELAY
JMP NEXT_COMMAND
CK_K: CMP AL,'K' ;Is it "K" ; non keyboard poll?
JNZ CK_M ;If no, check music.
MOV POLL_KEY,1 ;Else, flag as no poll.
JMP NEXT_COMMAND
CK_M: CMP AL,'M' ;Is it "M" ; music?
JNZ COMMAND_END ;If no, next command.
LODSB ;Else, get next byte.
CMP AL,13
JNZ CK_N2
JMP EXIT
CK_N2: CMP AL,'N' ;Is it "N" ; normal?
JNZ CK_L2 ;If no, check L.
MOV MUSIC,1 ;Else, flag as music one.
CK_L2: CMP AL,'L' ;Is it "L" ; legato?
JNZ CK_S2 ;If no, check S.
MOV MUSIC,2 ;Else, flag as music two.
CK_S2: CMP AL,'S' ;Is it "S" ; staccato?
JNZ COMMAND_END ;If no, skip.
MOV MUSIC,3 ;Else, flag as music three.
COMMAND_END: JMP NEXT_COMMAND
;*************;
; Subroutines ;
;*************;
;---------------------------------------------------------;
; This subroutine converts decimal command number to hex. ;
;---------------------------------------------------------;
CK_NUMBER: XOR BX,BX ;Initialize to zero.
NEXT_NUMBER: CMP BYTE PTR [SI],'0' ;Is it a number?
JB END_NUMBER ;If no, we're done.
CMP BYTE PTR [SI],'9'
JA END_NUMBER
LODSB ;Get number.
SUB AL,30H ;Convert to hex.
MOV DL,AL
MOV AX,10 ;Shift decimal by ten.
MUL BL
MOV BL,AL ;Result in BL.
ADD BL,DL ;Add new number.
JMP SHORT NEXT_NUMBER ;Get next number.
END_NUMBER: MOV AL,BL ;Return with number in AL.
XOR AH,AH ;Zero in high half.
RET
;---------------------------------------------;
; This subroutine checks for sharps or flats. ;
;---------------------------------------------;
BLACK_KEY: CMP BYTE PTR [SI],'+' ;Is it sharp?
JNZ CK_SHARP ;If no, check #.
ADD DI,2 ;Else, point to next note.
INC SI ;Adjust command pointer.
CK_SHARP: CMP BYTE PTR [SI],'#' ;Do same for #.
JNZ CK_FLAT
ADD DI,2
INC SI
CK_FLAT: CMP BYTE PTR [SI],'-' ;Do same for flat except
JNZ END_BLACK ; decrement note pointer.
SUB DI,2
INC SI
END_BLACK: RET
;---------------------------------;
; This subroutine plays the note. ;
;---------------------------------;
PLAY: MOV BX,[OFFSET NOTE+DI] ;Retrieve the note.
MOV CX,8 ;Complement the octave.
SUB CL,OCTAVE
PLAY_NUMBER: SHR BX,CL ;Divide by octave base two.
MOV DX,12H ;120000h dividend constant.
XOR AX,AX
DIV BX ;Divide by frequency.
MOV BX,AX ;Store in BX.
MOV AL,0B6H ;Latch to Channel 2.
OUT 43H,AL
MOV AX,BX ;Send LSB then MSB to 8253.
OUT 42H,AL
MOV AL,AH
OUT 42H,AL
IN AL,61H
OR AL,3 ;Turn bits 0 and 1 on 8255 chip
OUT 61H,AL ; to turn speaker on.
CALL DELAY ;Delay.
OFF: IN AL,61H ;Turn bits 0 and 1 back off.
AND AL,11111100B
OUT 61H,AL
MOV CX,BX ;Delay for pause between notes.
CALL STACCATO
RET
;------------------------------------------------;
; This subroutine calculates the length of play. ;
;------------------------------------------------;
DELAY: MOV BP,LEN ;Retrieve length.
PAUSE_DELAY: MOV CX,BP ;Store in CX.
NEXT_DOT: CMP BYTE PTR [SI],'.' ;Is it a dotted note?
JNZ GOT_DELAY ;If no, got length.
INC SI ;Else, adjust command pointer.
SHR CX,1 ;Divide length by two and
ADD BP,CX ; add to total length.
JMP SHORT NEXT_DOT ;Check for another dot.
GOT_DELAY: MOV CX,BP ;Length in CX.
PUSH CX ;Save.
CMP MUSIC,2 ;Is it music legato?
JZ WAIT ;If yes, play full length.
MOV AX,CX ;Else, length in AX.
XOR DX,DX ;Zero in high half.
CMP MUSIC,1 ;Is it music normal?
JNZ MUSIC_3 ;If no, must be music stacatto.
MOV CX,7 ;Multiply by 7/8.
MUL CX
MOV CL,3
SHR AX,CL
MOV CX,AX
JMP SHORT WAIT
MUSIC_3: MOV CX,3 ;Multiply by 3/4.
MUL CX
MOV CL,2
SHR AX,CL
MOV CX,AX
WAIT: POP BX ;Recover total length.
SUB BX,CX ;Subtract pause time.
STACCATO: MOV AX,TEMPO ;Retrieve tempo.
MUL CL ;Multiply by length of note.
XOR DX,DX
MOV CX,200 ;Adjust by looping constant.
DIV CX
INC AX ;Prevent zero loop.
MOV CX,COUNT ;Get present counter.
ADD CX,AX ;Add length of note.
JB END_DELAY ;Skip if count wrapped.
NEXT_DELAY: MOV AX,COUNT ;Get counter again.
CMP AX,CX ;Has delay expired?
JB NEXT_DELAY ;If no, get counter until it does.
END_DELAY: RET
;------------------------------------------------------;
; This subroutine programs Channel 0 of the 8253 chip. ;
;------------------------------------------------------;
SET_CLOCK: MOV AL,00110110B ;Latch to Channel 0.
OUT 43H,AL ;Send LSB then MSB.
MOV AL,BL
OUT 40H,AL
MOV AL,BH
OUT 40H,AL
RET
;------------------------------------;
; This is the new INT 8 interceptor. ;
;------------------------------------;
ASSUME DS:NOTHING
INT_8: STI ;Interrupts back on.
PUSHF ;Save registers.
PUSH AX
PUSH CX
PUSH DX
INC COUNT ;Increment our counter.
MOV AX,COUNT ;Check if divisible by 4.
XOR DX,DX
MOV CX,4
DIV CX
CMP DX,0
JNZ TIMER_RET ;If no, skip clock update.
POP DX ;Else, restore registers.
POP CX
POP AX
POPF
JMP TIMER_TIC ;Pass control to old INT 8.
TIMER_RET: POP DX ;Restore DX and CX; need AX still.
POP CX
CLI ;No interrupts.
MOV AL,20H ;Send 8259 command port EOI
OUT 20H,AL ; end-of-interrupt.
POP AX ;Restore other registers.
POPF
IRET ;Interrupt return.
BUFFER:
CODE ENDS
END START