[ < ] [ > ]   [ << ] [ Nach oben ] [ >> ]         [Anfang] [Inhalt] [Index] [ ? ]

1.2 Scheme in LilyPond

@translationof Scheme in LilyPond


[ < ] [ > ]   [ << ] [ Nach oben ] [ >> ]         [Anfang] [Inhalt] [Index] [ ? ]

1.2.1 LilyPond Scheme-Syntax

@translationof LilyPond Scheme syntax

Der Guile-Auswerter ist ein Teil von LilyPond, sodass Scheme also auch in normale LilyPond-Eingabedateien eingefügt werden kann. Es gibt mehrere Methoden, um Scheme in LilyPond zu integrieren.

Die einfachste Weise ist es, ein Rautenzeichen # vor einem Scheme-Ausdruck zu benutzen.

Die Eingabe von LilyPond ist in Zeichen und Ausdrücke gegliedert, so etwa wie die menschliche Sprache sich in Wörter und Sätze gliedert. LilyPond hat einen Lexer, der Zeichen erkennt (Zahlen, Zeichenketten, Scheme-Elemente, Tonhöhen usw.) und einen Parser, der die Syntax versteht, (contributor)LilyPond grammar in Abschnitt ‘LilyPond-Grammatik’ in Contributor’s Guide. Wenn dann eine bestimmte Syntaxregel als zuständig erkannt wurde, werden die damit verknüpften Aktionen ausgeführt.

Die Rautenzeichenmethode (#), mit der Scheme eingebettet werden kann, passt sehr gut in dieses System. Wenn der Lexer ein Rautenzeichen sieht, ruft er den Scheme-reader auf, um den ganzen Scheme-Ausdruck zu lesen (das kann eine Variable, ein Ausdruck in Klammern oder verschiedene andere Sachen sein). Nachdem der Scheme-Ausdruck gelesen wurde, wird er als Wert eines SCM_TOKEN in der Grammatik gespeichert. Wenn der Parser weiß, wie er diesen Wert benutzen kann, ruft er Guile auf, um den Scheme-Ausdruck auszuwerten. Weil der Parser normalerweise dem Lexer etwas voraus sein muss, ist die Trennung von Lesen und Auswerten zwischen Lexer und Parser genau das richtige, um die Auswertung von LilyPond- und Scheme-Ausdrücken synchron zu halten. Aus diesem Grund sollte das Rautenzeichen zum Einbinden von Scheme immer benutzt werden, wenn es möglich ist.

Eine andere Möglichkeit, Scheme aufzurufen, ist die Benutzung des Dollarzeichens ($) anstelle der Raute. In diesem Fall wertet LilyPond den Code sofort aus, nachdem der Lexer ihn gelesen hat. Dabei wird der resultierende Scheme-Ausdruckstyp geprüft und eine Tokentyp dafür ausgesucht (einer von mehreren xxx_IDENTIFIER in der Syntax). Wenn der Wert des Ausdrucks gültig ist (der Guilde-Wert für *unspecified*), dann wird nichts an den Parser übergeben.

Das ist auch der gleiche Mechanismus, nach dem LilyPond funktioniert, wenn man eine Variable oder musikalische Funktion mit ihrer Bezeichnung ausruft, wie in \Bezeichnung, mit dem einzigen Unterschied, dass ihre Bezeichnung durch den LilyPond-Lexer bestimmt wird, ohne den Scheme-reader einzubeziehen, und also nur Variablen akzeptiert werden, die im aktuellen LilyPond-Modus gültig sind.

Die direkte Auswirkung von $ kann zu Überraschungen führen, siehe Eingabe-Variablen und Scheme. Es bietet sich daher an, # immer zu benützen, wenn der Parser es unterstützt. Innerhalb von musikalischen Ausdrücken werden Ausdrücke, die mit # erstellt werden, tatsächlich als Noten interpretiert. Sie werden jedoch nicht vor der Benutzung kopiert. Wenn Sie Teil einer Struktur sind, die noch einmal benutzt werden soll, muss man eventuell ly:music-deep-copy explizit einsetzen.

Es gibt auch die Operatoren $@ und #@, die eine „listentrennende“ Funktion aufweisen, indem sie alle Elemente einer Liste in den umgebenden Kontext einfügen.

Jetzt wollen wir uns tatsächlichen Scheme-Code anschauen. Scheme-Prozeduren können in LilyPond-Eingabedateien definiert werden:

 
#(define (average a b c) (/ (+ a b c) 3))

LilyPond-Kommentare (% oder %{ %}) können innerhalb von Scheme-Code nicht benutzt werden, nicht einmal in einer LilyPond-Eingabedatei, weil der Guile-Interpreter und nicht der LilyPond-Parser den Scheme-Ausdruck liest. Kommentare in Guile Scheme werden wie folgt notiert:

 
; Einzeiliges Kommentar

#!
  Guile-Stil Blockkommentar (nicht schachtelbar)
  Diese Kommentare werden von Scheme-Programmierern
  selten benutzt und nie im Quellcode
  von LilyPond
!#

Für den Rest dieses Abschnitts soll angenommen werden, dass die Daten in einer LilyPond-Eingabedatei notiert werden sollen, sodass immer # vor die Scheme-Ausdrücke gestellt wird.

Alle Scheme-Ausdrücke auf oberster Ebene in einer LilyPond-Eingabedatei können in einen einzigen Scheme-Ausdruck zusammengefasst werden mit begin:

 
#(begin
  (define foo 0)
  (define bar 1))

