//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Aug 11 13:26:47 PDT 2012
// Last Modified: Sat Aug 11 13:26:51 PDT 2012
// Filename:      ...sig/examples/all/scr2hum.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/score/scr2hum.cpp
// Syntax:        C++; museinfo
//
// Description:   Display staff information for a SCORE page file.
//

#include "ScorePageSet.h"
#include "Options.h"
#include "Convert.h"
#include "HumdrumFile.h"
#include "PerlRegularExpression.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>


// function declarations:
void      checkOptions(Options& opts, int argc, char* argv[]);
void      example(void);
void      usage(const char* command);
void      makeBarline(ScorePageSet& work, int i, 
                              HumdrumRecord& barline);
void      setBarlineStyle(HumdrumRecord& barline, int staffidx, 
                              ScoreRecord& arecord);
void      printStartingBar(ScorePageSet& work, HumdrumRecord& barline, 
                              HumdrumFile& newfile);
void      printMeter(ScorePageSet& work, HumdrumRecord& meter,
                              HumdrumFile& newfile, int objidx, double hpos, 
                              Array<int>& kernstart);
void      printTempoNear(ScorePageSet& work, HumdrumRecord& tempoline, HumdrumFile& newfile,
                              int objidx, double hpos, Array<int>& kernstart);
void      convertScoreMeterToKernMeter(char* buffer, ScoreRecord& srecord);
void      printKeySignature(ScorePageSet& work, HumdrumRecord& keysigline, 
                              HumdrumFile& newfile, int objidx, double hpos,
                              Array<int>& keysig, Array<int>& kernstart);
int       convertScoreKeysigToKernKeysig(char* buffer, ScoreRecord& srecord);
void      convertScoreClefToKernClef(char* buffer, ScoreRecord& srecord);
void      printClef(ScorePageSet& work, HumdrumRecord& clefline, 
                               HumdrumFile& newfile, int objidx, double hpos, 
                               Array<Array<char> >& clefs, 
                               Array<int>& kernstart);
void      printTitle(ScorePageSet& work, HumdrumFile& newfile);
void      printComposer(ScorePageSet& work, HumdrumFile& newfile);
void      printInstruments(ScorePageSet& work, HumdrumFile& newfile, 
                               HumdrumRecord& instruments, 
                               Array<int>& kernstart);
void      getInstrumentCode(Array<char>& code, Array<char>& name);
void      getInstrumentAbbreviation(Array<char>& code, Array<char>& name);
int       getPrettyScoreText(Array<char>& textdata, ScoreRecord& arecord);
int       getPrettyScoreText(Array<char>& textdata);
void      setHeaderRecord(HumdrumRecord& header, Array<int>& kernstart, 
                               Array<int>& kerncount, Array<int>& dynamstart, 
                               Array<int>& dynamcount, Array<int>& versestart, 
                               Array<int>& versecount);
void      setStaffAssignmentRecord(HumdrumRecord& staffassignment, Array<int>& kernstart, 
                               Array<int>& kerncount, Array<int>& dynamstart, 
                               Array<int>& dynamcount, Array<int>& versestart, 
                               Array<int>& versecount);
void      extractLyrics(ScorePageSet& work, int item, 
                               HumdrumRecord& dataline, Array<int>& versestart,
                               Array<int>& versecount);
void      getNoteVerseLinks(Array<int>& verses, ScorePageSet& work, int witem);

// interface variables:
Options options;
int     verboseQ    = 0;       // used with -v option
int     debugQ      = 0;       // used with --debug option
int     invisibleQ  = 1;       // used with -I option
int     stemQ       = 1;       // used with --no-stems
int     plainQ      = 0;       // used with -p option
int     htmlQ       = 1;
int     lyricsQ     = 1;       // used with -L option
int     dynamQ      = 0;       // not used yet

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

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

   int i;
   ScorePageSet work;
   for (i=1; i<=options.getArgCount(); i++) {
      work.appendRead(options.getArg(i), verboseQ);
   }
   work.analyzeContent();
   char buffer[1024] = {0};

   HumdrumFile newfile;
   HumdrumRecord header;
   HumdrumRecord staffassignment;
   HumdrumRecord trailer;
   HumdrumRecord barline;
   HumdrumRecord dataline;
   HumdrumRecord meterline;
   HumdrumRecord tempoline;
   HumdrumRecord keysigline;
   HumdrumRecord clefline;
   HumdrumRecord instruments;

   int sysstaffcount = work.getSystemStaffCount();
   int sysStaffIdx;

   int versesum = 0;
   Array<int> versecount(sysstaffcount);
   versecount.setAll(0);

   if (lyricsQ) {
      for (i=0; i<versecount.getSize(); i++) {
         versecount[i] = work.getVerseCount(i);
         versesum += versecount[i];
      }
   }

   // allow for dynamics later...
   int dynamsum = 0;
   Array<int> dynamcount(sysstaffcount);
   dynamcount.setAll(0);
   Array<int> dynamstart(sysstaffcount);
   dynamstart.setAll(-1);   // starting spine index for start of track's dynams

   Array<int> versestart(sysstaffcount);
   versestart.setAll(-1);   // starting spine index for start of track's verses


   int kernsum = sysstaffcount;
   Array<int> kerncount(sysstaffcount);
   kerncount.setAll(1);     // not allowing spine splits for now...
   Array<int> kernstart(sysstaffcount);
   kernstart.setAll(-1);    // starting spine index for start of track's notes
 
   int allsum = versesum + dynamsum + kernsum;

   int currtrack = 0;
   for (i=0; i<sysstaffcount; i++) {
      kernstart[i] = currtrack;
      currtrack   += kerncount[i];
      if (dynamQ) {
         dynamstart[i] = currtrack;
         currtrack    += dynamcount[i];
      }
      if (lyricsQ) {
         versestart[i] = currtrack;
         currtrack    += versecount[i];
      }
   }

   double lastAbsBeat = -1;
   double absBeat = -1;

   header.setSize(allsum);
   staffassignment.setSize(allsum);

   setHeaderRecord(header, kernstart, kerncount, dynamstart, 
         dynamcount, versestart, versecount);

   setStaffAssignmentRecord(staffassignment, kernstart, kerncount, 
         dynamstart, dynamcount, versestart, versecount);

   trailer.setSize(allsum);
   trailer.setAllFields("*-");
   barline.setSize(allsum);
   barline.setAllFields("=");
   dataline.setSize(allsum);
   dataline.setAllFields(".");
   meterline.setSize(allsum);
   meterline.setAllFields(".");
   tempoline.setSize(allsum);
   tempoline.setAllFields(".");
   keysigline.setSize(allsum);
   keysigline.setAllFields(".");
   clefline.setSize(allsum);
   clefline.setAllFields(".");
   instruments.setSize(allsum);
   instruments.setAllFields("*");

   double lastbarbeat = 0.0;
   int emmitbarline = 0;

   Array<int> keysig(sysstaffcount);
   keysig.setAll(-100);

   Array<Array<char> > clefs(allsum);
   for (i=0; i<allsum; i++) {
      clefs[i].setSize(1);
      clefs[i][0] = '\0';
   }

   int dataQ = 0;
   int p11;

   printComposer(work, newfile);
   printTitle(work, newfile);
   newfile.appendLine(header);
   newfile.appendLine(staffassignment);
   printInstruments(work, newfile, instruments, kernstart);

   char token[1024] = {0};

   double hpos;

   // the meter hpos needs to be reset each system, but doing that by
   // resetting whenever a durational item is found.
   double lastmeterhpos  = -1;
   double lastkeysighpos = -1;
   double lastclefhpos = -1;
   int stemdir = 0;

   int haseditorial = 0;

   for (i=0; i<work.getItemCount(); i++) {
      absBeat = work.getItem(i).getAbsBeat();
      hpos    = work.getItem(i).getHpos();
  
      if ((!invisibleQ) && (work.getItem(i).isRestItem() && 
         (work.getItem(i).getValue(P6) == -1.0))) {
         continue;
      }

      if (work.getItem(i).isBarlineItem() && (lastbarbeat != absBeat)) {
         makeBarline(work, i, barline);
         // newfile.appendLine(barline);
         emmitbarline = 1;
         if (!(dataline.equalFieldsQ() && (strcmp(dataline[0], ".") == 0))) {
            newfile.appendLine(dataline);
            dataline.setAllFields(".");
         }
         if (emmitbarline) {
            if (work.isAtEnd(i)) {
               barline.setAllFields("==");
            } 
            if ((newfile.getNumLines() > 0) && 
                (strncmp(newfile[newfile.getNumLines()-1][0], "=", 1) != 0)) {
               newfile.appendLine(barline);
            }
            barline.setAllFields("=");
            emmitbarline = 0;
         }
         lastbarbeat = absBeat;
      }

      if ((fabs(hpos - lastkeysighpos) > 0.01) && 
            (work.getItem(i).isKeysigItem())) {
         printKeySignature(work, keysigline, newfile, i, hpos, keysig, 
               kernstart);
         lastkeysighpos = hpos;
      }

      // why is this after keysig?
      if ((fabs(hpos - lastclefhpos) > 0.01) && 
            (work.getItem(i).isClefItem())) {
         printClef(work, clefline, newfile, i, hpos, clefs, kernstart);
         lastclefhpos = hpos;
      }

      if ((fabs(hpos - lastmeterhpos) > 0.01) && 
            (work.getItem(i).isMeterItem())) {
         printMeter(work, meterline, newfile, i, hpos, kernstart);
         printTempoNear(work, tempoline, newfile, i, hpos, kernstart);
         lastmeterhpos = hpos;
      }
     
      if (!(work.getItem(i).hasDuration())) {
         continue;
      }

      lastmeterhpos = -1;
      lastkeysighpos = -1;
      lastclefhpos = -1;

      if (!dataQ) {
         printStartingBar(work, barline, newfile);
         dataQ = 1;
      }

      if ((absBeat != lastAbsBeat) && (lastAbsBeat >= 0)) {
         if (!(dataline.equalFieldsQ() && (strcmp(dataline[0], ".") == 0))) {
            newfile.appendLine(dataline);
         }
         if (emmitbarline) {
            if (work.isAtEnd(i)) {
               barline.setAllFields("==");
            }
            newfile.appendLine(barline);
            emmitbarline = 0;
            barline.setAllFields("=");
         }
         dataline.setAllFields(".");
      }
      lastAbsBeat = absBeat;
      
      token[0] = '\0';
      sysStaffIdx = work.getSystemStaffIndex(i);

      if (work.getItem(i).hasTieStart()) {
         strcat(token, "[");
      }

      Convert::durationToKernRhythm(buffer, work.getItem(i).getDuration());
      strcat(token, buffer);

      if (work.getItem(i).getPitch() <= 0) {
         strcat(token, "r");
      } else {
         Convert::base40ToKern(buffer, work.getItem(i).getPitch());
         strcat(token, buffer);
      }
      // if the note has a printed accidental which is predictable even if
      // the printed accidental was there or not, then it is a cautionary
      // accidental.  The pitch analysis stage marks cautionary accidentals
      // by setting the named parameter "cautionary" to non-zero (1).

      if (work.getItem(i).hasCautionary()) {
         strcat(token, "X");
      }

      // print articulations
      p11 = work.getItem(i).getValue(P11);
      if (abs(p11) == 1) {  // editorial flat
         if (strchr(token, '-') == NULL) {
            strcat(token, "-");
         }
         strcat(token, "i");
         haseditorial = 1;
      } else if (abs(p11) == 2) {  // editorial sharp
         if (strchr(token, '#') == NULL) {
            strcat(token, "#");
         }
         strcat(token, "i");
         haseditorial = 1;
      } else if (abs(p11) == 3) {  // editorial natural
         if (strchr(token, 'n') == NULL) {
            strcat(token, "n");
         }
         strcat(token, "i");
         haseditorial = 1;
      } else if (abs(p11) == 14) {
         // fermata
         strcat(token, ";");
      }

      if (stemQ) {
         stemdir = work.getItem(i).getStemDirection();
         if (stemdir > 0) {
            strcat(token, "/");
         } else if (stemdir < 0) {
            strcat(token, "\\");
         }
      }

      if (work.getItem(i).hasTieEnd()) {
         strcat(token, "]");
      } else if (work.getItem(i).hasTieContinue()) {
         strcat(token, "_");
      }

      dataline.changeField(kernstart[sysStaffIdx], token);

      if (lyricsQ) {
         extractLyrics(work, i, dataline, versestart, versecount);
      }

   }

   if (emmitbarline) {
      // no longer active
      barline.setAllFields("==");
      newfile.appendLine(barline);
      emmitbarline = 0;
   }
   newfile.appendLine(trailer);
  
   if (haseditorial) {
      newfile.appendLine("!!!RDF**kern: i=editorial accidental");
   }

   cout << newfile;
   return 0;
}



