//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Mar 31 21:51:34 PST 2005
// Last Modified: Thu Feb  9 07:34:18 PST 2012 SCORE display output by voice.
// Last Modified: Sat Mar 30 13:14:05 PDT 2013 Allow segmented input.
// Filename:      ...sig/examples/all/range.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/range.cpp
// Syntax:        C++; museinfo
//
// Description:   Calculate pitch histograms in a score of **kern data.
// 

#include "PerlRegularExpression.h"
#include "humdrum.h"

#include <string.h>
#include <math.h>

#define OBJTAB "\t\t\t\t\t\t"
#define SVGTAG "_99%svg%";

// function declarations
void   checkOptions(Options& opts, int argc, char* argv[]);
void   example(void);
void   usage(const char* command);
void   generateAnalysis(HumdrumFile& infile, 
                                   Array<Array<double> >& rdata, 
                                   Array<int>& kernspines);
void   printAnalysis(Array<double>& rdata);
void   printPercentile(Array<double>& midibins, 
                                   double percentile);
double countNotesInRange(Array<double>& midibins, 
                                   int low, int high);
void   clearHistograms(Array<Array<double> >& bins, int start = 0);
void   getRange(int& rangeL, int& rangeH, 
                                   const char* rangestring);
int    getTessitura(Array<double>& midibins);
double getMean(Array<double>& midibins);
int    getMedian(Array<double>& midibins);
void   printScoreVoice(HumdrumFile& infile, double hpos, 
                                   Array<double>& midibins, int kernspine, 
                                   double maxhist);
void   getVoice(char* voicestring, HumdrumFile& infile, 
                                   int kernspine);
int    getMaxPitch(Array<double>& midibins);
int    getMinPitch(Array<double>& midibins);
int    getStaffBase12(int pitch);
double getVpos(double pitch, int staff);
double getMaxValue(Array<double>& bins);
void   printScoreFile(Array<Array<double> >& midibins, 
                                   HumdrumFile& infile, Array<int>& kernspines);
void   getTitle(char* titlestring, HumdrumFile& infile);
void   growHistograms(Array<Array<double> >& midibins, int voices);
int    getVindex(int track, Array<int>& kernspines);
int    getTopQuartile(Array<double>& midibins);
int    getBottomQuartile(Array<double>& midibins);
int    getDiatonicInterval(int note1, int note2);
void   printScoreXmlHeader(void);
void   printScoreXmlFooter(void);
void   printFilenameBase(const char* filename);
void   printXmlEncodedText(const char* strang);
void   printScoreEncodedText(const char* strang);
int    getKeySignature(HumdrumFile& infile);
void   printHTMLStringEncodeSimple(const char* string);
void   printDiatonicPitchName(int base12);
const char* getTitle(char* hbuffer, double value, int pitch);

// global variables
Options      options;            // database for command-line arguments
int          durationQ    = 0;   // used with -d option
int          debugQ       = 0;   // used with --debug option
int          percentileQ  = 0;   // used with -p option
int          addfractionQ = 0;   // used with -f option
double       percentile   = 0.0; // used with -p option
int          rangeQ       = 0;   // used with -r option
int          rangeL       = 0;   // used with -r option
int          pitchQ       = 0;   // used with --pitch option 
int          rangeH       = 0;   // used with -r option
int          printQ       = 0;   // used with --print option
int          normQ        = 0;   // used with -N option
int          scoreQ       = 0;   // used with --score option
int          xmlQ         = 0;   // used with --sx option
int          diatonicQ    = 0;   // used with -D option
int          hoverQ       = 0;   // used with --hover option
int          quartileQ    = 0;   // used with --quartile option
int          fillonlyQ    = 0;   // used with --fill option
int          defineQ      = 1;   // used with --no-define option
int          base40Q      = 0;   // used with --base40 option
const char*  FILENAME     = "";

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

int main(int argc, char* argv[]) {
   HumdrumFileSet infiles;

   // process the command-line options
   checkOptions(options, argc, argv);

   // figure out the number of input files to process
   int numinputs = options.getArgCount();

   Array<double> values;
   values.setSize(0);

   Array<Array<double> > midibins;
   midibins.setSize(1);
   clearHistograms(midibins);

   Array<int> kernSpines;

   // can only handle one input if SCORE display is being given.
   if (scoreQ && numinputs > 1) {
      numinputs = 1;
   }

   int i, j;
   for (i=0; i<numinputs || i==0; i++) {
      infiles.clear();

      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         infiles.read(cin);
      } else {
         FILENAME = options.getArg(i+1);
         infiles.read(FILENAME);
      }
      for (j=0; j<infiles.getCount(); j++) {
         infiles[j].getTracksByExInterp(kernSpines, "**kern");
         growHistograms(midibins, kernSpines.getSize());
         generateAnalysis(infiles[j], midibins, kernSpines);
      }
   }

   if (xmlQ) {
      printScoreXmlHeader();
   }

   if (!scoreQ) {
      printAnalysis(midibins[0]);
   } else {
      printScoreFile(midibins, infiles[0], kernSpines);
   }

   if (xmlQ) {
      printScoreXmlFooter();
   }

   return 0;
}


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


//////////////////////////////
//
// printScoreXmlHeader --
//

void printScoreXmlHeader(void) {
   cout << "<ScoreXML version=\"1.0\">\n";
   cout << "\t<scoreHead>\n";
   cout << "\t\t<info>\n";
   cout << "\t\t\t<description>Pitch range information by voice</description>\n";
   cout << "\t\t</info>\n";
   cout << "\t</scoreHead>\n";
   cout << "\t<scoreData>\n";
}



//////////////////////////////
//
// printScoreXmlFooter --
//

void printScoreXmlFooter(void) {
   cout << "\t</scoreData>\n";
   cout << "</ScoreXML>\n";
}



//////////////////////////////
//
// growHistograms --
//

void growHistograms(Array >& midibins, int voices) {
   int oldsize = midibins.getSize();
   if (voices <= oldsize-1) {
      return;
   }
   midibins.setSize(voices+1);
   clearHistograms(midibins, oldsize);
}



//////////////////////////////
//
// printFilenameBase --
//

