Musik ist hier Programm! (2)

(Dies ist Teil 2 der Serie „Musik ist hier Programm!“  – aber der erste mit richtig Musik und Code.  Die Einleitung gibt’s hier.)

Wenn Musikexperten über Musik (egal ob Klassik, Jazz oder Pop) sprechen, klingt das häufig ziemlich mathematisch: „Die 5. Stufe einer Moll-Tonleiter entspricht der 3. Stufe der parallelen Dur-Tonleiter“, „der C7/9/13-Akkord“, „die II-V-I-Verbindung“ (hier sogar mit römischen Zahlen!) usw.  Das liegt daran, dass Musik auf eine absolut faszinierende Weise kreative Freiheit mit mathematischer Regelhaftigkeit verbindet — und gerade in diesem scheinbaren Widerspruch liegt ein großer Reiz.

Auch wir fangen mathematisch an, nämlich indem wir die Halbtonschritte, aus denen die chromatische Tonleiter besteht, durchnummerieren.  Also: c=0, cis=1, d=2, dis=3, …, h=11, c (eine Oktave höher) = 12, usw.   Genauso macht das auch das MIDI-Protokoll (übersichtlich an einer Klaviatur dargestellt z.B. hier).

In Python können wir damit eine chromatische Tonleiter, d.h. 12 aufeinanderfolgende Halbtonschritte, ganz einfach so beschreiben:

chromatic_scale = range(13)

Mein Vorschlag wäre übrigens, dass ihr den interaktiven Python-Interpreter öffnet und die Beispiele direkt dorthin kopiert und ausführt.  Am Ende dieses Artikels findet ihr alle Definitionen auch nochmal am Stück.

Euer „Dialog“ mit dem interaktiven Interpreter könnte also ungefähr so aussehen (die letzte Zeile stellt die Ausgabe des Interpreters dar):

chromatic_scale = range(13)
list(chromatic_scale)
==> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

Für eine direkte MIDI-Ausgabe wäre das ja gar nicht schlecht… aber leider kann Lilypond mit Zahlen alleine noch nichts anfangen. Wir müssen unsere Zahlen also vor der Ausgabe in Notennamen umwandeln. Wie das Format für Notennamen von LilyPond aussieht, steht hier.

Für den Anfang bestimmen wir Notennamen und Oktavangabe mit der folgenden einfachen Funktion:

def midi2lily(pitch):
    names = ["c", "cis", "d", "dis", "e", "f", "fis", "g", "gis", "a", "ais", "b"]
    octave, semi = divmod(pitch, 12)
    octave_marker = "'" if octave > 0 else ","
    return names[semi] + abs(octave)*octave_marker

Mit einer List Comprehension können wir diese Funktion auf die gesamte chromatische Tonleiter anwenden:

[midi2lily(pitch) for pitch in chromatic_scale]
==> ['c', 'cis', 'd', 'dis', 'e', 'f', 'fis', 'g', 'gis', 'a', 'ais', 'b', "c'"]

Schön, schön. Bei der letzten Note sieht man auch, dass noch ein Apostroph angehängt wurde, weil dort eine neue Oktave beginnt. Die Funktion scheint also zu tun, was sie soll.

(Aber Achtung: Diese Funktion ist musikalisch oft nicht sinnvoll… z.B. sollte in F-Dur nicht ‚ais‘ verwendet werden, sondern ‚bes‘ (LilyPond-Notation für die Note „B“), damit nicht ein Ais (A mit Kreuzzeichen), sondern B (H mit B-Vorzeichen) geschrieben wird. Darum kümmern wir uns in der nächsten Folge.)

Indem wir nur bestimmte Töne aus der chromatischen Skala auswählen, können wir andere Tonleitern erzeugen. Das gehen wir bald systematisch an – für den Anfang können wir aber z.B. eine Ganztonleiter produzieren, indem wir nur jeden zweiten Halbton verwenden:

[midi2lily(pitch) for pitch in chromatic_scale if pitch % 2 == 0]
==> ['c', 'd', 'e', 'fis', 'gis', 'ais', "c'"]

Das sieht ja schon ganz gut aus!  Ein zweites Beispiel (unter Verwendung von itertools.chain):

from itertools import chain
motiv1 = [pitch for pitch in chromatic_scale if pitch % 2 == 0]
motiv2 = reversed(motiv1)
theme = chain(motiv1, motiv2)