//////////////////////////////
//
// printStartingBar --
//

void printStartingBar(ScorePageSet& work, HumdrumRecord& barline, 
      HumdrumFile& newfile) {

   // check to see if a pickup measure, if so, then don't print anything
   // otherwise, print an invisible barline

   // for now, just print an invisible barline all of the time.
   barline.setAllFields("=-");
   newfile.appendLine(barline);
}


//////////////////////////////
//
// makeBarline --
//

void makeBarline(ScorePageSet& work, int index, HumdrumRecord& barline) {
   // just do a simple barline for now without any style adjustments
   barline.setAllFields("=");

   int i;
   double absbeat = work.getAbsBeat(index);
   for (i=index; i<work.getItemCount(); i++) {
      if (work.getAbsBeat(i) != absbeat) {
         break;
      }
      if (!work.getItem(i).isBarlineItem()) {
         continue;
      }
      setBarlineStyle(barline, work.getSysStaffIndex(i), work.getItem(i));
   }
   for (i=index-1; i>=0; i--) {
      if (work.getAbsBeat(i) != absbeat) {
         break;
      }
      if (!work.getItem(i).isBarlineItem()) {
         continue;
      }
      setBarlineStyle(barline, work.getSystemStaffIndex(i), work.getItem(i));
   }
}



//////////////////////////////
//
// setBarlineStyle --
//

void setBarlineStyle(HumdrumRecord& barline, int staffidx, ScoreRecord& arecord) {
   int partialQ = 0;
   int barnum = -1;

   if ((arecord.getValue(P10) > 3) && (arecord.getValue(P11) < 11)) {
      partialQ = 1;
   }
   if (arecord.hasValue("barnum")) {
      barnum = arecord.getValue("barnum");
   }

   char buffer[1024] = {0};

   if (barnum >=0) {
      sprintf(buffer, "=%d", barnum);
   } else {
      strcat(buffer, "=");
   }

   if (partialQ) {
      strcat(buffer, "'");
   }

   barline.changeField(staffidx, buffer);
}



//////////////////////////////
//
// printClef --
//

