4

I am in the process of writing commands to typeset chord tabulatures for guitar using TikZ. Because for every chord, there are two equivalent (enharmonic) names, I want to avoid redundancy when defining them, so I want to write a macro which produces slightly different code for every one of them. However, contrary to this question, since the typeset chord name needs to be included in the TikZ code, I need to handle an additional argument which can vary for each version of the command name.

Since I use TikZ anyway, my approach was to use a two-variable \foreach loop and a kind of anaphoric macro \newchord (BTW, I find it interesting that this term is apparently not common for TeX), which uses a \chordname command that should be expanded to the respective name in each definition:

\documentclass{article}
\usepackage{tikz}

\newcommand{\newchord}[2]{%
  \foreach \command / \name in {#1} {%
    \expandafter\gdef\csname \command\endcsname{%
      \def\chordname{\name}
      #2
    }
  }
}

% this is how I want to use the macro:
\newchord{ASharp/{A\(\sharp\)}, BFlat/{B\(\flat\)}}{%
  % here, actually some TikZ drawing will happen
  I am: \chordname
}

\begin{document}
\ASharp % -> I am: A♯
\BFlat % -> I am: B♭
\end{document}

This example fails with

! Undefined control sequence.

\chordname ->\name

l.20 \ASharp

I suspect that is because \chordname is expanded too early, but honestly, I don't really know much about expansion, anyway...

I wouldn't mind a solution not using \foreach, this was just what I found the easiest to start with. Also the way of using \chordname was just an idea -- as long as a solution works similar to my usage example, it's OK (but it would be interesting to get it work). The slash as separator is also not significant, it is just what \foreach requires to work.

2
  • I think, this is easier with expl3 features – user31729 Feb 24 '17 at 17:50
  • @christian I'd be OK with that, too. – phipsgabler Feb 24 '17 at 17:55
3

Usual problem: you have to expand \command and \name:

\documentclass{article}
\usepackage{tikz}

