//
// 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:      midifile/tools/mid2svg.cpp
// Web Address:   https://github.com/craigsapp/midifile/blob/master/tools/mid2svg.cpp
// Syntax:        C++; museinfo
//
// Description:   Convert a MIDI file into an SVG piano roll.
//

#include "MidiFile.h"
#include "Options.h"
#include <sstream>
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <math.h>

using namespace std;
using namespace smf;

// 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;

// Function declarations:
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);


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

int main(int argc, char* argv[]) {
   checkOptions(options, argc, argv);
   MidiFile midifile(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};
   sprintf(buffer, "%.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) {

}



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

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



