37

I am looking into using TikZ to represent pitch patterns over a phrase or sentence (intonation).

I've got it to produce some useful results, already, as in the attached MWE (although in 1.1 the baselines don't seem to level up).

%!TEX TS-program = xelatex
%!TEX encoding = UTF-8 Unicode
\documentclass[a4paper,12pt, oneside]{article}

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

\usepackage{tikz}

\begin{document}

\section{Marking intonation using TikZ}

\subsection{Setting words on different levels}

\begin{table}[hbtp]
\begin{tabular}{ll}
\cline{1-1} \noalign{\smallskip}
\tikz[x=1mm,y=1mm,baseline=12] \draw (0,10) node {\underline{Where}} (10,2) node {are} (20,2) node {you} (30,6) node {\underline{go}} (40,0) node{ing?}; & \textit{neutral/default - stress represented by underlining}\\
\cline{1-1} \noalign{\smallskip}
\tikz[x=1mm,y=1mm,baseline=12] \draw (0,0) node {Where} (10,10) node {\underline{are}} (20,5) node {you} (30,2) node {go} (40,0) node{ing?}; & \textit{ e.g. in response to ``I'm not going to the shops after all.''}\\
\cline{1-1}
\end{tabular}
\end{table}

Why do ``are'' and ``you'' in the first example appear on slightly
different levels, even though they are both set to 1mm on the y axis? 
Likewise for ``where'' and ``ing'' in the second example.

I'm not sure I'm using ``baseline'' properly here!

\subsection{As above, but boxed}

\begin{tikzpicture}
\node[draw] at (0,1) {\underline{Where}};
\node[draw] at (1,0.1) {are};
\node[draw] at (2,0.1) {you};
\node[draw] at (3,0.6) {\underline{go}};
\node[draw] at (4,0) {ing?};
\end{tikzpicture}\
Still have to figure out the difference between ``\textbackslash draw node'' and ``\textbackslash node[draw]''!

\subsection{Graphical contour}

\begin{table}[hbtp]
\begin{tabular}{l}
\tikz[x=1mm,y=1mm,rounded corners=2mm] \draw[very thick, gray](0,10)--(10,2)--(20,2){[red]--(30,6)--(40,0)}; \\
Where are you~~~going?
\end{tabular}
\end{table}

\begin{table}[hbtp]
\begin{tabular}{l}
\tikz[x=1mm,y=1mm,rounded corners=2mm] \draw[very thick, gray](0,10)--(12,2)--(23,2){[red]--(26,6)--(35,0)}; \\
Where are you going?
\end{tabular}
\end{table}

This is preferable to setting the individual words on different levels, but
requires trial and error in (i) matching the length of the whole contour to
the utterance, and (ii) spreading the parts of the contour to match the words
of the utterance, either by spacing the words (first example) or adjusting the
contour points (second example).

\end{document}

However, my main query is whether anyone has a method for dealing with the issue in 1.3. This involves trial-and-error in fitting the contour to the words, and I was wondering if there is any way to somehow anchor particular contour coordinates to particular syllables. I know that lines can be attached to nodes, but I haven't been able to find a way of getting the lines to "float" above the nodes. Any suggestions for allowing a line of text to be marked with anchor points as it is typed, with the contour then being drawn between these?

enter image description here

Moriambar
  • 11,466
donnek
  • 577
  • 3
  • 8
  • +1 and fav from me. Not for being a good technical question (which it is, too, of course) but for being one great example of LaTeX use cases. :) – Foo Bar Apr 10 '13 at 16:12
  • Wow, guys - so much brilliance out there! Sorry I'm slow to respond, but I'm trying to test all of these in depth (and understand at least some of the code!). – donnek Apr 11 '13 at 09:01
  • Note that mwibrow's code below has been extended to allow pitchlevels as well as contours to be depicted - see the question on pitchlevels. – donnek Apr 16 '13 at 22:35

5 Answers5

27

Following up on Matthew Leingang's answer, here's the same approach tied up with some syntactic sugar. It messes around with \catcode stuff, so care needs to be taken.

\documentclass{standalone}
\usepackage{tikz}

\newcount\contourmarkcount
\newdimen\contourraise

