//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Feb 19 22:25:22 PST 2016
// Last Modified: Sat Feb 27 18:16:39 PST 2016
// Filename:      tools/mid2svg.cpp
// URL:           https://github.com/craigsapp/midifile/blob/master/tools/mid2svg.cpp
// Syntax:        C++11
// vim:           ts=3
//
// Description:   Convert a MIDI file into an SVG piano roll.
//

#include "MidiFile.h"
#include "Options.h"

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <sstream>

using namespace std;
using namespace smf;


void           checkOptions          (Options& opts, int argc, char* argv[]);
void           usage                 (const char* command);
void           example               (void);
void           convertMidiFileToSvg  (stringstream& output, MidiFile& midifile,
                                      Options& options);
int            hasNotes              (MidiEventList& eventlist);
void           getMinMaxPitch        (const MidiFile& midifile,
                                      int& minpitch, int &maxpitch);
void           getMinMaxTrackPitch   (const MidiEventList& evl,
                                      int& minpitch, int &maxpitch);
double         getMaxTime            (const MidiFile& midifile);
vector<double> getTrackHues          (MidiFile& midifile);
void           drawNote              (ostream& out, MidiFile& midifile,
                                      int i, int j, int dataQ,
                                      int minpitch, int maxpitch);
void           drawLines             (ostream& out, MidiFile& midifile,
                                      vector<double>& hues, Options& options);
void           printLineToNextNote   (ostream& out, MidiFile& midifile,
                                      int track, int index, Options& options);
void           drawStaves            (ostream& out, double staffwidth,
                                      const string& staffcolor,
                                      double totalduration);
int            base12ToBase7         (int pitch);
void           printDoubleClass      (ostream& out, double value);
void           makeMappings          (vector<int>& mapping,
                                      const string& mapstring);
void           drawNoteShape         (ostream& out, string& shape, double x,
                                      double y, double width, double height);
void           drawRectangle         (ostream& out, double x, double y,
                                      double width, double height);
void           drawDiamond           (ostream& out, double x, double y,
                                      double width, double height);
void           drawEyelid            (ostream& out, double x, double y,
                                      double width, double height);
void           drawPlus              (ostream& out, double x, double y,
                                      double width, double height);
void           drawOval              (ostream& out, double x, double y,
                                      double width, double height);
void           drawAntiOval          (ostream& out, double x, double y,
                                      double width, double height);
void           drawRound1            (ostream& out, double x, double y,
                                      double width, double height);
void           drawRound2            (ostream& out, double x, double y,
                                      double width, double height);
void           drawRound3            (ostream& out, double x, double y,
                                      double width, double height);
void           drawRound4            (ostream& out, double x, double y,
                                      double width, double height);
void           drawAntiRound         (ostream& out, double x, double y,
                                      double width, double height);
void           drawAntiRound1        (ostream& out, double x, double y,
                                      double width, double height);
void           drawAntiRound2        (ostream& out, double x, double y,
                                      double width, double height);
void           drawAntiRound3        (ostream& out, double x, double y,
                                      double width, double height);
void           drawAntiRound4        (ostream& out, double x, double y,
                                      double width, double height);
void           drawCurvedInner       (ostream& out, double x, double y,
                                      double width, double height);
void           drawCurvedInner1      (ostream& out, double x, double y,
                                      double width, double height);
void           drawCurvedInner2      (ostream& out, double x, double y,
                                      double width, double height);
void           drawCurvedInner3      (ostream& out, double x, double y,
                                      double width, double height);
void           drawCurvedInner4      (ostream& out, double x, double y,
                                      double width, double height);
void           drawCurvedOuter       (ostream& out, double x, double y,
                                      double width, double height);
void           drawCurvedOuter1      (ostream& out, double x, double y,
                                      double width, double height);
void           drawCurvedOuter2      (ostream& out, double x, double y,
                                      double width, double height);
void           drawCurvedOuter3      (ostream& out, double x, double y,
                                      double width, double height);
void           drawCurvedOuter4      (ostream& out, double x, double y,
                                      double width, double height);
void           drawTriangleUp        (ostream& out, double x, double y,
                                      double width, double height);
void           drawTriangleDown      (ostream& out, double x, double y,
                                      double width, double height);
void           drawTriangleLeft      (ostream& out, double x, double y,
                                      double width, double height);
void           drawTriangleRight     (ostream& out, double x, double y,
                                      double width, double height);
void           drawTriangleRoundLeft (ostream& out, double x, double y,
                                      double width, double height);
void           drawTriangleRoundRight(ostream& out, double x, double y,
                                      double width, double height);
void           drawHexThick          (ostream& out, double x, double y,
                                      double width, double height);
void           drawHexThin           (ostream& out, double x, double y,
                                      double width, double height);
string         getTrackShape         (int track);
void           drawClefs             (ostream& out);
void           drawBrace             (ostream& out);

// User interface variables:
Options options;
int      dataQ        = 0;         // used with -d option
int      roundedQ     = 0;         // used with -r option
int      darkQ        = 0;         // used with --dark option
int      bwQ          = 0;         // used with --bw option
double   Scale        = 1.0;       // used with -s option
double   Border       = 2.0;       // used with -b option
double   Opacity      = 0.75;      // used with -o option
double   drumQ        = 0;         // used with --drum option
int      lineQ        = 0;         // used with -l option
int      curveQ       = 0;         // used with --cl option
int      radiusQ      = 0;         // used with --rl option
double   radius       = 1.0;       // used with --rl option
int      staffQ       = 0;         // used with --staff option
int      clefQ        = 0;         // used with --clef option
int      braceQ       = 0;         // used with --clef option
int      diatonicQ    = 0;         // used with --diatonic option
int      grandQ       = 0;         // used with --gs option
int      finalQ       = 0;         // used with -f option
int      doubleQ      = 0;         // used with --double option
int      transparentQ = 1;         // used with -T option
bool     velocitybQ   = false;     // used with -v option
double   ClefFactor   = 6;
double   StaffThickness = 2.0;     // used with --staff-width
double   LineThickness  = 2.0;     // used with --line-width
string   StaffColor   = "#555555"; // used with -staff-color
string   ClefColor    = "#555555"; // used with -clef-color
double   MaxRest      = 4.0;       // used with --max-rest option
double   EndSpace     = 0.0;       // used with -e option
int      percmapQ     = 0;         // used with --perc option
double   AspectRatio  = 2.5;       // used with -a option
vector<int> PercussionMap;         // used with --perc option
vector<string> Shapes;


//////////////////////////////////////////////////////////////////////////

