Last active
December 5, 2015 20:15
-
-
Save hephaestus9/81e04d7ad8e25b17e00e to your computer and use it in GitHub Desktop.
RTTTL Player in ASM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; CIS-261 | |
; Here is a song to past in d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6,q | |
; 'Indiana Jones' | |
; Some code taken from Lab M09 demo program: make_beeps.asm | |
; Lab M09, http://www.c-jump.com/bcc/c261c/MLabs/M09arrays/lecture.html Data Arrays and Windows API Calls | |
; | |
; Reference http://www.benchmark-companies.com/electronics/elt265/handouts/wam_ch8.pdf | |
; Reference https://github.com/ponty/arduino-rtttl-player/blob/master/rtttl/rtttl.h | |
; Piano & Keyboard http://www.chris.com/ascii/index.php?art=music/pianos | |
.586P | |
.MODEL FLAT ; Flat memory model | |
option casemap:none ; Treat labels as case-sensitive | |
INCLUDE IO.H ; header file for input/output | |
EXTERN _Beep@8:NEAR | |
EXTERN _GetLastError@0:NEAR | |
EXTERN _Sleep@4:NEAR | |
.CONST ; Constant data segment | |
intro1 BYTE "Jeramy Brian", 0 | |
intro2 BYTE "Assignment M09", 0 | |
intro3 BYTE "RTTL - The Ring Tone Text Transfer Language", 0 | |
piano1 BYTE " _ _ _ _ _ _", 0 | |
piano2 BYTE " _ - \", 0 | |
piano3 BYTE " - _ -||", 0 | |
piano4 BYTE "| _ - ||", 0 | |
piano5 BYTE "| _ - \ ||", 0 | |
piano6 BYTE "| _ - \||", 0 | |
piano7 BYTE "|_-_______________________________\", 0 | |
piano8 BYTE "| |", 0 | |
piano9 BYTE "|\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\", 0 | |
piano10 BYTE "||----------------------------------| --Keith Wright", 0 | |
piano11 BYTE "|| || || ||", 0 | |
piano12 BYTE "[] `UUU' []", 0 | |
menu1 BYTE "RTTL Player Options: ", 0 | |
menu2 BYTE " 1. Play a song", 0 | |
menu3 BYTE " 2. Write a song", 0 | |
menu4 BYTE " 3. Play notes", 0 | |
menu5 BYTE " 4. Paste a song", 0 | |
menu6 BYTE " 5. Quite", 0 | |
menu7 BYTE "What would you like to do?: ", 0 | |
songMenu1 BYTE "Available Songs:", 0 | |
songMenu2 BYTE " 1. TwinkleTwinkle", 0 | |
songMenu3 BYTE " 2. FrereJacques", 0 | |
songMenu4 BYTE " 3. Beethoven5", 0 | |
songMenu5 BYTE " 4. ForHesAJollyGoodFellow", 0 | |
songMenu6 BYTE " 5. TakeMeOutToTheBallgame", 0 | |
songMenu7 BYTE " 6. Reveille", 0 | |
songMenu8 BYTE " 7. Super Mario Bro's - Main Theme", 0 | |
songMenu9 BYTE " 8. Super Mario Bro's - Underground Theme", 0 | |
songMenu10 BYTE " 9. Super Mario Bro's - Death Theme", 0 | |
songMenu11 BYTE "Which would you like to play?: ", 0 | |
writeSong1 BYTE "Please enter the song that you would like to play.", 0 | |
writeSongNotes1 BYTE "1 = Whole Note", 0 | |
writeSongNotes2 BYTE "2 = Half Note", 0 | |
writeSongNotes3 BYTE "4 = Quarter Note", 0 | |
writeSongNotes4 BYTE "8 = Eigth Note", 0 | |
writeSongNotes5 BYTE "16 = Sixteenth Note", 0 | |
writeSongNotes6 BYTE "32 = Thirtysecond Note", 0 | |
writeSong2 BYTE "Default Duration (1, 2, 4, 8, 16 or 32): ", 0 | |
writeSongDur BYTE "d=", 0 | |
writeSong3 BYTE "Default Octave (4, 5, 6, or 7): ", 0 | |
writeSongOct BYTE "o=", 0 | |
writeSongNotes7 BYTE "For reference, 120 bpm makes a whole note last 2 seconds.", 0 | |
writeSong4 BYTE "Default Tempo/Beat (Beats per Min.): ",0 | |
writeSongTem BYTE "b=", 0 | |
writeSongNotes8 BYTE "Format = duration|Note|Dot|Octave; i.e.) 16d#6,16p,16e6,c,c,g,g,16e.6,q", 0 | |
writeSongNotes9 BYTE "Remember to make sure that the song ends in ',q'", 0 | |
writeSong5 BYTE "Song: ", 0 | |
writeSongSep BYTE ":", 0 | |
playNote1 BYTE "Please enter the note you would like to play.", 0 | |
playNoteNotes1 BYTE "Format = duration|Note|Dot|Octave; i.e.) 16d#6; The settings for this are d=4,o=7,b=120", 0 | |
playNoteNotes2 BYTE "Do not enter a comma or a q after the last note to be played. Seperate multiple notes with a comma. i.e.) p.,g,8p,8a" | |
playNote2 BYTE "Enter note: ", 0 | |
playNoteStart BYTE "d=4,o=7,b=120:", 0 | |
pasteSong1 BYTE "Please past the song you would like to play here: ", 0 | |
error1 BYTE "You entered an invalid input. Please try again.", 0 | |
exitPrompt BYTE "Press enter to exit...", 0 | |
menuNumbers BYTE "123456789", 0 | |
ENDL BYTE 13, 10, 0 | |
WHITE_SPACE BYTE ' ', 0 | |
;This is here for reference. | |
;C6 WORD 1047 ; Do | |
;C6ShpOrD6Flt WORD 1109 ; | |
;D6 WORD 1175 ; Re | |
;D6ShpOrE6Flt WORD 1244 ; | |
;E6 WORD 1319 ; Mi | |
;F6 WORD 1396 ; Fa | |
;F6ShpOrG6Flt WORD 1480 ; | |
;G6 WORD 1568 ; Sol | |
;G6ShpOrA6Flt WORD 1661 ; | |
;A6 WORD 1760 ; La | |
;A6ShpOrB6Flt WORD 1865 ; | |
;B6 WORD 1976 ; Ti | |
;C7 WORD 2093 ; Do | |
;C7ShpOrD7Flt WORD 2118 ; | |
;D7 WORD 2349 ; | |
;D7ShpOrE7Flt WORD 2489 ; | |
;E7 WORD 2637 ; | |
;F7 WORD 2793 ; | |
;F7ShpOrG7Flt WORD 2960 ; | |
;G7 WORD 3136 ; | |
;G7ShpOrA7Flt WORD 3322 ; | |
;A7 WORD 3520 ; | |
;A7ShpOrB7Flt WORD 3729 ; | |
;B7 WORD 3951 ; | |
;C8 WORD 4186 ; | |
NoteLengths BYTE 6 DUP (1, 2, 4, 8, 16, 32) ; Whole Note, Half Note, Quarter Note, Eigth Note, Sixteenth Note, Thirty-Second Note | |
NoteLengthsStr BYTE "124863A", 0 | |
comma BYTE ",", 0 | |
dot BYTE ".", 0 | |
;RTTTL - Ringing Tone Text Transfer Language | |
;***************************Format: Duration = x, Octave = x, Tempo/Beat = x, duration|Note|Dot|Octave, ... | |
TwinkleTwinkleSong BYTE "d=4,o=7,b=120:c,c,g,g,a,a,2g,f,f,e,e,d,d,2c,g,g,f,f,e,e,2d,g,g,f,f,e,e,2d,c,c,g,g,a,a,2g,f,f,e,e,d,d,1c,q", 0 | |
FrereJacquesSong BYTE "d=4,o=7,b=125:c,d,e,c,c,d,e,c,e,f,2g,e,f,2g,8g,8a,8g,8f,e,c,8g,8a,8g,8f,e,c,c,g6,2c,c,g6,2c,q", 0 | |
Beethoven5Song BYTE "d=8,o=7,b=125:g,g,g,2d#,p,f,f,f,2d,q", 0 | |
ForHesAJollyGoodFellowSong BYTE "d=4,o=7,b=320:c,2e,e,e,d,e,2f.,2e,e,2d,d,d,c,d,2e.,2c,d,2e,e,e,d,e,2f,g,2a,a,g,g,g,2f,d,2c,q", 0 | |
TakeMeOutToTheBallgameSong BYTE "d=4,o=7,b=225:2c6,c,a6,g6,e6,2g.6,2d6,p,2c6,c,a6,g6,e6,2g.6,g6,p,p,a6,g#6,a6,e6,f6,g6,a6,p,f6,2d6,p,2a6,a6,a6,b6,c,d,b6,a6,g6,q" , 0 | |
ReveilleSong BYTE "d=4,o=7,b=140:8g6,8c,16e,16c,8g6,8e,8c,16e,16c,8g6,8e,8c,16e,16c,8a6,8c,e,8c,8g6,8c,16e,16c,8g6,8e,8c,16e,16c,8g6,8e,8c,16e,16c,8g6,8e,c,p,8e,8e,8e,8e,g,8e,8c,8e,8c,8e,8c,e,8c,8e,8e,8e,8e,8e,g,8e,8c,8e,8c,8g6,8g6,c.,q", 0 | |
SuperMarioMain1Song BYTE "d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6," | |
SuperMarioMain2Song BYTE "16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6,q", 0 | |
SuperMarioUNGSong BYTE "d=4,o=6,b=100:32c,32p,32c7,32p,32a5,32p,32a,32p,32a#5,32p,32a#,2p,32c,32p,32c7,32p,32a5,32p,32a,32p,32a#5,32p,32a#,2p,32f5,32p,32f,32p,32d5,32p,32d,32p,32d#5,32p,32d#,2p,32f5,32p,32f,32p,32d5,32p,32d,32p,32d#5,32p,32d#,q", 0 | |
SuperMarioDeathSong BYTE "d=4,o=5,b=90:32c6,32c6,32c6,8p,16b,16f6,16p,16f6,16f.6,16e.6,16d6,16c6,16p,16e,16p,16c,q", 0 | |
DoneVal BYTE "q", 0 | |
Notes BYTE "abcdefgA", 0 | |
PauseVal BYTE "p", 0 | |
Sharp BYTE "#", 0 | |
Octave8 WORD 13 DUP (0,3520,3729,3951,4186,4435,4699,4978,5274,5588,5920,6272,6645) | |
octaves BYTE "5678A", 0 | |
octaveVals BYTE 0, 1, 2, 4, 8, 16 | |
divisor BYTE 10 | |
.STACK 100h ; (default is 1-kilobyte stack) | |
.DATA ; Begin initialized data segment | |
counter WORD 0 ; General purpose counter. | |
char BYTE ? ; Variable stores characters. | |
index BYTE 0 ; Index for pointing at data. | |
noteLetter BYTE ? ; Stores note character. | |
noteFreq WORD ? ; Stores note frequency. | |
noteOctave BYTE ? ; Stores note octave. | |
duration WORD ? ; Stores note duration. | |
tempo WORD ? ; Stores tempo. | |
default_d WORD 0 ; Stores default duration. | |
default_o BYTE 0 ; Stores default octave. | |
default_b WORD 0 ; Stores default beats/min. | |
WholeNote WORD ? ; length for a whole note | |
pastedSong BYTE 220 DUP (?) | |
playEnteredNote BYTE 30 DUP (?) | |
menuSel_buffer BYTE 5 DUP (?) | |
song_buffer BYTE 220 DUP (?) | |
tempDuration WORD ? | |
ERRORFLAG BYTE 0 ; 'BOOL' value to be set if an error has occured | |
tmpCounter BYTE 1 | |
playNotesVal BYTE 0 ; 'BOOL' value to know if the user just wants to play notes | |
endProg BYTE 0 | |
songLen WORD 0 | |
tmpChar BYTE ? | |
tmpAddr DWORD ? | |
beepMem DWORD 2 DUP (?) | |
ARRAY_LENGTH EQU LENGTHOF beepMem ; | |
ARRAY_TYPE EQU TYPE beepMem ; | |
.CODE ; Begin code segment | |
_main PROC ; Beginning of code | |
call Intro | |
entryPoint: | |
mov ERRORFLAG, 0 | |
call MainMenu | |
cmp ERRORFLAG, 0 | |
jne entryPoint | |
cmp endProg, 1 | |
je done | |
cmp playNotesVal, 1 | |
je playNotes | |
call FindEquals | |
call ProcessDuration ; Get default duration. | |
call FindEquals ; Find next '='. | |
call ProcessOctave ; Get default octave. | |
call FindEquals ; Find last '='. | |
call GetTempo ; Get default tempo. | |
do_until_q: | |
cmp char, 71h ; Loop until 'q' in DATA. | |
je clearVars | |
call ProcessDuration ; Get note duration. | |
call ProcessNote ; Get index value of note. | |
call CheckForDot ; If dot, 3/2 duration. | |
call ProcessOctave ; Get octave. | |
call PlayNote ; Get freq, play note, next. | |
jmp do_until_q | |
clearVars: | |
mov default_b, 0 | |
mov default_d, 0 | |
mov default_o, 0 | |
mov WholeNote, 0 | |
mov index, 0 | |
output ENDL | |
jmp entryPoint | |
playNotes: | |
jmp entryPoint | |
done: | |
output exitPrompt | |
input menuSel_buffer, 5 | |
ret | |
_main ENDP | |
; -----[ Subroutine - Show Intro ]----------------------------- | |
Intro PROC | |
output intro1 | |
output ENDL | |
output intro2 | |
output ENDL | |
output intro3 | |
output ENDL | |
output piano1 | |
output ENDL | |
output piano2 | |
output ENDL | |
output piano3 | |
output ENDL | |
output piano4 | |
output ENDL | |
output piano5 | |
output ENDL | |
output piano6 | |
output ENDL | |
output piano7 | |
output ENDL | |
output piano8 | |
output ENDL | |
output piano9 | |
output ENDL | |
output piano10 | |
output ENDL | |
output piano11 | |
output ENDL | |
output piano12 | |
output ENDL | |
output ENDL | |
ret | |
Intro ENDP | |
; -----[ Subroutine - Show Main Menu ]----------------------------- | |
MainMenu PROC | |
output menu1 | |
output ENDL | |
output menu2 | |
output ENDL | |
output menu3 | |
output ENDL | |
output menu4 | |
output ENDL | |
output menu5 | |
output ENDL | |
output menu6 | |
output ENDL | |
output menu7 | |
;There are only 5 options for the main menu | |
input menuSel_buffer, 5 | |
mov edx, OFFSET menuNumbers | |
mov al, menuSel_buffer | |
@@: | |
cmp al, [edx] | |
je gotSelection | |
inc tmpCounter | |
inc edx | |
push eax | |
mov al, 6 | |
cmp al, tmpCounter | |
pop eax | |
jle error | |
jmp @B | |
gotSelection: | |
;Select (tmpCounter): | |
;Case 1: | |
cmp tmpCounter, 1 | |
je playSong | |
;Case 2: | |
cmp tmpCounter, 2 | |
je writeSong | |
;Case 3: | |
cmp tmpCounter, 3 | |
je playNotes | |
;Case 4: | |
cmp tmpCounter, 4 | |
je pasteSong | |
;Case 5: | |
cmp tmpCounter, 5 | |
je endProgram | |
playSong: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
call PlaySongMenu | |
ret | |
writeSong: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
call WriteSongMenu | |
ret | |
playNotes: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
call PlayNotesMenu | |
ret | |
pasteSong: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
call PasteSongMenu | |
ret | |
endProgram: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
mov endProg, 1 | |
ret | |
error: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
output error1 | |
mov ERRORFLAG, 1 | |
ret | |
MainMenu ENDP | |
; -----[ Subroutine - Play Song Menu ]----------------------------- | |
PlaySongMenu PROC | |
output songMenu1 | |
output ENDL | |
output songMenu2 | |
output ENDL | |
output songMenu3 | |
output ENDL | |
output songMenu4 | |
output ENDL | |
output songMenu5 | |
output ENDL | |
output songMenu6 | |
output ENDL | |
output songMenu7 | |
output ENDL | |
output songMenu8 | |
output ENDL | |
output songMenu9 | |
output ENDL | |
output songMenu10 | |
output ENDL | |
output songMenu11 | |
;There are only 4 options for the main menu | |
input menuSel_buffer, 5 | |
mov edx, OFFSET menuNumbers | |
mov al, menuSel_buffer | |
@@: | |
cmp al, [edx] | |
je gotSelection | |
inc tmpCounter | |
inc edx | |
push eax | |
mov al, 10 | |
cmp al, tmpCounter | |
pop eax | |
je error | |
jmp @B | |
gotSelection: | |
;Select (tmpCounter): | |
;Case 1: | |
cmp tmpCounter, 1 | |
je TwinkleTwinkle | |
;Case 2: | |
cmp tmpCounter, 2 | |
je FrereJacques | |
;Case 3: | |
cmp tmpCounter, 3 | |
je Beethoven5 | |
;Case 4: | |
cmp tmpCounter, 4 | |
je ForHesAJollyGoodFellow | |
;Case 5: | |
cmp tmpCounter, 5 | |
je TakeMeOutToTheBallgame | |
;Case 6: | |
cmp tmpCounter, 6 | |
je Reveille | |
;Case 7: | |
cmp tmpCounter, 7 | |
je SuperMarioMain | |
;Case 8: | |
cmp tmpCounter, 8 | |
je SuperMarioUNG | |
;Case 9: | |
cmp tmpCounter, 9 | |
je SuperMarioDeath | |
TwinkleTwinkle: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
mov eax, OFFSET TwinkleTwinkleSong | |
call GetChar | |
ret | |
FrereJacques: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
mov eax, OFFSET FrereJacquesSong | |
call GetChar | |
ret | |
Beethoven5: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
mov eax, OFFSET Beethoven5Song | |
call GetChar | |
ret | |
ForHesAJollyGoodFellow: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
mov eax, OFFSET ForHesAJollyGoodFellowSong | |
call GetChar | |
ret | |
TakeMeOutToTheBallgame: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
mov eax, OFFSET TakeMeOutToTheBallgameSong | |
call GetChar | |
ret | |
Reveille: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
mov eax, OFFSET ReveilleSong | |
call GetChar | |
ret | |
SuperMarioMain: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
mov eax, OFFSET SuperMarioMain1Song | |
call GetChar | |
ret | |
SuperMarioUNG: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
mov eax, OFFSET SuperMarioUNGSong | |
call GetChar | |
ret | |
SuperMarioDeath: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
mov eax, OFFSET SuperMarioDeathSong | |
call GetChar | |
ret | |
error: | |
mov tmpCounter, 1 | |
mov menuSel_buffer, 0 | |
call Error | |
ret | |
PlaySongMenu ENDP | |
; -----[ Subroutine - Write Song Menu ]----------------- | |
WriteSongMenu PROC | |
ret | |
WriteSongMenu ENDP | |
; -----[ Subroutine - Play Notes Menu ]----------------- | |
PlayNotesMenu PROC | |
output ENDL | |
output playNote1 | |
output ENDL | |
output writeSongNotes1 | |
output ENDL | |
output writeSongNotes2 | |
output ENDL | |
output writeSongNotes3 | |
output ENDL | |
output writeSongNotes4 | |
output ENDL | |
output writeSongNotes5 | |
output ENDL | |
output writeSongNotes6 | |
output ENDL | |
output writeSongNotes7 | |
output ENDL | |
output ENDL | |
output playNoteNotes1 | |
output ENDL | |
output playNoteNotes2 | |
output ENDL | |
output ENDL | |
output playNote2 | |
input song_buffer, 70 | |
mov tmpCounter, 14 | |
mov eax, OFFSET playNoteStart | |
mov edi, OFFSET playEnteredNote | |
startNote: | |
mov bl, [eax] | |
mov ds:[edi], bl | |
dec tmpCounter | |
cmp tmpCounter, 0 | |
je getNote | |
inc eax | |
inc edi | |
jmp startNote | |
getNote: | |
inc edi | |
mov tmpCounter, 1 | |
szlen song_buffer | |
add al, tmpCounter | |
mov tmpCounter, al | |
mov eax, OFFSET song_buffer | |
keepGoing: | |
mov bl, [eax] | |
mov ds:[edi], bl | |
cmp bl, 0 | |
je done | |
dec tmpCounter | |
inc eax | |
inc edi | |
jmp keepGoing | |
done: | |
mov al, comma | |
mov ds:[edi], al | |
inc edi | |
mov al, 71h | |
mov ds:[edi], al | |
mov eax, OFFSET playEnteredNote | |
call GetChar | |
ret | |
PlayNotesMenu ENDP | |
; -----[ Subroutine - Paste Song Menu ]----------------- | |
PasteSongMenu PROC | |
output ENDL | |
output pasteSong1 | |
input song_buffer, 220 | |
szlen song_buffer | |
add al, tmpCounter | |
mov tmpCounter, al | |
mov eax, OFFSET song_buffer | |
mov edi, OFFSET pastedSong | |
getSong: | |
mov bl, [eax] | |
mov ds:[edi], bl | |
cmp bl, 0 | |
jne catchChar | |
continue: | |
dec tmpCounter | |
cmp tmpCounter, 0 | |
je checkForQ | |
inc eax | |
inc edi | |
jmp getSong | |
catchChar: | |
mov tmpChar, bl | |
jmp continue | |
checkForQ: | |
mov tmpCounter, 1 | |
cmp tmpChar, 71h ; 'q' | |
je done | |
jmp error | |
error: | |
mov tmpCounter, 1 | |
mov song_buffer, 0 | |
call Error | |
ret | |
done: | |
mov tmpCounter, 1 | |
mov eax, OFFSET pastedSong | |
call GetChar | |
ret | |
PasteSongMenu ENDP | |
; -----[ Subroutine - Find Equals Character ]----------------------------- | |
FindEquals PROC ; Go through characters in | |
@@: ; RTTTL file looking for | |
; *check that we have not gone through the buffer ; '='. Increment counter | |
cmp char, 3Dh ; until '=' is found, then | |
je done ; return. | |
push eax | |
mov al, DoneVal | |
cmp char, al | |
pop eax | |
je error | |
cmp counter, 256 | |
je error | |
push eax | |
mov al, char | |
mov tmpChar, al | |
pop eax | |
call GetChar | |
jmp @B | |
error: | |
call Error | |
ret | |
done: | |
call GetChar | |
ret | |
FindEquals ENDP | |
; -----[ Subroutine - Read Tempo from RTTTL Header ]---------------------- | |
; Each keyboard character has a unique number called an ASCII value. | |
; The characters 0, 1, 2,...9 have ASCII values of 48, 49, 50,...57. | |
; You can always convert from the character representing a digit to | |
; to its value by subtracting 48 from the variable storing the digit. | |
GetTempo PROC ; Parse RTTTL file for Tempo. | |
; Convert characters to | |
@@: | |
; digits by subtracting 48 (30h) | |
cmp char, 3Ah ; from each character's ASCII | |
je tempoIf ; value. Iteratively multiply | |
push eax ; each digit by 10 if there | |
mov ax, default_b ; is another digit, then add | |
add al, char ; the most recent digit to | |
sub al, 30h | |
push dx | |
mov dx, 10 | |
mul dx | |
pop dx ; one's column. | |
mov default_b, ax ; For example, the string | |
pop eax ; "120" is (1 X 10 X 10) | |
call GetChar ; + (2 X 10) + 0. The '1' | |
; is converted first, then | |
jmp @B ; multiplied by 10. The '2' | |
; is then converted/added. | |
; 0 is converted/added, done. | |
tempoIf: | |
push eax | |
push edx | |
xor eax, eax | |
xor edx, edx | |
xor ebx, ebx | |
mov bl, divisor | |
mov ax, default_b | |
div bx | |
mov default_b, ax | |
pop edx | |
pop eax | |
jmp done | |
error: | |
call Error | |
ret | |
done: ; BPM usually expresses the number of quarter notes per minute | |
cmp default_b, 0 ; wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole note (in milliseconds) | |
je error | |
push eax | |
xor eax, eax | |
xor edx, edx | |
xor ebx, ebx | |
mov eax, 60000 | |
mov bx, default_b | |
div bx | |
xor edx, edx | |
mov dl, 4 | |
mul dx | |
mov WholeNote, ax | |
pop eax | |
call GetChar | |
ret | |
GetTempo ENDP | |
; -----[ Subroutine - Look up Octave ]------------------------------------ | |
ProcessOctave PROC ; Octave may or may not be | |
cmp char, 2Ch ;',' ; included in a given note | |
je caseElse ; because any note that is | |
mov edx, OFFSET octaves ; played in the default | |
@@: ; octave does not specify | |
push eax ; the octave. If a char | |
mov eax, [edx] ; from '5' to '8' then use | |
cmp char, al ; it, else use default_o. | |
pop eax ; Characters are converted | |
je caseSelectChangeOctave ; to digits by subtracting | |
inc edx ; '0', which is the same as | |
push eax ; subtracting 48. | |
mov eax, [edx] | |
cmp al, 41h ;If we reached the 'A' then | |
pop eax | |
je caseElse | |
jmp @B | |
caseSelectChangeOctave: | |
push eax | |
mov al, char | |
sub al, 30h | |
mov noteOctave, al | |
pop eax | |
jmp done | |
caseElse: | |
push eax | |
mov al, default_o | |
mov noteOctave, al | |
pop eax | |
jmp doneNoChar | |
error: | |
call Error | |
ret | |
setDefault_o: | |
push eax | |
mov al, noteOctave | |
mov default_o, al | |
pop eax | |
jmp done | |
done: | |
cmp default_o, 0 | |
je setDefault_o | |
call GetChar | |
ret | |
doneNoChar: | |
cmp default_o, 0 | |
je setDefault_o | |
ret | |
ProcessOctave ENDP | |
; -----[ Subroutine - Find Index of Note ]-------------------------------- | |
ProcessNote PROC | |
mov tmpCounter, 1 | |
mov index, 0 ; Set index value for lookup | |
mov edx, OFFSET Notes ; of note frequency based on | |
@@: ; note character. If 'p', | |
push eax ; index is 0. If 'a' to 'g', | |
mov al, PauseVal ; read character values in | |
cmp char, al ; DATA table and find match. | |
pop eax ; Record index value when | |
je caseP ; match is found. If next | |
push eax ; char is a sharp (#), add | |
mov eax, [edx] ; 1 to the index value to | |
cmp char, al ; increase the index (and | |
pop eax ; frequency) by 1 notch. | |
je caseAtoG ; As with other subroutines, | |
inc edx ; increment counter for each | |
push eax ; character that is processed. | |
mov al, [edx] | |
cmp al, 41h ; 'A' | |
pop eax | |
je error | |
inc tmpCounter | |
jmp @B | |
caseP: | |
mov index, 0 | |
jmp done | |
caseAtoG: ; make sure that the nex char isn't a '#', otherwise index = tmpCounter | |
call GetChar | |
cmp char, 23h | |
je caseSharp | |
push eax | |
mov al, tmpCounter | |
mov index, al | |
pop eax | |
jmp doneNoChar | |
caseSharp: | |
inc tmpCounter | |
push eax | |
mov al, tmpCounter | |
mov index, al | |
pop eax | |
jmp done | |
error: | |
mov tmpCounter, 1 | |
call Error | |
ret | |
done: | |
mov tmpCounter, 1 | |
call GetChar | |
ret | |
doneNoChar: | |
mov tmpCounter, 1 | |
ret | |
ProcessNote ENDP | |
; -----[ Subroutine - Determine Note Duration ]--------------------------- | |
ProcessDuration PROC ; Check to see if characters | |
mov edx, OFFSET NoteLengthsStr ; form 1, 2, 4, 8, 16 or 32. | |
mov tmpCounter, 1 | |
@@: ; If yes, then convert from | |
push eax | |
mov eax, [edx] | |
cmp char, al ; ASCII character to a value | |
pop eax | |
je selectDur | |
inc tmpCounter ; by subtracting 48. In the | |
inc edx ; case of 16 or 32, multiply | |
push eax ; by 10 and add the next | |
mov eax, [edx] ; digit to the ones column. | |
cmp al, 41h ; 'A' | |
pop eax | |
je caseElse ; the user entered an invalid character | |
jmp @B | |
selectDur: ; duration = char - '0' | |
; Case 1 or 16: | |
cmp tmpCounter, 1 | |
je checkNextChar | |
; Case 2: | |
cmp tmpCounter, 2 | |
je dur2 | |
; Case 4: | |
cmp tmpCounter, 3 | |
je dur4 | |
; Case 8: | |
cmp tmpCounter, 4 | |
je dur8 | |
; Case 32: | |
cmp tmpCounter, 6 | |
je dur32 | |
checkNextChar: | |
push eax | |
xor eax, eax | |
mov al, char | |
sub al, 30h | |
mov duration, ax | |
pop eax | |
push edx | |
mov edx, [eax] | |
mov tmpChar, dl | |
pop edx | |
push eax | |
mov al, 36h | |
cmp tmpChar, al | |
pop eax | |
jne dur1 | |
jmp dur16 | |
dur1: ; duration = char - '0' | |
push eax | |
xor eax, eax | |
mov al, char | |
sub al, 30h | |
mov duration, ax | |
pop eax | |
jmp checkDefault_d | |
dur2: ; duration = char - '0' | |
push eax | |
xor eax, eax | |
mov al, char | |
sub al, 30h | |
mov duration, ax | |
pop eax | |
jmp checkDefault_d | |
dur4: ; duration = char - '0' | |
push eax | |
xor eax, eax | |
mov al, char | |
sub al, 30h | |
mov duration, ax | |
pop eax | |
jmp checkDefault_d | |
dur8: ; duration = char - '0' | |
push eax | |
xor eax, eax | |
mov al, char | |
sub al, 30h | |
mov duration, ax | |
pop eax | |
jmp checkDefault_d | |
dur16: ; duration = duration * 10 + char - '0' | |
call GetChar | |
push eax | |
xor eax, eax | |
mov ax, duration | |
push edx | |
mov dl, 10 | |
mul dl | |
pop edx | |
mov duration, ax | |
mov al, char | |
sub al, 30h | |
add ax, duration | |
mov duration, ax | |
pop eax | |
jmp checkDefault_d | |
dur32: ; duration = duration * 10 + char - '0' | |
push eax | |
xor eax, eax | |
mov al, char | |
sub al, 30h | |
mov duration, ax | |
pop eax | |
call GetChar | |
push eax | |
mov al, 32h ; '2' | |
cmp char, al | |
pop eax | |
jnz error | |
push eax | |
mov ax, duration | |
push edx | |
mov dl, 10 | |
mul dl | |
pop edx | |
mov duration, ax | |
mov al, char | |
sub al, 30h | |
add ax, duration | |
mov duration, ax | |
pop eax | |
jmp checkDefault_d | |
caseElse: ; If no duration, use | |
push eax ; default. | |
xor eax, eax | |
mov ax, default_d | |
mov duration, ax | |
pop eax | |
cmp duration, 20 | |
jmp doneNoChar | |
checkDefault_d: | |
cmp default_d, 0 ; If default_d not defined | |
je setDefault_d ; (if default_d = 0), then | |
jmp done ; set default_d = to the | |
; duration from the d=#. | |
setDefault_d: | |
push eax | |
xor eax, eax | |
mov ax, duration | |
mov default_d, ax | |
pop eax | |
cmp default_b, 0 | |
jne checkDefault_d | |
jmp done | |
error: | |
mov tmpCounter, 1 | |
call Error | |
ret | |
done: | |
call GetChar | |
ret | |
doneNoChar: | |
ret | |
ProcessDuration ENDP | |
; -----[ Subroutine - Check For '.' Indicating 1.5 Duration ]------------- | |
CheckForDot PROC ; Check for dot indicating | |
push eax | |
mov al, dot | |
cmp char, al ; multiply duration by 3/2. | |
pop eax | |
je addDot ; If dot found, multiply by | |
jmp done ; 3/2 and increment counter, | |
; else, do nothing and | |
addDot: ; return. | |
push eax | |
push edx | |
mov ax, duration | |
mov dl, 3 | |
mul dl | |
pop edx | |
mov duration, ax | |
pop eax | |
call GetChar | |
jmp done | |
done: | |
ret | |
CheckForDot ENDP | |
; -----[ Subroutine - Find Comma and Play Note/Duration ]----------------- | |
PlayNote PROC ; Find last comma in the | |
cmp char, 2Ch ; current note entry. Then, | |
je calcFreqandDur ; fetch the note frequency | |
jmp error ; from data, and play it, or | |
; pause if frequency = 0. | |
calcFreqandDur: | |
push eax | |
mov eax, OFFSET Octave8 | |
add al, index | |
add al, index | |
mov edx, [eax] | |
mov noteFreq, dx | |
mov al, 8 | |
sub al, noteOctave | |
mov noteOctave, al | |
mov edx, OFFSET (octaveVals + 1) | |
add dl, noteOctave | |
mov eax, [edx] | |
push ebx | |
push edx | |
xor ebx, ebx | |
mov bl, al | |
xor eax, eax | |
mov ax, noteFreq | |
xor edx, edx | |
div bx | |
mov noteFreq, ax | |
xor eax, eax | |
xor edx, edx | |
xor ebx, ebx | |
mov ax, WholeNote | |
mov bx, duration | |
div bx | |
mov duration, ax | |
pop edx | |
pop ebx | |
pop eax | |
cmp noteFreq, 0 | |
je pauseDur | |
jmp playFreq | |
pauseDur: | |
mov tmpAddr, eax | |
xor eax, eax | |
mov ax, duration | |
push eax ; specify time to sleep | |
call _Sleep@4 ; Sleep for duration | |
mov eax, tmpAddr | |
jmp done | |
playFreq: | |
mov tmpAddr, eax | |
xor eax, eax | |
mov edi, OFFSET beepMem | |
mov ax, noteFreq | |
mov [edi], eax | |
xor eax, eax | |
mov ax, duration | |
mov [edi + ARRAY_TYPE], eax | |
pushad | |
mov edi, OFFSET beepMem ; load address of our array into EDI | |
mov eax, [edi] | |
mov ebx, [edi + ARRAY_TYPE] | |
push ebx ; duration | |
push eax ; frequency | |
call _Beep@8 ; Beep( frequency, duration ); | |
; If Beep succeeds, the return value is nonzero. | |
; EAX contains result of Beep, see if there were any errors | |
or eax, eax | |
;jnz @F ; success | |
call _GetLastError@0 | |
; TODO: report error and exit | |
; EAX contains the error code to report | |
popad ; restore all registers from the stack | |
mov eax, tmpAddr | |
jmp done | |
error: | |
call Error | |
ret | |
done: | |
call GetChar | |
ret | |
PlayNote ENDP | |
; -----[ Subroutine - Get Next Char in RTTL String ]----------------- | |
GetChar PROC | |
push edx | |
mov dl, [eax] | |
mov char, dl | |
pop edx | |
inc counter | |
inc eax | |
ret | |
GetChar ENDP | |
; -----[ Subroutine - Invalid Input ]----------------- | |
Error PROC | |
output error1 | |
output ENDL | |
mov ERRORFLAG, 1 | |
ret | |
Error ENDP | |
END _main ; Marks the end of the module and sets the program entry point label |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment