Klimperklamper
Published February 12, 2022
· 1 min read
This is a small Python script I wrote to generate twelve-tone rows and variations thereof, formatted for LilyPond. It can also generate a script for MuseScore to automatically input the generated notes.
The origin of this project was a music assignment in school, where we had to compose a piece using the twelve-tone technique. I wanted to explore the possibilities of algorithmic composition and decided to write a script that could generate random twelve-tone rows and their variations. Because the music sounds quite chaotic, I named the project "Klimperklamper", which is a playful German term for random, clattering sounds.
It might be a poorly written script, but it was a fun exercise in Python programming and music theory.
import random
import sys
import os
slowLength = [ 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2 ]
mediumLength = [ 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4 ]
fastLength = [ 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16 ]
notes = [ "c", "cis", "d", "dis", "e", "f", "fis", "g", "gis", "a", "ais", "b" ]
string = '\\version "2.22.1"\n\\header {\ntitle = "Die Dissonanz der Schulzeit"\nsubtitle = "Musik Klausurersatzleistung"\ncomposer = "David Penkowoj"\ncopyright = ""\ntagline = ""\n}\n\\score {\n\\fixed c\' {\n\\time 4/4\n\\tempo "Allegro" 4 = 160\n'
variationDict = {
"normal": [],
"mirror": [],
"reverse": [],
"reverseMirror": [],
}
def mirror(noteCopy):
result = []
mirrorAxisNote = noteCopy[0]
baseIndex = notes.index(mirrorAxisNote)
for note in noteCopy:
index = notes.index(note)
newNoteIndex = index + (- 2 * (index - baseIndex))
result.append(notes[newNoteIndex % len(notes)])
return result
def reverse(noteCopy):
return noteCopy[::-1]
def reverseMirror(noteCopy):
return mirror(noteCopy)[::-1]
def randomVariation(rand):
rand = random.randrange(1, 5)
if rand == 1:
return variationDict["normal"].copy(), "Grundreihe"
if rand == 2:
return variationDict["mirror"].copy(), "Umkehrung"
if rand == 3:
return variationDict["reverse"].copy(), "Krebs"
if rand == 4:
return variationDict["reverseMirror"].copy(), "Krebsumkehrung"
def getLength(notesCopy, location):
lengthList = []
if location <= 1 or location >= 7:
lengthList = slowLength.copy()
elif location >= 3 and location <= 5:
lengthList = fastLength.copy()
else:
lengthList = mediumLength.copy()
return lengthList
def changeLength(notesCopy, location):
for note in notesCopy:
index = notesCopy.index(note)
rand = random.choice(lengthList)
notesCopy[index] = (f"{note}{rand} ")
lengthList.remove(rand)
return notesCopy
def generateRows():
notesCopy = notes.copy()
random.shuffle(notesCopy)
variationDict["normal"] = notesCopy
variationDict["mirror"] = mirror(notesCopy)
variationDict["reverse"] = reverse(notesCopy)
variationDict["reverseMirror"] = reverseMirror(notesCopy)
def makeMusescore():
global string
string = "#!/usr/local/bin/xdotool\n\n"
generateRows()
for i in range(4):
articualtedVariation, variationType = randomVariation(i)
getLength(articualtedVariation, i)
variation = " ".join(articualtedVariation)
string = f"{string}type {variation}\nkey Enter\n"
with open("/tmp/klimperklamper", "w") as file:
file.write(string)
file.close()
os.system("nvim /tmp/klimperklamper")
def makePDF():
global string
generateRows()
for i in range(8):
articualtedVariation, variationType = randomVariation(i)
changeLength(articualtedVariation, i)
variation = " ".join(articualtedVariation)
string = f"{string}\\mark \\markup \\smaller \\italic {variationType} {variation}|\\break\n"
string = string + "}\n\\midi {} \n\\layout {\nindent = 0\\mm\n}\n}"
with open("/tmp/klimperklamper", "w") as file:
file.write(string)
file.close()
os.system("nvim /tmp/klimperklamper")
os.system("lilypond -o /tmp/klimperklamper /tmp/klimperklamper")
os.system("zathura /tmp/klimperklamper.pdf")
if __name__ == "__main__":
if sys.argv[1] == "musescore":
makeMusescore()
else:
makePDF()