% Tonal Space Package, created by Mark Granroth-Wilding 2010
%
% Uses PGF/Tikz to do the drawings.
%
% The following commands can be used to create a tonal space with paths 
% on it:
%  \tspath(x0,y0)(x1,y1){ ... }
%     Sets up a tonal space for putting paths on. The dimensions specify
%     the size of the space in grid squares. The argument should contain
%     the commands below for the points in the space.
%  \tspoint(x,y)
%     Puts a point on a path. By default, this is connected by an arrow
%     from the previous point.
%  \tsgap
%     Do not put a connecting arrow between the previous point and the 
%     next.
%
\ProvidesPackage{tonalspace}[2010/09/29 Tonal Space drawing package by Mark Granroth-Wilding.]

\RequirePackage{tikz}
\RequirePackage{calc}

%%%%%%%%%%%%%%%%% Declarations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Avoid nesting and allow us to restrict TS points to be in a TS
\newif\if@intonalspace
\@intonalspacefalse
% Counters needed for computations
\newcount\@tsx
\newcount\@tsy
\newcount\@tsendx
\newcount\@tsendy
\newcount\@tsstarty
\newcounter{spacex}
\newcounter{spacey}
\newcounter{localx}
\newcounter{localy}
\newcount\@tsloop
\newlength{\tsradius}
% Store the coordinates of the last point (for drawing arrows)
\newcount\@tslastx
\newcount\@tslasty
\newif\if@tspathconnect
\global\@tspathconnectfalse

% Set the length \tsgridsize to scale the TS grid.
% TODO: this is not fully implemented yet
\newlength{\tsgridsize}
\setlength{\tsgridsize}{1cm}