int main(int argc, char* argv[]) {
	checkOptions(options, argc, argv);
	MidiFile midifile;
	if (options.getArgCount() == 0) {
		midifile.read(cin);
	} else {
		midifile.read(options.getArg(1));
	}
	stringstream notes;
	int minpitch = -1;
	int maxpitch = -1;
	getMinMaxPitch(midifile, minpitch, maxpitch);

	convertMidiFileToSvg(notes, midifile, options);

	double minx = 0;
	double miny = minpitch;
	double width = getMaxTime(midifile);
	double height = maxpitch - minpitch + 1;

	double clefwidth = 0;
	if (clefQ) {
		clefwidth = 180 / 12;
	}

	cout << "<?xml version=\"1.0\""
	     << " encoding=\"UTF-8\""
	     << " standalone=\"no\""
	     << "?>\n";
	cout << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\""
	     << " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\""
	     << ">\n";
	cout << "<svg"
	     << " version=\"1.1\""
	     << " xmlns=\"http://www.w3.org/2000/svg\""
	     << " xmlns:xlink=\"http://www.w3.org/1999/xlink\""
	     << " viewPort=\"" << minx << " " << miny  << " "
	                       << width << " " << height << "\""
	     << " viewBox=\"" << (-Border - clefwidth) * Scale << " "
	                      << -Border * Scale << " "
	                      << ((width + EndSpace) * AspectRatio + 2 * Border +
	                        clefwidth) * Scale << " "
	                      << (height + (2 * Border)) * Scale << "\""
	     << " width=\"" << ((width + EndSpace) * AspectRatio + 2 * Border +
	                        clefwidth) * Scale << "\""
	     << " height=\"" << (height + 2 * Border) * Scale << "\""
	     << ">\n";

	// Graphics setup:

	// This filter is used to show overlap between notes:
	if (transparentQ && !bwQ) {
		cout << "<filter id=\"constantOpacity\">\n";
		cout << "\t<feComponentTransfer>\n";
		cout << "\t\t<feFuncA type=\"table\" tableValues=\"0 .5 .5\" />\n";
		cout << "\t</feComponentTransfer>\n";
		cout << "</filter>\n";
	}

	if (darkQ && !bwQ) {
		cout << "<rect class=\"background\" x=\"-500%\" y=\"-500%\""
		     << " width=\"1000%\" height=\"1000%\" style=\"fill:black\" />\n";
	}

	cout << "<g"
	     << " transform=\""
	     << "scale(" << 1 * Scale * AspectRatio << ", " << -Scale << ")"
	     << " translate(0, " << -(maxpitch+1) << ")"
	     << "\" >\n";


	// Print the piano roll note boxes:
	cout << notes.str();

	// Graphics setup end closing:
	cout << "</g>\n";
	cout << "</svg>\n";
	cout << "<?mid2svg\n\t";
	for (int i=1; i<argc; i++) {
		if (strchr(argv[i], ' ') != NULL) {
			cout << '"';
		}
		cout << argv[i];
		if (strchr(argv[i], ' ') != NULL) {
			cout << '"';
		}
		if (i<argc-1) {
			cout << " ";
		}
	}
	cout << "\n?>\n";
	return 0;
}


//////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// convetMidiFileToSvg --
//

void convertMidiFileToSvg(stringstream& output, MidiFile& midifile,
		Options& options) {

	midifile.linkNotePairs();    // first link note-ons to note-offs
	midifile.doTimeAnalysis();   // then create ticks to seconds mapping

	stringstream notes;

	if (staffQ) {
		drawStaves(notes, StaffThickness, StaffColor,
				midifile.getFileDurationInSeconds());
		if (clefQ) {
			drawClefs(notes);
		}
		if (braceQ) {
			drawBrace(notes);
		}
	}

	string strokecolor = options.getString("stroke-color");
	double strokewidth = options.getDouble("stroke-width") * Scale;
	notes << "\t<g"
	      << " style=\""
	      <<   "stroke-width:" << strokewidth << ";"
	      <<   " stroke:" << strokecolor << ";"
	      << "\""
	      << ">\n";

	vector<double> trackhues = getTrackHues(midifile);

	if (lineQ) {
		drawLines(notes, midifile, trackhues, options);
	}

	int minpitch = 0;
	int maxpitch = 0;

	// Draw background for increasing contrast of notes and background
	// (needed due to constant opacity filter):
	int track = 0;
	if (!bwQ) {
		for (int i=midifile.size()-1; i>=0; i--) {
			if (!hasNotes(midifile[i])) {
				continue;
			}
			getMinMaxTrackPitch(midifile[i], minpitch, maxpitch);
			track = i;
			notes << "\t\t<g"
					<< " opacity=\"0.5\"";
			notes << " class=\"note-backdrops " << "track-" << i << "\"";
			if (trackhues[i] >= 0.0) {
				if (bwQ) {
					notes << " style=\""
					      << "fill:none;"
					      << "\"";
				 } else if (darkQ) {
					notes << " style=\""
					      << "fill:white;"
					      << "\"";
				 } else {
					notes << " style=\""
					      << "fill:black;"
					      << "\"";
				 }
			}
			notes << " >\n";
			for (int j=0; j<midifile[i].size(); j++) {
				if (!midifile[i][j].isNoteOn()) {
					continue;
				}
				if (!drumQ) {
					if (midifile[i][j].getChannel() == 0x09) {
						continue;
					}
				}
				drawNote(notes, midifile, i, j, 0, minpitch, maxpitch);
			}
			notes << "\t\t</g>\n";
		}
	}

	// draw the actual notes:
	for (int i=midifile.size()-1; i>=0; i--) {
		if (!hasNotes(midifile[i])) {
			continue;
		}
		track = i;
		getMinMaxTrackPitch(midifile[i], minpitch, maxpitch);
		notes << "\t\t<g"
		      << " class=\"track-" << track << "\"";
		if (transparentQ && !bwQ) {
			notes << " filter=\"url(#constantOpacity)\"";
		}
		if (trackhues[i] >= 0.0) {
			if (bwQ) {
				notes << " style=\""
				      << " fill:white;"
				      << "\"";
			} else {
				notes << " style=\""
				      << "opacity:" << Opacity << ";"
				      << " fill:hsl(" << trackhues[i] << ", 100%, 75%);"
				      << "\"";
			}
		}
		notes << " >\n";

		for (int j=0; j<midifile[i].size(); j++) {
			if (!midifile[i][j].isNoteOn()) {
				continue;
			}
			if (!drumQ) {
				if (midifile[i][j].getChannel() == 0x09) {
					continue;
				}
			}

			drawNote(notes, midifile, i, j, dataQ, minpitch, maxpitch);
		}
		notes << "\t\t</g>\n";
	}
	notes << "\t</g>\n";

	output << notes.str();
}



//////////////////////////////
//
// drawBrace -- Draw curley brace
//

void drawBrace(ostream& out) {
	double unscale = 2.5 / AspectRatio;
	string fill   = ClefColor;
	string stroke = ClefColor;
	if (bwQ) {
		fill   = "transparent";
	}
	double strokewidth = StaffThickness * ClefFactor;
	double xpos = 30.5;
	double ypos = -324;

	out << "<g transform=\"scale(" << unscale << ", 1)\">\n";

	out  <<
"	<g class=\"brace\" transform=\"rotate(0.2) translate(" << xpos << "," << ypos << ") scale(1,-1) scale(.0175)\">\n"
"		<path vector-effect=\"non-scaling-stroke\" stroke=\"" << stroke << "\" fill=\"" << fill << "\" stroke-width=\"" << strokewidth << "\" stroke-linejoin=\"round\" d=\"M-2031.812-22924.453\n"
"			c-24.919,88.91-36.494,171.418-39.024,279.09c-2.379,101.156,9.877,207.264,16.991,305.533\n"
"			c6.605,91.162,10.955,191.51-0.048,282.248c-2.757,22.732-8.614,43.227-13.597,63.254c-8.261,33.207-2.886,38.68,5.136,72.111\n"
"			c10.485,43.689,11.808,90.887,13.172,140.795c3.604,132-16.215,270.613-24.675,400.074\n"
"			c-8.818,134.883,3.651,255.861,34.113,366.164c-17.906-92.289-29.886-179.082-25.068-283.635\n"
"			c4.572-99.275,18.379-195.207,24.078-294.23c5.11-88.826,9.914-203.154-8.344-284.342c-4.91-21.82-10.217-42.227-15.835-62.98\n"
"			c-9.146-33.812-7.235-31.322,2.353-65.664c11.021-39.504,20.774-80.965,24.589-128.662c10.5-131.361-2.347-271.072-13.816-398.416\n"
"			c-5.957-66.148-10.878-134.006-7.34-202.238C-2055.575-22803.877-2044.086-22863.117-2031.812-22924.453L-2031.812-22924.453z\"/>\n"
"	</g>\n";

	out << "</g>\n";
}



//////////////////////////////
//
// drawClefs --
//

