//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon Mar 23 12:48:50 PST 2009
// Last Modified: Tue Mar 24 18:57:09 PST 2009
// Last Modified: Mon Sep  3 09:27:57 PDT 2012 Copied from noteheadtime.cpp
// Filename:      ...sig/examples/all/noteheadtime-webern.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/noteheadtime-webern.cpp
// Syntax:        C++; museinfo
//
// Description:   print performance data as noteheads outputting SCORE data.
// 		  
//

#include <math.h>
#include "humdrum.h"
#include "ScorePageBase.h"
#include "PerlRegularExpression.h"

#ifndef OLDCPP
   #include <iostream>
#else
   #include <iostream.h>
#endif

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      processInput(ScorePageBase& scorepage, HumdrumFile& infile,
                                 SigCollection<HumdrumFile>& marksfiles);
void      printPage(ScorePageBase& scorepage, int page, double maxtime,
                                 double mintime, Array<double>& times, 
                                 Array<double>& dyns, Array<int>& pitches, 
                                 Array<int>& pstaff, Array<int>& measures,
                                 SigCollection<HumdrumFile>& marks);
void      printSystem(ScorePageBase& scorepage, int basestaff, 
		                 Array<double>& times, Array<double>& dyns, 
                                 Array<int>& pitches, Array<int>& pstaff, 
                                 Array<int>& measures, double minsystime, 
                                 double maxsystime, int finalBarlineQ);
void      normalizeDyns(Array<double>& dyns, double maxdyn, 
                                 double mindyn);
void      getPitchInfo(const char* string, int& p1, int& p2);
void      getBetterPitchInfo(const char* string, int& p1, int& p2, 
                                 int auxdata);
double    printTicks(ScorePageBase& scorepage, int staff, int vpos, 
                                 int direction, double starttime, 
                                 double stoptime, double leftmar, 
                                 double rightmar, double majdur, 
                                 int secticks, int terticks, 
                                 double majticklen, double secticklen, 
                                 double terticklen);
void      drawtick(ScorePageBase& scorepage, int staff, double hpos, 
                                 double vpos, int direction, double len);
void      printMarks(ScorePageBase& scorepage, int basestaff, 
                                 Array<double>& marktimes, 
                                 Array<int>& markmeter, double starttime, 
                                 double stoptime, double leftmar, 
                                 double rightmar, double markcolor, 
                                 double markdashsize, double markspacesize,
                                 double markstyle, double markthick);
void      printPreMarks(ScorePageBase& scorepage, int basestaff, 
                                 Array<double>& marktimes, 
                                 Array<int>& markmeter, double starttime, 
                                 double stoptime, double leftmar, 
                                 double rightmar, double markcolor, 
                                 double markdashsize, double markspacesize,
                                 double markstyle, double markthick);
void      getMarkInfo(HumdrumFile& marks, Array<double>& marktimes,
                                 Array<int>& markmeter);
void      printTitle(ScorePageBase& scorepage, int basestaff, 
                                 HumdrumFile& infile);
void      printFooter(ScorePageBase& scorepage, int basestaff, 
		                 HumdrumFile& infile, double footerscale);
void      printComment(ScorePageBase& scorepage, int basestaff, 
		                 const char* string, double hpos, double scale);
char*     createHexColor(char* hexcolor, double value);

// User interface variables:
Options   options;
int       verboseQ       = 0;
double    timewidth      = 5.0;
int       systemsperpage = 6;
int       page           = 1;
int       pagecountQ     = 0;
int       systemZ        = 1;
int       systemQ        = 0;
int       systemcountQ   = 0;
int       svgQ           = 1;
double    staffscale     = 0.75;
double    leftmargin     = 10;
double    rightmargin    = 195;
double    quietnote      = 0.9;
double    loudnote       = 0.0;
int       scorever       = 6;   // 6 = winscore and p26val used for color
const char* ofile        = "";

// timeline variables
int       tickDisplayQ   = 1;
double    majortickdur   = 1.0;
int       secondaryticks = 2;
int       tertiaryticks  = 10;
double    majorticklen   = 1.5;
double    secticklen     = 1.0;
double    terticklen     = 0.5;

#define STYLE_LEFT 1
int       ticklabelstyle = STYLE_LEFT;
int       printstartmeasureQ = 1;

// additional information for correct pitch spelling
int      auxQ           = 0;
const char* auxfile     = "";
int      accidentalQ    = 0;   // used with --accidental option

// marks options
const char* marksfile   = "";
double    markdashsize  = 0.0;
double    markspacesize = 0.0;
//double    markstyle   = 7.0; // dashed line
double    markstyle     = 0.0; // solid line
int       marksonlyQ    = 0;
double    markcolor     = 997777; // red by default
double    markthick     = 7.0;

// secondary marks options
const char* marksfile2   = "";
double    markdashsize2  = 0.0;
double    markspacesize2 = 0.0;
//double    markstyle2   = 7.0; // dashed line
double    markstyle2     = 0.0; // solid line
double    markcolor2     = 777799;  // blue by default
double    markthick2     = 0.0;

// tertiary marks options
const char* marksfile3   = "";
double    markdashsize3  = 0.0;
double    markspacesize3 = 0.0;
double    markstyle3     = 7.0; // dashed line
double    markcolor3     = 779977;  // green by default
double    markthick3     = 4.5;

// title printing on first page, and footers for rest of pages
int       titleQ          = 1;
int       footerQ         = 1;

