Voice Roles, Part II

This module is a continuation of the previous module exploring the roles of the different voices in a four-part texture.

We’ll continue with J. S. Bach’s Jesu, meine Freude:

Let’s load the chorale in music21:

from music21 import *

chorale = corpus.parse('bach/bwv87.7.mxl')


The four voices are aligned vertically in the score to reflect their usual relationship to one another in terms of range. But we might be interested to know how often the voices cross, such that, for example, the soprano is lower than the alto, or the bass is higher than the tenor?

In order to undertake this kind of analysis, we can use the chordify() tool in music21. This function takes all four parts and consolidates them into a single line so that we can analyze the notes together. This makes it easier to compare which note is highest or lowest, etc.

We’ll set the optional “addPartIdAsGroup” parameter to true so that each note in the chord remains associated with its original part:

chorale_chords = chorale.chordify(addPartIdAsGroup=True)


It’s not so pleasing to look at it, but it makes analysis a breeze. Now we can view each successive “chord” to see its notes using the .getElementsByClass tool:

> <music21.chord.Chord D3 D4 F4 A4>

> <music21.chord.Chord E3 D4 F4 A4>

(A couple of things here: First, we need recurse() to access all levels of a stream. When we’re looking for notes in a chord, for example, we need to search through multiple hierarchical levels. Second, note that these first two chords correspond to the first two eighth notes of the chorale, reflecting the change in harmony as the bass changes notes while the top three voices remain as they are.)

We can observe that the notes are always given from low to high from left to right. Accordingly, to find out which note is lowest, we will look for the note in the leftmost position in the sequence (index 0).

Here’s the lowest note in the first two chords:

> <music21.pitch.Pitch D3>

> <music21.pitch.Pitch E3>

Next, if we use the .groups property, we can link each note to its original part (again, looking at the first two chords):

> 'Bass'

> 'Bass'

Let’s start by counting the number of times the lowest voice is not the bass. We’ll use a for loop to iterate over each chord, then a list comprehension to generate a list from the parts (in order from lowest to highest). Finally, an if statement counts each time the lowest part is not the bass:

i = 0

for chord in chorale_chords.recurse().getElementsByClass('Chord'):
 parts = [pitch.groups for pitch in chord.pitches]
 if parts[0][0] != 'Bass':
  i = i + 1

> 7

(The extra index number in “parts[0][0]” is necessary because each part is formatted as an element in a single-item list.)

There are only seven points at which the bass is not the lowest part in the musical texture, out of 81 “chords”:

len([chord for chord in chorale_chords.recurse().getElementsByClass('Chord')])
> 81

Let’s ask another, similar question: how often is the soprano not the highest part?

i = 0

for chord in chorale_chords.recurse().getElementsByClass('Chord'):
 parts = [p.groups for p in chord.pitches]
 if parts[-1][0] != 'Soprano':
  i = i + 1

> 0

(We use index “-1” instead of “3” because if two parts share the same pitch there will be nothing located at index number 3, but the rightmost element will always be the highest voice.)

The soprano part, which always carries the melody in Bach’s chorales, is also always the highest voice in this chorale. Indeed, as we might expect, the bass and soprano are consistently the lowest and highest voices, respectively.


  1. Design a function for each analysis.
  2. What approaches might you use to analyze the alto and tenor parts along similar lines?
  3. Looking at a single chorale is certainly informative, but we need a bigger sample size to be able to make general claims about Bach’s style. How could we perform the analyses above (and those in the previous module) over all of Bach’s chorales at once? Hint: Start with this:
all_chorales = stream.Opus()

all_chorales = [chorale for chorale in corpus.chorales.Iterator()]