//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Oct  4 21:57:39 PDT 2011
// Last Modified: Tue Oct  4 21:57:43 PDT 2011
// Filename:      ...museinfo/examples/all/activity.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/activity.cpp
// Syntax:        C++; museinfo
//
// Description:   Cumulative articulation plot generator.  Counts the number
//                of notes within each measure of the score and then outputs 
//                either raw data or instructions for creating a plot.
//
// Interesting webpages:
//    http://gnuplot-tricks.blogspot.com

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

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

// function declarations
void      checkOptions(Options& opts, int argc, char* argv[]);
void      example(void);
void      usage(const char* command);
void      processFile(HumdrumFile& infile);
void      processFileSeparate(HumdrumFile& infile);
int       countObject(const char* buffer);
ostream&  drawArrows(HumdrumFile& infile, ostream& out, 
                                 Array<int>& barvals);
int       getTrackMap(Array<int>& trackmap, HumdrumFile& infile);
int       allEqual(Array<int>& array, int value);
double    getMaximum(Array<Array<double> >& array);
void      printTickLabels(ostream& out, int maxval);
void      printMensurations(ostream& out, HumdrumFile& infile);
void      printSeparateMensurations(ostream& out, HumdrumFile& infile);
void      printTitle(ostream& out, HumdrumFile& infile);
ostream&  encodeText(ostream& out, const char* string);
ostream&  printPartAbbreviations(ostream& out, HumdrumFile& infile);
ostream&  placeMensuration(ostream& out, const char* mensur, double xpos, 
                                 double ypos, const char* color, 
                                 const char* graph);
int       checkSounding(HumdrumFile& infile, int row, int col);

// global variables
Options   options;             // database for command-line arguments
int       gnuplotQ = 0;        // used with --gnuplot option
int       separateQ = 0;       // used with -s option
int       LabelCount = 0;      // used for printing gnuplot labels
int       ciconiaQ = 1;
int       Scale    = 2;
const char* yrange = "";       // used with --yrange option


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


int main(int argc, char** argv) {
   checkOptions(options, argc, argv);
   HumdrumFile infile;

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

   for (i=0; i<numinputs || i==0; i++) {
      infile.clear();

      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         infile.read(cin);
      } else {
         infile.read(options.getArg(i+1));
      }
      if (separateQ) {
         processFileSeparate(infile);
      } else {
         processFile(infile);
      }
   }
}



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

//////////////////////////////
//
// allEqual -- true if all are equal to value.
//

int allEqual(Array& array, int value) {
   int i;
   if (array.getSize() <= 1) {
      return 1;
   }
   for (i=1; i<array.getSize(); i++) {
      if (array[i] != array[i-1]) {
         return 0;
      }
   }
 
   return 1;
}



//////////////////////////////
//
// processFileSeparate --
//