// display of timing numbers
int       notenumbersQ    = 0;
double    notenumbercolor = -1;
double    notenumbersize  = 1.0;

// comment string for bottom right of page
const char* commentstring = "";    // used with --comment
double      commenthpos   = 180.0; // used with --cp


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

int main(int argc, char** argv) {
   // process the command-line options
   checkOptions(options, argc, argv);
   HumdrumFile infile;

   if (options.getArgCount() < 1) {
      infile.read(cin);
   } else {
      infile.read(options.getArg(1));
   }
   SigCollection<HumdrumFile> marks;
   marks.setSize(1);
   if (strcmp(marksfile, "") != 0) {
      marks[0].read(marksfile);
   }
   if (strcmp(marksfile2, "") != 0) {
      marks.setSize(2);	     
      marks[1].read(marksfile2);
   }
   if (strcmp(marksfile3, "") != 0) {
      marks.setSize(3);	     
      marks[2].read(marksfile3);
   }

   ScorePageBase scorepage;
   processInput(scorepage, infile, marks);
   if (options.getBoolean("output")) {
      scorepage.writeBinary(ofile);
   } else {
      scorepage.printAscii(cout);
   }

   return 0;
}

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


//////////////////////////////
//
// processInput --
//

void processInput(ScorePageBase& scorepage, HumdrumFile& infile, 
      SigCollection<HumdrumFile>& marks) {
   Array<double> times;
   times.setSize(infile.getNumLines());
   times.setSize(0);

   Array<double> dyns;
   dyns.setSize(infile.getNumLines());
   dyns.setSize(0);

   Array<int> pitches;
   pitches.setSize(infile.getNumLines());
   pitches.setSize(0);

   Array<int> pstaff;
   pstaff.setSize(infile.getNumLines());
   pstaff.setSize(0);

   Array<int> measures;
   measures.setSize(infile.getNumLines());
   measures.setSize(0);

   double mintime =  10000000;
   double maxtime = -10000000;

   double mindyn =  10000000;
   double maxdyn = -10000000;

   int i;
   double value;
   int    intvalue;
   int p1, p2;

   Array<int> auxacci;
   auxacci.setSize(0);
   int pindex = 0;
   if (auxQ) {
      HumdrumFile auxhum;
      auxhum.read(auxfile);
      auxacci.setSize(auxhum.getNumLines());
      auxacci.setSize(0);
      for (i=0; i<auxhum.getNumLines(); i++) {
         if (auxhum[i].getType() != E_humrec_data) {
            continue;
         }
         // 8th column must be extra accidental information (saccid)
         sscanf(auxhum[i][7], "%d", &intvalue);
         auxacci.append(intvalue);
      }
   }

   for (i=0; i<infile.getNumLines(); i++) {
      switch(infile[i].getType()) {
         case E_humrec_data:
            value = 0;
            sscanf(infile[i][0], "%lf", &value);
            if (value < mintime) {
               mintime = value;
            }
            if (value > maxtime) {
               maxtime = value;
            }
            times.append(value);
            value = 0;
            sscanf(infile[i][1], "%lf", &value);
            if (value < mindyn) {
               mindyn = value;
            }
            if (value > maxdyn) {
               maxdyn = value;
            }
            dyns.append(value);
            value = 0;
            if (auxQ) {
	       getBetterPitchInfo(infile[i][2], p1, p2, auxacci[pindex++]);
            } else {
	       getPitchInfo(infile[i][2], p1, p2);
            }
	    pitches.append(p1);
	    pstaff.append(p2);

            sscanf(infile[i][4], "%d", &intvalue);
            measures.append(intvalue);
	    
            break;
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         case E_humrec_data_comment:
         case E_humrec_data_kern_measure:
         case E_humrec_interpretation:
         default:
            break;
      }
   }

   if (auxQ && (times.getSize() != auxacci.getSize())) {
      // something went wrong: number of notes in primary
      // and auxiliary file do not match, so throw away
      // any auxiliary data since it will be invalid.
      auxQ = 0;
      auxacci.setSize(0);
      cerr << "ERROR IN AUXILIARY FILE. GIVING UP" << endl;
      exit(1);
   }

   times.allowGrowth(0);
   dyns.allowGrowth(0);

   normalizeDyns(dyns, maxdyn, mindyn);

   int systems = int((maxtime - mintime) / timewidth + 0.99999);
   if (systemQ) {
      systemsperpage = 1;
      page = systemZ;
   }
   int pages   = int((double)systems / systemsperpage + 0.99999);

   if (systems > 1000) {
      // reduce page count if time is in milliseconds
      maxtime = maxtime / 1000.0;
      mintime = mintime / 1000.0;
      for (i=0; i<times.getSize(); i++) {
         times[i] = times[i] / 1000.0;
      }
      systems = int((maxtime - mintime) / timewidth + 0.99999);
      pages   = int((double)systems / systemsperpage + 0.99999);
   }
	     

   if (verboseQ) {
      cout << "@@Mintime:     " << mintime << endl;
      cout << "@@Maxtime:     " << maxtime << endl;
      cout << "@@Systems:     " << systems << endl;
      cout << "@@Pages:       " << pages   << endl;
      cout << "@@PageToPrint: " << page    << endl;
   }

   if (pagecountQ) {
      cout << pages << endl;
      exit(0);
   } else if (systemcountQ) {
      cout << systems << endl;
      exit(0);
   } else {

      if ((page == 1) && titleQ) {
         printTitle(scorepage, systemsperpage*2, infile);
      }
      if ((page > 1) && footerQ) {
         double footerscale = 0.75;
         if (page == pages) {
            if (systems % systemsperpage != 0) {
               // have to shrink footer if there is no
               // staff present at the bottom of the page:
               footerscale *= staffscale;
            }
         }
         printFooter(scorepage, 1, infile, footerscale);
      }

      if (strcmp(commentstring, "") != 0) {
         double footerscale = 0.75;
         if (page == pages) {
            if (systems % systemsperpage != 0) {
               // have to shrink footer if there is no
               // staff present at the bottom of the page:
               footerscale *= staffscale;
            }
         }
         printComment(scorepage, 1, commentstring, commenthpos, footerscale);
      }
      printPage(scorepage, page, maxtime, mintime, times, 
            dyns, pitches, pstaff, measures, marks);
   }
}



