In one of the last posts, we extracted the chords from a MusicXML formatted file. In this post, we're going to do two things:
First of all, let's concentrate on the first item
The first question to ask is: how is a note coded? In our case, we're going to work with Autumn Leaves as our sample file. After analyzing the tune, I've noticed that the whole lead sheet is a sequence of measures. Let's look at the first measure to see how the first note in the melody, a B, is embedded with its chord.
So what we need to do is to parse each measure by finding all blocks containing an harmonic part, the chords that's followed by notes.
import xml.etree.cElementTree as ET
xml = file("files/autumn_leaves.xml", 'r').readlines()
tree = ET.fromstringlist(xml)
first_measure = tree.find(".//measure")
first_measure
<Element 'measure' at 0x462b350>
chords = first_measure.findall("harmony/root/root-step")
chords
[<Element 'root-step' at 0x462b910>]
chords[0].text
'E'
for note in first_measure.findall("note/pitch/step"):
print note.text
B C D F E
This allows us to easily get the notes. However, there are two sorts of alterations that need to be taken into account:
First, let's see if we can take into account the accidental alterations.
for note in first_measure.findall("note/pitch"):
if note.find("alter") != None:
alter = note.find("alter").text
print note.find("step").text, alter
else:
print note.find("step").text
B C 1 D 1 F 1 E
We can easily take into account this sort of alteration by using a function already coded in a previous notebook that shifts a note by a required number of semitones.
def increment_note(note, half_tones):
notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
ind = notes.index(note)
return notes[(ind + half_tones) % 12]
for note in first_measure.findall("note/pitch"):
if note.find("alter") != None:
alter = note.find("alter").text
print increment_note(note.find("step").text, int(alter))
else:
print note.find("step").text
B C# D# F# E
After seing the output of this function, it turns out that the key alteration is in fact already coded within the note. That's a good point for us because then we don't need to take into account a behaviour of the style: "if this note is in the key accidentals then make it sharp or flat". We can just use the notes right away.
From what we've seen before, it seems logical to parse the song on a per-measure way. Using what we learnt before, we can loop over each child of each measure. If the child is a harmony element, we can set the active chord to be the one extracted from there. If it is a note element, we can increment the note count over a given chord.
def get_chord_from_harmony_tag(harmony_tag):
root = harmony_tag.find('root/root-step').text
alter_elem = harmony_tag.find('root/root-alter')
if alter_elem != None:
root = increment_note(root, int(alter_elem.text))
kind_attrib = harmony_tag.find('kind').attrib['text']
return " ".join([root, kind_attrib])
get_chord_from_harmony_tag(first_measure.find("harmony"))
'E m'
def get_note_from_note_tag(note_tag):
pitch = note_tag.find("pitch")
if pitch != None: #in case the note is a rest
note = pitch.find("step").text
if pitch.find("alter") != None:
note = increment_note(note, int(pitch.find("alter").text))
return note
else:
return None
note_tag = first_measure.findall("note")[1]
get_note_from_note_tag(note_tag)
'B'
we now have the needed helper functions and can turn to the main loop.
def write_note_to_dict(note, current_chord, note_chord_dict):
if not current_chord in note_chord_dict:
note_chord_dict[current_chord] = [0] * 12
ind = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'].index(note)
note_chord_dict[current_chord][ind] += 1
current_chord = None
note_chord_dict = {}
for measure in tree.findall(".//measure"):
for child in measure:
if child.tag == 'harmony':
current_chord = get_chord_from_harmony_tag(child)
if child.tag == 'note':
note = get_note_from_note_tag(child)
if current_chord != None and note != None:
write_note_to_dict(note, current_chord, note_chord_dict)
note_chord_dict
{'A ': [0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0], 'A 7': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 'A m': [1, 0, 1, 0, 3, 0, 8, 9, 0, 7, 0, 0], 'A m7': [2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 'B 7': [1, 1, 0, 1, 2, 0, 20, 8, 0, 8, 0, 4], 'B 7#5': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 'C 7': [0, 0, 0, 0, 5, 0, 1, 4, 0, 0, 1, 0], 'C Maj7': [2, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 2], 'D 7': [4, 0, 5, 0, 3, 0, 2, 0, 0, 1, 0, 0], 'E 7': [4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 4], 'E 7b9': [2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2], 'E m': [0, 1, 0, 1, 5, 0, 5, 4, 0, 0, 0, 6], 'E m7': [0, 0, 0, 1, 8, 0, 6, 10, 0, 0, 0, 2], 'F# 7': [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], 'F# m7b5': [4, 0, 1, 0, 2, 0, 3, 1, 0, 5, 0, 1], 'G ': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3], 'G 7': [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], 'G Maj7': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6]}
figure(figsize=(10, 10))
note_arr = array([note_chord_dict[key] for key in note_chord_dict])
matshow(note_arr, fignum=False, cmap=cm.gray)
xticks(arange(0, 12), ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']);
yticks(arange(0, len(note_chord_dict.keys())), note_chord_dict.keys());
colorbar(shrink=0.75)
<matplotlib.colorbar.Colorbar instance at 0x07AB9AF8>