void processFileSeparate(HumdrumFile& infile) {
   int i, j, k;
   char buffer[1024] = {0};
   int tcount;
   int measure = 0;
   
   int firstQ  = 0;
   PerlRegularExpression pre;
   Array<int> barvals(100000);
   Array<Array<double> > cumvals(100000);
   barvals.setSize(0);
   cumvals.setSize(0);

   Array<int> cumsum;
   Array<int> trackmap;
   int vcount = getTrackMap(trackmap, infile);
   cumsum.setSize(vcount);
   cumsum.setAll(0);
   Array<int> sounding;
   sounding.setSize(vcount);
   sounding.setAll(0);
   int vindex;

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isMeasure()) {
         if ((firstQ == 0) && (allEqual(cumsum, 0))) {
            firstQ++;
            if (pre.search(infile[i][0], "(\\d+)")) {
               measure = atoi(pre.getSubmatch(1));
            }
            continue;
         }
         firstQ++;
         if (barvals.last() == measure) {
            for (j=0; j<cumsum.getSize(); j++) {
               cumvals.last()[j] += cumsum[j];
            }
         } else {
            barvals.append(measure);
            cumvals.setSize(cumvals.getSize()+1);
            cumvals.last().setSize(vcount);
            cumvals.last().setAll(0);
            for (j=0; j<cumsum.getSize(); j++) {
               cumvals.last()[j] = cumsum[j];
               if ((cumsum[j] == 0) && (sounding[j] > 0)) {
                  // no notes attacked in the current measure
                  // but notes are sounding from the previous
                  // measure, so mark with special 0.5 code:
                  cumvals.last()[j] = 0.5;
               }
            }
            sounding.setAll(0);
         }
         if (pre.search(infile[i][0], "(\\d+)")) {
            measure = atoi(pre.getSubmatch(1));
         }
         cumsum.setAll(0);
         continue;
      }
      if (!infile[i].isData()) {
         continue;
      }

      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         tcount = infile[i].getTokenCount(j);
         vindex = trackmap[infile[i].getPrimaryTrack(j)];
         if (vindex == -1) {
            cerr << "Funny negative track index" << endl;
            cerr << "Line :   " << i+1 << endl;
            cerr << "Column : " << j+1 << endl;
            cerr << "Track :  " << infile[i].getPrimaryTrack(j);
            exit(1);
         }
         if (tcount == 1) {
            cumsum[vindex] += countObject(infile[i][j]);
            sounding[vindex] += checkSounding(infile, i, j);
         } else {
            for (k=0; k<tcount; k++) {
               infile[i].getToken(buffer, j, k);
               cumsum[vindex] += countObject(buffer);
               sounding[vindex] += checkSounding(infile, i, j);
            }
         }
      }
   }


   if (gnuplotQ) {
      // style template:
      // http://commons.wikimedia.org/wiki/File:Berlin_population2.svg
      cout << "set term png size " << Scale * 800 << "," << Scale * 250 
           << " enhanced font \"Vera," 
           << 10 * Scale << "\"\n";
      cout << "set o\n";      // send to standard output
      cout << "unset key\n";  // do not display legend
      cout << "set xlabel \"measure\" offset 0,0.75\n";
      cout << "set cbrange [0:1]\n";
      // cout << "set encoding iso_8859_1\n";
      // cout << "set encoding iso10646\n";

      printTitle(cout, infile);
      drawArrows(infile, cout, barvals);

      //cout << "set ylabel \"voice\" offset 1.75,0\n";
      cout << "set ylabel \"voice\"\n";
      // cout << "set ytics 1\n";
      printPartAbbreviations(cout, infile);
      //cout << "set mxtics 5\n"; 
      printTickLabels(cout, barvals.last());
      printSeparateMensurations(cout, infile);

      cout << "plot '-' w rgbimage\n";
   }


   if (gnuplotQ) {

      double maxval = getMaximum(cumvals);
      double color = 0.0;

      for (i=0; i<barvals.getSize(); i++) {
         for (j=0; j<cumvals[i].getSize(); j++) {
            color = 1.0 - (cumvals[i][j]+1)/maxval;
            if (cumvals[i][j] == 0) {
               color = 1.0;
            }
            // if (color > 0.0) {
               cout << barvals[i];
               cout << "\t" << j;
               cout << "\t" << color;
               cout << "\t" << color;
               cout << "\t" << color;
               cout << endl;
           //  }
         }
      }



   } else {

      // print the results
      int sum = 0;
      for (i=0; i<barvals.getSize(); i++) {
         cout << barvals[i];
         sum = 0;
         for (j=0; j<cumvals[i].getSize(); j++) {
            sum += cumvals[i][j];
         }
         cout << "\t" << sum;
         for (j=0; j<cumvals[i].getSize(); j++) {
            cout << "\t" << cumvals[i][j];
         }
         cout << "\n";
      }
   }


   if (gnuplotQ) {
      cout << "e\n";
   }

}



//////////////////////////////
//
// checkSounding -- returns true if there is a note sounding.
//     (not just if an attack).
//

int checkSounding(HumdrumFile& infile, int row, int col) {
   int& i = row;
   int& j = col;
   int ii;
   int jj;

   if (!infile[i].isExInterp(j, "**kern")) {
      return 0;
   }

   ii = i;
   jj = j;
   if (strcmp(infile[i][j], ".") == 0) {
      ii = infile[i].getDotLine(j);
      jj = infile[i].getDotSpine(j);
   }

   if (strchr(infile[ii][jj], 'r') != NULL) {
      return 0;
   }

   return 1;
}



//////////////////////////////
//
// printPartAbbreviations --  search for the abbreviations line
//    in the file.  The abbreviations. start with *I' and then the
//    part's abbreviation name.  Assuming no spine splits
//    before abbreviation name.  Also assuming abbreviations occurs
//    before any data.
//

ostream& printPartAbbreviations(ostream& out, HumdrumFile& infile) {
   int i, j;   

   Array<Array<char> > names;
   names.setSize(infile.getMaxTracks());
   for (i=0; i<names.getSize(); i++) {
      names[i][0] = '\0';
   }

   int kmax = 0;
   int len;
   int kindex = 0;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         continue;
      }
      kindex = -1;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {  
            continue;
         }
         kindex++;
         if (kindex + 1 > kmax) {
            kmax = kindex + 1;
         }
         if (strncmp("*I'", infile[i][j], 3) == 0) {
            len = strlen(infile[i][j] + 3);
            names[kindex].setSize(len+1);
            strcpy(names[kindex].getBase(), (infile[i][j])+3);
            // out << "# " << kindex << " = " << infile[i][j]+3 << endl;
         }
      }
   }

   names.setSize(kmax);
   if (kmax > 0) {
      out << "set ytics (";
      for (i=0; i<kmax; i++) {
         out << "\"" << names[i] << "\" " << i;
         if (i < kmax - 1) {
            out << ", ";
         }
      }
      out << ")\n";
   }

   if (kmax < 6) {
      out << "set yrange [-1:" << kmax << "]\n";
   } else {
      out << "set yrange [-1:" << kmax+0.5 << "]\n";
   }

   return out;
}



//////////////////////////////
//
// printTitle --
// http://lavica.fesb.hr/cgi-bin/info2html?%28gnuplot%29title_
//   [OPR ]OTL (SCT)
//

void printTitle(ostream& out, HumdrumFile& infile) {
   char OPR[1024] = {0};       
   char OTL[1024] = {0};       
   char SCT[1024] = {0};       

   infile.getBibValue(OPR, "OPR");
   infile.getBibValue(OTL, "OTL");
   infile.getBibValue(SCT, "SCT");

   if ((strlen(OPR) == 0) && (strlen(OTL) == 0) && (strlen(SCT) == 0)) {
      // no title to print.
      return;
   }

   out << "set title \"";
   if (strlen(OPR) > 0) {
      encodeText(out, OPR);
      out << "  ";
   }
   encodeText(out, OTL);
   if (strlen(SCT) > 0) {
      out << " (";
      encodeText(out, SCT);
      out << ")";
   }
   // out << "\" offset 0,-1 font \"VeraSe," << Scale * 12 << "\"\n";
   out << "\" offset 0,-0.75 font \"tt1095m_.ttf," << Scale * 16 << "\"\n";
   // tt1095m_.ttf  roman
   // tt1096m_.ttf  italic
   // tt1099m_.ttf  bold
   // tt1100m_.ttf  bold-italic
}



//////////////////////////////
//
// encodeText -- convert HTML style accented characters into printable form.
//    http://www.pjb.com.au/comp/diacritics.html
//

ostream& encodeText(ostream& out, const char* string) {
   Array<char> newstring;
   newstring.setSize(strlen(string)+1);
   strcpy(newstring.getBase(), string);
   PerlRegularExpression pre;
   pre.sar(newstring, "é", "\351", "g");
   out << newstring;
   return out;
}