//////////////////////////////
//
// printComment --
//

void printComment(ScorePageBase& scorepage, int basestaff, const char* comment,
      double hpos, double scale) {

   ScoreRecord srecord;

   srecord.setPValue(1, P1_Text);
   srecord.setPValue(2, basestaff);
   srecord.setPValue(3, hpos);
   srecord.setPValue(4, -8.0);
   srecord.setPValue(6, scale);
   srecord.setTextData(comment);
   srecord.setTextFont("_00");
   scorepage.addItem(srecord);
}



//////////////////////////////
//
// printFooter --
//

void printFooter(ScorePageBase& scorepage, int basestaff, HumdrumFile& infile,
      double footerscale) {
   char buffer[1024] = {0};
   char buffer2[1024] = {0};
   ScoreRecord srecord;

   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      infile[i].getBibKey(buffer);
      if (strcmp(buffer, "performance-id") != 0) {
         continue;
      }
      infile[i].getBibValue(buffer);
      strcat(buffer, ", page");
      sprintf(buffer2, "%d", page);
      strcat(buffer, buffer2);

      srecord.setPValue(1, P1_Text);
      srecord.setPValue(2, basestaff);
      srecord.setPValue(3, 0);
      srecord.setPValue(4, -8.0);
      srecord.setPValue(6, footerscale);
      srecord.setTextData(buffer);
      srecord.setTextFont("_00");
      scorepage.addItem(srecord);
      
      break;
   }
}



//////////////////////////////
//
// printTitle --
//

void printTitle(ScorePageBase& scorepage, int basestaff, HumdrumFile& infile) {
   int i;
   char performer[1024] = {0};
   char mazurkaname[1024] = {0};
   char pidstring[1024] = {0};
   char buffer[1024] = {0};

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      infile[i].getBibKey(buffer);
      if (strcmp(buffer, "performer") == 0) {
         infile[i].getBibValue(performer);
      } else if (strcmp(buffer, "title") == 0) {
         infile[i].getBibValue(mazurkaname);
      } else if (strcmp(buffer, "performance-id") == 0) {
         infile[i].getBibValue(pidstring);
      }
   }

   ScoreRecord srecord;

   srecord.setPValue(1, P1_Text);
   srecord.setPValue(2, basestaff);
   srecord.setPValue(3, 10);
   srecord.setTextFont("_00");

   if (strcmp(performer, "") != 0) {
      srecord.setPValue(4, 31);
      srecord.setPValue(6, 1.377);
      srecord.setTextData(performer);
      scorepage.addItem(srecord);
   }

   if (strcmp(mazurkaname, "") != 0) {
      srecord.setPValue(4, 25.5);
      srecord.setPValue(6, 1.377);
      srecord.setTextData(mazurkaname);
      scorepage.addItem(srecord);
   }

   if (strcmp(pidstring, "") != 0) {
      srecord.setPValue(4, 21);
      srecord.setPValue(6, 0.00);
      srecord.setTextData(pidstring);
      scorepage.addItem(srecord);
   }
}



//////////////////////////////
//
// getBetterPitchInfo --
//   p1 = base-40 number for pitch
//   p2 = who knows what that was for
//

void getBetterPitchInfo(const char* string, int& p1, int& p2, int auxdata) {
   if (islower(string[0])) {
      p2 = 1;
   } else {
      p2 = 0;
   }

   int diatonic = 0;
   int accidental = 0;
   int octave = 0;

   char ch;

   int length = strlen(string);
   int i;
   for (i=0; i<length; i++) {
     ch = tolower(string[i]);
     switch (ch) {
        case 'c': diatonic = 2;  break;
        case 'd': diatonic = 8;  break;
        case 'e': diatonic = 14; break;
        case 'f': diatonic = 19; break;
        case 'g': diatonic = 25; break;
        case 'a': diatonic = 31; break;
        case 'b': diatonic = 37; break;
        case '#': accidental++;  break;
        case '-': accidental--;  break;
      }
      if (isdigit(ch)) {
         sscanf(string+i, "%d", &octave);
         break;
      }
   }
   
   int base = diatonic + 400 + accidental;
   base = base % 40;
   p1 = base + octave * 40;

   // new stuff

   int newacci = auxdata;
   newacci = newacci % 10; // remove higher order information
   switch (newacci - accidental) {
      case 0: return;  // accidental is correctly encoded
      case -2:         // accidental is listed as # but should be -
         p1 = p1 + 4;  // on next higher diatonic note
         break;
      case +2:         // accidental is listed as - but should be #
         p1 = p1 - 4;  // on next lower diatonic note
         break;
      case -1:         // such as E should be F-flat
         p1 = p1 + 4;  
         break;
      case +1:         // such as F should be E-sharp
         p1 = p1 - 4;  
         break;
 
      // deal with double sharp problems here later
   }
}


//////////////////////////////
//
// getPitchInfo --
//

void getPitchInfo(const char* string, int& p1, int& p2) {

   PerlRegularExpression pre;
   if (!pre.search(string, "\\d")) {
      p1 = Convert::kernToBase40(string);
      if (p1 < 160) {
         p2=0;
      } else {
         p2=1;
      }
      return;
   }

   if (islower(string[0])) {
      p2 = 1;
   } else {
      p2 = 0;
   }

   int diatonic = 0;
   int accidental = 0;
   int octave = 0;

   char ch;

   int length = strlen(string);
   int i;
   for (i=0; i<length; i++) {
     ch = tolower(string[i]);
     switch (ch) {
        case 'c': diatonic = 2;  break;
        case 'd': diatonic = 8;  break;
        case 'e': diatonic = 14; break;
        case 'f': diatonic = 19; break;
        case 'g': diatonic = 25; break;
        case 'a': diatonic = 31; break;
        case 'b': diatonic = 37; break;
        case '#': accidental++;  break;
        case '-': accidental--;  break;
      }
      if (isdigit(ch)) {
         sscanf(string+i, "%d", &octave);
         break;
      }
   }
   
   int base = diatonic + 400 + accidental;
   base = base % 40;
   p1 = base + octave * 40;
}



//////////////////////////////
//
// normalizeDyns --
//

void normalizeDyns(Array& dyns, double maxdyn, double mindyn) {
   int i;
   double diff = maxdyn - mindyn;
   for (i=0; i<dyns.getSize(); i++) {
      dyns[i] = (dyns[i] - mindyn) / diff;
   }


}



//////////////////////////////
//
// printPage --
//

void printPage(ScorePageBase& scorepage, int page, double maxtime, double mintime, 
      Array<double>& times, Array<double>& dyns, Array<int>& pitches, 
      Array<int>& pstaff, Array<int>& measures, 
      SigCollection<HumdrumFile>& marks) {

   int systems = int((maxtime - mintime) / timewidth + 0.99999);
   int pages   = int((double)systems / systemsperpage + 0.99999);

   if (page > pages || page < 0) {
      exit(0);
   }

   if (page < pages) {
      systems = systemsperpage;
   } else {
      systems = systems - systemsperpage * (pages-1);
   }

   int i;

   Array<Array<double> > marktimes;
   Array<Array<int> >    markmeter;

   int maxmarks = 10;
   marktimes.setSize(maxmarks);
   marktimes.allowGrowth(0);

   markmeter.setSize(maxmarks);
   markmeter.allowGrowth(0);

   for (i=0; i<maxmarks; i++) {
      marktimes[i].setSize(0);
      markmeter[i].setSize(0);
   }

   for (i=0; (i<maxmarks) && (i<marks.getSize()); i++) {
      getMarkInfo(marks[i], marktimes[i], markmeter[i]);
   }

   double minsystime;
   double maxsystime;
   int systembase = (page - 1) * systemsperpage;
   int basestaff;

   if ((page == 1) && (marktimes.getSize() > 0)) {
      minsystime = mintime + systembase * timewidth;
      maxsystime = minsystime + timewidth;

      // print any marks before the start of the music
      if (marktimes.getSize() > 0) {
         if (marktimes[0].getSize() > 0) {
            printPreMarks(scorepage, systemsperpage * 2 - 1, marktimes[0], 
                  markmeter[0], minsystime, maxsystime, leftmargin, 
                  rightmargin, markcolor, markdashsize, markspacesize,
                  markstyle, markthick);
         }
      }
      if (marktimes.getSize() > 1) {
         if (marktimes[1].getSize() > 0) {
            printPreMarks(scorepage, systemsperpage * 2 - 1, marktimes[1], 
                  markmeter[1], minsystime, maxsystime, leftmargin, 
                  rightmargin, markcolor2, markdashsize2, markspacesize2,
                  markstyle2, markthick2);
         }
      }
      if (marktimes.getSize() > 2) {
         if (marktimes[2].getSize() > 0) {
            printPreMarks(scorepage, systemsperpage * 2 - 1, marktimes[2], 
                  markmeter[2], minsystime, maxsystime, leftmargin, 
                  rightmargin, markcolor3, markdashsize3, markspacesize3,
                  markstyle3, markthick3);
         }
      }

   }

   int lastsystem = 0;

   for (i=0; i<systems; i++) {
      minsystime = mintime + (systembase + i) * timewidth;
      maxsystime = minsystime + timewidth;
      basestaff = (systemsperpage - i) * 2 - 1;

      if ((i == systems-1) && (page == pages)) {
         lastsystem = 1;
      }
	        

      if (marktimes.getSize() > 0) {
         if (marktimes[0].getSize() > 0) {
            printMarks(scorepage, basestaff, marktimes[0], markmeter[0], 
                  minsystime, maxsystime, leftmargin, rightmargin, markcolor,
                  markdashsize, markspacesize, markstyle, markthick);
         } 
      } 

      if (marktimes.getSize() > 1) {
         if (marktimes[1].getSize() > 0) {
            printMarks(scorepage, basestaff, marktimes[1], markmeter[1], 
                  minsystime, maxsystime, leftmargin, rightmargin, markcolor2,
                  markdashsize2, markspacesize2, markstyle2, markthick2);
         } 
      } 

      if (marktimes.getSize() > 2) {
         if (marktimes[2].getSize() > 0) {
            printMarks(scorepage, basestaff, marktimes[2], markmeter[2], 
                  minsystime, maxsystime, leftmargin, rightmargin, markcolor3,
                  markdashsize3, markspacesize3, markstyle3, markthick3);
         } 
      } 

      if (!marksonlyQ) {
         printSystem(scorepage, basestaff, times, dyns, pitches, pstaff, 
            measures, minsystime, maxsystime, lastsystem);
      }

   }
}



