# Using TikZ to depict pitchlevel

After the very productive outcome of my recent question on intonation, I'm emboldened to ask a related question. In the previous one, particular pitchlevels are connected to produce an intonation contour, but for tone languages (where each syllable in a word usually has a particular pitch associated with it, a different approach is needed, where the pitchlevels are indicated by small horizontal lines. I have a couple of approaches to this that already work, and I have now created a new TikZ one based on Matthew Leingang's approach to the earlier question, but I'm sure improvements are possible. The options are set out in the MWE below, using examples from kiKongo, an African Bantu language.

%!TEX TS-program = xelatex
%!TEX encoding = UTF-8 Unicode

\documentclass{article}

\usepackage{fontspec}
\defaultfontfeatures{Mapping=tex-text, Scale=MatchLowercase}
\setmainfont{Charis SIL}

\usepackage{tikz}
\usetikzlibrary{backgrounds}
\newcommand{\tikzpitch}[1]{\tikz[overlay, remember picture, baseline] \coordinate (#1);}%
\tikzstyle{pitchlevel}=[x=1mm, y=1mm, yshift=-7mm, semithick]

% Charis SIL pitchlevel glyphs
\newcommand \pitch[1]{\char"F1F#1}  % use \pitch1..9 to generate the 9 pitch-level marks fromthe glyphs F1F{1..9}

% John Frampton's pitchlevels code
\usepackage{pstricks}
\newdimen\dimpla
\newdimen\dimplb
\newdimen\dimplc
\def\1{\contourpitch{-2.5ex}{-2.5ex}}
\def\2{\contourpitch{-2.1ex}{-2.1ex}}
\def\3{\contourpitch{-1.7ex}{-1.7ex}}
\def\4{\contourpitch{-1.3ex}{-1.3ex}}
\def\5{\contourpitch{-.9ex}{-.9ex}}
\def\6{\contourpitch{-.5ex}{-.5ex}}
% level pitches
\def\contourpitch #1#2#3{\setbox0=\hbox{#3}\dimpla=.5\wd0 \dimplb=.5\wd0
\dimplc=#1
\ifdim#2<#1 \dimplc=#2 \fi
\dimplc=-\dimplc
\psline(\dimpla,#1)(\dimplb,#2)
\vrule width0pt depth\the\dimplc #3}
% glides
\def\pitchup{\contourpitch{-2.5ex}{-1.7ex}}
\def\pitchdown{\contourpitch{-1.7ex}{-2.5ex}}
\def\steepup{\contourpitch{-2.5ex}{-.5ex}}

% format fbox
\setlength\fboxsep{1pt}

\begin{document}

% generate glides in Charis SIL
\fontspec[Renderer=Graphite]{Charis SIL}

\section{Pitchlevel marking using xelatex}

In Africanist linguistics, pitchlevel marks are often used so that the pitches can be discussed without drawing premature conclusions as to how these pitches should be represented in the eventual tone-marking.

\subsection{Word-side (inline) marking}

The example of \textbf{ibuuna [~\pitch1\, \pitch9\pitch9\, \pitch1~]} (\textit{so}) can be used to show inline marking of individual words.  This solution uses the pitchlevel glyphs in Charis SIL.  Glide variants are possible:
[~\pitch1\, \pitch9\, \pitch1\pitch5~]
[~\pitch1\, \pitch9\, \pitch1\pitch1\pitch5~]
[~\pitch1\, \pitch9\, \pitch1\pitch1\pitch5\pitch5~]
[~\pitch1\, \pitch9\, \pitch1\pitch5\pitch5~]

\bigskip

\noindent An alternative solution to the same issue of showing \noindent \textbf{ibuuna} [ \tikz\draw[x=1mm, y=1mm, semithick](0,0)--(1.5,0) (2.5,2)--(5,2) (6,0)--(7.5,0); ] uses TikZ.  Glide variants are also possible here:
[ \tikz\draw[x=1mm, y=1mm, semithick](0,0)--(1.5,0) (2.5,2)--(5,2) (6,0)--(7.5,1); ]
[ \tikz\draw[x=1mm, y=1mm, semithick](0,0)--(1.5,0) (2.5,2)--(5,2) (6,0)--(7.5,0)--(8.5,1); ]
[ \tikz\draw[x=1mm, y=1mm, semithick](0,0)--(1.5,0) (2.5,2)--(5,2) (6,0)--(7.5,0)--(8.5,1)--(10,1); ]
[ \tikz\draw[x=1mm, y=1mm, semithick](0,0)--(1.5,0) (2.5,2)--(5,2) (6,0)--(7.5,1)--(9,1); ]

\subsection{Parallel marking}

\subsubsection{Using Charis font features}

\noindent ibuuna · basiidi kilumbu · ki-bakweenda kuna-kiziitu \\
\pitch1~~\pitch9\pitch9~~\pitch1~~~\pitch1~~\pitch9~~\pitch7~~\pitch5~\pitch3~~~~~\pitch1\pitch5 ~~~~\pitch1~~~\pitch1~~~~\pitch9\pitch9~~~\pitch8~~~\pitch4~~\pitch1~~~\pitch5~\pitch5~\fbox{\pitch1\pitch5} \\
\textit{so · they set aside a day · on which they would go to the in-laws \dots}

\subsubsection{Using pstricks}

\noindent\1ib\5u\5un\1a · b\1as\5i\5id\4i k\3il\2umb\pitchup u · k\1i-b\1akw\5e\5end\4a k\3un\1a-k\3iz\3i\3it\fbox{\pitchup u}\\
\textit{so · they set aside a day · on which they would go to the in-laws \dots}

\subsubsection{Using TikZ}

\noindent\begin{tikzpicture}[remember picture]
\node{\tikzpitch{1}ib\tikzpitch{2}u\tikzpitch{3}un\tikzpitch{4}a · b\tikzpitch{5}as\tikzpitch{6}iid\tikzpitch{7}i k\tikzpitch{8}il\tikzpitch{9}umb\tikzpitch{10}u · k\tikzpitch{11}i-b\tikzpitch{12}akw\tikzpitch{13}eend\tikzpitch{14}a k\tikzpitch{15}un\tikzpitch{16}a-k\tikzpitch{17}iz\tikzpitch{18}iit\tikzpitch{19}u}; \\
\draw[pitchlevel] (1 |- 0,0)--+(2,0) (2 |- 0, 4.5)--+(2,0) (3 |- 0, 4.5)--+(2,0) (4 |- 0, 0)--+(2,0) (5 |- 0,0)--+(2,0) (6 |- 0, 4.5)--+(2,0) (7 |- 0, 4)--+(2,0) (8 |- 0, 3)--+(2,0) (9 |- 0,1.5)--+(2,0) (10 |- 0, 0)--+(1,0.5)--+(2,1.5) (11 |- 0,0)--+(2,0) (12 |- 0, 0)--+(2,0) (13 |- 0, 4.5)--+(3,0) (14 |- 0, 3.5)--+(2,0) (15 |- 0,3)--+(2,0) (16 |- 0, 1)--+(2,0) (17 |- 0, 3)--+(2,0) (18 |- 0, 3)--+(2,0) (19 |- 0,0)--+(1,0.5)--+(2,1.5);
\end{tikzpicture}\\
\mbox{\textit{so · they set aside a day · on which they would go to the in-laws \dots}}

\end{document}


The inline marking (1.1) is simple. Re the three versions of parallel marking (1.2):

1.2.1: Charis SIL can only be used with XeTeX. (For LaTeX there is an alternative approach using the tipa package and some additional code - see this question.) This appears to be the only font currently which contains these glyphs (in its Private Use Area), but in practice this is unlikely to be a problem. The main issue is that, as in the intonation question, it is necessary to use trial-and-error to space the glyphs to match the words. One benefit is that it is possible to use \fbox to highlight a particular pitch.

1.2.2: The pstricks code was kindly offered by John Frampton, author of expex. The main benefit is that it embeds the pitch levels into the text, making it easier to write out the example. An \fbox can be used to highlight a pitch (though it also marks the letter as well, which is less visually attractive). The pitchlevel lines are also "sharper" than in the other two options. As against that, it is slower to compile, and the current code does not allow word-side (inline) pitch marking. (It doesn't seem to work with LaTeX either, but perhaps that's just me.)

1.2.3: This is something I've put together by molesting Matthew Leingang's solution to the intonation question (I wouldn't even know where to start on mwibrow's more comprehensive code!). All I'm doing is getting the coordinate, and then drawing a line from that to a point 2mm to the right. (Ideally, I would draw 1mm each side of the coordinate, but how?) This actually works quite well - in particular, glides can be specified in more detail. However, as it stands, it needs two compiles because of remember picture, and I have no way of highlighting a pitch. Also, the kiKongo text seems to be indented slightly for some reason.

So all of these work, but I'd be interested in any suggestions for improvements, in particular, for tidying up the TikZ solution, since that works for both LaTeX and XeTeX, and is font-independent.

-

EDIT add some fixes and example of inline use (which probably should be tied up in a macro).

With some extra additions to the code from the intonation question (I provide the entire code here as it contains numerous fixes), it is possible to add marks. Not sure about font-independent, probably not as it stands. Certain it will be rubbish with right-to-left languages without extra work.

The basic idea is to associate symbols with marks which are then parsed after the characters are typeset. The main aim was to be able to align the marking symbols above the text, which looks a bit cleaner than putting marks in the text.

\contour[contour marks={0.55.0..0.55.?..3.2..+..0..0..5|..!..3.2..3.33.+}]
{ibuuna basiidi kilumbu ki-bakweenda kuna-kiziitu};


The example shown below is not supposed to be correct linguistically, but just illustrates what it can do.

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{fit}

\newdimen\contourraise
\newdimen\contourspacetokenwidth
\newdimen\contourtokenkern
\newcount\lasttokennumber
\newcount\currenttokennumber
\newcount\contourmarkcount
\newcount\contourtokenunderlinestate
\newbox\contourbox
\makeatletter

\tikzset{
tight fit/.style={
inner sep=0pt,
outer sep=0pt,
},
%
%
% How far above the reference anchor of the text,
contour raise/.code=\pgfmathsetlength\contourraise{#1},
contour reference anchor/.store in=\contourreferenceanchor,
contour reference anchor=base east,
% The scale' for the values in the contour height specification
contour scale/.store in=\contourscale,
contour scale=3pt,
% The prefix for the contour marks.
contour mark prefix/.store in=\contourmarkprefix,
contour mark prefix=contour,
% The style for the contour path
contour/.style={
draw,
rounded corners=1ex,
},
% The style for the token nodes
every contour token/.style={
anchor=base west,
tight fit,
},
contour underline/.style={
draw
},
% The character to insert a mark (use with care)
contour mark character/.store in=\contourmarkchar,
contour mark character=|,
% Want to change the code for contour marks? Use this key.
contour mark code/.store in=\contourmarkcode,
% Want to change the code for tokens? Use this key.
contour token code/.store in=\contourtokencode,
% Want to change the code for drawing the contour? Use this  key.
contour code/.store in=\contourcode,
%
% Default stuff
contour mark code={%
\coordinate (\contourmarkprefix-\the\contourmarkcount)
at ([yshift=\contourraise, y=\contourscale,
shift={(0,\currentcontourheight)}]token-\the\currenttokennumber.\contourreferenceanchor);
},
contour token code={%
\node [every contour token/.try] at
([xshift=\contourtokenkern]token-\the\lasttokennumber.base east)
(token-\the\currenttokennumber) {\token};
},
contour code={
\draw [contour] (\contourmarkprefix-1)
\foreach \y in {2,...,\the\contourmarkcount}{ --
(\contourmarkprefix-\y) };
},
contour marks/.style={
contour mark list={#1},
contour code={
\draw [y=\contourscale, contour] \contourpath;
},
contour mark code={%
\coordinate (@a) at ([yshift=\contourraise]token-\the\currenttokennumber.base west);
\coordinate (@b) at ([yshift=\contourraise]token-\the\currenttokennumber.base east);
\node [tight fit, fit={(@a) (@b)}] (\contourmarkprefix-\the\contourmarkcount) {};
},
},
% Don't draw the contour.
tokens only/.style={
contour code={}
},
%
% Only draw the contour (but the space is still used for the tokens)
contour only/.style={
every contour token/.append style={
execute at begin node={\setbox\contourbox=\hbox\bgroup},
execute at end node=\egroup\phantom{\box\contourbox}%
},
underline/.style={
draw=none
}
},
%
% Make tokens follow the contour marks.
tokens only,
contour token code={%
\node [every contour token/.try, y=\contourscale] at
([xshift=\contourtokenkern]token-\the\lasttokennumber.base east |-
0,\currentcontourheight)
(token-\the\currenttokennumber) {\token};
},
},
% What style to use when drawing underline
underline/.style={
draw
},
% The underline is drawn along the south side of a node which
% takes this style.
underline token/.style={
inner ysep=1pt
},
% When grouping tokens (e.g., for putting box around)
% this style is applied to a node that is fitted around the group
token group/.style={
inner xsep=1pt,
inner ysep=2pt,
rounded corners=2pt
},
% Draw boxes around tokens groups.
box tokens/.style={
token group/.append style={
draw
}
},
% Change the width of the spaces.
space token width/.code=\pgfmathsetlength\contourspacetokenwidth{#1},
space token width=0.125cm,
contour mark list/.store in=\@contourmarklist%
}

\def\at@{@}

\let\@contourmarklist=\@empty

\def\contour{%
\pgfutil@ifnextchar[{\contour@opts}{\contour@opts[]}}
\def\contour@opts[#1]{%
\pgfutil@ifnextchar x{\contour@@opts[#1]}{\contour@@opts[#1]}}
\def\contour@@opts[#1]#2;{%
\begin{scope}[#1]
\coordinate (token-0);
\currenttokennumber=0\relax%
\lasttokennumber=0\relax%
\contourmarkcount=0\relax%
\def\lastcontourheight{0}%
\contourtokenunderlinestate=0\relax%
\let\lastcontourtoken=\relax%
\contourtokenkern=0pt\relax%
\def\contourpath{}%
\@contour#2@%
}

% Must check for a spaces
\def\@contour{\futurelet\@token\@checkforspace}

\def\@uscore{_}
\def\@checkforspace{%
\ifx\@token\pgfutil@sptoken%
\let\@next=\@replacespace%
\else%
\if\@token\contourmarkchar%
\let\@next=\@contour@insertmark
\else%
\if\@token\@uscore
\let\@next=\@contourtoggleunderline%
\else%
\let\@next=\@@contour%
\fi%
\fi%
\fi%
\@next%
}

\def\@contourtoggleunderline#1{%
\ifnum\contourtokenunderlinestate>3\relax%
\contourtokenunderlinestate=0\relax%
\fi%
\@contour%
}

\def\@contour@insertmark{%
\afterassignment\@@contour@insertmark\let\@token=%
}

\def\@@contour@insertmark{%
\futurelet\@token\@@@contour@insertmark}%

\def\@@@contour@insertmark{%
\if\@token[%
\let\@next=\@@@@contour@insertmark%
\else%
\let\currentcontourheight=\lastcontourheight%
\let\@next=\@@@@@contour@insertmark%
\fi%
\@next%
}

\def\@@@@contour@insertmark[#1]{%
\def\@tmp{#1}%
\ifx\@tmp\@empty%
\let\currentcontourheight=\lastcontourheight%
\else%
\def\currentcontourheight{#1}%
\fi%
\@@@@@contour@insertmark}

\def\@@@@@contour@insertmark{%
% Code for inserting mark
\contourmarkcode%
\let\lastcontourheight=\currentcontourheight%
\@contour}

\def\contourspacetoken{{\hbox to \contourspacetokenwidth{\hfill}}}

\def\@replacespace#1{%
\@contour\contourspacetoken#1%
}

\def\@@countour@afterlatenode{%
\pgf@x=\pgfpositionnodelatermaxx\relax%
\global\edef\@contournodewidth{\the\pgf@x}%
}

\def\@@contour#1{%
\def\@token{#1}%
\if\@token\at@%
\@contourdounderline%
\pgfutil@ifundefined{pgf@sh@ns@tokengroup}{}{%
\node [tight fit, fit={(tokengroup)}, token group/.try] {};
\global\let\pgf@sh@ns@tokengroup=\relax%
}%
\let\@next=\@@@contour%
\else%
\lasttokennumber=\currenttokennumber%
\contourtokenkern=0pt\relax%
\ifnum\currenttokennumber>1\relax%
%
% Take care of kerning.
%
% First get the width of the last and current token in the same hbox.
%
\let\pgfpositionnodelaterbox=\contourbox
\pgfpositionnodelater\@@countour@afterlatenode%
\def\token{\lastcontourtoken\@token}%
\begingroup%
\tikzset{every contour token/.append style={tight fit}}%
\contourtokencode%
\endgroup%
\let\@contourkerntmp=\@contournodewidth%
%
% Now subtract the width of last and current token in separate boxes.
%
\def\token{\hbox{\lastcontourtoken}\hbox{\@token}}%
\begingroup%
\tikzset{every contour token/.append style={tight fit}}%
\contourtokencode%
\endgroup%
\pgfmathsetlength\contourtokenkern{\@contourkerntmp-\@contournodewidth}%
\pgfpositionnodelater\relax%
\fi%
%
% OK, now actually typset the current token
%
\let\token=\@token%
\contourtokencode%
\let\lastcontourtoken=\token%
% Manage underline state
\@contourdounderline%
\def\@@token{\contourspacetoken}%
\ifx\@token\@@token%
\pgfutil@ifundefined{pgf@sh@ns@tokengroup}{}{%
\pgfutil@ifundefined{pgf@sh@ns@underline}{}{%
\node [tight fit, fit={(tokengroup) (underline)}]
(tokengroup)
{};}%
\node [tight fit, fit={(tokengroup)}, token group/.try] {};
\global\let\pgf@sh@ns@tokengroup=\relax%
}%
\else
\pgfutil@ifundefined{pgf@sh@ns@tokengroup}{%
\node [tight fit,
fit={(token-\the\currenttokennumber)}]
(tokengroup) {};
}{%
\node [tight fit,
fit={(token-\the\currenttokennumber)
(tokengroup)}]
(tokengroup){};
}%
\fi%
\let\@next=\@contour
%
\fi%
\@next%
}

\def\@contourdounderline{%
\ifcase\contourtokenunderlinestate%
\or
\node [tight fit, fit={(token-\the\currenttokennumber)}]
(underline) {};
\contourtokenunderlinestate=2\relax%
\or%
\node [tight fit,fit={(token-\the\currenttokennumber) (underline)}]
(underline) {};
\or%
\node [tight fit, fit={(underline)}, underline token/.try]
(underline) {};
\draw [underline/.try]
(underline.south west) -- (underline.south east);
\pgfutil@ifundefined{pgf@sh@ns@tokengroup}{}{%
\node [tight fit, fit={(tokengroup) (underline)}]
(tokengroup) {};%
\node [tight fit, fit={(tokengroup)}, token group/.try] {};
\global\let\pgf@sh@ns@tokengroup=\relax%
\global\let\pgf@sh@ns@underline=\relax%
}
\contourtokenunderlinestate=0\relax
\fi%
}
\def\@@@contour{%
\ifx\@contourmarklist\@empty%
\else%
\@contourdolist%
\fi%
\ifnum\contourmarkcount>1
% Code for drawing contour
\contourcode%
\fi%
\end{scope}%
\ignorespaces%
}

\def\@contourstackpop{%
\let\@contourstackitem=\@empty%
\ifx\@contourstack\@empty%
\else%
\expandafter\@@contourstackpop\@contourstack\@@contourstackpop%
\fi%
}

\def\@@contourstackpop#1#2\@@contourstackpop{%
\def\@contourstackitem{#1}%
\ifx\@contourstackitem\@empty%
\def\@contourstackitem{#2}%
\let\@contourstack=\@empty%
\else%
\def\@contourstack{#2}%
\fi%
}

\def\@contourdolist{%
\let\@contourstack=\@contourmarklist%
\let\@contourstacklastitem=\@empty%
\let\contourpath=\@empty%
\edef\contourtotaltokens{\the\currenttokennumber}%
\currenttokennumber=0\relax%
\contourmarkcount=0\relax%
\@@contourdolist%
}

\def\@@contourdolist{%
\@contourstackpop%
\ifx\@contourstackitem\@empty%
\let\@next=\relax%
\else%
\expandafter\ifx\csname contourcontourpathcommand@\@contourstackitem @\endcsname\relax%
\else%
\let\currentcontourheight=\@contourstackitem%
\contourmarkcode%
\def\contourmarkstart{\contourmarkprefix-\the\contourmarkcount.west}%
\def\contourmarkend{\contourmarkprefix-\the\contourmarkcount.east}%
\edef\contourpath{\contourpath \csname contourcontourpathcommand@\@contourstackitem @\endcsname}%
\fi%
\let\@next=\@@contourdolist%
\let\@contourstacklastitem=\@contourstackitem%
\fi
\@next%
}

% \contourcontourpathcommand{<symbol>}{<contour path command code>}
% \contourmarkstart and \contourmarkend are setup as the
% left and right points of the charactor at zero contour height.
\def\contourcontourpathcommand#1{\expandafter\def\csname contourcontourpathcommand@#1@\endcsname}

% \contourmark{<symbol>}{<mark start height>}{<mark end height>}
%

\def\contourmark#1#2#3{%
\contourcontourpathcommand{#1}{([shift={(0,#2)}]\contourmarkstart) -- ([shift={(0,#3)}]\contourmarkend)}
}

\makeatother

% Separator.
\contourcontourpathcommand{.}{}

\contourmark{0}{0}{0}
\contourmark{1}{1}{1}
\contourmark{2}{2}{2}
\contourmark{3}{3}{3}
\contourmark{4}{4}{4}
\contourmark{5}{5}{5}
\contourmark{+}{0}{1}

\contourcontourpathcommand{!}{
(\contourmarkstart) .. controls ++(0,2) and ++(0,2) .. (\contourmarkend)
}

\contourcontourpathcommand{?}{
(\contourmarkstart) .. controls ++(0,-1) and ++(0,-1) .. ([shift={(0,2)}]\contourmarkend)
}
\contourcontourpathcommand{|}{
% Continue previous path
-- ([shift={(0, 3)}]\contourmarkend)
}

\begin{document}

\begin{tabular}{c}
\begin{tikzpicture}
\contour[contour/.append style={rounded corners=0}, contour raise=-0.75cm,
contour marks={0.55.0..0.55.?..3.2..+..0..0..5|..!..3.2..3.33.+}]
{ibuuna basiidi kilumbu ki-bakweenda kuna-kiziitu};

\end{tikzpicture}
\\[0.5cm]
in line marking: ibuuna [\tikz[baseline={(0,0.25ex)}]%
\contour[contour only, contour scale=2ex/6,
contour marks={0.55.0}]
{ibuuna};]

\end{tabular}
\end{document}


-
This is outstanding! I've now tested it with a dozen fonts, and it works fine in all of them, so for me that's "font-independent". The idea of aligning the marks above the text is inspired - switch to a mono font,and adding the levels is child's play. I also like the \contourcontourpathcommand code, which in effect allows any sort of squiggle to be generated and used as a pitchmark, so the system is very versatile. I've tested various entry characters here, and since , < > : ; @ ^ ( ) - _ = '` all work, there is plenty of scope for expansion. Thank you so much for this! –  donnek Apr 16 '13 at 8:21
Mark, to make your code easier to adjust and document, I've abstracted it into a Gitorious repo and licensed it GPL3 with you as the copyright holder. If you object to any of this, please let me know, and I'll change it. –  donnek Apr 30 '13 at 10:09
I have no objection to it being uploaded to gitorious, but I think (1) the gitorious page should contain a link to this stack-exchange article (2) the tex file should also contain the link to this article (3) instead of "Mark Wibrow's pitch contour code for TikZ", just "Pitch contour code for TikZ" (my name is in the file, that's all that's required) (4) Your name also needs be in the file as well you have adapted it, added code (i.e., "Copyright Mark Wibrow and Kevin Donnelly 2013" - or "donnek", whichever you prefer). –  Mark Wibrow Apr 30 '13 at 16:44
OK, great. I've made all those changes. –  donnek Apr 30 '13 at 19:27