//////////////////////////////
//
// getMaximum -- return the largest value in the matrix.
//

double getMaximum(Array >& array) {
   int i, j;
   int output = 0;
   for (i=0; i<array.getSize(); i++) {
      for (j=0; j<array[i].getSize(); j++) {
         if (output < array[i][j]) {
            output = array[i][j];
         }
      }
   }
   return output;
}



///////////////////////////////
//
// printMensurations --
//

void printMensurations(ostream& out, HumdrumFile& infile) {
   int i, j;
   int barnum = 1;
   PerlRegularExpression pre;
   int first = 0;
   int pfirst = 0;
   char lastbuffer[1024] = {0};
   char buffer[1024] = {0};
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isMeasure() && pre.search(infile[i][0], "(\\d+)")) {
         barnum = atoi(pre.getSubmatch(1));
      }
      buffer[0] = '\0';
      if (infile[i].isGlobalComment()) {
         // check for a primary-mensuration marker in the case that
         // the mensuration in different voices are different.
         if (pre.search(infile[i][0], 
               "!!primary-mensuration:\\s*met\\((.*)\\)\\s*$", "i")) {
            strcpy(buffer, pre.getSubmatch(1));
         }
      } else {
         if (!infile[i].isInterpretation()) {
            continue;
         }
         first = 0;
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (!infile[i].isExInterp(j, "**kern")) {
               continue;
            }
            if (!pre.search(infile[i][j], "^\\*met\\((.*?)\\)")) {
               // all mensurations must be the same; otherwise ignore
               buffer[0] = '\0';
               break;
            }
            if (first == 0) {
               strcpy(buffer, pre.getSubmatch(1));
               first++;
            } else {
               if (strcmp(buffer, pre.getSubmatch(1)) != 0) {
                  buffer[0] = '\0';
                  break;
               }
            }
         }
      }

      if (pre.search(buffer, "^\\s*$")) {
         continue;
      }

      if (strcmp(buffer, lastbuffer) == 0) {
         // don't repeat a mensuration display
         continue;
      }

      strcpy(lastbuffer, buffer);


      double vpos    = 0.04;
      double offset  = 0.00;
      double poffset = 0.00;

      if (pfirst++ == 0) {
         poffset = 0.5;
      } else {
         poffset = 0.0;
      }
      if (separateQ) {
         poffset = 0;
      }

      placeMensuration(out, buffer, barnum+poffset, vpos + offset, 
            "dark-blue", "graph ");
   }
}



///////////////////////////////
//
// printSeparateMensurations --
//

void printSeparateMensurations(ostream& out, HumdrumFile& infile) {
   int i, j;
   int barnum = 1;
   PerlRegularExpression pre;
   char buffer[1024] = {0};

   Array<Array<char> > lastmensur;

   lastmensur.setSize(infile.getMaxTracks());
   for (i=0; i<lastmensur.getSize(); i++) {
      lastmensur[i][0] = '\0';
   }
  
   Array<int> track2part;
   track2part.setSize(infile.getMaxTracks()+1);
   track2part.setAll(-1);
   int kindex = -1;

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         kindex++;
         track2part[infile[i].getPrimaryTrack(j)] = kindex;
      }
      break;
   }

   int k;
   // double vpos    = 0.04;
   //double offset  = 0.00;
   // double poffset = 0.00;

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isMeasure() && pre.search(infile[i][0], "(\\d+)")) {
         barnum = atoi(pre.getSubmatch(1));
      }
      buffer[0] = '\0';
      if (infile[i].isGlobalComment()) {
         // check for a primary-mensuration marker in the case that
         // the mensuration in different voices are different.
         //if (pre.search(infile[i][0], 
         //      "!!primary-mensuration:\\s*met\\((.*)\\)\\s*$", "i")) {
         //   strcpy(buffer, pre.getSubmatch(1));
         //}
      } else {
         if (!infile[i].isInterpretation()) {
            continue;
         }
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (!infile[i].isExInterp(j, "**kern")) {
               continue;
            }

            if (pre.search(infile[i][j], "^\\*met\\((.*?)\\)")) {
               k = track2part[infile[i].getPrimaryTrack(j)];
               if (strcmp(lastmensur[k].getBase(), pre.getSubmatch(1)) != 0) {
                  lastmensur[k].setSize(strlen(pre.getSubmatch(1))+1);
                  strcpy(lastmensur[k].getBase(), pre.getSubmatch(1));
                  placeMensuration(out, pre.getSubmatch(1), barnum, k-0.1,
                        "red", "");
               }
            }
         }
      }
   }
}



