%%
%% musixlyr.tex:  Convenient lyrics handling for MusiXTeX T.52 or later
%%
%% Copyright (C) 1996-2001  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\write10{MusiXLYRics 2.0c\space<December 06, 2001>}

\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\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}}

% 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}%
  }

% 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\gdef\csname stp@#1\endcsname{0pt}% 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=0pt
  \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=0pt
  \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=1
       % 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=1 % 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=1
          % 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{1}%
        \else
	  \if@strich    % Verlaengerungs-Strich gefordert?
	    \expandafter\gdef\csname zwr@\text@name\endcsname{2}%
          \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}

% Verlaengerungs-Striche abschliessen:
\def\lyrruleend{\forall@verses\lyrrule@end}

\def\lyrrule@end{%
  \expandafter\ifcase\csname zwr@\text@name\endcsname
    % 0 -> Bindestrich von vorigem System anhaengig (nur bei Melisma;
    %      wurde bei Systemende zurückgesetzt)
    %   -> Einstellung fuer Bindestrich-Einfuegen an nächster Note setzen:
    \expandafter\gdef\csname zwr@\text@name\endcsname{1}%
  \or
    % 1 -> Bindestrich anhaengig -> nichts tun
  \or
    % 2 -> Strich anhaengig:
    \roff{% Zum rechten Notenkopf-Rand
      \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}%
  \fi}

\def\print@lyr@rule{%
  % Kleiner Zwischenraum zur vorherigen Silbe:
  \advance\y@v by -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:
  \expandafter\ifcase\csname zwr@\text@name\endcsname
    % 0 -> fertig;
  \or
    % 1 -> Bindestrich anhaengig:
    \csname llay@\text@name\endcsname% Layoutkontext abrufen
    \get@lyrspace
    \print@hyphen
    % Einstellung zuruecksetzen
    % (-> Bindestrich wird nicht im nächsten System fortgesetzt,
    %     außer bei \lyrrule@end am Melisma-Ende):
    \expandafter\gdef\csname zwr@\text@name\endcsname{0}%
  \or
    % 2 -> Verlaengerungs-Strich anhaengig:
    % Platz zum rechten Systemrand, damit Strich nicht in Taktstrich ragt:
    \loffset{0.3}{\get@lyrspace\print@lyr@rule}%
  \fi
  % Startposition fuer naechstes System zuruecksetzen:
  \expandafter\gdef\csname stp@\text@name\endcsname{0pt}}

\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
  \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 hight of current staff
  \raise\y@iv\vbox\@to\z@{\vss\offinterlineskip#1\vss}}

\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
