Skip to content

Instantly share code, notes, and snippets.

@hephaestus9
Last active December 5, 2015 20:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hephaestus9/81e04d7ad8e25b17e00e to your computer and use it in GitHub Desktop.
Save hephaestus9/81e04d7ad8e25b17e00e to your computer and use it in GitHub Desktop.
RTTTL Player in ASM
; 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