//////////////////////////////
//
// placeMensuration --
//

ostream& placeMensuration(ostream& out, const char* mensur, double xpos, 
      double ypos, const char* color, const char* graph) {

   // found a mensuration, so print it at the current measure position
   // http://lavica.fesb.hr/cgi-bin/info2html?%28gnuplot%29label
 
   if (strcmp(mensur, "C|") == 0) {
      if (ciconiaQ)  {
         out << "set label " << ++LabelCount << " \"Z\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"ciconia_.pfb," 
             << Scale * 20 << "\" "
             << "textcolor rgb \"" << color << "\"\n";
      } else {
         out << "set label " << ++LabelCount << " \"C\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"Vera," << Scale * 10 
             << "\" textcolor rgb "
             << "\"" << color << "\"\n";
         out << "set label " << ++LabelCount << " \"|\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"Vera," << Scale * 10
             << "\" textcolor rgb "
             << "\"" << color << "\n";
      }

   } else if (strcmp(mensur, "C|2") == 0) {
      if (ciconiaQ)  {
         out << "set label " << ++LabelCount << " \"Z\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"ciconia_.pfb," 
             << Scale * 20 << "\" "
             << "textcolor rgb \"" << color << "\"\n";
         out << "set label " << ++LabelCount << " \"     2\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"Vera," 
             << Scale * 10 << "\" "
             << "textcolor rgb \"" << color << "\"\n";
      } else {
         out << "set label " << ++LabelCount << " \"" << mensur << "\" "  
             << "at first " << xpos+.5 << "," << graph 
             << ypos << " center offset 0.3,0 front font \"Vera," 
             << Scale * 10 << "\""
             << " textcolor rgb " << "\"" << color << "\"\n";
      }

   } else if (strcmp(mensur, "C|3") == 0) {
      if (ciconiaQ)  {
         out << "set label " << ++LabelCount << " \"Z\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"ciconia_.pfb," 
             << Scale * 20 << "\" "
             << "textcolor rgb \"" << color << "\"\n";
         out << "set label " << ++LabelCount << " \"     3\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"Vera," 
             << Scale * 10 << "\" "
             << "textcolor rgb \"" << color << "\"\n";
      } else {
         out << "set label " << ++LabelCount << " \"" << mensur << "\" "  
             << "at first " << xpos+.5 << "," << graph 
             << ypos << " center offset 0.3,0 front font \"Vera," 
             << Scale * 10 << "\""
             << " textcolor rgb " << "\"" << color << "\"\n";
      }

   } else if (strcmp(mensur, "O|") == 0) {
      if (ciconiaQ) {
         out << "set label " << ++LabelCount << " \"o\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"ciconia_.pfb," 
             << Scale * 20 << "\" "
             << "textcolor rgb \"" << color << "\"\n";
      } else {
         out << "set label " << ++LabelCount << " \"O\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"Vera," 
             << Scale * 10 << "\" textcolor rgb "
             << "\"" << color << "\"\n";
         out << "set label " << ++LabelCount << " \"|\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"Vera," << Scale * 10 
             << "\" textcolor rgb "
             << "\"" << color << "\"\n";
      }

   } else if (strcmp(mensur, "O.") == 0) {
      if (ciconiaQ) {
         out << "set label " << ++LabelCount << " \"P\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front "
             << "font \"ciconia_.pfb," << Scale * 20 << "\" textcolor rgb "
             << "\"" << color << "\"\n";
      } else {
         out << "set label " << ++LabelCount << " \"O\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"Vera," 
             << Scale * 10 << "\" textcolor rgb "
             << "\"" << color << "\"\n";
         out << "set label " << ++LabelCount << " \"\267\" "  
             << "at first " << xpos+.5 << "," << graph << ypos
             << " center offset 0.3,0 front font \"symbol.ttf," 
             << Scale * 9 << "\" "
             << "textcolor rgb \"" << color << "\"\n";
      }

   } else if (strcmp(mensur, "C.") == 0) {
      if (ciconiaQ) {
         out << "set label " << ++LabelCount << " \"c\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front "
             << "font \"ciconia_.pfb," << Scale * 20 << "\" textcolor rgb "
             << "\"" << color << "\"\n";
      } else {
         out << "set label " << ++LabelCount << " \"C\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"Vera," 
             << Scale * 10 << "\" textcolor rgb "
             << "\"" << color << "\"\n";
         out << "set label " << ++LabelCount << " \"\267\" "  
             << "at first " << xpos+.5 << "," << graph << ypos
             << " center offset 0.5,0 front font \"symbol.ttf," 
             << Scale * 9 << "\" "
             << "textcolor rgb \"" << color << "\"\n";
      }

   } else if (strcmp(mensur, "O") == 0) {
      if (ciconiaQ) {
         out << "set label " << ++LabelCount << " \"O\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front "
             << "font \"ciconia_.pfb," << Scale * 20 << "\" textcolor rgb "
             << "\"" << color << "\"\n";
      } else {
         out << "set label " << ++LabelCount << " \"O\" "  << "at first "
             << xpos+.5 << "," << graph << ypos 
             << " center offset 0.3,0 front font \"Vera,"
             << Scale * 10 << "\" textcolor rgb "
             << "\"" << color << "\"\n";
      }

   } else if (strcmp(mensur, "Cr") == 0) {
      out << "set label " << ++LabelCount << " \"" << "C" << "\" "  
          << "at first " << xpos+.5 << "," << graph 
          << ypos << " center rotate by 180 offset 0.3,graph 0.04 front font \"Vera," 
          << Scale * 10 << "\""
          << " textcolor rgb " << "\"" << color << "\"\n";

   } else {
      out << "set label " << ++LabelCount << " \"" << mensur << "\" "  
          << "at first " << xpos+.5 << "," << graph 
          << ypos << " center offset 0.3,0 front font \"Vera," 
          << Scale * 10 << "\""
          << " textcolor rgb " << "\"" << color << "\"\n";

   }

   return out;
}



