15

I want to write a command that responds to an argument being in one set of reference strings or another.

The argument takes a (musical) note name, and the command will print an accidental depending on whether the argument is in (a, h, c, d, e, f, g), (ces, des, es, fes, ges, as, b) or (cis, dis, eis, fis, gis, ais, his) (German note names).

In a programming language I would use the in operator to achieve this, but I don't know how to do it in LaTeX.

What I would like to write is

\notewithacc{c}\\
\notewithacc{cis}\\
\notewithacc{des}

and get

\natural c\\
\sharp cis\\
\flat des

as the output.

uli_1973
  • 1,920

3 Answers3

13

Taking a similar approach to Heiko's answer but using the LaTeX3 programming layer, expl3, might lead to something like:

\documentclass{article}

\usepackage{expl3,xparse}
\ExplSyntaxOn
\makeatletter
% Comma lists of notes
\clist_const:Nn \c_ulinotes_natural_clist {a, h, c, d, e, f, g}
\clist_const:Nn \c_ulinotes_flat_clist {ces, des, es, fes, ges, as, b}
\clist_const:Nn \c_ulinotes_sharp_clist {cis, dis, eis, fis, gis, ais, his}

\cs_new_protected:Npn \ulinotes_note:n #1
  {
    \clist_if_in:NnT \c_ulinotes_natural_clist {#1}
      { \c_math_toggle_token \m@th \natural \c_math_toggle_token }
    \clist_if_in:NnT \c_ulinotes_flat_clist {#1}
      { \c_math_toggle_token \m@th \flat \c_math_toggle_token }
    \clist_if_in:NnT \c_ulinotes_sharp_clist {#1}
      { \c_math_toggle_token \m@th \sharp \c_math_toggle_token }
  #1
  }

\NewDocumentCommand { \notewithacc } { >{\TrimSpaces} m }
  { \ulinotes_note:n {#1} }
 \makeatother
\ExplSyntaxOff

\begin{document}
  \noindent
  \notewithacc{c}\\
  \notewithacc{cis}\\
  \notewithacc{des}\\
  \notewithacc{x}
\end{document}

This uses the expl3 tests for comma-list membership along with a built-in command to strip spaces from arguments when 'grabbed'. As in the 'standard' LaTeX2e approach we have to allow for the fact that math mode is required for the symbols, which currently means using a bit of LaTex2e code (\m@th).

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
12

The following example uses \in@{<substring>}{<string>} that tests, whether <substring> is part of <string>. After the test, the result is provided by the switch \ifin@.

\documentclass{article}

% List with notes
\newcommand*{\NotesNatural}{a, h, c, d, e, f, g}
\newcommand*{\NotesFlat}{ces, des, es, fes, ges, as, b}
\newcommand*{\NotesSharp}{cis, dis, eis, fis, gis, ais, his}

\makeatletter

% Sanitize note lists
\newcommand*{\NotesNormalize}[1]{%
  \edef#1{%
    ,\expandafter\zap@space#1 \@empty,%
  }%
}

\NotesNormalize{\NotesNatural}
\NotesNormalize{\NotesFlat}
\NotesNormalize{\NotesSharp}

% \NotesTest{<note>}{<note list>}{<yes>}{<no>}
\newcommand*{\NotesTest}[2]{%
  \edef\NotesTemp{%
    \noexpand\in@{,#1,}{#2}%
  }\NotesTemp
  \ifin@
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\newcommand*{\notewithacc}[1]{%
  \in@{,}{#1}%
  \ifin@
    ?#1%
  \else
    \NotesTest{#1}{\NotesNatural}{%
      $\m@th\natural$#1%
    }{%
      \NotesTest{#1}{\NotesFlat}{%
        $\m@th\flat$#1%
      }{%
        \NotesTest{#1}{\NotesSharp}{%
          $\m@th\sharp$#1%  
        }{%
          ?#1%
        }%
      }%
    }%
  \fi   
}
\makeatother

\begin{document}
  \noindent
  \notewithacc{c}\\
  \notewithacc{cis}\\
  \notewithacc{des}\\
  \notewithacc{x}\\
  \notewithacc{dis,eis}
\end{document}

Result

Remarks:

  • \zap@space removes spaces in the sanitizing step from the note lists.
  • Also the lists all start and ends with commas. This way, the substring with the searched note is surrounded by commas. This ensures exact matches.
  • BTW, I do not know, why accidentals are math symbols in LaTeX. \m@th removes surrounding space if \mathsurround is set.
  • The updated solution also checks, whether there is a comma in the argument of \notewithacc as in \notewithacc{dis,eis}.
Heiko Oberdiek
  • 271,626
  • Thank you for this solution. It works perfectly, although I don't fully understand it. The math/spacing issue is dealt with fontspec/lilyglyphs in the real files BTW (looks better anyway). – uli_1973 Jan 22 '14 at 12:32
  • Is there an easy way to recognize the input like \notewithacc{dis,eis} as an error as well? – g.kov Jan 22 '14 at 14:51
  • @g.kov: Yes, I have updated the answer by adding a check for the comma in the argument of \notewithacc. – Heiko Oberdiek Jan 22 '14 at 15:56
11

MWE using \csname mapping:

\documentclass{article}

\makeatletter
\@for\@n@te:=a,h,c,d,e,f,g\do{\expandafter\def\csname n@te(\@n@te)\endcsname{\natural}}
\@for\@n@te:=ces,des,es,fes,ges,as,b\do{\expandafter\def\csname n@te(\@n@te)\endcsname{\flat}}
\@for\@n@te:=cis,dis,eis,fis,gis,ais,his\do{\expandafter\def\csname n@te(\@n@te)\endcsname{\sharp}}

\def\notewithacc#1{%
\ifcsname n@te(#1)\endcsname%
\ensuremath{\csname n@te(#1)\endcsname}#1%
\else ?#1 %
\fi
}
\makeatother

\begin{document}

\makeatletter
\begin{itemize}
\item \@for\@n@te:=a,h,c,d,e,f,g\do{\notewithacc{\@n@te} }
\item \@for\@n@te:=ces,des,es,fes,ges,as,b\do{\notewithacc{\@n@te} }
\item \@for\@n@te:=cis,dis,eis,fis,gis,ais,his\do{\notewithacc{\@n@te} }
\item \@for\@n@te:=x,fi,{dis,eis}\do{\notewithacc{\@n@te} }
\end{itemize}
\makeatother

\notewithacc{des}

\end{document}

enter image description here

g.kov
  • 21,864
  • 1
  • 58
  • 95