| [ < ] | [ > ] | [ << ] | [ Nach oben ] | [ >> ] | [Anfang] | [Inhalt] | [Index] | [ ? ] |
@translationof Scheme in LilyPond
| [ < ] | [ > ] | [ << ] | [ Nach oben ] | [ >> ] | [Anfang] | [Inhalt] | [Index] | [ ? ] |
@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] | [ ? ] |
@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] | [ ? ] |
@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
|
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] | [ ? ] |
@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 }
|
Scheme- und LilyPond-Variablen können nicht gemischt werden, wenn man die ‘--safe’-Option benutzt.
| [ < ] | [ > ] | [ << ] | [ Nach oben ] | [ >> ] | [Anfang] | [Inhalt] | [Index] | [ ? ] |
@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] | [ ? ] |
@translationof LilyPond compound variables
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, 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.
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.
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).
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] | [ ? ] |
@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:
\simultaneous
produziert
(internals)SimultaneousMusic in Abschnitt ‘SimultaneousMusic’ in Referenz der Interna. Eine Liste aller möglichen
Ausdrücke findet sich in der Referenz der Interna, unter
(internals)Music expressions in Abschnitt ‘Music expressions’ in Referenz der Interna.
event),
aber auch ein Notenereignis (note-event), ein rhythmisches Ereignis
(rhythmic-event) und ein Melodieereignis (melodic-event).
Alle Musikklassen sind in der Referenz der Interna aufgelistet, unter
(internals)Music classes in Abschnitt ‘Music classes’ in Referenz der Interna.
Music
repräsentiert.
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.