void drawClefs(ostream& out) {
	string fill        = ClefColor;
	string stroke      = ClefColor;
	double unscale = 2.5 / AspectRatio;
	double strokewidth = StaffThickness * ClefFactor;
	if (bwQ) {
		fill = "transparent";
	}

	out << "<g transform=\"scale(" << unscale << ", 1)\">\n";
	out  <<
"	<g vector-effect=\"non-scaling-stroke\" class=\"treble-clef\" stroke=\"" << stroke << "\" stroke-width=\"" << strokewidth << "\" fill=\"" << fill << "\" transform=\"translate(-17.25, 5.15) scale(0.06, 0.07)\">\n"
"		<path vector-effect=\"non-scaling-stroke\" d=\"M250.026,1080.111c-2.375-5.813-4.501-12.037-5.616-19.656c-1.07-7.32-1.387-15.609-1.62-23.113\n"
"			c-0.426-13.713,0.409-28.548,1.296-41.038c0.132-1.861,0.517-3.882,0.432-5.183c-0.118-1.794-1.622-3.834-2.484-5.401\n"
"			c-4.551-8.266-9.023-16.291-13.176-25.488c-3.56-7.883-6.46-16.235-8.316-27.648c-1.087-6.682-1.406-14.26-2.053-21.816"
"			c0-3.313,0-6.623,0-9.936c0.817-17.854,4.099-30.916,9.396-39.743c1.084-1.808,2.228-3.951,3.24-5.186\n"
"			c6.218-7.579,16.081-9.873,25.812-7.343c0.598-8.887,1.858-19.53,2.376-28.944c0.193-3.49,0.54-7.194,0.54-10.368\n"
"			c-0.003-13.193-3.44-20.603-7.452-26.135c-3.645-2.613-9.925-3.099-13.176,0.43c4.904-0.174,8.174,5.425,9.612,12.744\n"
"			c2.427,12.351-1.738,24.386-7.344,25.918c-8.458,2.316-13.738-12.691-11.556-28.295c1.622-11.598,6.603-18.42,13.176-19.655\n"
"			c5.073-0.956,10.149,0.426,13.608,5.399c1.988,2.858,4.009,7.685,4.968,11.879c1.246,5.448,2.18,12.212,2.16,18.792\n"
"			c-0.01,3.246-0.453,6.761-0.648,10.152c-0.614,10.689-1.579,19.729-2.376,29.812c6.095,5.437,10.941,13.432,13.392,25.704\n"
"			c1.302,6.52,2.035,15.517,1.727,23.111c-0.375,9.302-2.565,17.712-5.183,23.975c-3.863,9.248-9.852,13.985-17.604,14.257\n"
"			c-0.849,9.749-1.633,19.633-2.593,29.159c2.376,4.619,4.55,9.643,6.805,14.906c2.185,5.095,3.892,11.286,5.508,17.712\n"
"			c2.98,11.855,5.576,29.004,5.4,46.44c-0.102,10.157-1.538,19.319-3.24,27.215c-1.691,7.847-3.557,15.81-6.912,20.089\n"
"			c-0.36,0-0.72,0-1.08,0C254.113,1090.306,252.068,1085.104,250.026,1080.111z M248.19,997.6\n"
"			c-1.362,15.429-0.295,36.475,2.7,48.384c1.367,5.441,2.426,10.329,4.428,15.122c0.854,2.04,3.006,5.909,4.104,5.83\n"
"			c2.104-0.153,3.064-9.245,3.24-13.824c0.542-14.092-2.623-28.387-5.832-37.366C254.247,1008.515,250.911,1002.572,248.19,997.6z\n"
"			M249.27,935.823c-6.565-5.809-13.02-17.569-13.716-34.776c-0.425-10.517,1.463-18.693,3.888-24.624\n"
"			c1.422-3.478,3.789-6.692,6.156-9.072c1.287-1.293,4.09-3.666,3.996,1.296c-0.048,2.584-2.504,4.06-3.888,6.263\n"
"			c-2.557,4.079-4.489,12.399-3.348,21.385c0.481,3.784,1.636,7.983,2.808,10.153c1.458,2.697,3.725,4.34,6.048,5.615\n"
"			c1.987-19.137,3.451-39.323,5.292-58.753c-4.493-1.347-9.224-1.49-13.068-0.213c-6.922,2.298-11.753,12.045-14.256,23.111\n"
"			c-1.838,8.128-2.944,20.546-1.837,31.752c0.529,5.353,1.939,11.271,3.348,15.984c4.113,13.751,11.055,26.606,16.308,36.289\n"
"			C247.971,952.523,248.466,943.862,249.27,935.823z M260.286,855.473c-1.479,19.361-3.545,38.701-4.86,57.671\n"
"			c5.07-0.638,9.023-3.541,11.556-9.718c4.25-10.369,3.378-29.217-0.648-39.53C264.924,860.283,262.694,856.868,260.286,855.473z\"/>\n"
"	</g>\n";

	out <<
"	<g class=\"bass-clef\" fill=\"" << fill << "\" stroke=\"" << stroke << "\" transform=\"scale(1.02, 1) translate(-0.25, 0.25)\" stroke-width=\"" << strokewidth << "\">\n"
"		<g transform=\"matrix(0.1835537,0,0,0.1830159,-98.297967,-1128.8415)\">\n"
"			<path vector-effect=\"non-scaling-stroke\" d=\"M514.308,6407.837c3.419,4.086,5.655,9.64,7.124,12.803\n"
"				c2.204,4.648,4.046,10.097,5.538,16.384c1.492,6.287,2.238,13.068,2.238,20.385c0,4.115-0.338,7.925-0.991,11.507\n"
"				c-0.653,3.543-1.527,6.4-2.647,8.459c-1.108,2.095-2.297,3.125-3.545,3.125c-1.142,0-2.262-0.687-3.369-2.135\n"
"				c-1.119-1.41-2.029-3.505-2.752-6.325c-0.711-2.819-1.073-6.135-1.073-9.982c0-3.011,0.326-5.411,0.968-7.317\n"
"				c0.653-1.867,1.446-2.78,2.379-2.78c0.513,0,0.991,0.342,1.422,1.104c0.431,0.724,0.769,1.753,1.014,3.011\n"
"				c0.245,1.257,0.373,2.628,0.373,4.077c0,2.019-0.245,3.733-0.735,5.144c-0.501,1.41-1.049,2.132-1.667,2.132\n"
"				c-0.21,0-0.466-0.152-0.781-0.38c-0.303-0.229-0.501-0.342-0.571-0.342c-0.14,0-0.292,0.152-0.466,0.495\n"
"				c-0.175,0.342-0.268,0.799-0.292,1.41c0.07,0.495,0.117,0.838,0.117,1.104c0.338,2.896,0.944,4.953,1.796,6.249\n"
"				c0.863,1.258,1.784,1.905,2.763,1.905c1.014,0,1.83-0.953,2.46-2.858c0.63-1.943,1.073-4.344,1.329-7.277\n"
"				c0.268-2.896,0.396-5.981,0.396-9.22c-0.035-3.468-0.221-7.125-0.583-10.897c-0.361-3.81-0.804-7.163-1.317-10.059\n"
"				c-0.653-3.925-1.458-7.468-2.414-10.593c-0.956-3.124-1.889-5.752-2.775-7.849c-0.886-2.133-2.731-5.973-4.104-9.297\n"
"				C514,6409.468,514.126,6407.62,514.308,6407.837z\"/>\n"
"			<path vector-effect=\"non-scaling-stroke\" d=\"M531.259,6465.557c0.313-0.979,0.705-1.415,1.189-1.415\n"
"				c0.457,0,0.862,0.436,1.228,1.306c0.339,0.908,0.522,2.032,0.522,3.339c0,1.233-0.17,2.358-0.522,3.302\n"
"				c-0.353,0.979-0.745,1.488-1.163,1.488c-0.483,0-0.888-0.436-1.215-1.343c-0.34-0.871-0.509-1.958-0.509-3.266\n"
"				C530.789,6467.626,530.945,6466.5,531.259,6465.557z\"/>\n"
"			<path vector-effect=\"non-scaling-stroke\" d=\"M531.272,6447.847c0.327-0.871,0.732-1.342,1.241-1.342\n"
"				c0.457,0,0.849,0.471,1.189,1.378c0.327,0.908,0.497,2.033,0.497,3.447c0,1.162-0.183,2.214-0.522,3.193\n"
"				c-0.366,0.98-0.745,1.452-1.163,1.452c-0.509,0-0.914-0.436-1.241-1.343c-0.313-0.87-0.483-1.996-0.483-3.301\n"
"				C530.789,6449.879,530.958,6448.717,531.272,6447.847z\"/>\n"
"		</g>\n"
"	</g>\n";

	out << "</g>\n";

}



