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