Extracting Harmonies from Leadsheets#

Say you have one or a lot of leadsheets in musicXML format such as Effendi’s Realbook collection

[1]:
from music21 import chord, key, stream, converter, harmony
from pprint import pprint
import json
[2]:
f = chord.Chord('F4 C5 A5')
kf = key.Key('F')
sf = stream.Measure([kf, f])
sf.show()
_images/Extracting_Harmonies_2_0.png

Loading a Song#

Assuming you have already downloaded Effendi’s Whole collection to your local machine you can use music21 to open them and get a lot of information out.

[ ]:
bm = converter.parse('/mnt/Datos/RealBooks/effendi.me/jazz/repo/II/Black Ice.xml')
bm.show()
_images/Extracting_Harmonies_4_0.png
[ ]:
bm.analyze('key')
<music21.key.Key of d minor>
[ ]:
bm.metadata.all()
[('composer', 'Jeff Lorber\nArr. 6EQUJ5'),
 ('copyright', '©2016 Finmusic/effendi.me 5/21/2016'),
 ('movementName', 'Black Ice')]
[ ]:
bm.metadata.title
'Black Ice'
[ ]:
bm.metadata.composer
'Jeff Lorber\nArr. 6EQUJ5'
[ ]:
bm.getTimeSignatures().show()
<<<<<<< local <modified: >
/home/fccoelho/Documentos/Projects_software/musicir/.venv/lib/python3.10/site-packages/music21/musicxml/m21ToXml.py:477: MusicXMLWarning: <music21.stream.Score 0x7f629d7409a0> is not well-formed; see isWellFormedNotation()
  warnings.warn(f'{scOut} is not well-formed; see isWellFormedNotation()',
=======
/home/fccoelho/Documentos/Software_projects/musicir/venv/lib/python3.10/site-packages/music21/musicxml/m21ToXml.py:477: MusicXMLWarning: <music21.stream.Score 0x7ff0b0746ef0> is not well-formed; see isWellFormedNotation()
  warnings.warn(f'{scOut} is not well-formed; see isWellFormedNotation()',
>>>>>>> remote <modified: >
_images/Extracting_Harmonies_9_1.png
[ ]:
m = bm.measure(1)
[ ]:
m.show()
_images/Extracting_Harmonies_11_0.png

However, if you want to extract the harmony, i.e. the sequence of chords, measure by measure. Music21 does not help much. That’s when you will resort to MusicIR SongParser.

[ ]:
from musicir.leadsheets.musicxml import ChordParser, SongParser, import_into_db
[ ]:
SP = SongParser("/mnt/Datos/RealBooks/effendi.me/jazz/repo/II/Black Ice.xml")
SP.number_of_measures
62
[ ]:
c= SP.get_measure_chords(1)[0]
[ ]:
Ch = ChordParser(c)
Ch
B -
[15]:
pprint(json.loads(SP.chords_as_json()))
[{'chords': [], 'measure': 0},
 {'chords': ['B - '], 'measure': 1},
 {'chords': ['E - ', 'F - '], 'measure': 2},
 {'chords': ['E - ', 'A - '], 'measure': 3},
 {'chords': ['E - ', 'F - '], 'measure': 4},
 {'chords': ['E - ', 'A - '], 'measure': 5},
 {'chords': ['A - '], 'measure': 6},
 {'chords': ['E - ', 'F - '], 'measure': 7},
 {'chords': ['E - ', 'A - '], 'measure': 8},
 {'chords': ['E - ', 'F - '], 'measure': 9},
 {'chords': ['E - ', 'A - '], 'measure': 10},
 {'chords': ['E - ', 'F - '], 'measure': 11},
 {'chords': ['E - ', 'A - '], 'measure': 12},
 {'chords': ['E - ', 'F - '], 'measure': 13},
 {'chords': ['E - ', 'A - '], 'measure': 14},
 {'chords': ['E - ', 'D - ', 'G - '], 'measure': 15},
 {'chords': ['C - ', 'B - '], 'measure': 16},
 {'chords': ['D - ', 'G - ', 'C - ', 'E♭ - '], 'measure': 17},
 {'chords': ['B♭ - ', 'A♭ - ', 'G - '], 'measure': 18},
 {'chords': ['F♯ - '], 'measure': 19},
 {'chords': ['C♯ - '], 'measure': 20},
 {'chords': ['F♯ - '], 'measure': 21},
 {'chords': ['C♯ - '], 'measure': 22},
 {'chords': ['F♯ - '], 'measure': 23},
 {'chords': ['C♯ - '], 'measure': 24},
 {'chords': ['F♯ - '], 'measure': 25},
 {'chords': ['G - ', 'B - '], 'measure': 26},
 {'chords': ['E - ', 'F - '], 'measure': 27},
 {'chords': ['E - ', 'A - '], 'measure': 28},
 {'chords': ['E - ', 'F - '], 'measure': 29},
 {'chords': ['E - ', 'A - '], 'measure': 30},
 {'chords': ['E - ', 'F - '], 'measure': 31},
 {'chords': ['E - ', 'A - '], 'measure': 32},
 {'chords': ['E - ', 'F - '], 'measure': 33},
 {'chords': ['E - ', 'A - '], 'measure': 34},
 {'chords': ['E - ', 'F - '], 'measure': 35},
 {'chords': ['E - ', 'A - '], 'measure': 36},
 {'chords': ['E - ', 'A - '], 'measure': 37},
 {'chords': ['E - ', 'D - ', 'G - '], 'measure': 38},
 {'chords': ['C - ', 'B - '], 'measure': 39},
 {'chords': ['D - ', 'G - ', 'C - ', 'E♭ - '], 'measure': 40},
 {'chords': ['B♭ - ', 'A♭ - ', 'G - '], 'measure': 41},
 {'chords': ['F♯ - '], 'measure': 42},
 {'chords': ['C♯ - '], 'measure': 43},
 {'chords': [], 'measure': 44},
 {'chords': [], 'measure': 45},
 {'chords': [], 'measure': 46},
 {'chords': [], 'measure': 47},
 {'chords': [], 'measure': 48},
 {'chords': [], 'measure': 49},
 {'chords': [], 'measure': 50},
 {'chords': [], 'measure': 51},
 {'chords': [], 'measure': 52},
 {'chords': [], 'measure': 53},
 {'chords': [], 'measure': 54},
 {'chords': [], 'measure': 55},
 {'chords': ['F♯ - '], 'measure': 56},
 {'chords': ['B - '], 'measure': 57},
 {'chords': ['F♯ - '], 'measure': 58},
 {'chords': ['C♯ - '], 'measure': 59},
 {'chords': ['F♯ - '], 'measure': 60},
 {'chords': ['G - ', 'B - '], 'measure': 61}]
[24]:
for m in range(SP.number_of_measures):
    h = SP.get_measure_chords(m)
    if h:
        print(f"Measure {m}: ", end='')
    for i, c in enumerate(h):
        if i < len(h) - 1:
            print(ChordParser(c), end=', ')
        else:
            print(f" {ChordParser(c)}")

Measure 1:  B -
Measure 2: E - ,  F -
Measure 3: E - ,  A -
Measure 4: E - ,  F -
Measure 5: E - ,  A -
Measure 6:  A -
Measure 7: E - ,  F -
Measure 8: E - ,  A -
Measure 9: E - ,  F -
Measure 10: E - ,  A -
Measure 11: E - ,  F -
Measure 12: E - ,  A -
Measure 13: E - ,  F -
Measure 14: E - ,  A -
Measure 15: E - , D - ,  G -
Measure 16: C - ,  B -
Measure 17: D - , G - , C - ,  E♭ -
Measure 18: B♭ - , A♭ - ,  G -
Measure 19:  F♯ -
Measure 20:  C♯ -
Measure 21:  F♯ -
Measure 22:  C♯ -
Measure 23:  F♯ -
Measure 24:  C♯ -
Measure 25:  F♯ -
Measure 26: G - ,  B -
Measure 27: E - ,  F -
Measure 28: E - ,  A -
Measure 29: E - ,  F -
Measure 30: E - ,  A -
Measure 31: E - ,  F -
Measure 32: E - ,  A -
Measure 33: E - ,  F -
Measure 34: E - ,  A -
Measure 35: E - ,  F -
Measure 36: E - ,  A -
Measure 37: E - ,  A -
Measure 38: E - , D - ,  G -
Measure 39: C - ,  B -
Measure 40: D - , G - , C - ,  E♭ -
Measure 41: B♭ - , A♭ - ,  G -
Measure 42:  F♯ -
Measure 43:  C♯ -
Measure 56:  F♯ -
Measure 57:  B -
Measure 58:  F♯ -
Measure 59:  C♯ -
Measure 60:  F♯ -
Measure 61: G - ,  B -
[25]:
SP.get_measure_melody(2)[0]
<Element 'note' at 0x7f629aff0450>
[21]:
SP.melody_as_json()
[21]:
'[{"measure": 0, "notes": ["E"]}, {"measure": 1, "notes": ["G", "A", "A\\u266f", "B", "D", "E", "G", "D", "D"]}, {"measure": 2, "notes": ["Rest whole"]}, {"measure": 3, "notes": ["Rest whole"]}, {"measure": 4, "notes": ["Rest whole"]}, {"measure": 5, "notes": ["Rest whole"]}, {"measure": 6, "notes": ["Rest quarter", "Rest quarter", "Rest quarter", "E", "D", "B\\u266d", "A"]}, {"measure": 7, "notes": ["A", "B\\u266d", "A", "A"]}, {"measure": 8, "notes": ["Rest quarter", "Rest eighth", "B", "D", "D\\u266f", "F", "D", "E", "F", "G", "A"]}, {"measure": 9, "notes": ["B", "G", "F", "E", "A", "G", "Rest eighth", "Rest half"]}, {"measure": 10, "notes": ["Rest half", "Rest quarter", "G", "E", "B\\u266d", "A"]}, {"measure": 11, "notes": ["A", "B\\u266d", "A", "A"]}, {"measure": 12, "notes": ["Rest half", "D", "B\\u266d", "A", "G", "A", "G", "D", "E"]}, {"measure": 13, "notes": ["E", "G", "Rest quarter"]}, {"measure": 14, "notes": ["Rest half", "Rest quarter", "E", "D", "B\\u266d", "A"]}, {"measure": 15, "notes": ["Rest half", "Rest quarter", "B", "D", "B"]}, {"measure": 16, "notes": ["G", "A", "Rest 16th", "E", "G", "G", "G", "A", "C", "D"]}, {"measure": 17, "notes": ["D", "E\\u266d", "D", "C", "D", "Rest 16th", "A", "C", "C", "B\\u266d", "G", "D"]}, {"measure": 18, "notes": ["F", "B\\u266d", "Rest 16th", "C", "F", "F", "E\\u266d"]}, {"measure": 19, "notes": ["Rest whole"]}, {"measure": 20, "notes": ["Rest whole"]}, {"measure": 21, "notes": ["Rest whole"]}, {"measure": 22, "notes": ["Rest whole"]}, {"measure": 23, "notes": ["Rest whole"]}, {"measure": 24, "notes": ["Rest whole"]}, {"measure": 25, "notes": ["Rest half", "Rest eighth", "E"]}, {"measure": 26, "notes": ["G\\u266f", "A", "A\\u266f", "B", "D", "E", "G", "D", "D"]}, {"measure": 27, "notes": ["Rest whole"]}, {"measure": 28, "notes": ["Rest whole"]}, {"measure": 29, "notes": ["Rest whole"]}, {"measure": 30, "notes": ["Rest whole"]}, {"measure": 31, "notes": ["Rest whole"]}, {"measure": 32, "notes": ["Rest whole"]}, {"measure": 33, "notes": ["Rest whole"]}, {"measure": 34, "notes": ["Rest whole"]}, {"measure": 35, "notes": ["Rest whole"]}, {"measure": 36, "notes": ["Rest whole"]}, {"measure": 37, "notes": ["Rest whole"]}, {"measure": 38, "notes": ["Rest whole"]}, {"measure": 39, "notes": ["Rest whole"]}, {"measure": 40, "notes": ["Rest whole"]}, {"measure": 41, "notes": ["Rest whole"]}, {"measure": 42, "notes": ["Rest whole"]}, {"measure": 43, "notes": ["Rest whole"]}, {"measure": 44, "notes": ["Rest whole"]}, {"measure": 45, "notes": ["Rest whole"]}, {"measure": 46, "notes": ["Rest whole"]}, {"measure": 47, "notes": ["Rest whole"]}, {"measure": 48, "notes": ["Rest whole"]}, {"measure": 49, "notes": ["Rest whole"]}, {"measure": 50, "notes": ["Rest whole"]}, {"measure": 51, "notes": ["Rest whole"]}, {"measure": 52, "notes": ["Rest whole"]}, {"measure": 53, "notes": ["Rest whole"]}, {"measure": 54, "notes": ["Rest whole"]}, {"measure": 55, "notes": ["Rest whole"]}, {"measure": 56, "notes": ["Rest half", "Rest eighth", "E"]}, {"measure": 57, "notes": ["G\\u266f", "A", "A\\u266f", "B", "D", "E", "G", "D", "D"]}, {"measure": 58, "notes": ["Rest whole"]}, {"measure": 59, "notes": ["Rest whole"]}, {"measure": 60, "notes": ["Rest half", "Rest eighth", "E"]}, {"measure": 61, "notes": ["G\\u266f", "A", "A\\u266f", "B", "D", "E", "G", "D", "D"]}]'
[ ]:
Suppose you want to store the analyzed songs into a database for further analysys
[32]:
from glob import glob
[33]:
songs = glob('/home/fccoelho/Downloads/effendi.me/jazz/repo/*.xml', recursive=True)
[22]:
import_into_db('/home/fccoelho/Downloads/effendi.me/jazz/repo/')
[ ]: