Cantus Firmus, Part I

This module explores the basics of composing a cantus firmus.

The cantus firmus is a melody that forms the basis for polyphonic music and other musical textures that employ strict counterpoint, including specifies counterpoint exercises. The cantus firmus must exhibit certain musical qualities to be considered “well-formed,” including smoothness, motion, and variety.

We’ll use the tinyNotation feature of music21 to produce an example of a well-formed cantus firmus composed by Johann Joseph Fux (transposed to C major):

from music21 import *

fux = converter.parse('tinyNotation: 4/4 c1 d1 e1 c1 A1 B1 c1 g1 e1 c1 d1 c1')

fux.show()

To learn more about tinyNotation, check out this page. Play the example on the piano, or use MIDI playback if your XML viewer supports it.

In this module, we’ll take an algorithmic approach to generating an original cantus firmus, gradually applying more and more of the rules and norms of the genre. (Note that many of the “rules” given below are intentionally simplified for the purposes of this module, and can be expanded or substituted by the instructor as necessary.)

Let’s start with the following:

  1. Our cantus firmus will span a range of one octave or less.
  2. Our cantus firmus will use only the notes of C major.
  3. Our cantus firmus will contain 8-16 notes.

Let’s use MIDI note numbers, starting with the octave over the range 60-72.

We can use the random library to generate a random integer in this range:

import random

random.randint(60,72)
> 64

The random.randint() function generates a random integer within the range specified (inclusive). Each time you run this function, you will get a different result.

Now let’s ensure that the output is in the key of C major. We’ll use a while loop structure assuming that the note is not in the key of C major until proved otherwise.

Then we’ll use the key object from music21 to generate a list of notes in the key (using a comprehension), against which we compare our random note. If the note is not in C major, we run the loop again. If it is, we print the note and change the value of in_c to True to stop the loop:

in_c = False

while in_c == False:
 my_note = random.randint(60,72)
 if my_note not in [p.midi for p in key.Key('C', 'major').pitches]:
  in_c = False
 else:
  print(my_note)
  in_c = True	
> 62

If you run the block of code several times, you should get a variety of different notes, but all of them should be in C major (60, 62, 64, 65, 67, 69, 71, or 72). (Remember that you must reset the value of in_c to False each time as well.)

We can turn this into a function to simplify things (or at least so we don’t have to keep manually resetting in_c):

def random_note():
 in_c = False
 while in_c == False:
  note = random.randint(60,72)
  if note not in [p.midi for p in key.Key('C', 'major').pitches]:
   in_c = False
  else:
   return note
   in_c = True

random_note_in_c()
> 71

Of course, we actually want to do this several times–once for each note in our cantus firmus. We can use a for loop to run the function repeatedly. Let’s say we want a 12-note cantus like the Fux example above:

for i in range(12):
 print(random_note())

Or we could randomize the length within our stated range of 8-16 notes:

my_cantus = [random_note() for i in range(random.randint(8,16))]
> [62, 62, 60, 72, 67, 69, 67, 65]

Now let’s add one final criteria: we must begin and end our cantus firmus on the tonic note C (MIDI note number: 60). We can use the insert() method to add the value 60 to the beginning of the list, and append() to add it to the end of our list:

my_cantus.insert(0, 60)

my_cantus.append(60)

my_cantus
> [60, 62, 62, 60, 72, 67, 69, 67, 65, 60]

If the first and last notes of our cantus firmus are already determined, we need to generate two fewer random numbers, so our range becomes 6-14. We can put all of these steps together in an enclosing function, nesting the first function inside:

def make_a_cantus_firmus():
 my_cantus = [random_note() for i in range(random.randint(6,14))]
 my_cantus.insert(0, 60)
 my_cantus.append(60)
 return my_cantus

finished_cf = make_a_cantus_firmus()

finished_cf
> [60, 60, 62, 69, 60, 62, 67, 64, 64, 60]

We can view our cantus firmus as notation by converting it into a music21 stream object. We’ll set each pitch to the corresponding MIDI note number, and set each duration to a whole note:

cf_notation = stream.Stream()
     
for i in finished_cf:
 n = note.Note()
 n.duration.type = 'whole'
 n.pitch.midi = i
 cf_notation.append(n)

cf_notation.show()

Try playing through yours. Does it sound like a cantus firmus? It might, but if it doesn’t, that’s OK–there’s actually a lot more that goes into it, and we’ve just scratched the surface. And of course, you can always run the function again!

Check out the next module to continue refining our cantus firmus algorithm.

Extensions

  1. What are other qualities of a well-formed cantus firmus? How could we represent these as constraints in our algorithm?
  2. Modify both functions so that the user can specify the starting note (and therefore, the key) of the cantus firmus.

Further Reading

Check out the Open Music Theory entry on composing a cantus firmus.