Reading and Playing Notes in Syscall 33
#-----------------------------------------------------------------------
# Lab6.asm
#
# Subroutines to interpret and play a song that is encoded in a string.
#
#
# REGISTER USAGE:
# $a0 - $a3: used to give the arguments for subroutines and syscall
# $v0, $v1: used to save the return values for subroutines
# $t0: used as a temporary register in the subroutines
# $s0 - $s2: used to save subroutine data in subroutines that call other
# subroutines, their initial value is preserved
# In play_song:
# $s0 = used to save initial string pointer and to save
# read rhythm
# $s1 = used to save the number of notes
# $s2 = used to save the constant for converting beats to ms
# In get_song_length:
# $s0 = used to count the number of notes in the song
# In read_note:
# $s0 = used to save the read pitch
#-----------------------------------------------------------------------
.text
.globl play_song # declare all subroutines as global so they can
.globl get_song_length # be accessed from other asm files
.globl play_note
.globl read_note
.globl get_pitch
.globl get_rhythm
#-----------------------------------------------------------------------
# play_song
#
# input: $a0 - address of first character in string containing song
# $a1 - tempo of song in beats per minute
#-----------------------------------------------------------------------
play_song:
addi $sp, $sp, -16 # allocate space in stack for 4 words
sw $ra, 0($sp) # save return address in stack
sw $s0, 4($sp) # save s0 in stack
sw $s1, 8($sp) # save s1 in stack
sw $s2, 12($sp) # save s2 in stack
move $s0, $a0 # save string pointer in $s0
li $t0,240000 # 4* 60s * 1000 ms in a second
div $t0, $a1 # divide by tempo
mflo $s2 # save result in $s2, this will be used to convert beats to ms
jal get_song_length # get number of notes to play
move $s1, $v0 # save note count in $s1
move $a0, $s0 # restore initial string pointer in $a0
li $a1, 1 # default rhythm of 4 beats
loop:
jal read_note # read a note
srl $s0, $v0, 16 # get rhythm of last note and save it in s0
andi $a0, $v0, 0xFFFF # get pitch from bits 15:0 of returned note
div $a1, $s2, $s0 # convert rhythm beats to milliseconds
jal play_note # play the note
move $a0, $v1 # set last position as start of string for next loop
move $a1, $s0 # get rhythm of last note and set it as default rhythm for next one
addi $s1, $s1, -1 # decrement number of notes to play
bnez $s1, loop # repeat while there are more notes
lw $ra, 0($sp) # load return address from stack
lw $s0, 4($sp) # load s0 from stack
lw $s1, 8($sp) # load s1 from stack
lw $s2,12($sp) # load s2 from stack
addi $sp, $sp, 16 # restore stack pointer
jr $ra
#-----------------------------------------------------------------------
# get_song_length
#
# input: $a0 - address of first character in string containing song
#
# output: $v0 - number of notes in song
#-----------------------------------------------------------------------
get_song_length:
addi $sp, $sp, -8 # allocate space in stack for 2 words
sw $ra, 0($sp) # save return address in stack
sw $s0, 4($sp) # save $s0 in stack
li $s0, 1 # $s0 will count the number of notes, initialize to 1
count:
jal read_note # read a note
lb $t0, 0($v1) # load last character
beqz $t0, endcnt # if we reached end of string, exit
move $a0, $v1 # else, set last position as start of string for next loop
addi $s0, $s0, 1 # increment number of notes
j count
endcnt:
move $v0, $s0 # return number of notes
lw $ra, 0($sp) # load return address from stack
lw $s0, 4($sp) # load $s0 from stack
addi $sp, $sp, 8 # restore stack pointer
jr $ra
#-----------------------------------------------------------------------
# play_note
#
# input: $a0 - pitch
# $a1 - note duration in milliseconds
#
# output: no output arguments, play a one-note using syscall system service 33
#-----------------------------------------------------------------------
play_note:
li $a2, 0 # use piano as the MIDI instrument
li $a3, 127 # use maximum volume
bnez $a0, skip # if the pitch is zero, is no sound
li $a0, 0 # use 0 as volume
skip:
li $v0, 33 # call the system service 33, MIDI out
syscall
jr $ra
#-----------------------------------------------------------------------
# read_note
#
# input: $a0 - address of first character in string containing note encoding
# $a1 - rhythm of previous note
#
# output: $v0 - note rhythm in bits [31:16], note pitch in bits [15:0]
# note rhythm (1 = 4 beats, 2 = 2 beats, 4 = 1 beat,
# 8 = 1/2 beat, 16 = 1/4 beat)
# note pitch: (MIDI value, 0-127)
# $v1 - address of first character of what would be next note
#-----------------------------------------------------------------------
read_note:
addi $sp, $sp, -8 # allocate space in stack for 2 words
sw $ra, 0($sp) # save return address in stack
sw $s0, 4($sp) # save $s0 in stack
spc1: # remove initial spaces
lb $t0,0($a0) # load character from song
beqz $t0,rnend # if it's end of string exit, no more notes
bne $t0,' ', rpitch # it it's not space, read pitch
addi $a0, $a0, 1 # advance to next char in song
j spc1 # read another char
rpitch:
jal get_pitch # get pitch
move $s0, $v0 # save pitch in $s0
move $a0, $v1 # use position after pitch as input to find rhythm
jal get_rhythm
sll $v0, $v0, 16 # shift rhythm to bits 31:16
add $v0, $v0, $s0 # add pitch in low part of $v0, bits 15:0
rnend:
lw $ra, 0($sp) # load return address from stack
lw $s0, 4($sp) # load $s0 from stack
addi $sp, $sp, 8 # restore stack pointer
jr $ra
#-----------------------------------------------------------------------
# get_pitch
#
# input: $a0 - address of first character in string containing note encoding
#
# output: $v0 - MIDI pitch value
# $v1 - address of character after pitch is determined
# e.g. if a portion of the song looks like c'' f4
# $v1 will point to ---------------------^
#-----------------------------------------------------------------------
get_pitch:
lb $t0, 0($a0) # load character from song
addi $a0, $a0, 1 # advance to next character
ifa:
bne $t0, 'a', ifb # if it's a
li $v0, 57
b getmod # get modifiers
ifb:
bne $t0, 'b', ifc # if it's b
li $v0, 59
b getmod # get modifiers
ifc:
bne $t0, 'c', ifd # if it's c
li $v0, 60
b getmod # get modifiers
ifd:
bne $t0, 'd', ife # if it's d
li $v0, 62
b getmod # get modifiers
ife:
bne $t0, 'e', iff # if it's e
li $v0, 64
b getmod # get modifiers
iff:
bne $t0, 'f', ifg # if it's f
li $v0, 65
b getmod # get modifiers
ifg:
bne $t0, 'g', ifr # if it's g
li $v0, 67
b getmod # get modifiers
ifr: # if it's r
li $v0, 0 # we will use pitch 0 as no sound
getmod:
lb $t0, 0($a0) # load character from song
ifes:
bne $t0, 'e', ifis # if it's e, we will assume 'es'
add $v0, $v0, -1 # is a flat, decrease pitch
add $a0, $a0, 2 # advance to next char skipping 'es'
lb $t0, 0($a0) # load character from song
j getoct # see if there are octave modifiers
ifis:
bne $t0, 'i', getoct # if it's i, we will assume 'is'
add $v0, $v0, 1 # is a sharp, increase pitch
add $a0, $a0, 2 # advance to next char skipping 'is'
lb $t0, 0($a0) # load character from song
getoct:
bne $t0, 0x2c, ifapo # if it's ,
add $v0, $v0, -12 # decrease pitch by 12
add $a0, $a0, 1 # advance to next char in song
lb $t0, 0($a0) # load character from song
j getoct # repeat to check for more octave modifiers
ifapo:
bne $t0, 0x27, gpend # if it's '
add $v0, $v0, 12 # increase pitch by 12
add $a0, $a0, 1 # advance to next char in song
lb $t0, 0($a0) # load character from song
j getoct # repeat to check for more octave modifiers
gpend:
move $v1, $a0 # save last song position in $v1
jr $ra
#-----------------------------------------------------------------------
# get_rhythm
#
# input: $a0 - address of character in string containing song encoding
# after pitch is determined
# --> e.g. if song portion looks like: c'' f4
# $a0 will point to ------------------^
# --> e.g. if song portion looks like: aes,8 f16
# $a0 will point to -------------------^
# $a1 - previous note rhythm
#
# output: $v0 - note rhythm, default to previous note rhythm if no number
# is present in note encoding
# (1 = 4 beats, 2 = 2 beats, 4 = 1 beat, 8 = 1/2 beat, 16 = 1/4 beat)
# $v1 - address of first character of next note
#-----------------------------------------------------------------------
get_rhythm:
move $v0, $a1 # by default return previous note rhythm
lb $t0, 0($a0) # load current character from song
if1:
bne $t0, '1', if2 # if it's 1
li $v0, 1 # set rhythm to 4 beats
add $a0, $a0, 1 # advance to next char in song
lb $t0, 0($a0) # load current character from song
if16:
bne $t0, '6', grend # if it's 16
li $v0, 16 # set rhythm to 1/4 beat
j grnxt
if2:
bne $t0, '2', if4 # if it's 2
li $v0, 2 # set rhythm to 2 beats
j grnxt
if4:
bne $t0, '4', if8 # if it's 4
li $v0, 4 # set rhythm to 1 beat
j grnxt
if8:
bne $t0, '8', grend # if it's 8
li $v0, 8 # set rhythm to 1/2 beat
grnxt:
add $a0, $a0, 1 # advance to next char in song
grend:
move $v1, $a0 # return current position in song
jr $ra