//////////////////////////////
//
// getTrackMap --
//

int getTrackMap(Array& trackmap, HumdrumFile& infile) {
   int i, j;
   trackmap.setSize(infile.getMaxTracks()+1);
   trackmap.setAll(-1);
   int track;
   int counter = 0;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].isExInterp(j, "**kern")) {
            track = infile[i].getPrimaryTrack(j);
            trackmap[track] = counter++;
         }
      }
      break;
   }
   return counter;
}



//////////////////////////////
//
// processFile --
//

void processFile(HumdrumFile& infile) {
   int i, j, k;
   char buffer[1024] = {0};
   int tcount;
   int measure = 0;
   int cumsum  = 0;
   int firstQ  = 0;
   PerlRegularExpression pre;
   Array<int> barvals(100000);
   Array<int> cumvals(100000);
   barvals.setSize(0);
   cumvals.setSize(0);

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isMeasure()) {
         if ((firstQ == 0) && (cumsum == 0)) {
            firstQ++;
            if (pre.search(infile[i][0], "(\\d+)")) {
               measure = atoi(pre.getSubmatch(1));
            }
            continue;
         }
         firstQ++;
         if (barvals.last() == measure) {
            cumvals.last() += cumsum;
         } else {
            barvals.append(measure);
            cumvals.append(cumsum);
         }
         if (pre.search(infile[i][0], "(\\d+)")) {
            measure = atoi(pre.getSubmatch(1));
         }
         cumsum = 0;
         continue;
      }
      if (!infile[i].isData()) {
         continue;
      }

      for (j=0; j<infile[i].getFieldCount(); j++) {
         tcount = infile[i].getTokenCount(j);
         if (tcount == 1) {
            cumsum += countObject(infile[i][j]);
         } else {
            for (k=0; k<tcount; k++) {
               infile[i].getToken(buffer, j, k);
               cumsum += countObject(buffer);
            }
         }
      }
   }


   if (gnuplotQ) {
      // style template:
      // http://commons.wikimedia.org/wiki/File:Berlin_population2.svg
      cout << "set term png size " << Scale * 800 << "," 
           << Scale* 250 << " enhanced font \"Vera,"
           << Scale * 10 << "\"\n";
      cout << "set o\n";      // send to standard output
      cout << "unset key\n";  // do not display legend
      cout << "set xlabel \"measure\" offset 0,0.75\n";
      if (pre.search(yrange, "[\\d.+-]+:[\\d.+-]")) {
         if (pre.search(yrange, "^\\s*\\[.*\\]\\s*$")) {
            cout << "set yrange " << yrange << "\n";
         } else {
            cout << "set yrange [" << yrange << "]\n";
         }
      }
      cout << "set grid linewidth " << Scale * 0.5 << "\n";
      // cout << "set encoding iso_8859_1\n";
      // cout << "set encoding iso10646\n";

      printTitle(cout, infile);
      drawArrows(infile, cout, barvals);

      cout << "set ylabel \"attacks/measure\" offset 1.75,0\n";
      cout << "set style fill solid 0.25 transparent\n";

      printTickLabels(cout, barvals.last());
      printMensurations(cout, infile);

      cout << "plot '-' u 1:2 w filledcurves below x1 lt rgb 'dark-blue' lw 1\n";
   }


   // print the results
   for (i=0; i<barvals.getSize(); i++) {
      cout << barvals[i] << "\t" << cumvals[i] << endl;
   }

   if (gnuplotQ) {
      cout << "e\n";
   }

}