%%%%%%%%%%%%%%%%% Internal macros %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% A loop construct that can be nested (thanks to Pawel Jackowski)
\long\def\loop#1\repeat{%
\iterate\gobbleone{#1}}
\long\def\iterate\gobbleone#1{%
#1\expandafter\iterate\fi
\gobbleone{#1}}
\long\def\gobbleone#1{}

% Convert a (1D) coordinate (#2) into the appropriate grid position.
% Assigns the result to the given escape sequence (#1)
\def\inner@coord2grid#1#2#3{
	\ifnum #2 = -1
		\edef#1{-0.#3}
	\else
		\ifnum #2 < 0
			\count255=#2
			\advance\count255 by 1
			\edef#1{\the\count255.#3}
		\else
			\edef#1{#2.#3}
		\fi
	\fi
}
\def\@coord2grid#1#2{
	\inner@coord2grid{#1}{#2}{5}
}
\def\@topcoord2grid#1#2{
	\ifnum #2 < 0
		\inner@coord2grid{#1}{#2}{2}
	\else
		\inner@coord2grid{#1}{#2}{8}
	\fi
}
\def\@bottomcoord2grid#1#2{
	\ifnum #2 < 0
		\inner@coord2grid{#1}{#2}{8}
	\else
		\inner@coord2grid{#1}{#2}{2}
	\fi
}


%%%%%%%%%%%%%%%%% Public macros %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Tonal space path: prepares a grid for putting a path on
\def\tspath(#1,#2)(#3,#4)#5{%
	% Don't allow nesting
	\if@intonalspace\errmessage{Cannot nest tonal spaces}\else{%
		\@intonalspacetrue%
		\begin{center}%
		\begin{tikzpicture}%
			\draw[style=help lines,step=\tsgridsize] (#1,#2) grid (#3,#4);%
			% Take the lowest x and the starting point
			\ifnum #1 < #3 \@tsx #1 \@tsendx #3 \else \@tsx #3 \@tsendx #1\fi
			\ifnum #2 < #4 \@tsstarty #2 \@tsendy #4 \else \@tsstarty #4 \@tsendy #2\fi
			% Make the ranges inclusive of the beginning
			\advance\@tsx by -1
			\advance\@tsstarty by -1
			% Loop over the rows and columns drawing the names
			\loop{
				% New row
				\@tsy \the\@tsstarty
				\advance\@tsx by 1
				\ifnum \@tsx < \@tsendx
					\loop
						\advance\@tsy by 1
						\ifnum \@tsy < \@tsendy
							% Compute the label position
							%% X
							\@coord2grid{\@xval}{\the\@tsx}
							%% Y
							\@coord2grid{\@yval}{\the\@tsy}
							% Find which space we're in
							\setcounter{spacex}{(\the\@tsy + (2*\the\@tsx) + 2)}
							\setcounter{spacey}{((4*\the\@tsy) + \the\@tsx + 1)}
							%%% This is to deal with the fact that int division 
							%%% truncates rather than rounding down
							\ifnum \the\value{spacex} < 0
								\setcounter{spacex}{(\the\value{spacex} + 1) / 7 - 1}
							\else
								\setcounter{spacex}{\the\value{spacex} / 7}
							\fi
							\ifnum \the\value{spacey} < 0
								\setcounter{spacey}{(\the\value{spacey} + 1) / 7 - 1}
							\else
								\setcounter{spacey}{\the\value{spacey} / 7}
							\fi
							% Find the coordinate relative to the space
							\setcounter{localx}{\the\@tsx - 4*\the\value{spacex} + \the\value{spacey} + 1}
							\setcounter{localy}{\the\@tsy - 2*\the\value{spacey} + \the\value{spacex}}
							% Work out what basic label to use
							\def\@tssymb{%
								\ifcase\the\value{localy}%
									\ifcase\the\value{localx}%
										IV\or I\or V\or II%
									\fi%
								\or%
									\ifcase\the\value{localx}%
										VI\or III\or VII%
									\fi%
								\fi}
							% Add +s and -s for the x-spaces
							\def\@tsdetune{%
								\ifnum \the\value{spacex} < 0%
									% Add -s
									\@tsloop=0%
									\loop%
										\advance\@tsloop by -1%
										\ifnum \@tsloop > \the\value{spacex}%
											-%
									\repeat%
								\else\ifnum \the\value{spacex} > 0%
									% Add +s
									\@tsloop=0%
									\loop%
										\advance\@tsloop by 1%
										\ifnum \@tsloop < \the\value{spacex}%
											+%
									\repeat%
								\fi\fi}
							% Add sharps and flats for the y-spaces
							\def\@tsaccid{%
								\ifnum \the\value{spacey} < 0%
									% Add flats
									\@tsloop=0%
									\loop%
										\advance\@tsloop by -1%
										\ifnum \@tsloop > \the\value{spacey}%
											\flat%
									\repeat%
								\else\ifnum \the\value{spacey} > 0%
									% Add sharps
									\@tsloop=0%
									\loop%
										\advance\@tsloop by 1%
										\ifnum \@tsloop < \the\value{spacey}%
											\sharp%
									\repeat%
								\fi\fi}
							% The actual label
							\node at (\@xval,\@yval) {$\@tsaccid\@tssymb^{\@tsdetune}$};
						\repeat
				}%
			\repeat
			#5
		\end{tikzpicture}%
		\end{center}%
		\@intonalspacefalse%
	}\fi%
}

% Tonal space point: puts a point on a tonal space path
\def\tspoint(#1,#2){%
	% Only allow this within a tonalspace path
	\if@intonalspace{%
		% Work out the grid position
		\@coord2grid{\@xpos}{#1}
		\@coord2grid{\@ypos}{#2}
		% Draw a circle around the label
		\setlength{\tsradius}{\the\tsgridsize * 9 / 20}
		\draw (\@xpos,\@ypos) circle (\the\tsradius);
		% If a previous point has been drawn, draw an arrow to connect them
		\if@tspathconnect
			% Work out where to draw the line from and to
			% X
			\ifnum #1 > \the\@tslastx
				\@topcoord2grid{\@tssourcex}{\the\@tslastx}
				\@bottomcoord2grid{\@tssinkx}{#1}
			\else\ifnum #1 < \the\@tslastx
				\@bottomcoord2grid{\@tssourcex}{\the\@tslastx}
				\@topcoord2grid{\@tssinkx}{#1}
			\else
				\@coord2grid{\@tssourcex}{\the\@tslastx}
				\@coord2grid{\@tssinkx}{#1}
			\fi\fi
			% Y
			\ifnum #2 > \the\@tslasty
				\@topcoord2grid{\@tssourcey}{\the\@tslasty}
				\@bottomcoord2grid{\@tssinky}{#2}
			\else\ifnum #2 < \the\@tslasty
				\@bottomcoord2grid{\@tssourcey}{\the\@tslasty}
				\@topcoord2grid{\@tssinky}{#2}
			\else
				\@coord2grid{\@tssourcey}{\the\@tslasty}
				\@coord2grid{\@tssinky}{#2}
			\fi\fi
			% Draw the connecting arrow
			\draw [->, line width=0.5mm] (\@tssourcex, \@tssourcey)--(\@tssinkx, \@tssinky);
		\fi
		% Connect to the next point if one gets drawn
		\global\@tspathconnecttrue
		\global\@tslastx=#1
		\global\@tslasty=#2
	}\else\errmessage{You can only use a tspoint within a tonal space (use tspath)}\fi%
}

% Leave a gap between the last point on the path and the next one
\def\tsgap{
	\global\@tspathconnectfalse
}
