EECS151 / riscv-cpu / scripts / musicxml_parser.py
musicxml_parser.py
Raw
import wave
import random
import struct
import sys
import zipfile
from xml.dom import minidom
import os
import glob
import shutil

step_to_int = {
    'A': 1,
    'B': 3,
    'C': 4,
    'D': 6,
    'E': 8,
    'F': 9,
    'G': 11
}

# Remove the temp directory from previous runs
if (os.path.isdir('temp/')):
    shutil.rmtree('temp/')

# Fetch the filepath to the compressed MusicXML file (.mxl)
musicxml_filepath = sys.argv[1]

# Fetch the filepath to the generated memory contents file
memory_contents_filepath = sys.argv[2]

# Unzip the compressed file to the temp directory
zip_ref = zipfile.ZipFile(musicxml_filepath, 'r')
zip_ref.extractall('temp/')
zip_ref.close()

# Fetch and parse the XML file in the temp directory
xml_filepath = glob.glob("temp/*.xml")
#print(xml_filepath)
xmldoc = minidom.parse(xml_filepath[0])

# Print the XML tree for debugging
#print(xmldoc.toprettyxml())

# For the first part in the sheet music
parts = xmldoc.getElementsByTagName('part')[0]

notes = []

# Loop through every measure and pull each note
for measure in parts.getElementsByTagName('measure'):
    print("measure %s" % (measure.getAttribute('number')))
    for note in measure.getElementsByTagName('note'):
        rest_check = note.getElementsByTagName('rest')
        if (len(rest_check) > 0):
            duration = int(note.getElementsByTagName('duration')[0].childNodes[0].nodeValue)
            print("\trest note, duration %d" % (duration))
            notes += [(0, 0, 0, duration)]
        else:
            note_pitch = note.getElementsByTagName('pitch').item(0)
            step = note_pitch.getElementsByTagName('step')[0].childNodes[0].nodeValue
            octave = int(note_pitch.getElementsByTagName('octave')[0].childNodes[0].nodeValue)
            alter = note_pitch.getElementsByTagName('alter')
            if (len(alter) > 0):
                alter = int(alter[0].childNodes[0].nodeValue)
            else:
                alter = 0
            duration = int(note.getElementsByTagName('duration')[0].childNodes[0].nodeValue)
            note_type = note.getElementsByTagName('type')[0].childNodes[0].nodeValue
            print("\tnote with step %s octave %d alter %d duration %d type %s" % (step, octave, alter, duration, note_type))
            notes += [(step, octave, alter, duration)]

note_list = []
for note in notes:
    if (note[0] == 0):
        note_list += [(0, note[3])]
    else:
        semitone_above_below_middle_C = step_to_int[note[0]] - step_to_int['C'] + note[2]
        if (step_to_int[note[0]] >= step_to_int['C']):
            note_number = semitone_above_below_middle_C + ((note[1] - 4) * 12)
        else:
            note_number = semitone_above_below_middle_C + ((note[1] - 3) * 12)
        note_number = note_number + 40
        frequency = (2 ** ((float(note_number) - 49.0)/12.0)) * 440.0
        note_list += [(int(frequency), note[3])]

print("note_list (frequency, duration)\n")
print(note_list)

# Translate each note to the equivalent frequency and to the individual half-periods of 32nd notes
with open(memory_contents_filepath, 'w') as memory_file:
    for note in note_list:
        if (note[0] == 0):
            for i in range(0, 4 * note[1]):
                memory_file.write(str(0) + "\n")
            continue
        # Write the note in terms of tone_switch_period (125 Mhz clock)
        for i in range(0, 4 * note[1]):
            memory_file.write(str(int(125e6/note[0]/2)) + "\n")
        # Write a note pause for each note corresponding to its duration
        for i in range(0, note[1]):
            memory_file.write(str(0) + "\n")

# Remove the temp directory from current run
if (os.path.isdir('temp/')):
    shutil.rmtree('temp/')