musiktheorietheorie/musixtex/tex/musixlyr.tex

1163 lines
35 KiB
TeX
Raw Normal View History

2022-03-27 12:09:14 +02:00
%%
%% musixlyr.tex: Convenient lyrics handling for MusiXTeX T.52 or later
%%
%% Copyright (C) 1996-2003 Rainer Dunker
%%
%% This program is free software; you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation; either version 2 of the License, or
%% any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program; if not, write to the Free Software
%% Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
%%
%% Author:
%% Rainer Dunker
%% Wachtelweg 31
%% 85591 Vaterstetten
%% Germany
%%
%% E-mail: rainer.dunker@web.de
%%
\ifx\undefined\lyr\else\endinput\fi
\immediate\write16{MusiXLYRics 2.1c\space<June 10, 2003>}
\def\musixlyrversion{2.12}
\makeatletter
%%%%%%%%%%
%
% register allocation
%
%%%%%%%%%%
\newtoks\alle@texte
% internal parameters for setting text
\let\evtl@klein\empty
\let\evtl@komma\empty
\let\evtl@punktweg\empty
\let\evtl@offset\empty
\let\evtl@next@lyr\empty
\newif\if@strich
\newif\iflyr@processing
\newif\if@pmx@nextvoice
\newif\if@lyrmode
\newif\if@hyphen
\newif\ifaux@active
\newif\if@single@token
\newdimen\lyr@shift
\newbox\lyr@box
\newbox\lyr@hyphen@box
\newbox\lyr@linkbox
\newbox\lyr@linkdepthbox
\def\ma@sw{lyr@m} % "main/aux switch"
% helpers for shuffling data around
\newtoks\@rohtext
\newtoks\@textvar
\let\text@name\empty
% public parameters
\newif\ifleftlyr
\newif\ifforcelyrhyphens
\newif\ifshowlyrshift
\newdimen\minlyrrulelength \minlyrrulelength=2mm
\newdimen\minmulthyphens \minmulthyphens=1.5cm
\newdimen\minlyrspace \minlyrspace=3pt
\def\lyrhyphenchar{-}
\newbox\lyrstrutbox
\def\lyrlinestartpos{-10cm }
\def\oldlyrlinestart{\def\lyrlinestartpos{0pt }} % to restore 2.1 behaviour
\def\lyrlog#1{\immediate\write16{#1}} % just for debugging convenience
% for testing token lists for emptyness with \ifx
\def\emp@tst{\empty@test@errmsg}
\def\empty@test@errmsg{%
\errmessage{This shouldn't happen; you have found a musixlyr bug}}
%%%%%%%%%%
%
% lyrics definition
%
%%%%%%%%%%
% set up text completely
\def\setlyrics#1#2{% iterative variant
% Parameter:
% #1 - lyrics line name
% #2 - text
\@rohtext={#2 -}%
\@textvar={}%
%\lyrlog{setlyrics{#1}, raw text: \the\@rohtext}%
\loop
\expandafter\ifx\expandafter\emp@tst\the\@rohtext\emp@tst % no more text
\let\@weiter n\else \let\@weiter j%
\fi
\ifx\@weiter j%
\expandafter\split@lyr@by@hyphens\the\@rohtext\@end
\repeat
\expandafter\xdef\csname dertext@#1\endcsname{\the\@textvar}%
\expandafter\xdef\csname nochtext@#1\endcsname{\the\@textvar}%
%\lyrlog{setlyrics{#1}: \the\@textvar}%
\initialize@verse{#1}}
\def\appendlyrics#1#2{% iterative variant
% Parameter:
% #1 - lyrics line name
% #2 - text
% test whether lyrics line name already defined
\expandafter\ifx\csname stp@#1\endcsname\relax
% if not: set it up newly
\setlyrics{#1}{#2}%
\else
% if yes: preprocess new material, then append it to existing stuff
\@rohtext={#2 -}%
\@textvar={}%
%\lyrlog{appendlyrics{#1}, raw text: \the\@rohtext}%
\loop
\expandafter\ifx\expandafter\emp@tst\the\@rohtext\emp@tst % no more text
\let\@weiter n\else \let\@weiter j%
\fi
\ifx\@weiter j%
\expandafter\split@lyr@by@hyphens\the\@rohtext\@end
\repeat
%
% properly append new material to \dertext@#1
\toks@=\expandafter\expandafter\expandafter{\csname dertext@#1\endcsname}%
\expandafter\test@final@hyphen@i\the\toks@\@end
\expandafter\xdef\csname dertext@#1\endcsname{\the\toks@\the\@textvar}%
%
% properly append new material to \nochtext@#1
\toks@=\expandafter\expandafter\expandafter{\csname nochtext@#1\endcsname}%
\expandafter\ifx\expandafter\emp@tst\the\toks@\emp@tst
% pending text empty - don't call \test@final@hyphen
\else
\expandafter\test@final@hyphen@i\the\toks@\@end
\fi
\expandafter\xdef\csname nochtext@#1\endcsname{\the\toks@\the\@textvar}%
%\lyrlog{appendlyrics{#1}: \the\@textvar}%
\fi}
% separate hyphens from syllables
\def\split@lyr@by@hyphens#1-#2\@end{%
% #1: text before first hyphen
% #2: text after first hyphen; may be empty
\ifx\emp@tst#2\emp@tst % -> no hyphen present
\@textvar=\expandafter{\the\@textvar#1}%
\else % -> hyphen present
\@textvar=\expandafter{\the\@textvar#1 @}%
\fi
\@rohtext={#2}%
%\lyrlog{splitlyrics 1: \the\@textvar}%
%\lyrlog{splitlyrics 2: \the\@rohtext}%
}
% helper macros for \appendlyrics, handling the case that pre-existing
% lyrics material ends with an open hyphen
\def\test@final@hyphen@i#1 \@end{%
% truncate trailing space, then proceed with detecting a trailing @
\test@final@hyphen@ii#1@@\@end}
\def\test@final@hyphen@ii#1@@#2\@end{%
\ifx\emp@tst#2\emp@tst
% no trailing, open hyphen
%\lyrlog{No trailing hyphen: #1}%
\else
% reset \toks@ so that trailing @ is not longer followed by a space
\toks@={#1@}%
%\lyrlog{Trailing hyphen: #1}%
\fi}
% copy whole text under different name
\def\copylyrics#1#2{%
% #1 - existing text name
% #2 - new text name
% text still undefined?
\expandafter\ifx\csname dertext@#1\endcsname\relax
\errmessage{Trying to copy undefined verse "#1" to "#2"}%
\setlyrics{#2}{UNDEFINED}%
\else
\expandafter\let\expandafter\text@copy\csname dertext@#1\endcsname
\global\expandafter\let\csname dertext@#2\endcsname\text@copy
\global\expandafter\let\csname nochtext@#2\endcsname\text@copy
\initialize@verse{#2}%
\fi}
% at 1st definition of a text name
\def\initialize@verse#1{%
% test whether lyrics line name already defined
\expandafter\ifx\csname stp@#1\endcsname\relax
\expandafter\xdef\csname stp@#1\endcsname{\lyrlinestartpos}% tracks horizontal progress
\global\expandafter\let\csname cont@#1\endcsname\relax% context stuff
\expandafter\gdef\csname zwr@#1\endcsname{0}% flag for hyphen/rule status
% for layout definitions
\global\expandafter\let\csname llay@#1\endcsname\relax
% switch on auto-text
{\def\text@name{#1}\lyricson}%
% insert in list of all defined text names
\global\alle@texte=\expandafter{\the\alle@texte#1,}%
\fi}
% just for more elegance ...
\def\if@multistaff{\ifnum\st@ffs>1 }
\def\set@texte#1#2{%
% assign assigned lyrics lines to \@texte
% or \empty in case they are empty
% #1: instrument number
% #2: staff number of instrument
\expandafter\let\expandafter\@texte\csname\ma@sw#1-#2\endcsname
\ifx\@texte\relax\let\@texte\empty\fi}
\def\set@texte@current#1{%
% apply \set@texte to current context
% using PMX, automatically switch to aux lyrics where required
\switch@pmx@aux{%
% get the verses
\ifnum\st@ffs>1 % multi-staff instrument
\set@texte{\the\noinstrum@nt}{\the\noport@@}%
\else
\set@texte{\the\noinstrum@nt}{1}%
\fi
% perform given action
#1}}
\def\loop@texte#1\@repeat{%
% assumption: \@texte is already set properly, may be empty
% #1: action to be executed
\ifx\@texte\empty\else
\expandafter\loop@texte@step\@texte\@end{#1}%
\fi}
\def\loop@texte@step#1,#2\@end#3{%
% assumption: text list is not empty, #1 contains list head
% #1: text list head
% #2: text list tail
% #3: action to be executed
%
% perform action on first text
\def\text@name{#1}%
#3\relax
%
% prepare iteration
\ifx\emp@tst#2\emp@tst % list tail empty
\let\@iterate \empty % stop looping
\let\text@name\empty % reset working environment
\else
\def\@iterate{\loop@texte@step#2\@end{#3}}%
\fi
\@iterate}
% assign text name to staff
\def\assignlyrics#1{% for single-staff instruments
% #1: instrument number
\assignlyricsmulti{#1}1}
\def\assignlyricshere#1{% assign lyrics to current instrument/staff context
% #1: comma-separated list of text names
\switch@pmx@aux{%
\ifnum\st@ffs>1 % multi-staff instrument
\assignlyricsmulti{\the\noinstrum@nt}{\the\noport@@}{#1}%
\else
\assignlyrics{\the\noinstrum@nt}{#1}%
\fi}}
\def\assignlyricsmulti#1#2#3{%
% #1: instrument number
% #2: staff number of instrument
% #3: comma-separated list of text names
% gather farthest right current position
% of currently assigned lyrics lines
\y@v=\lyrlinestartpos
\set@texte{#1}{#2}%
\loop@texte
% starting position greater than retrieved so far?
\ifdim\csname stp@\text@name\endcsname > \y@v
\y@v=\csname stp@\text@name\endcsname % advance maximum
\fi
\@repeat
%
% new text names list non-empty -> append comma
\ifx\emp@tst#3\emp@tst
\expandafter\global\expandafter\let\csname\ma@sw#1-#2\endcsname\relax
\else
\expandafter\gdef\csname\ma@sw#1-#2\endcsname{#3,}%
\fi
% set parameters according to newly assigned lyrics lines
\set@texte{#1}{#2}%
\loop@texte
% check for existence
\expandafter\ifx\csname stp@\text@name\endcsname\relax
\errmessage{Trying to assign undefined verse "\text@name"}%
\expandafter\setlyrics\expandafter{\text@name}{EMPTY}%
\fi
\reset@params
\@repeat}
\def\reset@params{%
\expandafter\xdef\csname stp@\text@name\endcsname{\the\y@v}%
\expandafter\gdef\csname zwr@\text@name\endcsname{0}}
%
% reset horizontal positioning parameters of all lyrics lines
%
\def\resetlyrics{%
\edef\@texte{\the\alle@texte}%
\y@v=\lyrlinestartpos
\loop@texte \reset@params \@repeat}
%%%%%%%%%%
%
% process lyrics verse-wise
%
%%%%%%%%%%
% same action for all assigned verses
\def\forall@verses#1{%
% #1: action to be executed
\ifx\text@name\empty
% perform action for all assigned verses
\set@texte@current{%
\ifx\@texte\empty\else
\vplace@lyrics{\loop@texte \hbox{\lyr@strut #1}\@repeat}%
\fi}%
\else
% text name already selected -> perform action for this one only
#1%
\fi}
% specify separate actions per verse
\def\verses#1{%
% #1: comma-separated list of actions (from top to bottom)
\set@texte@current{%
\def\@param{#1}% running variable for per-text actions
\vplace@lyrics{%
\loop@texte \expandafter\one@verse\@param,\@end \@repeat}}}
\def\one@verse#1,#2\@end{%
% #1: action list head = action for current verse
% #2: action list tail = actions for remaining verses
\def\@param{#2}%
% perfrom action
\hbox{\lyr@strut #1}}
%%%%%%%%%%
%
% line spacing for multiple verses
%
%%%%%%%%%%
\def\lyr@strut{\copy\lyrstrutbox}
\def\setlyrstrut{% set up strut according to currently active font
\setbox0=\hbox{()}%
\setbox\lyrstrutbox=\hbox{\vrule height 1.1\ht0 depth 1.1\dp0 width\z@}}
\setlyrstrut % initialize
%%%%%%%%%%
%
% retrieve text by syllable
%
%%%%%%%%%%
\def\next@lyr{%
\expandafter\let\expandafter\@nochtext\csname nochtext@\text@name\endcsname
%\show\@nochtext
\ifx\@nochtext\empty
% no more text
\@hyphenfalse\@lyric{???}%
\else
\@textvar=\expandafter{\@nochtext}%
\expandafter\next@syllable\the\@textvar\relax\relax
\fi}
\def\next@syllable#1 #2#3\relax{%
% #1 - first syllable
% #2 - either hyphenation symbol @
% or \relax (if text ends after #2)
% or 1st char/group of rest text
% #3 - rest text, may be empty
\parse@melisma{#2}{#3}#1_\@end}
\def\test@single@token#1#2\@end{%
% #2 is empty if argument consists of a single token
\ifx\emp@tst#2\emp@tst
\@single@tokentrue
\else
\@single@tokenfalse
\fi}
\def\parse@melisma#1#2#3_#4\@end{% parse trailing underscores
% #1: either hyphenation indicator @
% or \relax (if text ends after #1),
% or 1st char/group of rest text
% #2: rest text, may be empty
% #3: current syllable; may be empty if melisma pending
% #4: trailing underscores, if any, or
% melisma notes number followed by single underscore, or
% empty if no melisma
%
% evaluate hyphenation sign
\ifx @#1%
\@hyphentrue
\@textvar={#2}% may be empty
\else
\@hyphenfalse
% decide rest text (#1 was no hyphen sign)
\ifx\relax#1% current syllable is final syllable
\@textvar={}%
\else
\test@single@token#1\@end % #1 may have been grouped
\if@single@token
\@textvar={#1#2}%
\else
\@textvar={{#1}#2}%
\fi
\fi
\fi
%
% melisma pending?
\ifx\emp@tst#4\emp@tst % no melisma
\let\melisma@spec\empty
\@lyric{\evtl@klein{\evtl@punktweg{#3}}\evtl@komma}%
\else % melisma
\ifx\emp@tst#3\emp@tst % syllable empty, d.i. in mid-melisma
\parse@melisma@tail#4\@end
\ifx\melisma@spec\empty % final melisma note
\lyrruleend
\fi
\else % syllable non-empty, d.i. at melisma start
\leftlyrtrue\@strichtrue
\parse@melisma@start#4\@end
\@lyric{\evtl@klein{\evtl@punktweg{#3}}\evtl@komma}%
\fi % at melisma start
\fi % in melisma
%
% set remaining text
\expandafter\xdef
\csname nochtext@\text@name\endcsname{\melisma@spec\the\@textvar}}
\def\parse@melisma@start#1_\@end{%
% Cut trailing underscore and attach it at argument head.
% For underscore sequences, the effect is void.
% For numbers, it converts "num_" to "_num".
% Moreover, append a single space.
\def\melisma@spec{_#1 }}
\def\parse@melisma@tail#1_\@end{%
% #1: either trailing underscores minus one
% or melisma notes number
% or empty
\ifx\emp@tst#1\emp@tst % no more underscores
\let\melisma@spec\empty
\else
\parse@melisma@tail@ii#1\@end
\fi}
\def\parse@melisma@tail@ii#1#2\@end{% helper for deciding melisma spec type
% #1#2: either trailing underscores minus one
% or melisma notes number
\if#1_% % underscore sequence given
\def\melisma@spec{#1#2 }%
\else % number given
\ifnum#1#2>1 % more melisma notes pending
\count@=#1#2
\advance\count@\m@ne
\edef\melisma@spec{_\the\count@\space}%
\else % no more melisma notes
\let\melisma@spec\empty
\fi
\fi}
\def\@lyric#1{%
% #1: Text
\evtl@offset
% Alles Folgende ist Argument fuer obiges \evtl@offset:
{\csname llay@\text@name\endcsname% Layoutkontext abrufen
\lyr@processingtrue
\setbox\lyr@box=\hbox{#1}%
\setbox\lyr@hyphen@box=\hbox{\lyrhyphenchar}%
% Zwischenraum zu voriger Silbe ermitteln:
\get@lyrspace
\ifleftlyr\else
% Silbe zentriert -> Zwischenraum entspr. kleiner:
\advance\y@v -0.5\wd\lyr@box
\advance\y@v 0.5\qn@width % halbe Notenkopfbreite dazu
\fi
% Bindestrich von voriger Silbe anhaengig?
\expandafter\ifnum\csname zwr@\text@name\endcsname=2
% Minimalzwischenraum entsprechend aendern:
\ifforcelyrhyphens
% Min. Zw.-R. mindestens so breit wie Bindestrich:
\ifdim\minlyrspace < \wd\lyr@hyphen@box
\minlyrspace=\wd\lyr@hyphen@box
\fi
\else
\minlyrspace=0pt % % kein Zwischenraum noetig
\fi
\fi
\ifdim\y@v < \minlyrspace % Zwischenraum zu klein?
\lyr@shift=\minlyrspace % Silbe um Differenz nach rechts verschieben
\advance\lyr@shift -\y@v
\y@v=\minlyrspace % Zwischenraumbreite = geg. Minimum
\else
\expandafter\ifnum\csname zwr@\text@name\endcsname=2 % Bindestrich anhaengig?
\ifforcelyrhyphens\else % Bindestrich nicht erzwungen?
\ifdim\y@v < \wd\lyr@hyphen@box % Zwischenraum zu schmal?
\advance\lyr@shift -\y@v % => Zw.raum ganz wegnehmen
% Dank an Sebastian Clauss fuer diese Verbesserung
\fi
\fi
\fi
\fi
\rlap{%
\hskip\lyr@shift
{\ifleftlyr
\aftergroup\rlap % linksbuendig
\else
\aftergroup\qlrlap % zentriert
\fi}%
% Alles Folgende ist Argument fuer obiges \qlrlap bzw. \rlap:
{% Ist von voriger Silbe noch ein Bindestrich anhaengig?
\expandafter\ifnum\csname zwr@\text@name\endcsname=2
% limit hyphens at line beginning to zero position
\ifdim\csname stp@\text@name\endcsname < \z@
\advance\y@v \csname stp@\text@name\endcsname \fi
% Bindestrich nur setzen, wenn Platz genug vorhanden:
\ifdim\y@v < \wd\lyr@hyphen@box\else
\print@hyphen
\fi
\fi
\ifshowlyrshift
% Mit Rechteck Wortverschiebung zeigen:
\llap{\vrule width \lyr@shift height \ht\strutbox}%
\fi
\unhcopy\lyr@box % Wort setzen
% Startposition des nachfolgenden Zwischenraums festhalten:
\getcurpos
\advance\y@v by \lyr@shift
\ifleftlyr
\advance\y@v \wd\lyr@box
\else
\advance\y@v 0.5\wd\lyr@box
\advance\y@v 0.5\qn@width % halbe Notenkopfbreite dazu
\fi
\expandafter\xdef\csname stp@\text@name\endcsname{\the\y@v}% Startposition setzen
\if@hyphen % Bindestrich gefordert?
\expandafter\gdef\csname zwr@\text@name\endcsname{2}%
\else
\if@strich % Verlaengerungs-Strich gefordert?
\expandafter\gdef\csname zwr@\text@name\endcsname{1}%
\else % nichts gefordert
\expandafter\gdef\csname zwr@\text@name\endcsname{0}%
\fi
\fi}}}}
\def\print@hyphen{%
\llap{\hbox to \y@v{%
% Zwischenraum mit "Strich-Kette" ausfuellen:
\loop
\hfil\lyrhyphenchar\hss% rechter Raum darf am Systemende negativ werden
\advance\y@v by -\minmulthyphens
\ifdim\y@v > 0pt%
\repeat}}}
\def\get@lyrspace{%
\getcurpos
\advance\y@v by -\csname stp@\text@name\endcsname
\relax}
% set lyrpos to zero if less than that
\def\limit@lyrpos{%
\ifdim\csname stp@\text@name\endcsname < \z@
\expandafter\xdef\csname stp@\text@name\endcsname{\the\z@}\fi}
% Verlaengerungs-Striche abschliessen:
\def\lyrruleend{\forall@verses\lyrrule@end}
\def\lyrrule@end{%
\expandafter\ifcase\csname zwr@\text@name\endcsname
% 0 -> kommt nicht vor
\or
% 1 -> Strich anhaengig:
\roff{% Zum rechten Notenkopf-Rand
\limit@lyrpos\get@lyrspace \print@lyr@rule
% Zwischenraum-Startposition festhalten, wenn Wort nicht nach
% rechts darueber hinausragt:
\getcurpos
\ifdim\csname stp@\text@name\endcsname < \y@v
\expandafter\xdef\csname stp@\text@name\endcsname{\the\y@v}%
\fi}%
% Einstellung zuruecksetzen:
\expandafter\gdef\csname zwr@\text@name\endcsname{0}%
\or
% 2 -> Bindestrich anhaengig -> nichts tun
\or
% 3 -> fortgesetzter Bindestrich anhaengig
% -> Einstellung fuer Bindestrich-Einfuegen an n<>chster Note setzen
\expandafter\gdef\csname zwr@\text@name\endcsname{2}%
\fi}
\def\print@lyr@rule{%
% Kleiner Zwischenraum zur vorherigen Silbe:
\advance\y@v -2pt
\ifdim \y@v > \minlyrrulelength
\llap{\vrule\@width\y@v\@height\lthick\@depth0pt}%
\fi}
%%%%%%%%%%
%
% Textstellen per Label anspringen
%
%%%%%%%%%%
\def\llabel#1{}% Damit Kennzeichnung bei der Ausgabe ohne Effekt bleibt
\def\golyr#1{\forall@verses{\@golyr{#1}}}
\def\@golyr#1{{%
\expandafter\let\expandafter\@nochtext\csname dertext@\text@name\endcsname
\def\query@label{#1}%
\loop
\expandafter\find@llabel\@nochtext\ende
\ifx\query@label\cur@label\let\@weiter n\else \let\@weiter j\fi
\if\@weiter j\repeat
\expandafter\global\expandafter\let
\csname nochtext@\text@name\endcsname\@nochtext}}
\def\find@llabel#1\llabel#2#3\ende{%
% #1 - Text vor erstem \llabel
% #2 - naechstfolgender \llabel-Name
% #3 - Resttext
\def\cur@label{#2}%
\def\@nochtext{#3}}
%%%%%%%%%%
%
% Offene Silbentrennungen und -verlaengerungen am Systemende abschliessen
%
%%%%%%%%%%
\let\@orig@z@suspend\z@suspend
\def\z@suspend{%
\znotes\sysend@lyrics\empty\en
\znotes\sysend@lyrics\auxlyr\en
\@orig@z@suspend}
\def\sysend@verse{%
% Flag auswerten:
\ifnum\csname zwr@\text@name\endcsname = 1 % Verlaengerungs-Strich anhaengig
% Platz zum rechten Systemrand, damit Strich nicht in Taktstrich ragt
\loffset{0.3}{\limit@lyrpos\get@lyrspace\print@lyr@rule}%
\else\ifnum\csname zwr@\text@name\endcsname > 1 % Bindestrich anhaengig:
\csname llay@\text@name\endcsname% Layoutkontext abrufen
\limit@lyrpos\get@lyrspace\print@hyphen
% als forgesetzten Bindestrich fortfuehren
\expandafter\gdef\csname zwr@\text@name\endcsname{3}%
\fi\fi
% Startposition fuer naechstes System zuruecksetzen:
\expandafter\xdef\csname stp@\text@name\endcsname{\lyrlinestartpos}}
\def\sysend@lyrics#1{%
% #1: \auxlyr or \empty
#1{\let\switch@pmx@aux\empty \forall@verses\sysend@verse}%
\if@multistaff
% loop over staves of instrument
\ifnum \noport@@ < \st@ffs
\def\@next{\nextstaff\sysend@lyrics#1}%
\else % staves of instrument finished; continue with next instrument
\sysend@lyrics@instrum@loop#1%
\fi
\else % single-staff instrument
\sysend@lyrics@instrum@loop#1%
\fi
% iterate
\@next}
\def\sysend@lyrics@instrum@loop#1{%
% prepare instruments loop
\ifnum \noinstrum@nt < \nbinstruments
\def\@next{\nextinstrument\sysend@lyrics#1}%
\else % \noinstrum@nt >= \nbinstruments
\let\@next\empty
\fi}
%%%%%%%%%%
%
% Zeilenspezifisches Layout festlegen
%
%%%%%%%%%%
\def\lyrlayout#1{%
\forall@verses{%
\expandafter\gdef\csname llay@\text@name\endcsname{#1}%
% Wenn gerade Text verarbeitet wird, Kontext sofort abrufen:
\iflyr@processing #1\fi}}
%
% Kontextbehandlung fuer Textnamen:
%
% Aktion zu Kontext hinzufuegen:
\def\add@context#1{%
\toks@=\expandafter\expandafter\expandafter
{\csname cont@\text@name\endcsname #1}%
\expandafter\xdef\csname cont@\text@name\endcsname{\the\toks@}}
% Kontext abrufen:
\def\@context{%
\expandafter\let\expandafter\der@kontext\csname cont@\text@name\endcsname
\clear@context
\der@kontext}
% Kontext loeschen:
\def\clear@context{%
\global\expandafter\let\csname cont@\text@name\endcsname\empty}
%%%%%%%%%%
%
% Zusaetzliche Textzeilen oberhalb der Notenzeile (auxiliary lyrics)
%
%%%%%%%%%%
\let\enableauxlyrics\empty % just for backward compatibility
% Befehle auf auxlyrics beziehen:
\def\auxlyr#1{{%
\def\ma@sw{lyr@a}%
\aux@activetrue
#1}}
%%%%%%%%%%
%
% vertical lyrics positioning
%
%%%%%%%%%%
\def\lyrraise#1{%
% #1: instrument number
\lyrraisemulti{#1}1}
\def\lyrraisehere#1{%
% #1: position/offset
\switch@pmx@aux{%
\ifnum\st@ffs>1 % multi-staff instrument
\lyrraisemulti{\the\noinstrum@nt}{\the\noport@@}{#1}%
\else
\lyrraise{\the\noinstrum@nt}{#1}%
\fi}}
\def\lyrraisemulti#1#2#3{%
% #1: instrument number
% #2: staff of instrument
% #3: position/offset
\toks@=\expandafter{\csname l@raise#1-#2\endcsname}%
\expandafter\ifx\the\toks@\relax
% raise parameter still unset
\expandafter\lyrraise@init\the\toks@
\fi
% now set raise parameter
\expandafter\expandafter\expandafter
\lyr@raise@multii\the\toks@\@end{#1}{#2}{#3}%
%\lyrlog{raise #1-#2: \expandafter\empty\the\toks@}%
}
\def\lyr@raise@multii#1@#2\@end#3#4#5{%
% #1: current main position/offset
% #2: current aux position/offset
% #3: instrument number
% #4: staff of instrument
% #5: new position/offset
\expandafter\xdef\csname l@raise#3-#4\endcsname{%
\ifaux@active #1@#5\else #5@#2\fi}}
% be backward compatible
\let\setsongraise@orig\setsongraise
\def\setsongraise#1#2{\setsongraise@orig{#1}{#2}\lyrraise{#1}{b#2}}
\def\auxsetsongraise#1#2{\auxlyr{\lyrraise{#1}{b#2}}}
\def\lyrraise@init#1{%
% #1: control sequence to be set to default value
\gdef#1{b0pt@a0pt}}
% vertically place lyrics columns
% replaces MusiXTeX's \C@tx
\def\vplace@lyrics#1{%
% find out applicable positioning settings
\edef\placelyr@staff{\ifnum\st@ffs>1 \the\noport@@ \else 1\fi}%
\toks@=\expandafter
{\csname l@raise\the\noinstrum@nt-\placelyr@staff\endcsname}%
% eventually initialize lyrraise setting first
\expandafter\ifx\the\toks@\relax % lyrraise still unset
\expandafter\lyrraise@init\the\toks@
\fi
\expandafter\expandafter\expandafter\vplace@lyricsii\the\toks@\@end{#1}}
\def\vplace@lyricsii#1#2@#3#4\@end#5{%
% #1: main lyrics positioning switch (a/b)
% #2: main lyrics raise value
% #3: aux lyrics positioning switch (a/b)
% #4: aux lyrics raise value
% #5: lyrics material to be issued
%
% decice main/aux context
%
\ifaux@active \let\lyr@ab#3\toks@={#4}%
\else \let\lyr@ab#1\toks@={#2}%
\fi
%
% decide placement situation: above/below/in-mid of system or instrument
%
\if\lyr@ab a% above staff
\ifnum\placelyr@staff<\st@ffs % non-highest staff of multiple staves
\vplaceLyricsAboveMultistaff{#5}%
\else % single or highest staff of instrument
\ifnum\noinstrum@nt<\nbinstruments % lyrics go above instrument
\vplaceLyricsAboveInstrument{#5}%
\else % lyrics go into top margin
\vplaceLyricsTopMargin{#5}%
\fi
\fi
\else % below staff
\ifnum\placelyr@staff>1 % non-lowest staff of multiple staves
\vplaceLyricsBelowMultistaff{#5}%
\else % single or lowest staff of instrument
\ifnum\noinstrum@nt>1 % lyrics go below instrument
\vplaceLyricsBelowInstrument{#5}%
\else % lyrics go into bottom margin
\vplaceLyricsBottomMargin{#5}%
\fi
\fi
\fi}
%
% user-supersedable placement calculations
%
\def\vplaceLyricsBelowMultistaff#1{%
% #1: lyrics material
% reduced \C@Tx algorithm
\y@iv=\the\toks@\relax
\C@Inter % compute \stem@skip (?)
\advance\y@iv -0.5\stem@skip
\raise\y@iv\vbox\@to\z@{\vss\offinterlineskip#1\vss}}
\def\vplaceLyricsAboveMultistaff#1{%
% #1: lyrics material
% reduced \C@Tx algorithm
\y@iv=\the\toks@\relax
\C@Inter % compute \stem@skip (?)
\advance\y@iv -0.5\stem@skip
% add height difference to base line of upper staff
\advance\y@iv \interportee
\raise\y@iv\vbox\@to\z@{\vss\offinterlineskip#1\vss}}
\def\vplaceLyricsBottomMargin#1{%
% #1: lyrics material
% based on \C@tx
\y@iv=\the\toks@\relax
\advance\y@iv -\staffbotmarg
\raise\y@iv\vbox\@to\z@{\vss\offinterlineskip#1\vss}}
\def\vplaceLyricsTopMargin#1{%
% #1: lyrics material
% based on \C@tx, "mirrored" bottom margin situation
\begingroup % seems to be necessary to make \Comp@High local;
% problem occurred with helper lines for low/high notes
\y@iv=\the\toks@\relax
\advance\y@iv \stafftopmarg
\Comp@High \advance\y@iv\y@v % \y@v = total height of instrument
\advance\y@iv \altitude % for multi-staff:
\advance\y@iv-\altportee % reduce by base height of current staff
\raise\y@iv\vbox\@to\z@{\vss\offinterlineskip#1\vss}%
\endgroup}
\def\vplaceLyricsBelowInstrument#1{%
% #1: lyrics material
% assumption: \noinstrum@nt > 1
% based on \C@tx
\y@iv=\the\toks@\relax
\multiply\y@iv 2 % balance division by 2 below
\advance\noinstrum@nt\m@ne
\advance\y@iv -\csname interinstrument\romannumeral\noinstrum@nt\endcsname
\C@Inter % compute \stem@skip (?)
\advance\y@iv -\stem@skip
\divide\y@iv\tw@
\advance\noinstrum@nt\@ne
\raise\y@iv\vbox\@to\z@{\vss\offinterlineskip#1\vss}}
\def\vplaceLyricsAboveInstrument#1{%
% #1: lyrics material
% assumption: \noinstrum@nt < \nbinstruments
% analogous to \C@tx, but refers to upper instrument instead
\y@iv=\the\toks@\relax
\multiply\y@iv 2 % balance division by 2 below
\advance\y@iv -\csname interinstrument\romannumeral\noinstrum@nt\endcsname
\C@Inter % compute \stem@skip (?)
\advance\y@iv -\stem@skip
\divide\y@iv\tw@
% add height difference to base line of upper instrument
\begingroup
\advance\y@iv-\altportee \advance\noinstrum@nt\@ne \s@l@ctinstr
\advance\y@iv \altitude
\raise\y@iv\vbox\@to\z@{\vss\offinterlineskip#1\vss}%
\endgroup}
%\def\C@tx{%
% \ifnum\noinstrum@nt=\@ne
% \y@iv\staffbotmarg
% \else
% \advance\noinstrum@nt\m@ne
% \y@iv\csname interinstrument\romannumeral\noinstrum@nt\endcsname
% \C@Inter % compute \stem@skip
% \advance\y@iv\stem@skip
% \divide\y@iv\tw@
% \advance\noinstrum@nt\@ne
% \fi
% \advance\y@iv-\csname T@R\romannumeral\noinstrum@nt\endcsname
% \lower\y@iv\uplap}
%\def\uplap#1{\vbox\@to\z@{\vss#1}}
%%%%%%%%%%
%
% Textsatz-Automatik
%
%%%%%%%%%%
% Text automatisch unter alle "spacing"-Noten eines Systems:
\let\orig@writ@note\writ@note
\def\writ@note{%
\ifnum\n@i<\@c % Bedingung fuer's Notenschreiben in \writ@note (warum?)
% \ifx\st@m\beamst@m
% \uptext{\csname s@bl\balken@nr\endcsname}%
% \else
\decide@lyrmode
\main@aux@or@not{\forall@verses{\@context\evtl@next@lyr}}%
% \fi
\fi
\orig@writ@note}
\def\lyrmode@no {\def\main@aux@or@not##1{}}
\def\lyrmode@main{\def\main@aux@or@not##1{##1}}
\def\lyrmode@aux {\let\main@aux@or@not\auxlyr}
\let\pmx@auxmode\lyrmode@aux
% Feststellen, ob Haupt- oder Nebentext oder gar nichts gesetzt werden soll:
\ifx\nextvoice\undefined % ohne PMX
\def\decide@lyrmode{%
\call@lyrmode
\if@lyrmode \decide@stem@direction % Notenhals-Automatik
\else % manuell, nur "spacing"-Noten
\ifadvance \lyrmode@main
\else \lyrmode@no
\fi
\fi}
\def\decide@stem@direction{%
\ifx\st@m\upst@m \lyrmode@aux \else
\ifx\st@m\up@flag \lyrmode@aux \else
\ifx\st@m\downst@m \lyrmode@main\else
\ifx\st@m\down@flag\lyrmode@main\else
\ifx\st@m\setst@m \lyrmode@no \else % kein Hals
\ifx\st@m\resetst@m\lyrmode@no \else
\ifx\st@m\beamst@m
\expandafter\ifx\csname s@bl\balken@nr\endcsname o\lyrmode@aux
\else\lyrmode@main
\fi
\else\errmessage{invalid \string\st@m\space setting}%
\fi\fi\fi\fi\fi\fi\fi}
\else % mit PMX
\def\decide@lyrmode{%
\ifadvance % nur "spacing"-Noten
\call@lyrmode
\if@pmx@nextvoice % Oberstimme
\if@lyrmode\lyrmode@main \else\pmx@auxmode \fi
\else % Unterstimme
\if@lyrmode\pmx@auxmode \else\lyrmode@main \fi
\fi
\else
\lyrmode@no % non-spacing Note
\fi}
\let\orig@nextvoice\nextvoice % \nextvoice erweitern
\def\nextvoice{\orig@nextvoice\@pmx@nextvoicetrue}
\fi
\def\call@lyrmode{% activate lyrmode setting of current staff/instrument
\ifnum\st@ffs>1 % multi-staff instrument
\csname zlm@\the\noinstrum@nt-\the\noport@@\endcsname
\else % single-staff instrument
\csname zlm@\the\noinstrum@nt-1\endcsname
\fi}
\def\switch@pmx@aux#1{%
% using PMX, automatically activate auxlyr context for #1 after \nextvoice
\ifx\nextvoice\undefined % not using PMX
#1%
\else % using PMX
\call@lyrmode
\if@pmx@nextvoice % upper PMX voice
\if@lyrmode\lyrmode@main \else\pmx@auxmode \fi
\else % lower PMX voice
\if@lyrmode\pmx@auxmode \else\lyrmode@main \fi
\fi
\main@aux@or@not{#1}%
\fi}
% lyrmode umschalten:
\def\switch@lyrmode#1#2#3{%
% #1: instrument number
% #2: staff-of-instrument number
% #3: lyrmode setting
\ifnum#1=0 % % alle Zeilen einbeziehen
\switch@lyrmode@all@instrum#3%
\else % nur 1 Zeile
\global\expandafter\let\csname zlm@#1-#2\endcsname=#3%
\fi}
% loop over all possible instruments
\def\switch@lyrmode@all@instrum#1{%
\m@loop \switch@lyrmode@all@staves#1\repeat}
% loop over 4 possible staves of instrument
\def\switch@lyrmode@all@staves#1{%
\begingroup
\count@=0
\loop
\advance\count@ 1
\switch@lyrmode{\the\noinstrum@nt}{\the\count@}#1%
\ifnum\count@<4 \repeat
\endgroup}
% ... nicht wahnsinnig effizient, zugegeben ...
\def\lyrmodenormal#1{\lyrmodenormalmulti{#1}1}
\def\lyrmodealter #1{\lyrmodealtermulti {#1}1}
\def\lyrmodenormalmulti#1#2{\switch@lyrmode{#1}{#2}\@lyrmodefalse}
\def\lyrmodealtermulti #1#2{\switch@lyrmode{#1}{#2}\@lyrmodetrue }
\def\lyrmodenormalhere{\lyrmode@here\@lyrmodefalse}
\def\lyrmodealterhere {\lyrmode@here\@lyrmodetrue }
\def\lyrmode@here#1{% apply lyrmode to current instrument/staff context
% #1: lyrmode switch
\if@multistaff
\switch@lyrmode{\the\noinstrum@nt}{\the\noport@@}#1%
\else
\switch@lyrmode{\the\noinstrum@nt}1#1%
\fi}
\lyrmodenormal0 % auf "normal" initialisieren
% fuer Halsrichtungs-Automatik: Balkenlage in \s@bl<nr> vermerken
\def\balk@nlage#1{\global\expandafter\let
\csname s@bl\balken@nr\endcsname #1\relax}
\let\orig@i@bu\i@bu \def\i@bu{\balk@nlage o\orig@i@bu}% <o>ben
\let\orig@i@bl\i@bl \def\i@bl{\balk@nlage u\orig@i@bl}% <u>nten
\let\orig@s@l@ctbeam\s@l@ctbeam
\def\s@l@ctbeam#1\relax{\orig@s@l@ctbeam#1\relax \xdef\balken@nr{\number\n@i}}
% Auto-Text ein- und ausschalten (innerhalb \notes...\enotes):
\def\lyricson{\forall@verses{\add@context\verse@on@context}}
\def\verse@on@context{%
\let\evtl@next@lyr\next@lyr
\add@context\verse@on@context}
\def\lyricsoff{\forall@verses\clear@context}
\def\lyric {\let\evtl@hyph\@hyphenfalse\futurelet\ast@risk\lyric@i}
\def\lyrich{\let\evtl@hyph\@hyphentrue \futurelet\ast@risk\lyric@i}
\def\lyric@i{%
\ifx\ast@risk*%
\let\evtl@nolyr\relax \let\@next\lyric@ii
\else
\let\evtl@nolyr\nolyr \def\@next{\nolyr\lyric@ii*}%
\fi
\@next}
% Silben abseits vom Haupttext ausgeben:
\def\lyric@ii*#1{\forall@verses{\@context\evtl@hyph\@lyric{#1}}\evtl@nolyr}
% Manipulationen einzelner Haupttext-Silben:
\def\forall@context#1{\forall@verses{\add@context{#1}}}
% 1 Silbe ausgeben:
\def\lyr{\forall@verses{\@context\next@lyr}}
% Linksbuendig:
\def\llyr{\forall@context{\leftlyrtrue}}
% Verlaengerungs-Strich:
\def\lyrrule{\forall@context{\@strichtrue}}
% Horizontale Verschiebung (analog \roffset):
\def\lyroffset#1{\forall@context{\def\evtl@offset{\roffset{#1}}}}
% Kein automatischer Text:
\def\nolyr{\forall@context{\let\evtl@next@lyr\empty}}
% Kleinbuchstaben:
\def\lclyr{\forall@context{\let\evtl@klein\@klein}}
\def\@klein#1{\lowercase\expandafter{#1}}
% Satzzeichen anhaengen:
\def\lyrpt#1{\forall@context{\def\evtl@komma{#1}}}
% Punkt vom Ende abschneiden:
\def\lyrnop{\forall@context{\let\evtl@punktweg\@punktweg}}
\def\@punktweg#1{{\punktweg@rek#1\ende}}
\def\punktweg@rek#1#2\ende{%
\def\par@ii{#2}%
\ifx\par@ii\empty\else
\aftergroup#1%
\expandafter\punktweg@rek\par@ii\ende
\fi}
% Melisma beginnen und abschliessen:
\def\beginmel{\forall@verses{\llyr\lyrrule\add@context\lyricsoff}}
\def\endmel{\forall@verses{\lyrruleend\add@context\lyricson}}
%
% Bindebogen unter zwei Silben derselben Note:
%
\def\lyrlink {\lyr@link0}
\def\lowlyrlink{\lyr@link1}
\def\lyr@link#1{%
% Bogen erstellen:
\setbox\lyr@linkbox=\hbox{$\smile$}%
% In Box der Breite eines Wortzwischenraums einsetzen:
\setbox\lyr@linkbox=\hbox to\the\fontdimen2\the\font{%
\hss
% Unter die Grundlinie druecken:
\lower\ht\lyr@linkbox\hbox{%
% Zusaetzlicher vertikaler Abstand zur Wortunterseite:
\lower1pt\hbox{%
\if#10\relax
\hbox{$\smile$}%
\else
% Buchstabe mit Unterlaenge -> auch darunter druecken:
\setbox\lyr@linkdepthbox=\hbox{y}%
\lower\dp\lyr@linkdepthbox\hbox{$\smile$}%
\fi}}%
\hss}%
% Keine zusaetzliche Tiefe fuer Bogen anrechnen:
\dp\lyr@linkbox=0pt
% Bogen setzen:
\box\lyr@linkbox}
\makeatother