//////////////////////////////
//
// drawStaves --
//

void drawStaves(ostream& out, double staffwidth, const string& staffcolor,
		double totalduration) {
	vector<double> vpos;
	double unscale = 2.5 / AspectRatio;
	if (diatonicQ) {
		vpos.insert(vpos.end(), {37.5, 39.5, 41.5, 43.5, 45.5}); // treble clef
		vpos.insert(vpos.end(), {25.5, 27.5, 29.5, 31.5, 33.5}); // bass clef
	} else {
		vpos.insert(vpos.end(), {64.5, 67.5, 71.5, 74.5, 77.5}); // treble clef
		vpos.insert(vpos.end(), {43.5, 47.5, 50.5, 53.5, 57.5}); // bass clef
	}

	out << "\t<g"
	    << " class=\"staff-lines\""
	    << " stroke-width=\"" << staffwidth << "\""
	    << " stroke=\"" << staffcolor << "\""
	    << ">\n";

	double start = 0.0;
	if (clefQ) {
		start = -4.65 * unscale;
	}
	double endx = totalduration + EndSpace;
	for (int i=0; i<(int)vpos.size(); i++) {
		out << "\t\t<path vector-effect=\"non-scaling-stroke\""
		    << " d=\"M" << start << " " << vpos[i]
		    << " L" << endx << " " << vpos[i] << "\" />\n";
	}

	double maxy = *max_element(vpos.begin(), vpos.end());
	double miny = *min_element(vpos.begin(), vpos.end());
	double thickness = 0.5 * unscale;
	if (finalQ) {
		out << "\t\t<path stroke=\"#cccccc\" fill=\"#cccccc\""
		    << " d=\"M" << endx << "," << miny
		    << " L"  << endx << "," << maxy
		    << " L"  << endx - thickness << "," << maxy
		    << " L"  << endx - thickness << "," << miny
		    << " z"
		    << "\"/>\n";

		out << "\t\t<path stroke=\"" << StaffColor << "\" fill=\"" << StaffColor << "\""
		    << " d=\"M" << endx-thickness-thickness/2.0 << "," << miny
		    << " L" << endx-thickness-thickness/2.0 << "," << maxy
		    << "\"/>\n";
	} else if (doubleQ) {
		out << "\t\t<path vector-effect=\"non-scaling-stroke\""
		    << " d=\"M" << endx-thickness << "," << miny
		    << " L" << endx-thickness << "," << maxy
		    << "\"/>\n";
		out << "\t\t<path vector-effect=\"non-scaling-stroke\""
		    << " d=\"M" << endx << "," << miny
		    << " L" << endx << "," << maxy
		    << "\"/>\n";
	}

	if (braceQ) {
		// staffwidth = 5 * staffwidth;
		out << "\t\t<path vector-effect=\"non-scaling-stroke\""
		    << " d=\"M" << start << "," << miny
		    << " L"  << start << "," << maxy
		    << " z"
		    << "\"/>\n";
	}

	out << "\t</g>\n";
}



//////////////////////////////
//
// drawLines -- Draw lines to connect notes.
//

void drawLines(ostream& out, MidiFile& midifile, vector<double>& hues,
		Options& options) {
	int dashing = options.getBoolean("dash");
	int track = 0;
	for (int i=midifile.size()-1; i>=0; i--) {
		if (!hasNotes(midifile[i])) {
			continue;
		}
		track = i;
		string color = "hsl(" + to_string(hues[i]) + ", 100%, 75%)";
		if (bwQ) {
			color = StaffColor;
		}
		out << "\t\t<g"
		    << " class=\"note-lines track-" << track << "\""
		    << " fill=\"none\" stroke=\"" << color << "\"";

		// double scale = options.getDouble("scale");
		if (dashing) {
			double dwidth = 2.25;
			out << " stroke-dasharray=\"" << dwidth << "\"";
			out << " vector-effect=\"non-scaling-stroke\"";
		}
		out << " stroke-width=\""<< LineThickness << "\">\n";
		for (int j=0; j<midifile[i].size(); j++) {
			if (!midifile[i][j].isNoteOn()) {
				continue;
			}
			printLineToNextNote(out, midifile, i, j, options);
		}
		out << "\t\t</g>\n";
	}
}



//////////////////////////////
//
// printLineToNextNote --
//

void printLineToNextNote(ostream& out, MidiFile& midifile, int track,
		int index, Options& options) {
	int p1 = midifile[track][index].getP1();
	if (midifile[track][index].getChannel() == 9) {
		p1 = PercussionMap[p1];
	}
	if (diatonicQ) {
		p1 = base12ToBase7(p1);
	}
	double endtime = midifile[track][index].seconds
			+ midifile[track][index].getDurationInSeconds();

	stringstream path;

	int nextindex = -1;
	for (int i=index+1; i<midifile[track].size(); i++) {
		if (!midifile[track][i].isNoteOn()) {
			continue;
		}
		if (midifile[track][i].seconds >= endtime) {
			nextindex = i;
			break;
		}
	}

	if (nextindex < 0) {
		return;
	}

	int p2 = midifile[track][nextindex].getP1();
	if (midifile[track][nextindex].getChannel() == 9) {
		p2 = PercussionMap[p2];
	}
	if (diatonicQ) {
		p2 = base12ToBase7(p2);
	}
	double nextstarttime = midifile[track][nextindex].seconds;
	double difference = nextstarttime - endtime;
	if (difference > MaxRest) {
		// don't connect notes after long rests.
		return;
	}

	double Ax = endtime;
	double Ay = p1 + 0.5;
	double Bx = nextstarttime;
	double By = p2 + 0.5;
	double Cx = nextstarttime;
	double Cy = p1 + 0.5;

	double tradius = radius;
	if (tradius > fabs(Cx - Ax)) {
		tradius = fabs(Cx - Ax);
	}
	if (tradius > fabs(By - Cy)) {
		tradius = fabs(By - Cy);
	}

	double Rx = tradius / AspectRatio;
	double Ry = tradius;

	double Dx = Cx;
	double Dy = Cy + Ry;
	double Dxn = Cx;
	double Dyn = Cy - Ry;
	double Ex = Cx - Rx;
	double Ey = Cy;

	if (radiusQ && (radius > 0)) {
	if (Ay < By) {
		path << " M" << Ax << " " << Ay;
		path << " L" << Ex << " " << Ey;
		path << " A" << Rx << " " << Ry << " 0 0 1 ";
		path << Dx << " " << Dy;
		path << " L" << Bx << " " << By;
	} else {
		path << " M" << Ax << " " << Ay;
		path << " L" << Ex << " " << Ey;
		path << " A" << Rx << " " << Ry << " 0 0 0 ";
		path << Dxn << " " << Dyn;
		path << " L" << Bx << " " << By;
	}
	} else if (curveQ) {
		if (difference > 0.0) {
			path << " M" << Ax << "," << Ay;
			path << " Q" << Cx << "," << Cy;
			path << " " << Bx << " " << By;
		} else {
			// vertical line:
			path << " M" << Cx << " " << Cy;
			path << " L" << Bx << " " << By;
		}
	} else {
		if (difference > 0.0) {
			// there is a rest so extent the line horizontally
			path << " M" << Ax << " " << Ay;
			path << " L" << Cx << " " << Cy;
			path << " L" << Bx << " " << By;
		} else {
			// vertical line:
			path << " M" << Cx << " " << Cy;
			path << " L" << Bx << " " << By;
		}
	}


	out << "\t\t\t<path ";
	out << " vector-effect=\"non-scaling-stroke\"";
	out << " stroke-linejoin=\"round\"";
	out << " d=\"" << path.str() << "\" />\n";
}



