To speed up some analyses and avoid doing everything by hand, I quickly wrote some Python functions to perform transformational music theory. You can find the source code at the bottom of this post. I usually save it as a “neor.py” file to use it as a module. Note that there already seems to exist a Python library, “Sator”, for atonal music analysis available here (though it hasn’t been updated since 2012).

For now, the objective of neor is more modest: it simply allows one to get the transformations between the specified chords. For example, if we want to find the transformation from the T/I group which transforms F# major into A minor, we would write

import neor as nr nr.TI_operation("Fs","Am") 'I_10'

If now we want to find the transformation from the PRL group between the same chords, we would write

import neor as nr nr.PRL_operation("Fs","Am") '(RL)^6R'

The transformations in the PRL group are given using the normal form with , and .

You can also get the transformations in a given progression. For example, using the famous Beethoven progression, we have:

nr.TI_progression(["C","Am","F","Dm","Bb","Gm","Eb","Cm","Ab","Fm","Cs","Bbm"]) [('C', 'Am', 'I_4'), ('Am', 'F', 'I_9'), ('F', 'Dm', 'I_2'), ('Dm', 'Bb', 'I_7'), ('Bb', 'Gm', 'I_0'), ('Gm', 'Eb', 'I_5'), ('Eb', 'Cm', 'I_10'), ('Cm', 'Ab', 'I_3'), ('Ab', 'Fm', 'I_8'), ('Fm', 'Cs', 'I_1'), ('Cs', 'Bbm', 'I_6')] nr.PRL_progression(["C","Am","F","Dm","Bb","Gm","Eb","Cm","Ab","Fm","Cs","Bbm"]) [('C', 'Am', 'R'), ('Am', 'F', '(RL)^11R'), ('F', 'Dm', 'R'), ('Dm', 'Bb', '(RL)^11R'), ('Bb', 'Gm', 'R'), ('Gm', 'Eb', '(RL)^11R'), ('Eb', 'Cm', 'R'), ('Cm', 'Ab', '(RL)^11R'), ('Ab', 'Fm', 'R'), ('Fm', 'Cs', '(RL)^11R'), ('Cs', 'Bbm', 'R')]

You will notice in the souce code that I have implemented another group of transformations, which is also an extension of by , but with a non-trivial 2-cocycle. I haven’t quite figured its real interest yet in music, but the calculations sure are easier.

The purpose of the remaining functions should be quite evident. I don’t yet know if I will extend this module into something more complex. Feel free to comment if you are interested !

And now for the source code:

chords = {'C':(0,1), 'Cs':(1,1), 'D':(2,1), 'Eb':(3,1), 'E':(4,1), 'F':(5,1), 'Fs':(6,1), 'G':(7,1), 'Ab':(8,1), 'A':(9,1), 'Bb':(10,1), 'B':(11,1), 'Cm':(0,11), 'Csm':(1,11), 'Dm':(2,11), 'Ebm':(3,11), 'Em':(4,11), 'Fm':(5,11), 'Fsm':(6,11), 'Gm':(7,11), 'Abm':(8,11), 'Am':(9,11), 'Bbm':(10,11), 'Bm':(11,11)} TI_operations = {(0,1):"T_0", (1,1):"T_1", (2,1):"T_2", (3,1):"T_3", (4,1):"T_4", (5,1):"T_5", (6,1):"T_6", (7,1):"T_7", (8,1):"T_8", (9,1):"T_9", (10,1):"T_10", (11,1):"T_11", (0,11):"I_7", (1,11):"I_8", (2,11):"I_9", (3,11):"I_10", (4,11):"I_11", (5,11):"I_0", (6,11):"I_1", (7,11):"I_2", (8,11):"I_3", (9,11):"I_4", (10,11):"I_5", (11,11):"I_6"} PRL_operations = {(0,1):"e", (1,1):"(RL)^7", (2,1):"(RL)^2", (3,1):"(RL)^9", (4,1):"(RL)^4", (5,1):"(RL)^11", (6,1):"(RL)^6", (7,1):"(RL)^1", (8,1):"(RL)^8", (9,1):"(RL)^3", (10,1):"(RL)^10", (11,1):"(RL)^5", (0,11):"(RL)^3R", (1,11):"(RL)^8R", (2,11):"(RL)^1R", (3,11):"(RL)^6R", (4,11):"(RL)^11R", (5,11):"(RL)^4R", (6,11):"(RL)^9R", (7,11):"(RL)^2R", (8,11):"(RL)^7R", (9,11):"R", (10,11):"(RL)^5R", (11,11):"(RL)^10R"} #### def LEFT_2CHI_operation(chord1,chord2): if (type(chord1)==str): n1,k1 = chords[chord1] else: n1,k1 = chord1 if (type(chord2)==str): n2,k2 = chords[chord2] else: n2,k2 = chord2 z = (k2*k1) % 12 if (k1==11 and z==11): x = (n2-z*n1+6) % 12 else: x = (n2-z*n1) % 12 return (x,z) def RIGHT_2CHI_operation(chord1,chord2): if (type(chord1)==str): n1,k1 = chords[chord1] else: n1,k1 = chord1 if (type(chord2)==str): n2,k2 = chords[chord2] else: n2,k2 = chord2 z = (k2*k1) % 12 if (k1==11 and z==11): x = ((n2-n1+6)*k1) % 12 else: x = ((n2-n1)*k1) % 12 return (x,z) def TI_operation(chord1,chord2,as_numeric=False): if (type(chord1)==str): n1,k1 = chords[chord1] else: n1,k1 = chord1 if (type(chord2)==str): n2,k2 = chords[chord2] else: n2,k2 = chord2 z = (k2*k1) % 12 x = (n2-z*n1) % 12 if as_numeric: return (x,z) else: return TI_operations[(x,z)] def PRL_operation(chord1,chord2,as_numeric=False): if (type(chord1)==str): n1,k1 = chords[chord1] else: n1,k1 = chord1 if (type(chord2)==str): n2,k2 = chords[chord2] else: n2,k2 = chord2 z = (k2*k1) % 12 x = ((n2-n1)*k1) % 12 if as_numeric: return (x,z) else: return PRL_operations[(x,z)] #### def TI_progression(list_chords,as_numeric=False): list_op = [(x,y,TI_operation(x,y,as_numeric)) for i,x in enumerate(list_chords) for j,y in enumerate(list_chords) if j==(i+1)] return list_op def PRL_progression(list_chords,as_numeric=False): list_op = [(x,y,PRL_operation(x,y,as_numeric)) for i,x in enumerate(list_chords) for j,y in enumerate(list_chords) if j==(i+1)] return list_op def LEFT_2CHI_progression(list_chords): list_op = [(x,y,LEFT_2CHI_operation(x,y)) for i,x in enumerate(list_chords) for j,y in enumerate(list_chords) if j==(i+1)] return list_op def RIGHT_2CHI_progression(list_chords): list_op = [(x,y,RIGHT_2CHI_operation(x,y)) for i,x in enumerate(list_chords) for j,y in enumerate(list_chords) if j==(i+1)] return list_op #### def TI_interactions(list_chords,as_numeric=False,get_reversed=True): list_op = [(x,y,TI_operation(x,y,as_numeric)) for i,x in enumerate(list_chords) for j,y in enumerate(list_chords) if (i!=j or get_reversed)] return list_op def PRL_interactions(list_chords,as_numeric=False,get_reversed=True): list_op = [(x,y,PRL_operation(x,y,as_numeric)) for i,x in enumerate(list_chords) for j,y in enumerate(list_chords) if (i!=j or get_reversed)]