{\catcode`\|=13
\gdef\installbarmark#1\ignorespaces{%
    #1\ignorespaces%
    \catcode`\|=13%
    \global\contourmarkcount=0\relax%
    \global\def\lastmarkshift{0}%
    \let|=\marktext}%
}


\def\star{*}
\newcommand\marktext[1][*]{%
    \def\markshift{#1}%
    % When not followed by the optional argument
    % the contour mark is set at the previous height.
    \ifx\markshift\star
        \let\markshift=\lastmarkshift%
    \fi
    \global\advance\contourmarkcount by1\relax%
    \xdef\tmpmark{\the\contourmarkcount}%
    \tikz[remember picture, overlay, y=\pgfkeysvalueof{/tikz/contour scale}]
        \path [yshift=\contourraise, shift={(0,\markshift)}]
                coordinate (\contourmarkprefix-\tmpmark);%
    \global\contourmarkcount=\tmpmark\relax% 
    \global\let\lastmarkshift=\markshift%
}


\tikzset{
    intonation contour/.style={%
        execute at begin node={%
            \installbarmark%
        },
        append after command={%
            \pgfextra{%
                \ifnum\contourmarkcount>1
                    \draw [contour] (\contourmarkprefix-1)
                        \foreach \y in {2,...,\the\contourmarkcount}{ -- (\contourmarkprefix-\y) };
                \fi
            }
        }
    },
    % How far above the base line of the text,
    raise contour/.code=\pgfmathsetlength\contourraise{#1},
    % The `scale' for the values in the contour height specification
    contour scale/.initial=3pt,
    % The prefix for the contour marks.
    contour mark prefix/.code=\xdef\contourmarkprefix{#1},
    contour mark prefix=intonation contour,
    contour/.style={
        draw, 
        rounded corners=1ex,
    }           
}

\begin{document}

\begin{tikzpicture}[remember picture]

\node [intonation contour, raise contour=0.5cm] 
    {|[10]Where |[3]are |[3]you |[5]go|[2]ing|[0]?};

\end{tikzpicture}

\begin{tikzpicture}[remember picture]

\node [intonation contour, raise contour=0.5cm, contour mark prefix=my contour] 
    {|[2]I  |am  |[7]fina|lly |[4]go|ing |[2]home|[0].};

\path [draw=red, ->] ([yshift=0.25cm]my contour-2) -- ([yshift=0.25cm]my contour-3)
        node [midway, left] {\tiny rising};

\path [draw=red, ->] ([yshift=0.25cm]my contour-4) -- ([yshift=0.25cm]my contour-5)
    node [midway, right] {\tiny falling};
\end{tikzpicture}

\end{document}

enter image description here

And just for fun, here's a version that doesn't use remember picture so doesn't need two compilations. Also there is no fooling around with category codes. It is, unfortunately, a bit more involved, and the letters are not typeset ideally as they are all in separate boxes.

EDIT A bit more versatility has been implemented, but as a result it is a bit more complex.

\documentclass{standalone}
\usepackage{tikz}

\newdimen\contourraise

\tikzset{
    % How far above the base line of the text,
    raise contour/.code=\pgfmathsetlength\contourraise{#1},
    % The `scale' for the values in the contour height specification
    contour scale/.initial=3pt,
    % The prefix for the contour marks.
    contour mark prefix/.code=\xdef\contourmarkprefix{#1},
    contour mark prefix=contour,
    % The style for the contour path
    contour/.style={
        draw, 
        rounded corners=1ex,
    },
    % The style for the token nodes
    contour token/.style={
        anchor=base west, 
        inner sep=0pt,
        text depth=0.6ex, % controls underline depth
    },
    contour underline/.style={
        draw
    },
    % The character to insert a mark (use with care)
    contour mark character/.store in=\contourmarkchar,
    contour mark character=|
}


\makeatletter

\def\at@{@}

\newcount\lasttokennumber
\newcount\currenttokennumber
\newcount\contourmarkcount
\newcount\contourtokenunderlinestate
\newcommand\contour[2][]{%
    \begin{scope}[#1]
        \coordinate (token-0);
        \currenttokennumber=0\relax%
        \lasttokennumber=0\relax%
        \contourmarkcount=0\relax%
        \def\lastcontourheight{0}%
        \contourtokenunderlinestate=0\relax%
        \@contour#2@%
}


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

\def\@uscore{_}
\def\@checkforspace{%
    \ifx\@token\@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{%
    \advance\contourtokenunderlinestate by1\relax
    \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{%
    \advance\contourmarkcount by1\relax%
    % Code for inserting mark
    \coordinate (\contourmarkprefix-\the\contourmarkcount)
        at ([yshift=\contourraise, y=\pgfkeysvalueof{/tikz/contour scale}, 
        shift={(0,\currentcontourheight)}]token-\the\currenttokennumber.base east);
    %
    \let\lastcontourheight=\currentcontourheight
\@contour}

\def\customspace{{\hbox to 1ex{\hfill}}}

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

\def\@@contour#1{%
    \def\@token{#1}%
    \if\@token\at@
        \let\@next=\@@@contour%
    \else%
        \lasttokennumber=\currenttokennumber%
        \advance\currenttokennumber by1%
        % Code for typesetting token
        \node [contour token/.try] at (token-\the\lasttokennumber.base east) (token-\the\currenttokennumber) {\@token};
        % Manage underline state
        \ifnum\contourtokenunderlinestate=1\relax%
            \coordinate (underline start) at (token-\the\currenttokennumber.south west);
            \contourtokenunderlinestate=2\relax%
        \else
            \ifnum\contourtokenunderlinestate=3\relax%
                \coordinate (underline end) at (token-\the\currenttokennumber.south west);
                \draw (underline start) -- (underline end);
                \contourtokenunderlinestate=0\relax
            \fi%
        \fi%
        \let\@next=\@contour
        %
    \fi%
    \@next%
}
\def\@@@contour{%
    \ifnum\contourmarkcount>1
        % Code for drawing contour
        \draw [contour] (\contourmarkprefix-1)
            \foreach \y in {2,...,\the\contourmarkcount}{ -- (\contourmarkprefix-\y) };
        %
    \fi%
    \end{scope}%
}

\makeatother

\begin{document}

\begin{tikzpicture}[baseline={(0,-0.25)}]

    \contour[raise contour=0.5cm]
        {|[10]Where |[3]are |[3]_you_ |[5]go|[2]ing|[0]?}

\end{tikzpicture} 


\begin{tikzpicture}[baseline={(0,-0.25)}]

\contour[
    raise contour=0.5cm, 
    contour mark prefix=my contour, 
    contour/.style={
        thick, 
        rounded corners=1mm,
        line cap=round,
        dotted},
    contour mark character=*] 
    {*[2]I *am  *[7]_{fi}na*lly_ *[4]go*ing *[2]home*[0].};

\path [draw=red, ->] ([yshift=0.25cm]my contour-2) -- ([yshift=0.25cm]my contour-3)
        node [midway, left] {\tiny rising};

\path [draw=red, ->] ([yshift=0.25cm]my contour-4) -- ([yshift=0.25cm]my contour-5)
    node [midway, right] {\tiny falling};

\path (0,-0.25);
\end{tikzpicture}

\end{document}

enter image description here

EDIT: enhanced version which hopefully covers all the use cases given above.

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

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

\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, 
        inner sep=0pt,
    },
    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 
        (token-\the\lasttokennumber.base east) 
            (token-\the\currenttokennumber) {\token};
    },
    contour code={
        \draw [contour] (\contourmarkprefix-1)
            \foreach \y in {2,...,\the\contourmarkcount}{ -- 
                    (\contourmarkprefix-\y) };                  
    },
    %
    % 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 follow contour/.style={
        tokens only,
        contour token code={%
            \node [every contour token/.try, y=\contourscale] at 
                (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
}


\makeatletter

\def\at@{@}


\newcommand\contour[2][]{%
    \begin{scope}[#1]
        \coordinate (token-0);
        \currenttokennumber=0\relax%
        \lasttokennumber=0\relax%
        \contourmarkcount=0\relax%
        \def\lastcontourheight{0}%
        \contourtokenunderlinestate=0\relax%
        \@contour#2@%
}


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

\def\@uscore{_}
\def\@checkforspace{%
    \ifx\@token\@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{%
    \advance\contourtokenunderlinestate by1\relax
    \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{%
    \advance\contourmarkcount by1\relax%
     % Code for inserting mark
    \contourmarkcode%
    \let\lastcontourheight=\currentcontourheight%
    \@contour}

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

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

\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%
        \advance\currenttokennumber by1%
        \let\token=\@token%
        % Code for typesetting token
        \contourtokencode%
        % 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{%
    \ifnum\contourmarkcount>1
        % Code for drawing contour
        \contourcode%
    \fi%
    \end{scope}%
}

\makeatother

\begin{document}

\begin{tabular}{c}
\\
\begin{tikzpicture}
    \contour[tokens follow contour]
        {|[10]_Where_ |[3]are you |[6]_go_|[1]ing?|[0]}
\end{tikzpicture} 

\\[0.5cm]

\begin{tikzpicture}
    \contour[tokens follow contour, box tokens, space token width=0.2cm]
       {|[10]_Where_ |[3]are you |[6]_go_ |[1]ing?|[0]}
\end{tikzpicture} 

\\[0.5cm]

\begin{tikzpicture}
    \contour[contour raise=0.5cm]
        {|[10]_Where_ |[3]are you |[6]_go_|[1]ing?|[0]}
\end{tikzpicture} 


\\[0.5cm]

\begin{tikzpicture}
    \contour[contour raise=0.5cm]
        {|[10]Where |[3]are |[3]_you_ |[5]go|[2]ing?|[0]}

    \contour[contour raise=0.5cm, contour only, contour/.append style={dashed}]
            {|[0]Where |[2]are |[8]you |[2]go|[2]ing?|[1]}

\end{tikzpicture} 

\\[1cm]

\begin{tikzpicture}
\contour[tokens follow contour,
    contour mark character=*] 
    {*[2]I *am  *[7]_{fi}na*lly_ *[4]go*ing *[2]_home_*[0]};
\end{tikzpicture} 
\\[0.5cm]
\begin{tikzpicture}
\contour[
    contour raise=0.5cm, 
    contour mark prefix=my contour,
    contour/.style={
        thick, 
        rounded corners=1mm,
        line cap=round,
        dotted},
    contour mark character=*] 
    {*[2]I *am  *[7]_{fi}na*lly_ *[4]go*ing *[2]home*[0].};

\path [draw=red, ->] ([yshift=0.25cm]my contour-2) -- ([yshift=0.25cm]my 
contour-3)
        node [midway, left] {\tiny rising};

\path [draw=red, ->] ([yshift=0.25cm]my contour-4) -- ([yshift=0.25cm]my 
contour-5)
    node [midway, right] {\tiny falling};

\end{tikzpicture}

\end{tabular}
\end{document}

enter image description here

David Carlisle
  • 757,742
Mark Wibrow
  • 70,437
  • 1
    Marvelous. I was thinking of a \foreach implementation but this is cleaner. – Matthew Leingang Apr 10 '13 at 20:54
  • remember picture does not work with xetex driver used in the question or perhaps i'm wrong ... – Alain Matthes Apr 10 '13 at 22:20
  • @AlainMatthes it compiles fine with xelatex, lualatex, and pdflatex (using the CVS version at least). – Mark Wibrow Apr 11 '13 at 05:43
  • Yes today the code compiles and I don't understand why yesterday I got an error : Your driver pgfsys-xetex.def does not support marking the current position ... TL2012 pgf_cvs march 2013 – Alain Matthes Apr 11 '13 at 05:57
  • I was looking for a key such as execute at begin node for another answer. I'm glad it really exists. pgfkeys...is there anything they can't do? – Matthew Leingang Apr 11 '13 at 11:14
  • I always feel I have be more cautious than I should be with execute at begin node, firstly because it is inherited by any nodes inside the node (and should be easier to cancel but isn't), and secondly, after the code is executed, TikZ does a tonne of (useful) other stuff before the node begins. That's why I define a macro with #1\ignorespaces as arguments. \ignorespaces is the last thing that TikZ does before the actual node contents begins. For details, see \tikz@do@fig in tikz.code.tex. – Mark Wibrow Apr 11 '13 at 12:08
  • So remember picture is why I needed two compilations! I'm afraid I understand hardly any of the code, so concern about making it more complex is unwarranted! (One thing I find strange is that TikZ allows spaces in its keys!). I've done several hours of testing the various versions you've produced, and this is undoubtedly very versatile - it's almost a package. The ability to add annotations to the contour is very useful, and because the flex points and contour are drawn as one, it's also possible to overlay two contours, which might be useful ... (continued in next comment) – donnek Apr 11 '13 at 16:45
  • ... if comparing changes in intonation. The only shortcoming is that the earlier version allowed words in the example to be (e.g.) underlined, but the newer version doesn't, presumably because all the letters are in separate boxes. I'm also slightly nervous about how robust this would be in the longer term (I've had things break going from TeX Live 2009 to 2012). But even so, this is a terrrific addition to the set of tools available in TeX for linguists, and I'm very grateful to you, Matthew, David and others for putting so much time into this. – donnek Apr 11 '13 at 16:50
  • Forgot to say - when I'm writing this up, if you want me to quote your real name instead of your handle, email me that. – donnek Apr 11 '13 at 16:51
  • I've updated the answer to include a (somewhat crude) underline feature. Also, I notice that if you are careful you can get correct kerning and ligatures by grouping letters in between braces, for example, {fi}. – Mark Wibrow Apr 11 '13 at 17:54
  • I don't know how you guys do this! I've tested this again, and I don't think there's anything much that could be added. Terrific! Thanks again. – donnek Apr 11 '13 at 19:55
  • @donnek An interface with a speech synthesis software to automatically generate the tikz code would be great ;-). – cjorssen Apr 11 '13 at 21:07
  • Excellent! So the new version allows us to use the same syntax to place words on a contour (tokens follow contour), and show a contour only. I really like the new boxes too - very neat. – donnek Apr 13 '13 at 10:55
  • @cjorssen I'm hoping mwibrow will add this to the next version - it shouldn't be difficult. :-) – donnek Apr 13 '13 at 10:56
  • This is really fantastic! – Gonzalo Medina Apr 15 '13 at 03:40
17

I would use the famous \tikzmark macro and combine it with intersection coordinates. Here is the code:

\documentclass{standalone}
\usepackage{tikz}
\newcommand{\tikzmark}[1]{\tikz[overlay,remember picture,baseline] \node [anchor=base] (#1) {};}%
\tikzstyle{intonation}=[rounded corners=2mm,yshift=1.5ex]
\begin{document}

\begin{tikzpicture}[remember picture]
\node{\tikzmark{w}Where \tikzmark{a}are \tikzmark{y}you \tikzmark{g}go\tikzmark{i}ing\tikzmark{q}?};
\draw[intonation] (w |- 0,1) -- (a |- 0,0.2) -- (y |- 0,0.2) -- (g |- 0,0.6) -- (q |- 0,0);
\end{tikzpicture}

\end{document}

The \tikzmark macro saves the coordinates of the point of the document at which it is called. So the node text \tikzmark{w}Where \tikzmark{a}are... saves coordinates called w, a, etc., at various points in the sentence.

If A and B are TikZ nodes or coordinates, then (A |- B) is the intersection of a vertical line through A and a horizontal line through B. So (w |- 0,1) has as its x coordinate the baseline-left point of the w in where, and y coordinate 1. You can change this y coordinate as you wish for the other coordinate names.

Here is the result:

sample code output

You might be able to design a macro that specifies/marks the text and defines the y-values in the same argument. But this is a possible implementation.

Matthew Leingang
  • 44,937
  • 14
  • 131
  • 195
  • +1 (although I think knowing what the tikz syntax means is giving you an unfair advantage:-) – David Carlisle Apr 10 '13 at 15:55
  • 1
    @DavidCarlisle: and you have the advantage of remembering \sbox, \strip@pt, and \wd :-D – Matthew Leingang Apr 10 '13 at 15:57
  • The driver pgfsys-xetex.def does not support marking the current position. – Alain Matthes Apr 10 '13 at 20:56
  • Sorry! Yes today the code compiles and I don't understand why yesterday I got an error : Your driver pgfsys-xetex.def does not support marking the current position ... TL2012 pgf_cvs march 2013 – Alain Matthes Apr 11 '13 at 05:57
  • This is elegant, and also simple, since it only needs the \newcommand definition. I've tested it a lot, and it's robust. The minor drawback is that the \tikzmark{x} makes the text more difficult to read. The first 0 in each coordinate is also redundant. I think this solution is probably the best option if additional decorations are not needed (mwibrow's annotations, Alain's embedded contour). – donnek Apr 11 '13 at 16:19
  • @donnek: yes, that's true. Like I said in the answer, you can get a cleaner interface; it just requires a lot of preamble code and maybe some catcode trickerations. Which is what mwibrow has provided. – Matthew Leingang Apr 11 '13 at 17:43
  • @MatthewLeingang Yes, mwibrow's solution is excellent, but OTOH, yours was simple enough to allow me to batter it into another option for showing pitchlevels (as opposed to contours) - see a new question. – donnek Apr 13 '13 at 15:57
9

I'm not sure I put the coordinates on the right places (and reused \zzb in two points) but something like

enter image description here

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

\documentclass[a4paper,12pt, oneside]{article}

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

\makeatletter
\def\savecoordinate#1#2{\sbox0{#2}\edef#1{\strip@pt\wd0}}
\makeatother

\usepackage{tikz}

\begin{document}


\savecoordinate\zza{Where }
\savecoordinate\zzb{Where are you}
\savecoordinate\zzc{Where are you going?}

\begin{table}[hbtp]
\begin{tabular}{l}
\tikz[x=1pt,y=1mm,rounded corners=2mm] \draw[very thick, gray](0,10)--(\zza,2)--(\zzb,2)--(\zzb,6)--(\zzc,0); \\
\mbox{Where are you going?}
\end{tabular}
\end{table}

\end{document}
David Carlisle
  • 757,742
  • David, is the Charis SIL font the original font used in the picture commands? – yannisl Apr 10 '13 at 18:09
  • 1
    {[red]...} you can't change the color inside a path. This works only with edge the line is added after the main path. This is a mistake in the question. – Alain Matthes Apr 10 '13 at 21:24
  • @AlainMatthes I did wonder if that was intended to be a colour but it didn't generate an error and I had no idea what any of the syntax means so I left it in. (Can I just delete the [red] ?) – David Carlisle Apr 10 '13 at 21:28
  • Yes you remove [red] and the group. If you remove only the group then the line is red because it's the last option in the main path. In the group, the lines are drawn but the color is fixed and it's black. – Alain Matthes Apr 10 '13 at 21:39
  • @YiannisLazarides Yes, Charis SIL was the font in my OP, mainly because it has lots of things (eg pitch marks) that no other font seems to have. – donnek Apr 11 '13 at 07:36
  • @AlainMatthes and David Yes, sorry, that was something I was trying earlier and forgot to take out of the MWE. At least Iknow now why it didn't work! – donnek Apr 11 '13 at 07:39
  • @DavidCarlisle This was attractive because it is simple, and it deals neatly with the first issue (matching the contour to the utterance). But after playing around with it for a couple of hours, it's not intuitive enough about what contour shape you'll get from a given set of co-ordinates, or what co-ordinates you need for a given contour. But thanks for showing me \mbox! – donnek Apr 11 '13 at 08:50
5

With PSTricks.

enter image description here

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-node,graphicx}

\newsavebox\IBox
\savebox\IBox{\scalebox{15}{\raisebox{\depth}{Where are you going?}}}

\psset
{
    algebraic,
    xunit=\dimexpr\wd\IBox/10,
    yunit=\dimexpr\ht\IBox/10,
}

\begin{document}
\begin{pspicture}[showgrid=false](10,20)
    \psline[linearc=25pt,linewidth=5pt]{->}(0,20)(2.5,11)(6.5,11)(7.2,18)(9.5,11)
    \rput[bl](0,0){\usebox\IBox}
\end{pspicture}
\end{document}
  • Thanks a lot for this. However, it uses pstricks, and it doesn't get around the issue of having to use trail-and-error to position the contour. – donnek Apr 11 '13 at 07:41
4

1.1

The problem is not baseline. A tikzpicture environment defines a box ( a tex's box). This box has depth and heigth. By default the depth is null and height is the height of the box. It is because the lower left point defines the baseline. In the next code, I wrote baseline=(current bounding box.east) because I don't want to use a tabular. I think it's very easy to place objects with tikz. The text \textit{neutral/default - stress represented by underlining} is aligned with the middle of the box.

Remark : I need to place the text in \mbox because there is an \overful box with the next text.

The problem with are and you is avoided with every node/.style={anchor=base}. It's a problem of box and depth. The box with the letter y has a big depth. With anchor=base all the nodes have the same baseline.

Last remark : To get the same result than with tabular, I used show background bottom etc.

\begin{table}[hbtp]
\begin{tikzpicture}[baseline=(current bounding box.east),every node/.style={anchor=base},show background top,show background bottom]
\path (0,1) node {\underline{Where}} (1,.2) node {are} (2,.2) node {you} (3,.6) node {\underline{go}} (4,0) node{ing?}; 
\end{tikzpicture}%
\mbox{\textit{neutral/default - stress represented by underlining}}%

\begin{tikzpicture}[baseline=(current bounding box.east),every node/.style={anchor=base},show background bottom] 
    \draw (0,0) node {Where} (1,1) node {\underline{are}} (2,.5) node {you} (3,.2) node {go} (4,0) node{ing?}; 
\end{tikzpicture}%
\mbox{\textit{e.g. in response to ``I'm not going to the shops after all.''}}%
\end{table}

1.2

When you add a node in a path, this node is not exactly a part of the path. It's an object attach to the path but some options of the path are not used for the node

\draw node ..., signifies \path[draw] node ... ; you decide to draw the path but the drawoption of the path is not the draw option of node. If you want to draw the path and the node, you need to write \path[draw] node[draw] ... ;

1.3

Complete code and result

%!TEX TS-program = xelatex
%!TEX encoding = UTF-8 Unicode
\documentclass[a4paper,12pt, oneside]{article}

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

\usepackage{tikz}
\usetikzlibrary{backgrounds}
\begin{document}

\section{Marking intonation using TikZ}

\subsection{Setting words on different levels}

\begin{table}[hbtp]
\begin{tikzpicture}[baseline=(current bounding box.east),every node/.style={anchor=base},show background top,show background bottom]
\path (0,1) node {\underline{Where}} (1,.2) node {are} (2,.2) node {you} (3,.6) node {\underline{go}} (4,0) node{ing?}; 
\end{tikzpicture}%
\mbox{\textit{neutral/default - stress represented by underlining}}%

\begin{tikzpicture}[baseline=(current bounding box.east),every node/.style={anchor=base},show background bottom] 
    \draw (0,0) node {Where} (1,1) node {\underline{are}} (2,.5) node {you} (3,.2) node {go} (4,0) node{ing?}; 
\end{tikzpicture}%
\mbox{\textit{e.g. in response to ``I'm not going to the shops after all.''}}%
\end{table}


\subsection{Final attempt}

\makeatletter
\xdef\tmp{}

\def\MarkWord(#1/#2){%
\node[anchor=base west,inner sep=0pt] (a) at (\xa,#2) {#1};
\path (a.west);\pgfgetlastxy{\xa}{\ya}
 \xdef\tmp{\tmp(\xa,#2)}%
\path (a.base east) -- ++(1 ex,0) coordinate (a);
\path (a);
\pgfgetlastxy{\xa}{\ya}
\global\let\xa\xa
}

\def\util@empty{}

\def\multiwords#1 #2\@nil{%
 \protected@edef\@temp{%
   \noexpand\MarkWord(#1)}\@temp
   \def\@nextArg{#2}%  
    \ifx\util@empty\@nextArg
       \let\next\@gobble
     \fi
   \next#2\@nil
    }

\def\MarkWords#1{% 
  \begingroup
    \path (0,0) coordinate (a);
    \xdef\xa{0}
   \let\next\multiwords
   \next#1 \@nil %    
\endgroup 
} 
\makeatother

\begin{tikzpicture}
    \MarkWords{Where/1 are/.2 you/.2 go/.6 ing?/0}
   \path[draw,red] (0,0) plot[smooth] coordinates{\tmp};
\end{tikzpicture}
\end{document}

enter image description here

Alain Matthes
  • 95,075
  • Thanks for these. 1.1: anchor=base - terrific! 1.2: Thanks for explaining that - I can now see that if I use \draw node[draw] in the ``words on different levels'' code, I get a box. This would actually be useful for highlighting particular words. 1.3: The new version is a great way of combining the contour and the text, which I hadn't thought of before. After testing, it's quite versatile and looks good. – donnek Apr 11 '13 at 16:12