//////////////////////////////
//
// drawNote --
//

void drawNote(ostream& out, MidiFile& midifile, int i, int j, int dataQ,
		int minpitch, int maxpitch) {
	int tickstart, tickend, tickdur;
	double starttime, endtime, duration;
	int height = 1;
	tickstart  = midifile[i][j].tick;
	starttime  = midifile[i][j].seconds;
	if (midifile[i][j].isLinked()) {
		tickdur  = midifile[i][j].getTickDuration();
		tickend  = tickstart + tickdur;
		duration = midifile[i][j].getDurationInSeconds();
		endtime  = starttime + duration;
	} else {
		tickdur = 0;
		tickend = tickstart;
		duration = 0.0;
		endtime = starttime;
	}
	int pitch    = midifile[i][j].getP1();
	if (midifile[i][j].getChannel() == 9) {
		pitch = PercussionMap[pitch];
	}
	int pitch12  = pitch;
	if (diatonicQ) {
		pitch = base12ToBase7(pitch);
	}
	int velocity = midifile[i][j].getP2();
	int channel  = midifile[i][j].getChannel();  // 0-offset
	int track    = i;                            // 0-offset

	if (dataQ) {
		out << "\t\t\t<!-- ==========================================" << endl;
		out << "\t\t\t\t@Track:      " << track                        << endl;
		out << "\t\t\t\t@Pitch:      " << pitch                        << endl;
		out << "\t\t\t\t@Minpitch:   " << minpitch                     << endl;
		out << "\t\t\t\t@Maxpitch:   " << maxpitch                     << endl;
		out << "\t\t\t\t@Velocity:   " << velocity                     << endl;
		out << "\t\t\t\t@Channel:    " << channel                      << endl;
		out << "\t\t\t\t@Start-tick: " << tickstart << " ticks"        << endl;
		out << "\t\t\t\t@End-tick:   " << tickend   << " ticks"        << endl;
		out << "\t\t\t\t@Tick-dur:   " << tickdur   << " ticks"        << endl;
		out << "\t\t\t\t@Start-time: " << starttime << " seconds"      << endl;
		out << "\t\t\t\t@End-time:   " << endtime   << " seconds"      << endl;
		out << "\t\t\t\t@Duration:   " << duration  << " seconds"      << endl;
		out << "\t\t\t=========================================== -->" << endl;
	}


	// note box:
	out << "\t\t\t<g";
	if (velocitybQ) {
		double bright = velocity / 127.0;
		bright -= 0.2;
		if (bright < 0.0) {
			bright = 0.0;
		}
		bright *= 100;
		bright = int(bright);
		double hue = 0;
		out << " fill=\"hsl(";
		out << hue;
		out << ",100%,";
		out << bright;
		out << "%)\"";
	}
	out << " class=\"note key-" << pitch12;

	out << " ont-";
	printDoubleClass(out, starttime);

	out << " offt-";
	printDoubleClass(out, starttime + duration);

	if (pitch <= minpitch) {
		out << " minima";
	}
	if (pitch >= maxpitch) {
		out << " maxima";
	}
	out << "\"";
	out << ">\n";

	// string shape = "diamond";
	// string shape = "rectangle";
	// string shape = "eyelid";
	// string shape = "hexthin";
	// string shape = "hexthick";
	// string shape = "plus";
	// string shape = "round1";
	// string shape = "round2";
	// string shape = "round3";
	// string shape = "round4";
	// string shape = "oval";
	// string shape = "antioval";
	// string shape = "antiround";
	// string shape = "antiround1";
	// string shape = "antiround2";
	// string shape = "antiround3";
	// string shape = "antiround4";
	// string shape = "triangleup";
	// string shape = "triangledown";
	// string shape = "triangleleft";
	// string shape = "triangleright";
	// string shape = "triangleroundleft";
	// string shape = "triangleroundright";
	// string shape = "curvedinner";
	// string shape = "curvedinner1";
	// string shape = "curvedinner2";
	// string shape = "curvedinner3";
	// string shape = "curvedinner4";
	// string shape = "curvedouter";
	// string shape = "curvedouter1";
	// string shape = "curvedouter2";
	// string shape = "curvedouter3";
	// string shape = "curvedouter4";
	string shape = getTrackShape(track);
	drawNoteShape(out, shape, starttime, pitch, duration, height);

	out << "\t\t\t</g>\n";
}



//////////////////////////////
//
// getTrackShape --
//

string getTrackShape(int track) {
	if (track < 0) {
		track = 0;
	}
	if (track > 0) {
		track = track - 1;
	}
	if (track < (int)Shapes.size()) {
		return Shapes[track];
	} else {
		return "rectangle";
	}
}



//////////////////////////////
//
// drawNoteShape -- Draw the desired note shape.
//

void drawNoteShape(ostream& out, string& shape, double x, double y,
		double width, double height) {
	if (shape == "rectangle") {
		drawRectangle(out, x, y, width, height);
	} else if (shape == "diamond") {
		drawDiamond(out, x, y, width, height);
	} else if (shape == "eyelid") {
		drawEyelid(out, x, y, width, height);
	} else if (shape == "hexthin") {
		drawHexThin(out, x, y, width, height);
	} else if (shape == "hexthick") {
		drawHexThick(out, x, y, width, height);
	} else if (shape == "plus") {
		drawPlus(out, x, y, width, height);
	} else if (shape == "round1") {
		drawRound1(out, x, y, width, height);
	} else if (shape == "round2") {
		drawRound2(out, x, y, width, height);
	} else if (shape == "round3") {
		drawRound3(out, x, y, width, height);
	} else if (shape == "round4") {
		drawRound4(out, x, y, width, height);
	} else if (shape == "oval") {
		drawOval(out, x, y, width, height);
	} else if (shape == "antioval") {
		drawAntiOval(out, x, y, width, height);
	} else if (shape == "antiround") {
		drawAntiRound(out, x, y, width, height);
	} else if (shape == "antiround1") {
		drawAntiRound1(out, x, y, width, height);
	} else if (shape == "antiround2") {
		drawAntiRound2(out, x, y, width, height);
	} else if (shape == "antiround3") {
		drawAntiRound3(out, x, y, width, height);
	} else if (shape == "antiround4") {
		drawAntiRound4(out, x, y, width, height);
	} else if (shape == "curvedinner") {
		drawCurvedInner(out, x, y, width, height);
	} else if (shape == "curvedinner1") {
		drawCurvedInner1(out, x, y, width, height);
	} else if (shape == "curvedinner2") {
		drawCurvedInner2(out, x, y, width, height);
	} else if (shape == "curvedinner3") {
		drawCurvedInner3(out, x, y, width, height);
	} else if (shape == "curvedinner4") {
		drawCurvedInner4(out, x, y, width, height);
	} else if (shape == "curvedouter") {
		drawCurvedOuter(out, x, y, width, height);
	} else if (shape == "curvedouter1") {
		drawCurvedOuter1(out, x, y, width, height);
	} else if (shape == "curvedouter2") {
		drawCurvedOuter2(out, x, y, width, height);
	} else if (shape == "curvedouter3") {
		drawCurvedOuter3(out, x, y, width, height);
	} else if (shape == "curvedouter4") {
		drawCurvedOuter4(out, x, y, width, height);
	} else if (shape == "triangleup") {
		drawTriangleUp(out, x, y, width, height);
	} else if (shape == "triangledown") {
		drawTriangleDown(out, x, y, width, height);
	} else if (shape == "triangleleft") {
		drawTriangleLeft(out, x, y, width, height);
	} else if (shape == "triangleright") {
		drawTriangleRight(out, x, y, width, height);
	} else if (shape == "triangleroundleft") {
		drawTriangleRoundLeft(out, x, y, width, height);
	} else if (shape == "triangleroundright") {
		drawTriangleRoundRight(out, x, y, width, height);
	} else {
		drawRectangle(out, x, y, width, height);
	}
}