[midi2lily(pitch) for pitch in theme]
==> ['c', 'd', 'e', 'fis', 'gis', 'ais', "c'", "c'", 'ais', 'gis', 'fis', 'e', 'd', 'c']

Ah, schön: Wir haben ein musikalisches Thema beschrieben, das aus einer aufsteigenden, gefolgt von einer absteigenden Ganztonleiter besteht.  Interessant daran ist v.a. die Art, wie wir das in Python ausgedrückt haben.  Das Thema besteht aus zwei Motiven, nämlich zuerst der Ganztonleiter und dann einer Wiederholung des ersten Motivs — aber rückwärts gespielt!  In der Musik nennt man so etwas „Krebs“. Indem wir das zweite Motiv mittels reversed(motiv1) definieren, können wir diese Krebs-Beziehung direkt und kompakt beschreiben.  Wir bewegen uns im Programm also auf einer viel höheren Abstraktionsebene als im späteren Notentext, wo der Zusammenhang zwischen den Motiven komplett verlorengeht.  Auch wenn das motiv1 ein anderes wäre —  motiv2 wäre immer dessen Krebs, denn wir haben nicht einzelne Noten definiert, sondern genau diese Beziehung zwischen den beiden Motiven.  Sehr spannend — und wir werden das noch viel weiter treiben!

Bisher erzeugen wir nur Python-Listen, die Notennamen enthalten.  Natürlich muss aus einer solchen Liste irgendwann ein String werden, der in einer Lilypond-Datei verwendet werden kann. Definieren wir uns also gleich mal:

def join(it):
    return " ".join(it)

Die folgende Zeile z.B. nimmt also jedes zweite Element der chromatischen Tonleiter, übersetzt es in einen Notennamen und fügt alle diese Namen zu einem String zusammen:

join(midi2lily(p) for p in chromatic_scale if p % 2 == 0)
==> "c d e fis gis ais c'"

Übrigens: Wer sich jetzt wundert, dass hier keine eckigen Klammern mehr vorkommen, d.h. die List Comprehension aus dem vorigen Beispiel irgendwie verschwunden ist, der hat gut beobachtet! join() wird hier mit einer „generator comprehension“ aufgerufen, d.h. es wird keine Liste, sondern ein Generator erzeugt, dessen konkrete Werte erst berechnet werden, wenn wirklich auf die einzelnen Elemente zugegriffen werden soll. Wer mehr über List Comprehensions und Generator Comprehensions wissen will, kann sich
ja mal diesen Artikel von Guido van Rossum himself anschauen oder auch diesen.

Bevor wir nun aber über Tonleitern und Akkorde nachdenken, sollten wir zuerst noch die Infrastruktur schaffen, um wirklich mit Lilypond Noten darstellen und sie als MIDI-Datei anhören zu können.  Die Ganztonleiter von gerade eben beispielsweise, müsste zumindest in die folgende Lilypond-Datei „verpackt“ werden:

\version "2.16.2"    % compatibility information for future LilyPond versions