void printFilenameBase(const char* filename) {
   PerlRegularExpression pre;
   if (pre.search(filename, "([^/]+)\\.([^.]*)", "")) {
      if (strlen(pre.getSubmatch(1)) <= 8) {
         printXmlEncodedText(pre.getSubmatch(1));
      } else {
         // problem with too long a name (MS-DOS will have problems).
         // optimize to chop off everything after the dash in the
         // name (for Josquin catalog numbers).
         PerlRegularExpression pre2;
         Array<char> shortname;
         shortname.setSize(strlen(pre.getSubmatch())+1);
         strcpy(shortname.getBase(), pre.getSubmatch());
         if (pre2.sar(shortname, "-.*", "", "")) {
            printXmlEncodedText(shortname.getBase());
         } else {
            printXmlEncodedText(shortname.getBase());
         }
      }
   }
}


//////////////////////////////
//
// printReferenceRecords --
//

void printReferenceRecords(HumdrumFile& infile) {
   int i;
   char buffer[1024] = {0};
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      cout <<  "\t\t\t\t\t\t<?Humdrum key=\"";
      printXmlEncodedText(infile[i].getBibKey(buffer));
      cout << "\" value=\"";
      printXmlEncodedText(infile[i].getBibValue(buffer));
      cout << "\"?>\n";
   }
}



//////////////////////////////
//
// printScorEncodedText -- print SCORE text string
//    See SCORE 3.1 manual additions (page 19) for more.
//

void printScoreEncodedText(const char* strang) {
   Array<char> newstring;
   newstring.setSize(strlen(strang)+1);
   strcpy(newstring.getBase(), strang);
   PerlRegularExpression pre;
   // pre.sar(newstring, "&(.)acute;", "<<$1", "g");
   pre.sar(newstring, "á", "<<a", "g");
   pre.sar(newstring, "é", "<<e", "g");
   pre.sar(newstring, "í", "<<i", "g");
   pre.sar(newstring, "ó", "<<o", "g");
   pre.sar(newstring, "ú", "<<u", "g");
   pre.sar(newstring, "Á", "<<A", "g");
   pre.sar(newstring, "É", "<<E", "g");
   pre.sar(newstring, "Í", "<<I", "g");
   pre.sar(newstring, "Ó", "<<O", "g");
   pre.sar(newstring, "Ú", "<<U", "g");

   // pre.sar(newstring, "&(.)grave;", ">>$1", "g");
   pre.sar(newstring, "à", ">>a", "g");
   pre.sar(newstring, "è", ">>e", "g");
   pre.sar(newstring, "ì", ">>i", "g");
   pre.sar(newstring, "ò", ">>o", "g");
   pre.sar(newstring, "ù", ">>u", "g");
   pre.sar(newstring, "À", ">>A", "g");
   pre.sar(newstring, "È", ">>E", "g");
   pre.sar(newstring, "Ì", ">>I", "g");
   pre.sar(newstring, "Ò", ">>O", "g");
   pre.sar(newstring, "Ù", ">>U", "g");

   // pre.sar(newstring, "&(.)uml;",   "%%$1", "g");
   pre.sar(newstring, "ä",   "%%a", "g");
   pre.sar(newstring, "ë",   "%%e", "g");
   pre.sar(newstring, "ï",   "%%i", "g");
   pre.sar(newstring, "ö",   "%%o", "g");
   pre.sar(newstring, "ü",   "%%u", "g");
   pre.sar(newstring, "Ä",   "%%A", "g");
   pre.sar(newstring, "Ë",   "%%E", "g");
   pre.sar(newstring, "Ï",   "%%I", "g");
   pre.sar(newstring, "Ö",   "%%O", "g");
   pre.sar(newstring, "Ü",   "%%U", "g");

   // pre.sar(newstring, "&(.)circ;",  "^^$1", "g");
   pre.sar(newstring, "â",  "^^a", "g");
   pre.sar(newstring, "ê",  "^^e", "g");
   pre.sar(newstring, "î",  "^^i", "g");
   pre.sar(newstring, "ô",  "^^o", "g");
   pre.sar(newstring, "û",  "^^u", "g");
   pre.sar(newstring, "Â",  "^^A", "g");
   pre.sar(newstring, "Ê",  "^^E", "g");
   pre.sar(newstring, "Î",  "^^I", "g");
   pre.sar(newstring, "Ô",  "^^O", "g");
   pre.sar(newstring, "Û",  "^^U", "g");

   // pre.sar(newstring, "&(.)cedil;", "##$1", "g");
   pre.sar(newstring, "ç", "##c", "g");
   pre.sar(newstring, "Ç", "##C", "g");

   pre.sar(newstring, "\\|",        "?|",   "g");
   pre.sar(newstring, "\\\\",       "?\\",  "g");
   pre.sar(newstring, "---",        "?m",   "g");
   pre.sar(newstring, "--",         "?n",   "g");
   pre.sar(newstring, "-sharp",     "?2",   "g");
   pre.sar(newstring, "-flat",      "?1",   "g");
   pre.sar(newstring, "-natural",   "?3",   "g");
   pre.sar(newstring, "/",          "\\",   "g");
   pre.sar(newstring, "\\[",        "?[",   "g");
   pre.sar(newstring, "\\]",        "?]",   "g");

   if (xmlQ) {
      printXmlEncodedText(newstring.getBase());
   } else {
      cout << newstring;
   }
}



//////////////////////////////
//
// printXmlEncodedText -- convert 
//    & to &
//    " to "
//    ' to &spos;
//    < to <
//    > to >
//

