Just a quick follow up to the last post about using Google Chart Tools to outline melodic contours from MusicXML files …
I wanted to add support for compressed MusicXML files in addition to the non-compressed ones. So far, the code I've got seems to be working with the two or three compressed MusicXML files from Wikifonia I tested.
Here's a screenshot below of A-Ha's "Take On Me", one of the best songs from the 80's with one of the absolute best videos, too! To make the graph I passed it to the app a la "http://localhost:8083/?mxml=http://static.wikifonia.org/1934/musicxml.mxl".

Here's the video:
Keep in mind the contour script doesn't take repeats into account and that the entire melody repeats three times in the song.
Also, I don't like to make code downloadable if I'm still working on it because I don't want to junk up my web directory, but I'll paste everything essential below: the Google App Engine YAML file, the Python code, and the Jinja/HTML template.
YAML:
application: museline version: 1 runtime: python27 api_version: 1 threadsafe: true handlers: - url: /stylesheets static_dir: stylesheets - url: /.* script: museline.app libraries: - name: jinja2 version: latest - name: lxml version: latest
Python:
### museline.py
### 2012, Nitin Arora
### import modules
import urllib
from lxml import etree
import math
import re
import webapp2
import jinja2
import os
jinja_environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
#####
class museline(webapp2.RequestHandler):
def get(self):
### read MusicXML file
try:
url = self.request.get('mxml')
## url = 'http://blog.humaneguitarist.org/uploads/i_heart_thee.xml' #test line
if url[-4:] == '.xml': # uncompressed MusicXML
readUrl = urllib.urlopen(url).read()
else: # compressed MusicXML
### References:
# http://stackoverflow.com/a/8858735
# http://stackoverflow.com/questions/1313845/if-i-have-the-contents-of-a-zipfile-in-a-python-string-can-i-decompress-it-with
from cStringIO import StringIO
compressed = urllib.urlopen(url)
compressedString = StringIO(compressed.read())
import zipfile
zipped = zipfile.ZipFile(compressedString, "r")
archiveFiles = zipped.namelist()
## self.response.out.write(archiveFiles) # test line
for archiveFile in archiveFiles:
if archiveFile[-4:] == ".xml" and "/" not in archiveFile:
realXML = archiveFile
extracted = zipped.open(realXML,'r')
readUrl = extracted.read()
## self.response.out.write(readUrl) # test line
except:
errorMessage = '''<pre>
You must pass an "mxml" parameter.
If you have but still see this message, then there is a problem accessing/reading the MusicXML file.
</pre>'''
self.response.out.write(errorMessage)
return
### setup pitch values
notes = ['C','D','E','F','G','A','B']
i = 0
noteVals = {}
for note in notes:
if note == 'C' or note == 'F':
noteVals[note] = i + 1
i = i + 1
else:
noteVals[note] = i + 2
i = i + 2
### parse MusicXML file
parsed = etree.XML(readUrl)
### get basic descriptive metadata
metadata = []
elementList = ['work-title',
'work-number',
'movement-number',
'movement-title',
'creator[@type="composer"]',
'creator[@type="lyricist"]']
for element in elementList:
xpath = str(".//%s") %element
if parsed.find(xpath) !=None:
found = parsed.find(xpath).text
att = re.match(r'(.*)type="(.*)\"', element)
if att:
element = att.group(2)
if found:
metadata.append((element,found))
## self.response.out.write(metadata) # test line
### access part one tree
part = parsed.find('.//part[@id="P1"]')
pitches = part.findall('.//pitch')
## self.response.out.write(str(len(pitches)) + " pitches.\n") # test line, number of notes (non-rests)
## self.response.out.write(str(len(pitches)*.618) + " Golden Ratio.\n") # test line, maybe something for the future.
### put pitch values in a list
pitchList = []
i = 1
for pitch in pitches:
if pitch.find('.//alter') != None:
alter = int(pitch.find('.//alter').text)
else:
alter = 0
step = pitch.find('.//step')
octave = int(pitch.find('.//octave').text)
pitchPos = str('pitch: ' + str(i))
pitchClassVal = ((int(noteVals[step.text]) + alter)) * .01
pitchVal = ((int(noteVals[step.text]) + alter) + (octave * 12)) * .01
label = (pitchPos, pitchVal, pitchClassVal)
pitchList.append(label)
i = i + 1
## for pitch in pitchList: # test block
## self.response.out.write(str(pitch)+'<br>')
#data for the Jinja template
template_values = {
'pitchList': pitchList,
'url': url,
'metadata': metadata}
template = jinja_environment.get_template('museline.html')
self.response.out.write(template.render(template_values)) #write data to the html template
app = webapp2.WSGIApplication([('/', museline)],
debug=True)
Template:
<!DOCTYPE HTML>
<!-- museline.html -->
<html>
<head>
<title>
museline
</title>
<link type="text/css" rel="stylesheet" href="/stylesheets/style.css" />
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load('visualization', '1', {packages: ['corechart']});
</script>
<script type="text/javascript">
function drawVisualization() {
// Create and populate the data table.
var data = google.visualization.arrayToDataTable([
['pitch position', 'melodic contour'],
{% for pitch in pitchList %}
['{{ pitch[0] }}', {{ pitch[1] }}],
{% endfor %}
]);
// Create and draw the visualization.
new google.visualization.LineChart(document.getElementById('visualization')).
draw(data, {curveType: "function",
width: 800, height: 400,
vAxis: {maxValue: 1}}
);
}
google.setOnLoadCallback(drawVisualization);
</script>
</head>
<body>
<div id="visualization"></div>
<p>Metadata:</p>
<ul>
{% for metadatum in metadata %}
<li>{{ metadatum[0] }} : {{ metadatum[1] }}</li>
{% endfor %}
<li>URL: <a href="{{ url }}">{{ url }}</a></li>
</ul>
</body>
</html>
--------------

