[ << Notensatz ] | [Anfang][Inhalt][Index] | [ Literatur >> ] |
[ < Alles richtig machen ] | [ Nach oben : Notensatz ] | [ Die Darstellung der Musik > ] |
1.4 Ein Programm bauen
Dieser Abschnitt beschreibt einige der Entscheidungen, die wir während des Programmierens für das Design von LilyPond getroffen haben.
Die Darstellung der Musik | ||
Welche Symbole? | ||
Flexible Architektur |
[ << Notensatz ] | [Anfang][Inhalt][Index] | [ Literatur >> ] |
[ < Ein Programm bauen ] | [ Nach oben : Ein Programm bauen ] | [ Welche Symbole? > ] |
Die Darstellung der Musik
Idealerweise ist das Eingabeformat für ein komplexes Satzsystem die abstrakte Beschreibung des Inhaltes. In diesem Fall wäre das die Musik selber. Das stellt uns aber vor ein ziemlich großes Problem, denn wie können wir definieren, was Musik wirklich ist? Anstatt darauf eine Antwort zu suchen, haben wir die Frage einfach umgedreht. Wir schreiben ein Programm, das den Notensatz beherrscht und machen das Format so einfach wie möglich. Wenn es nicht mehr vereinfacht werden kann, haben wir per Definition nur noch den reinen Inhalt. Unser Format dient als die formale Definition eines Musiktextes.
Die Syntax ist gleichzeitig die Benutzerschnittstelle bei LilyPond, darum soll sie einfach zu schreiben sein; z. B. bedeutet
{ c'4 d'8 }
dass eine Viertel c’ und eine Achtel d’ erstellt werden sollen, wie in diesem Beispiel:
In kleinem Rahmen ist diese Syntax sehr einfach zu benutzen. In größeren Zusammenhängen aber brauchen wir Struktur. Wie sonst kann man große Opern oder Symphonien notieren? Diese Struktur wird gewährleistet durch sog. music expressions (Musikausdrücke): indem kleine Teile zu größeren kombiniert werden, kann komplexere Musik dargestellt werden. So etwa hier:
f'4
Gleichzeitig erklingende Noten werden hinzugefügt, indem man alle in <<
und >>
einschließt.
<<c4 d4 e4>>
Um aufeinanderfolgende Noten darzustellen, werden sie in geschweifte Klammern gefasst:
{ … }
{ f4 <<c4 d4 e4>> }
Dieses Gebilde ist in sich wieder ein Ausdruck, und kann
daher mit einem anderen Ausdruck kombiniert werden (hier mit einer Halben), wobei <<
, \\
, and >>
eingesetzt wird:
<< g2 \\ { f4 <<c4 d4 e4>> } >>
Solche geschachtelten Strukturen können sehr gut in einer kontextunabhängigen Grammatik beschrieben werden. Der Programmcode für den Satz ist auch mit solch einer Grammatik erstellt. Die Syntax von LilyPond ist also klar und ohne Zweideutigkeiten definiert.
Die Benutzerschnittstelle und die Syntax werden als erstes vom Benutzer wahrgenommen. Teilweise sind sie eine Frage des Geschmackes und werden viel diskutiert. Auch wenn Geschmacksfragen ihre Berechtigung haben, sind sie nicht sehr produktiv. Im großen Rahmen von LilyPond spielt die Eingabe-Syntax nur eine geringe Rolle, denn eine logische Syntax zu schreiben ist einfach, guten Formatierungscode aber sehr viel schwieriger. Das kann auch die Zeilenzahl der Programmzeilen zeigen: Analysieren und Darstellen nimmt nur etwa 10% des Codes ein:
Während wir die Strukturen von LilyPond entwickelten, machten wir einige Entscheidungen, die sich von anderen Programmen unterscheiden. Nehmen wir etwa die hierarchische Natur der Musiknotation:
In diesem Fall werden Tonhöhen in Akkorde gruppiert, die zu Takten gehören, welche wiederum zu Notensystemen gehören. Das erinnert an die saubere Struktur von geschachtelten Kästen:
Leider ist die Struktur nur sauber, weil sie auf einige sehr beschränkte Annahmen basiert. Das wird offensichtlich, wenn man ein komplizierteres Beispiel heranzieht:
In diesem Beispiel beginnen Systeme plötzlich und enden plötzlich, Stimmen springen zwischen den Systemen herum und die Systeme haben unterschiedliche Taktarten. Viele Software-Pakte würden sehr damit zu kämpfen haben, dieses Beispiel darzustellen, weil sie nach dem Prinzip von geschachtelten Kästen aufgebaut sind. In LilyPond dagegen haben wir versucht, die Struktur und das Eingabeformat so flexibel wie möglich zu gestalten.
[ << Notensatz ] | [Anfang][Inhalt][Index] | [ Literatur >> ] |
[ < Die Darstellung der Musik ] | [ Nach oben : Ein Programm bauen ] | [ Flexible Architektur > ] |
Welche Symbole?
Während des Notensatzprozesses entscheidet sich, wo Symbole platziert werden. Das kann aber nur gelingen, wenn vorher entschieden wird, welche Symbole gesetzt werden sollen, also welche Art von Notation benutzt werden soll.
Die heutige Notation ist ein System zur Musikaufzeichnung, das sich über die letzten 1000 Jahre entwickelt hat. Die Form, die heute üblicherweise benutzt wird, stammt aus der frühen Renaissance. Auch wenn sich die grundlegenden Formen (also die Notenköpfe, das Fünfliniensystem) nicht verändert haben, entwickeln sich die Details trotzdem immer noch weiter, um die Errungenschaften der Neuen Musik darstellen zu können. Die Notation umfasst also 500 Jahre Musikgeschichte. Ihre Anwendung reicht von monophonen Melodien bis zu ungeheuer komplexem Kontrapunkt für großes Orchester.
Wie bekommen wir dieses vielköpfige Monster zu fassen und in die
Fesseln eines Computerprogrammes zu legen?
Unsere Lösung ist es, das Problem in kleine (programmierbare) Happen zu zerteilen, so dass jede Art
von Symbol durch ein eigenes Modul, als Plugin bezeichnet,
verarbeitet werden kann. Jedes Plugin ist vollständig modular
und unabhängig und kann unabhängig entwickelt und verbessert
werden. Derartige Plugins werden engraver
genannt,
analog zu den Notenstechern (engl. engraver), die musikalische
Ideen in graphische Symbole übersetzen.
Im nächsten Beispiel wird gezeigt, wie mit dem Plugin für die Notenköpfe,
dem Note_heads_engraver
(„Notenkopfstecher“) der Satz begonnen wird.
Dann fügt ein Staff_symbol_engraver
(„Notensystemstecher“)
die Notenlinien hinzu.
Der Clef_engraver
(„Notenschlüsselstecher“) definiert
eine Referenzstelle für das System.
Der Stem_engraver
(„Halsstecher“) schließlich fügt
Hälse hinzu.
Dem Stem_engraver
wird jeder Notenkopf mitgeteilt,
der vorkommt. Jedes Mal, wenn ein Notenkopf erscheint (oder mehrere bei
einem Akkord), wird ein Hals-Objekt erstellt und an den
Kopf geheftet. Wenn wir dann noch Engraver für Balken, Bögen,
Akzente, Versetzungszeichen, Taktstriche, Taktangaben und Tonartbezeichnungen
hinzufügen, erhalten wir eine vollständige Notation.
Dieses System funktioniert gut für monophone Musik, aber wie geht es mit Polyphonie? Hier müssen sich mehrere Stimmen ein System teilen.
In diesem Fall benutzen beide Stimmen das System und die Vorzeichen gemeinsam, aber die Hälse, Bögen, Balken usw. sind jeder einzelnen Stimme eigen. Die Engraver müssen also gruppiert werden. Die Köpfe, Hälse, Bögen usw. werden in einer Gruppe mit dem Namen „Voice context“ (Stimmenkontext) zusammengefasst, die Engraver für den Schlüssel, die Vorzeichen, Taktstriche usw. dagegen in einer Gruppe mit dem Namen „Staff context“ (Systemkontext). Im Falle von Polyphonie hat ein Staff-Kontext dann also mehr als nur einen Voice-Kontext. Auf gleiche Weise können auch mehrere Staff-Kontexte in einen großen Score-Kontext (Partiturkontext) eingebunden werden. Der Score-Kontext ist auf der höchsten Ebene der Kontexte.
Siehe auch
Referenz der Interna: Contexts.
[ << Notensatz ] | [Anfang][Inhalt][Index] | [ Literatur >> ] |
[ < Welche Symbole? ] | [ Nach oben : Ein Programm bauen ] | [ LilyPond die Arbeit überlassen > ] |
Flexible Architektur
Als wir anfingen, haben wir LilyPond vollständig in der Programmiersprache C++ geschrieben. Das hieß, dass der Funktionsumfang des Programms vollständig durch die Programmierer festgelegt war. Das stellte sich aus einer Reihe von Gründen als unzureichend heraus:
- Wenn LilyPond Fehler macht, muss der Benutzer die Einstellungen ändern können. Er muss also Zugang zur Formatierungsmaschinerie haben. Deshalb können die Regeln und Einstellungen nicht beim Kompilieren des Programms festgelegt werden, sondern sie müssen zugänglich sein, während das Programm aktiv ist.
- Notensatz ist eine Frage des Augenmaßes, und damit auch vom Geschmack abhängig. Benutzer können mit unseren Entscheidungen unzufrieden sein. Darum müssen also auch die Definitionen des typographischen Stils dem Benutzer zugänglich sein.
- Schließlich verfeinern wir unseren Formatierungsalgorithmus immer weiter, also müssen die Regeln auch flexibel sein. Die Sprache C++ zwingt zu einer bestimmten Gruppierungsmethode, die nicht den Regeln für den Notensatz entspricht.
Diese Probleme wurden angegangen, indem ein Übersetzer für die Programmiersprache Scheme integriert wurde und Teile von LilyPond in Scheme neu geschrieben wurden. Die derzeitige Formatierungsarchitektur ist um die Notation von graphischen Objekten herum aufgebaut, die von Scheme-Variablen und -Funktionen beschrieben werden. Diese Architektur umfasst Formatierungsregeln, typographische Stile und individuelle Formatierungsentscheidungen. Der Benutzer hat direkten Zugang zu den meisten dieser Einstellungen.
Scheme-Variablen steuern Layout-Entscheidungen. Zum Beispiel haben viele graphische Objekte eine Richtungsvariable, die zwischen oben und unten (oder rechts und links) wählen kann. Hier etwa sind zwei Akkorde mit Akzenten und Arpeggien. Beim ersten Akkord sind alle Objekte nach unten (oder links) ausgerichtet, beim zweiten nach oben (rechts).
Der Prozess des Notensetzens besteht für das Programm darin, die Variablen der graphischen Objekte zu lesen und zu schreiben. Einige Variablen haben festgelegte Werte. So ist etwa die Dicke von vielen Linien – ein Charakteristikum des typographischen Stils – von vornherein festgelegt. Wenn sie geändert werden, ergibt sich ein anderer typographischer Eindruck.
Formatierungsregeln sind auch vorbelegte Variablen. Zu jedem Objekt gehören Variablen, die Prozeduren enthalten. Diese Prozeduren machen die eigentliche Satzarbeit aus, und wenn man sie durch andere ersetzt, kann die Darstellung von Objekten verändert werden. Im nächsten Beispiel wird die Regel, nach der die Notenköpfe gezeichnet werden, während des Ausschnitts verändert.
[ << Notensatz ] | [Anfang][Inhalt][Index] | [ Literatur >> ] |
[ < Welche Symbole? ] | [ Nach oben : Ein Programm bauen ] | [ LilyPond die Arbeit überlassen > ] |