void printClef(ScorePageSet& work, HumdrumRecord& clefline, 
      HumdrumFile& newfile, int objidx, double hpos, 
      Array<Array<char> >& clefs, Array<int>& kernstart) {

   clefline.setAllFields("*");
   int staffidx;

   int i;
   int foundQ = 0;
   double h;
   char buffer[1024] = {0};
   for (i=objidx; i<work.getItemCount(); i++) {
      if (!work.getItem(i).isClefItem()) {
         continue;
      }
      h = work.getItem(i).getHpos();
      if (h != hpos) {
         continue;
      }
      staffidx = work.getItem(i).getSystemStaffIndex();
      convertScoreClefToKernClef(buffer, work.getItem(i));
      if (strcmp(clefs[staffidx].getBase(), buffer) == 0) {
         // the new key signature is the same as the previous one
         // so ignore the new key signature (since it is probably at
         // the start of a new system).
         continue;
      } else {
         clefs[staffidx].setSize(strlen(buffer)+1);
         strcpy(clefs[staffidx].getBase(), buffer);
      }
      if (strcmp(buffer, "*") != 0) {
         foundQ = 1;
      }
      clefline.changeField(kernstart[staffidx], buffer);
   }

   if (foundQ) {
      newfile.appendLine(clefline);
   }
}



//////////////////////////////
//
// printKeySignature --
//

void printKeySignature(ScorePageSet& work, HumdrumRecord& keysigline, 
      HumdrumFile& newfile, int objidx, double hpos, Array<int>& keysig,
      Array<int>& kernstart) {

   keysigline.setAllFields("*");
   int staffidx;

   int i;
   int foundQ = 0;
   double h;
   int keystate;
   char buffer[1024] = {0};
   for (i=objidx; i<work.getItemCount(); i++) {
      if (!work.getItem(i).isKeysigItem()) {
         continue;
      }
      h = work.getItem(i).getHpos();
      if (fabs(h - hpos) > 1.0) {
         // if key sigantures are not vertically aligned enough, they will 
         // be printed on separte lines.
         continue;
      }
      staffidx = work.getItem(i).getSystemStaffIndex();
      keystate = convertScoreKeysigToKernKeysig(buffer, work.getItem(i));
      if (keysig[staffidx] == keystate) {
         // the new key signature is the same as the previous one
         // so ignore the new key signature (since it is probably at
         // the start of a new system).
         continue;
      } else {
         keysig[staffidx] = keystate;
      }
      if (strcmp(buffer, "*") != 0) {
         foundQ = 1;
      }
      keysigline.changeField(kernstart[staffidx], buffer);
   }

   if (foundQ) {
      for (i=0; i<keysig.getSize(); i++) {
         if ((keysig[i] < -7) && (strcmp(keysigline[kernstart[i]], "*") == 0)) {
            keysigline.changeField(kernstart[i], "*k[]");
            keysig[i] = 0;
         }
      }
      newfile.appendLine(keysigline);
   }
}



//////////////////////////////
//
// printTempoNear -- print a tempo marking in all **kern spines if one is found near
//    the given hpos location on the staff.
//

void printTempoNear(ScorePageSet& work, HumdrumRecord& tempoline, HumdrumFile& newfile,
      int objidx, double hpos, Array<int>& kernstart) {
   int ssidx = work.getItem(objidx).getPageSystemIndex();
   int tssidx;
   int thpos;
   ScoreRecord* srecord = NULL;

   double limiting = 20.0;  // maximum before/after search range on staff to look for tempo

   double tempofound = -1;
   double tempohpos = -1;

   int i;

   // search forward for a tempo indication on page
   for (i=objidx; i<work.getObjectCount(); i++) {
      srecord = &(work.getItem(i));

      tssidx = srecord->getPageSystemIndex();

      if (tssidx != ssidx) {
         break;
      }
      thpos = srecord->getHpos();

      if (fabs(thpos - hpos) > limiting) {
         break;
      }
      if (!srecord->isTextItem()) {
         continue;
      }
      if (srecord->getValue("tempo") > 0.0) {
         tempofound = i;
         tempohpos = thpos;
         break;
      }
   }

   // search backward for a tempo indication on page
   for (i=objidx; i>=0; i--) {
      srecord = &(work.getItem(i));
      tssidx = srecord->getPageSystemIndex();
      if (tssidx != ssidx) {
         break;
      }
      thpos = srecord->getHpos();
      if (fabs(thpos - hpos) > limiting) {
         break;
      }
      if (!srecord->isTextItem()) {
         continue;
      }
      if (srecord->getValue("tempo") > 0.0) {
         if (fabs(thpos - hpos) < fabs(tempohpos - hpos)) {
            tempofound = i;
            tempohpos = thpos;
         }
         break;
      }
   }

   if (tempofound <= 0) {
      return;
   }

   char buffer[1024] = {0};
   tempoline.setAllFields("*");
   sprintf(buffer, "*MM%d", (int)work.getItem(tempofound).getValue("tempo"));
   
   for (i=0; i<kernstart.getSize(); i++) {
      tempoline.changeField(kernstart[i], buffer);
   }
   newfile.appendLine(tempoline);
}



//////////////////////////////
//
// printMeter --
//

void printMeter(ScorePageSet& work, HumdrumRecord& meter, HumdrumFile& newfile,
      int objidx, double hpos, Array<int>& kernstart) {

   meter.setAllFields("*");
   int staffidx;

   int i;
   int foundQ = 0;
   double h;
   char buffer[1024] = {0};
   for (i=objidx; i<work.getItemCount(); i++) {
      if (!work.getItem(i).isMeterItem()) {
         continue;
      }
      h = work.getItem(i).getHpos();
      if (fabs(h - hpos) > 1.0) {
         // if time sigantures are not vertically aligned enough, they will be printed on
         // separte lines.
         continue;
      }
      staffidx = work.getItem(i).getSystemStaffIndex();
      convertScoreMeterToKernMeter(buffer, work.getItem(i));
      if (strcmp(buffer, "*") != 0) {
         foundQ = 1;
      }
      meter.changeField(kernstart[staffidx], buffer);
   }

   if (foundQ) {
      newfile.appendLine(meter);
   }

}