[ < ] [ > ]   [ << ] [ Nach oben ] [ >> ]         [Anfang] [Inhalt] [Index] [ ? ]

1.2.2 LilyPond-Variablen

@translationof LilyPond variables

LilyPond-Variablen werden intern als Scheme-Variablen gespeichert. Darum ist

 
Zwoelf = 12

das Gleiche wie

 
#(define Zwoelf 12)

Das bedeutet, dass Scheme-Ausdrücke auf LilyPond-Variablen zugreifen können. Man könnte also schreiben:

 
Vierundzwanzig = #(* 2 Zwoelf)

was zur Folge hätte, dass die Zahl 24 in der LilyPond (und Scheme-)Variablen Vierundzwanzig gespeichert wird.

Üblicherweise greift man auf LilyPond-Variablen zu, indem man ihnen einen Backslash voranstellt. Siehe auch LilyPond Scheme-Syntax, also etwa \Vierundzwanzig. Weil dadurch eine Kopie des Wertes für die meisten von LilyPonds internen Typen erstellt wird (insbesondere musikalische Funktionen), erstellen musikalische Funktionen normalerweise Kopien von Material, das sie verändern. Aus diesem Grund sollten musikalische Funktionen, die mit # aufgerufen werden, kein Material enthalten, dass entweder von Grund auf neu erstellt wird oder explizit kopiert wird, sondern besser direkt auf das relevante Material verweisen.


[ < ] [ > ]   [ << ] [ Nach oben ] [ >> ]         [Anfang] [Inhalt] [Index] [ ? ]

1.2.3 Eingabe-Variablen und Scheme

@translationof Input variables and Scheme

Das Eingabeformat unterstützt die Notation von Variablen: Im folgenden Beispiel wird ein musikalischer Ausdruck einer Variablen mit der Bezeichnung traLaLa zugewiesen:

 
traLaLa = { c'4 d'4 }

Variablen haben beschränkte Geltungsbereiche: im unten stehenden Beispiel enthält auch die \layout-Umgebung eine traLaLa-Variable, die sich aber von der traLaLa-Variable auf oberster Ebene unterscheidet:

 
traLaLa = { c'4 d'4 }
\layout { traLaLa = 1.0 }