//////////////////////////////
//
// printTickLabels -- print tick labels based on how much space is
//      available.
//

void printTickLabels(ostream& out, int maxval) {

   //cout << "set mxtics 5\n"; 
   if (maxval > 271) {
      cout << "set xtics 25\n";  // horizontal ticks every 25 measures
   } else if (maxval < 139) {
      cout << "set xtics 5\n";  // horizontal ticks every 5 measures
   } else {
      cout << "set xtics 10\n";  // horizontal ticks every 10 measures
   }

}



//////////////////////////////
//
// drawArrows -- draw arrows at double barlines
//

ostream& drawArrows(HumdrumFile& infile, ostream& out, Array& barvals) {
   int count = 0;
   PerlRegularExpression pre;
   int i, j;
   double barnum = 0;
   const char* omd = "";
   char buffer[1024] = {0};

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isMeasure()) {
         continue;
      }
      // found a measure, check to see if a double barline
      if ((strstr(infile[i][0], "||") == NULL) && 
          (strstr(infile[i][0], "|!") == NULL)) {
         continue;
      }
      // continue if no measure number on double barline
      if (!pre.search(infile[i][0], "(\\d+)")) {
         continue;
      }
      barnum = atoi(pre.getSubmatch(1)) - 0.5;

/*      // search for an !!!OMD: record after the double barline, but
      // before any data
      omd = "";
      
      for (j=i+1; j<infile.getNumLines(); j++) {
         if (infile[j].isData()) {
            break;
         }
         if (infile[j].isBibliographic()) {
            if (strcmp(infile[j].getBibKey(buffer, 1000), "OMD") != 0) {
               continue;
            }
            omd = infile[j].getBibValue(buffer, 1000);
         }
      }
*/

      if (count == 0) {
         out << "set style line 10"
             << " linecolor rgb \"red\" linewidth " << Scale * 2.05 << "\n";
      }
      count++;
      // arrow parameters
      // http://lavica.fesb.hr/cgi-bin/info2html?%28gnuplot%29arrow
      out << "set arrow " << count << " from " << barnum << ",graph 0.01 to "
          << barnum << ",graph 0.99 nohead linestyle 10\n";
   }

   int toggle = 0;
   double lastfrac = -100;
   double offset = 0.0;
   double curfrac = -100;
   int length;
   barnum = 1;
   for (j=0; j<infile.getNumLines(); j++) {
      if (infile[j].isMeasure()) {
         if (pre.search(infile[j][0], "(\\d+)")) {
            barnum = atoi(pre.getSubmatch(1)) + 0.5;
         }
      }
      if (!infile[j].isBibliographic()) {
         continue;
      }

      if (strcmp(infile[j].getBibKey(buffer, 1000), "OMD") != 0) {
         continue;
      }
      omd = infile[j].getBibValue(buffer, 1000);
      length = strlen(omd);
      if (!pre.search(omd, "^\\s*$")) {
         //if (count == 0) {
         //   out << "set style line 10"
         //       << " linecolor rgb \"red\" linewidth 2\n";
         //}
         count++;
         curfrac = (double)barnum/barvals.last();
         if ((curfrac - lastfrac) < 0.14) {
            toggle = !toggle;
            if (toggle == 0) {
               offset = -0.00;
            } else {
               offset = -0.05;
            }
         } else {
            toggle = 0;
         }
         lastfrac = curfrac;
         double vpos = 0.93;
         // label parameters
         // http://lavica.fesb.hr/cgi-bin/info2html?%28gnuplot%29label
         if (pre.search(omd, ".*pars:\\s(.*)$", "i")) {
            out << "set label " << ++LabelCount << " \"" << pre.getSubmatch(1) 
                << "\" at first " << barnum << ",graph " << vpos + offset 
                << " left front font \"," << Scale * 20 << "\" textcolor rgb "
                << "\"orange\"\n";
         } else {
            out << "set label " << ++LabelCount << " \"" << omd 
                << "\" at first " << barnum << ",graph " << vpos + offset 
                << " left front font \"," << Scale * 20 << "\" textcolor rgb "
                << "\"orange\"\n";
         }
      }
   }
   return out;
}



//////////////////////////////
//
// countObject -- count a note if it is not part of a sustain or a rest
//    or a null token.
//

int countObject(const char* buffer) {
   if (strcmp(buffer, ".") == 0) {
      return 0;
   }
   if (strchr(buffer, 'r') != NULL) {  // rests
      return 0;
   }
   if (strchr(buffer, '_') != NULL) {  // middle of sustained note
      return 0;
   }
   if (strchr(buffer, ']') != NULL) {  // end of sustained note
      return 0;
   }

   return 1;
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("g|gnuplot=b", "create gnuplot script");
   opts.define("s|separate=b", "separate each voices values");
   opts.define("yrange=s:", "set the vertical axis numeric range");

   opts.define("author=b", "author of the 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, October 2011" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 4 October 2010" << 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);
   }
  
   if (opts.getBoolean("gnuplot")) {
      gnuplotQ = 1;
   }

   separateQ = opts.getBoolean("separate");
   if (opts.getBoolean("yrange")) {
      yrange = opts.getString("yrange");
   }

}



//////////////////////////////
//
// example -- example usage of the quality program
//

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



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

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


// md5sum: 775283ee5a0157bef408559191d436e9 activity.cpp [20120614]