//////////////////////////////
//
// convertScoreClefToKernClef -- not dealing with all P4!=0 possibilities yet.
//

void convertScoreClefToKernClef(char* buffer, ScoreRecord& srecord) {
   int p5int = srecord.getValue(P5);
   double frac = srecord.getValue(P5) - p5int;
   if (frac < 0) {
      frac = -frac;
   }
   int octave = int(frac * 10.0 + 0.490);
   int p4 = srecord.getValue(P4);

   strcpy(buffer, "");

   if (octave == 0) {
      switch (p5int) {
         case 0: strcpy(buffer, "*clefG2"); return;
         case 1: 
            if (p4 == 0) {                 
               strcpy(buffer, "*clefF4");  // bass clef
            } else if (p4==-2) {            
               strcpy(buffer, "*clefF3");  // bariton clef
            }
            return;
         case 2: 
            if (p4 == 0) {
               strcpy(buffer, "*clefC3");  // alto clef
            } else if (p4 == -4) {
               strcpy(buffer, "*clefC1");  // soprano clef
            } else if (p4 == -2) {
               strcpy(buffer, "*clefC2");  // mezzo-soprano clef
            }
            return;
         case 3: strcpy(buffer, "*clefC4"); return; // tenor clef
         case 4: strcpy(buffer, "*clefX"); return;
      }

   } else {
      switch (p5int) {
         case 0: 
            strcpy(buffer, "*clefGv2"); // vocal tenor clef
            return;
         case 1:
            strcpy(buffer, "*clefFv2"); // bass down clef
            return;
      }
   }
}



//////////////////////////////
//
// convertScoreKeysigToKernKeysig --
//

int convertScoreKeysigToKernKeysig(char* buffer, ScoreRecord& srecord) {
   int p5 = srecord.getValue(P5);
   if ((p5 > 100) || (p5 <= -100)) {
      // ignoring cancellation key signatures for now...
      strcpy(buffer, "*");
      return 0;
   }
   switch (p5) {
      case 0: strcpy(buffer, "*k[]"); return 0;
      case 1: strcpy(buffer, "*k[f#]"); return 1;
      case 2: strcpy(buffer, "*k[f#c#]"); return 2;
      case 3: strcpy(buffer, "*k[f#c#g#]"); return 3;
      case 4: strcpy(buffer, "*k[f#c#g#d#]"); return 4;
      case 5: strcpy(buffer, "*k[f#c#g#d#a#]"); return 5;
      case 6: strcpy(buffer, "*k[f#c#g#d#a#e#]"); return 6;
      case 7: strcpy(buffer, "*k[f#c#g#d#a#e#b#]"); return 7;
      case -1: strcpy(buffer, "*k[b-]"); return -1;
      case -2: strcpy(buffer, "*k[b-e-]"); return -2;
      case -3: strcpy(buffer, "*k[b-e-a-]"); return -3;
      case -4: strcpy(buffer, "*k[b-e-a-d-]"); return -4;
      case -5: strcpy(buffer, "*k[b-e-a-d-g-]"); return -5;
      case -6: strcpy(buffer, "*k[b-e-a-d-g-c-]"); return -6;
      case -7: strcpy(buffer, "*k[b-e-a-d-g-c-f-]"); return -7;
   }

   // shouldn't get here
   strcpy(buffer, "*");
   return 0;
}



//////////////////////////////
//
// convertScoreMeterToKernMeter --
//

void convertScoreMeterToKernMeter(char* buffer, ScoreRecord& srecord) {
   int p5 = srecord.getValue(P5);
   int p6 = srecord.getValue(P6);
   int p8 = srecord.getValue(P8);
   int p9 = srecord.getValue(P9);

   if (p8 != 0) {
      if (p9 != 0) {
         sprintf(buffer, "*M%d/%d/+%d/%d", p5, p6, p8, p9);
         return;
      } else {
         sprintf(buffer, "*M%d+%d/%d", p5, p8, p6);
         return;
      }
   }

   if ((p5==99) && (p6==1)) {
      strcpy(buffer, "*met(c)");
      return;
   }
   if ((p5==98) && (p6==1)) {
      strcpy(buffer, "*met(c|)");
      return;
   }
   if ((p5!=0) && (p6!=0)) {
      sprintf(buffer, "*M%d/%d", p5, p6);
      return;
   }
   if ((p5==0) && (p6!=0)) {
      sprintf(buffer, "*M%d", p6);
      return;
   }
}


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

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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("v|verbose=b", "verbose display of information");
   opts.define("I|no-invisible=b", "do not dispay invisible rests");
   opts.define("S|no-stems=b", "do not dispay note stems");
   opts.define("p|plain=b", "display SCORE text as plain ASCII");
   opts.define("L|no-lyrics=b", "do not display lyrcs");

   opts.define("debug=b");              // determine bad input line num
   opts.define("author=b");             // author of program
   opts.define("version=b");            // compilation info
   opts.define("example=b");            // example usages
   opts.define("h|help=b");             // short description
   opts.process(argc, argv);
   
   // handle basic options:
   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
           << "craig@ccrma.stanford.edu, Jul 2012" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 29 Jul 2012" << endl;
      cout << "compiled: " << __DATE__ << 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");
   debugQ     =  opts.getBoolean("debug");
   invisibleQ = !opts.getBoolean("no-invisible");
   stemQ      = !opts.getBoolean("no-stems");
   plainQ     =  opts.getBoolean("plain");
   lyricsQ    = !opts.getBoolean("no-lyrics");
   htmlQ = !plainQ;
}



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



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

