Haskore - Philipps-Universität Marburg

Werbung
Haskore
Ein System zur Beschreibung und Interpretation von Musik
Eine Arbeit von: Ulrike Georgi
im Sommersemester 2006
an der Philipps-Universität Marburg
zum Seminar „Fun of Programming“
Veranstaltungsleiterin: Prof. Dr. Rita Loogen
Hauptgrundlage dieser Arbeit bildet das Haskore Music Tutorial von Paul Hudak:
http://www.haskell.org/haskore/Haskore/Haskore/Docs/tutorial.ps
Inhaltsverzeichnis
Kurzzusammenfassung...................................................................................................... 1
Einführung......................................................................................................................... 1
Grundlagen........................................................................................................................ 2
Kapitel 1: Wichtige Datentypen und Basisfunktionen...................................................... 2
1.1: Die Datentypen in Haskore....................................................................................2
1.2: Einige grundlegende Funktionen........................................................................... 3
Kapitel 2: Von der Notenvorlage zum Haskore-Code.......................................................5
2.1: Interpretation der ersten und zweiten Stimme....................................................... 5
2.2: Interpretation der Begleitakkorde.......................................................................... 7
Kapitel 3: Vom Haskore-Code zur MIDI-Datei................................................................ 9
Kapitel 4: Musik und die Macht der Mathematik............................................................11
4.1 Musik nach Zahlen................................................................................................12
4.2 Musik – rekursiv bis ins Unendliche.................................................................... 12
Schlusswort......................................................................................................................14
Anhang: Der vollständige Beispiel-Sourcecode..............................................................16
Quellenverzeichnis.......................................................................................................... 18
Kurzzusammenfassung
Das „Haskore Music
System“ ist eine Sammlung
von
Haskell-Funktionen
und -Modulen, welche, teils vordefiniert und teils speziell zu diesem Zweck
geschrieben, der Beschreibung und Interpretation von Musikstücken dienen sollen.
Neben der nützlichen algebraischen Notation der Notendaten baut Haskore die
musikalische Interpretation vor allem auf der Verwendung von Listenoperationen auf
und bietet dem Musik-Programmierer hiermit komfortable Möglichkeiten, etwa
Umkehrungen, Wiederholungen oder Transpositionen auszudrücken. Als praktische
Anwendungsmöglichkeit bietet Haskore einen Export in Dateien nach dem General
MIDI1 Standard, so dass Musik tatsächlich nicht nur beschrieben, sondern auch hörbar
gemacht werden kann. Die Wahl der zugrunde liegenden Programmiersprache Haskell
erweist sich durch den einfachen funktionalen Aufbau der Musikinterpretationen schnell
als sinnvoll, denn Haskore definiert und untermauert die Algebra der Musik.
Einführung
Diese Arbeit beschäftigt sich mit dem Haskore Music System von Paul Hudak. Sie soll
dem Leser anhand eines Beispielliedes, welches Stück für Stück in Haskore interpretiert
und beschrieben und schließlich in eine abspielbare MIDI-Datei exportiert wird, einen
Teil der umfassenden Möglichkeiten des Systems vorstellen.
Zunächst werde ich im ersten Kapitel einige grundlegende Funktionen und Datentypen
von Haskore vorstellen und den Aufbau des Systems kurz umschreiben. Im zweiten
Kapitel stelle ich das für diese Arbeit verwendete Musikstück vor und beschreibe die
ersten Schritte bei der Umwandlung der vorliegenden Noten in eine mögliche HaskoreNotation. Kapitel 3 beschreibt den Export der fertigen Musikumschreibung in eine
Datei nach General MIDI Standard. Im Anschluss hieran beschäftige ich mich mit der
Frage, warum als Grundlage für die Musikinterpretation gerade Haskell als
Programmiersprache ausgewählt wurde und gehe noch einmal auf die Möglichkeiten
des Systems ein. In allen vorgenannten Kapiteln werden, wenn nötig, auch musikalische
Hintergründe erklärt.
1 MIDI = Abk. für Musical Instrument Digital Device
1
Grundlagen
Das „Haskore Music System“ besteht aus einer Zusammenstellung mehrerer HaskellModule, die von Professor Paul Hudak von der Yale University als Erweiterung der
Programmiersprache
Haskell
für
die
Verwendung
des
Interpreters
Hugs
( http://www.haskell.org/hugs ) oder des Compilers GHC ( http://www.haskell.org/ghc )
konzipiert wurde. Ich habe zur Arbeit mit dem Haskore-System den Hugs98-Interpreter
in der Version für Windows vom November 2002 verwendet. Die zu Haskore gehörigen
Source-Dateien werden zur Verwendung in das /lib-Verzeichnis im HugsInstallationspfad kopiert und können hernach im Interpreter durch Aufruf des Befehls
:load Haskore geladen werden. Die Verwendung einer neueren Version des
Interpreters gestaltet sich als schwierig, da das Library-System nach der genannten
Version etwas abgeändert wurde.
Um die Funktionen und Typen der einzelnen Kapitel in eigenen Modulen verwenden zu
können, müssen diese die Module Haskore und HaskToMidi importieren.
Die Hauptgrundlage meiner Erläuterungen bildet das Haskore Music Tutorial, welches
von Paul Hudak selbst als umfassende Einführung in sein System (in englischer
Sprache) verfasst wurde Das Tutorial liegt in einer PostScript- sowie in einer HTMLVersion vor und kann, wie die Bibliothek selbst, von http://www.haskell.org/haskore
frei heruntergeladen beziehungsweise eingesehen werden.
Kapitel 1: Wichtige Datentypen und Basisfunktionen
Die hier erläuterten Funktionen und Typen finden sich im Modul Basics.lhs
1.1: Die Datentypen in Haskore
Strukturell betrachtet ist Musik die Über- oder Aneinanderreihung einzelner Töne. Ein
Ton kann dabei hörbar sein, also eine Note mit Eigenschaften wie Lautstärke und
Geräuschart besitzen, oder er kann unhörbar sein, eine Pause (engl. rest) in der
Klangfolge. Noten und Pausen haben jeweils eine spezifische Dauer (engl. dur,
duration) Das einfachste Musikstück ist also eine einzelne Note oder eine einzelne
Pause. Kompliziertere Stücke sind Kombinationen dieser Grundelemente.
Entsprechend dieser Überlegungen gibt es in Haskore den Grunddatentyp Music, der
wie folgt definiert ist (Code 1):
2
01
data Music = Note Pitch Dur [NoteAttribute]
02
| Rest Dur
03
| Music :+: Music
04
| Music :=: Music
05
| Tempo (Ratio Int) Music
06
| Trans Int Music
07
| Instr IName Music
08
| Player PName Music
09
| Phrase [PhraseAttribute] Music
10
deriving (Show, Eq)
Code 1: Datentyp Music
Ein Music-Objekt ist also entweder eine Note (01), eine Pause (02), eine
Aneinanderreihung der vorgenannten (03), eine Übereinanderlegung (04), Musik mit
veränderter Geschwindigkeit oder Tonhöhe (05 – 06) oder Musik mit bestimmten
Eigenschaften, die vornehmlich für das Erzeugen von Dateien nach dem General MIDI
Standard vonnöten sind (07 – 09) und bildet damit eine rekursive Struktur. Das Symbol
:+: ist als Aneinanderreihung zu verstehen, während :=: die Gleichzeitigkeit bzw.
Übereinanderlegung ausdrückt.
Pausen besitzen lediglich eine Dauer. Weitere Attribute werden nicht benötigt. Eine
Note besitzt in Haskore die Eigenschaften Höhe (Pitch), Dauer (Dur von duration) und
eine Liste von zusätzlichen Eigenschaften wie etwa Lautstärke des Tones. Zur
Eigenschaft der Tonhöhe gehört die Angabe des Namens des Tons sowie der Oktave, in
welcher der Ton steht. Die Notennamen sind im Datentyp PitchClass notiert.
Octave ist ein beliebiger ganzzahliger Wert. Code 2 zeigt die Definition.
01
02
03
04
type Pitch = (PitchClass,Octave)
data PitchClass = Cf | C | Cs | Df | ... | Bf | B | Bs
deriving (Eq,Ord,Ix,Show,Read)
type Octave = Int
Code 2: Typ Pitch
1.2: Einige grundlegende Funktionen
Der vorgenannte Datentyp PitchClass definiert somit die insgesamt 21 Namen der
zwölf Halbtöne der Tonleiter2. Jeder möglichen PitchClass wird, entsprechend der
zwölf Halbtöne, mittels der Funktion pitchClass ein Wert von -1 bis 12 zugeordnet.
0 – 11 sind dabei die Werte der Noten C bis B, Cs erhält den Wert -1, da es der letzte
Ton (also gleich B) in der darunterliegenden Oktave ist und entsprechend ist der Wert
von Bf 12, als erster Ton (entsprechend C) der folgenden Oktave. Unter Zuhilfenahme
2 Die C-Tonleiter besteht aus den sieben Grundtönen C, D, E, F, G, A und B (deutsch H), sowie den
Zwischentönen Cf, Df, Ff, Gf und Bs (im Deutschen: Cis, Dis, Fis, Gis und B). Die insgesamt 21
Notennamen ergeben sich aus der Gleichbedeutung von zum Beispiel Cf und Ds
3
der Oktavennummer kann mit dem Notenwert der Absolutwert der Tonhöhe, in Haskore
absPitch, ermittelt werden. Die Umkehrfunktion pitch ermittelt aus dem
Absolutwert wiederum den Notennamen sowie den Oktavwert.
Zur Reihung von Musik gibt es die Infix-Operatoren :+: und :=:. Ersterer verbindet
je zwei Music-Objekte aufeinanderfolgend, während letzterer die Gleichzeitigkeit je
zweier Music-Objekte beschreibt. Diese beiden Operatoren repräsentieren also das
Bilden einer Notenzeile beziehungsweise von Akkorden und Mehrstimmigkeit. Um aber
eine Klangfolge von n Noten/Pausen nicht mit n-1 dieser Operatoren verbinden zu
müssen, sind in Haskore zusätzlich die Funktionen line und chord vordefiniert.
Beide erhalten als Argument eine Liste mit Elementen vom Typ Music und bilden
daraus wiederum ein Music-Objekt, durch Listenfaltung mit dem Operator :+: bzw.
:=:. Die Funktion lineToList ist die Umkehrung von line und liefert aus einem
mit line erzeugten Music-Objekt eine entsprechende Liste zurück. Die Funktion dur
erwartet ein Music-Objekt als Eingabe und liefert einen Int-Wert zurück, dieser
bestimmt die Gesamtdauer des Stückes. Hudak definiert zusätzlich einige Konstanten,
die bei der Musikbeschreibung immer wieder gebraucht werden (Code 3). Die Zeilen
01 bis 24 zeigen die Notenabkürzungen, die in der Noteninterpretation benutzt werden
können. Es wird ein Int-Wert als Oktavenangabe erwartet, um die Höhe der Note zu
bestimmen. Die Zeilen 25 ff. definieren die Dauern der Noten und Pausen.3
01
02
03
..
24
cf,c,cs,df,d,ds,ef,e,es,ff,f,fs,gf,g,gs,af,a,as,bf,b,bs ::
Octave -> Dur -> [NoteAttribute] -> Music
cf o = Note (Cf,o);
...
bs 0 = Note (BS,o);
25
26
27
28
bn,hn,qn,en,sn,tn,sfn :: Dur
dwn,dhn,dqn,den,dsn,dtn,ddhn,ddqn,dden :: Dur
bnr,wnr,hnr,qnr,enr,snr,tnr :: Music
dwnr,dhnr,dqnr,denr,dsnr,dtnr,ddhnr,ddqnr,ddenr :: Music
29
bn = 2;
bnr = Rest bn;
30
wn = 1;
wnr = Rest wn;
..
...
...
36
sfn = 1%64;
sfnr = Rest sfn;
37
dwn = 3%2;
dwnr = Rest dwn;
..
...
...
45
dden = 7%32;
ddenr = Rest dden;
Code 3: Abkürzungen4 und Konstanten
3 Punktierte (dotted) Noten besitzen die Länge der nicht-punktierten + Länge der nächst kürzeren (Bsp:
punktierte Viertel = ¼ + 1/8 = 3/8). Doppelt punktierte Noten sind entsprechend nicht-punktierte +
nächstkleinere + zweitkleinere (Bsp:doppelt punktierte halbe Note = ½ + ¼ + 1/8 = 7/8)
4 Abk.: n = note, r = rest, b = brevis, w = whole, h = half, q = quarter, e = eighth, s = sixteenth, t =
thirty-second, sf = sixty-fourth, d = dotted (punktiert), dd = double-dotted (doppelt punktiert)
4
Kapitel 2: Von der Notenvorlage zum Haskore-Code
Um die Interpretation und Beschreibung von Musik unter Verwendung des Haskore
Music Systems aufzuzeigen, habe ich als Beispielstück das Volkslied Die Gedanken
sind frei in einer zweistimmigen Variante mit Begleitakkorden ausgewählt.
Abbildung 1: Notenvorlage "Die Gedanken sind frei"
2.1: Interpretation der ersten und zweiten Stimme
Mit den im ersten Kapitel genannten Definitionen können nun die erste Stimme (die
jeweils oberen Noten) sowie die zweite Stimme (die unteren Noten) in eine erste
einfache Haskell-Notation umgewandelt werden. Zunächst definieren wir uns noch ein
paar kleinere Hilfsfunktionen, die wir schon bald benötigen werden (Code 4):
01
fd d n = n d volFV
02
vol
n = n volFV
03
fda d n = n d volSV
04
vola n = n volSV
05
volFV = [Volume 120]
06
volSV = [Volume 75]
Code 4: Hilfsfunktionen für die erste und zweite Stimme
Die Funktionen fd und fda bekommen als Eingabe je einen Wert vom Typ Dur
(Dauer) und eine Note (Notenhöhe und Oktave) übergeben. Die Ausgabe ist die Note
mit entsprechender Dauer und der zugewiesenen Noteneigenschaft Volume xx, die für
die erste und zweite Stimme in diesem Beispiel unterschiedlich sein soll. Die
Funktionen vol und vola erwarten ein Notenelement mit bereits vorhandener Dauer
5
und
liefern
damit
die
gleiche
Ausgabe
wie
fd
und
fda
zurück.
Nun aber zur eigentlichen Beschreibung unserer Noten. Intuitiv schreiben wir die Noten
Zeile für Zeile in eine Liste nieder. Wir verwenden dabei die in Code 3 vorgestellten
Kurzschreibweisen und eine mittelhohe Oktavenhöhe. Ich habe mich für dieses Beispiel
für die vierte Oktave als Ausgangshöhe entschieden. Je nachdem, ob viele Noten
unterschiedlicher Länge hintereinander stehen, schreiben wir ihre Dauern gleich mit in
die Listen oder lassen sie zunächst weg, wenn viele gleichlange Noten aufeinander
folgen.
Das
Lautstärke-Attribut
schreiben
wir
zunächst
nicht
hinzu.
Nachdem nun die Listen erstellt sind, je eine bis zwei pro tatsächlicher Notenzeile,
entsprechend der Ähnlichkeiten der Notenlängen, weisen wir mit Hilfe der mapFunktion den einzelnen Listenelementen, also unseren Noten, die fehlenden Attribute
zu. Die Funktionen, die wir hierfür benötigen, haben wir wie im Code 4 beschrieben,
zuvor erstellt. Ist all dies vollbracht, liegt uns ein Code wie folgt vor (Code 5):
01
02
03
04
05
06
07
08
09
fVFL1 = map vol [g 4 en,g 4 en,c 5 qn,c 5 qn,
e 5 en,c 5 en,g 4 hn]
fVFL2 = map (fd qn) [g 4,f 4,d 4,g 4,e 4,c 4]
fVSL1 = map vol [c 5 qn,b 4 qn,d 5 dqn,b 4 en,c 5 qn,
e 5 qn,c 5 qn,b 4 qn,d 5 dqn,b 4 en]
fVTL1 = map (fd qn) [c 5,e 5,c 5,a 4,a 4]
fVTL2 = map vol [c 5 en,a 4 en,g 4 hn]
fVFoL1 = map (fd en) [c 5,e 5,e 5,d 5]
fVFoL2 = map vol [c 5 qn,b 4 qn,c 5 hn]
10
11
12
13
14
15
16
17
18
sVFL1 = map vola [g 4 en,f 4 en,e 4 qn,
e 4 qn,g 4 en,a 4 en,e 4 hn]
sVFL2 = map (fda qn) [e 4,d 4,d 4,b 3,c 4,c 4]
sVSL1 = map (fda qn) [e 4,g 4,g 4,f 4,e 4,c 4,
e 4,g 4,g 4,f 4]
sVTL1 = map (fda qn) [e 4,c 4,e 4,f 4,e 4,d 4]
sVTL2 = [e 4 hn volSV]
sVFoL1 = map vola [e 4 en,g 4 en,g 4 en,
f 4 en,e 4 qn,d 4 qn,e 4 hn]
19
firstVoice = line (fVFL1 ++ fVFL2 ++ fVFL1 ++ fVFL2
20
++ fVSL1 ++ fVTL1 ++ fVTL2 ++ fVFoL1 ++ fVFoL2)
21
secondVoice = line (sVFL1 ++ sVFL2 ++ sVFL1 ++ sVFL2
22
++ sVSL1 ++ sVTL1 ++ sVTL2 ++ sVFoL1)
Code 5: Haskore-Interpretation der ersten und zweiten Stimme
Wie in den Zeilen 19 bis 22 setzen wir nun noch die Listen, deren Elemente nun alle
die gleiche Anzahl und Art von Eigenschaften besitzen, zu je einem einzigen Stück vom
Haskore-Typ Music. der ersten und zweiten Stimme zusammen. Mit diesen werden
wir später dann leichter weiterarbeiten können. Wir verwenden zur Erzeugung die
einfache Listenkonkatenation ++ von Haskell und die vorgenannte line-Funktion
6
2.2: Interpretation der Begleitakkorde
Ähnlich wie in 2.1 wollen wir nun auch die Begleitakkorde unseres Stückes in
Haskore beschreiben. Natürlich soll das Ganze möglichst einfach geschehen,
komfortabel für den Musikprogrammierer. Um dies zu gewährleisten, definieren wir
uns Funktionen, die bei Funktionsaufruf unter Eingabe des Grundtones einen
entsprechenden Akkord automatisch erzeugen, so dass wir uns nicht mehr um die
einzelnen Töne der jeweiligen Akkorde kümmern müssen. Die Eingabe der Begleitung
entspricht dann wiederum intuitiv der tatsächlichen Notendarstellung, wenn wir die
Benennung geschickt wählen. Zudem definieren wir wie für die Grundstimmen des
Stückes auch für die Begleitung entsprechende Hilfsfunktionen, die Lautstärke- und
Längen-Informationen an Noten zuweisen.
Um unsere Akkorde richtig zu definieren, müssen wir nur wissen, dass ein Dur-Akkord
(engl. major chord) aus folgenden drei Teilen besteht: Grundton, Grundton + große
Terz und Grundton + große Terz + kleine Terz. Eine große Terz ist eine Erhöhung um
vier, eine kleine Terz um drei Halbtonschritte im eingangs beschriebenen System der
zwölf Halbtöne. So ist beispielsweise C-Dur = [c,e,g]. Entsprechend bestehen MollAkkorde (engl. minor chord) aus der Kombination Grundton, Grundton + kleine Terz,
Grundton + kleine Terz + große Terz. C-Moll wäre also [c,es/ff,g]5. Die dritte
Akkordart, die in unserem Stück „Die Gedanken sind frei“ auftaucht, ist Dur-Septim
bzw. Dur-Sept (engl. major-seventh). Die Definition der Dur-Sept-Akkorde entspricht
der eines Dur-Akkords, wobei eine vierte Note hinzukommt, die eine weitere kleine
Terz oberhalb der dritten Note des Akkordes liegt. Um zu verhindern, dass die Noten,
die oberhalb des übergebenen Grundtons liegen, durch die Berechnung der moduloRestklasse aus pitchClass (siehe Kapitel 1.2), nach unten „rutscht“, etwa bei einem
Akkord zum Grundton G, bestimmen wir zusätzlich mit der Funktion absPitch aus den
Informationen über Note und Oktave die absolute Höhe der Note und berechnen die
Folgenoten aus diesem Wert. Die Summe wandeln wir mit der Funktion pitch in ein
entsprechendes Tupel aus Notenhöhe und Oktave zurück und haben so die exakten
Akkordnoten bestimmt. So erhalten wir für unsere Akkorde die in Code 6 gezeigten
Zeilen 05 bis 17. Die Zeilen davor enthalten die oben genannte Definition zur
Lautstärkezuweisung und eine Hilfsfunktion cmap, die zunächst eine map-Anweisung
ausführt und die übergebene Liste dann mittels chord zu einem Music-Objekt faltet.
5 Englische Notation gemäß der in Code 3 genannten Konventionen, entspricht deutsch [C, Eis, G]
7
01
02
03
fdb d n = n d volAcc
volb n = n volAcc
volAcc = [Volume 50]
04
cmap f c = chord (map f c)
05
majChord n oct dur =
06
cmap (fdb dur)[Note (n,oct),
07
Note (pitch(absPitch(n,oct)+4)),
08
Note (pitch(absPitch(n,oct)+7))]
09
minChord n oct dur =
10
cmap (fdb dur)[Note (n,oct),
11
Note (pitch(absPitch(n,oct)+3)),
12
Note (pitch(absPitch(n,oct)+7))]
13
majChord7 n oct dur =
14
cmap (fdb dur)[Note (n,oct),
15
Note (pitch(absPitch(n,oct)+4)),
16
Note (pitch(absPitch(n,oct)+7)),
17
Note (pitch(absPitch(n,oct)+10))]
Code 6: Hilfsfunktionen für die Begleitakkorde
Mit diesen Werkzeugen ausgestattet, ist es ein Leichtes, die Begleitung des Stückes
niederzuschreiben. Wieder setzen wir dies zeilenweise um, um die Übersicht zu erhalten
und konkatenieren die einzelnen Teile am Ende wieder zu einer einzigen Liste. Es ist zu
beachten, dass die Funktionen, die unsere Akkorde erzeugen, auch eine Dauerangabe
erwarten, so übergeben wir jedem Aufruf auch die entsprechende Dauer des
Begleitgriffs, wie sie der Notenvorlage (Abbildung 1) zu entnehmen ist Als Oktave der
Grundtöne legen wir die zweite fest, da sie ein gutes Stück tiefer als die Hauptstimmen
des Stückes liegen sollte. Schließlich erhalten wir die im folgenden Code 7 dargestellte
Notation und haben damit auch unsere Begleitung in Haskore übertragen:
01
accFL1 = [majChord7 G 2 qn,majChord C 2 (6 * qn),
02
minChord D 2 (2 * qn),majChord G 2 qn,
03
majChord C 2 (2 * qn)]
04
accFL2 = [majChord7 G 2 qn,majChord C 2 (6 * qn),
05
minChord D 2 (2 * qn),majChord G 2 qn,
06
majChord C 2 (3 * qn)]
07
accSL1 = [majChord7 G 2 (3 * qn),
08
majChord C 2 (3 * qn),majChord7 G 2 (3 * qn)]
09
accTL1 = [majChord C 2 (3 * qn),
10
majChord F 2 (3 * qn),majChord C 2 (3 * qn)]
11
accFoL1 = [minChord D 2 (2 * qn),
12
majChord7 G 2 qn,majChord C 2 (2 * qn)]
13
accomp =
14
line (accFL1 ++ accFL2 ++ accSL1 ++ accTL1 ++ accFoL1)
Code 7: Interpretation der Begleitung
Die Dauer-Angaben der einzelnen Akkorde sind je in Vielfachen von Viertelnoten
angegeben, da das Stück im ¾-Takt vorliegt und auch die meisten Noten des Liedes
entsprechend Viertelnoten sind. Man beachte hierbei die einfache Verwendung des
Multiplikations-Operators *. Auf die mathematischen Möglichkeiten innerhalb des
Haskore Systems werde ich in Kapitel 5 noch einmal näher eingehen.
8
Kapitel 3: Vom Haskore-Code zur MIDI-Datei
In Kapitel 2 haben wir uns drei separate Music-Objekte mit der Funktion line
erzeugt. Somit haben wir eine für Haskore bzw. Haskell verständliche textuelle
Interpretation unserer Notenvorlage geschaffen. Ein Komponist aber möchte sicher
nicht nur einen Text besitzen, der alle seine Noteninformationen enthält; er will auch
wissen, wie sich sein Stück schließlich anhört, und so bedient Prof. Hudak in Haskore
auch diesen Wunsch. Im Modul HaskToMidi sind eine Reihe Funktionen definiert,
die zum Erzeugen und Auslesen von Dateien nach dem General MIDI Standard6,7
benutzt werden können. Ich werde hier nur auf ein paar der Funktionen zum Erzeugen
eingehen.
Wir haben bereits bei der ersten Notation unserer Musik Lautstärke-Informationen mit
übergeben, als Notenattribut, das für uns bisher nur rein beschreibende Bedeutung hatte.
Für die Erzeugung einer MIDI-Datei ist diese Information aber bereits wichtig. Im
MIDI-Format besitzt jede einzelne Note Informationen zur Höhe, Dauer, Lautstärke,
zum Instrument, welches für die Note benutzt werden soll und zum Ausgabekanal des
abspielenden Gerätes. So fehlen unserer Musik also vornehmlich noch die Daten zu
Instrumenten und Kanälen. Zu diesem Zwecke definieren wir uns eine UserPatchMap.
Diese besteht aus einer Liste mit Tripeln, bestehend aus einem Bezeichner-String,
einem Namen eines Instrumentes gemäß GM-Standard8 und einer Kanal-Nummer,
welche mit dem eingestellten Instrument angesteuert werden soll. Diese UserPatchMap
benötigen wir später zum Erzeugen der fertigen Datei. Um die einzelnen Stimmen
klanglich voneinander abzugrenzen, wählen wir je unterschiedliche Instrumente. Ich
habe mich in diesem Beispiel für eine Kombination aus Piano für die erste, Harmonium
(engl. reed organ) für die zweite Stimme und Gitarre für die Begleitakkorde
entschieden (Code 8). Vorsicht ist geboten bei der Verwendung von PerkussionsKlängen. Diese dürfen und können nur an den Kanal 9 gesendet werden. Die
verschiedenen Noten repräsentieren dann unterschiedliche Instrumente.
6 Haskore verwendet die Konventionen des General MIDI Standard (GM) von 1991. Da Haskore
1995/96 entstand, ist GM 2, der 1999 als Überarbeitung von GM entstand, hier nicht berücksichtigt.
7 General MIDI ist ein Standard, um einheitliche MIDI-Dateien auf unterschiedlichen Systemen
produzieren und spielen zu können. GM definiert eine Reihe von Instrumentennamen mit jeweils
zugeordneten Instrumentennummern. Halten sich die Soundgerätehersteller an diese Vorgaben, ist
zumindest die Instrumentenwahl bei der Wiedergabe der Stücke vereinheitlicht, nicht zwingend aber
der
Klang
der
Einzelinstrumente.
Mehr
Information
zu
GM:
http://de.wikipedia.org/wiki/General_Midi und http://www.borg.com/~jglatt/tutr/gm.htm
8 Eine entsprechende Liste der GM-Instrumente mit Instrumentennummern ist im Modul
GeneralMidi unter GenMidiMap zu finden.
9
01
defMyUpm :: UserPatchMap
02
defMyUpm = [("piano","Acoustic Grand Piano",1),
03
("reed","Reed Organ",3),
04
("guitar","Acoustic Bass",6),
05
("drums","Acoustic Grand Piano",9)] -– Perkussion!
Code 8: Unsere selbsdefinierte PatchMap
Außer der Angabe von Instrumenten ist es auch möglich, den Musikstücken zusätzliche
Informationen zur Artikulation, Dynamik und Performance zuzuweisen. Diese werden
jedoch nicht von allen Playern unterstützt, die das MIDI-Format abspielen, darum
werde ich auch darauf hier nicht näher eingehen. Weiterführende Informationen über
diese Möglichkeiten finden sich im Haskore Music Tutorial in den Kapiteln 3.3 bis 5.
Zusätzlich finden sich in den Kapiteln 6 bis 9 des Tutorials eingehende Beschreibungen
zu verschiedensten Funktionen zum Erzeugen und Auslesen von MIDI-Dateien mit
einigen Details über deren internen Aufbau und technischen Hintergrund. Wir wollen
uns aber für unser Beispiel auf eine einfache Möglichkeit beschränken, aus unseren
Musikdaten eine tatsächlich abspielbare Datei zu produzieren. Dies soll als Einstieg und
Arbeitsgrundlage anwendbar sein, und dem Komponisten/Programmierer sei hernach
jede Kreativität freigestellt.
Um nun also unsere Music zur Musik zu machen, benötigen wir noch eine generelle
Information über die Geschwindigkeit, in der sie schließlich gespielt werden soll. Diese
weisen wir mit der Funktion Tempo zu. Als Eingabe benötigt die Funktion einen
beliebigen Zahlenwert als erstes sowie ein Music-Objekt als zweites Argument.
Zurückgegeben wird ein Music-Objekt mit der zusätzlichen Eigenschaft der
übergebenen Geschwindigkeit. Für unser Beispiel wählen wir eine mittlere
Geschwindigkeit, die den Wert 2 besitzt. In Code 9 versehen wir unsere Stimmen mit
Geschwindigkeits- und Instrument-Attributen. Anschließend setzen wir das Lied zu
einem Ganzen zusammen und verwenden eine einfache rekursive WiederholungsHilfsfunktion times dazu, das Stück auf drei Strophen auszudehnen.
01
02
times 1
m = m
times (n+1) m = m :+: (times n m)
03
fVMidi = Instr "piano" (Tempo 2 firstVoice)
04
sVMidi = Instr "reed" (Tempo 2 secondVoice)
05
accompanyMidi = Instr "guitar" (Tempo 2 (accomp))
06
dieGedankenSindFrei =
07
times 3 (fVMidi :=: sVMidi :=: accompanyMidi)
Code 9: Zuweisen der letzten Eigenschaften
Der endgültige Weg zur MIDI-Datei erfolgt über ein Zwischenformat, das in Haskore
vom Typ MidiFile ist. In Haskore ist die Umwandlung von Music zu MidiFile
10
mittels der Funktion performToMidi möglich. Diese erwartet als Argumente ein
Objekt vom Typ Performance (dies ist Music mit übergebenen bestimmten
Performance-Eigenschaften) und der zuvor angesprochenen UserPatchMap. Der
Einfachheit halber wählen wir als Performance eine in Haskore im Modul
TestHaskore integrierte und versehen unsere Musik mit dieser. Die PatchMap haben
wir bereits erzeugt, und da wir immer die gleiche UserPatchMap verwenden,
definieren wir uns eine Hilfsfunktion, die uns ein wenig Tipparbeit sparen wird wie in
Code 10, Zeilen 01 – 02. Im Anschluss daran verwenden wir in einer weiteren
Hilfsfunktion
die
Haskore-Funktion
outputMidiFile,
die
aus
unseren
Musikbeschreibungen endgültig einen IO-Stream erzeugt und die fertige MIDI-Datei
schreibt, die in jedem MIDI-fähigen Player abgespielt werden kann. Unsere
Hilfsfunktion erwartet im ersten Argument ein Music-Objekt, im zweiten den Namen
der zu schreibenden Datei. Schlussendlich schreiben wir noch ein paar kurze
Funktionsdefinitionen nieder, die uns aus den einzelnen Stimmen bzw. der Begleitung
je eine eigene MIDI-Datei erzeugen und eine solche, die das gesamte Stück erzeugt. So
haben wir genügend Möglichkeiten, andere Instrumente, Geschwindigkeiten usw.
auszuprobieren.
01
02
goMidi :: Music -> MidiFile
goMidi m = performToMidi (testPerf m) defMyUpm
03
04
05
makeMidiFile :: Music -> String -> IO ()
makeMidiFile musicTrack fileName =
outputMidiFile fileName (goMidi musicTrack)
06
makeFV, makeSV, makeAcc, makeItSo :: IO ()
07
makeFV
= makeMidiFile fVMidi "dieGedankenSindFrei-FV.mid"
08
makeSV
= makeMidiFile sVMidi "dieGedankenSindFrei-SV.mid"
09
makeAcc =
10
makeMidiFile accompanyMidi "dieGedankenSindFrei-Acc.mid"
11
makeItSo =
12
makeMidiFile dieGedankenSindFrei "dieGedankenSindFrei.mid"
Code 10: Die Ausgabefunktionen
Nach Laden unseres Moduls genügt der Aufruf makeItSo, um das Stück Die
Gedanken sind frei im Verzeichnis des Moduls zu erzeugen.
Kapitel 4: Musik und die Macht der Mathematik
Die
Wahl
der
Programmiersprache
Haskell
als
Grundlage
für
die
Musikprogrammierung mag dem einen oder anderen zunächst einmal sicherlich seltsam
vorkommen.
Nicht
zuletzt
gibt
es
bereits
11
viele
Schnittstellen
für
andere
Programmiersprachen, die das Erstellen der schlanken Musikdateien recht komfortabel
ermöglichen. Doch einen wichtigen Punkt können viele gängig Programmiersprachen
niemals so effizient umsetzen, wie es Haskell kann: Musik ist auch Mathematik!
4.1 Musik nach Zahlen
Haskore bedient sich an vielen Stellen dieser Erkenntnis. Im ersten Kapitel habe ich die
Definitionen der Notenwerte, Oktavenhöhen und Notendauern vorgestellt. Sicher ist
dem Leser dabei nicht entgangen, dass die gesamte Definition letztlich aus
Zahlenwerten, einzeln oder in Tupeln, besteht. In Kapitel 2.2, Code 7 haben wir uns
diese Tatsache bereits zunutze gemacht. So ist es jederzeit möglich, durch Anwendung
von Grundrechenoperationen die Eigenschaften der einzelnen Noten oder mittels map
einer ganzen Liste von Noten oder durch entsprechende Funktionen die eines
umfangreichen Music-Objekts zu verändern.
Beispielsweise ist Transponieren, eine Erhöhung oder Verminderung der Gesamthöhe
eines Musikstückes, ist durch Addieren bzw. Subtrahieren eines Int-wertes auf den
jeweiligen Absoluthöhenwert der Note (absPitch) umzusetzen. Eine solche Funktion
ist in Haskore vordefiniert und lässt sich direkt auf Music-Objekte anwenden. Wollen
wir beispielsweise die erste Stimme unseres Liedes um zwölf Halbtöne, also eine
gesamte Oktave erhöhen, ändern wie die Zeile fVMidi = Instr "piano"
(Tempo
2
firstVoice) einfach um in fVMidi
=
Instr
"piano"
(Tempo 2 (Trans 12 firstVoice)) und erhalten das gewünschte Ergebnis
bei Erzeugung der Ausgabedatei. Haskore implementiert einige solcher Funktionen, die
die mathematischen Eigenschaften der Musik intuitiv ausnutzen. So ist im Grunde auch
die Aneinanderreihung mittels des Operators :+: als Addition zu betrachten, denn das
Ergebnis ist eine Sequenz, eine Summe von Noten, die das ganze Stück ergeben.
4.2 Musik – rekursiv bis ins Unendliche
Auch die Rekursion spielt in den vorgenannten Definitionen eine große und immer
wiederkehrende Rolle. Der Datentyp Music ist selbst rekursiv definiert, ähnlich dem
Aufbau einer Liste. Einige Funktionsdefinitionen bauen auf der Ausnutzung von
Rekursionseigenschaften auf. Alle diese Funktionen, wie auch das oben genannte
Trans greifen auf den rekursiven Aufbau des übergebenen Music-Objekts (Noten,
12
Pausen, Sequenzen oder Akkorde von Noten und Pausen) zurück und wenden so
entsprechend ihrer Definition Berechnungen oder Zuweisungen auf die Grundelemente
an, ohne die tatsächliche Struktur zu beeinträchtigen; etwas, das gerade Haskell sehr
schnell und effizient umsetzt.
Denkbar einfach ist es auch, eine unendliche Struktur zu erzeugen. Haben wir etwa
einen einfachen Begleitrhythmus im Sinn, der einem Stück schlicht etwas Takt geben
soll, müssen wir nur wissen, welchen Takt das Stück besitzt und diesen einmal
vorgeben. Eine simple Funktion, ähnlich der in Code 9 vorgestellten times,
ermöglicht es uns, den Takt unendlich oft zu wiederholen. In Haskore existiert diese
Möglichkeit unter dem Namen repeatM. Mit Hilfe der Funktion cut kann ein derart
erzeugtes theoretisch unendliches Stück leicht auf eine bestimmte Länge geschnitten
werden, etwa die Länge des zu begleitenden Liedes. Diese kann mittels der Funktion
dur wiederum rekursiv ermittelt werden. An unserem Beispiel könnte dies wie in Code
11 gezeigt aussehen. Man beachte, dass hier die unterschiedlichen Noten nicht
unterschiedliche Höhen, sondern vielmehr verschiedene Instrumente definieren, wenn
wir den Takt auf den MIDI-Kanal 9, also den Kanal für Perkussionsklänge mappen.
Unser Beispiellied hat einen ¾-Takt, wobei der erste volle Takt erst mit der zweiten
Viertelnote beginnt.
01
02
repeatM :: Music -> Music
repeatM m = m :+: repeatM m
03
04
05
06
07
beatLine = [e 3 qn [Volume 60],
d 3 qn [Volume 90],e 3 qn [Volume 60]]
beat = line beatLine
beatMidi = Instr "drums"
(cut (dur fVMidi) (repeatM (Tempo 2 beat)))
08
09
dieGedankenSindFreiMitBeat = times 3
(fVMidi :=: sVMidi :=: accompanyMidi :=: beatMidi)
10
makeItSo2 = makeMidiFile
11
dieGedankenSindFreiMitBeat "dieGedankenSindFrei2.mid"
Code 11: Takt-Erstellung und Einbringung ins gesamte Lied
Somit haben wir aus einfachsten Mitteln unserem Lied eine durchgehende rhythmische
Begleitung zugewiesen und implizit mehrfach Haskells Effektivität bei der Verwendung
von Rekursionen ausgenutzt. Auch ist es möglich, die Notenlisten mittels Haskells
reverse einfach umzukehren, um das interpretierte Stück rückwärts zu erzeugen, und
Haskore geht noch einen Schritt weiter und ermöglicht das Umkehren von MusicObjekten. Die Funktion retro nimmt ein mit line erstelltes Music-Objekt, wandelt
es in eine Liste um, invertiert diese und wandelt mit line wieder zu Music um.
13
Haskore ist voll von Funktionen, die mathematische Möglichkeiten um die Musikstücke
herum definieren und nutzen. Die Musik wird nicht nur notiert, interpretiert und
beschrieben; sie wird analysiert, eingeteilt, definiert. Mathematik und Musik werden
miteinander verbunden, wo immer es möglich ist, und durch den rekursiven Aufbau
über Listen und den Music-Datentyp erhält der Haskore-Programmierer weitreichende
Möglichkeiten, Systematiken innerhalb eines Musikstückes funktional wiederzugeben.
In wenigen Zeilen entsteht so ein Stück, für das in anderen Sprachen weit mehr
Aufwand vonnöten wäre.
Zur Ausnutzung von Rekursion und Rechenregeln gibt es im Haskore Music Tutorial
im Anhang D auch einige Beispiele zur Erzeugung von Fraktal-Musik mit Haskore.
Diese mag der interessierte Programmierer sich ebenfalls einmal näher betrachten, da
hier bereits ein sehr einfacher Aufbau zu interessanten und komplex strukturierten
Ergebnissen führt.
Schlusswort
Paul Hudak zeigt mit dem Haskore Music System ein eindrucksvolles Beispiel der
Möglichkeiten von Haskell. Er gibt dem Musikprogrammierer ein mächtiges und
vielseitiges
Instrument
an
Anwendungsmöglichkeiten
die
Hand,
vordefiniert
das
bereits
mitliefert.
eine
Hudak
breite
Palette
an
selbst
betont
an
verschiedenen Stellen, dass er mit Haskore nicht nur ein Instrument zur Notation von
Musik und zur Umwandlung in bestimmte Dateistandards produzieren wollte. Vielmehr
habe er einiges spezifisch so konzipiert, dass es gerade auch den analytischen
Ansprüchen eines mathematisch denkenden Geistes genügen mag.
Mit meiner Arbeit habe ich versucht, einen kleinen Teil dieser funktionalen Vielfalt des
Haskore Music Systems aufzuzeigen und vorzustellen. Natürlich ist es schier
unmöglich, alle Möglichkeiten der Module des Systems vorzustellen, jedoch sollten die
wichtigsten Funktionen, Datentypen und weiteren Verwendungszwecke ausreichend
behandelt sein, so dass ein Einstieg und tieferes Arbeiten mit Haskore ermöglicht ist.
Bei meiner Arbeit mit Haskore fiel mir auf, dass an manchen Stellen noch weitere
Funktionen hätten vordefiniert werden können, um das Beschreiben und Erzeugen von
Musik noch etwas komfortabler zu gestalten. Beispielsweise sieht Hudak keine
einfachen vordefinierten Funktionen zur automatisierten Akkord-Erzeugung vor. Dies
14
habe ich (siehe Code 6, Kapitel 2.2) beispielsweise selbst erledigt, um auch hieran die
teils verborgene Mächtigkeit des Systems abermals zu zeigen und natürlich auch zu
nutzen. Insgesamt wäre vielleicht eine stellenweise Überarbeitung des Systems
wünschenswert, um Haskore-Neulingen den Einstieg in das Music System zu
erleichtern. Ansonsten aber lässt sich abschließend nur sagen, dass diese Bibliothek von
Prof. Hudak, ist man einmal eingearbeitet, eindrucksvolle Möglichkeiten der
Musikinterpretation bietet. Mit dem Export in das MIDI-Format erhält man zudem ein
gutes und praktikables Instrument, um den so entwickelten Code zu testen.
Hudak spezifiziert außer einer Schnittstelle nach MIDI auch eine solche nach CSound,
einem Soundsystem, das in der Sprache C entwickelt wurde. CSound ist portabler als
MIDI-Musik, da die hier erzeugten Töne direkt softwareseitig berechnet werden und
somit auf unterschiedlichen Systemen gleich klingen. Bei der Programmierung von
Haskore-Musik für CSound müssen jedoch einige zusätzliche Voraussetzungen
geschaffen und beachtet werden, so dass ich mich aus Gründen der Einfachheit in
meiner Arbeit auf die Erzeugung von MIDI konzentriert habe.
Abschließend bleibt zu sagen, dass Haskore für all jene, die bereits Grundkenntnisse in
der Sprache Haskell besitzen und an der Erzeugung von computergenerierter Musik
interessiert sind, beispielsweise als Hintergrunduntermalung von Spiele-Software, ein
vielseitiges und empfehlenswertes Werkzeug ist. Gerade durch die Ausnutzung der
funktionalen Struktur von Haskell ist die Umsetzung so mancher Systematik
betrachteter Kompositionen im Vergleich zu anderen Musik-Erstellungs-Programmen
bzw. -Sprachen um ein vielfaches vereinfacht.
15
Anhang: Der vollständige Beispiel-Sourcecode
module DieGedankenSindFrei where
import Haskore
import HaskToMidi
-- Benoetigte Hilfsfunktionen
-- Mapfunktionen fuer die erste Stimme
fd d n = n d volFV
vol n = n volFV
-- Mapfunktionen fuer die zweite Stimme
fda d n = n d volSV
vola n = n volSV
-- Mapfunktionen fuer die Begleitung
fdb d n = n d volAcc
volb n = n volAcc
cmap f c = chord (map f c)
-- Lautstaerkedefinitionen erste, zweite und Begleitstimme
volFV
= [Volume 120]
volSV
= [Volume 75]
volAcc
= [Volume 50]
-- Wiederholungsfunktion
times 1
m = m
times (n+1) m = m :+: (times n m)
-- Aufbau von Akkorden: Dur, Dur-Sept, moll
majChord n oct dur = cmap (fdb dur)[Note (n,oct),
Note (pitch(absPitch(n,oct)+4)),Note (pitch(absPitch(n,oct)+7))]
majChord7 n oct dur = cmap (fdb dur)[Note (n,oct),
Note (pitch(absPitch(n,oct)+4)),Note (pitch(absPitch(n,oct)+7)),
Note(pitch(absPitch(n,oct)+10))]
minChord n oct dur = cmap (fdb dur)[Note (n,oct),
Note (pitch(absPitch(n,oct)+3)),Note (pitch(absPitch(n,oct)+7))]
-- Erste Stimme
fVFL1 = map vol [g 4 en,g 4 en,c 5 qn,c 5 qn,e 5 en,c 5 en,g 4 hn]
fVFL2 = map (fd qn) [g 4,f 4,d 4,g 4,e 4,c 4]
fVSL1 = map vol [c 5 qn,b 4 qn,d 5 dqn,b 4 en,c 5 qn,
e 5 qn,c 5 qn,b 4 qn,d 5 dqn,b 4 en]
fVTL1 = map (fd qn) [c 5,e 5,c 5,a 4,a 4]
fVTL2 = map vol [c 5 en,a 4 en,g 4 hn]
fVFoL1 = map (fd en) [c 5,e 5,e 5,d 5]
fVFoL2 = map vol [c 5 qn,b 4 qn,c 5 hn]
-- Zweite Stimme
sVFL1 = map vola [g 4 en,f 4 en,e 4 qn,e 4 qn,g 4 en,a 4 en,e 4 hn]
sVFL2 = map (fda qn) [e 4,d 4,d 4,b 3,c 4,c 4]
sVSL1 = map (fda qn) [e 4,g 4,g 4,f 4,e 4,c 4,e 4,g 4,g 4,f 4]
sVTL1 = map (fda qn) [e 4,c 4,e 4,f 4,e 4,d 4]
sVTL2 = [e 4 hn volSV]
sVFoL1 = map vola [e 4 en,g 4 en,g 4 en,f 4 en,e 4 qn,d 4 qn,e 4 hn]
16
-- Begleitung
accFL1 = [majChord7 G 2 qn,majChord C 2 (6 * qn),
minChord D 2 (2 * qn),majChord G 2 qn,majChord C 2 (2 * qn)]
accFL2 = [majChord7 G 2 qn,majChord C 2 (6 * qn),
minChord D 2 (2 * qn),majChord G 2 qn,majChord C 2 (3 * qn)]
accSL1 = [majChord7 G 2 (3 * qn),majChord C 2 (3 * qn),
majChord7 G 2 (3 * qn)]
accTL1 = [majChord C 2 (3 * qn),majChord F 2 (3 * qn),
majChord C 2 (3 * qn)]
accFoL1 = [minChord D 2 (2 * qn),majChord7 G 2 qn,
majChord C 2 (2 * qn)]
-- Takt
beatLine = [e 3 qn [Volume 60],d 3 qn [Volume 90],e 3 qn [Volume 60]]
-- Zusammensetzen jeweils einer Stimme
firstVoice = line (fVFL1 ++ fVFL2 ++ fVFL1
++ fVFL2 ++ fVSL1 ++ fVTL1 ++ fVTL2 ++ fVFoL1 ++ fVFoL2)
secondVoice = line (sVFL1 ++ sVFL2 ++ sVFL1
++ sVFL2 ++ sVSL1 ++ sVTL1 ++ sVTL2 ++ sVFoL1)
accomp = line (accFL1 ++ accFL2 ++ accSL1 ++ accTL1 ++ accFoL1)
beat = line beatLine
-- MIDI-Parameter
defMyUpm :: UserPatchMap
defMyUpm = [("piano","Acoustic Grand Piano",1),
("reed","Reed Organ",3),
("guitar","Acoustic Bass",6),
("drums","Acoustic Grand Piano",9)]
-- Benennung ist zweitrangig, Kanal 9 definiert Perkussion
-- Zuweisen der MIDI-Eigenschaften Instrument und Geschwindigkeit
fVMidi = Instr "piano" (Tempo 2 firstVoice)
sVMidi = Instr "reed" (Tempo 2 secondVoice)
accompanyMidi = Instr "guitar" (Tempo 2 (accomp))
beatMidi = Instr "drums" (cut (dur fVMidi) (repeatM (Tempo 2 beat)))
dieGedankenSindFrei
=
times 3 (fVMidi :=: sVMidi :=: accompanyMidi)
dieGedankenSindFreiMitBeat =
times 3 (fVMidi :=: sVMidi :=: accompanyMidi :=: beatMidi)
-- Umwandeln von Music-Objekten in ein Vorformat
goMidi :: Music -> MidiFile
goMidi m = performToMidi (testPerf m) defMyUpm
-- Ausgabefunktionen zum Erzeugen der endgueltigen Midi-Dateien
makeMidiFile :: Music -> String -> IO ()
makeMidiFile musicTrack fileName =
outputMidiFile fileName (goMidi musicTrack)
makeFV, makeSV, makeAcc, makeItSo, makeItSo2 :: IO ()
makeFV
= makeMidiFile fVMidi "dieGedankenSindFrei-FV.mid"
makeSV
= makeMidiFile sVMidi "dieGedankenSindFrei-SV.mid"
makeAcc
= makeMidiFile accompanyMidi "dieGedankenSindFrei-Acc.mid"
makeBeat = makeMidiFile beatMidi "dieGedankenSindFrei-Beat.mid"
makeItSo = makeMidiFile dieGedankenSindFrei "dieGedankenSindFrei.mid"
makeItSo2 = makeMidiFile
dieGedankenSindFreiMitBeat "dieGedankenSindFrei2.mid"
17
Quellenverzeichnis
Hauptquelle:
•
Paul Hudak – Haskore Music Tutorial
http://www.haskell.org/haskore/Haskore/Haskore/Docs/tutorial.ps
Nebenquellen:
•
General MIDI – Kurze Erläuterung
http://de.wikipedia.org/wiki/General_Midi
•
General MIDI – Spezifikationen
http://www.borg.com/~jglatt/tutr/gm.htm
•
Notenvorlage Die Gedanken sind frei
Kein schöner Land – Das große Buch unserer beliebtesten Volkslieder, S. 62
Falken Verlag – ISBN 380684150
Der Source-Code im Anhang und in den Codebeispielen in den einzelnen Kapiteln
wurde vollständig von mir selbst erzeugt. Ausnahmen bilden die Funktionen fd und
times, die aus dem Haskore Music Tutorial übernommen wurden.
18
Herunterladen