//////////////////////////////
//
// getMarkInfo --
//

void getMarkInfo(HumdrumFile& marks, Array<double>& marktimes, 
      Array<int>& markmeter) {

   if (marks.getNumLines() <= 0) {
      marktimes.setSize(0);
      markmeter.setSize(0);
      return;
   }

   marktimes.setSize(marks.getNumLines());
   marktimes.setSize(0);
   markmeter.setSize(marks.getNumLines());
   markmeter.setSize(0);

   double value;
   int intval, intval2;
   int i;
   for (i=0; i<marks.getNumLines(); i++) {
      if (marks.getType(i) == E_humrec_data) {
         value = 0;
         sscanf(marks[i][0], "%lf", &value);
         marktimes.append(value);
         if (marks[i].getFieldCount() > 1) {
            intval = 0;
            if (strchr(marks[i][1], ':') != NULL) {
               sscanf(marks[i][1], "%d:%d", &intval2, &intval);
            } else if (strchr(marks[i][1], '.') != NULL) {
               sscanf(marks[i][1], "%d.%d", &intval2, &intval);
            }
            markmeter.append(intval);
         } else {
            intval = 0;
            markmeter.append(intval);
         }
      }
   }
}



//////////////////////////////
//
// printPreMarks --
//

void printPreMarks(ScorePageBase& scorepage, int basestaff, 
      Array<double>& marktimes, Array<int>& markmeter, double starttime, 
      double stoptime, double leftmar, double rightmar, double markcolor,
      double markdashsize, double markspacesize, double markstyle, 
      double markthick) {

   double sysdur = stoptime - starttime;
   double hpos;

   ScoreRecord srecord;

   int i;
   for (i=0; i<marktimes.getSize(); i++) {
      if ((marktimes[i] < starttime)) {
         hpos = (marktimes[i] - starttime) / sysdur;
         hpos = hpos * (rightmar - leftmar) + leftmar;

         srecord.clear();
         srecord.setPValue(1, P1_Barline);
         srecord.setPValue(2, basestaff);
         srecord.setPValue(3, hpos);
         srecord.setPValue(4,   2.0);
         srecord.setPValue(5,   markstyle);
         if (markmeter[i] == 1) {
            srecord.setPValue(6, markthick); // make first beat thicker
         }
	 if (markstyle == 7.0) {
            srecord.setPValue(8,   markdashsize);
            srecord.setPValue(9,   markspacesize);
         }
         srecord.setPValue(10, 11.0);
         srecord.setPValue(11,  3.0);
         srecord.setPValue(26,  markcolor);
         scorepage.addItem(srecord);
      } 
   }
}



//////////////////////////////
//
// printMarks --
//

void printMarks(ScorePageBase& scorepage, int basestaff, 
      Array<double>& marktimes, Array<int>& markmeter, double starttime, 
      double stoptime, double leftmar, double rightmar, double markcolor, 
      double markdashsize, double markspacesize, double markstyle, 
      double markthick) {

   double sysdur = stoptime - starttime;
   double hpos;

   ScoreRecord srecord;

   int i;
   for (i=0; i<marktimes.getSize(); i++) {
      if (marktimes[i] >= stoptime) {
         break;
      }
      if (marktimes[i] < starttime) {
         continue;
      }
	        
      hpos = (marktimes[i] - starttime) / sysdur;
      hpos = hpos * (rightmar - leftmar) + leftmar;

      if (hpos < 0.0) {
         continue;
      }

      srecord.clear();
      srecord.setPValue(1, P1_Barline);
      srecord.setPValue(2, basestaff);
      srecord.setPValue(3, hpos);
      srecord.setPValue(4,   2.0);
      srecord.setPValue(5,   markstyle);
      if (markmeter[i] == 1) {
         srecord.setPValue(6, markthick); // make first beat of measure thicker
      }
      if (markstyle == 7.0) {
         srecord.setPValue(8,   markdashsize);
         srecord.setPValue(9,   markspacesize);
      }
      srecord.setPValue(10, 11.0);
      srecord.setPValue(11,  3.0);
      srecord.setPValue(26,  markcolor);
      scorepage.addItem(srecord);
   }
}



//////////////////////////////
//
// printTicks --
//