void printTitle(ScorePageSet& work, HumdrumFile& newfile) {
   Array<char> title;
   work.getTitle(title);
   getPrettyScoreText(title);
   PerlRegularExpression pre;
   char buffer[1024] = {0};
   if (title.getSize() > 1) {
      strcpy(buffer, "!!!OTL: ");
      pre.sar(title, "_\\d\\d", "", "g");
      strcat(buffer, title.getBase());
      newfile.appendLine(buffer);
   }
}



//////////////////////////////
//
// printComposer --
//

void printComposer(ScorePageSet& work, HumdrumFile& newfile) {
   Array<char> composer;

   work.getComposer(composer);
   PerlRegularExpression pre;
   pre.sar(composer, "^\\s+", "");
   pre.sar(composer, "\\s+$", "");
   pre.sar(composer, "\\s+", " ");
   int spacecount = 0;
   int commacount = 0;
   int i;
   for (i=0; i<composer.getSize(); i++) {
      if (composer[i] == ' ') {
         spacecount++;
      } else if (composer[i] == ',') {
         commacount++;
      }
   }
   if ((spacecount == 1) && (commacount == 0)) {
      pre.search(composer, "(.*) (.*)");
      composer.increase();
      strcpy(composer.getBase(), pre.getSubmatch(2));
      strcat(composer.getBase(), ", ");
      strcat(composer.getBase(), pre.getSubmatch(1));
   }

   char buffer[1024] = {0};
   if (composer.getSize() > 1) {
      pre.sar(composer, "_\\d\\d", "", "g");
      if (pre.search(composer, "\\?\\s*$")) {
         strcpy(buffer, "!!!COA: ");
         pre.sar(composer, "\\?\\s*$", "");
      } else { 
         strcpy(buffer, "!!!COM: ");
      }
      if (strcmp(composer.getBase(), "Guillaume Du Fay") == 0) {
         strcat(buffer, "Du Fay, Guillaume");
      } else {
         strcat(buffer, composer.getBase());
      }
      newfile.appendLine(buffer);

      if (pre.search(composer, "Du\\s*Fay", "i")) {
         newfile.appendLine("!!!CDT: ~1397/08/05/-1474/11/27/");
      }
   }
}



//////////////////////////////
//
// printInstruments --
//

void printInstruments(ScorePageSet& work, HumdrumFile& newfile, 
      HumdrumRecord& instruments, Array<int>& kernstart) {
   int staffcount = work.getSystemStaffCount();

   instruments.setAllFields("*");

   char buffer[1024] = {0};
   Array<Array<char> > instrumentnames(staffcount);
   Array<Array<char> > abbreviations(staffcount);
   Array<Array<char> > instrumentcodes(staffcount);

   int instrumentsQ = 0;
  
   int i;
   for (i=0; i<staffcount; i++) {
      instrumentnames[i].setSize(1); instrumentnames[i][0] = '\0';
      work.getInstrumentName(i, instrumentnames[i]);
      if (instrumentnames[i].getSize() > 1) {
         instrumentsQ = 1;
      }

      abbreviations[i].setSize(1);   abbreviations[i][0]   = '\0';
      getInstrumentAbbreviation(abbreviations[i], instrumentnames[i]);

      instrumentcodes[i].setSize(1); instrumentcodes[i][0] = '\0';
      getInstrumentCode(instrumentcodes[i], instrumentnames[i]);
   }

   if (!instrumentsQ) {
      return;
   }

   for (i=0; i<instrumentnames.getSize(); i++) {
      if (instrumentnames[i].getSize() > 1) {
         strcpy(buffer, "*I\"");
         strcat(buffer, instrumentnames[i].getBase());
         instruments.changeField(kernstart[i], buffer);
      } else {
         instruments.changeField(kernstart[i], "*");
      }
   }
   newfile.appendLine(instruments);

   for (i=0; i<abbreviations.getSize(); i++) {
      if (abbreviations[i].getSize() > 1) {
         strcpy(buffer, abbreviations[i].getBase());
         instruments.changeField(kernstart[i], buffer);
      } else {
         instruments.changeField(kernstart[i], "*");
      }
   }
   newfile.appendLine(instruments);
}



//////////////////////////////
//
// getInstrumentAbbreviation --
//