//////////////////////////////
//
// drawDiamond --
//

void drawDiamond(ostream& out, double x, double y, double width,
		double height) {
	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";

	out << "M "  << x           << " " << y+height/2.0;
	out << " L " << x+width/2.0 << " " << y;
	out << " L " << x+width     << " " << y+height/2.0;
	out << " L " << x+width/2.0 << " " << y+height;
	out << " z";
	out << "\"";
	out << "/>\n";

	//if (roundedQ) {
	//   out << "\trx=\""  << 1      << "\""
	//       << "\try=\""  << 1      << "\"";
	//}
}



//////////////////////////////
//
// drawEyelid --
//

void drawEyelid(ostream& out, double x, double y, double width,
		double height) {
	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << "M "  << x           << " " << y+height/2.0;
	out << " Q " << x+width/2.0 << " " << y+height;
	out << " "   << x+width     << " " << y+height/2.0;
	out << " Q " << x+width/2.0 << " " << y;
	out << " "   << x           << " " << y+height/2.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawHexThin --
//

void drawHexThin(ostream& out, double x, double y, double width,
		double height) {
	double& h = height;
	double& w = width;
	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << "M "  << x           << " " << y+h/3.0;
	out << " L " << x           << " " << y+h*2.0/3.0;
	out << " L " << x+w/2.0     << " " << y+h;
	out << " L " << x+w         << " " << y+h*2.0/3.0;
	out << " L " << x+w         << " " << y+h/3.0;
	out << " L " << x+w/2.0     << " " << y;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawHexThick --
//

void drawHexThick(ostream& out, double x, double y, double width,
		double height) {
	double& h = height;
	double& w = width;
	double maxbevel = h/2.0/AspectRatio;
	if (width*AspectRatio < height) {
		maxbevel = w/2.0/AspectRatio;
	}
	double& b = maxbevel;
	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << "M "  << x           << " " << y+h/2.0;
	out << " L " << x+b         << " " << y+h;
	out << " L " << x+w-b       << " " << y+h;
	out << " L " << x+w         << " " << y+h/2.0;
	out << " L " << x+w-b       << " " << y;
	out << " L " << x+b         << " " << y;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawRectangle --
//

void drawRectangle(ostream& out, double x, double y, double width,
		double height) {
	out << "\t\t\t\t<rect vector-effect=\"non-scaling-stroke\"";
	if (roundedQ) {
		out << "\trx=\""  << 1      << "\""
	    << "\try=\""  << 1      << "\"";
	}
	out << "\tx=\""      << x      << "\""
	    << "\ty=\""      << y      << "\""
	    << "\twidth=\""  << width  << "\""
	    << "\theight=\"" << height << "\""
	    << " />\n";
}



//////////////////////////////
//
// drawTriangleUp --
//

void drawTriangleUp(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << "M " << x << " " << y;
	out << " L " << x+w/2 << " " << y2;
	out << " L " << x2 << " " << y;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawTriangleDown --
//

void drawTriangleDown(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << "M " << x << " " << y2;
	out << " L " << x+w/2 << " " << y;
	out << " L " << x2 << " " << y2;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawTriangleLeft --
//

void drawTriangleLeft(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << "M " << x << " " << y+h/2;
	out << " L " << x2 << " " << y2;
	out << " L " << x2 << " " << y;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawTriangleRight --
//

void drawTriangleRight(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << "M " << x2 << " " << y+h/2;
	out << " L " << x << " " << y2;
	out << " L " << x << " " << y;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawTriangleRoundLeft --
//

void drawTriangleRoundLeft(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double  a = AspectRatio;
	double wa = w * a;
	double x2 = x+w;
	double  r = h/2.0;
	double  q = sqrt(wa*wa+r*r);
	double ww = (w*a*q-w*a*r)/q;
	double rr = (r*q-r*r)/q;
	double xa = x + ww/a;
	double ya = y + r + rr;
	double xb = xa;
	double yb = y + r - rr;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << "M " << x << " " << y+r;
	out << " L " << xa << " " << ya;
	out << " A " << r << " " << r << " 0 0 0 " << x2 << " " << y+r;
	out << " A " << r << " " << r << " 0 0 0 " << xb << " " << yb;
	out << " z\"";
	out << "/>\n";

}



//////////////////////////////
//
// drawTriangleRoundRight --
//

void drawTriangleRoundRight(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << "M " << x << " " << y+h/2;
	out << " L " << x2 << " " << y2;
	out << " L " << x2 << " " << y;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawPlus --
//

void drawPlus(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " L " << x+d         << " " << y+h*3.0/4.0;
	out << " L " << x+d         << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " L " << x2-d        << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " L " << x2-d        << " " << y+h/4.0;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " L " << x+d         << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawCurvedInner --
//

void drawCurvedInner(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/4;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawCurvedInner1 --
//

void drawCurvedInner1(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/4;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2 << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawCurvedInner2 --
//

void drawCurvedInner2(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/4;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawCurvedInner3 --
//

void drawCurvedInner3(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/4;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2 << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawCurvedInner4 --
//

void drawCurvedInner4(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/4;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2 << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawCurvedOuter --
//

void drawCurvedOuter(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/4;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2 << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawCurvedOuter1 --
//

void drawCurvedOuter1(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/4;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x           << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawCurvedOuter2 --
//

void drawCurvedOuter2(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/4;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2 << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawCurvedOuter3 --
//

void drawCurvedOuter3(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/4;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawCurvedOuter4 --
//

void drawCurvedOuter4(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/4;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+h/4.0;
	out << " L " << x           << " " << y+h*3.0/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h*3.0/4.0;
	out << " L " << x2          << " " << y+h/4.0;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x << " " << y+h/4.0;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawOval --
//

void drawOval(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M "  << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawAntiOval --
//

void drawAntiOval(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = 2.0;
	if (d > w / 3) {
		d = w/3;
	}
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawAntiRound --
//

void drawAntiRound(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = w/2.0;
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawAntiRound1 --
//

void drawAntiRound1(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = w/2.0;
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawAntiRound2 --
//

void drawAntiRound2(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = w/2.0;
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawAntiRound3 --
//

void drawAntiRound3(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = w/2.0;
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawAntiRound4 --
//

void drawAntiRound4(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = w/2.0;
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawRound1 --
//

void drawRound1(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = w/2.0;
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawRound2 --
//

void drawRound2(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = w/2.0;
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawRound3 --
//

void drawRound3(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = w/2.0;
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// drawRound4 --
//

void drawRound4(ostream& out, double x, double y, double width, double height) {
	double& h = height;
	double& w = width;
	double x2 = x+w;
	double y2 = y+h;
	double m  = h/2.0;
	double d  = w/2.0;
	double Rx  = d;
	double Ry  = h/2;

	out << "\t\t\t\t<path vector-effect=\"non-scaling-stroke\" d=\"";
	out << " M " << x          << " " << y+m;
	out << " L " << x           << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x+d << " " << y2;
	out << " L " << x2-d        << " " << y2;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2 << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " L " << x2          << " " << y+m;
	out << " A " << Rx << " " << Ry << " 0 0 0 " << x2-d << " " << y;
	out << " L " << x2-d        << " " << y;
	out << " L " << x+d         << " " << y;
	out << " A " << Rx << " " << Ry << " 0 0 1 " << x << " " << y+m;
	out << " z\"";
	out << "/>\n";
}



//////////////////////////////
//
// base12ToBase7 -- MIDI to diatonic pitch name. Middle C is 5th octave.
//

int base12ToBase7(int pitch) {
	int octave = pitch / 12;
	int chroma = pitch % 12;
	int output = 0;
	switch (chroma) {
		case  0: output = 0; break; // C
		case  1: output = 0; break; // C#
		case  2: output = 1; break; // D
		case  3: output = 2; break; // Eb
		case  4: output = 2; break; // E
		case  5: output = 3; break; // F
		case  6: output = 3; break; // F#
		case  7: output = 4; break; // G
		case  8: output = 4; break; // G#
		case  9: output = 5; break; // A
		case 10: output = 6; break; // Bb
		case 11: output = 6; break; // B
	}
	return output + 7 * octave;
}



//////////////////////////////
//
// printDoubleClass -- print a double note with a "d" instead of
//     decimal point.
//

void printDoubleClass(ostream& out, double value) {
	value = int(value * 1000.0 + 0.5)/1000.0;
	char buffer[32] = {0};
	snprintf(buffer, 32, "%.3lf", value);
	char* decimal = strchr(buffer, '.');
	if (decimal != NULL) {
		decimal[0] = 'd';
	}
	out << buffer;
}



//////////////////////////////
//
// getTrackHues -- Assign track colors by maximally spaced hue.  Maybe
//   shuffle or randomize if there are a lot of tracks.
//

vector<double> getTrackHues(MidiFile& midifile) {
	vector<double> output;
	output.resize(midifile.size());
	fill(output.begin(), output.end(), -1);
	int tcount = 0;
	int i;
	for (i=0; i<midifile.size(); i++) {
		if (hasNotes(midifile[i])) {
			tcount++;
		}
	}
	int count = 0;
	double hue = 0;
	for (i=0; i<midifile.size(); i++) {
		if (!hasNotes(midifile[i])) {
			continue;
		}
		hue = (double)count / (double)tcount * 360.0;
		count++;
		output[i] = hue;
	}

	return output;
}



//////////////////////////////
//
// hasNotes -- Return true if a track has any notes-ons in it.
//

int hasNotes(MidiEventList& eventlist) {
	for (int i=0; i<eventlist.size(); i++) {
		if (eventlist[i].isNoteOn()) {
			if (drumQ) {
				return 1;
			} else if (eventlist[i].getChannel() != 0x09) {
				return 1;
			}
		}
	}
	return 0;
}



//////////////////////////////
//
// getMinMaxPitch -- Determine the minimum and maximum pitch in the file.
//

void getMinMaxPitch(const MidiFile& midifile, int& minpitch, int& maxpitch) {
	int key = 0;
	for (int i=0; i<midifile.size(); i++) {
		for (int j=0; j<midifile[i].size(); j++) {
			if (midifile[i][j].isNoteOn()) {
				key = midifile[i][j].getP1();
				if ((minpitch < 0) || (minpitch > key)) {
					minpitch = key;
				}
				if ((maxpitch < 0) || (maxpitch < key)) {
					maxpitch = key;
				}
			}
		}
	}

	if (grandQ) {
		if (minpitch > 40) {
			minpitch = 40;
		}
		if (maxpitch < 80) {
			maxpitch = 80;
		}
	}
	if (diatonicQ) {
		minpitch = base12ToBase7(minpitch);
		maxpitch = base12ToBase7(maxpitch);
	}
}



//////////////////////////////
//
// getMinMaxTrackPitch -- Determine the minimum and maximum pitch in a
//   particular Track.
//

void getMinMaxTrackPitch(const MidiEventList& evl, int& minpitch,
		int &maxpitch) {
	int key = 0;
	minpitch = -1;
	maxpitch = -1;
	for (int i=0; i<evl.size(); i++) {
		if (evl[i].isNoteOn()) {
			key = evl[i].getP1();
			if ((minpitch < 0) || (minpitch > key)) {
				minpitch = key;
			}
			if ((maxpitch < 0) || (maxpitch < key)) {
				maxpitch = key;
			}
		}
	}

	if (diatonicQ) {
		minpitch = base12ToBase7(minpitch);
		maxpitch = base12ToBase7(maxpitch);
	}
}



//////////////////////////////
//
// getMaxTime -- return the ending time of the last note in any track.
//

double getMaxTime(const MidiFile& midifile) {
	double maxtime = 0.0;
	for (int i=0; i<midifile.size(); i++) {
		for (int j=midifile[i].size()-1; j>=0; j--) {
			if (midifile[i][j].isNoteOff()) {
				if (maxtime < midifile[i][j].seconds) {
					maxtime = midifile[i][j].seconds;
				}
				break;
			}
		}
	}
	return maxtime;
}



//////////////////////////////
//
// checkOptions --
//

void checkOptions(Options& opts, int argc, char* argv[]) {
	opts.define("a|aspect-ratio=d:2.5", "Aspect ratio for SVG image");
	opts.define("w|stroke-width=d:0.1", "Stroke width for line around note boxes");
	opts.define("stroke-color=s:black", "Stroke color for line around note boxes");
	opts.define("staff=b",              "Draw staff lines.");
	opts.define("gs|grand|grand-staff=b", "show at least all grand staff.");
	opts.define("sc|staff-color=s:#555555", "staff line color.");
	opts.define("cc|clef-color=s:#cdcdcd", "clef fill/stroke color.");
	opts.define("sw|st|staff-width|staff-thickness=d:0.5",    "staff line width.");
	opts.define("lw|lt|line-width|line-thickness=d:0.5",  "Width of note lines");
	opts.define("dash|dashing=b",       "Dash connecting lines");
	opts.define("T|no-transparency=b",  "Do not show notes with transparency");
	opts.define("s|scale=d:1.0",        "Scaling factor for SVG image");
	opts.define("d|data=b",             "Embed note data in SVG image");
	opts.define("f|final|final-barline=b", "draw final barline");
	opts.define("double|double-barline=b", "draw final double barline");
	opts.define("bw|black-and-white=b", "Display as black and white (outlines only)");
	opts.define("diatonic=b",           "Vertical axis is base-7 pitch");
	opts.define("drum=b",               "Show drum track (channel 10)");
	opts.define("pm|perc|percussion-map=s", "Map percussion notes to different pitch");
	opts.define("r|round|rounded=b",    "Round edges of note boxes");
	opts.define("b|border=d:1.0",       "Border around piano roll");
	opts.define("dark=b",               "Background is black");
	opts.define("o|opacity=d:1.0",      "Opacity for notes");
	opts.define("l|line=b",             "Draw lines between center of notes");
	opts.define("cl|cline=b",           "Draw curved lines between notes");
	opts.define("rl|rline=d:0.25",      "Draw lines with curved radius between notes");
	opts.define("e|end-space=d:0.0",    "extra horiz. space at end of piece");
	opts.define("c|clef|clefs=b",       "Draw clefs");
	opts.define("v|velocity-brightness=b",  "Show velocity as brightness on note");
	opts.define("S|shapes=s:rectangle,rectangle", "shape of notes for each track");
	opts.define("mr|rest|max-rest=d:4.0 seconds",
		"Maximum rest through which to draw lines");

	opts.define("author=b",  "author of program");
	opts.define("version=b", "compilation info");
	opts.define("example=b", "example usages");
	opts.define("h|help=b",  "short description");
	opts.process(argc, argv);

	// handle basic options:
	if (opts.getBoolean("author")) {
		cout << "Written by Craig Stuart Sapp, "
			  << "craig@ccrma.stanford.edu, 20 February 2016" << endl;
		exit(0);
	} else if (opts.getBoolean("version")) {
		cout << argv[0] << ", version: 20 February 2016" << endl;
		cout << "compiled: " << __DATE__ << endl;
		exit(0);
	} else if (opts.getBoolean("help")) {
		usage(opts.getCommand().c_str());
		exit(0);
	} else if (opts.getBoolean("example")) {
		example();
		exit(0);
	}

	if (opts.getArgCount() > 1) {
		usage(opts.getCommand().c_str());
		exit(1);
	}

	dataQ        =  opts.getBoolean("data");
	drumQ        =  opts.getBoolean("drum");
	darkQ        =  opts.getBoolean("dark");
	lineQ        =  opts.getBoolean("line");
	curveQ       =  opts.getBoolean("cline");
	if (curveQ) {
		lineQ = 1;
	}
	radiusQ      =  opts.getBoolean("rline");
	if (radiusQ) {
		lineQ = 1;
		curveQ = 1;
		radius = opts.getDouble("rline");
	}
	clefQ        =  opts.getBoolean("clefs");
	staffQ       =  opts.getBoolean("staff");
	grandQ       =  opts.getBoolean("grand-staff");
	bwQ          =  opts.getBoolean("black-and-white");
	diatonicQ    =  opts.getBoolean("diatonic");
	transparentQ = !opts.getBoolean("no-transparency");
	roundedQ     =  opts.getBoolean("rounded");
	Scale        =  opts.getDouble("scale");
	Border       =  opts.getDouble("border");
	AspectRatio  =  opts.getDouble("aspect-ratio");
	Opacity      =  opts.getDouble("opacity");
	MaxRest      =  opts.getDouble("max-rest");
	if (clefQ) {
		staffQ = 1;
		braceQ = 1;
	}

	PercussionMap.resize(128);
	for (int i=0; i<(int)PercussionMap.size(); i++) {
		PercussionMap[i] = i;
	}
	percmapQ     =  opts.getBoolean("percussion-map");
	if (percmapQ) {
		makeMappings(PercussionMap, opts.getString("percussion-map"));
		drumQ = 1;
	}

	finalQ   = opts.getBoolean("final-barline");
	doubleQ = opts.getBoolean("double-barline");
	EndSpace = opts.getDouble("end-space");
	if (finalQ || doubleQ) {
		EndSpace += 2.0;
	}
	StaffThickness = opts.getDouble("staff-width");
	if (bwQ) {
		StaffThickness = StaffThickness * 0.25;
	}
	StaffColor = opts.getString("staff-color");
	ClefColor = opts.getString("clef-color");
	if (!opts.getBoolean("line-width")) {
		LineThickness = StaffThickness;
	}

	velocitybQ = opts.getBoolean("velocity-brightness");
	char buffer[12345] = {0};
	strcpy(buffer, opts.getString("shapes").c_str());
	Shapes.resize(0);
	string current;
	const char* spacers = "\t :,;|\n";
	char* ptr = strtok(buffer, spacers);
	while (ptr != NULL) {
		current = ptr;
		if (current == "r") {
			Shapes.push_back("rectangle");
		} else if (current == "e") {
			Shapes.push_back("eyelid");
		} else if (current == "d") {
			Shapes.push_back("diamond");
		} else if (current == "h") {
			Shapes.push_back("hexthin");
		} else if (current == "H") {
			Shapes.push_back("hexthick");
		} else if (current == "p") {
			Shapes.push_back("plus");
		} else if (current == "r1") {
			Shapes.push_back("round1");
		} else if (current == "r2") {
			Shapes.push_back("round2");
		} else if (current == "r3") {
			Shapes.push_back("round3");
		} else if (current == "r4") {
			Shapes.push_back("round4");
		} else if (current == "o") {
			Shapes.push_back("oval");
		} else if (current == "O") {
			Shapes.push_back("antioval");
		} else if (current == "R") {
			Shapes.push_back("antiround");
		} else if (current == "R1") {
			Shapes.push_back("antiround1");
		} else if (current == "R2") {
			Shapes.push_back("antiround2");
		} else if (current == "R3") {
			Shapes.push_back("antiround3");
		} else if (current == "R4") {
			Shapes.push_back("antiround4");
		} else if (current == "c") {
			Shapes.push_back("curvedinner");
		} else if (current == "c1") {
			Shapes.push_back("curvedinner1");
		} else if (current == "c2") {
			Shapes.push_back("curvedinner2");
		} else if (current == "c3") {
			Shapes.push_back("curvedinner3");
		} else if (current == "c4") {
			Shapes.push_back("curvedinner4");
		} else if (current == "C") {
			Shapes.push_back("curvedouter");
		} else if (current == "C1") {
			Shapes.push_back("curvedouter1");
		} else if (current == "C2") {
			Shapes.push_back("curvedouter2");
		} else if (current == "C3") {
			Shapes.push_back("curvedouter3");
		} else if (current == "C4") {
			Shapes.push_back("curvedouter4");
		} else if (current == "tu") {
			Shapes.push_back("triangleup");
		} else if (current == "td") {
			Shapes.push_back("triangledown");
		} else if (current == "tl") {
			Shapes.push_back("triangleleft");
		} else if (current == "tr") {
			Shapes.push_back("triangleright");
		} else if (current == "trl") {
			Shapes.push_back("triangleroundleft");
		} else if (current == "trr") {
			Shapes.push_back("triangleroundright");
		} else {
			Shapes.push_back(current);
		}
		ptr = strtok(NULL, spacers);
	}
}



//////////////////////////////
//
// makeMappings -- Manually move percussion notes to other locations in pitch range.
//     Maybe add separate coloring of percussion instruments.
//     Maybe need to add automatic note offs for persussion instruments (as they
//     do not always have note-offs.
//
//   --perc "60>40, 61>51,62>44"
//

void makeMappings(vector<int>& mapping, const string& mapstring) {
	string newmap = mapstring + ' ';
	int ltx = 0;
	int d = 1;
	int digit1 = 0;
	int digit2 = 0;
	int ii;
	for (ii=0; ii<(int)newmap.size(); ii++) {
		if (isdigit(newmap[ii])) {
			break;
		}
	}
	for (int i=ii; i<(int)newmap.size(); i++) {
		if (isdigit(newmap[i])) {
			if (d) {
				digit1 = digit1 * 10 + (newmap[i] - '0');
			} else {
				digit2 = digit2 * 10 + (newmap[i] - '0');
			}
			ltx = 0;
		} else {
			if (!ltx) {
				d = !d;
			}
			ltx = 1;
			if (d) {
				digit1 = digit1 > 127 ? 127 : digit1;
				digit1 = digit1 <   0 ?   0 : digit1;
				digit2 = digit2 > 127 ? 127 : digit2;
				digit2 = digit2 <   0 ?   0 : digit2;
				mapping[digit1] = digit2;
				digit1 = 0;
				digit2 = 0;
			}
		}
	}
}



//////////////////////////////
//
// example --
//

void example(void) {
	// add example usages here
}



//////////////////////////////
//
// usage --
//

void usage(const char* command) {
	cout << "Usage: " << command << " input.mid > output.svg" << endl;
}



