Home

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 $(RL)^nR^p$ with $n \in \{0,1,...,12\}$, and $p \in \{0,1\}$.

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 $\mathbb{Z}_{12}$ by $\mathbb{Z}_2$, 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)]