void getInstrumentAbbreviation(Array& code, Array& name) {
   code.setSize(1);
   code[0] = '\0';
   char buffer[1024] = {0};
   char buffer2[1024] = {0};
   PerlRegularExpression pre;

   if (pre.search(name, "^\\s$")) {
      return;
   } else if (pre.search(name, "^Superius", "i")) {
      strcpy(buffer, "*I'S");
   } else if (pre.search(name, "^Soprano", "i")) {
      strcpy(buffer, "*I'S");
   } else if (pre.search(name, "^Discantus", "i")) {
      strcpy(buffer, "*I'D");
   } else if (pre.search(name, "^Altus", "i")) {
      strcpy(buffer, "*I'A");
   } else if (pre.search(name, "^Alto", "i")) {
      strcpy(buffer, "*I'A");
   } else if (pre.search(name, "^Tenor", "i")) {
      strcpy(buffer, "*I'T");
   } else if (pre.search(name, "^Tenore", "i")) {
      strcpy(buffer, "*I'T");
   } else if (pre.search(name, "^Contratenor", "i")) {
      strcpy(buffer, "*I'Ct");
   } else if (pre.search(name, "^Contra", "i")) {
      strcpy(buffer, "*I'C");
   } else if (pre.search(name, "^Bassus", "i")) {
      strcpy(buffer, "*I'B");
   } else if (pre.search(name, "^Bass", "i")) {
      strcpy(buffer, "*I'B");
   } else if (pre.search(name, "^Vagans", "i")) {
      strcpy(buffer, "*I'V");
   } else if (pre.search(name, "^Cantus", "i")) {
      strcpy(buffer, "*I'Ca");
   } else {
      // unknown instrument, put in a dummy
      strcpy(buffer, "*I'XXX");
   }
   
   if (pre.search(name, "(\\d+)")) {
      strcpy(buffer2, pre.getSubmatch(1));
      strcat(buffer, buffer2);
   } 

   int len = strlen(buffer);
   code.setSize(len+1);
   strcpy(code.getBase(), buffer);
}



//////////////////////////////
//
// getInstrumentCode --
//

void getInstrumentCode(Array& code, Array& name) {


}



//////////////////////////////
//
// getPrettyScoreText -- returns the number of charcters printed
//

int getPrettyScoreText(Array& textdata, ScoreRecord& arecord) {
   arecord.getTextDataWithoutFonts(textdata);
   return getPrettyScoreText(textdata);
}



//////////////////////////////
//
// getPrettyScoreText -- returns the number of charcters printed
//

int getPrettyScoreText(Array& textdata) {
   if (plainQ) {
      ScoreRecord::convertScoreTextToPlainText(textdata);
   } else if (htmlQ) {
      ScoreRecord::convertScoreTextToHtmlText(textdata);
   } 
   return strlen(textdata.getBase());
}



//////////////////////////////
//
// setStaffAssignmentRecord --
//

void setStaffAssignmentRecord(HumdrumRecord& staffassignment, Array<int>& kernstart, 
      Array<int>& kerncount, Array<int>& dynamstart, Array<int>& dynamcount, 
      Array<int>& versestart, Array<int>& versecount) {
   int current = 0;
   int i, j;
   int staffnum;
   char buffer[1024] = {0};
   for (i=0; i<kernstart.getSize(); i++) {
      staffnum = kernstart.getSize() - i;
      sprintf(buffer, "*staff%d", staffnum);
      for (j=0; j<kerncount[i]; j++) {
         staffassignment.changeField(current++, buffer);
      }
      for (j=0; j<dynamcount[i]; j++) {
         staffassignment.changeField(current++, buffer);
      }
      for (j=0; j<versecount[i]; j++) {
         staffassignment.changeField(current++, buffer);
      }
   }
}



//////////////////////////////
//
// setHeaderRecord -- assign exclusive interpretations for each column.
//

void setHeaderRecord(HumdrumRecord& header, Array<int>& kernstart, 
      Array<int>& kerncount, Array<int>& dynamstart, Array<int>& dynamcount, 
      Array<int>& versestart, Array<int>& versecount) {
   int current = 0;
   int i, j;
   for (i=0; i<kernstart.getSize(); i++) {
      for (j=0; j<kerncount[i]; j++) {
         header.changeField(current++, "**kern");
      }
      for (j=0; j<dynamcount[i]; j++) {
         header.changeField(current++, "**dynam");
      }
      for (j=0; j<versecount[i]; j++) {
         header.changeField(current++, "**text");
      }
   }
}



//////////////////////////////
//
// extractLyrics --
//

void extractLyrics(ScorePageSet& work, int item, HumdrumRecord& dataline, 
      Array<int>& versestart, Array<int>& versecount) {

   PerlRegularExpression pre;

   int sysstaffidx = work.getSysStaffIndex(item);
   int j;
   Array<char> buffer;
   Array<int> verses(versecount[sysstaffidx]);
   verses.setAll(-1);
   getNoteVerseLinks(verses, work, item);
   for (j=0; j<verses.getSize(); j++) {
      if (verses[j] < 0) {
         dataline.changeField(versestart[sysstaffidx]+j, ".");
         continue;
      }
      work.getItem(verses[j]).getTextDataWithoutFonts(buffer);
      ScoreRecord::convertScoreTextToHtmlText(buffer);
      if (strlen(buffer.getBase()) == 0) {
         dataline.changeField(versestart[sysstaffidx]+j, ".");
      } else {
         // need to handle cases where text starts with Humdrum control 
         // charcters: * or !
         if (!work.getItem(verses[j]).isWordStart()) {
            pre.sar(buffer, "^", "-");
         }
         if (!work.getItem(verses[j]).isWordEnd()) {
            pre.sar(buffer, "$", "-");
         }
         dataline.changeField(versestart[sysstaffidx]+j, buffer.getBase());
      }
   }
}