double printTicks(ScorePageBase& scorepage, int staff, int vpos, int direction, 
      double starttime, double stoptime, double leftmar, double rightmar, 
      double majdur, int secticks, int terticks, double majticklen,
      double secticklen, double terticklen) {

   double sysdur = stoptime - starttime;

   Array<double> secticktimes;
   secticktimes.setSize(secticks*2);
   secticktimes.setSize(0);

   int i, j;
   int secfound; 
   double sectime, tertime;
 
   // print major ticks:
   double temp = starttime/majdur;
   double startmajor;
   if (temp - int(temp) == 0.0) {
      startmajor = starttime;
   } else {
      startmajor = int(temp+1.0) * majdur;
   }

   double output = startmajor;
   
   // draw secondary ticks which occur befor the first major tick
   double hpos = 0;
   secticktimes.setSize(0);
   for (i=1; i<secticks; i++) {
      sectime = startmajor - i * majdur/secticks;
      if (sectime >= starttime) {
         secticktimes.append(sectime);
         hpos = (sectime - starttime) / sysdur;
         hpos = hpos * (rightmar - leftmar) + leftmar;
         drawtick(scorepage, staff, hpos, vpos, direction, secticklen);
      }
   }
   for (i=1; i<terticks; i++) {
      tertime = startmajor - i * majdur/terticks;
      secfound = 0;
      for (j=0; j<secticktimes.getSize(); j++) {
         if (fabs(tertime - secticktimes[j]) < 0.0001) {
            secfound = 1;
         }
      }
      if ((secfound == 0) && (tertime >= starttime)) {
         hpos = (tertime - starttime) / sysdur;
         hpos = hpos * (rightmar - leftmar) + leftmar;
         drawtick(scorepage, staff, hpos, vpos, direction, terticklen);
      }
   }

   // draw major ticks, secondary ticks and tertiary ticks starting
   // at the first major tick
   while (startmajor <= stoptime) {
      hpos = (startmajor - starttime) / sysdur;
      hpos = hpos * (rightmar - leftmar) + leftmar;
      drawtick(scorepage, staff, hpos, vpos, direction, majticklen);

      secticktimes.setSize(0);
      for (i=1; i<secticks; i++) {
         sectime = startmajor + i * majdur/secticks;
         secticktimes.append(sectime);
         if (sectime <= stoptime) {
            hpos = (sectime - starttime) / sysdur;
            hpos = hpos * (rightmar - leftmar) + leftmar;
            drawtick(scorepage, staff, hpos, vpos, direction, secticklen);
         }
      }

      for (i=1; i<terticks; i++) {
         tertime = startmajor + i * majdur/terticks;
         secfound = 0;
         for (j=0; j<secticktimes.getSize(); j++) {
            if (fabs(tertime - secticktimes[j]) < 0.0001) {
               secfound = 1;
            }
         }
         if ((secfound == 0) && (tertime <= stoptime)) {
            hpos = (tertime - starttime) / sysdur;
            hpos = hpos * (rightmar - leftmar) + leftmar;
            drawtick(scorepage, staff, hpos, vpos, direction, terticklen);
         }
      }

      startmajor += majdur;
   }

   return output;
}



//////////////////////////////
//
// drawtick --
//

void drawtick(ScorePageBase& scorepage, int staff, double hpos, double vpos, 
     int direction, double len) {
   ScoreRecord srecord;
   srecord.setPValue(1, P1_Line);
   srecord.setPValue(2, staff);
   srecord.setPValue(3, hpos);
   srecord.setPValue(4, vpos);
   srecord.setPValue(5, vpos);
   srecord.setPValue(6, hpos+len);
   srecord.setPValue(13, -1 * direction * 90.0);
   scorepage.addItem(srecord);
}



//////////////////////////////
//
// printSystem --
//