\newcommand{\newchord}[2]{%
  \foreach \command/\name in {#1} {%
    \expandafter\xdef\csname \command\endcsname{%
      \def\noexpand\chordname{\unexpanded\expandafter{\name}}%
      \unexpanded{#2}%
    }%
  }%
}

% this is how I want to use the macro:
\newchord{ASharp/{A\(\sharp\)}, BFlat/{B\(\flat\)}}{%
  % here, actually some TikZ drawing will happen
  I am: \chordname
}

\begin{document}
\ASharp % -> I am: A♯

\BFlat % -> I am: B♭
\end{document}

enter image description here

A version with expl3:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\newchord}{mm}
 {
  \clist_map_inline:nn { #1 }
   {
    \phg_newchord:nn { ##1 } { #2 }
   }
 }
\cs_new_protected:Nn \phg_newchord:nn
 {
  \__phg_newchord:nw {#2} #1 \q_stop
 }
\cs_new_protected:Npn \__phg_newchord:nw #1 #2/#3 \q_stop
 {
  \cs_new:cpn { #2 }
   {
    \cs_set:Npn \chordname { #3 }
    #1
   }
 }
\ExplSyntaxOff

% this is how I want to use the macro:
\newchord{
  ASharp/A\(\sharp\),
  BFlat/B\(\flat\)
}{%
  % here, actually some TikZ drawing will happen
  I am: \chordname
}

\begin{document}
\ASharp % -> I am: A♯

\BFlat % -> I am: B♭
\end{document}

A second version with a slightly different syntax (but better, in my opinion):

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\newchord}{mm}
 {
  \clist_map_inline:nn { #1 }
   {
    \phg_newchord:nn { ##1 } { #2 }
   }
 }

\tl_new:N \l__phg_newchord_replacement_tl

\cs_new_protected:Nn \phg_newchord:nn
 {
  \tl_set:Nn \l__phg_newchord_replacement_tl { #2 }
  \keys_set:nn { phg/newchord } { #1 }
 }
\keys_define:nn { phg/newchord}
 {
  unknown .code:n = \__phg_newchord_define:Vn \l_keys_key_tl { #1 }
 }
\cs_new_protected:Nn \__phg_newchord_define:nn
 {
  \cs_new_protected:cpx { #1 }
   {
    \cs_set:Npn \exp_not:N \chordname { \exp_not:n { #2 } }
    \exp_not:V \l__phg_newchord_replacement_tl
   }
 }
\cs_generate_variant:Nn \__phg_newchord_define:nn { V }
\ExplSyntaxOff

% this is how I want to use the macro:
\newchord{
  ASharp = A\(\sharp\),
  BFlat = B\(\flat\),
}{%
  % here, actually some TikZ drawing will happen
  I am: \chordname
}

\begin{document}
\ASharp % -> I am: A♯

\BFlat % -> I am: B♭
\end{document}
3
  • Awesome. I have tried xdef alone before, but could explain a bit why the the "unexpansions" help to make it work in the correct order? Just for me learning to prevent usual mistakes in the future... – phipsgabler Feb 24 '17 at 18:03
  • @phg \xdef is needed because you want the definition to be global; you just have to not go all the way down, so \unexpanded\expandafter{\name} just does one step of expansion, leaving the meaning of \name; the #2 part should not be expanded at all, so it's passed through \unexpanded. – egreg Feb 24 '17 at 18:13
  • Thank you, that makes sense. While I agree that the third version has the better syntax, I think I will still use the first one, since it's the only one I can really read ;) – phipsgabler Feb 24 '17 at 18:30
3

You can use pgfkeys and its .list handler to avoid global definitions:

\documentclass{article}
\usepackage{tikz}

\pgfkeys{
  chords/.is family,chords,
  define/.code args={#1/#2/#3}{
    \expandafter\def\csname#1\endcsname{%
      \def\chordname{#2}
      #3
    }
  },
}
\newcommand{\newchord}[2]{%
  \pgfkeys{chords,
    define hardcode/.style={define=##1/#2},
    define hardcode/.list={#1},
  }
}

% this is how I want to use the macro:
\newchord{ASharp/{A\(\sharp\)}, BFlat/{B\(\flat\)}}{%
  % here, actually some TikZ drawing will happen
  I am: \chordname
}

\begin{document}
\ASharp % -> I am: A♯
\BFlat % -> I am: B♭
\end{document}
2

A version with expl3 and direct expansion on definition

\documentclass{article}
\usepackage{tikz}

\usepackage{expl3}


\ExplSyntaxOn

\cs_new:Npn \newchord #1 {%
  \seq_set_from_clist:Nn \l_tmpa_seq {#1}
  \seq_map_inline:Nn \l_tmpa_seq {%
    \seq_set_split:Nnn \l_tmpb_seq {/} {##1} 
    \cs_gset:cpx {\seq_item:Nn \l_tmpb_seq {1}} {\seq_item:Nn \l_tmpb_seq {2}}
  }
}
\ExplSyntaxOff

\newchord{ASharp/{A\(\sharp\)}, BFlat/{B\(\flat\)}}{%
  % here, actually some TikZ drawing will happen
  %I am: \chordname
}

\begin{document}
\ASharp % -> I am: A♯

\BFlat % -> I am: B♭
\end{document}
2
  • And where would I put in a template here? If the "I am:" line is uncommented, this leads to an error. Modifying the code to use {I am \seq_item:Nn \l_tmpb_seq {2}} also does not gain much, as that would then be constant for all definitions, wouldn't it? – phipsgabler Feb 24 '17 at 18:19
  • Because \chordname is not defined, of course. It's an error to use it the preamble anyway then -- and your initial example does use it the preamble... – user31729 Feb 24 '17 at 19:06
1

Here is an approach using the listofitems package to parse the data.

EDITED to give the syntax specified by the OP.

\documentclass{article}
\usepackage{listofitems}
\newtoks\Chordtoks
\newtoks\ChordDatatoks
\newcommand{\addtotoks}[2]{#1\expandafter{\the#1#2}}
\newcommand{\xaddtotoks}[2]{\expandafter\addtotoks\expandafter#1\expandafter{#2}}
\newcommand\newchord[2]{%
  \setsepchar{,}%
  \readlist*\ChordList{#1}%
  \foreachitem\x\in\ChordList{%
    \setsepchar[$]{/}%
    \readlist*\ChordData{\x}%
    \ChordDatatoks\expandafter\expandafter\expandafter{\ChordData[2]}%
    \Chordtoks{\def\chordname}%
    \xaddtotoks\Chordtoks{\the\ChordDatatoks}%
    \addtotoks\Chordtoks{#2}%
    \def\tmp{\expandafter\def\csname\ChordData[1]\endcsname}%
    \expandafter\tmp\expandafter{\the\Chordtoks}%
  }%
}
\newchord{ASharp/{A\(\sharp\)}, BFlat/{B\(\flat\)}}{%
  % here, actually some TikZ drawing will happen
  I am: \chordname
}
\begin{document}
\ASharp

\BFlat
\end{document}

enter image description here


ORIGINAL APPROACH

\documentclass{article}
\usepackage{listofitems}
\newcommand\newchord[2]{%
  \setsepchar{,}%
  \readlist*\ChordList{#1}%
  \foreachitem\x\in\ChordList{%
    \setsepchar[$]{/}%
    \readlist*\ChordData{\x}%
    \def\tmp{\expandafter\def\csname\ChordData[1]\endcsname}%
    \expandafter\expandafter\expandafter\tmp%
    \expandafter\expandafter\expandafter{\ChordData[2]}%
    \def\chordname{\csname\ChordData[1]\endcsname}%
    #2\par
  }%
}
\begin{document}
\newchord{ASharp/{A\(\sharp\)}, BFlat/{B\(\flat\)}}{%
  % here, actually some TikZ drawing will happen
  I am: \chordname
}
\end{document}

I have separately verified that \ASharp contains the tokens {A\(\sharp\)} and likewise, \BFlat contains the tokens {B\(\flat\)} [for this original approach].

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.