//////////////////////////////
//
// getNoteVerseLinks --
//

void getNoteVerseLinks(Array& verses, ScorePageSet& work, int witem) {

   verses.setAll(-1);
   int pageidx          = work.getItemPage(witem);
   ScorePage& page      = work.getPage(pageidx);
   int pitem            = work.getItemPageIndex(witem);

   double hpos = page.getItem(pitem).getHpos();
   double nextnotehpos = page.getNextNotePositionOnStaff(pitem);
   double prevnotehpos = page.getPreviousNotePositionOnStaff(pitem);
   double nextcut = (hpos + nextnotehpos) / 2.0;
   double prevcut = (hpos + prevnotehpos) / 2.0;

   // Collect all verse syllables within 1/2 of the distance to the 
   // next/previous note/rest.  Only taking the closest syllable from 
   // each verse, however (not all of them).
 
   int versenum;
   int sysidx    = page.getSystemIndex(pitem);
   int staffidx  = page.getSystemStaffIndex(pitem);
   int ssitem    = page.getSystemStaffItemIndex(pitem);
   double thpos;
   ScoreRecord* srecord;
   int i;
   int pstaff   = (int)page.getItem(pitem).getValue(P2);
   int tstaff;

   Array<int> backwarditem(verses.getSize());
   backwarditem.setAll(-1);
   Array<double> backwardhpos(verses.getSize());
   backwardhpos.setAll(-1.0);

   // search for lyric syllables to the left of a note.
   for (i=ssitem-1; i>=0; i--) {
      srecord = &(page.getSysStaffItem(sysidx, staffidx, i));

      // check that the staff number has not changed; exit loop otherwise.
      tstaff = (int)srecord->getValue(P2);
      if (tstaff != pstaff) {
         break;
      }

      // get horizontal position of item.
      thpos = srecord->getHpos();

      // if it is less than the cutoff horizontal position, exit the loop.
      if (thpos < prevcut) {
         break;
      }

      // ignore item if it is not a lyric syllable.
      if (!srecord->isVerse()) {
         continue;
      }

      // if there is an undefined verse number for some strange reason skip it:
      versenum = srecord->getVerseIndex();
      if (versenum < 0) {
         continue;
      }

      // if the verse number is larger than expectd, skip over it as well:
      if (versenum > verses.getSize()-1) {
         // strange error
         continue;
      }

      // only keep track of the closest syllable of each verse
      if (backwarditem[versenum] < 0) {
         backwarditem[versenum] = srecord->getSetIndex();
         backwardhpos[versenum] = thpos;
      } 
   }


   Array<int> forwarditem(verses.getSize());
   forwarditem.setAll(-1);
   Array<double> forwardhpos(verses.getSize());
   forwardhpos.setAll(-1.0);

   // search for lyric syllables to the right of a note.
   for (i=ssitem+1; i<page.getSysStaffItemCount(sysidx, staffidx); i++) {
      srecord = &(page.getSysStaffItem(sysidx, staffidx, i));

      // check that the staff number has not changed; exit otherwise.
      tstaff = (int)srecord->getValue(P2);
      if (tstaff != pstaff) {
         break;
      }

      // get horizontal position of item.
      thpos = srecord->getHpos();

      // if it is greater than the cutoff horizontal position, exit the loop.
      if (thpos > nextcut) {
         break;
      }

      // ignore item if it is not a lyric syllable.
      if (!srecord->isVerse()) {
         continue;
      }

      // if there is an undefined verse number for some strange reason skip it:
      versenum = srecord->getVerseIndex();
      if (versenum < 0) {
         continue;
      }

      // if the verse number is larger than expectd, skip over it as well:
      if (versenum > verses.getSize()-1) {
         // strange error: verse number larger than expected.
         continue;
      }

      // only keep track of the closest syllable of each verse
      if (forwarditem[versenum] < 0) {
         forwarditem[versenum] = srecord->getSetIndex();
         forwardhpos[versenum] = thpos;
      } 
   }

   double backdist;
   double foredist;
   for (i=0; i<verses.getSize(); i++) {
      if ((backwarditem[i] < 0) && (forwarditem[i] > 0)) {
         verses[i] = forwarditem[i];
         continue;
      } else if ((backwarditem[i] > 0) && (forwarditem[i] < 0)) {
         verses[i] = backwarditem[i];
         continue;
      } else if ((backwarditem[i] < 0) && (forwarditem[i] < 0)) {
         continue;
      } 
      backdist = fabs(backwardhpos[i] - hpos);
      foredist = fabs(forwardhpos[i] - hpos);
      if (backdist < foredist) {
         verses[i] = backwarditem[i];
      } else {
         verses[i] = forwarditem[i];
      }
   }

}



// md5sum: 513e0ca6f0325103ff902791bc811bbe scr2hum.cpp [20130206]