void printSystem(ScorePageBase& scorepage, int basestaff, Array<double>& times, 
      Array<double>& dyns, Array<int>& pitches, Array<int>& pstaff, 
      Array<int>& measures, double minsystime, double maxsystime, 
      int finalbarlineQ) {

   if (verboseQ) {
      cout << "@minsystime:\t" << minsystime << endl;
      cout << "@maxsystime:\t" << maxsystime << endl;
      cout << "@basestaff:\t"  << basestaff  << endl;
   }

   ScoreRecord srecord;

   double startmajortick = -1000;
   if (tickDisplayQ) {
      startmajortick = printTicks(scorepage, basestaff, 3, -1, minsystime, 
         maxsystime, leftmargin, rightmargin, majortickdur, secondaryticks, 
         tertiaryticks, majorticklen, secticklen, terticklen);
      // print above treble staff
      printTicks(scorepage, basestaff+1, 11, 1, minsystime, maxsystime, 
         leftmargin, rightmargin, majortickdur, secondaryticks, tertiaryticks, 
         majorticklen, secticklen, terticklen);

      if (ticklabelstyle == STYLE_LEFT) {
         srecord.setPValue(1, P1_Number);
         srecord.setPValue(2, basestaff);
         srecord.setPValue(3,  0.0);
         srecord.setPValue(4, -0.5);
         srecord.setPValue(5, startmajortick);
         srecord.setPValue(6,  0.5);
         scorepage.addItem(srecord);
         srecord.clear();
      }
   }

   srecord.setPValue(1, P1_Staff);
   srecord.setPValue(2, basestaff);
   if (staffscale != 1.0) {
      srecord.setPValue(5, staffscale);
   }
   scorepage.appendItem(srecord);

   srecord.setPValue(2, basestaff+1);
   // needs to be adjusted when more than one system on a page:
   srecord.setPValue(4, -5);
   scorepage.appendItem(srecord);

   srecord.clear();

   srecord.setPValue(1, P1_Clef);
   srecord.setPValue(2, basestaff+1);
   srecord.setPValue(3, 1.5);
   scorepage.appendItem(srecord);

   srecord.setPValue(2, basestaff);
   srecord.setPValue(5, 1);  // bass cleff
   scorepage.appendItem(srecord);

   srecord.clear();

   // add basic measure lines
   srecord.setPValue(1, P1_Barline);
   srecord.setPValue(2, basestaff);
   srecord.setPValue(4, 2.0);

   srecord.setPValue(3, 0.0);
   scorepage.appendItem(srecord);   // barline at start of system

   srecord.setPValue(5, 8.0);
   scorepage.appendItem(srecord);   // curly-brace at start of system

   srecord.setPValue(3, 200.0);
   if (finalbarlineQ) {
      srecord.setPValue(5, 2.0);    // add final barline
   } else {
      srecord.setPValue(5, 0.0);
   }
   scorepage.appendItem(srecord);   // right-side barline


   srecord.clear();


   // notes have to be placed on page so that those
   // with more ledger lines are printed before those
   // with fewer ledger lines; otherwise, the ledger lines
   // of the ones with more will over write those with less.
   SigCollection<SigCollection<ScoreRecord> > notelayers;
   notelayers.setSize(50);
   int i;
   for (i=0; i<notelayers.getSize(); i++) {
      notelayers[i].setSize(100);
      notelayers[i].setGrowth(1000);
      notelayers[i].setSize(0);
   }
   int layer;
   int closeSvg = 0;

   double p2val, p3val, p4val, p26val = 0;
   double p32val = 0.0;

   int foundfirstnote = 0;
   ScoreRecord prefix;
   ScoreRecord postfix;
   char buffer[1024] = {0};
   char hexcolor[1024] = {0};

   for (i=0; i<times.getSize(); i++) {
      if (times[i] < minsystime) {
         continue;
      }
      if (times[i] > maxsystime) {
         continue;
      }

      if (foundfirstnote == 0) {
         // place the measure number of the first note on the system
         // on the top-left side of the system:
         srecord.clear();
         srecord.setPValue(1, P1_Number);
         srecord.setPValue(2, basestaff+1);
         srecord.setPValue(3, 0.0);
         srecord.setPValue(4, 15.0);
         srecord.setPValue(5, measures[i]);
         srecord.setPValue(7, 1.0);
         scorepage.addItem(srecord);
         
         foundfirstnote = 1;
      }

      // cout << times[i] << "\t" << dyns[i] 
      //     << "\t" << pitches[i] 
      //     << "\t" << pstaff[i] << endl;

      srecord.clear();
      srecord.setPValue(1, P1_Note);

      p2val = basestaff + pstaff[i];
      srecord.setPValue(2, p2val);

      p3val = (times[i] - minsystime) / (maxsystime - minsystime);
      p3val = (rightmargin - leftmargin) * p3val + leftmargin;
      srecord.setPValue(3, p3val);

      p4val = Convert::base40ToScoreVPos(pitches[i], !pstaff[i]);
      srecord.setPValue(4, p4val);
      if (!svgQ) {
         srecord.setPValue(25, 100 - fabs(p4val)); // drawing layers:
      	   				           // outer-most notes first
      }
      // have to use the following system because P25 system
      // does not work very well:
      layer = abs(int(p4val+0.999));
      if (layer >= 50) {
         layer = 49;
      }

      if (accidentalQ) {
         int accval = Convert::base40ToAccidental(pitches[i]);
         switch (accval) {
            case 0: break; // natural: no accidental
            case 1:  // sharp
               srecord.setPValue(5, 2); // sharp 
               break;
            case -1:  // flat
               srecord.setPValue(5, 1); // flat 
               break;
            case 2:  // double-sharp
               srecord.setPValue(5, 5); // double-sharp
               break;
            case -2:  // double-flat
               srecord.setPValue(5, 4); // double-flat
               break;
         }
         if (accval != 0) {
            srecord.setPValue(29, 0.5); // make accidental 50% than normal
         }
      }

      if (scorever >= 6) {
	 // WinScore version
         p26val = dyns[i] * (quietnote - loudnote) + loudnote;
	 p26val = int(p26val * 100.0 + 0.5)/100.0;
         p26val = 1.0 - p26val*0.9 - 0.1;
         if (p26val < 0.0) { p26val = 0.0; }
         if (p26val > 1.0) { p26val = 1.0; }
	 
         if (!svgQ) {
            srecord.setPValue(26, p26val);
         } else {
            prefix.clear();
            prefix.setPValue(1, 16);
            prefix.setPValue(2, 1);
            prefix.setPValue(3, 1);
            createHexColor(hexcolor, p26val);
            sprintf(buffer, "_99%%svg%%<g color=\"%s\" stroke=\"%s\">", 
                  hexcolor, hexcolor);
            prefix.setText(buffer);
            notelayers[layer].append(prefix);
            closeSvg++;
         }
      } else {
         // for SCORE 5 (color)
         p32val = int(100.0 * dyns[i] * (quietnote - loudnote) + loudnote + 0.5);
         p32val = 100.0 - p32val;
         if (p32val > 99) { p32val = 99; }
         if (p32val < 1) { p32val = 1; }
         
         p32val = p32val/100.0;
         
         if (!svgQ) {
            srecord.setPValue(32, p32val);
         } else {
            prefix.clear();
            prefix.setPValue(1, 16);
            prefix.setPValue(2, 1);
            prefix.setPValue(3, 1);
            prefix.setText("_99%svg%<g color=\"red\" stroke=\"red\">");
            notelayers[layer].append(prefix);
            closeSvg++;
         }
      }
      notelayers[layer].append(srecord); // store note for later adding to page
      while (closeSvg > 0) {
         closeSvg--;
         postfix.clear();
         postfix.setPValue(1, 16);
         postfix.setPValue(2, 1); 
         postfix.setPValue(3, 1); 
         postfix.setText("_99%svg%<\\g>");
         notelayers[layer].append(postfix);
      }

      if (notenumbersQ) {
         int notenumber = int(times[i] * 1000.0 + 0.5) % 100;
         srecord.clear();
         srecord.setPValue(1, P1_Text);
         srecord.setPValue(2, p2val);
         srecord.setPValue(3, p32val);
	 if ((int(p4val)+100) % 2 == 0) {
            // note occurs on a space
	    srecord.setPValue(4, p4val+0.25-1);  // place on space after note
         } else {
            // note occurs on a line
	    srecord.setPValue(4, p4val+0.25); // place on space above note
         }
         char tempbuff[128] = {0};
         if (notenumber < 10) {
            sprintf(tempbuff, "0%d", notenumber);
         } else {
            sprintf(tempbuff, "%d", notenumber);
         }
         srecord.setTextData(tempbuff);
	 srecord.setTextFont("_08"); // Helvetica-Narrow

         srecord.setPValue(6, 0.5 * notenumbersize);
         srecord.setPValue(11, 2.2);       // horizontal offset
         srecord.setPValue(5, 0.9);        // squeeze digits together
         if (notenumbercolor < 0) {
            double color = p26val;    // match color of notehead with a minimum
            if (color > 0.75) {
               color = 0.75;
            }
            srecord.setPValue(16, color); 
         } else {
            srecord.setPValue(16, notenumbercolor); 
         }
         srecord.setPValue(14, -1); // needed for colored text
         // consider adding a printing layer here (P15 for text)
         notelayers[layer].append(srecord); // store timing number 
      }
   }

   for (i=notelayers.getSize()-1; i>=0; i--) {
      scorepage.addItem(notelayers[i]);  // add notes to page in proper ordering
   }
}