Jede Eingabedatei ist solch ein Gültigkeitsbereich, und alle \header-, \midi- und \layout-Umgebungen sind Gültigkeitsbereiche, die in diesen obersten Gültigkeitsbereich eingebettet sind.

Sowohl Variablen als auch Gültigkeitsbereiche sind im Guile Modulsystem eingebaut. Ein anonymes Scheme-Modul wird jedem Gültigkeitsbereich angehängt. Eine Zuweisen in der Form von

 
traLaLa = { c'4 d'4 }

wird intern in die Scheme-Definition

 
(define traLaLa Scheme-Wert von `... ')

konvertiert.

Das bedeutet, dass LilyPond-Variablen und Scheme-Variablen frei gemischt werden können. Im folgenden Beispiel wird ein Notenfragment in der Variable traLaLa gespeichert und mit Scheme dupliziert. Das Resultat wird in eine \score-Umgebung mit einer weiteren Variable twice importiert:

 
traLaLa = { c'4 d'4 }

#(define newLa (map ly:music-deep-copy
  (list traLaLa traLaLa)))
#(define twice
  (make-sequential-music newLa))

\twice

[image of music]

Das ist ein interessantes Beispiel. Die Zuweisung findet erst statt, nachdem der Parser sichergestellt hat, dass nichts folgt, das Ähnlichkeit mit \addlyrics hat, sodass er prüfen muss, was als nächstes kommt. Er liest # und den darauf folgenden Scheme-Ausdruck, ohne ihn auszuwerten, so dass er weiterlesen und erst danach wird der Scheme-Code ohne Probleme ausführen kann.


[ < ] [ > ]   [ << ] [ Nach oben ] [ >> ]         [Anfang] [Inhalt] [Index] [ ? ]

1.2.4 Scheme in LilyPond importieren

@translationof Importing Scheme in LilyPond

Das Beispiel zeigt, wie man musikalische Ausdrücke aus der Eingabe in den Scheme-Auswerter „exportieren“ kann. Es geht auch andersherum. Indem man Scheme-Werte nach $ schreibt, wird ein Scheme-Wert interpretiert, als ob er in LilyPond-Syntax eingeben wäre. Anstatt \twice zu definieren, könne man also auch schreiben:

 
...
$(make-sequential-music newLa)

Mann kann $ zusammen mit einem Scheme-Ausdruck überall benutzen, wo auch \Bezeichnung gültig wäre, nachdem der Scheme-Ausdruck einmal einer Variable Bezeichnung zugewiesen worden ist. Der Austausch geschieht im Lexer, sodass LilyPond den Unterschied gar nicht merkt.

Ein negativer Effekt ist aber das Timing. Wenn man $ anstelle von # für die Definition von newLa im obigen Beispiel eingesetzt hätte, würde der folgende Scheme-Ausdruck fehlschlagen, weil traLaLa noch nicht definiert worden wäre. Zu einer Erklärung dieses Timingproblems siehe LilyPond Scheme-Syntax.

Eine weitere Bequemlichkeit können die „listentrennenden“ Operatoren $@ und #@ bieten, indem sie die Elemente einer Liste in den umgebenden Kontext einfügen. Wenn man sie einsetzt, hätte der letzte Teil des Beispiels auch so geschrieben werden können:

 
...
{ #@newLa }

Hier wird jedes Element der Liste, welche in newLa gespeichert ist, der Reihenfolge nach genommen und in die Liste eingefügt, als ob man geschrieben hätte:

 
{ #(first newLa) #(second newLa) }

In allen diesen Formen findet die Auswertung des Scheme-Codes statt, während die Eingabe noch gelesen wird, entweder im Lexer oder im Parser. Wenn man es später ausgeführt haben möchte, muss man Leere Scheme-Funktionen benutzen oder es in einem Makro speichern:

 
#(define (nopc)
  (ly:set-option 'point-and-click #f))

...
#(nopc)
{ c'4 }

Bekannte Probleme und Warnungen

Scheme- und LilyPond-Variablen können nicht gemischt werden, wenn man die ‘--safe’-Option benutzt.


[ < ] [ > ]   [ << ] [ Nach oben ] [ >> ]         [Anfang] [Inhalt] [Index] [ ? ]

1.2.5 Objekteigenschaften

@translationof Object properties

Objekteigenschaften werden in LilyPond in Form von Alisten-Ketten gespeichert, also als Listen von Alisten. Eigenschaften werden geändert, indem man Werte an den Anfang der Eigenschaftsliste hinzufügt. Eigenschaften werden gelesen, indem Werte aus der Aliste gelesen werden.

Ein neuer Wert für eine Eigenschaft kann gesetzt werden, indem man der Alist einen Wert mit Schlüssel und dem Wert zuweist. Die LilyPond-Syntax hierfür ist:

 
\override Stem.thickness = #2.6

Diese Anweisung verändert die Erscheinung der Notenhälse. Der Alist-Eintrag '(thickness . 2.6) wird zu der Eigenschaftsliste eines Stem-(Hals-)Objektes hinzugefügt. thickness wird relativ zu den Notenlinien errechnet, in diesem Fall sind die Hälse also 2,6 mal so dick wie die Notenlinien. Dadurch werden Hälse fast zweimal so dick dargestellt, wie sie normalerweise sind. Um zwischen Variablen zu unterscheiden, die in den Quelldateien direkt definiert werden (wie Vierundzwanzig weiter oben), und zwischen denen, die für interne Objekte zuständig sind, werden hier die ersteren „Variablen“ genannt, die letzteren dagegen „Eigenschaften“. Das Hals-Objekt hat also eine thickness-Eigenschaft, während Vierundzwanzig eine Variable ist.


[ < ] [ > ]   [ << ] [ Nach oben ] [ >> ]         [Anfang] [Inhalt] [Index] [ ? ]

1.2.6 Zusammengesetzte LilyPond-Variablen

@translationof LilyPond compound variables

Abstände (offset)

Zweidimensionale Abstände (X- und Y-Koordinaten) werden als pairs (Paare) gespeichert. Der car-Wert des Abstands ist die X-Koordinate und der cdr-Wert die Y-Koordinate.

 
\override TextScript.extra-offset = #'(1 . 2)

Hierdurch wird das Paar (1 . 2) mit der Eigenschaft extra-offset des TextScript-Objektes verknüpft. Diese Zahlen werden in Systembreiten gemessen, so dass der Befehl das Objekt eine Systembreite nach rechts verschiebt und zwei Breiten nach oben.

Prozeduren, um mit Abständen zu arbeiten, finden sich in ‘scm/lily-library.scm’.

Brüche (fractions)

Brüche, wie sie LilyPond benutzt, werden wiederum als Paare gespeichert, dieses Mal als unbezeichnete ganze Zahlen. Während Scheme rationale Zahlen als einen negativen Typ darstellen kann, sind musikalische gesehen ‘2/4’ und ‘1/2’ nicht das selbe, sodass man zwischen beiden unterscheiden können muss. Ähnlich gibt es auch keine negativen Brüche in LilyPonds Sinn. Somit bedeutet 2/4 in LilyPond (2 . 4) in Scheme, und #2/4 in LilyPond bedeutet 1/2 in Scheme.

Bereiche (extend)

Paare werden auch benutzt, um Intervalle zu speichern, die einen Zahlenbereich vom Minimum (dem car) bis zum Maximum (dem cdr) darstellen. Intervalle werden benutzt, um die X- und Y-Ausdehnung von druckbaren Objekten zu speichern. Bei X-Ausdehnungen ist car die linke X-Koordinate und cdr die rechte X-Koordinate. Für Y-Ausdehnungen ist car die untere Koordinate und cdr die obere Koordinate.

Prozeduren, um mit Intervallen zu arbeiten, finden sich in ‘scm/lily-library.scm’. Diese Prozeduren sollten benutzt, wenn es möglich ist, um den Code konsistent zu halten.

Eigenschafts-Alisten (property alist)

Eine Eigenschafts-Aliste ist eine LilyPond-Datenstruktur, die eine Aliste darstellt, deren Schlüssel Eigenschaften sind und deren Werte Scheme-Ausdrücke sind, die den erwünschen Wert der Eigenschaft ausgeben.

LilyPond-Eigenschaften sind Scheme-Symbole, wie etwa 'thickness (Dicke).

Alisten-Ketten (alist chains)

Eine Alisten-Kette ist eine Liste, die Eigenschafts-Alisten enthält.

Die Menge aller Eigenschaften, die sich auf einen Grob auswirken, wird typischerweise in einer Alisten-Kette gespeichert. Um den Wert einer bestimmten Eigenschaft zu finden, die ein Grob haben soll, wird jede Liste in der Kette nach einander durchsucht, wobei nach einem Eintrag geschaut wird, der den Eigenschaftsschlüssel enthält. Der erste gefundene Alisten-Eintrag wird benutzt und dessen Wert ist der Wert der Eigenschaft.

Die Scheme-Prozedur chain-assoc-get wird normalerweise benutzt, um Grob-Eigenschaftenwerte zu erhalten.


[ < ] [ > ]   [ << ] [ Nach oben ] [ >> ]         [Anfang] [Inhalt] [Index] [ ? ]

1.2.7 Interne musikalische Repräsentation

@translationof Internal music representation

Intern werden Noten als Scheme-Liste dargestellt. Die Liste enthält verschiedene Elemente, die die Druckausgabe beeinflussen. Parsen nennt man den Prozess, der die Noten aus der LilyPond-Repräsentation in die interne Scheme-Repräsentation überführt.

Wenn ein musikalischer Ausdruck geparst wird, wird er in eine Gruppe von Scheme-Musikobjekten konvertiert. Die definierende Eigenschaft eines Musikobjektes ist, dass es Zeit einnimmt. Die Zeit, die ein Objekt braucht, wird Dauer (engl. duration) genannt. Dauern werden in rationalen Zahlen ausgedrückt, die die Länge des Musikobjekts in Ganzen Noten angeben.

Ein Musikobjekt hat drei Typen:

Die eigentliche Information eines musikalischen Ausdrucks wird in Eigenschaften gespeichert. Ein (internals)NoteEvent in Abschnitt ‘NoteEvent’ in Referenz der Interna beispielsweise hat die Eigenschaften Tonhöhe (pitch) und Dauer (duration), die die Dauer und die Tonhöhe der Note speichern. Eine Liste aller möglichen Eigenschaften findet sich in der Referenz der Interna, unter (internals)Music properties in Abschnitt ‘Music properties’ in Referenz der Interna.

Ein zusammengesetzter musikalischer Ausdruck ist ein Musikobjekt, das andere Musikobjekte als Eigenschaften enthält. Eine Liste an Objekten kann in der elements-Eigenschaft eines Musikobjekts bzw. ein einziges Ableger-Musikelement in der element-Eigenschaft gespeichert werden. (internals)SequentialMusic in Abschnitt ‘SequentialMusic’ in Referenz der Interna beispielsweise hat sein einziges Argument in element. Der Körper einer Wiederholung wird in der element-Eigenschaft von (internals)RepeatedMusic in Abschnitt ‘RepeatedMusic’ in Referenz der Interna gespeichert, und die alternativen Endungen in elements.


[ < ] [ > ]   [ << ] [ Nach oben ] [ >> ]

Dieses Dokument wurde erzeugt von gub am 13. Juli 2020 durch texi2html 1.82.

Andere Sprachen: English, español, français.
Über die automatische Sprachauswahl.