\score {
{ c d e fis gis ais c'}

\layout { }

\midi { }
}

Diese Datei kann LilyPond dann verarbeiten und eine PDF-Datei erzeugen, die so aussieht:minimal-ausschnittHier ist vielleicht noch nicht alles, wie wir es uns vorstellen (wollten wir wirklich so tiefe Töne?), aber ein Anfang ist es allemal.

Wie bekommen wir unsere Daten also in das LilyPond-Format?  Ich benutze für so etwas gerne Pythons template strings. Damit bauen wir die Lilypond-Datei einfach nach, verwenden aber an den enscheidenden Stellen Platzhalter (hier z.B. $melody und $speed):

from string import Template

SIMPLE_LP_MELODY = """
\\version "2.16.2"   % compatibility information for future LilyPond versions

\\score {
{ $melody }

\\layout { }
\\midi { \\tempo 2 = $speed}
}
"""

Wir können das z.B. so benutzen:

>>> adict = dict(melody="c d e fis gis ais c'", speed=120)
>>> text = Template(SIMPLE_LP_MELODY).substitute(adict)
>>> print(text)

Auf Dauer noch hilfreicher wird das, wenn die Ersetzungen selbst wieder Platzhalter enthalten dürfen und wir dann (quasi rekursiv) so lange ersetzen, bis ein String ohne Platzhalter entstanden ist.  Das machen wir mit folgender Funktion:

def fill_template(tmpl, adict):
    previous = ""
    while tmpl != previous:
        previous = tmpl
        tmpl = Template(tmpl).safe_substitute(adict)
    return tmpl

Damit könnten wir im interaktiven Python Interpreter jetzt z.B. definieren:

motiv1 = [pitch for pitch in chromatic_scale if pitch % 2 == 0]
motiv2 = reversed(motiv1)
theme = chain(motiv1, motiv2)
melody = join(midi2lily(p) for p in theme)
speed = 120
text = fill_template(SIMPLE_LP_MELODY, locals())
print(text)

und erhalten als Ausgabe tatsächlich eine komplette LilyPond-Beschreibung:


\version "2.16.2"   % compatibility information for future LilyPond versions

\score {
{ c d e fis gis ais c' c' ais gis fis e d c }

\layout { }
\midi { \tempo 4 = 120}
}

Der obige Code enthält einen praktischen Python-Trick (an der Grenze zum Hack): Die Funktion locals() liefert ein Dictionary mit den lokal definierten Namen und ihren aktuellen Werten.  Wenn ich z.B. locals() in einer Funktion verwende, sind das alle dort definierten Variablen und die Aufrufparameter.  Im Klartext: Die Werte für die in SIMPLE_LP_MELODY verwendeten Platzhalter werden direkt aus den lokalen Variablen ermittelt, ohne dass wir dazu ein anderes Dictionary bauen müssen.  Wem das zu nahe an schwarzer Magie ist, der schreibt:

text = fill_template(SIMPLE_LP_MELODY, dict(melody=melody, speed=speed))

Egal in welcher Variante, die Variable text enthält nun eine sinnvolle Eingabe für LilyPond.  Also schnell in eine Datei damit:

def save_file(astring, fn="output.ly"):
    out_file = open(fn, "wt")
    out_file.write(astring)
    out_file.close()

save_file(text)

Damit wird der Text standardmäßig die Datei output.ly geschrieben.  Wenn wir LilyPond darauf loslassen (unter Windows z.B. so), erzeugt es für uns Noten (output.pdf)

ganztonund anhörbare Musik in output.midi.  Hat’s geklappt?  Die MIDI-Datei kann z.B. euer Webbrowser abspielen (mein Firefox zumindest kann es).  Leider kann ich sie aber weder bei wordpress.com noch bei soundcloud hochladen. Damit ihr unser erstes Musikstück anhören könnt, habe ich output.midi deshalb nach output.mp3 konvertiert:

Juhu, wir haben Musik produziert! Keine besonders schöne, zugegeben.  Ganztonleitern klingen irgendwie… seltsam.  Deswegen werden wir uns als Nächstes mit Tonleitern beschäftigen, die uns vertrauter sind: Dur, Moll, aber auch exotisch Klingenderes wie Phrygisch oder Mixolydisch (das ist z.B. die Tonart von Norwegian Wood) sind nur noch wenige Programmzeilen entfernt.

Aber das machen wir beim nächsten Mal.  Dieser Beitrag ist lange genug geworden und bietet hoffentlich schon einige Ansatzpunkte zum Experimentieren und Weiterdenken.  Da ich wegen der anstehenden Prüfungen hier mindestens zwei Wochen nicht weiterschreiben kann, könnt ihr euch ja gerne selbst schon mal überlegen, wie ihr  Tonarten modellieren würdet.  Es gibt unendlich viele Wege das zu tun.

Viel Spaß beim Ausprobieren! Über Kommentare, Fragen und Anregungen freue ich mich sehr.

Hier noch mal der vollständige Code, den wir heute entwickelt haben, inklusive des letzten Beispiels.  Diesen Code kann man so als Datei speichern und von Python3 ausführen lassen:

from string import Template

def midi2lily(pitch):
    names = ["c", "cis", "d", "dis", "e", "f", "fis", "g", "gis", "a", "ais", "b"]
    octave, semi = divmod(pitch, 12)
    octave_marker = "'" if octave > 0 else ","
    return names[semi] + abs(octave)*octave_marker

def join(it):
    return " ".join(it)

SIMPLE_LP_MELODY = """
\\version "2.16.2"   % compatibility information for future LilyPond versions

\\score {
  { $melody }

  \\layout { }
  \\midi { \\tempo 2 = $speed}
}
"""

def fill_template(tmpl, adict):
    previous = ""
    while tmpl != previous:
        previous = tmpl
        tmpl = Template(tmpl).safe_substitute(adict)
    return tmpl

def save_file(astring, fn="output.ly"):
    out_file = open(fn, "wt")
    out_file.write(astring)
    out_file.close()

## Beispiel
from itertools import chain

chromatic_scale = range(13)
motiv1 = [pitch for pitch in chromatic_scale if pitch % 2 == 0]
motiv2 = reversed(motiv1)
theme = chain(motiv1, motiv2)
melody = join(midi2lily(pitch) for pitch in theme)
speed = 120
text = fill_template(SIMPLE_LP_MELODY, locals())
save_file(text)

Musik ist hier Programm! (1)

(Alternativer Titel: „Musik machen mit Embee“ bzw.  „Komponieren durch Programmieren“  bzw. „Hilfe! Mein Computer will Musiker werden!“ bzw. „Harmonielehre am Computer“ bzw. „Vom Personal Computer zum Personal Composer“.  Sucht euch einen aus.)

Dies ist der Beginn einer Serie, in der ich mit euch Musik machen will.  Beziehungsweise: Eigentlich will ich, dass mein (bzw. euer) Computer die Musik macht — und wir bringen ihm bei, wie. Er soll lernen, was Töne, Tonleitern, Akkorde und Akkordverbindungen sind und er soll dieses Wissen nutzen um Musikstücke in Noten zu setzen, sie zu verändern, sie zu begleiten und — als Krönung — sie selbst zu komponieren.  Ob das mehr wird als Kakophonie, wird sich zeigen!

Bevor es losgeht, will ich ein paar Fragen beantworten (die mir natürlich niemand gestellt, sondern die ich mir selbst ausgedacht habe):

Für wen ist das hier gedacht?

Die Reihe richtet sich an alle, die sich für Musik und für Programmierung interessieren. Auf beiden Gebieten setze ich Grundkenntnisse voraus; weder soll das eine Einführung in die Musiktheorie noch ein Programmierkurs werden.  Ich gehe aber kleinschrittig und hoffentlich so anschaulich vor, dass es Spaß macht, mitzulesen, mitzudenken, die Musikbeispiele anzuhören, die Notenbeispiele anzusehen, v. a. aber die Programme selbst auszuprobieren und mit ihnen zu spielen.  Links zu ein- und weiterführenden Informationen versuche ich, wo immer möglich, einzubinden.

Warum sollte man sowas überhaupt wollen, ein Computerprogramm das Musik macht?

Mir persönlich würde als Antwort auf diese Frage ja schon ausreichen: Aus Neugierde! Um zu sehen, ob es möglich ist!  Um etwas zu lernen über Musik und über Programmierung!

Aber viele Leute fühlen sich von der Idee, mit dem Computer irgendeine Form von Kunst zu machen, richtiggehend abgestoßen.  Das äußert sich dann ungefähr so:

„Oh nein, was soll den der Mist? Ist Musik (und eigentlich alle Kunst) nicht in erster Linie Intuition, Emotion, Kreativität — und damit einem Computer gar nicht zugänglich?  Muss man denn alles Schöne durch Analyse, durch Automatisierung, durch Maschinen seines Zaubers berauben? Ich fand schon Gedichtinterpretationen in der Schule immer irgendwie pervers…“

Ich halte die Sache für etwas komplexer — und dadurch für viel spannender.  Aber den langen Beitrag, den ich dazu in der Schublade habe, lasse ich erstmal liegen.  Vielleicht können wir das ja in einiger Zeit diskutieren, wenn wir sehen, was wir überhaupt zustande bekommen.

Wird das etwas für die Schule?

Wohl eher nicht.  Die Programme, die wir entwickeln, werden nicht besonders umfangreich, aber doch anspruchsvoll werden. Für meine Schüler wäre das Niveau definitiv zu hoch; sie haben leider viel zu wenig Informatikunterricht.  Aber wer weiß? Vielleicht kann man mit Anpassungen in anderen Bundesländern, wo mehr Informatik möglich ist, durchaus was machen…  Fächerübergreifend… Ein Traum…

Welche Programmiersprache und Tools verwendest du?

Programmiert wird in Python.  Output unserer Programme werden Dateien sein, die die freie Notensatzsoftware LilyPond dann für uns in wunderschöne Noten übersetzt.  Weil Musik aber in erster Linie zum Hören da ist, erzeugen wir auch MIDI-Dateien (am Anfang lassen wir das einfach LilyPond für uns machen; später brauchen wir ein bisschen mehr Freiheit und machen es selbst).

Warum Python?

Python ist toll! Python-Code sieht aus wie Pseudocode und ist deshalb selbst für Leute nachvollziehbar, die selbst nicht in Python programmieren.  Python ist einfach, wenn etwas Einfaches ausgedrückt werden soll (im Gegensatz zu, sagen wir, Java) und macht einem auch bei komplexeren Aufgaben das Leben nicht unnötig schwer.

Wichtig für mich in diesem Projekt ist v. a. auch, dass Python eine funktionalen Programmierstil unterstützt: Funktionen können Parameter oder auch Rückgabewert anderer Funktionen sein — und das macht beim Beschreiben musikalischer Zusammenhänge sehr viel Sinn.  Z.B. werden wir die Tonika der Es-Moll-Tonleiter (es, ges, b) als Verknüpfung dreier Funktionen (Tonika, Es, Moll) beschreiben können.  Das wird sehr elegant, glaubt mir.  Im Vergleich zu ausschließlich funktionalen Sprachen (Lisp, Haskell, ML, …) bleiben wir mit Python aber immer im für Normalsterbliche nachvollziehbaren Rahmen.

Übrigens werden wir Python 3 verwenden.  Es gibt keinen zwingenden Grund dafür und die Codebeispiele sollten größtenteils auch unter Python 2 funktionieren bzw. sich leicht anpassen lassen.  Für mich war dieses Projekt aber der Anlass, endlich zu Python 3 zu wechseln.

Warum LilyPond?

Weil man Musik für LilyPond in einem textbasierten Format beschreibt, das relativ leicht programmatisch zu erzeugen ist.  (Aber auch als Mensch finde ich LilyPond zur Noteneingabe super.)

Geht’s jetzt mal endlich los?

Jawoll. Aber in einem neuen Eintrag.  Viel Spaß! Ich freue mich sehr über Kommentare, Fragen, Vorschläge…

Mitteilungen über den Stand der Dinge

Time flies like an arrow.  Wie ihr bestimmt wisst, kann man diesen Satz auf viele, viele Arten lesen*.   Ich meine hier aber nur die wohl offensichtlichste Bedeutung: Die Zeit fliegt dahin wie ein Pfeil.  Und zwar pfeilgerade meinen letzten Prüfungen und dem Ende des Referendariats entgegen.  Meine TODO-Listen sind lang und, obwohl ich fleißig vorne abarbeite, kommt hinten immer sofort wieder etwas Neues dazu (rhymes with Queue).

Hier mal ein kurzes Update über den Stand der Dinge:

  • Ich habe aus sogenannten „schulscharfen“ Stellenausschreibungen zwei Angebote bekommen und eines davon vorgestern angenommen.  Falls ich also nicht in den Prüfungen noch totalen Mist baue, bin ich in der unglaublich privilegierten Position, schon im April zu wissen, dass ich eine Stelle habe.  Und das, obwohl die Stellensituation in BW allgemein ziemlich angespannt ist.
  • Wenn alles klappt, darf ich zusätzlich noch 3 Deputatsstunden woanders etwas sehr Spannendes machen, von dem ich aber erst dann erzähle, wenn es in trockenen Tüchern ist.
  • Ich habe die zwei ersten Blogposts einer kleinen Serie übers Musikmachen mit Python und LilyPond fast fertig.  Ich sollte ja wirklich anderes tun, aber das macht einfach Spaß!
  • Dies Beiträge werde ich in den nächsten Tagen noch hochladen, dann aber erstmal Pause machen, bis die Prüfungen rum und eine noch viel wichtigere familiäre… ähem… Expansion gut über die Bühne gegangen ist!

Zuguterletzt: Der Frühling kommt endlich — genießt ihn! Bis bald.

* Wenn’s nicht so ausgelutscht wäre, würde ich mir ja gerne mal ein T-Shirt mit dem (Groucho Marx zugeschriebenen) Klassiker „Time flies like an arrow; fruit flies like a banana“ drucken lassen.  Macht das mal, ihr Englischlehrer da draußen! Und wenn die Schüler euch dann darauf ansprechen, könnt ihr ihnen nach Lust und Laune von der Schönheit des Mehrdeutigen, den Schwierigkeiten der Interpretation schon auf Syntaxebene, von „garden path“-Sätzen und dergleichen erzählen.  Falls ihr zufällig auch noch Informatiklehrer seid, habt ihr auch gleich einen Anlass, mit ihnen über Parsing, Grammatiken usw. zu sprechen — aber das geht ja wohl fast immer über die Lehrpläne hinaus. Schade.