//////////////////////////////
//
// createHexColor -- convert a number in the range from 0.0 to 1.0 into
// a 3 byte hex color code.
//

char* createHexColor(char* hexcolor, double value) {
   int value2 = int(value * 255 + 0.5);
   if (value2 < 0) {
      value2 = 0;
   }
   if (value2 > 255) {
      value2 = 255;
   }
   sprintf(hexcolor, "#%2x%2x%2x", value2, value2, value2);
   return hexcolor;
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("c|count|page-count=b","list the number of pages input data produces");
   opts.define("system-count=b","list the number of system input data produces");
   opts.define("comment=s", "comment string for bottom right of page");
   opts.define("cp|comment-position=d:180.0", "horz. position of comment");
   opts.define("page=i:1", "output data for specified page");
   opts.define("sys|system=i:1", "output data for specified system");
   opts.define("s|scale|staff-scale=d:0.75", "staff size factor");
   opts.define("l|left|left-margin=d:10.0", "left margin for data");
   opts.define("timewidth|tw=d:5", "time width of system");
   opts.define("r|right|right-margin=d:195.0", "right margin for data");
   opts.define("v|verbose=b", "verbose information for debugging");
   opts.define("p|piano=d:0.9", "grayscale for soft notes");
   opts.define("f|forte=d:0.0", "grayscale for soft notes");
   opts.define("o|output=s", "output file name for binary data");
   opts.define("m|marks=s", "auxiliary vertical line data");
   opts.define("2|m2|marks2=s", "auxiliary vertical lines (blue color)");
   opts.define("3|m3|marks3=s", "auxiliary vertical lines (green color)");
   opts.define("markcolor=d:997777", "color for marks");
   opts.define("nns|note-number-size=d:1.0", "size of numbers for notes");
   opts.define("nnc|note-number-color=d:-1.0", "note number color");
   opts.define("aux=s", "auxiliary notes files with accidental info");
   opts.define("accidental|acci=b", "display accidental marks");
   opts.define("svg|SVG=b", "Use SVG extensions");

   opts.define("author=b",  "author of program"); 
   opts.define("version=b", "compilation info");
   opts.define("example=b", "example usages");   
   opts.define("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 2009" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 9 Mar 2009" << 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);
   }

   verboseQ     = opts.getBoolean("verbose");
   pagecountQ   = opts.getBoolean("page-count");
   systemcountQ = opts.getBoolean("system-count");
   page         = opts.getInteger("page");
   systemZ      = opts.getInteger("system");
   systemQ      = opts.getBoolean("system");
   staffscale   = opts.getDouble("staff-scale");
   leftmargin   = opts.getDouble("left-margin");
   rightmargin  = opts.getDouble("right-margin");
   quietnote    = opts.getDouble("piano");
   loudnote     = opts.getDouble("forte");
   if (opts.getBoolean("output")) {
      ofile = opts.getString("output");
   }
   svgQ        = opts.getBoolean("svg");

   marksfile   = opts.getString("marks");
   marksfile2  = opts.getString("marks2");
   marksfile3  = opts.getString("marks3");
   markcolor   = opts.getDouble("markcolor");
   auxfile     = opts.getString("aux");
   auxQ        = opts.getBoolean("aux");
   accidentalQ = opts.getBoolean("accidental");
   timewidth   = opts.getDouble("timewidth");

   notenumbersize   = opts.getDouble("note-number-size");
   notenumbercolor  = opts.getDouble("note-number-color");

   commentstring = opts.getString("comment");
   commenthpos   = opts.getDouble("comment-position");
}



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

void example(void) {


}



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

void usage(const char* command) {

}



// md5sum: 16f80a1b3bc2573fe650e13ae0b4ac48 noteheadtime-webern.cpp [20120910]