Dieses Beispiel geht zurück auf die Frage, wie man mit TeX musikalische Funktionssymbole für Tonika, Dominate etc. zusammen mit den Ziffern für die Stimmführung (das Voicing) darstellen kann, die vor einiger Zeit auf TeX.SX gestellt wurde. Bei der Lösung des Problems wollen wir uns vor allem mit den neuen Funktionen von LaTeX3 vertraut machen, sodass dieses Beispiel auch für Nicht-Musiker interessant bleibt.
Einleitung
Wir wollen einen Befehl entwickeln, der es uns ermöglicht, Funktionssymbole und Bewegungen der einzelnen Stimmen (den „Melodien“ aus denen der Akkord sich zusammensetzt) wie in diesem Bild darzustellen:
Diese Darstellung besteht aus drei Elementen: 1. Dem Funktionsymbol – hier für die sog. Doppeldominante – 2. den Fortschreitungen der Oberstimmen sowie 3. der der Bassstimme, wobei letztere optional ist. Für die Umsetzung werden wir aber alle Stimmen als Zeilen einer Tabelle behandeln. Außerdem führen wir ein Raster ein, um die Ziffern untereinander ausrichten zu können. Neben den Ziffern benötigen wir noch die Möglichkeit, Leerräume und vertikale Linien zu erzeugen.
Für die Eingabe soll folgende Syntax gelten:
\fsymb{Symbol}[Bass]{Oberstimmen}
Die Stimmen wollen wir in der Form ..8--7--
notieren können, wobei der Punkt einen Leerschritt erzeugen und die Trennstriche zu einer Linie werden sollen. Mehrere Oberstimmen wollen wir durch ein Komma getrennt eingeben, so dass für das obige Bild diese Eingabe nötig wäre:
\fsymb{DD}[8----7]{4---3-, 6---5-, ..{11}---}
Neben der Verarbeitung der Stimmen brauchen wir also auch noch etwas, das aus DD
das korrekte Symbol macht. Darüber hinaus werden wir noch ein zweites optionales Argument einführen, um den Abstand zwischen Symbol und Stimmverlauf verändern zu können:
\fsymb{Symbol}[Bass][Abstand]{Oberstimmen}
LaTeX3-Grundlagen
Bevor wir nun aber mit der Programmierung beginnen können, müssen wir uns mit ein paar Grundlagen von LaTeX3 vertraut machen. LaTeX3 bietet uns viele Makros und Funktionen, die das Programmieren von eigenen Befehlen wesentlich erleichtern. Es gelten die folgenden Konventionen:
- Wir unterscheiden zwischen Variablen (und Konstanten), die Werte repräsentieren, und Befehlen (Funktionen), die auf diese Variablen wirken können und/oder andere Aufgaben ausführen.
- Module fassen Variablen und Befehle zusammen.
- Jeder Variablen-/Befehlsname enthält eine Bezeichnung für das Modul, zu dem er gehört. Wir werden in diesem Beispiel
mfs
(musical function symbol) benutzen. - Leerzeichen werden ignoriert; wird ein Leerzeichen benötigt, kann man es mit Tilde
~
eingeben. Daher ist es auch nicht nötig, Zeilen mit%
zu beenden (vgl. FAQ 6). - Der Unterstrich
_
wird als Teil von Befehls- und Variablennamen benutzt, um diese lesbar zu machen (analog zum@
, das in klassischen Paketen benutzt wird). - Ein Variablenname (Konstantenname) beginnt immer mit
\l_modul_
,\g_modul_
oder\c_modul_
, um anzuzeigen, ob der Wert der Variablen sich lokal ändern kann, seine Änderung immer global ist oder eine Konstante (c) bildet. Ein Variablenname endet mit der Angabe des Typs, z. B._int
für natürliche Zahlen (integer),_dim
für Längen (dimension) oder_tl
für sog. Token-Listen (eine Folge von Zeichen und Befehlen, siehe auch What is a token?).
Beispiel:\c_mfs_boxwidth_dim
. - Ein Befehlsname beginnt immer mit
\modul_
und endet mit:arg
, wobeiarg
für die Angabe der Argumente des Befehls steht. Bspw.nn
für zwei „normale“ Argumente in geschweiften Klammern oderN
für ein Einzel-Token wie etwa eine Variable. Hat ein Befehl keine Argumente, endet sein Name mit einem Doppelpunkt.
Beispiel:\mfs_process_list:n
.
Eine ausführlichere Einführung in die neuen Konventionen inkl. der vollständigen Listen der Variablen- und Argumenttypen findet man in The expl3 package and LaTeX3 programming.
Datei funktionssymbole.tex
\documentclass[ ngerman, parskip = full, ]{scrartcl}
Das Paket xparse
laden wir, um die neue Syntax zur Befehlsdefinition verwenden zu können. Außerdem lädt dieses Paket expl3
, was uns wiederum die LaTeX3-Funktionen zur Verfügung stellt.
\usepackage{xparse}
Um die LaTeX3-Syntax und die o. g. Konventionen nutzen zu können, aktivieren wir diese mit \ExplSyntaxOn
. Die wichtigste Änderung: Ab hier werden Leerzeichen, Zeilenumbrüche und Leerzeilen ignoriert!
\ExplSyntaxOn
Konstanten
Jetzt definieren wir ein paar Konstanten. Dafür nutzen wir jeweils den \x_new:N
-Befehl gefolgt vom Namen und dann \x_set:Nn
, um den Wert der Konstante zu setzen (x
= Typ).
Eine Länge, die die Breite des Rasters (s. Abb. oben) angibt. (Zu Einheiten siehe FAQ 12.)
\dim_new:N \c_mfs_boxwidth_dim \dim_set:Nn \c_mfs_boxwidth_dim { 0.5em }
Um später die horizontalen Linien auszugeben definieren wir die Token-Liste \c_mfs_line_tl
mithilfe von \rule
als eine 0,3 pt dicke Linie, die 0,7 ex über der Grundlinie liegt, mit der Breite \c_mfs_boxwidth_dim
\tl_new:N \c_mfs_line_tl \tl_set:Nn \c_mfs_line_tl { \rule [ 0.7ex ] { \c_mfs_boxwidth_dim } { 0.3pt } }
Analog dazu definieren wir den Leerschritt mit \hspace
als einen unsichtbaren Abstand derselben Breite.
\tl_new:N \c_mfs_space_tl \tl_set:Nn \c_mfs_space_tl { \hspace { \c_mfs_boxwidth_dim } }
Da Leerzeichen ignoriert werden, setzt man in der neuen Syntax alle Klammern ab, was die Lesbarkeit erhöht. Die Konvention fordert außerdem, dass die Klammer eines mehrzeiligen Argumentes alleine in einer Zeile steht und man nur mit zwei Leerzeichen einrückt, wie bei \c_mfs_space_tl
; ich bevorzuge aber meine gewohnte Schreibweise.
Spezielle Funktionssymbole
In der musiktheoretischen Funktionsanalyse gibt es neben den Funktionen, die durch normale Buchstaben wie T, S, D, tP etc. dargestellt werden, u. a. auch noch spezielle Symbole für die Doppeldominante (Doppeltes D, s. o.), die verkürzte Dominante (durchgestrichenes D) und die Doppelsubdominante (doppeltes S). Für diese Sonderzeichen definieren wir ebenfalls Token-Listen-Konstanten.
Das Symbol für die Doppeldominante erzeugen wir, indem wir ein D mit \raisebox
um 0,2 em anheben und dieses dann mit \makebox
in eine Box verpacken, deren Breite unabhängig vom Inhalt immer 0,2 em beträgt. Ihr Inhalt ist an der linken Kante ([l]
) ausgerichtet. Da die Box schmaler ist, als das erhöhte D, überlappt dieses das folgende normale D und es entsteht das gewünschte Symbol.
Bei einer anderen Schriftart müsste man die Werte anpassen.
\tl_new:N \c_mfs_double_dominant_tl \tl_set:Nn \c_mfs_double_dominant_tl { \makebox [ 0.2em ] [ l ] { \raisebox { 0.2em } { D } } D }
Beim Symbol für die verkürzte Dominante (diesem Akkord fehlt der Grundton) gehen wir ganz ähnlich vor, nur dass wir dieses mal einen kleinen Abstand (\hspace
) gefolgt von einem Schrägstrich in eine Box mit der Breite 0 em verpacken, um diesen über ein D zu legen. In der Box verringern wir mit \small
außerdem die Schriftgröße, um einen etwas kleineren Schrägstrich zu erhalten.
Die Schriftgrößenänderung wirkt natürlich auch auf die Einheit em
und damit auf den \hspace
in der Box.
\tl_new:N \c_mfs_dominant_without_root \tl_set:Nn \c_mfs_dominant_without_root { \makebox [ 0em ] [ l ] { \raisebox { 0.25ex } { \small \hspace { 0.2em } / } } D }
Das Symbol für die Doppelsubdominante bauen wir genau wie das für die Doppeldominante nur mit etwas anderen Werten und S anstelle von D.
\tl_new:N \c_mfs_double_subdominant_tl \tl_set:Nn \c_mfs_double_subdominant_tl { \makebox [ 0.1em ] [ l ] { \raisebox { 0.2em } { S } } S }
Interne Befehle
Jetzt definieren wir zwei interne Hilfsbefehle. Dazu verwenden wir \cs_new_protected:Npn
(cs = control sequence), jeweils gefolgt vom Namen des Befehls (= N
), der Argumentspezifikation (= p
) und schließlich der Definition (= n
). Die Argumentspezifikation erfolgt wie beim klassischen \def
, in dem man für jedes Argument, dass der Befehl später haben soll eine Raute #
gefolgt von einer Ziffer (1
–9
) angibt.
Die Argumentspezifikation kann auch komplexer sein, zum Beispiel im Fall von sog. delimited Arguments, das führt an dieser Stelle aber zu weit.
Unser erster Hilfsbefehl \mfs_makebox:n
dient dazu, das ihm später zu übergebende Argument in eine Box mit zentriertem Inhalt ([c]
) und der Breite \c_mfs_boxwidth_dim
zu verpacken (siehe auch FAQ 10).
\cs_new_protected:Npn \mfs_makebox:n #1 { \makebox [ \c_mfs_boxwidth_dim ] [ c ] { #1 } }
Der nächste Befehl, den wir definieren, hat ebenfalls ein Argument und soll uns dazu dienen, eine Stimmbewegung zu verarbeiten.
\cs_new_protected:Npn \mfs_process_list:n #1 {
Dazu setzen wir zunächst den Wert der temporären und von expl3
für solche Zwecke bereitgestellten Variable \l_tmpa_tl
gleich dem Wert des Argumentes.
\tl_set:Nn \l_tmpa_tl { #1 }
Mithilfe von \tl_replace_all:Nnn
können wir in dieser Liste nun alle -
bzw. .
durch die zuvor definierten Konstanten für vertikale Linien bzw. Leerräume ersetzen.
\tl_replace_all:Nnn \l_tmpa_tl { - } { \c_mfs_line_tl } \tl_replace_all:Nnn \l_tmpa_tl { . } { \c_mfs_space_tl }
Mit \tl_map_function:NN
geben wir die Token-Liste dann aus, wobei wir auf jedes Token die Funktion \mfs_makebox:n
anwenden. Es wird also jedes Token in einer eigenen Box mit der von uns vorgegebenen Breite ausgegeben.
\tl_map_function:NN \l_tmpa_tl \mfs_makebox:n
Wir wissen, dass jede Stimmbewegung später eine Zeile in einer Tabelle sein soll, weshalb wir ihre Ausgabe mit \\
beenden.
\\
Ende der Definition von \mfs_process_list:n
}
Der Benutzerbefehl \fsymb
Nachdem wir nun alles vorbereitet haben, können wir mit \NewDocumentCommand
FAQ 11 den eigentlichen benötigten Befehl \fsymb
definieren.
Wie oben gesagt, soll der Befehl je zwei obligatorische und optionale Argumente haben. Der Standardwert für das erste optionale Argument – die Bassstimme – ist leer und der für das zweite Argumente – den Abstand zwischen Symbol und Stimmen – ist 0 pt.
\NewDocumentCommand { \fsymb } { m O{} O{0pt} m } {
Wie schon in der vorigen Definition setzen wir zunächst \l_tmpa_tl
auf den Wert des ersten Argumentes (Symbol).
\tl_set:Nn \l_tmpa_tl { #1 }
In dieser Token-Liste ersetzen wir die Abkürzungen DD
, /D
und SS
durch die richtigen Symbole. Alles was nicht von dieser Ersetzung betroffen ist, wird ausgegeben, wie der Nutzer es auch eingeben hat.
\tl_replace_all:Nnn \l_tmpa_tl { DD } { \c_mfs_double_dominant_tl } \tl_replace_all:Nnn \l_tmpa_tl { /D } { \c_mfs_dominant_without_root } \tl_replace_all:Nnn \l_tmpa_tl { SS } { \c_mfs_double_subdominant_tl }
Mit \tl_use:N
geben wir die so bearbeitete Variable aus.
\tl_use:N \l_tmpa_tl
Mit \kern
fügen wir nun einen Abstand in der durch das dritte Argument vorgegebenen Breite ein.
\kern#3
Als nächstes setzen wir eine Komma-Listen-Variable auf den Wert des vierten Argumentes. Damit ist jede Stimmbewegung der Oberstimmen, die wir angeben, ein Element in \l_tmpa_clist
.
\clist_set:Nn \l_tmpa_clist { #4 }
Da wir die Tabelle von oben nach unten ausgeben, wir die Stimmen in der Liste aber von unten nach oben angeben wollen, kehren wir die Reihenfolge von \l_tmpa_clist
um.
\clist_reverse:N \l_tmpa_clist
Mit der so vorbereiteten Stimmenliste können wir uns nun an die Ausgabe machen. Dazu beginnen wir eine Gruppe (FAQ 7), in der wir die Schriftgröße auf \tiny
und den Wert für \arraystretch
auf 0,75 (siehe vorletztes Beispiel) setzen.
{ \tiny \renewcommand { \arraystretch } { 0.75 }
Die Tabelle verpacken wir in einer Box, die wir um 0,68 ex nach unten verschieben.
\raisebox { -0.68ex } {
Die Tabelle selbst soll mit der untersten Zeile an der (verschobenen) Grundlinie ausgerichtet werden ([b]
) und eine Spalte (l
) haben (zu Tabellen siehe auch das vorletzte Beispiel).
\begin{tabular} [ b ] { @ { } l @ { } }
Mit \clist_map_function:NN
geben wir die Komma-Liste der Stimmbewegungen aus, wobei wir auf jedes Element die Funktion \mfs_process_list:n
, die wiederum eine einzelne Stimmbewegung verarbeitet und als Tabellenzeile ausgibt, anwenden.
\clist_map_function:NN \l_tmpa_clist \mfs_process_list:n
Zum Abschluss der Oberstimmen fügen wir eine Leerzeile in die Tabelle ein, der ein negativer vertikaler Abstand der Höhe 1,2 ex folgt.
\\ [ -1.2ex ]
Die Bassstimme können wir direkt an \mfs_process_list:n
übergeben, da es immer nur eine geben kann. Gibt der Nutzer gar keine Bassstimme an, wird eine leere Zeile ausgegeben.
\mfs_process_list:n { #2 }
Ende der Tabelle
\end{tabular}
Ende der \raisebox
}
Ende der Gruppe
}
Ende der Definition von \fsymb
}
Jetzt schalten wir die LaTeX3-Syntax wieder ab.
\ExplSyntaxOff
weitere Definitionen
Zum Testen der Ausrichtung der Symbole und Stimmen, definieren wir diverse Befehle, die jeweils eine farbige Linie ausgeben, die so breit ist wie der Text, aber in einer Box mit der Breite 0 pt verpackt ist. Um die Linien zu färben, nutzen wir die Funktionen von xcolor
(siehe Beispiel vom Jahresanfang).
Die folgenden Definitionen dienen uns nur zum Testen der Ausrichtung, für die Funktion von \fsymb
sind sie irrelevant.
\usepackage{xcolor} \newcommand{\showBaseLine}{\makebox[0pt][l]{\color{magenta}\rule{\textwidth}{0.1pt}}} \newcommand{\showXHeightLine}{\makebox[0pt][l]{\color{yellow}\rule[1ex]{\textwidth}{0.1pt}}} \newcommand{\showMidLine}{\makebox[0pt][l]{\color{cyan}\rule[0.785ex]{\textwidth}{0.1pt}}} \newcommand{\showCapHeightLine}{\makebox[0pt][l]{\color{magenta}\rule[1.57ex]{\textwidth}{0.1pt}}} \newcommand{\showAllLines}{\showBaseLine\showXHeightLine\showMidLine\showCapHeightLine}
\begin{document}
Test
Nun gilt es, den Befehl \fsybm
zu testen.
Zunächst testen wir die verschiedenen Kombinationen, in denen Stimmen angeben werden können, wobei wir das von uns definierte \showAllLines
benutzen, um die Ausrichtung beurteilen zu können.
\showAllLines Xx \qquad \fsymb{D}{8-7} \enskip \fsymb{D}[8-7]{} \enskip \fsymb{D}[8-7]{4-3} \enskip \fsymb{D}[8--7]{4-3,6-5}
Dann probieren wir die Ausrichtung mit längeren Linien aus.
\fsymb{T}{..7-8,3---4} \enskip \fsymb{D}{4---3, 6-5, ..7-8} \enskip \fsymb{D}{4----3, 6-5---, ..7-8-,{11}-----} \enskip \fsymb{s}{5,6}
Auch die korrekte Verarbeitung der Spezialsymbole sollte klappen.
\fsymb{DD}{8-7} \enskip \fsymb{DD}[8----7]{4---3-, 6---5-,..{11}---} \enskip \fsymb{/D}{8-7} \enskip \fsymb{SS}{5,6}
Über das zweite optionale Argument können wir den Abstand zwischen Symbol und Stimmen fine tunen.
\fsymb{D}[]{8-7} \enskip \fsymb{D}[4-3]{} \enskip \fsymb{D}[4-3]{8-7} vs. \fsymb{D}[][-1pt]{8-7} \enskip \fsymb{D}[4-3][-2.3pt]{} \enskip \fsymb{D}[4-3][-1pt]{8-7}
Als letztes testen wir noch das Verhalten des Befehls in Überschriften (und damit auch im Inhaltsverzeichnis) und im Fließtext, wobei dort höchsten Konstrukte mit einer Oberstimme stehen sollten, um den Zeilenabstand einhalten zu können.
\clearpage \tableofcontents \section{Der Quartsextvorhalt \fsymb{D}{4-3,6-5}} Das ist ein Test, wie die Funktionssymbole sich im Fließtext verhalten. Das ist ein Test, wie die Funktionssymbole sich im Fließtext verhalten. \fsymb{D}[1-----]{4----3, 6-5---, ..7-8-,{11}-----} Das ist ein Test, wie die Funktionssymbole sich im Fließtext verhalten. Das ist ein Test, wie die Funktionssymbole sich im Fließtext verhalten.
\end{document}
Weiterlesen …
Neben dem oben bereits genannten The expl3 package and LaTeX3 programming sie die vollständige Dokumentation zu allen LaTeX3-Funktionen The LaTeX3 Interfaces empfohlen.
Wir laden die Dokumentklasse (FAQ 2) mit zwei Optionen, nämlich der Sprachoption sowie zudem mit der Option
parskip
, um einen Absatzabstand anstelle des Einzuges einzustellen. Das macht die Ausgabe der Testsymbole etwas übersichtlicher.