Transposition Functions

This module builds on the Transposition module by illustrating how to create transposition-related functions.

In the previous module, we covered how to identify how two melodies were related by transposition. In this module, we’ll create a few transposition-related functions.

Let’s start out with a function to transpose a given melody. We’ll use the motif from Marion Bauer’s Viola Sonata, as in the previous module:

motif = [48, 54, 59, 57, 60, 71]

If we want to transpose it, we simply add the value n from the operation Tn to each value in the list. For example, if we say n = 5 and use a list comprehension:

transposed_melody = [i + 5 for i in motif]

transposed_melody
> [53, 59, 64, 62, 65, 76]

To turn this into a function, we want to be able to specify not only the melody, but also the value for n. So we’ll need not one but two arguments:

def transposer(melody, n_val):
 transposed_melody = []
 transposed_melody = [i + n_val for i in melody]
 return transposed_melody

Let’s use it to bring the motif down one octave:

transposer(motif, -12)
> [36, 42, 47, 45, 48, 59]

We can assign the transposed melody to a new variable directly:

new_version = transposer(motif, -12)

new_version
> [36, 42, 47, 45, 48, 59]

For transposing pitch classes, we add the mod (%) 12 function to our function:

def pc_transposer(melody, n_val):
 transposed_melody = []
 transposed_melody = [(i + n_val) % 12 for i in melody]
 return transposed_melody

Then our result will always use pitch classes (even if the input is in pitches):

pc_melody = [0, 6, 11, 9, 0, 11]

pc_transposer(pc_melody, 6)
> [6, 0, 5, 3, 6, 5]

This process is relatively straightforward, and doesn’t go far beyond what we’ve previously covered. A more intriguing challenge would be an analysis function: given two melodies, is there a transposition-based relationship between them?

In the previous module, we used a for loop to calculate the pitch-class intervals between the notes in any two melodies. To turn this into a robust function, we have to think through the process a little more deliberately.

First, since the user can choose to analyze melodies of any length, our range should be dependent on the length of the melodies (which must be the same length):

for x in range(len(melody1)):
 print((melody1[x] - melody2[x]) % 12)

This code is a good starting point in that it gives us the pitch-class intervals between each pair of corresponding notes in each melody.

Now let’s start to plan our function by making a list of the things it will have to do:

  1. Find the pitch-class intervals between each pair of notes.
  2. Determine whether there’s a transposition-based relationship.
  3. If yes, output the n value.
  4. If no, print “Not related by transposition.”

We’ll collect all of the pitch-class intervals into a list, and then determine whether they are all the same by using a conditional if statement with Python’s set data type. Unlike lists, sets do not allow duplicates, so if the length of the set is 1, that means that all of the list values were identical.

Also note that we subtract pitches in melody1 from melody2 so that the output makes logical sense based on the format of the input. Finally, we’ll convert the for loop above into a list comprehension:

def transpose_analysis(melody1, melody2):
 list_of_n_vals = []
 list_of_n_vals = [(melody2[x] - melody1[x]) % 12 for x in range(len(melody1))]
 if len(set(list_of_n_vals)) == 1:
  print("Related by transposition at n =", list_of_n_vals[0])
 else:
  print("Not related by transposition")

It works as expected:

a = [60, 62, 63]
b = [49, 51, 52]

transpose_analysis(a,b)
> Related by transposition at n = 1

Remember, we’re using pitch classes instead of pitches directly (it’s more common to refer to transposition levels modulo 12). That’s why we get a 1 here instead of a -11. (If we wanted to get pitch transpositions, we could simply eliminate the mod (%) 12 from the function.)

A final feature we can add to our function is to check whether the two melodies have the same number of notes before proceeding. Performing this check will prevent Python from generating an error, and instead send a message to the user:

def transpose_analysis(melody1, melody2):
 if len(melody1) == len(melody2):
  list_of_n_vals = []
  list_of_n_vals = [(melody2[x] - melody1[x]) % 12 for x in range(len(melody1))]
  if len(set(list_of_n_vals)) == 1:
   print("Related by transposition at n =", list_of_n_vals[0])
  else:
   print("Not related by transposition")
 else:
  print("Melodies must contain the same number of notes!")

Now we have a robust analysis function that will be useful in many contexts. Give it a try!

Extensions

  1. What is the relationship between the n values when the melodies are reversed (i.e. “a,b” vs. “b,a”)?
  2. Can you adapt this function so that it can accept other types of input besides lists of MIDI numbers? For example, music21 streams?
  3. Could we adapt this to recognize not only literal transposition, but also diatonic transposition?