void printXmlEncodedText(const char* strang) {
   PerlRegularExpression pre;
   Array<char> astring;
   astring.setSize(strlen(strang)+128);
   astring.setGrowth(12345);
   astring.setSize(strlen(strang)+1);
   strcpy(astring.getBase(), strang);
  
   pre.sar(astring, "&",  "&",  "g");
   pre.sar(astring, "'",  "'", "g");
   pre.sar(astring, "\"", """, "g");
   pre.sar(astring, "<",  "<",   "g");
   pre.sar(astring, ">",  ">",   "g");

   cout << astring;
}



//////////////////////////////
//
// printScoreFile --
//

void printScoreFile(Array<Array<double> >& midibins, HumdrumFile& infile,
   Array<int>& kernspines) {

   char titlestring[1024] = {0};
   getTitle(titlestring, infile);

   if (xmlQ) {
      // print file start info;
      cout << "\t\t<page>\n";
      cout << "\t\t\t<pageData>\n";
      cout << "\t\t\t\t<scoreFile>\n";
      cout << "\t\t\t\t\t<fileHead>\n";
      cout << "\t\t\t\t\t\t<name>";
      printFilenameBase(FILENAME);
      cout << "</name>\n";
      cout << "\t\t\t\t\t\t<pmxExt>pmx";
      cout << "</pmxExt>\n";
      printReferenceRecords(infile);
      cout << "\t\t\t\t\t</fileHead>\n";
      cout << "\t\t\t\t\t<fileObjects>\n";
   }

   if ((!xmlQ) && defineQ) {
      cout << "#define SVG t 1 1 \\n_99%svg%\n";
   }

   // print CSS style information if requested
   if (hoverQ) {
      if (!xmlQ) {
         if (defineQ) {
            cout << "SVG ";
         } else {
            cout << "t 1 1\n";
            cout << SVGTAG;
         }
         if (fillonlyQ) {
            printScoreEncodedText("<style type=\"text/css\">.bar:hover path{fill:red}</style>");
         } else {
            printScoreEncodedText("<style type=\"text/css\">.bar:hover {color:red;stroke:red}</style>");
         }
         cout << "\n";
      } else {
         cout << OBJTAB << "<obj p1=\"16\" p2=\"1\"";
         cout << " p3=\"2\" p4=\"20\" p5=\"1\" p6=\"1\" p7=\"0\"";
         cout << " p8=\"0\" p9=\"0\" p10=\"0\" p11=\"-1\"";
         cout << " text=\"" << SVGTAG;
         if (fillonlyQ) {
            printScoreEncodedText("<style type=\"text/css\">.bar:hover path{fill:red}</style>");
         } else {
            printScoreEncodedText("<style type=\"text/css\">.bar:hover {color:red}stroke:red;</style>");
         }
         cout << "\">\n";
      }
   }

   // print title
   if (!xmlQ) {
      cout << "t 2 10 14 1 1 0 0 0 0 -1.35\n";
      // cout << "_03";
      printScoreEncodedText(titlestring);
      cout << "\n";
   } else {
      cout << OBJTAB << "<obj p1=\"16\" p2=\"2\" p3=\"10\""
           <<    " p4=\"14\" p5=\"1\" p6=\"1\" p11=\"-1.35\" "
           <<    "text=\""; 
      printScoreEncodedText(titlestring);
      cout << "\"/>\n";
   }

   // print duration label if duration weighting is being used
   if (durationQ) {
      if (!xmlQ) {
         cout << "t 2 180.075 14 1 0.738 0 0 0 0 0\n";
         cout << "_01(durations)\n";
      } else {
         cout << OBJTAB << "<obj p1=\"16\" p2=\"2\" p3=\"180.075\""
              <<    " p4=\"14\" p5=\"1\""
              <<    " p6=\"0.738\" text=\"_01(durations)\"/>\n";
      }
   }

   // print staff lines
   if (!xmlQ) {
      cout << "8 1 0 0 0 200\n";   // staff 1
      cout << "8 2 0 -6 0 200\n";   // staff 2
   } else {
      cout << OBJTAB << "<obj p1=\"8\" p2=\"1\" p6=\"200\"/>\n";
      cout << OBJTAB << "<obj p1=\"8\" p2=\"2\" p4=\"-6\" "
           <<    "p6=\"200\"/>\n";
   }

   int keysig = getKeySignature(infile);
   // print key signature
   if (keysig) {
      if (!xmlQ) {
         cout << "17 1 10 0 " << keysig << " 1.0\n"; 
         cout << "17 2 10 0 " << keysig << "\n";    
      } else {
         cout << OBJTAB << "<obj p1=\"17\" p2=\"1\" p3=\"10\" p4=\"0\"";
         cout << " p5=\"" << keysig << "\" p6=\"1\"/>\n";
         cout << OBJTAB << "<obj p1=\"17\" p2=\"2\" p3=\"10\" p4=\"0\"";
         cout << " p5=\"" << keysig << "\"/>\n";
      }
   }

   // print barlines
   if (!xmlQ) {
      cout << "14 1 0 2\n";         // starting barline
      cout << "14 1 200 2\n";       // ending barline
      cout << "14 1 0 2 8\n";       // curly brace at start
   } else {
      cout << OBJTAB << "<obj p1=\"14\" p2=\"1\" p4=\"2\"/>\n";
      cout << OBJTAB << "<obj p1=\"14\" p2=\"1\" p3=\"200\" "
           <<    "p4=\"2\"/>\n";
      cout << OBJTAB << "<obj p1=\"14\" p2=\"1\" p4=\"2\" "
           <<    "p5=\"8\"/>\n";
   }

   // print clefs
   if (!xmlQ) {
      cout << "3 2 2\n";            // treble clef
      cout << "3 1 2 0 1\n";        // bass clef
   } else {
      cout << OBJTAB << "<obj p1=\"3\" p2=\"2\" p3=\"2\"/>\n";
      cout << OBJTAB << "<obj p1=\"3\" p2=\"1\" p3=\"2\" p5=\"1\"/>\n";
   }

   int ii;
   // calculate the locations for each voice.
   Array<double> hpos;
   double minn = 25;
   double maxx = 170.0;
   hpos.setSize(kernspines.getSize());
   hpos.last() = minn;
   hpos[0] = maxx;
   int i;
   if (hpos.getSize() > 2) {
      for (i=1; i<hpos.getSize()-1; i++) {
         ii = hpos.getSize() - i - 1;
         hpos[i] = (double)ii / (hpos.getSize()-1) * (maxx - minn) + minn;
      }
   }

   for (i=hpos.getSize()-1; i>=0; i--) {
      printScoreVoice(infile, hpos[i], midibins[i+1], kernspines[i], 17.6);
   }


   if (xmlQ) {
      cout << "\t\t\t\t\t</fileObjects>\n";
      cout << "\t\t\t\t</scoreFile>\n";
      cout << "\t\t\t</pageData>\n";
      cout << "\t\t</page>\n";
   }
}



//////////////////////////////
//
// getKeySignature -- find first key signature in file.
//

int getKeySignature(HumdrumFile& infile) {
   int i, j;
   PerlRegularExpression pre;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (pre.search(infile[i][j], "^\\*k\\[(.*)\\]", "")) {
            return Convert::kernKeyToNumber(infile[i][j]);
         }
      }
   }

   return 0; // C major key signature
}



//////////////////////////////
//
// printScoreVoice -- print the range information for a particular voice.
//


void printScoreVoice(HumdrumFile& infile, double hpos, Array<double>& midibins,
      int kernspine, double maxhist) {
   
   int minpitchbase12, maxpitchbase12;
   int mini = getMinPitch(midibins);
   int maxi = getMaxPitch(midibins);
   
   if (diatonicQ) {
      minpitchbase12 = Convert::base7ToBase12(mini);
      maxpitchbase12 = Convert::base7ToBase12(maxi);
   } else {
      minpitchbase12 = mini;
      maxpitchbase12 = maxi;
   }
   if ((minpitchbase12 <= 0) && (maxpitchbase12 <= 0)) {
      return;
   }

   int    staff;
   double vpos;
   char voicestring[1024] = {0};
   getVoice(voicestring, infile, kernspine);
   int voicevpos = -3;
   staff = getStaffBase12(minpitchbase12);
   int lowestvpos = getVpos(minpitchbase12, staff);
   if ((staff == 1) && (lowestvpos <= 0)) {
      voicevpos += lowestvpos;
      voicevpos -= 1;
   }

   double maxvalue = getMaxValue(midibins);
   double value;
   double hoffset = 2.3333;
   int i;
   int base12;
   char hbuffer[1024] = {0};
   for (i=mini; i<=maxi; i++) {
      if (midibins[i] == 0.0) {
         continue;
      }
      base12 = i;
      if (diatonicQ) {
         base12 = Convert::base7ToBase12(base12);
      }
      staff = getStaffBase12(base12);
      vpos  = getVpos(base12, staff);
      value = midibins[i] / maxvalue * maxhist + hoffset;
      if (!xmlQ) {
         if (hoverQ) {
            if (defineQ) {
               cout << "SVG ";
            } else {
               cout << "t 1 1\n";
               cout << SVGTAG;
            }
            getTitle(hbuffer, (int)midibins[i], i);
            printScoreEncodedText(hbuffer);
            cout << "\n";
         }
         cout << "1 " << staff << " " << hpos << " " << vpos;
         cout << " 0 -1 4 0 0 0 99 0 0 ";
         cout << value << "\n";      
         if (hoverQ) {
            if (defineQ) {
               cout << "SVG ";
            } else {
               cout << "t 1 1\n";
               cout << SVGTAG;
            }
            printScoreEncodedText("</g>");
            cout << "\n";
         }
      } else {
         if (hoverQ) {
            cout << OBJTAB << "<obj p1=\"16\" p2=\"1\"";
            cout << " p3=\"2\" p4=\"20\" p5=\"1\" p6=\"1\" p7=\"0\"";
            cout << " p8=\"0\" p9=\"0\" p10=\"0\" p11=\"-1\"";
            cout << " text=\"" << SVGTAG;
            getTitle(hbuffer, (int)midibins[i], i);
            printScoreEncodedText(hbuffer);
            cout << "\"/>\n";
         }
         cout << OBJTAB << "<obj p1=\"1\" p2=\"" << staff << "\" p3=\""
              << hpos << "\" p4=\"" << vpos << "\" p6=\"-1\" p7=\"4\""
              << " p11=\"99\" p14=\"" << value << "\"/>\n";
         if (hoverQ) {
            cout << OBJTAB << "<obj p1=\"16\" p2=\"1\"";
            cout << " p3=\"2\" p4=\"20\" p5=\"1\" p6=\"1\" p7=\"0\"";
            cout << " p8=\"0\" p9=\"0\" p10=\"0\" p11=\"-1\"";
            cout << " text=\"" << SVGTAG;
            printScoreEncodedText("</g>");
            cout << "\"/>\n";
         }
      }
   }

   if (strlen(voicestring) > 0) {
      // print voice name
      double tvoffset = -2.0;
      if (!xmlQ) {
         cout << "t 1 " << hpos << " " << voicevpos 
              << " 1 1 0 0 0 0 " << tvoffset;
         cout << "\n";
         cout << "_00";
         printScoreEncodedText(voicestring);
         cout << "\n";
      } else {
         cout << OBJTAB << "<obj p1=\"16\" p2=\"1\" p3=\"" << hpos
              <<   "\" p4=\"" << voicevpos << "\" p5=\"1\" p6=\"1\" "
              <<   "p11=\"" << tvoffset << "\" text=\"" << "_00"; 
         printScoreEncodedText(voicestring);
         cout << "\"/>\n";
      }
   }

   // print the lowest pitch in range
   staff = getStaffBase12(minpitchbase12);
   vpos = getVpos(minpitchbase12, staff);
   if (!xmlQ) {
      // if (hoverQ) {
      //    if (defineQ) {
      //       cout << "SVG ";
      //    } else {
      //       cout << "t 1 1\n";
      //       cout << SVGTAG;
      //    }
      //    printScoreEncodedText("<g><title>");
      //    printDiatonicPitchName(minpitchbase12);
      //    cout << ": lowest note";
      //    if (strlen(voicestring) > 0) {
      //       cout <<  " of " << voicestring << "\'s range";
      //    }
      //    printScoreEncodedText("</title>\n");
      // }
      cout << "1 " << staff << " " << hpos << " " << vpos
           << " 0 0 4 0 0 -2\n";
      //if (hoverQ) {
      //   if (defineQ) {
      //      cout << "SVG ";
      //   } else {
      //      cout << "t 1 1\n";
      //      cout << SVGTAG;
      //   }
      //   printScoreEncodedText("</g>\n");
      //}
   } else {
      cout << OBJTAB << "<obj p1=\"1\" p2=\"" << staff << "\" p3=\""
           << hpos << "\" p4=\"" << vpos << "\" p7=\"4\" p10=\"-2\"/>\n";
   }

   // print the highest pitch in range
   staff = getStaffBase12(maxpitchbase12);
   vpos = getVpos(maxpitchbase12, staff);
   if (!xmlQ) {
      // if (hoverQ) {
      //    if (defineQ) {
      //       cout << "SVG ";
      //    } else {
      //       cout << "t 1 1\n";
      //       cout << SVGTAG;
      //    }
      //    printScoreEncodedText("<g><title>");
      //    printDiatonicPitchName(maxpitchbase12);
      //    cout << ": highest note";
      //    if (strlen(voicestring) > 0) {
      //       cout <<  " of " << voicestring << "\'s range";
      //    }
      //    printScoreEncodedText("</title>\n");
      // }
      cout << "1 " << staff << " " << hpos << " " << vpos
           << " 0 0 4 0 0 -2\n";
      //if (hoverQ) {
      //   if (defineQ) {
      //      cout << "SVG ";
      //   } else {
      //      cout << "t 1 1\n";
      //      cout << SVGTAG;
      //   }
      //   printScoreEncodedText("</g>\n");
      //}
   } else {
      cout << OBJTAB << "<obj p1=\"1\" p2=\"" << staff << "\" p3=\""
           << hpos << "\" p4=\"" << vpos << "\" p7=\"4\" p10=\"-2\"/>\n";
   }

   /*   double mean = getMean(midibins);
   // print the mean note
   staff = getStaffBase12(mean);
   vpos = getVpos(mean, staff);
   cout << "1.0 " << staff << ".0 " << hpos << " ";
   if (vpos > 0) {
      cout << vpos + 100;
   } else {
      cout << vpos - 100;
   }
   cout << " 0.0 0.0 4.0 0.0 0.0 -5.0\n";
   */

   double goffset = -1.66;
   double toffset = 1.5;

   double median = getMedian(midibins);
   if (diatonicQ) {
      median = Convert::base7ToBase12(median);
   }
   staff = getStaffBase12(median);
   vpos = getVpos(median, staff);

   // these offsets are useful when the quartile pitches are not shown...
   int vvpos = getDiatonicInterval(median, maxpitchbase12);
   int vvpos2 = getDiatonicInterval(median, minpitchbase12);
   double offset = goffset;
   if (vvpos <= 2) { 
      offset += toffset;
   } else if (vvpos2 <= 2) {
      offset -= toffset;
   }

   // print the median note
   if (!xmlQ) {
      //if (hoverQ) {
      //   if (defineQ) {
      //      cout << "SVG ";
      //   } else {
      //      cout << "t 1 1\n";
      //      cout << SVGTAG;
      //   }
      //   printScoreEncodedText("<g><title>");
      //   printDiatonicPitchName(median);
      //   cout << ": median note";
      //   if (strlen(voicestring) > 0) {
      //      cout <<  " of " << voicestring << "\'s range";
      //   }
      //   printScoreEncodedText("</title>\n");
      //}
      cout << "1 " << staff << " " << hpos << " ";
      if (vpos > 0) {
         cout << vpos + 100;
      } else {
         cout << vpos - 100;
      }
      cout << " 0 1 4 0 0 " << offset << "\n";
      //if (hoverQ) {
      //   if (defineQ) {
      //      cout << "SVG ";
      //   } else {
      //      cout << "t 1 1\n";
      //      cout << SVGTAG;
      //   }
      //   printScoreEncodedText("</g>\n");
      //}
   } else {
      if (hoverQ) {
         cout << OBJTAB << "<obj p1=\"16\" p2=\"1\" p3=\"1\" ";
         cout <<    "text=\"";
         printHTMLStringEncodeSimple("_99%svg%<g><title>: median note");
         if (strlen(voicestring) > 0) {
            cout <<  " for " << voicestring;
         }
         printHTMLStringEncodeSimple("<\\title>\n");
      }
      cout << OBJTAB << "<obj p1=\"1\" p2=\"" << staff << "\""
           << " p3=\"" << hpos << "\"" << " p4=\"";
      if (vpos > 0) {
         cout << vpos + 100;
      } else {
         cout << vpos - 100;
      }
      cout << "\" p6=\"1\" p7=\"4\" p10=\"" << offset << "\"/>\n";
      if (hoverQ) {
         cout << OBJTAB << "<obj p1=\"16\" p2=\"1\" p3=\"1\" text=\"";
         printHTMLStringEncodeSimple("_99%svg <\\g>");
         cout << "\"/>\n";
      }
   }


   int topquartile;
   if (quartileQ) { 
      // print top quartile
      topquartile = getTopQuartile(midibins);
      if (diatonicQ) {
         topquartile = Convert::base7ToBase12(topquartile);
      }
      staff = getStaffBase12(topquartile);
      vpos = getVpos(topquartile, staff);
      vvpos = getDiatonicInterval(median, topquartile);
      if (vvpos <= 2) {
         offset = goffset + toffset;
      } else {
         offset = goffset;
      }
      vvpos = getDiatonicInterval(maxpitchbase12, topquartile);
      if (vvpos <= 2) {
         offset = goffset + toffset;
      }
      if (!xmlQ) {
         if (hoverQ) {
            if (defineQ) {
               cout << "SVG ";
            } else {
               cout << "t 1 1\n";
               cout << SVGTAG;
            }
            printScoreEncodedText("<g><title>");
            printDiatonicPitchName(topquartile);
            cout << ": top quartile note";
            if (strlen(voicestring) > 0) {
               cout <<  " of " << voicestring << "\'s range";
            }
            printScoreEncodedText("</title>\n");
         }
         cout << "1 " << staff << " " << hpos << " ";
         if (vpos > 0) {
            cout << vpos + 100;
         } else {
            cout << vpos - 100;
         }
         cout << " 0 0 4 0 0 " << offset << "\n";
         if (hoverQ) {
            if (defineQ) {
               cout << "SVG ";
            } else {
               cout << "t 1 1\n";
               cout << SVGTAG;
            }
            printScoreEncodedText("</g>\n");
         }
      } else {
         cout << OBJTAB << "<obj p1=\"1\" p2=\"" << staff << "\""
              << " p3=\"" << hpos << "\"" << " p4=\"";
         if (vpos > 0) {
            cout << vpos + 100;
         } else {
            cout << vpos - 100;
         }
         cout << "\" p7=\"4\" p10=\"" << offset << "\"/>\n";
      }
   }
   
   
   // print bottom quartile
   if (quartileQ) {
      int bottomquartile = getBottomQuartile(midibins);
      if (diatonicQ) {
         bottomquartile = Convert::base7ToBase12(bottomquartile);
      }
      staff = getStaffBase12(bottomquartile);
      vpos = getVpos(bottomquartile, staff);
      vvpos = getDiatonicInterval(median, bottomquartile);
      if (vvpos <= 2) {
         offset = goffset + toffset;
      } else {
         offset = goffset;
      }
      vvpos = getDiatonicInterval(minpitchbase12, bottomquartile);
      if (vvpos <= 2) {
         offset = goffset - toffset;
      }
      if (!xmlQ) {
         if (hoverQ) {
            if (defineQ) {
               cout << "SVG ";
            } else {
               cout << "t 1 1\n";
               cout << SVGTAG;
            }
            printScoreEncodedText("<g><title>");
            printDiatonicPitchName(bottomquartile);
            cout << ": bottom quartile note";
            if (strlen(voicestring) > 0) {
               cout <<  " of " << voicestring << "\'s range";
            }
            printScoreEncodedText("</title>\n");
         }
         cout << "1.0 " << staff << ".0 " << hpos << " ";
         if (vpos > 0) {
            cout << vpos + 100;
         } else {
            cout << vpos - 100;
         }
         cout << " 0 0 4 0 0 " << offset << "\n";
         if (hoverQ) {
            if (defineQ) {
               cout << "SVG ";
            } else {
               cout << "t 1 1\n";
               cout << SVGTAG;
            }
            printScoreEncodedText("</g>\n");
         }
      } else {
         cout << OBJTAB << "<obj p1=\"1\" p2=\"" << staff << "\""
              << " p3=\"" << hpos << "\"" << " p4=\"";
         if (vpos > 0) {
            cout << vpos + 100;
         } else {
            cout << vpos - 100;
         }
         cout << "\" p7=\"4\" p10=\"" << offset << "\"/>\n";
      }
   }
}



//////////////////////////////
//
// printDiatonicPitchName --
//

void printDiatonicPitchName(int base12) {
   char buffer[16] = {0};
   Convert::base12ToKern(buffer, base12);
   buffer[1] = '\0';
   buffer[0] = toupper(buffer[0]);
   cout << buffer;
   int octave = base12 / 12 - 1;
   sprintf(buffer, "%d", octave);
   cout << buffer;
}



//////////////////////////////
//
// printHTMLStringEncodeSimple --
//

void printHTMLStringEncodeSimple(const char* string) {
   Array<char> newstring;
   newstring.setSize(strlen(string)+1);
   strcpy(newstring.getBase(), string);
   PerlRegularExpression pre;
   pre.sar(newstring, "&", "&", "g");
   pre.sar(newstring, "<", "<", "g");
   pre.sar(newstring, ">", "<", "g");
   cout << newstring;
}



//////////////////////////////
//
// getTitle -- return the title of the histogram bar.
//

const char* getTitle(char* hbuffer, double value, int pitch) {
   strcpy(hbuffer, "<g class=\"bar\"><title>");
   char tempbuf[128] = {0};
   char pitchstring[128] = {0};
   int base12 = pitch;
   if (diatonicQ) {
      base12 = Convert::base7ToBase12(pitch);
   }

   Convert::base12ToKern(pitchstring, base12);
   pitchstring[1] = '\0';
   pitchstring[0] = toupper(pitchstring[0]);
   int octave = base12 / 12 - 1;
   char obuf[16] = {0};
   sprintf(obuf, "%d", octave);

   if (durationQ) {
      sprintf(tempbuf, "%lf", value/8.0);
      if (value/8.0 == 1.0) {
         strcat(tempbuf, " long on ");
      } else {
         strcat(tempbuf, " longs on ");
      }
      strcat(tempbuf, pitchstring);
   } else {
      sprintf(tempbuf, "%d ", (int)value);
      strcat(tempbuf, pitchstring);               
      strcat(tempbuf, obuf);
      if (value != 1.0) {
         strcat(tempbuf, "s");
      } 
   }
   strcat(hbuffer, tempbuf);
   strcat(hbuffer, "</title>");

   return hbuffer;
}



//////////////////////////////
//
// getDiatonicInterval --
//

int getDiatonicInterval(int note1, int note2) {
   int vpos1 = getVpos(note1, 0);
   int vpos2 = getVpos(note2, 0);
   return abs(vpos1 - vpos2) + 1;
}



//////////////////////////////
//
// getTopQuartile --
//

int getTopQuartile(Array& midibins) {
   double sum = midibins.sum();

   double cumsum = 0.0;
   int i;
   for (i=midibins.getSize()-1; i>=0; i--) {
      if (midibins[i] <= 0.0) {
         continue;
      }      
      cumsum += midibins[i]/sum;
      if (cumsum >= 0.25) {
         return i;
      }
   }

   return -1;
}



//////////////////////////////
//
// getBottomQuartile --
//

int getBottomQuartile(Array& midibins) {
   double sum = midibins.sum();

   double cumsum = 0.0;
   int i;
   for (i=0; i<midibins.getSize(); i++) {
      if (midibins[i] <= 0.0) {
         continue;
      }      
      cumsum += midibins[i]/sum;
      if (cumsum >= 0.25) {
         return i;
      }
   }

   return -1;
}




//////////////////////////////
//
// getMaxValue --
//

double getMaxValue(Array& bins) {
   int i;
   double maxi = 0;
   for (i=1; i<bins.getSize(); i++) {
      if (bins[i] > bins[maxi]) {
         maxi = i;
      } 
   }
   return bins[maxi];
}


//////////////////////////////
//
// getVpos == return the position on the staff given the MIDI pitch
//     and the staff. 1=bass, 2=treble.
//

double getVpos(double pitch, int staff) {
   int base40;

   base40 = Convert::base12ToBase40(int(pitch));

   if (staff == 2) {
      return (Convert::base40ToScoreVPos(base40, 0) + (pitch - int(pitch)));
   } else {
      return (Convert::base40ToScoreVPos(base40, 1) + (pitch - int(pitch)));
   }
}



//////////////////////////////
//
// getStaffBase12 -- return 1 if less than middle C; otherwise return 2.
//

int getStaffBase12(int pitch) {
   if (pitch < 60) {
      return 1;
   } else {
      return 2;
   }
}


//////////////////////////////
//
// getMaxPitch -- return the highest non-zero content.
//

int getMaxPitch(Array& midibins) {
   int i;
   for (i=midibins.getSize()-1; i>=0; i--) {
      if (midibins[i] != 0.0) {
         return i;
      }
   }
   return -1;
}



//////////////////////////////
//
// getMinPitch -- return the lowest non-zero content.
//

int getMinPitch(Array& midibins) {
   int i;
   for (i=0; i<midibins.getSize(); i++) {
      if (midibins[i] != 0.0) {
         return i;
      }
   }
   return -1;
}



//////////////////////////////
//
// getVoice --
//

void getVoice(char* voicestring, HumdrumFile& infile, int kernspine) {
   int i,j;
   strcpy(voicestring, "");
   Array<char> tempstring;
   PerlRegularExpression pre;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (kernspine != infile[i].getPrimaryTrack(j)) {
            continue;
         }
         if (strncmp(infile[i][j], "*I\"", strlen("*I\"")) == 0) {
            strcpy(voicestring, &(infile[i][j][3]));
            tempstring.setSize(strlen(voicestring)+1);
            strcpy(tempstring.getBase(), voicestring);
            pre.sar(tempstring, "\\s+", " ", "g");
            strcpy(voicestring, tempstring.getBase());
         }
      }
   }


   // don't print editorial marks for voice names
   if (strchr(voicestring, '[') == NULL) {
      return;
   }

   Array<char> buffer;
   buffer.setSize(strlen(voicestring)+1);
   strcpy(buffer.getBase(), voicestring);
   pre.sar(buffer, "[\\[\\]]", "", "g");
   strcpy(voicestring, buffer.getBase());
}



//////////////////////////////
//
// getTitle --
//

void getTitle(char* titlestring, HumdrumFile& infile) {
   strcpy(titlestring, "_00");

   char buffer[1024] = {0};
   infile.getBibValue(buffer, "SCT");
   if (strlen(buffer) > 0) {
      strcat(titlestring, buffer);
      strcat(titlestring, " ");
      infile.getBibValue(buffer, "OPR");
      if (strlen(buffer) > 0) {
         strcat(titlestring, buffer);
         strcat(titlestring, " _02");
      }
      infile.getBibValue(buffer, "OTL");
      if (strlen(buffer) > 0) {
         strcat(titlestring, buffer);
      }
      
   }
}



//////////////////////////////
//
// clearHistograms --
//

void clearHistograms(Array >& bins, int start) {
   int i;
   for (i=start; i<bins.getSize(); i++) {
      bins[i].setSize(40*11);
      bins[i].setAll(0);
      bins[i].allowGrowth(0);
   }
   
}




//////////////////////////////
//
// printAnalysis --
//

void printAnalysis(Array& midibins) {
   if (percentileQ) {
      printPercentile(midibins, percentile);
      return;
   }  else if (rangeQ) {
      double notesinrange = countNotesInRange(midibins, rangeL, rangeH);
      cout << notesinrange << endl;
      return;
   }

   int i;
   double normval = 1.0;

   // print the pitch histogram

   double fracL = 0.0;
   double fracH = 0.0;
   double fracA = 0.0;
   double sum = midibins.sum();
   if (normQ) {
      normval = sum;
   }
   double runningtotal = 0.0;


   cout << "**keyno\t";
   if (pitchQ) {
      cout << "**pitch";
   } else {
      cout << "**kern";
   }
   cout << "\t**count";
   if (addfractionQ) {
      cout << "\t**fracL";
      cout << "\t**fracA";
      cout << "\t**fracH";
   }
   cout << "\n";


   int base12;
   char buffer[1024] = {0};
   for (i=0; i<midibins.getSize(); i++) {
      if (midibins[i] <= 0.0) {
         continue;
      }      
      if (diatonicQ) {
         base12 = Convert::base7ToBase12(i);
      } else {
         base12 = i;
      }
      cout << base12 << "\t";
      if (pitchQ) {
         cout << Convert::base12ToPitch(buffer, base12);
      } else {
         cout << Convert::base12ToKern(buffer, base12);
      }
      cout << "\t";
      cout << midibins[i] / normval;
      fracL = runningtotal/sum;
      runningtotal += midibins[i];
      fracH = runningtotal/sum;
      fracA = (fracH + fracL)/2.0;
      fracL = (int)(fracL * 10000.0 + 0.5)/10000.0;
      fracH = (int)(fracH * 10000.0 + 0.5)/10000.0;
      fracA = (int)(fracA * 10000.0 + 0.5)/10000.0;
      if (addfractionQ) {
         cout << "\t" << fracL;
         cout << "\t" << fracA;
         cout << "\t" << fracH;
      }
      cout << "\n";
   }
   cout << "*-\t*-\t*-";
   if (addfractionQ) {
      cout << "\t*-";
      cout << "\t*-";
      cout << "\t*-";
   }
   cout << "\n";

   cout << "!!tessitura:\t" << getTessitura(midibins) << " semitones\n";

   double mean = getMean(midibins);
   if (diatonicQ) {
      mean = Convert::base7ToBase12(mean);
   }
   cout << "!!mean:\t" << mean;
   cout << " (";
   cout << Convert::base12ToKern(buffer, int(mean+0.5));
   cout << ")" << "\n";

   int median = getMedian(midibins);
   if (diatonicQ) {
      median = Convert::base7ToBase12(median);
   }
   cout << "!!median:\t" << median;
   cout << " (";
   cout << Convert::base12ToKern(buffer, median);
   cout << ")" << "\n";

}



//////////////////////////////
//
// getMedian -- return the pitch on which half of pitches are above
//     and half are below.
//

int getMedian(Array& midibins) {
   double sum = midibins.sum();

   double cumsum = 0.0;
   int i;
   for (i=0; i<midibins.getSize(); i++) {
      if (midibins[i] <= 0.0) {
         continue;
      }      
      cumsum += midibins[i]/sum;
      if (cumsum >= 0.50) {
         return i;
      }
   }

   return -1;
}


//////////////////////////////
//
// getMean -- return the interval between the highest and lowest
//     pitch in terms if semitones.
//

double getMean(Array& midibins) {
   double top    = 0.0;
   double bottom = 0.0;

   int i;
   for (i=0; i<midibins.getSize(); i++) {
      if (midibins[i] <= 0.0) {
         continue;
      }      
      top += midibins[i] * i;
      bottom += midibins[i];
     
   }

   return top / bottom;
}



//////////////////////////////
//
// getTessitura -- return the interval between the highest and lowest
//     pitch in terms if semitones.
//

int getTessitura(Array& midibins) {
   int minn = -1000;
   int maxx = -1000;
   int i;

   for (i=0; i<midibins.getSize(); i++) {
      if (midibins[i] <= 0.0) {
         continue;
      }      
      if (minn < 0) {
         minn = i;
      }
      if (maxx < 0) {
         maxx = i;
      }
      if (minn > i) {
         minn = i;
      }
      if (maxx < i) {
         maxx = i;
      }
   }
   if (diatonicQ) {
      maxx = Convert::base7ToBase12(maxx);
      minn = Convert::base7ToBase12(minn);
   }

   return maxx - minn;
}



//////////////////////////////
//
// countNotesInRange --
//

double countNotesInRange(Array& midibins, int low, int high) {
   int i;
   double sum = 0;
   for (i=low; i<=high; i++) {
      sum += midibins[i];
   }
   return sum;
}



//////////////////////////////
//
// printPercentile --
//

void printPercentile(Array& midibins, double percentile) {
   double sum = midibins.sum();
   double runningtotal = 0.0;
   int i;
   for (i=0; i<midibins.getSize(); i++) {
      if (midibins[i] <= 0) {
         continue;
      }
      runningtotal += midibins[i] / sum;
      if (runningtotal >= percentile) {
         cout << i << endl;
         return;
      }
   }

   cout << "unknown" << endl;
}



//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("c|range|count=s:60-71", "count notes in a particular range");
   opts.define("d|duration=b",      "weight pitches by duration");   
   opts.define("f|fraction=b",      "display histogram fractions");   
   opts.define("fill=b",            "change color of fill only");
   opts.define("p|percentile=d:0.0","display the xth percentile pitch");   
   opts.define("print=b",           "count printed notes rather than sounding");
   opts.define("pitch=b",           "display pitch info in **pitch format");
   opts.define("N|norm=b",          "normalize pitch counts");
   opts.define("score=b",           "convert range info to SCORE");
   opts.define("q|quartile=b",      "display quartile notes");
   opts.define("sx|scorexml|score-xml|ScoreXML|scoreXML=b", 
                                    "output ScoreXML format");
   opts.define("hover=b",           "include svg hover capabilities");
   opts.define("D|diatonic=b",      
         "diatonic counts ignore chormatic alteration");
   opts.define("no-define=b", "Do not use defines in output SCORE data");

   opts.define("debug=b",       "trace input parsing");   
   opts.define("author=b",      "author of the program");   
   opts.define("version=b",     "compilation information"); 
   opts.define("example=b",     "example usage"); 
   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, Mar 2005" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 31 Mar 2005" << endl;
      cout << "compiled: " << __DATE__ << endl;
      cout << MUSEINFO_VERSION << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   scoreQ       = opts.getBoolean("score");
   fillonlyQ    = opts.getBoolean("fill");
   xmlQ         = opts.getBoolean("score-xml");
   if (xmlQ) {
      scoreQ = 1;
   }
   quartileQ    = opts.getBoolean("quartile");
   debugQ       = opts.getBoolean("debug");
   normQ        = opts.getBoolean("norm");
   printQ       = opts.getBoolean("print");
   pitchQ       = opts.getBoolean("pitch");
   durationQ    = opts.getBoolean("duration");
   percentileQ  = opts.getBoolean("percentile");
   rangeQ       = opts.getBoolean("range");
   getRange(rangeL, rangeH, opts.getString("range"));
   addfractionQ = opts.getBoolean("fraction");
   percentile   = opts.getDouble("percentile");
   hoverQ       = opts.getBoolean("hover");
   diatonicQ    = opts.getBoolean("diatonic");

   // the percentile is a fraction from 0.0 to 1.0.
   // if the percentile is above 1.0, then it is assumed
   // to be a percentage, in which case the value will be
   // divided by 100 to get it in the range from 0 to 1.
   if (percentile > 1) {
      percentile = percentile / 100.0;
   }

}



//////////////////////////////
//
// getRange --
//

void getRange(int& rangeL, int& rangeH, const char* rangestring) {
   rangeL = -1; rangeH = -1;
   if (rangestring == NULL) {
      return;
   }
   if (rangestring[0] == '\0') {
      return;
   }
   int length = strlen(rangestring);
   char* buffer = new char[length+1];
   strcpy(buffer, rangestring);
   char* ptr;
   if (isdigit(buffer[0])) {
      ptr = strtok(buffer, " \t\n:-");
      sscanf(ptr, "%d", &rangeL);
      ptr = strtok(NULL, " \t\n:-");
      if (ptr != NULL) {
         sscanf(ptr, "%d", &rangeH);
      }
   } else {
      ptr = strtok(buffer, " :");
      if (ptr != NULL) {
         rangeL = Convert::kernToMidiNoteNumber(ptr);
         ptr = strtok(NULL, " :");
         if (ptr != NULL) {
            rangeH = Convert::kernToMidiNoteNumber(ptr);
         }
      }
   }

   if (rangeH < 0) {
      rangeH = rangeL;
   }

   if (rangeL <   0) { rangeL =   0; }
   if (rangeH <   0) { rangeH =   0; }
   if (rangeL > 127) { rangeL = 127; }
   if (rangeH > 127) { rangeH = 127; }
   if (rangeL > rangeH) {
      int temp = rangeL;
      rangeL = rangeH;
      rangeH = temp;
   }

}



//////////////////////////////
//
// example -- example usage of the maxent program
//

void example(void) {
   cout <<
   "                                                                        \n"
   << endl;
}




//////////////////////////////
//
// usage -- gives the usage statement for the quality program
//

void usage(const char* command) {
   cout <<
   "                                                                        \n"
   << endl;
}



//////////////////////////////
//
// generateAnalysis --
//

void generateAnalysis(HumdrumFile& infile, Array<Array<double> >& midibins,
      Array<int>& kernspines) {
   int i, j, k;

   char buffer[1024] = {0};
   int tokencount;
   int keynum;
   double duration;

   int vindex;

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      } 
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i].getExInterp(j), "**kern") != 0) {
            continue;
         }
         vindex = getVindex(infile[i].getPrimaryTrack(j), kernspines);
         if (strcmp(infile[i][j], ".") == 0) {  // ignore null tokens
            continue;
         }
         tokencount = infile[i].getTokenCount(j);
         for (k=0; k<tokencount; k++) {
            infile[i].getToken(buffer, j, k);         
            if (strcmp(buffer, ".") == 0) {    // ignore strange null tokens
               continue;
            }
            if (strchr(buffer, 'r') != NULL) { // ignore rests
               continue;
            }
            if (!printQ && !durationQ) {
               // filter out middle and ending tie notes if counting
               // by sounding pitch
               if (strchr(buffer, '_') != NULL) {
                  continue;
               }
               if (strchr(buffer, ']') != NULL) {
                  continue;
               }
            }
            if (!diatonicQ) {
               keynum = Convert::kernToMidiNoteNumber(buffer);
            } else {
               keynum = Convert::kernToDiatonicPitch(buffer);
            }
            if (keynum > 127) {
               cout << "ERROR: Funny pitch: " << keynum 
                    << " = " << buffer << endl;
            } else if (durationQ) {
               duration = Convert::kernToDuration(buffer);
               midibins[0][keynum] += duration;
               midibins[vindex+1][keynum] += duration;
            } else {
               midibins[0][keynum] += 1.0;
               midibins[vindex+1][keynum] += 1.0;
            }
         }
      }
   }
}



//////////////////////////////
//
// getVindex -- return the voice index of the primary track.
//

int getVindex(int track, Array& kernspines) {
   int i;
   for (i=0; i<kernspines.getSize(); i++) {
      if (kernspines[i] == track) {
         return i;
      }
   }
   return -1;
}




// md5sum: 70b888b51e1f7619512b93ea733ee7d7 prange.cpp [20130404]